309 lines
9.3 KiB
JavaScript
309 lines
9.3 KiB
JavaScript
#include animate.js
|
|
#include args.js
|
|
#include utility.js
|
|
|
|
const MamiMessageBoxContainer = function() {
|
|
const container = <div class="msgbox-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 = <form class="msgbox-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 = <div class="msgbox-dialog-body"/>;
|
|
dialog.appendChild(body);
|
|
|
|
if(info.body !== undefined) {
|
|
if(Array.isArray(info.body))
|
|
for(const line of info.body)
|
|
body.appendChild(<p class="msgbox-dialog-line">{line}</p>);
|
|
else
|
|
body.appendChild(<p class="msgbox-dialog-line">{info.body}</p>);
|
|
}
|
|
|
|
const buttons = <div class="msgbox-dialog-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 = <button class="msgbox-dialog-button" name={buttonName} data-action={button.reject ? 'reject' : 'resolve'}>{button.text ?? buttonName ?? ''}</button>;
|
|
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();
|
|
});
|
|
});
|
|
},
|
|
};
|
|
};
|