1000 lines
38 KiB
JavaScript
1000 lines
38 KiB
JavaScript
window.Umi = { UI: {} };
|
|
|
|
#include array.js
|
|
#include html.js
|
|
#include uniqstr.js
|
|
#include xhr.js
|
|
|
|
#include animate.js
|
|
#include args.js
|
|
#include awaitable.js
|
|
#include common.js
|
|
#include compat.js
|
|
#include conman.js
|
|
#include context.js
|
|
#include emotes.js
|
|
#include events.js
|
|
#include mobile.js
|
|
#include mszauth.js
|
|
#include parsing.js
|
|
#include themes.js
|
|
#include txtrigs.js
|
|
#include users.js
|
|
#include weeb.js
|
|
#include audio/autoplay.js
|
|
#include chatform/form.jsx
|
|
#include colpick/picker.jsx
|
|
#include controls/msgbox.jsx
|
|
#include controls/ping.jsx
|
|
#include controls/views.js
|
|
#include eeprom/eeprom.js
|
|
#include eeprom/eepromv2.js
|
|
#include emotes/picker.jsx
|
|
#include notices/baka.jsx
|
|
#include notices/youare.jsx
|
|
#include settings/backup.js
|
|
#include settings/settings.js
|
|
#include sidebar/act-clear-backlog.jsx
|
|
#include sidebar/act-collapse-all.jsx
|
|
#include sidebar/act-ping.jsx
|
|
#include sidebar/act-scroll.jsx
|
|
#include sidebar/act-sound.jsx
|
|
#include sidebar/act-toggle.jsx
|
|
#include sidebar/pan-channels.jsx
|
|
#include sidebar/pan-settings.jsx
|
|
#include sidebar/pan-uploads.jsx
|
|
#include sidebar/pan-users.jsx
|
|
#include sidebar/sidebar.jsx
|
|
#include sockchat/client.js
|
|
#include sockchat/handlers.js
|
|
#include sound/context.js
|
|
#include sound/osukeys.js
|
|
#include sound/sndtest.jsx
|
|
#include ui/chat-layout.js
|
|
#include ui/emotes.js
|
|
#include ui/loading-overlay.jsx
|
|
|
|
const MamiInit = async args => {
|
|
args = MamiArgs('args', args, define => {
|
|
define('parent').default(document.body).constraint(value => value instanceof Element).done();
|
|
define('eventTarget').required().constraint(MamiIsEventTarget).done();
|
|
define('settingsPrefix').default('umi-').done();
|
|
});
|
|
|
|
const ctx = new MamiContext(args.eventTarget);
|
|
|
|
// remove this later and replace with the one commented out way below
|
|
if(!('mami' in window))
|
|
Object.defineProperty(window, 'mami', { enumerable: true, value: ctx });
|
|
|
|
ctx.views = new MamiViewsControl({ body: args.parent });
|
|
ctx.msgbox = new MamiMessageBoxControl({ parent: args.parent });
|
|
|
|
const loadingOverlay = new Umi.UI.LoadingOverlay('spinner', 'Loading...');
|
|
await ctx.views.push(loadingOverlay);
|
|
|
|
if(!('futami' in window)) {
|
|
loadingOverlay.message = 'Loading environment...';
|
|
try {
|
|
window.futami = await FutamiCommon.load();
|
|
} catch(ex) {
|
|
console.error('Failed to load common settings.', ex);
|
|
loadingOverlay.icon = 'cross';
|
|
loadingOverlay.header = 'Failed!';
|
|
loadingOverlay.message = 'Failed to load common settings.';
|
|
return;
|
|
}
|
|
}
|
|
|
|
if(!MamiMisuzuAuth.hasInfo()) {
|
|
loadingOverlay.message = 'Fetching credentials...';
|
|
try {
|
|
const auth = await MamiMisuzuAuth.update();
|
|
if(!auth.ok)
|
|
throw 'Authentication failed.';
|
|
} catch(ex) {
|
|
console.error(ex);
|
|
location.assign(futami.get('login'));
|
|
return;
|
|
}
|
|
|
|
setInterval(() => {
|
|
MamiMisuzuAuth.update()
|
|
.then(auth => {
|
|
if(!auth.ok)
|
|
location.assign(futami.get('login'));
|
|
})
|
|
}, 600000);
|
|
}
|
|
|
|
|
|
loadingOverlay.message = 'Loading settings...';
|
|
|
|
const settings = new MamiSettings(args.settingsPrefix, ctx.events.scopeTo('settings'));
|
|
ctx.settings = settings;
|
|
|
|
settings.define('style').default('dark').create();
|
|
settings.define('compactView').default(false).create();
|
|
settings.define('autoScroll').default(true).create();
|
|
settings.define('closeTabConfirm').default(false).create();
|
|
settings.define('autoCloseUserContext').default(true).create();
|
|
settings.define('preventOverflow').default(false).create();
|
|
settings.define('expandTextBox').default(false).create();
|
|
settings.define('eepromAutoInsert').default(true).create();
|
|
settings.define('autoEmbedV1').default(false).create();
|
|
settings.define('autoEmbedPlay').default(false).create();
|
|
settings.define('soundEnable').default(true).critical().create();
|
|
settings.define('soundPack').default('').create();
|
|
settings.define('soundVolume').default(80).min(0).max(100).create();
|
|
settings.define('soundEnableJoin').default(true).create();
|
|
settings.define('soundEnableLeave').default(true).create();
|
|
settings.define('soundEnableError').default(true).create();
|
|
settings.define('soundEnableServer').default(true).create();
|
|
settings.define('soundEnableIncoming').default(true).create();
|
|
settings.define('onlySoundOnMention').default(false).create();
|
|
settings.define('soundEnableOutgoing').default(true).create();
|
|
settings.define('soundEnablePrivate').default(true).create();
|
|
settings.define('soundEnableForceLeave').default(true).create();
|
|
settings.define('minecraft').type(['no', 'yes', 'old']).default('no').create();
|
|
settings.define('windowsLiveMessenger').default(false).create();
|
|
settings.define('seinfeld').default(false).create();
|
|
settings.define('flashTitle').default(true).create();
|
|
settings.define('onlyConnectWhenVisible2').default(MamiIsMobileDevice()).create();
|
|
settings.define('playJokeSounds').default(true).create();
|
|
settings.define('weeaboo').default(false).create();
|
|
settings.define('motivationalImages').default(false).create();
|
|
settings.define('motivationalVideos').default(false).create();
|
|
settings.define('osuKeys').default(false).create();
|
|
settings.define('osuKeysV2').type(['no', 'yes', 'rng']).default('no').create();
|
|
settings.define('explosionRadius').default(20).min(0).create();
|
|
settings.define('dumpPackets').default(FUTAMI_DEBUG).create();
|
|
settings.define('dumpEvents').default(FUTAMI_DEBUG).create();
|
|
settings.define('marqueeAllNames').default(false).create();
|
|
settings.define('dbgAnimDurationMulti').default(1).min(0).max(10).create();
|
|
settings.define('newLineOnEnter').default(false).create();
|
|
settings.define('keepEmotePickerOpen').default(true).create();
|
|
settings.define('doNotMarkLinksAsVisited').default(false).create();
|
|
settings.define('showMarkupSelector').type(['always', 'focus', 'never']).default('always').create();
|
|
settings.define('dbgEEPROMv2').default(false).create();
|
|
|
|
const noNotifSupport = !('Notification' in window);
|
|
settings.define('enableNotifications').default(false).immutable(noNotifSupport).critical().create();
|
|
settings.define('notificationShowMessage').default(false).immutable(noNotifSupport).create();
|
|
settings.define('notificationTriggers').default('').immutable(noNotifSupport).create();
|
|
|
|
|
|
loadingOverlay.message = 'Loading sounds...';
|
|
const soundCtx = new MamiSoundContext;
|
|
ctx.sound = soundCtx;
|
|
|
|
futami.getJson('sounds2')
|
|
.catch(ex => { console.error('Failed to load sound library and packs.', ex); })
|
|
.then(sounds => {
|
|
if(Array.isArray(sounds.library))
|
|
soundCtx.library.register(sounds.library, true);
|
|
|
|
if(Array.isArray(sounds.packs)) {
|
|
soundCtx.packs.register(sounds.packs, true);
|
|
settings.touch('soundPack', true);
|
|
}
|
|
});
|
|
|
|
MamiDetectAutoPlay()
|
|
.then(canAutoPlay => {
|
|
if(canAutoPlay) return;
|
|
|
|
settings.set('soundEnable', false);
|
|
settings.virtualise('soundEnable');
|
|
});
|
|
|
|
settings.watch('soundEnable', ev => {
|
|
if(ev.detail.value) {
|
|
if(!soundCtx.ready)
|
|
soundCtx.reset();
|
|
|
|
settings.touch('soundVolume');
|
|
settings.touch('soundPack', true);
|
|
|
|
// do we need to do this?
|
|
if(!ev.detail.initial && !ev.detail.silent && ev.detail.local)
|
|
soundCtx.library.play(soundCtx.pack.getEventSound('server'));
|
|
}
|
|
|
|
soundCtx.muted = !ev.detail.value;
|
|
});
|
|
|
|
settings.watch('soundPack', ev => {
|
|
const packs = soundCtx.packs;
|
|
let packName = ev.detail.value;
|
|
|
|
if(packName === '') {
|
|
const names = packs.names;
|
|
if(names.length < 1)
|
|
return;
|
|
|
|
packName = names[0];
|
|
} else if(!packs.has(packName))
|
|
return;
|
|
|
|
soundCtx.pack = packs.get(packName);
|
|
|
|
if(!ev.detail.initial && !ev.detail.silent && ev.detail.local)
|
|
soundCtx.library.play(soundCtx.pack.getEventSound('server'));
|
|
});
|
|
|
|
settings.watch('soundVolume', ev => {
|
|
soundCtx.volume = ev.detail.value / 100;
|
|
})
|
|
|
|
|
|
// loading these asynchronously makes them not show up in the backlog
|
|
// revisit when emote reparsing is implemented
|
|
loadingOverlay.message = 'Loading emoticons...';
|
|
try {
|
|
MamiEmotes.load(await futami.getApiJson('/v1/emotes'));
|
|
} catch(ex) {
|
|
console.error('Failed to load emoticons.', ex);
|
|
}
|
|
|
|
const onHashChange = () => {
|
|
if(location.hash === '#reset') {
|
|
settings.clear(true);
|
|
location.assign('/');
|
|
}
|
|
};
|
|
|
|
window.addEventListener('hashchange', onHashChange);
|
|
onHashChange();
|
|
|
|
window.addEventListener('keydown', ev => {
|
|
if(ev.altKey && ev.shiftKey && (ev.key === 'R' || ev.key === 'r'))
|
|
location.hash = 'reset';
|
|
});
|
|
|
|
|
|
loadingOverlay.message = 'Preparing UI...';
|
|
|
|
ctx.textTriggers = new MamiTextTriggers;
|
|
|
|
const sidebar = new MamiSidebar;
|
|
|
|
MamiCompat('Umi.UI.Menus.Add', {
|
|
value: (baseId, title) => {
|
|
sidebar.createPanel({
|
|
name: `compat:${baseId}`,
|
|
text: title,
|
|
createdButton: button => {
|
|
button.element.id = `umi-menu-icons-${baseId}`;
|
|
button.element.append($element('div', { className: `sidebar__selector-mode--${baseId}` }));
|
|
},
|
|
element: $element('div', { 'class': `sidebar__menu--${baseId}`, id: `umi-menus-${baseId}` }),
|
|
});
|
|
}
|
|
});
|
|
MamiCompat('Umi.UI.Menus.Get', {
|
|
value: (baseId, icon) => {
|
|
const info = sidebar[icon ? 'getButton' : 'getPanel'](`compat:${baseId}`);
|
|
if(info === undefined)
|
|
return null;
|
|
|
|
return icon ? info.element : info.element.firstElementChild;
|
|
},
|
|
});
|
|
MamiCompat('Umi.UI.Menus.Attention', {
|
|
value: baseId => {
|
|
sidebar.getButton(`compat:${baseId}`)?.attention();
|
|
},
|
|
});
|
|
|
|
|
|
const chatForm = new MamiChatForm(ctx.events.scopeTo('form'));
|
|
|
|
window.addEventListener('keydown', ev => {
|
|
if((ev.ctrlKey && ev.key !== 'v') || ev.altKey)
|
|
return;
|
|
|
|
if(!ev.target.matches('input, textarea, select, button'))
|
|
chatForm.focus();
|
|
});
|
|
|
|
settings.watch('showMarkupSelector', ev => {
|
|
chatForm.markup.visible = ev.detail.value !== 'never';
|
|
Umi.UI.Messages.ScrollIfNeeded(chatForm.markup.height);
|
|
});
|
|
|
|
let colourPicker, colourPickerVisible = false;
|
|
for(const bbCode of UmiBBCodes)
|
|
if(bbCode.text !== undefined)
|
|
chatForm.markup.createButton({
|
|
title: bbCode.text,
|
|
style: bbCode.style,
|
|
onclick: ev => {
|
|
if(bbCode.tag === 'color') {
|
|
if(colourPicker === undefined) {
|
|
colourPicker = new MamiColourPicker({ presets: futami.get('colours') });
|
|
layout.element.appendChild(colourPicker.element);
|
|
}
|
|
|
|
if(colourPickerVisible) {
|
|
colourPicker.close();
|
|
} else {
|
|
colourPickerVisible = true;
|
|
colourPicker.dialog(ev)
|
|
.then(colour => { chatForm.input.insertAroundSelection(`[${bbCode.tag}=${MamiColour.hex(colour)}]`, `[/${bbCode.tag}]`) })
|
|
.catch(() => {}) // noop so the console stops screaming
|
|
.finally(() => colourPickerVisible = false);
|
|
}
|
|
} else
|
|
chatForm.input.insertAroundSelection(`[${bbCode.tag}${(bbCode.arg ? '=' : '')}]`, `[/${bbCode.tag}]`);
|
|
},
|
|
});
|
|
|
|
|
|
const layout = new Umi.UI.ChatLayout(chatForm, sidebar);
|
|
await ctx.views.unshift(layout);
|
|
|
|
ctx.events.watch('form:resize', ev => {
|
|
Umi.UI.Messages.ScrollIfNeeded(ev.detail.diffHeight);
|
|
});
|
|
|
|
settings.watch('style', ev => {
|
|
for(const className of layout.element.classList)
|
|
if(className.startsWith('umi--'))
|
|
layout.element.classList.remove(className);
|
|
layout.element.classList.add(`umi--${ev.detail.value}`);
|
|
|
|
UmiThemeApply(ev.detail.value);
|
|
});
|
|
settings.watch('compactView', ev => {
|
|
layout.element.classList.toggle('chat--compact', ev.detail.value);
|
|
layout.interface.messageList.element.classList.toggle('chat--compact', ev.detail.value);
|
|
});
|
|
settings.watch('preventOverflow', ev => { args.parent.classList.toggle('prevent-overflow', ev.detail.value); });
|
|
settings.watch('doNotMarkLinksAsVisited', ev => { layout.interface.messageList.element.classList.toggle('mami-do-not-mark-links-as-visited', ev.detail.value); });
|
|
settings.watch('newLineOnEnter', ev => { chatForm.input.newLineOnEnter = ev.detail.value; });
|
|
settings.watch('expandTextBox', ev => { chatForm.input.growInputField = ev.detail.value; });
|
|
|
|
settings.watch('minecraft', ev => {
|
|
if(ev.detail.initial && ev.detail.value === 'no')
|
|
return;
|
|
|
|
soundCtx.library.play((() => {
|
|
if(ev.detail.initial)
|
|
return 'minecraft:nether:enter';
|
|
if(ev.detail.value === 'yes')
|
|
return 'minecraft:door:open';
|
|
if(ev.detail.value === 'old')
|
|
return 'minecraft:door:open-old';
|
|
return soundCtx.pack.getEventSound('join');
|
|
})());
|
|
});
|
|
|
|
settings.watch('enableNotifications', ev => {
|
|
if(!ev.detail.value || !('Notification' in window)
|
|
|| (Notification.permission === 'granted' && Notification.permission !== 'denied'))
|
|
return;
|
|
|
|
Notification.requestPermission()
|
|
.then(perm => {
|
|
if(perm !== 'granted')
|
|
settings.set('enableNotifications', false);
|
|
});
|
|
});
|
|
|
|
settings.watch('playJokeSounds', ev => {
|
|
if(!ev.detail.value) return;
|
|
|
|
if(!ctx.textTriggers.hasTriggers)
|
|
futami.getJson('texttriggers').then(trigInfos => ctx.textTriggers.addTriggers(trigInfos));
|
|
});
|
|
|
|
settings.watch('weeaboo', ev => {
|
|
if(ev.detail.value) Weeaboo.init();
|
|
});
|
|
|
|
settings.watch('osuKeysV2', ev => {
|
|
// migrate old value
|
|
if(ev.detail.initial) {
|
|
if(settings.has('osuKeys')) {
|
|
settings.set('osuKeysV2', settings.get('osuKeys') ? 'yes' : 'no');
|
|
settings.delete('osuKeys');
|
|
return;
|
|
}
|
|
}
|
|
|
|
OsuKeys.enable = ev.detail.value !== 'no';
|
|
OsuKeys.randomRate = ev.detail.value === 'rng';
|
|
});
|
|
|
|
|
|
loadingOverlay.message = 'Building menus...';
|
|
|
|
MamiCompat('Umi.Parser.SockChatBBcode.EmbedStub', { value: () => {} }); // intentionally a no-op
|
|
MamiCompat('Umi.UI.View.SetText', { value: text => { chatForm.input.setText(text); } });
|
|
|
|
const sbUsers = new MamiSidebarPanelUsers;
|
|
sidebar.createPanel(sbUsers);
|
|
|
|
sbUsers.addOption({
|
|
name: 'profile',
|
|
text: 'View profile',
|
|
onclick: entry => window.open(futami.get('profile').replace('{user:id}', entry.id), '_blank'),
|
|
});
|
|
sbUsers.addOption({
|
|
name: 'action',
|
|
text: 'Describe action',
|
|
condition: entry => Umi.User.getCurrentUser()?.id === entry.id,
|
|
onclick: entry => { chatForm.input.setText('/me '); },
|
|
});
|
|
sbUsers.addOption({
|
|
name: 'nick',
|
|
text: 'Set nickname',
|
|
condition: entry => Umi.User.getCurrentUser()?.id === entry.id && Umi.User.getCurrentUser().perms.canSetNick,
|
|
onclick: entry => { chatForm.input.setText('/nick '); },
|
|
});
|
|
sbUsers.addOption({
|
|
name: 'bans',
|
|
text: 'View bans',
|
|
condition: entry => Umi.User.getCurrentUser()?.id === entry.id && Umi.User.getCurrentUser().perms.canKick,
|
|
onclick: entry => { Umi.Server.sendMessage('/bans'); },
|
|
});
|
|
sbUsers.addOption({
|
|
name: 'dm',
|
|
text: 'Send direct message',
|
|
condition: entry => Umi.User.getCurrentUser()?.id !== entry.id,
|
|
onclick: entry => { chatForm.input.setText(`/msg ${entry.name} `); },
|
|
});
|
|
sbUsers.addOption({
|
|
name: 'kick',
|
|
text: 'Kick from chat',
|
|
condition: entry => Umi.User.hasCurrentUser() && Umi.User.getCurrentUser().id !== entry.id && Umi.User.getCurrentUser().perms.canKick,
|
|
onclick: entry => { chatForm.input.setText(`/kick ${entry.name} `); },
|
|
});
|
|
sbUsers.addOption({
|
|
name: 'ipaddr',
|
|
text: 'View IP address',
|
|
condition: entry => Umi.User.hasCurrentUser() && Umi.User.getCurrentUser().id !== entry.id && Umi.User.getCurrentUser().perms.canKick,
|
|
onclick: entry => { Umi.Server.sendMessage(`/ip ${entry.name}`); },
|
|
});
|
|
|
|
|
|
const sbChannels = new MamiSidebarPanelChannels;
|
|
sidebar.createPanel(sbChannels);
|
|
|
|
|
|
const sbSettings = new MamiSidebarPanelSettings(settings);
|
|
sidebar.createPanel(sbSettings);
|
|
|
|
sbSettings.category(category => {
|
|
category.header('Interface');
|
|
category.setting('style').title('Theme').type('select').options(() => {
|
|
const themes = {};
|
|
for(const theme of UmiThemes)
|
|
themes[theme.id] = theme.name;
|
|
|
|
return themes;
|
|
}).done();
|
|
category.setting('compactView').title('Use compact view').done();
|
|
category.setting('autoScroll').title('Enable auto scroll').done();
|
|
category.setting('closeTabConfirm').title('Confirm tab close').done();
|
|
category.setting('autoCloseUserContext').title('Auto-close user menus').done();
|
|
category.setting('doNotMarkLinksAsVisited').title("Don't mark links as visited").done();
|
|
});
|
|
sbSettings.category(category => {
|
|
category.header('Text');
|
|
category.setting('preventOverflow').title('Prevent overflow').done();
|
|
category.setting('expandTextBox').title('Grow input box while typing').done();
|
|
category.setting('eepromAutoInsert').title('Auto-insert uploads').done();
|
|
category.setting('autoEmbedV1').title('Auto-embed media').done();
|
|
category.setting('autoEmbedPlay').title('Auto-play embedded media').done();
|
|
category.setting('keepEmotePickerOpen').title('Keep emoticon picker open').done();
|
|
category.setting('newLineOnEnter').title('Swap Enter and Shift+Enter behaviour').done();
|
|
category.setting('showMarkupSelector').title('Show markup buttons').type('checkbox').on('always').off('never').done();
|
|
});
|
|
sbSettings.category(category => {
|
|
category.header('Notifications');
|
|
category.setting('flashTitle').title('Strobe title on new message').done();
|
|
category.setting('enableNotifications').title('Show notifications').done();
|
|
category.setting('notificationShowMessage').title('Show contents of message').done();
|
|
category.setting('notificationTriggers').title('Triggers').done();
|
|
});
|
|
sbSettings.category(category => {
|
|
category.header('Sound');
|
|
category.setting('soundEnable').title('Enable sound').done();
|
|
category.setting('soundPack').title('Sound pack').type('select').options(() => {
|
|
const options = { '': 'Default' };
|
|
|
|
for(const name of soundCtx.packs.names)
|
|
options[name] = soundCtx.packs.info(name).title;
|
|
|
|
return options;
|
|
}).done();
|
|
category.setting('soundVolume').title('Sound volume').type('range').done();
|
|
category.setting('soundEnableJoin').title('Play join sound').done();
|
|
category.setting('soundEnableLeave').title('Play leave sound').done();
|
|
category.setting('soundEnableError').title('Play error sound').done();
|
|
category.setting('soundEnableServer').title('Play server message sound').done();
|
|
category.setting('soundEnableIncoming').title('Play receive message sound').done();
|
|
category.setting('onlySoundOnMention').title('Only plays sounds when you are mentioned').done();
|
|
category.setting('soundEnableOutgoing').title('Play send message sound').done();
|
|
category.setting('soundEnablePrivate').title('Play private message sound').done();
|
|
category.setting('soundEnableForceLeave').title('Play kick sound').done();
|
|
category.setting('minecraft').title('Minecraft').type('select').options(() => {
|
|
return {
|
|
'no': 'No Minecraft',
|
|
'yes': 'Yes Minecraft',
|
|
'old': 'Old Minecraft',
|
|
};
|
|
}).done();
|
|
category.setting('windowsLiveMessenger').title('Windows Live Messenger').done();
|
|
category.setting('seinfeld').title('Seinfeld').done();
|
|
});
|
|
sbSettings.category(category => {
|
|
category.header('Misc');
|
|
category.setting('onlyConnectWhenVisible2').title('Only connect when the tab is in the foreground').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.',
|
|
]).done();
|
|
category.setting('playJokeSounds').title('Run joke triggers').done();
|
|
category.setting('weeaboo').title('Weeaboo').done();
|
|
category.setting('motivationalImages').title('Make images motivational').done();
|
|
category.setting('motivationalVideos').title('Make videos motivational').done();
|
|
category.setting('osuKeysV2').title('osu! keyboard sounds').type('select').options(() => {
|
|
return {
|
|
'no': 'Off',
|
|
'yes': 'On',
|
|
'rng': 'On, random pitch',
|
|
};
|
|
}).done();
|
|
category.setting('explosionRadius').title('Messages to keep on clear').done();
|
|
});
|
|
sbSettings.category(category => {
|
|
category.header('Actions');
|
|
category.button('Open compatibility client', () => { window.open(window.AMI_URL, '_blank', 'noopener'); });
|
|
category.button('Manual reconnect', async button => {
|
|
const textOrig = button.textContent;
|
|
let lock = 10;
|
|
|
|
button.disabled = true;
|
|
button.textContent = 'Reconnecting...';
|
|
try {
|
|
await ctx.conMan.start();
|
|
|
|
while(--lock > 0) {
|
|
button.textContent = textOrig + ` (${lock}s)`;
|
|
await MamiSleep(1000);
|
|
}
|
|
} finally {
|
|
button.textContent = textOrig;
|
|
button.disabled = false;
|
|
}
|
|
});
|
|
category.button('Reload emoticons', async button => {
|
|
const textOrig = button.textContent;
|
|
|
|
button.disabled = true;
|
|
button.textContent = 'Reloading emoticons...';
|
|
try {
|
|
const emotes = await futami.getApiJson('/v1/emotes', true);
|
|
MamiEmotes.clear();
|
|
MamiEmotes.load(emotes);
|
|
} finally {
|
|
button.textContent = textOrig;
|
|
button.disabled = false;
|
|
}
|
|
});
|
|
category.button('Reload sound library', async button => {
|
|
const textOrig = button.textContent;
|
|
|
|
button.disabled = true;
|
|
button.textContent = 'Reloading sound library...';
|
|
try {
|
|
const sounds = await futami.getJson('sounds2');
|
|
if(Array.isArray(sounds.library))
|
|
soundCtx.library.register(sounds.library, true);
|
|
|
|
if(Array.isArray(sounds.packs)) {
|
|
soundCtx.packs.register(sounds.packs, true);
|
|
settings.touch('soundPack', true);
|
|
}
|
|
} finally {
|
|
button.textContent = textOrig;
|
|
button.disabled = false;
|
|
}
|
|
});
|
|
category.button('Reload joke triggers', async button => {
|
|
const textOrig = button.textContent;
|
|
|
|
button.disabled = true;
|
|
button.textContent = 'Reloading joke triggers...';
|
|
try {
|
|
const triggers = await futami.getJson('texttriggers', true);
|
|
ctx.textTriggers.clearTriggers();
|
|
ctx.textTriggers.addTriggers(triggers)
|
|
} finally {
|
|
button.textContent = textOrig;
|
|
button.disabled = false;
|
|
}
|
|
});
|
|
});
|
|
sbSettings.category(category => {
|
|
category.header('Settings');
|
|
category.button('Import settings', () => {
|
|
(new MamiSettingsBackup(settings)).importUpload(args.parent);
|
|
}, ['Your current settings will be replaced with the ones in the export.', 'Are you sure you want to continue?']);
|
|
category.button('Export settings', () => {
|
|
const user = Umi.User.getCurrentUser();
|
|
let fileName;
|
|
if(user !== null)
|
|
fileName = `${user.name}'s settings.mami`;
|
|
|
|
(new MamiSettingsBackup(settings)).exportDownload(args.parent, fileName);
|
|
});
|
|
category.button('Reset settings', () => {
|
|
settings.clear();
|
|
}, ['This will reset all your settings to their defaults values.', 'Are you sure you want to do this?']);
|
|
});
|
|
sbSettings.category(category => {
|
|
category.header('Debug');
|
|
category.collapse();
|
|
category.warning("Only touch these settings if you're ABSOLUTELY sure you know what you're doing, you're on your own if you break something.");
|
|
category.setting('dumpPackets').title('Dump packets to console').done();
|
|
category.setting('dumpEvents').title('Dump events to console').done();
|
|
category.setting('dbgEEPROMv2').title('Use EEPROM v2 API (requires reload)').done();
|
|
category.setting('marqueeAllNames').title('Apply marquee on everyone (requires reload)').done();
|
|
category.setting('dbgAnimDurationMulti').title('Animation multiplier').type('range').done();
|
|
category.button('Test kick/ban notice', async button => {
|
|
button.disabled = true;
|
|
await ctx.views.push(new MamiForceDisconnectNotice({ perma: true, type: 'ban' }));
|
|
await MamiSleep(5000);
|
|
await ctx.views.pop();
|
|
button.disabled = false;
|
|
});
|
|
category.button('You are an idiot!', async button => {
|
|
button.disabled = true;
|
|
await ctx.views.push(new MamiYouAreAnIdiot(soundCtx.library, ctx.views));
|
|
button.disabled = false;
|
|
});
|
|
category.button('Sound test', async (button, ev) => {
|
|
button.disabled = true;
|
|
await ctx.views.push(new MamiSoundTest(
|
|
settings,
|
|
soundCtx.audio,
|
|
soundCtx.manager,
|
|
soundCtx.library,
|
|
[ev.clientX, ev.clientY],
|
|
));
|
|
button.disabled = false;
|
|
});
|
|
category.button('Reset audio context', () => {
|
|
soundCtx.reset();
|
|
});
|
|
});
|
|
|
|
|
|
const sbUploads = new MamiSidebarPanelUploads;
|
|
sidebar.createPanel(sbUploads);
|
|
|
|
const sbActToggle = new MamiSidebarActionToggle(sidebar);
|
|
sidebar.createAction(sbActToggle);
|
|
if(window.innerWidth < 800)
|
|
sbActToggle.click();
|
|
|
|
sidebar.createAction(new MamiSidebarActionScroll(settings));
|
|
sidebar.createAction(new MamiSidebarActionSound(settings));
|
|
sidebar.createAction(new MamiSidebarActionCollapseAll);
|
|
sidebar.createAction(new MamiSidebarActionClearBacklog(settings, soundCtx.library, ctx.msgbox));
|
|
|
|
const pingIndicator = new MamiPingIndicator;
|
|
const sbActPing = new MamiSidebarActionPing(pingIndicator, ctx.msgbox);
|
|
sidebar.createAction(sbActPing);
|
|
|
|
|
|
let emotePicker, emotePickerVisible = false;
|
|
chatForm.input.createButton({
|
|
title: 'Emoticons',
|
|
icon: 'fas fa-smile',
|
|
onclick: (ev, button) => {
|
|
if(emotePicker === undefined) {
|
|
emotePicker = new MamiEmotePicker({
|
|
onClose: () => { emotePickerVisible = false; },
|
|
onPick: emote => {
|
|
chatForm.input.insertAtCursor(`:${emote.strings[0]}:`);
|
|
},
|
|
getEmotes: () => MamiEmotes.all(Umi.User.getCurrentUser().perms.rank),
|
|
setKeepOpenOnPick: value => { settings.set('keepEmotePickerOpen', value); },
|
|
});
|
|
layout.element.appendChild(emotePicker.element);
|
|
settings.watch('keepEmotePickerOpen', ev => { emotePicker.keepOpenOnPick = ev.detail.value; });
|
|
}
|
|
|
|
if(emotePickerVisible) {
|
|
emotePicker.close();
|
|
} else {
|
|
emotePickerVisible = true;
|
|
emotePicker.dialog(button);
|
|
}
|
|
},
|
|
});
|
|
|
|
|
|
ctx.eeprom = settings.get('dbgEEPROMv2')
|
|
? new MamiEEPROMv2(futami.get('api'), MamiMisuzuAuth.getLine, 'chat')
|
|
: new MamiEEPROM(futami.get('eeprom2'), MamiMisuzuAuth.getLine);
|
|
|
|
sbUploads.addOption({
|
|
name: 'view',
|
|
text: 'View upload',
|
|
condition: entry => entry.uploadInfo !== undefined,
|
|
onclick: entry => window.open(entry.uploadInfo.url),
|
|
});
|
|
sbUploads.addOption({
|
|
name: 'insert',
|
|
text: 'Insert into message',
|
|
condition: entry => entry.uploadInfo !== undefined,
|
|
onclick: entry => {
|
|
const upload = entry.uploadInfo;
|
|
|
|
let text;
|
|
let url = upload.url;
|
|
if(upload.isImage()) {
|
|
text = `[img]${url}[/img]`;
|
|
} else if(upload.isAudio()) {
|
|
text = `[audio]${url}[/audio]`;
|
|
} else if(upload.isVideo()) {
|
|
text = `[video]${url}[/video]`;
|
|
} else {
|
|
if(url.startsWith('//'))
|
|
url = location.protocol + url;
|
|
text = url;
|
|
}
|
|
|
|
chatForm.input.insertAtCursor(text);
|
|
},
|
|
});
|
|
sbUploads.addOption({
|
|
name: 'delete',
|
|
text: 'Delete upload',
|
|
condition: entry => entry.uploadInfo !== undefined,
|
|
onclick: async entry => {
|
|
try {
|
|
await ctx.eeprom.delete(entry.uploadInfo);
|
|
sbUploads.deleteEntry(entry);
|
|
} catch(ex) {
|
|
console.error(ex);
|
|
await ctx.msgbox.show({ body: ['An error occurred while trying to delete an uploaded file:', ex] });
|
|
}
|
|
},
|
|
});
|
|
|
|
const doUpload = async file => {
|
|
const entry = sbUploads.createEntry(file);
|
|
|
|
const task = ctx.eeprom.create(file);
|
|
entry.addOption({
|
|
name: 'cancel',
|
|
text: 'Cancel upload',
|
|
onclick: () => { task.abort(); },
|
|
});
|
|
|
|
try {
|
|
const fileInfo = await task.start(prog => { entry.progress = prog.progress; });
|
|
|
|
entry.optionsVisible = false;
|
|
entry.uploadInfo = fileInfo;
|
|
entry.removeOption('cancel');
|
|
entry.nukeProgress();
|
|
sbUploads.reloadOptionsFor(entry);
|
|
|
|
if(settings.get('eepromAutoInsert'))
|
|
entry.clickOption('insert');
|
|
} catch(ex) {
|
|
if(ex !== '') {
|
|
console.error(ex);
|
|
ctx.msgbox.show({ body: ['An error occurred while trying to upload a file:', ex] });
|
|
}
|
|
|
|
sbUploads.deleteEntry(entry);
|
|
}
|
|
};
|
|
|
|
const uploadForm = $element('input', {
|
|
type: 'file',
|
|
multiple: true,
|
|
style: { display: 'none' },
|
|
onchange: ev => {
|
|
for(const file of ev.target.files)
|
|
doUpload(file);
|
|
},
|
|
});
|
|
args.parent.appendChild(uploadForm);
|
|
|
|
chatForm.input.createButton({
|
|
title: 'Upload',
|
|
icon: 'fas fa-file-upload',
|
|
onclick: () => { uploadForm.click(); },
|
|
});
|
|
|
|
ctx.events.watch('form:upload', ev => {
|
|
console.info(ev);
|
|
for(const file of ev.detail.files)
|
|
doUpload(file);
|
|
});
|
|
|
|
// figure out how to display a UI for this someday
|
|
//args.parent.addEventListener('dragenter', ev => { console.info('dragenter', ev); });
|
|
//args.parent.addEventListener('dragleave', ev => { console.info('dragleave', ev); });
|
|
args.parent.addEventListener('dragover', ev => { ev.preventDefault(); });
|
|
args.parent.addEventListener('drop', ev => {
|
|
if(ev.dataTransfer === undefined || ev.dataTransfer === null || ev.dataTransfer.files.length < 1)
|
|
return;
|
|
|
|
ev.preventDefault();
|
|
|
|
for(const file of ev.dataTransfer.files) {
|
|
if(file.name.slice(-5) === '.mami') {
|
|
ctx.msgbox.show({
|
|
body: [
|
|
'This file appears to be a settings export.',
|
|
'Do you want to import it? This will overwrite your existing settings!',
|
|
],
|
|
yes: true,
|
|
no: true,
|
|
})
|
|
.then(() => {
|
|
(new MamiSettingsBackup(settings)).importFile(file);
|
|
})
|
|
.catch(() => {
|
|
if(doUpload !== undefined)
|
|
doUpload(file);
|
|
});
|
|
} else if(doUpload !== undefined)
|
|
doUpload(file);
|
|
}
|
|
});
|
|
|
|
window.addEventListener('beforeunload', function(ev) {
|
|
if(settings.get('closeTabConfirm')) {
|
|
ev.preventDefault();
|
|
return ev.returnValue = 'Are you sure you want to close the tab?';
|
|
}
|
|
|
|
ctx.isUnloading = true;
|
|
});
|
|
|
|
|
|
loadingOverlay.message = 'Connecting...';
|
|
|
|
const setLoadingOverlay = async (icon, header, message, optional) => {
|
|
const currentView = ctx.views.current;
|
|
|
|
if('icon' in currentView) {
|
|
currentView.icon = icon;
|
|
currentView.header = header;
|
|
currentView.message = message;
|
|
return currentView;
|
|
}
|
|
|
|
if(!optional) {
|
|
const loading = new Umi.UI.LoadingOverlay(icon, header, message);
|
|
await ctx.views.push(loading);
|
|
}
|
|
};
|
|
|
|
const sockChat = new MamiSockChat(ctx.events.scopeTo('sockchat'));
|
|
const conMan = new MamiConnectionManager(sockChat, settings, futami.get('servers'), ctx.events.scopeTo('conn'));
|
|
ctx.conMan = conMan;
|
|
|
|
ctx.events.watch('form:send', ev => {
|
|
sockChat.client.sendMessage(ev.detail.text);
|
|
});
|
|
|
|
sbChannels.onClickEntry = async info => {
|
|
await sockChat.client.switchChannel(info);
|
|
|
|
// hack for DM channels
|
|
if(info.isUserChannel) {
|
|
sbChannels.setActiveEntry(info.name);
|
|
Umi.UI.Messages.SwitchChannel(info);
|
|
}
|
|
};
|
|
|
|
let sockChatRestarting;
|
|
|
|
const sockChatReconnect = () => {
|
|
if(conMan.isActive)
|
|
return;
|
|
|
|
sbActPing.pingMs = -1;
|
|
pingIndicator.strength = -1;
|
|
|
|
const reconManAttempt = ev => {
|
|
if(sockChatRestarting || ev.detail.delay > 2000)
|
|
setLoadingOverlay('spinner', 'Connecting...', 'Connecting to server...');
|
|
};
|
|
const reconManFail = ev => {
|
|
// this is absolutely disgusting but i really don't care right now sorry
|
|
if(sockChatRestarting || ev.detail.delay > 2000)
|
|
setLoadingOverlay('unlink', sockChatRestarting ? 'Restarting...' : 'Disconnected', `Attempting to reconnect in ${(ev.detail.delay / 1000).toLocaleString()} seconds...<br><a href="javascript:void(0)" onclick="mami.conMan.force()">Retry now</a>`);
|
|
};
|
|
const reconManSuccess = () => {
|
|
conMan.unwatch('success', reconManSuccess);
|
|
conMan.unwatch('attempt', reconManAttempt);
|
|
conMan.unwatch('fail', reconManFail);
|
|
};
|
|
|
|
conMan.watch('attempt', reconManAttempt);
|
|
conMan.watch('fail', reconManFail);
|
|
conMan.watch('success', reconManSuccess);
|
|
|
|
conMan.start();
|
|
};
|
|
|
|
const sockChatHandlers = new MamiSockChatHandlers(
|
|
ctx, sockChat, setLoadingOverlay, sockChatReconnect, pingIndicator,
|
|
sbActPing, sbChannels, sbUsers
|
|
);
|
|
settings.watch('dumpEvents', ev => sockChatHandlers.setDumpEvents(ev.detail.value));
|
|
settings.watch('dumpPackets', ev => sockChat.setDumpPackets(ev.detail.value));
|
|
sockChatHandlers.register();
|
|
|
|
const conManAttempt = ev => {
|
|
let message = ev.detail.attempt > 2 ? `Attempt ${ev.detail.attempt}...` : 'Connecting to server...';
|
|
setLoadingOverlay('spinner', 'Connecting...', message);
|
|
};
|
|
const conManFail = ev => {
|
|
setLoadingOverlay('cross', 'Failed to connect', `Retrying in ${ev.detail.delay / 1000} seconds...`);
|
|
};
|
|
const conManSuccess = () => {
|
|
conMan.unwatch('success', conManSuccess);
|
|
conMan.unwatch('attempt', conManAttempt);
|
|
conMan.unwatch('fail', conManFail);
|
|
};
|
|
|
|
conMan.watch('success', conManSuccess);
|
|
conMan.watch('attempt', conManAttempt);
|
|
conMan.watch('fail', conManFail);
|
|
|
|
await sockChat.create();
|
|
conMan.client = sockChat;
|
|
await conMan.start();
|
|
|
|
return ctx;
|
|
};
|
|
|
|
(() => {
|
|
const eventTarget = new MamiEventTargetWindow;
|
|
Object.defineProperty(window, 'mamiEventTarget', { enumerable: true, value: eventTarget });
|
|
|
|
MamiInit({
|
|
eventTarget: eventTarget,
|
|
}).then(mami => {
|
|
//Object.defineProperty(window, 'mami', { enumerable: true, value: mami });
|
|
});
|
|
})();
|
|
|
|
const MamiDbgCreateFloatingInstance = async () => {
|
|
if(!FUTAMI_DEBUG)
|
|
return;
|
|
|
|
const prefix = $rngs(8);
|
|
const parent = $element('div', {
|
|
style: {
|
|
position: 'absolute',
|
|
bottom: '100px',
|
|
right: '100px',
|
|
zIndex: '9001',
|
|
width: '640px',
|
|
height: '480px',
|
|
background: '#0f0',
|
|
},
|
|
});
|
|
|
|
document.body.appendChild(parent);
|
|
|
|
return await MamiInit({
|
|
parent: parent,
|
|
settingsPrefix: `dbg:${prefix}:`,
|
|
eventTarget: mamiEventTarget.scopeTo(prefix),
|
|
});
|
|
};
|