snappymail/dev/Knoin/Knoin.js

338 lines
8.9 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';
2021-07-22 03:34:17 +08:00
import { arrayLength, isFunction } from 'Common/Utils';
2016-07-08 07:22:58 +08:00
2019-07-05 03:19:24 +08:00
let currentScreen = null,
visiblePopups = new Set,
defaultScreenName = '';
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();
2021-08-20 21:40:07 +08:00
},
/**
* @param {string} screenName
* @returns {?Object}
*/
screen = screenName => screenName && null != SCREENS[screenName] ? SCREENS[screenName] : null,
/**
* @param {Function} ViewModelClass
* @param {Object=} vmScreen
* @returns {*}
*/
buildViewModel = (ViewModelClass, vmScreen) => {
if (ViewModelClass && !ViewModelClass.__builded) {
let vmDom = null;
const vm = new ViewModelClass(vmScreen),
position = vm.viewModelPosition || '',
vmPlace = position ? doc.getElementById('rl-' + position.toLowerCase()) : null;
ViewModelClass.__builded = true;
ViewModelClass.__vm = vm;
if (vmPlace) {
vmDom = Element.fromHTML('<div class="rl-view-model RL-' + vm.viewModelTemplateID + '" hidden=""></div>');
vmPlace.append(vmDom);
vm.viewModelDom = vmDom;
ViewModelClass.__dom = vmDom;
if (ViewType.Popup === position) {
vm.cancelCommand = vm.closeCommand = createCommand(() => {
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 => {
if (value) {
visiblePopups.add(vm);
2021-08-20 21:40:07 +08:00
vmDom.style.zIndex = 3000 + popupVisibilityNames().length + 10;
vmDom.hidden = false;
vm.storeAndSetScope();
popupVisibilityNames.push(vm.viewModelName);
requestAnimationFrame(() => { // wait just before the next paint
vmDom.offsetHeight; // force a reflow
vmDom.classList.add('show'); // trigger the transitions
});
} else {
visiblePopups.delete(vm);
2021-08-20 21:40:07 +08:00
vm.onHide && vm.onHide();
vmDom.classList.remove('show');
vm.restoreScope();
popupVisibilityNames(popupVisibilityNames.filter(v=>v!==vm.viewModelName));
}
2021-08-20 21:40:07 +08:00
vmDom.setAttribute('aria-hidden', !value);
});
if ('ontransitionend' in vmDom) {
vmDom.addEventListener('transitionend', endShowHide);
2019-07-05 03:19:24 +08:00
} else {
2021-08-20 21:40:07 +08:00
// For Edge < 79 and mobile browsers
vm.modalVisibility.subscribe(() => ()=>setTimeout(endShowHide({target:vmDom}), 500));
2016-07-08 07:22:58 +08:00
}
}
2016-07-08 07:22:58 +08:00
2021-08-20 21:40:07 +08:00
ko.applyBindingAccessorsToNode(
vmDom,
{
i18nInit: true,
template: () => ({ name: vm.viewModelTemplateID })
},
vm
);
2021-05-31 22:19:01 +08:00
2021-08-20 21:40:07 +08:00
vm.onBuild && vm.onBuild(vmDom);
if (vm && ViewType.Popup === position) {
vm.registerPopupKeyDown();
}
dispatchEvent(new CustomEvent('rl-view-model', {detail:vm}));
} else {
console.log('Cannot find view model position: ' + position);
2016-07-08 07:22:58 +08:00
}
}
2021-08-20 21:40:07 +08:00
return ViewModelClass && ViewModelClass.__vm;
},
2016-07-08 07:22:58 +08:00
2021-08-20 21:40:07 +08:00
/**
* @param {string} screenName
* @param {string} subPart
* @returns {void}
*/
screenOnRoute = (screenName, subPart) => {
let vmScreen = null,
isSameScreen = false;
2016-07-08 07:22:58 +08:00
2021-08-20 21:40:07 +08:00
if (null == screenName || '' == screenName) {
screenName = defaultScreenName;
}
2016-07-08 07:22:58 +08:00
// Close all popups
for (let vm of visiblePopups) {
vm.closeCommand();
}
2021-08-20 21:40:07 +08:00
if (screenName) {
vmScreen = screen(screenName);
if (!vmScreen) {
vmScreen = screen(defaultScreenName);
if (vmScreen) {
subPart = screenName + '/' + subPart;
screenName = defaultScreenName;
}
2016-07-08 07:22:58 +08:00
}
2021-08-20 21:40:07 +08:00
if (vmScreen && vmScreen.__started) {
isSameScreen = currentScreen && vmScreen === currentScreen;
if (!vmScreen.__builded) {
vmScreen.__builded = true;
vmScreen.viewModels.forEach(ViewModelClass =>
buildViewModel(ViewModelClass, vmScreen)
);
vmScreen.onBuild && vmScreen.onBuild();
2016-07-08 07:22:58 +08:00
}
2021-08-20 21:40:07 +08:00
setTimeout(() => {
// hide screen
if (currentScreen && !isSameScreen) {
currentScreen.onHide && currentScreen.onHide();
currentScreen.onHideWithDelay && setTimeout(()=>currentScreen.onHideWithDelay(), 500);
if (arrayLength(currentScreen.viewModels)) {
currentScreen.viewModels.forEach(ViewModelClass => {
if (
ViewModelClass.__vm &&
ViewModelClass.__dom &&
ViewType.Popup !== ViewModelClass.__vm.viewModelPosition
) {
ViewModelClass.__dom.hidden = true;
ViewModelClass.__vm.viewModelVisible = false;
ViewModelClass.__vm.onHide && ViewModelClass.__vm.onHide();
ViewModelClass.__vm.onHideWithDelay && setTimeout(()=>ViewModelClass.__vm.onHideWithDelay(), 500);
}
});
}
}
// --
currentScreen = vmScreen;
2016-07-08 07:22:58 +08:00
2021-08-20 21:40:07 +08:00
// show screen
if (currentScreen && !isSameScreen) {
currentScreen.onShow && currentScreen.onShow();
2016-07-08 07:22:58 +08:00
2021-08-20 21:40:07 +08:00
if (arrayLength(currentScreen.viewModels)) {
currentScreen.viewModels.forEach(ViewModelClass => {
if (
ViewModelClass.__vm &&
ViewModelClass.__dom &&
ViewType.Popup !== ViewModelClass.__vm.viewModelPosition
) {
ViewModelClass.__vm.onBeforeShow && ViewModelClass.__vm.onBeforeShow();
2016-07-08 07:22:58 +08:00
2021-08-20 21:40:07 +08:00
ViewModelClass.__dom.hidden = false;
ViewModelClass.__vm.viewModelVisible = true;
2016-07-08 07:22:58 +08:00
2021-08-20 21:40:07 +08:00
ViewModelClass.__vm.onShow && ViewModelClass.__vm.onShow();
2016-07-08 07:22:58 +08:00
2021-08-20 21:40:07 +08:00
autofocus(ViewModelClass.__dom);
2021-08-20 21:40:07 +08:00
ViewModelClass.__vm.onShowWithDelay && setTimeout(()=>ViewModelClass.__vm.onShowWithDelay, 200);
}
});
}
2016-07-08 07:22:58 +08:00
}
2021-08-20 21:40:07 +08:00
// --
vmScreen && vmScreen.__cross && vmScreen.__cross.parse(subPart);
}, 1);
}
}
};
export const
2021-09-01 22:10:44 +08:00
popupVisibilityNames = ko.observableArray(),
2021-08-20 21:40:07 +08:00
ViewType = {
Popup: 'Popups',
Left: 'Left',
Right: 'Right',
Content: 'Content'
},
/**
* @param {Function} fExecute
* @param {(Function|boolean|null)=} fCanExecute = true
* @returns {Function}
*/
createCommand = (fExecute, fCanExecute = true) => {
let fResult = fExecute
? (...args) => {
if (fResult && fResult.canExecute && fResult.canExecute()) {
fExecute.apply(null, args);
2016-07-08 07:22:58 +08:00
}
2021-08-20 21:40:07 +08:00
return false;
} : ()=>0;
fResult.enabled = ko.observable(true);
fResult.isCommand = true;
2016-07-08 07:22:58 +08:00
2021-08-20 21:40:07 +08:00
if (isFunction(fCanExecute)) {
fResult.canExecute = ko.computed(() => fResult && fResult.enabled() && fCanExecute.call(null));
} else {
fResult.canExecute = ko.computed(() => fResult && fResult.enabled() && !!fCanExecute);
2016-07-08 07:22:58 +08:00
}
2021-08-20 21:40:07 +08:00
return fResult;
},
/**
* @param {Function} ViewModelClassToHide
* @returns {void}
*/
hideScreenPopup = ViewModelClassToHide => {
if (ViewModelClassToHide && ViewModelClassToHide.__vm && ViewModelClassToHide.__dom) {
ViewModelClassToHide.__vm.modalVisibility(false);
2016-07-08 07:22:58 +08:00
}
2021-08-20 21:40:07 +08:00
},
getScreenPopupViewModel = ViewModelClassToShow =>
(buildViewModel(ViewModelClassToShow) && ViewModelClassToShow.__dom) && ViewModelClassToShow.__vm,
/**
* @param {Function} ViewModelClassToShow
* @param {Array=} params
* @returns {void}
*/
showScreenPopup = (ViewModelClassToShow, params = []) => {
const vm = getScreenPopupViewModel(ViewModelClassToShow);
if (vm) {
params = params || [];
vm.onBeforeShow && vm.onBeforeShow(...params);
vm.modalVisibility(true);
vm.onShow && vm.onShow(...params);
}
},
arePopupsVisible = () => 0 < visiblePopups.size,
2021-08-20 21:40:07 +08:00
/**
* @param {Function} ViewModelClassToShow
* @returns {boolean}
*/
isPopupVisible = ViewModelClassToShow =>
ViewModelClassToShow && ViewModelClassToShow.__vm && ViewModelClassToShow.__vm.modalVisibility(),
/**
* @param {Array} screensClasses
* @returns {void}
*/
startScreens = screensClasses => {
screensClasses.forEach(CScreen => {
if (CScreen) {
const vmScreen = new CScreen(),
screenName = vmScreen && vmScreen.screenName();
if (screenName) {
defaultScreenName || (defaultScreenName = screenName);
SCREENS[screenName] = vmScreen;
}
}
});
Object.values(SCREENS).forEach(vmScreen =>
vmScreen && vmScreen.onStart && vmScreen.onStart()
);
2016-07-08 07:22:58 +08:00
2021-08-20 21:40:07 +08:00
const cross = new Crossroads();
cross.addRoute(/^([a-zA-Z0-9-]*)\/?(.*)$/, 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);
setTimeout(() => $htmlCL.add('rl-started-delay'), 200);
},
2016-07-08 07:22:58 +08:00
2021-08-20 21:40:07 +08:00
decorateKoCommands = (thisArg, commands) =>
Object.entries(commands).forEach(([key, canExecute]) => {
let command = thisArg[key],
fn = (...args) => fn.enabled() && fn.canExecute() && command.apply(thisArg, args);
2016-07-08 07:22:58 +08:00
2021-08-20 21:40:07 +08:00
// fn.__realCanExecute = canExecute;
// fn.isCommand = true;
2021-08-20 21:40:07 +08:00
fn.enabled = ko.observable(true);
2021-08-20 21:40:07 +08:00
fn.canExecute = (typeof canExecute === 'function')
? ko.computed(() => fn.enabled() && canExecute.call(thisArg, thisArg))
: ko.computed(() => fn.enabled());
2021-08-20 21:40:07 +08:00
thisArg[key] = fn;
});
ko.decorateCommands = decorateKoCommands;