Compare commits
2 commits
a08793d992
...
36e14399d8
Author | SHA1 | Date | |
---|---|---|---|
flash | 36e14399d8 | ||
flash | 1dcddffc03 |
76
src/mami.css/controls/msgbox.css
Normal file
76
src/mami.css/controls/msgbox.css
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
.msgbox-container {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: #1118;
|
||||||
|
color: #fff;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
backdrop-filter: blur(2px);
|
||||||
|
display: grid;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
grid-template-columns: 340px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.msgbox-dialog {
|
||||||
|
grid-row: 1;
|
||||||
|
grid-column: 1;
|
||||||
|
font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;
|
||||||
|
font-size: .8em;
|
||||||
|
line-height: 1.4em;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: #444d;
|
||||||
|
border: 1px solid #5554;
|
||||||
|
backdrop-filter: blur(2px);
|
||||||
|
filter: drop-shadow(0 6px 1em #000);
|
||||||
|
}
|
||||||
|
|
||||||
|
.msgbox-dialog-body {
|
||||||
|
margin: 6px;
|
||||||
|
}
|
||||||
|
.msgbox-dialog-line {
|
||||||
|
margin: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.msgbox-dialog-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 2px;
|
||||||
|
margin: 2px;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
}
|
||||||
|
.msgbox-dialog-buttons-many {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.msgbox-dialog-button {
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
border: 0;
|
||||||
|
background: #555d;
|
||||||
|
border: 1px solid #6664;
|
||||||
|
color: inherit;
|
||||||
|
padding: 7px 4px 6px;
|
||||||
|
transition: background .1s, border-color .1s;
|
||||||
|
}
|
||||||
|
.msgbox-dialog-button:focus {
|
||||||
|
outline: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
.msgbox-dialog-button:focus,
|
||||||
|
.msgbox-dialog-button:hover {
|
||||||
|
background: #666d;
|
||||||
|
border-color: #7774;
|
||||||
|
}
|
||||||
|
.msgbox-dialog-button:active {
|
||||||
|
background: #5a5a5add;
|
||||||
|
border-color: #6a6a6a44;
|
||||||
|
}
|
||||||
|
.msgbox-dialog-button-primary {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
|
@ -1,7 +1,8 @@
|
||||||
@include __animations.css;
|
@include __animations.css;
|
||||||
@include _main.css;
|
@include _main.css;
|
||||||
|
|
||||||
@include views.css;
|
@include controls/msgbox.css;
|
||||||
|
@include controls/views.css;
|
||||||
|
|
||||||
@include baka.css;
|
@include baka.css;
|
||||||
@include ping.css;
|
@include ping.css;
|
||||||
|
|
|
@ -15,6 +15,7 @@ const MamiContext = function(globalEventTarget, eventTarget) {
|
||||||
let isUnloading = false;
|
let isUnloading = false;
|
||||||
|
|
||||||
let settings;
|
let settings;
|
||||||
|
let msgbox;
|
||||||
let views;
|
let views;
|
||||||
let sound;
|
let sound;
|
||||||
let textTriggers;
|
let textTriggers;
|
||||||
|
@ -40,6 +41,15 @@ const MamiContext = function(globalEventTarget, eventTarget) {
|
||||||
settings = value;
|
settings = value;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
get msgbox() { return msgbox; },
|
||||||
|
set msgbox(value) {
|
||||||
|
if(msgbox !== undefined)
|
||||||
|
throw 'msgbox is already defined';
|
||||||
|
if(typeof value !== 'object' || value === null)
|
||||||
|
throw 'msgbox must be a non-null object';
|
||||||
|
msgbox = value;
|
||||||
|
},
|
||||||
|
|
||||||
get views() { return views; },
|
get views() { return views; },
|
||||||
set views(value) {
|
set views(value) {
|
||||||
if(views !== undefined)
|
if(views !== undefined)
|
||||||
|
|
262
src/mami.js/controls/msgbox.jsx
Normal file
262
src/mami.js/controls/msgbox.jsx
Normal file
|
@ -0,0 +1,262 @@
|
||||||
|
#include animate.js
|
||||||
|
#include args.js
|
||||||
|
#include utility.js
|
||||||
|
|
||||||
|
const MamiMessageBoxContainer = function() {
|
||||||
|
const container = <div class="msgbox-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 = <form class="msgbox-dialog"/>;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
|
@ -53,7 +53,6 @@ const MamiViewsControl = function(options) {
|
||||||
await elementInfo.onViewPush();
|
await elementInfo.onViewPush();
|
||||||
|
|
||||||
const element = extractElement(elementInfo);
|
const element = extractElement(elementInfo);
|
||||||
element.classList.toggle('hidden', false);
|
|
||||||
element.classList.toggle('views-item', true);
|
element.classList.toggle('views-item', true);
|
||||||
element.classList.toggle('views-background', false);
|
element.classList.toggle('views-background', false);
|
||||||
|
|
||||||
|
@ -80,8 +79,6 @@ const MamiViewsControl = function(options) {
|
||||||
fromInfo: prevElemInfo,
|
fromInfo: prevElemInfo,
|
||||||
fromElem: prevElem,
|
fromElem: prevElem,
|
||||||
});
|
});
|
||||||
|
|
||||||
prevElem.classList.toggle('hidden', true);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -98,7 +95,6 @@ const MamiViewsControl = function(options) {
|
||||||
const nextElemInfo = views[views.length - 1];
|
const nextElemInfo = views[views.length - 1];
|
||||||
const nextElem = extractElement(nextElemInfo);
|
const nextElem = extractElement(nextElemInfo);
|
||||||
|
|
||||||
nextElem.classList.toggle('hidden', false);
|
|
||||||
nextElem.classList.toggle('views-background', false);
|
nextElem.classList.toggle('views-background', false);
|
||||||
|
|
||||||
if(typeof nextElemInfo.onViewForeground === 'function')
|
if(typeof nextElemInfo.onViewForeground === 'function')
|
||||||
|
@ -115,8 +111,6 @@ const MamiViewsControl = function(options) {
|
||||||
if(typeof elementInfo.onViewBackground === 'function')
|
if(typeof elementInfo.onViewBackground === 'function')
|
||||||
await elementInfo.onViewBackground();
|
await elementInfo.onViewBackground();
|
||||||
|
|
||||||
element.classList.toggle('hidden', true);
|
|
||||||
|
|
||||||
if(targetBody.contains(element))
|
if(targetBody.contains(element))
|
||||||
targetBody.removeChild(element);
|
targetBody.removeChild(element);
|
||||||
|
|
||||||
|
@ -135,9 +129,24 @@ const MamiViewsControl = function(options) {
|
||||||
return views[views.length - 1];
|
return views[views.length - 1];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const raise = async (elementInfo, transition) => {
|
||||||
|
if(typeof elementInfo !== 'object')
|
||||||
|
throw 'elementInfo must be an object';
|
||||||
|
|
||||||
|
if(current() === elementInfo)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const index = views.indexOf(elementInfo);
|
||||||
|
if(index >= 0)
|
||||||
|
views.splice(index, 1);
|
||||||
|
|
||||||
|
return await push(elementInfo, transition);
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
push: push,
|
push: push,
|
||||||
pop: pop,
|
pop: pop,
|
||||||
|
raise: raise,
|
||||||
count: () => views.length,
|
count: () => views.length,
|
||||||
current: current,
|
current: current,
|
||||||
currentElement: () => {
|
currentElement: () => {
|
||||||
|
@ -147,6 +156,8 @@ const MamiViewsControl = function(options) {
|
||||||
|
|
||||||
return extractElement(currentInfo);
|
return extractElement(currentInfo);
|
||||||
},
|
},
|
||||||
|
includes: elementInfo => views.includes(elementInfo),
|
||||||
|
isCurrent: elementInfo => current() === elementInfo,
|
||||||
unshift: async elementInfo => {
|
unshift: async elementInfo => {
|
||||||
if(views.length < 1)
|
if(views.length < 1)
|
||||||
return await push(elementInfo, null);
|
return await push(elementInfo, null);
|
||||||
|
@ -159,7 +170,6 @@ const MamiViewsControl = function(options) {
|
||||||
if(!views.includes(elementInfo))
|
if(!views.includes(elementInfo))
|
||||||
views.unshift(elementInfo);
|
views.unshift(elementInfo);
|
||||||
|
|
||||||
element.classList.toggle('hidden', true);
|
|
||||||
element.classList.toggle('views-item', true);
|
element.classList.toggle('views-item', true);
|
||||||
element.classList.toggle('views-background', true);
|
element.classList.toggle('views-background', true);
|
||||||
|
|
||||||
|
@ -175,8 +185,6 @@ const MamiViewsControl = function(options) {
|
||||||
const elementInfo = views.shift();
|
const elementInfo = views.shift();
|
||||||
const element = extractElement(elementInfo);
|
const element = extractElement(elementInfo);
|
||||||
|
|
||||||
element.classList.toggle('hidden', false);
|
|
||||||
|
|
||||||
if(targetBody.contains(element))
|
if(targetBody.contains(element))
|
||||||
targetBody.removeChild(element);
|
targetBody.removeChild(element);
|
||||||
|
|
||||||
|
|
|
@ -1,35 +1,37 @@
|
||||||
#include utility.js
|
#include eeprom/script.jsx
|
||||||
|
|
||||||
const MamiEEPROM = function() {
|
const MamiEEPROM = function(baseUrl, getAuthLine) {
|
||||||
//
|
if(typeof baseUrl !== 'string')
|
||||||
};
|
throw 'baseUrl must be a string';
|
||||||
MamiEEPROM.init = (function() {
|
if(typeof getAuthLine !== 'function')
|
||||||
let initialised = false;
|
throw 'getAuthLine must be a function';
|
||||||
|
|
||||||
return () => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
if(initialised)
|
|
||||||
return new Promise(resolve => resolve());
|
|
||||||
|
|
||||||
const script = $e({
|
// when the pools rewrite happen, retrieve this from futami common
|
||||||
tag: 'script',
|
const appId = '1';
|
||||||
attrs: {
|
let client;
|
||||||
charset: 'utf-8',
|
|
||||||
type: 'text/javascript',
|
|
||||||
src: `${futami.get('eeprom2')}/scripts/eepromv1a.js`,
|
|
||||||
onload: () => {
|
|
||||||
initialised = true;
|
|
||||||
resolve();
|
|
||||||
},
|
|
||||||
onerror: () => {
|
|
||||||
$r(script);
|
|
||||||
console.error('Failed to load EEPROM script!');
|
|
||||||
reject();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
document.body.appendChild(script);
|
return {
|
||||||
});
|
get client() {
|
||||||
|
return client;
|
||||||
|
},
|
||||||
|
|
||||||
|
init: async () => {
|
||||||
|
await MamiEEPROMLoadScript(baseUrl);
|
||||||
|
client = new EEPROM(appId, baseUrl, getAuthLine);
|
||||||
|
},
|
||||||
|
|
||||||
|
create: fileInput => {
|
||||||
|
if(client === undefined)
|
||||||
|
throw 'eeprom client is uninitialised';
|
||||||
|
|
||||||
|
return client.create(fileInput);
|
||||||
|
},
|
||||||
|
|
||||||
|
delete: async fileInfo => {
|
||||||
|
if(client === undefined)
|
||||||
|
throw 'eeprom client is uninitialised';
|
||||||
|
|
||||||
|
await client.delete(fileInfo);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
})();
|
};
|
||||||
|
|
19
src/mami.js/eeprom/script.jsx
Normal file
19
src/mami.js/eeprom/script.jsx
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
#include utility.js
|
||||||
|
|
||||||
|
const MamiEEPROMLoadScript = baseUrl => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if(typeof baseUrl !== 'string')
|
||||||
|
throw 'baseUrl must be a string';
|
||||||
|
|
||||||
|
const src = `${baseUrl}/scripts/eepromv1a.js`;
|
||||||
|
|
||||||
|
let script = $q(`script[src="${src}"]`);
|
||||||
|
if(script instanceof Element) {
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
script = <script src={src} onload={() => { resolve(); }} onerror={() => { $r(script); reject(); }} />;
|
||||||
|
document.body.appendChild(script);
|
||||||
|
});
|
||||||
|
};
|
|
@ -1,4 +1,4 @@
|
||||||
window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } };
|
window.Umi = { UI: {} };
|
||||||
|
|
||||||
#include animate.js
|
#include animate.js
|
||||||
#include common.js
|
#include common.js
|
||||||
|
@ -14,6 +14,7 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } };
|
||||||
#include weeb.js
|
#include weeb.js
|
||||||
#include worker.js
|
#include worker.js
|
||||||
#include audio/autoplay.js
|
#include audio/autoplay.js
|
||||||
|
#include controls/msgbox.jsx
|
||||||
#include controls/views.js
|
#include controls/views.js
|
||||||
#include eeprom/eeprom.js
|
#include eeprom/eeprom.js
|
||||||
#include settings/backup.js
|
#include settings/backup.js
|
||||||
|
@ -40,11 +41,11 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } };
|
||||||
const ctx = new MamiContext(eventTarget);
|
const ctx = new MamiContext(eventTarget);
|
||||||
Object.defineProperty(window, 'mami', { enumerable: true, value: ctx });
|
Object.defineProperty(window, 'mami', { enumerable: true, value: ctx });
|
||||||
|
|
||||||
const views = new MamiViewsControl({ body: document.body });
|
ctx.views = new MamiViewsControl({ body: document.body });
|
||||||
ctx.views = views;
|
ctx.msgbox = new MamiMessageBoxControl({ views: ctx.views });
|
||||||
|
|
||||||
const loadingOverlay = new Umi.UI.LoadingOverlay('spinner', 'Loading...');
|
const loadingOverlay = new Umi.UI.LoadingOverlay('spinner', 'Loading...');
|
||||||
await views.push(loadingOverlay);
|
await ctx.views.push(loadingOverlay);
|
||||||
|
|
||||||
loadingOverlay.setMessage('Loading environment...');
|
loadingOverlay.setMessage('Loading environment...');
|
||||||
try {
|
try {
|
||||||
|
@ -233,7 +234,7 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } };
|
||||||
|
|
||||||
// should be dynamic when possible
|
// should be dynamic when possible
|
||||||
const layout = new Umi.UI.ChatLayout;
|
const layout = new Umi.UI.ChatLayout;
|
||||||
await views.unshift(layout);
|
await ctx.views.unshift(layout);
|
||||||
|
|
||||||
Umi.UI.View.AccentReload();
|
Umi.UI.View.AccentReload();
|
||||||
Umi.UI.Hooks.AddHooks();
|
Umi.UI.Hooks.AddHooks();
|
||||||
|
@ -375,7 +376,7 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } };
|
||||||
|
|
||||||
Umi.UI.Toggles.Add('clear', {
|
Umi.UI.Toggles.Add('clear', {
|
||||||
'click': function() {
|
'click': function() {
|
||||||
if(confirm('ARE YOU SURE ABOUT THAT???')) {
|
ctx.msgbox.show({ body: 'ARE YOU SURE ABOUT THAT???', yes: true, no: true }).then(() => {
|
||||||
const limit = settings.get('explosionRadius');
|
const limit = settings.get('explosionRadius');
|
||||||
const explode = $e({
|
const explode = $e({
|
||||||
tag: 'img',
|
tag: 'img',
|
||||||
|
@ -408,13 +409,13 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } };
|
||||||
|
|
||||||
for(const blMsg of backLog)
|
for(const blMsg of backLog)
|
||||||
Umi.Messages.Add(blMsg);
|
Umi.Messages.Add(blMsg);
|
||||||
}
|
}).catch(() => {});
|
||||||
}
|
}
|
||||||
}, 'Clear Logs');
|
}, 'Clear Logs');
|
||||||
|
|
||||||
const pingIndicator = new MamiPingIndicator;
|
const pingIndicator = new MamiPingIndicator;
|
||||||
const pingToggle = Umi.UI.Toggles.Add('ping', {
|
const pingToggle = Umi.UI.Toggles.Add('ping', {
|
||||||
click: () => alert(pingToggle.title),
|
click: () => { ctx.msgbox.show({ body: `Your current ping is ${pingToggle.title}` }); },
|
||||||
}, 'Ready~');
|
}, 'Ready~');
|
||||||
pingToggle.appendChild(pingIndicator.getElement());
|
pingToggle.appendChild(pingIndicator.getElement());
|
||||||
|
|
||||||
|
@ -422,14 +423,12 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } };
|
||||||
Umi.UI.InputMenus.Add('emotes', 'Emoticons');
|
Umi.UI.InputMenus.Add('emotes', 'Emoticons');
|
||||||
|
|
||||||
let doUpload;
|
let doUpload;
|
||||||
MamiEEPROM.init()
|
ctx.eeprom = new MamiEEPROM(futami.get('eeprom2'), MamiMisuzuAuth.getLine);
|
||||||
|
ctx.eeprom.init()
|
||||||
.catch(ex => {
|
.catch(ex => {
|
||||||
console.log('Failed to initialise EEPROM.', ex);
|
console.log('Failed to initialise EEPROM.', ex);
|
||||||
ctx.eeprom = undefined;
|
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
ctx.eeprom = new EEPROM('1', futami.get('eeprom2'), MamiMisuzuAuth.getLine);
|
|
||||||
|
|
||||||
Umi.UI.Menus.Add('uploads', 'Upload History', !FUTAMI_DEBUG);
|
Umi.UI.Menus.Add('uploads', 'Upload History', !FUTAMI_DEBUG);
|
||||||
|
|
||||||
doUpload = async file => {
|
doUpload = async file => {
|
||||||
|
@ -453,7 +452,7 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } };
|
||||||
.then(() => uploadEntry.remove())
|
.then(() => uploadEntry.remove())
|
||||||
.catch(ex => {
|
.catch(ex => {
|
||||||
console.error(ex);
|
console.error(ex);
|
||||||
alert(ex);
|
ctx.msgbox.show({ body: ['An error occurred while trying to delete an uploaded file:', ex] });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -476,7 +475,7 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } };
|
||||||
} catch(ex) {
|
} catch(ex) {
|
||||||
if(!ex.aborted) {
|
if(!ex.aborted) {
|
||||||
console.error(ex);
|
console.error(ex);
|
||||||
alert(ex);
|
ctx.msgbox.show({ body: ['An error occurred while trying to upload a file:', ex] });
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadEntry.remove();
|
uploadEntry.remove();
|
||||||
|
@ -517,13 +516,23 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } };
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
|
||||||
for(const file of ev.dataTransfer.files) {
|
for(const file of ev.dataTransfer.files) {
|
||||||
if(file.name.slice(-5) === '.mami'
|
if(file.name.slice(-5) === '.mami') {
|
||||||
&& confirm('This file appears to be a settings export. Do you want to import it? This will overwrite your existing settings!')) {
|
ctx.msgbox.show({
|
||||||
(new MamiSettingsBackup(settings)).importFile(file);
|
body: [
|
||||||
return;
|
'This file appears to be a settings export.',
|
||||||
}
|
'Do you want to import it? This will overwrite your existing settings!',
|
||||||
|
],
|
||||||
if(doUpload !== undefined)
|
yes: true,
|
||||||
|
no: true,
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
(new MamiSettingsBackup(settings)).importFile(file);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
if(doUpload !== undefined)
|
||||||
|
doUpload(file);
|
||||||
|
});
|
||||||
|
} else if(doUpload !== undefined)
|
||||||
doUpload(file);
|
doUpload(file);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -541,7 +550,7 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } };
|
||||||
loadingOverlay.setMessage('Connecting...');
|
loadingOverlay.setMessage('Connecting...');
|
||||||
|
|
||||||
const setLoadingOverlay = async (icon, header, message, optional) => {
|
const setLoadingOverlay = async (icon, header, message, optional) => {
|
||||||
const currentView = views.current();
|
const currentView = ctx.views.current();
|
||||||
|
|
||||||
if('setIcon' in currentView) {
|
if('setIcon' in currentView) {
|
||||||
currentView.setIcon(icon);
|
currentView.setIcon(icon);
|
||||||
|
@ -552,7 +561,7 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } };
|
||||||
|
|
||||||
if(!optional) {
|
if(!optional) {
|
||||||
const loading = new Umi.UI.LoadingOverlay(icon, header, message);
|
const loading = new Umi.UI.LoadingOverlay(icon, header, message);
|
||||||
await views.push(loading);
|
await ctx.views.push(loading);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,7 @@ Umi.UI.Hooks = (function() {
|
||||||
if((ev.ctrlKey && ev.key !== 'v') || ev.altKey)
|
if((ev.ctrlKey && ev.key !== 'v') || ev.altKey)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if(!ev.target.matches('input, textarea, select'))
|
if(!ev.target.matches('input, textarea, select, button'))
|
||||||
$i('umi-msg-text').focus();
|
$i('umi-msg-text').focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -43,38 +43,38 @@ Umi.UI.Menus = (function() {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
Add: function(baseId, title, initiallyHidden) {
|
Add: function(baseId, title, initiallyHidden) {
|
||||||
if(ids.indexOf(baseId) < 0) {
|
if(ids.includes(baseId))
|
||||||
ids.push(baseId);
|
return;
|
||||||
|
ids.push(baseId);
|
||||||
|
|
||||||
const menuClass = [sidebarMenu, sidebarMenu + '--' + baseId];
|
const menuClass = [sidebarMenu, `${sidebarMenu}--${baseId}`];
|
||||||
const iconClass = [sidebarSelectorMode, sidebarSelectorMode + '--' + baseId];
|
const iconClass = [sidebarSelectorMode, `${sidebarSelectorMode}--${baseId}`];
|
||||||
|
|
||||||
const menus = $i('umi-menus');
|
const menus = $i('umi-menus');
|
||||||
const icons = $i('umi-menu-icons');
|
const icons = $i('umi-menu-icons');
|
||||||
|
|
||||||
if(menus.children.length < 1) {
|
if(menus.children.length < 1) {
|
||||||
menuClass.push(sidebarMenuActive);
|
menuClass.push(sidebarMenuActive);
|
||||||
iconClass.push(sidebarSelectorModeActive);
|
iconClass.push(sidebarSelectorModeActive);
|
||||||
}
|
|
||||||
|
|
||||||
if(initiallyHidden) {
|
|
||||||
menuClass.push(sidebarMenuHidden);
|
|
||||||
iconClass.push(sidebarSelectorModeHidden);
|
|
||||||
}
|
|
||||||
|
|
||||||
icons.appendChild($e({
|
|
||||||
attrs: {
|
|
||||||
id: 'umi-menu-icons-' + baseId,
|
|
||||||
classList: iconClass,
|
|
||||||
title: title,
|
|
||||||
onclick: function() {
|
|
||||||
activate(baseId);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
menus.appendChild($e({ attrs: { 'class': menuClass, id: 'umi-menus-' + baseId } }));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(initiallyHidden) {
|
||||||
|
menuClass.push(sidebarMenuHidden);
|
||||||
|
iconClass.push(sidebarSelectorModeHidden);
|
||||||
|
}
|
||||||
|
|
||||||
|
icons.appendChild($e({
|
||||||
|
attrs: {
|
||||||
|
id: `umi-menu-icons-${baseId}`,
|
||||||
|
classList: iconClass,
|
||||||
|
title: title,
|
||||||
|
onclick: function() {
|
||||||
|
activate(baseId);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
menus.appendChild($e({ attrs: { 'class': menuClass, id: `umi-menus-${baseId}` } }));
|
||||||
},
|
},
|
||||||
Get: function(baseId, icon) {
|
Get: function(baseId, icon) {
|
||||||
const id = (icon ? 'umi-menu-icons' : 'umi-menus') + '-' + baseId;
|
const id = (icon ? 'umi-menu-icons' : 'umi-menus') + '-' + baseId;
|
||||||
|
|
|
@ -231,7 +231,7 @@ Umi.UI.Settings = (function() {
|
||||||
name: 'onlyConnectWhenVisible',
|
name: 'onlyConnectWhenVisible',
|
||||||
title: 'Only connect when the tab is in the foreground',
|
title: 'Only connect when the tab is in the foreground',
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
confirm: 'Please only disable this setting if you are using a desktop or laptop computer, this should always remain on on a phone, tablet or other device of that sort. Ignoring this warning may carry consequences.',
|
confirm: ['Please only disable this setting if you are using a desktop or laptop computer, this should always remain on on a phone, tablet or other device of that sort.', 'Are you sure you want to change this setting? Ignoring this warning may carry consequences.'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'playJokeSounds',
|
name: 'playJokeSounds',
|
||||||
|
@ -346,10 +346,8 @@ Umi.UI.Settings = (function() {
|
||||||
{
|
{
|
||||||
title: 'Import settings',
|
title: 'Import settings',
|
||||||
type: 'button',
|
type: 'button',
|
||||||
|
confirm: ['Your current settings will be replaced with the ones in the export.', 'Are you sure you want to continue?'],
|
||||||
invoke: () => {
|
invoke: () => {
|
||||||
if(!confirm('Your current settings will be replaced with the ones in the export. Are you sure you want to continue?'))
|
|
||||||
return;
|
|
||||||
|
|
||||||
(new MamiSettingsBackup(mami.settings)).importUpload(document.body);
|
(new MamiSettingsBackup(mami.settings)).importUpload(document.body);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -368,10 +366,8 @@ Umi.UI.Settings = (function() {
|
||||||
{
|
{
|
||||||
title: 'Reset settings',
|
title: 'Reset settings',
|
||||||
type: 'button',
|
type: 'button',
|
||||||
|
confirm: ['This will reset all your settings to their defaults values.', 'Are you sure you want to do this?'],
|
||||||
invoke: () => {
|
invoke: () => {
|
||||||
if(!confirm('This will reset all your settings to their defaults values. Are you sure you want to do this?'))
|
|
||||||
return;
|
|
||||||
|
|
||||||
mami.settings.clear();
|
mami.settings.clear();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -463,7 +459,16 @@ Umi.UI.Settings = (function() {
|
||||||
</option>);
|
</option>);
|
||||||
} else if(display.type === 'button') {
|
} else if(display.type === 'button') {
|
||||||
input.value = display.title;
|
input.value = display.title;
|
||||||
input.addEventListener('click', () => display.invoke(input));
|
input.addEventListener('click', () => {
|
||||||
|
if(display.confirm !== undefined)
|
||||||
|
mami.msgbox.show({
|
||||||
|
body: display.confirm,
|
||||||
|
yes: { primary: false },
|
||||||
|
no: { primary: true },
|
||||||
|
}).then(() => { display.invoke(input); }).catch(() => {});
|
||||||
|
else
|
||||||
|
display.invoke(input);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if(setting !== undefined) {
|
if(setting !== undefined) {
|
||||||
|
@ -473,12 +478,19 @@ Umi.UI.Settings = (function() {
|
||||||
if(display.type === 'checkbox') {
|
if(display.type === 'checkbox') {
|
||||||
mami.settings.watch(setting.name, ev => input.checked = ev.detail.value);
|
mami.settings.watch(setting.name, ev => input.checked = ev.detail.value);
|
||||||
input.addEventListener('change', () => {
|
input.addEventListener('change', () => {
|
||||||
if(display.confirm !== undefined && input.checked !== setting.fallback && !confirm(display.confirm)) {
|
if(display.confirm !== undefined && input.checked !== setting.fallback) {
|
||||||
input.checked = setting.fallback;
|
mami.msgbox.show({
|
||||||
return;
|
body: display.confirm,
|
||||||
|
yes: { primary: false },
|
||||||
|
no: { primary: true },
|
||||||
|
}).then(() => {
|
||||||
|
mami.settings.toggle(setting.name);
|
||||||
|
}).catch(() => {
|
||||||
|
input.checked = setting.fallback;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
mami.settings.toggle(setting.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
mami.settings.toggle(setting.name);
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
mami.settings.watch(setting.name, ev => input.value = ev.detail.value);
|
mami.settings.watch(setting.name, ev => input.value = ev.detail.value);
|
||||||
|
|
|
@ -112,11 +112,15 @@ Umi.UI.Users = (function() {
|
||||||
}));
|
}));
|
||||||
uOptions.appendChild(createAction('Kick Fucking Everyone', {
|
uOptions.appendChild(createAction('Kick Fucking Everyone', {
|
||||||
'click': function() {
|
'click': function() {
|
||||||
if(confirm('You are about to detonate the fucking bomb. Are you sure?')) {
|
mami.msgbox.show({
|
||||||
const targets = Umi.Users.All();
|
body: ['You are about to detonate the fucking bomb.', 'Are you sure?'],
|
||||||
|
yes: { text: 'FUCK IT, WE BALL' },
|
||||||
|
no: { text: 'nah' },
|
||||||
|
}).then(() => {
|
||||||
|
const targets = Umi.Users.All().reverse();
|
||||||
for(const target of targets) // this shouldn't call it like this but will have to leave it for now
|
for(const target of targets) // this shouldn't call it like this but will have to leave it for now
|
||||||
Umi.Server.sendMessage('/kick ' + target.name);
|
Umi.Server.sendMessage('/kick ' + target.name);
|
||||||
}
|
}).catch(() => {});
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue