snappymail/dev/Knoin/Knoin.js

371 lines
9.4 KiB
JavaScript
Raw Normal View History

2016-07-08 07:22:58 +08:00
import ko from 'ko';
2021-02-15 23:05:38 +08:00
import { doc, $htmlCL } from 'Common/Globals';
import { isNonEmptyArray } from 'Common/Utils';
2016-07-08 07:22:58 +08:00
2019-07-05 03:19:24 +08:00
let currentScreen = null,
defaultScreenName = '',
popupVisibilityNames = [];
2016-07-08 07:22:58 +08:00
2020-08-27 21:45:47 +08:00
const SCREENS = {},
autofocus = dom => {
const af = dom.querySelector('[autofocus]');
af && af.focus();
};
2020-08-24 03:51:26 +08:00
export const popupVisibility = ko.computed(() => 0 < popupVisibilityNames.length);
2016-07-08 07:22:58 +08:00
export const ViewType = {
Popup: 'Popups',
Left: 'Left',
Right: 'Right',
Center: 'Center'
};
2016-09-10 06:38:16 +08:00
/**
* @param {Function} fExecute
* @param {(Function|boolean|null)=} fCanExecute = true
* @returns {Function}
*/
2019-07-05 03:19:24 +08:00
export function createCommand(fExecute, fCanExecute = true) {
let fResult = null;
fResult = fExecute
? (...args) => {
if (fResult && fResult.canExecute && fResult.canExecute()) {
fExecute.apply(null, args);
}
return false;
} : ()=>{};
fResult.enabled = ko.observable(true);
fResult.isCommand = true;
if (typeof fCanExecute === 'function') {
fResult.canExecute = ko.computed(() => fResult && fResult.enabled() && fCanExecute.call(null));
} else {
fResult.canExecute = ko.computed(() => fResult && fResult.enabled() && !!fCanExecute);
}
return fResult;
2016-09-10 06:38:16 +08:00
}
2016-07-08 07:22:58 +08:00
/**
* @param {string} screenName
* @returns {?Object}
*/
function screen(screenName) {
return screenName && null != SCREENS[screenName] ? SCREENS[screenName] : null;
2016-07-08 07:22:58 +08:00
}
/**
* @param {Function} ViewModelClassToHide
* @returns {void}
*/
2019-07-05 03:19:24 +08:00
export function hideScreenPopup(ViewModelClassToHide) {
2021-01-26 18:46:30 +08:00
if (ViewModelClassToHide && ViewModelClassToHide.__vm && ViewModelClassToHide.__dom) {
ViewModelClassToHide.__vm.modalVisibility(false);
2016-07-08 07:22:58 +08:00
}
}
/**
* @param {Function} ViewModelClass
* @param {Object=} vmScreen
* @returns {*}
*/
2020-08-31 22:33:40 +08:00
function buildViewModel(ViewModelClass, vmScreen) {
2019-07-05 03:19:24 +08:00
if (ViewModelClass && !ViewModelClass.__builded) {
2016-07-08 07:22:58 +08:00
let vmDom = null;
2019-07-05 03:19:24 +08:00
const vm = new ViewModelClass(vmScreen),
position = vm.viewModelPosition || '',
vmPlace = position ? doc.querySelector('#rl-content #rl-' + position.toLowerCase()) : null;
2016-07-08 07:22:58 +08:00
ViewModelClass.__builded = true;
ViewModelClass.__vm = vm;
2020-08-27 21:45:47 +08:00
if (vmPlace) {
vmDom = Element.fromHTML('<div class="rl-view-model RL-' + vm.viewModelTemplateID + '" hidden=""></div>');
vmPlace.append(vmDom);
2016-07-08 07:22:58 +08:00
vm.viewModelDom = vmDom;
ViewModelClass.__dom = vmDom;
2016-07-08 07:22:58 +08:00
2019-07-05 03:19:24 +08:00
if (ViewType.Popup === position) {
vm.cancelCommand = vm.closeCommand = createCommand(() => {
2016-07-08 07:22:58 +08:00
hideScreenPopup(ViewModelClass);
});
// show/hide popup/modal
const endShowHide = e => {
if (e.target === vmDom) {
if (vmDom.classList.contains('show')) {
autofocus(vmDom);
vm.onShowWithDelay && vm.onShowWithDelay();
} else {
vmDom.hidden = true;
vm.onHideWithDelay && vm.onHideWithDelay();
}
}
};
vm.modalVisibility.subscribe(value => {
2019-07-05 03:19:24 +08:00
if (value) {
vmDom.style.zIndex = 3000 + popupVisibilityNames.length + 10;
vmDom.hidden = false;
2016-07-08 07:22:58 +08:00
vm.storeAndSetKeyScope();
popupVisibilityNames.push(vm.viewModelName);
requestAnimationFrame(() => { // wait just before the next paint
vmDom.offsetHeight; // force a reflow
vmDom.classList.add('show'); // trigger the transitions
});
2019-07-05 03:19:24 +08:00
} else {
vm.onHide && vm.onHide();
vmDom.classList.remove('show');
2016-07-08 07:22:58 +08:00
vm.restoreKeyScope();
popupVisibilityNames = popupVisibilityNames.filter(v=>v!==vm.viewModelName);
2016-07-08 07:22:58 +08:00
}
vmDom.setAttribute('aria-hidden', !value);
2016-07-08 07:22:58 +08:00
});
if ('ontransitionend' in vmDom) {
vmDom.addEventListener('transitionend', endShowHide);
} else {
// For Edge < 79 and mobile browsers
vm.modalVisibility.subscribe(() => ()=>setTimeout(endShowHide({target:vmDom}), 500));
}
2016-07-08 07:22:58 +08:00
}
2019-07-05 03:19:24 +08:00
ko.applyBindingAccessorsToNode(
vmDom,
2019-07-05 03:19:24 +08:00
{
i18nInit: true,
2019-07-05 03:19:24 +08:00
template: () => ({ name: vm.viewModelTemplateID })
},
vm
);
2016-07-08 07:22:58 +08:00
vm.onBuild && vm.onBuild(vmDom);
2019-07-05 03:19:24 +08:00
if (vm && ViewType.Popup === position) {
2016-07-08 07:22:58 +08:00
vm.registerPopupKeyDown();
}
2019-07-05 03:19:24 +08:00
} else {
console.log('Cannot find view model position: ' + position);
2016-07-08 07:22:58 +08:00
}
}
2021-01-25 05:58:06 +08:00
return ViewModelClass && ViewModelClass.__vm;
2016-07-08 07:22:58 +08:00
}
2021-01-22 23:32:08 +08:00
function getScreenPopupViewModel(ViewModelClassToShow) {
2021-01-26 18:46:30 +08:00
return (buildViewModel(ViewModelClassToShow) && ViewModelClassToShow.__dom) && ViewModelClassToShow.__vm;
2021-01-22 23:32:08 +08:00
}
2016-07-08 07:22:58 +08:00
/**
* @param {Function} ViewModelClassToShow
* @param {Array=} params
* @returns {void}
*/
2019-07-05 03:19:24 +08:00
export function showScreenPopup(ViewModelClassToShow, params = []) {
2021-01-22 23:32:08 +08:00
const vm = getScreenPopupViewModel(ViewModelClassToShow);
if (vm) {
params = params || [];
2021-01-22 23:32:08 +08:00
vm.onBeforeShow && vm.onBeforeShow(...params);
2016-07-08 07:22:58 +08:00
2021-01-22 23:32:08 +08:00
vm.modalVisibility(true);
2016-07-08 07:22:58 +08:00
2021-01-22 23:32:08 +08:00
vm.onShow && vm.onShow(...params);
2016-07-08 07:22:58 +08:00
}
}
/**
* @param {Function} ViewModelClassToShow
* @returns {void}
*/
2019-07-05 03:19:24 +08:00
export function warmUpScreenPopup(ViewModelClassToShow) {
2021-01-22 23:32:08 +08:00
const vm = getScreenPopupViewModel(ViewModelClassToShow);
vm && vm.onWarmUp && vm.onWarmUp();
}
2016-07-08 07:22:58 +08:00
/**
* @param {Function} ViewModelClassToShow
* @returns {boolean}
*/
2019-07-05 03:19:24 +08:00
export function isPopupVisible(ViewModelClassToShow) {
2021-01-26 18:46:30 +08:00
return ViewModelClassToShow && ViewModelClassToShow.__vm && ViewModelClassToShow.__vm.modalVisibility();
2016-07-08 07:22:58 +08:00
}
/**
* @param {string} screenName
* @param {string} subPart
* @returns {void}
*/
2020-08-31 22:33:40 +08:00
function screenOnRoute(screenName, subPart) {
2019-07-05 03:19:24 +08:00
let vmScreen = null,
2020-10-02 18:40:33 +08:00
isSameScreen = false;
2016-07-08 07:22:58 +08:00
if (null == screenName || '' == screenName) {
2016-07-08 07:22:58 +08:00
screenName = defaultScreenName;
}
if (screenName) {
2016-07-08 07:22:58 +08:00
vmScreen = screen(screenName);
2019-07-05 03:19:24 +08:00
if (!vmScreen) {
2016-07-08 07:22:58 +08:00
vmScreen = screen(defaultScreenName);
2019-07-05 03:19:24 +08:00
if (vmScreen) {
2016-07-08 07:22:58 +08:00
subPart = screenName + '/' + subPart;
screenName = defaultScreenName;
}
}
2019-07-05 03:19:24 +08:00
if (vmScreen && vmScreen.__started) {
2016-07-08 07:22:58 +08:00
isSameScreen = currentScreen && vmScreen === currentScreen;
2019-07-05 03:19:24 +08:00
if (!vmScreen.__builded) {
2016-07-08 07:22:58 +08:00
vmScreen.__builded = true;
vmScreen.viewModels.forEach(ViewModelClass =>
buildViewModel(ViewModelClass, vmScreen)
);
2016-07-08 07:22:58 +08:00
vmScreen.onBuild && vmScreen.onBuild();
2016-07-08 07:22:58 +08:00
}
setTimeout(() => {
2016-07-08 07:22:58 +08:00
// hide screen
2019-07-05 03:19:24 +08:00
if (currentScreen && !isSameScreen) {
currentScreen.onHide && currentScreen.onHide();
currentScreen.onHideWithDelay && setTimeout(()=>currentScreen.onHideWithDelay(), 500);
2016-07-08 07:22:58 +08:00
if (isNonEmptyArray(currentScreen.viewModels)) {
currentScreen.viewModels.forEach(ViewModelClass => {
2019-07-05 03:19:24 +08:00
if (
ViewModelClass.__vm &&
ViewModelClass.__dom &&
ViewType.Popup !== ViewModelClass.__vm.viewModelPosition
) {
2020-08-27 21:45:47 +08:00
ViewModelClass.__dom.hidden = true;
2020-10-02 18:40:33 +08:00
ViewModelClass.__vm.viewModelVisible = false;
2016-07-08 07:22:58 +08:00
ViewModelClass.__vm.onHide && ViewModelClass.__vm.onHide();
ViewModelClass.__vm.onHideWithDelay && setTimeout(()=>ViewModelClass.__vm.onHideWithDelay(), 500);
2016-07-08 07:22:58 +08:00
}
});
}
}
// --
currentScreen = vmScreen;
// show screen
2019-07-05 03:19:24 +08:00
if (currentScreen && !isSameScreen) {
currentScreen.onShow && currentScreen.onShow();
2016-07-08 07:22:58 +08:00
if (isNonEmptyArray(currentScreen.viewModels)) {
currentScreen.viewModels.forEach(ViewModelClass => {
2019-07-05 03:19:24 +08:00
if (
ViewModelClass.__vm &&
ViewModelClass.__dom &&
ViewType.Popup !== ViewModelClass.__vm.viewModelPosition
) {
ViewModelClass.__vm.onBeforeShow && ViewModelClass.__vm.onBeforeShow();
2016-07-08 07:22:58 +08:00
2020-08-27 21:45:47 +08:00
ViewModelClass.__dom.hidden = false;
2020-10-02 18:40:33 +08:00
ViewModelClass.__vm.viewModelVisible = true;
2016-07-08 07:22:58 +08:00
ViewModelClass.__vm.onShow && ViewModelClass.__vm.onShow();
2016-07-08 07:22:58 +08:00
autofocus(ViewModelClass.__dom);
ViewModelClass.__vm.onShowWithDelay && setTimeout(()=>ViewModelClass.__vm.onShowWithDelay, 200);
2016-07-08 07:22:58 +08:00
}
});
}
}
// --
2020-10-02 18:40:33 +08:00
vmScreen && vmScreen.__cross && vmScreen.__cross.parse(subPart);
}, 1);
2016-07-08 07:22:58 +08:00
}
}
}
/**
* @param {Array} screensClasses
* @returns {void}
*/
2019-07-05 03:19:24 +08:00
export function startScreens(screensClasses) {
screensClasses.forEach(CScreen => {
2019-07-05 03:19:24 +08:00
if (CScreen) {
const vmScreen = new CScreen(),
2021-01-25 05:58:06 +08:00
screenName = vmScreen && vmScreen.screenName();
2016-07-08 07:22:58 +08:00
if (screenName) {
defaultScreenName || (defaultScreenName = screenName);
2016-07-08 07:22:58 +08:00
SCREENS[screenName] = vmScreen;
}
}
});
Object.values(SCREENS).forEach(vmScreen =>
vmScreen && vmScreen.onStart && vmScreen.onStart()
);
2016-07-08 07:22:58 +08:00
const cross = new Crossroads();
2019-07-05 03:19:24 +08:00
cross.addRoute(/^([a-zA-Z0-9-]*)\/?(.*)$/, screenOnRoute);
2016-07-08 07:22:58 +08:00
hasher.changed.add(cross.parse.bind(cross));
2016-07-08 07:22:58 +08:00
hasher.init();
setTimeout(() => $htmlCL.remove('rl-started-trigger'), 100);
setTimeout(() => $htmlCL.add('rl-started-delay'), 200);
2016-07-08 07:22:58 +08:00
}
function decorateKoCommands(thisArg, commands) {
Object.entries(commands).forEach(([key, canExecute]) => {
let command = thisArg[key],
fn = function(...args) {
if (fn.enabled() && canExecute.call(thisArg, thisArg)) {
command.apply(thisArg, args);
}
return false;
};
// fn.__realCanExecute = canExecute;
// fn.isCommand = true;
fn.enabled = ko.observable(true);
fn.canExecute = (typeof canExecute === 'function')
? ko.computed(() => fn.enabled() && canExecute.call(thisArg, thisArg))
: ko.computed(() => fn.enabled() && !!canExecute);
thisArg[key] = fn;
});
}
2017-07-11 20:40:31 +08:00
/**
* @param {miced} $items
* @returns {Function}
*/
function settingsMenuKeysHandler(items) {
return ((event, handler)=>{
let index = items.length;
if (event && index) {
while (index-- && !items[index].matches('.selected'));
if (handler && 'arrowup' === handler.shortcut) {
index && --index;
} else if (index < items.length - 1) {
++index;
}
2017-07-11 20:40:31 +08:00
const resultHash = items[index].href;
resultHash && rl.route.setHash(resultHash, false, true);
2017-07-11 20:40:31 +08:00
}
}).throttle(200);
2017-07-11 20:40:31 +08:00
}
2016-09-10 06:38:16 +08:00
export {
decorateKoCommands,
2019-03-30 06:45:12 +08:00
settingsMenuKeysHandler
2016-09-10 06:38:16 +08:00
};