mirror of
https://github.com/the-djmaze/snappymail.git
synced 2024-11-13 02:55:24 +08:00
295 lines
7.6 KiB
JavaScript
295 lines
7.6 KiB
JavaScript
import ko from 'ko';
|
|
import { koComputable } from 'External/ko';
|
|
import { doc, $htmlCL, elementById, createElement, fireEvent } from 'Common/Globals';
|
|
import { forEachObjectEntry } from 'Common/Utils';
|
|
import { i18nToNodes } from 'Common/Translator';
|
|
|
|
import { leftPanelDisabled } from 'Common/Globals';
|
|
import { ThemeStore } from 'Stores/Theme';
|
|
|
|
let
|
|
currentScreen = null,
|
|
defaultScreenName = '';
|
|
|
|
const
|
|
SCREENS = new Map,
|
|
|
|
autofocus = dom => dom.querySelector('[autofocus]')?.focus(),
|
|
|
|
visiblePopups = new Set,
|
|
|
|
/**
|
|
* @param {string} screenName
|
|
* @returns {?Object}
|
|
*/
|
|
screen = screenName => (screenName && SCREENS.get(screenName)) || null,
|
|
|
|
/**
|
|
* Creates the extended AbstractView model
|
|
* @param {Function} ViewModelClass
|
|
* @param {Object=} vmScreen
|
|
* @returns {*}
|
|
*/
|
|
buildViewModel = (ViewModelClass, vmScreen) => {
|
|
if (ViewModelClass && !ViewModelClass.__vm) {
|
|
const
|
|
vm = new ViewModelClass(vmScreen),
|
|
id = vm.viewModelTemplateID,
|
|
position = 'rl-' + vm.viewType,
|
|
dialog = ViewTypePopup === vm.viewType,
|
|
vmPlace = doc.getElementById(position);
|
|
|
|
if (vmPlace) {
|
|
ViewModelClass.__vm = vm;
|
|
|
|
let vmDom = dialog
|
|
? createElement('dialog',{id:'V-'+id})
|
|
: createElement('div',{id:'V-'+id,hidden:''})
|
|
vmPlace.append(vmDom);
|
|
|
|
vm.viewModelDom = vmDom;
|
|
|
|
if (dialog) {
|
|
// Firefox < 98 / Safari < 15.4 HTMLDialogElement not defined
|
|
if (!vmDom.showModal) {
|
|
vmDom.className = 'polyfill';
|
|
vmDom.showModal = () => {
|
|
vmDom.backdrop ||
|
|
vmDom.before(vmDom.backdrop = createElement('div',{class:'dialog-backdrop'}));
|
|
vmDom.setAttribute('open','');
|
|
vmDom.open = true;
|
|
vmDom.backdrop.hidden = false;
|
|
};
|
|
vmDom.close = () => {
|
|
// if (vmDom.dispatchEvent(new CustomEvent('cancel', {cancelable:true}))) {
|
|
vmDom.backdrop.hidden = true;
|
|
vmDom.removeAttribute('open', null);
|
|
vmDom.open = false;
|
|
// 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));
|
|
|
|
// show/hide popup/modal
|
|
// transitionend is called for each property, so we only listen to `opacity`
|
|
// as defined in CSS by `dialog:not(.animate)`
|
|
const endShowHide = e => {
|
|
if (e.target === vmDom && 'opacity' === e.propertyName) {
|
|
if (vmDom.classList.contains('animate')) {
|
|
vm.afterShow?.();
|
|
fireEvent('rl-vm-visible', vm);
|
|
} else {
|
|
vmDom.close();
|
|
vm.afterHide?.();
|
|
// fireEvent('rl-vm-hidden', vm);
|
|
}
|
|
}
|
|
};
|
|
|
|
vm.modalVisible.subscribe(value => {
|
|
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);
|
|
requestAnimationFrame(() => { // wait just before the next paint
|
|
vmDom.offsetHeight; // force a reflow
|
|
vmDom.classList.add('animate'); // trigger the transitions
|
|
});
|
|
} else {
|
|
visiblePopups.delete(vm);
|
|
vm.onHide?.();
|
|
vm.keyScope.unset();
|
|
vmDom.classList.remove('animate'); // trigger the transitions
|
|
}
|
|
arePopupsVisible(0 < visiblePopups.size);
|
|
});
|
|
vmDom.addEventListener('transitionend', endShowHide);
|
|
}
|
|
|
|
fireEvent('rl-view-model.create', vm);
|
|
|
|
ko.applyBindingAccessorsToNode(
|
|
vmDom,
|
|
{
|
|
template: () => ({ name: id })
|
|
},
|
|
vm
|
|
);
|
|
|
|
vm.onBuild?.(vmDom);
|
|
|
|
fireEvent('rl-view-model', vm);
|
|
} else {
|
|
console.log('Cannot find view model position: ' + position);
|
|
}
|
|
}
|
|
|
|
return ViewModelClass?.__vm;
|
|
},
|
|
|
|
forEachViewModel = (screen, fn) => {
|
|
screen.viewModels.forEach(ViewModelClass => {
|
|
if (
|
|
ViewModelClass.__vm &&
|
|
ViewTypePopup !== ViewModelClass.__vm.viewType
|
|
) {
|
|
fn(ViewModelClass.__vm, ViewModelClass.__vm.viewModelDom);
|
|
}
|
|
});
|
|
},
|
|
|
|
hideScreen = (screenToHide, destroy) => {
|
|
screenToHide.onHide?.();
|
|
forEachViewModel(screenToHide, (vm, dom) => {
|
|
dom.hidden = true;
|
|
vm.onHide?.();
|
|
destroy && dom.remove();
|
|
});
|
|
ThemeStore.isMobile() && leftPanelDisabled(true);
|
|
},
|
|
|
|
/**
|
|
* @param {string} screenName
|
|
* @param {string} subPart
|
|
* @returns {void}
|
|
*/
|
|
screenOnRoute = (screenName, subPart) => {
|
|
screenName = screenName || defaultScreenName;
|
|
if (screenName && fireEvent('sm-show-screen', screenName + (subPart ? '/' + subPart : ''), 1)) {
|
|
// Close all popups
|
|
for (let vm of visiblePopups) {
|
|
(false === vm.onClose()) || vm.close();
|
|
}
|
|
|
|
let vmScreen = screen(screenName);
|
|
if (!vmScreen) {
|
|
vmScreen = screen(defaultScreenName);
|
|
if (vmScreen) {
|
|
subPart = screenName + '/' + subPart;
|
|
screenName = defaultScreenName;
|
|
}
|
|
}
|
|
|
|
if (vmScreen?.__started) {
|
|
let isSameScreen = currentScreen && vmScreen === currentScreen;
|
|
|
|
if (!vmScreen.__builded) {
|
|
vmScreen.__builded = true;
|
|
|
|
vmScreen.viewModels.forEach(ViewModelClass =>
|
|
buildViewModel(ViewModelClass, vmScreen)
|
|
);
|
|
|
|
vmScreen.onBuild?.();
|
|
}
|
|
|
|
setTimeout(() => {
|
|
// hide screen
|
|
currentScreen && !isSameScreen && hideScreen(currentScreen);
|
|
// --
|
|
|
|
currentScreen = vmScreen;
|
|
|
|
// show screen
|
|
if (!isSameScreen) {
|
|
vmScreen.onShow?.();
|
|
|
|
forEachViewModel(vmScreen, (vm, dom) => {
|
|
vm.beforeShow?.();
|
|
i18nToNodes(dom);
|
|
dom.hidden = false;
|
|
vm.onShow?.();
|
|
autofocus(dom);
|
|
});
|
|
}
|
|
// --
|
|
|
|
vmScreen.__cross?.parse(subPart);
|
|
}, 1);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
export const
|
|
ViewTypePopup = 'popups',
|
|
|
|
/**
|
|
* @param {Function} ViewModelClassToShow
|
|
* @param {Array=} params
|
|
* @returns {void}
|
|
*/
|
|
showScreenPopup = (ViewModelClassToShow, params = []) => {
|
|
const vm = buildViewModel(ViewModelClassToShow);
|
|
if (vm) {
|
|
params = params || [];
|
|
vm.beforeShow?.(...params);
|
|
vm.modalVisible(true);
|
|
vm.onShow?.(...params);
|
|
}
|
|
},
|
|
|
|
arePopupsVisible = ko.observable(false),
|
|
|
|
/**
|
|
* @param {Array} screensClasses
|
|
* @returns {void}
|
|
*/
|
|
startScreens = screensClasses => {
|
|
hasher.clear();
|
|
SCREENS.forEach(screen => hideScreen(screen, 1));
|
|
SCREENS.clear();
|
|
currentScreen = null,
|
|
defaultScreenName = '';
|
|
|
|
screensClasses.forEach(CScreen => {
|
|
const vmScreen = new CScreen(),
|
|
screenName = vmScreen.screenName;
|
|
defaultScreenName || (defaultScreenName = screenName);
|
|
SCREENS.set(screenName, vmScreen);
|
|
});
|
|
|
|
SCREENS.forEach(vmScreen => {
|
|
if (!vmScreen.__started) {
|
|
vmScreen.onStart();
|
|
vmScreen.__started = true;
|
|
}
|
|
});
|
|
|
|
const cross = new Crossroads();
|
|
cross.addRoute(/^([^/]*)\/?(.*)$/, screenOnRoute);
|
|
|
|
hasher.add(cross.parse.bind(cross));
|
|
hasher.init();
|
|
|
|
setTimeout(() => $htmlCL.remove('rl-started-trigger'), 100);
|
|
|
|
const c = elementById('rl-content'), l = elementById('rl-loading');
|
|
c && (c.hidden = false);
|
|
l?.remove();
|
|
},
|
|
|
|
/**
|
|
* Used by ko.bindingHandlers.command (template data-bind="command: ")
|
|
* to enable/disable click/submit action.
|
|
*/
|
|
decorateKoCommands = (thisArg, commands) =>
|
|
forEachObjectEntry(commands, (key, canExecute) => {
|
|
let command = thisArg[key],
|
|
fn = (...args) => fn.canExecute() && command.apply(thisArg, args);
|
|
|
|
fn.canExecute = koComputable(() => canExecute.call(thisArg, thisArg));
|
|
|
|
thisArg[key] = fn;
|
|
});
|
|
|
|
ko.decorateCommands = decorateKoCommands;
|