#include animate.js #include args.js #include utility.js const MamiMessageBoxContainer = function() { const container =
; return { getElement: () => container, show: async dialog => { if(typeof dialog !== 'object' || dialog === null) throw 'dialog must be a non-null object'; try { return await dialog.show(container); } finally { 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 }, ]; let showResolve, showReject; const doResolve = (...args) => { if(typeof showResolve !== 'function') return; showReject = undefined; showResolve(...args); }; const doReject = (...args) => { if(typeof showReject !== 'function') return; showResolve = undefined; showReject(...args); }; const dialog =
; 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 { getElement: () => dialog, 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 () => { 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.type('views', 'object', undefined, true), ]); const views = options.views; 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 () => { if(!views.isCurrent(container)) views.raise(container, ctx => MamiAnimate({ async: true, duration: 300, easing: 'outExpo', update: t => { ctx.toElem.style.opacity = t; }, end: () => { ctx.toElem.style.opacity = null; }, })); if(!processingQueue) await processQueue(); }; const dismiss = async () => { processingQueue = false; currentDialog?.cancel(); if(views.isCurrent(container)) await views.pop(ctx => MamiAnimate({ async: true, duration: 300, easing: 'outExpo', update: t => { ctx.fromElem.style.opacity = 1 - t; }, end: t => { ctx.fromElem.style.opacity = '0'; }, })); }; 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(); }); }); }, }; };