snappymail/dev/Knoin/Knoin.js

296 lines
7.3 KiB
JavaScript
Raw Normal View History

2016-07-08 07:22:58 +08:00
import ko from 'ko';
import { koComputable } from 'External/ko';
import { doc, $htmlCL, elementById, createElement, fireEvent } from 'Common/Globals';
2022-11-22 18:00:09 +08:00
import { forEachObjectEntry } from 'Common/Utils';
import { i18nToNodes } from 'Common/Translator';
2016-07-08 07:22:58 +08:00
let
currentScreen = null,
defaultScreenName = '';
2016-07-08 07:22:58 +08:00
const
2022-11-22 18:00:09 +08:00
SCREENS = new Map,
2022-09-02 17:52:07 +08:00
autofocus = dom => dom.querySelector('[autofocus]')?.focus(),
2021-08-20 21:40:07 +08:00
visiblePopups = new Set,
2021-08-20 21:40:07 +08:00
/**
* @param {string} screenName
* @returns {?Object}
*/
2022-11-22 18:00:09 +08:00
screen = screenName => (screenName && SCREENS.get(screenName)) || null,
2021-08-20 21:40:07 +08:00
/**
* @param {Function} ViewModelClass
* @param {Object=} vmScreen
* @returns {*}
*/
buildViewModel = (ViewModelClass, vmScreen) => {
if (ViewModelClass && !ViewModelClass.__builded) {
let vmDom = null;
const
vm = new ViewModelClass(vmScreen),
id = vm.viewModelTemplateID,
2022-10-10 19:52:56 +08:00
position = 'rl-' + vm.viewType,
dialog = ViewTypePopup === vm.viewType,
vmPlace = doc.getElementById(position);
2021-08-20 21:40:07 +08:00
ViewModelClass.__builded = true;
ViewModelClass.__vm = vm;
if (vmPlace) {
vmDom = dialog
? createElement('dialog',{id:'V-'+id})
: createElement('div',{id:'V-'+id,hidden:''})
2021-08-20 21:40:07 +08:00
vmPlace.append(vmDom);
2022-10-10 19:52:56 +08:00
vm.viewModelDom = ViewModelClass.__dom = vmDom;
2021-08-20 21:40:07 +08:00
2022-10-10 19:52:56 +08:00
if (dialog) {
// Firefox < 98 / Safari < 15.4 HTMLDialogElement not defined
if (!vmDom.showModal) {
2022-11-16 22:55:16 +08:00
vmDom.className = 'polyfill';
vmDom.showModal = () => {
2022-03-04 02:22:17 +08:00
vmDom.backdrop ||
vmDom.before(vmDom.backdrop = createElement('div',{class:'dialog-backdrop'}));
vmDom.setAttribute('open','');
vmDom.open = true;
vmDom.returnValue = null;
vmDom.backdrop.hidden = false;
};
vmDom.close = v => {
// if (vmDom.dispatchEvent(new CustomEvent('cancel', {cancelable:true}))) {
vmDom.backdrop.hidden = true;
vmDom.returnValue = v;
vmDom.removeAttribute('open', null);
vmDom.open = false;
2022-09-08 16:07:27 +08:00
// vmDom.dispatchEvent(new CustomEvent('close'));
// }
};
}
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/cancel_event
// vmDom.addEventListener('cancel', event => (false === vm.onClose() && event.preventDefault()));
// vmDom.addEventListener('close', () => vm.modalVisible(false));
2021-08-20 21:40:07 +08:00
// show/hide popup/modal
const endShowHide = e => {
if (e.target === vmDom) {
if (vmDom.classList.contains('animate')) {
2022-09-02 17:52:07 +08:00
vm.afterShow?.();
2021-08-20 21:40:07 +08:00
} else {
vmDom.close();
2022-09-02 17:52:07 +08:00
vm.afterHide?.();
2021-08-20 21:40:07 +08:00
}
}
};
2022-02-26 08:06:18 +08:00
vm.modalVisible.subscribe(value => {
2021-08-20 21:40:07 +08:00
if (value) {
i18nToNodes(vmDom);
visiblePopups.add(vm);
vmDom.style.zIndex = 3001 + (visiblePopups.size * 2);
vmDom.showModal();
if (vmDom.backdrop) {
vmDom.backdrop.style.zIndex = 3000 + (visiblePopups.size * 2);
}
vm.keyScope.set();
setTimeout(()=>autofocus(vmDom),1);
2021-08-20 21:40:07 +08:00
requestAnimationFrame(() => { // wait just before the next paint
vmDom.offsetHeight; // force a reflow
vmDom.classList.add('animate'); // trigger the transitions
2021-08-20 21:40:07 +08:00
});
} else {
visiblePopups.delete(vm);
2022-09-02 17:52:07 +08:00
vm.onHide?.();
vm.keyScope.unset();
vmDom.classList.remove('animate'); // trigger the transitions
}
arePopupsVisible(0 < visiblePopups.size);
2021-08-20 21:40:07 +08:00
});
vmDom.addEventListener('transitionend', endShowHide);
}
2016-07-08 07:22:58 +08:00
2022-11-15 20:37:25 +08:00
fireEvent('rl-view-model.create', vm);
2021-08-20 21:40:07 +08:00
ko.applyBindingAccessorsToNode(
vmDom,
{
template: () => ({ name: id })
2021-08-20 21:40:07 +08:00
},
vm
);
2021-05-31 22:19:01 +08:00
2022-09-02 17:52:07 +08:00
vm.onBuild?.(vmDom);
2021-08-20 21:40:07 +08:00
fireEvent('rl-view-model', vm);
2021-08-20 21:40:07 +08:00
} else {
console.log('Cannot find view model position: ' + position);
2016-07-08 07:22:58 +08:00
}
}
2022-11-22 18:00:09 +08:00
return ViewModelClass?.__vm;
2021-08-20 21:40:07 +08:00
},
2016-07-08 07:22:58 +08:00
forEachViewModel = (screen, fn) => {
screen.viewModels.forEach(ViewModelClass => {
if (
ViewModelClass.__vm &&
ViewModelClass.__dom &&
2022-03-08 19:28:16 +08:00
ViewTypePopup !== ViewModelClass.__vm.viewType
) {
fn(ViewModelClass.__vm, ViewModelClass.__dom);
}
});
},
hideScreen = (screenToHide, destroy) => {
2022-09-02 17:52:07 +08:00
screenToHide.onHide?.();
forEachViewModel(screenToHide, (vm, dom) => {
dom.hidden = true;
2022-09-02 17:52:07 +08:00
vm.onHide?.();
destroy && vm.viewModelDom.remove();
});
},
2021-08-20 21:40:07 +08:00
/**
* @param {string} screenName
* @param {string} subPart
* @returns {void}
*/
screenOnRoute = (screenName, subPart) => {
2023-02-12 03:41:54 +08:00
screenName = screenName || defaultScreenName;
if (screenName && fireEvent('sm-show-screen', screenName, 1)) {
// Close all popups
for (let vm of visiblePopups) {
(false === vm.onClose()) || vm.close();
2016-07-08 07:22:58 +08:00
}
2023-02-12 03:41:54 +08:00
let vmScreen = screen(screenName);
if (!vmScreen) {
vmScreen = screen(defaultScreenName);
if (vmScreen) {
subPart = screenName + '/' + subPart;
screenName = defaultScreenName;
}
2023-02-12 03:41:54 +08:00
}
2021-08-20 21:40:07 +08:00
2023-02-12 03:41:54 +08:00
if (vmScreen?.__started) {
let isSameScreen = currentScreen && vmScreen === currentScreen;
2021-08-20 21:40:07 +08:00
2023-02-12 03:41:54 +08:00
if (!vmScreen.__builded) {
vmScreen.__builded = true;
2021-08-20 21:40:07 +08:00
2023-02-12 03:41:54 +08:00
vmScreen.viewModels.forEach(ViewModelClass =>
buildViewModel(ViewModelClass, vmScreen)
);
2016-07-08 07:22:58 +08:00
2023-02-12 03:41:54 +08:00
vmScreen.onBuild?.();
}
2021-08-20 21:40:07 +08:00
2023-02-12 03:41:54 +08:00
setTimeout(() => {
// hide screen
currentScreen && !isSameScreen && hideScreen(currentScreen);
// --
2016-07-08 07:22:58 +08:00
2023-02-12 03:41:54 +08:00
currentScreen = vmScreen;
2023-02-12 03:41:54 +08:00
// show screen
if (!isSameScreen) {
vmScreen.onShow?.();
2021-08-20 21:40:07 +08:00
2023-02-12 03:41:54 +08:00
forEachViewModel(vmScreen, (vm, dom) => {
vm.beforeShow?.();
i18nToNodes(dom);
dom.hidden = false;
vm.onShow?.();
autofocus(dom);
});
}
// --
2023-02-12 03:41:54 +08:00
vmScreen.__cross?.parse(subPart);
}, 1);
2021-08-20 21:40:07 +08:00
}
}
};
2021-08-20 21:40:07 +08:00
export const
2022-10-10 19:52:56 +08:00
ViewTypePopup = 'popups',
2021-08-20 21:40:07 +08:00
/**
* @param {Function} ViewModelClassToShow
* @param {Array=} params
* @returns {void}
*/
showScreenPopup = (ViewModelClassToShow, params = []) => {
const vm = buildViewModel(ViewModelClassToShow) && ViewModelClassToShow.__dom && ViewModelClassToShow.__vm;
2021-08-20 21:40:07 +08:00
if (vm) {
params = params || [];
2022-09-02 17:52:07 +08:00
vm.beforeShow?.(...params);
2021-08-20 21:40:07 +08:00
2022-02-26 08:06:18 +08:00
vm.modalVisible(true);
2021-08-20 21:40:07 +08:00
2022-09-02 17:52:07 +08:00
vm.onShow?.(...params);
2021-08-20 21:40:07 +08:00
}
},
arePopupsVisible = ko.observable(false),
2021-08-20 21:40:07 +08:00
/**
* @param {Array} screensClasses
* @returns {void}
*/
startScreens = screensClasses => {
hasher.clear();
2022-11-22 18:00:09 +08:00
SCREENS.forEach(screen => hideScreen(screen, 1));
SCREENS.clear();
currentScreen = null,
defaultScreenName = '';
2021-08-20 21:40:07 +08:00
screensClasses.forEach(CScreen => {
2022-11-22 18:00:09 +08:00
const vmScreen = new CScreen(),
screenName = vmScreen.screenName;
defaultScreenName || (defaultScreenName = screenName);
SCREENS.set(screenName, vmScreen);
2021-08-20 21:40:07 +08:00
});
2022-11-22 18:00:09 +08:00
SCREENS.forEach(vmScreen => {
if (!vmScreen.__started) {
vmScreen.onStart();
vmScreen.__started = true;
}
});
2016-07-08 07:22:58 +08:00
2021-08-20 21:40:07 +08:00
const cross = new Crossroads();
2023-02-12 07:27:11 +08:00
cross.addRoute(/^([^/]*)\/?(.*)$/, screenOnRoute);
2016-07-08 07:22:58 +08:00
2021-08-20 21:40:07 +08:00
hasher.add(cross.parse.bind(cross));
hasher.init();
2016-07-08 07:22:58 +08:00
2021-08-20 21:40:07 +08:00
setTimeout(() => $htmlCL.remove('rl-started-trigger'), 100);
const c = elementById('rl-content'), l = elementById('rl-loading');
c && (c.hidden = false);
2022-09-02 17:52:07 +08:00
l?.remove();
2021-08-20 21:40:07 +08:00
},
2016-07-08 07:22:58 +08:00
2022-02-21 22:36:34 +08:00
/**
* Used by ko.bindingHandlers.command (template data-bind="command: ")
* to enable/disable click/submit action.
*/
2021-08-20 21:40:07 +08:00
decorateKoCommands = (thisArg, commands) =>
forEachObjectEntry(commands, (key, canExecute) => {
2021-08-20 21:40:07 +08:00
let command = thisArg[key],
fn = (...args) => fn.canExecute() && command.apply(thisArg, args);
fn.canExecute = koComputable(() => canExecute.call(thisArg, thisArg));
2021-08-20 21:40:07 +08:00
thisArg[key] = fn;
});
ko.decorateCommands = decorateKoCommands;