#include animate.js #include args.js #include utility.js const MamiMessageBoxContainer = function() { const container =
; let raised = false; let currAnim; return { raise: async parent => { if(raised) return; raised = true; container.style.pointerEvents = null; if(!parent.contains(container)) parent.appendChild(container); currAnim?.cancel(); currAnim = MamiAnimate({ async: true, delayed: true, duration: 300, easing: 'outExpo', start: () => { container.style.opacity = '0'; }, update: t => { container.style.opacity = t; }, end: () => { container.style.opacity = null; }, }); try { await currAnim.start(); } catch(ex) {} }, dismiss: async parent => { if(!raised) return; raised = false; container.style.pointerEvents = 'none'; currAnim?.cancel(); currAnim = MamiAnimate({ async: true, delayed: true, duration: 300, easing: 'outExpo', update: t => { container.style.opacity = 1 - t; }, end: t => { parent.removeChild(container); }, }); try { await currAnim.start(); } catch(ex) {} }, show: async dialog => { if(typeof dialog !== 'object' || dialog === null) throw 'dialog must be a non-null object'; const backgroundClick = ev => { ev.stopPropagation(); if(ev.target === container) dialog.clickButtonIndex(0); }; try { if(dialog.buttonCount === 1) container.addEventListener('click', backgroundClick); return await dialog.show(container); } finally { container.removeEventListener('click', backgroundClick); dialog.dismiss(); } }, }; }; const MamiMessageBoxDialog = function(info) { const defaultButtons = [ { name: 'ok', text: 'OK', primary: true, reject: false }, { name: 'yes', text: 'Yes', primary: true, reject: false }, { name: 'no', text: 'No', reject: true }, { name: 'cancel', text: 'Cancel', reject: true }, ]; const dialog =
; let showResolve, showReject; const doResolve = (...args) => { if(typeof showResolve !== 'function') return; showReject = undefined; dialog.style.pointerEvents = 'none'; showResolve(...args); }; const doReject = (...args) => { if(typeof showReject !== 'function') return; showResolve = undefined; dialog.style.pointerEvents = 'none'; showReject(...args); }; const body =
; dialog.appendChild(body); if(info.body !== undefined) { if(Array.isArray(info.body)) for(const line of info.body) body.appendChild(

{line}

); else body.appendChild(

{info.body}

); } const buttons =
; const buttonActions = {}; dialog.appendChild(buttons); let primaryButton; const createButton = button => { if(button.name === undefined) { console.error('No name specified for dialog button. Skipping...'); return; } const buttonName = button.name.toString(); if(buttons.querySelector(`[name="${buttonName}"]`) !== null) { console.error('A duplicate button name was attempted to be registered. Skipping...'); return; } const elem = ; buttons.appendChild(elem); if(button.primary) { primaryButton = elem; elem.classList.add('msgbox-dialog-button-primary'); } if(typeof button.action === 'function') buttonActions[buttonName] = button.action; }; for(const item of defaultButtons) if(item.name in info) { const button = typeof info[item.name] === 'object' && info[item.name] !== null ? info[item.name] : {}; button.name = item.name; button.reject = item.reject; if(button.primary === undefined) button.primary = item.primary; if(button.text === undefined) button.text = item.text; createButton(button); } if(Array.isArray(info.buttons)) for(const button of info.buttons) { if(button.text === undefined) button.text = button.name; createButton(button); } if(buttons.childElementCount < 1) createButton({ name: 'dismiss', text: 'Dismiss', primary: true }); if(buttons.childElementCount > 2) buttons.classList.add('msgbox-dialog-buttons-many'); return { get buttonCount() { return buttons.childElementCount; }, clickButtonIndex: num => { buttons.children[num].click(); }, show: container => { return new Promise((resolve, reject) => { showResolve = resolve; showReject = reject; dialog.addEventListener('submit', ev => { ev.preventDefault(); if(ev.submitter instanceof HTMLButtonElement) { const action = ev.submitter.dataset.action === 'resolve' ? doResolve : doReject; let result; if(ev.submitter.name in buttonActions) result = buttonActions[ev.submitter.name](); if(result instanceof Promise) result.then(result => { action(ev.submitter.name, result, true); }) .catch(result => { action(ev.submitter.name, result, false); }); else action(ev.submitter.name, result); } else doResolve(); }); dialog.style.transform = 'scale(0)'; container.appendChild(dialog); if(primaryButton instanceof HTMLButtonElement) primaryButton.focus(); MamiAnimate({ async: true, duration: 500, easing: 'outElasticHalf', update: t => { dialog.style.transform = `scale(${t})`; }, end: () => { dialog.style.transform = null; }, }); }); }, dismiss: async () => { dialog.style.pointerEvents = 'none'; await MamiAnimate({ async: true, duration: 600, easing: 'outExpo', update: t => { dialog.style.opacity = 1 - t; }, end: () => { dialog.style.opacity = '0'; }, }); $r(dialog); }, cancel: () => { doReject(); }, }; }; const MamiMessageBoxControl = function(options) { options = MamiArguments.verify(options, [ MamiArguments.check('parent', undefined, value => value instanceof Element, true), ]); const parent = options.parent; const container = new MamiMessageBoxContainer; const queue = []; let currentDialog; let processingQueue = false; const processQueue = async () => { if(processingQueue) return; try { processingQueue = true; let item; while((item = queue.shift()) !== undefined) { if(!processingQueue) break; try { currentDialog = new MamiMessageBoxDialog(item.info); item.resolve(await container.show(currentDialog)); } catch(ex) { item.reject(ex); } finally { currentDialog = undefined; } } } finally { processingQueue = false; } }; const raise = async () => { container.raise(parent); if(!processingQueue) await processQueue(); }; const dismiss = async () => { processingQueue = false; container.dismiss(parent); currentDialog?.cancel(); }; return { raise: raise, dismiss: dismiss, show: (info, priority) => { return new Promise((resolve, reject) => { const item = { info: info, resolve: resolve, reject: reject }; if(priority) queue.unshift(item); else queue.push(item); let queueWasRunning = processingQueue; raise().finally(() => { if(!queueWasRunning) dismiss(); }); }); }, }; };