From f539c46954506388be3c90e15e342f8da37c0df5 Mon Sep 17 00:00:00 2001 From: flashwave Date: Mon, 22 Jan 2024 21:45:39 +0000 Subject: [PATCH] Separated settings core and UI code. --- src/mami.js/context.js | 4 + src/mami.js/main.js | 211 +++++-- src/mami.js/parsing.js | 5 +- src/mami.js/settings.js | 846 ----------------------------- src/mami.js/settings/backup.js | 126 +++++ src/mami.js/settings/scoped.js | 37 ++ src/mami.js/settings/settings.js | 151 +++++ src/mami.js/settings/virtual.js | 29 + src/mami.js/settings/webstorage.js | 18 + src/mami.js/sockchat_old.js | 19 +- src/mami.js/sound/sndpacks.js | 1 - src/mami.js/sound/umisound.js | 32 +- src/mami.js/ui/emotes.js | 2 +- src/mami.js/ui/hooks.js | 17 +- src/mami.js/ui/messages.jsx | 14 +- src/mami.js/ui/settings.js | 280 ---------- src/mami.js/ui/settings.jsx | 515 ++++++++++++++++++ src/mami.js/ui/users.js | 9 +- src/mami.js/ui/view.js | 12 +- src/mami.js/watcher.js | 67 +++ src/mami.js/websock.js | 6 +- 21 files changed, 1172 insertions(+), 1229 deletions(-) delete mode 100644 src/mami.js/settings.js create mode 100644 src/mami.js/settings/backup.js create mode 100644 src/mami.js/settings/scoped.js create mode 100644 src/mami.js/settings/settings.js create mode 100644 src/mami.js/settings/virtual.js create mode 100644 src/mami.js/settings/webstorage.js delete mode 100644 src/mami.js/ui/settings.js create mode 100644 src/mami.js/ui/settings.jsx create mode 100644 src/mami.js/watcher.js diff --git a/src/mami.js/context.js b/src/mami.js/context.js index 85b3777..0d9120d 100644 --- a/src/mami.js/context.js +++ b/src/mami.js/context.js @@ -8,6 +8,10 @@ const MamiContext = function(targetBody) { const pub = {}; + let settings; + pub.setSettings = inst => settings = inst; + pub.getSettings = () => settings; + const viewsCtx = new MamiViewsControl({ body: targetBody }); pub.getViews = () => viewsCtx; diff --git a/src/mami.js/main.js b/src/mami.js/main.js index efcc0c0..8588451 100644 --- a/src/mami.js/main.js +++ b/src/mami.js/main.js @@ -7,8 +7,14 @@ const Umi = { UI: {} }; #include messages.js #include mszauth.js #include server.js -#include settings.js #include utility.js +#include weeb.js +#include audio/context.js +#include eeprom/eeprom.js +#include settings/backup.js +#include settings/settings.js +#include sound/osukeys.js +#include sound/umisound.js #include ui/chat-layout.js #include ui/domaintrans.jsx #include ui/elems.js @@ -18,12 +24,10 @@ const Umi = { UI: {} }; #include ui/markup.js #include ui/menus.js #include ui/view.js -#include ui/settings.js +#include ui/settings.jsx #include ui/title.js #include ui/toggles.js #include ui/uploads.js -#include audio/context.js -#include eeprom/eeprom.js (async () => { const ctx = new MamiContext(document.body), @@ -98,6 +102,63 @@ const Umi = { UI: {} }; Umi.UI.Elements.MessageMenus = $i('umi-msg-menu'); + lo.setMessage('Loading settings...'); + + const settings = new MamiSettings('umi-'); + ctx.setSettings(settings); + + settings.define('style', 'string', 'dark'); + settings.define('compactView', 'boolean', false); + settings.define('autoScroll', 'boolean', true); + settings.define('closeTabConfirm', 'boolean', false); + settings.define('showChannelList', 'boolean', false); + settings.define('fancyInfo', 'boolean', true); + settings.define('autoCloseUserContext', 'boolean', true); + settings.define('enableParser', 'boolean', true); + settings.define('enableEmoticons', 'boolean', true); + settings.define('autoParseUrls', 'boolean', true); + settings.define('preventOverflow', 'boolean', false); + settings.define('expandTextBox', 'boolean', false); + settings.define('eepromAutoInsert', 'boolean', true); + settings.define('autoEmbedV1', 'boolean', false); + settings.define('soundEnable', 'boolean', true, false, true); + settings.define('soundPack', 'string', 'ajax-chat'); + settings.define('soundVolume', 'number', 80); + settings.define('soundEnableJoin', 'boolean', true); + settings.define('soundEnableLeave', 'boolean', true); + settings.define('soundEnableError', 'boolean', true); + settings.define('soundEnableServer', 'boolean', true); + settings.define('soundEnableIncoming', 'boolean', true); + settings.define('onlySoundOnMention', 'boolean', true); + settings.define('soundEnableOutgoing', 'boolean', false); + settings.define('soundEnablePrivate', 'boolean', true); + settings.define('soundEnableForceLeave', 'boolean', true); + settings.define('minecraft', ['no', 'yes', 'old'], 'no'); + settings.define('playSoundOnConnect', 'boolean', false); + settings.define('windowsLiveMessenger', 'boolean', false); + settings.define('seinfeld', 'boolean', false); + settings.define('flashTitle', 'boolean', true); + settings.define('showServerMsgInTitle', 'boolean', true); + settings.define('playJokeSounds', 'boolean', true); + settings.define('weeaboo', 'boolean', false); + settings.define('motivationalImages', 'boolean', false); + settings.define('motivationalVideos', 'boolean', false); + settings.define('osuKeys', 'boolean', false); + settings.define('osuKeysV2', ['no', 'yes', 'rng'], 'no'); + settings.define('explosionRadius', 'number', 20); + settings.define('dumpPackets', 'boolean', FUTAMI_DEBUG); + settings.define('neverUseWorker', 'boolean', false, false, true); + settings.define('forceUseWorker', 'boolean', false, false, true); + settings.define('marqueeAllNames', 'boolean', false); + settings.define('tmpDisableOldThemeSys', 'boolean', false, false, true); + settings.define('tmpSkipDomainPopUpThing', 'boolean', false, false, true); + + const noNotifSupport = !('Notification' in window); + settings.define('enableNotifications', 'boolean', false, noNotifSupport, true); + settings.define('notificationShowMessage', 'boolean', false, noNotifSupport); + settings.define('notificationTriggers', 'string', '', noNotifSupport); + + lo.setMessage('Loading sounds...'); try { const sounds = await futami.getJson('sounds2'); @@ -109,6 +170,46 @@ const Umi = { UI: {} }; console.error(ex); } + if(!await MamiDetectAutoPlay()) { + settings.set('soundEnable', false); + settings.virtualise('soundEnable'); + } + + settings.watch('soundEnable', (v, n, i) => { + if(v && !mami.hasSound()) { + mami.initSound(); + + settings.touch('soundVolume'); + settings.touch('soundPack'); + + const player = mami.getSoundPackPlayer(); + if(player !== null) + player.playEvent('server'); + } + + if(mami.hasAudio()) + mami.getAudio().setMuted(!v); + }); + + settings.watch('soundPack', (v, n, i) => { + const packs = mami.getSoundPacks(); + if(!packs.hasPack(v)) { + settings.delete(n); + return; + } + + const player = mami.getSoundPackPlayer(); + if(player !== null) { + player.loadPack(packs.getPack(v)); + if(!i) player.playEvent('server'); + } + }); + + settings.watch('soundVolume', v => { + if(mami.hasAudio()) + mami.getAudio().setVolume(v / 100); + }) + lo.setMessage('Loading emoticons...'); try { @@ -119,32 +220,13 @@ const Umi = { UI: {} }; } - lo.setMessage('Loading settings...'); - - Umi.Settings = new Umi.Settings(UmiSettings.settings); - - if(!await MamiDetectAutoPlay()) { - Umi.Settings.set('soundEnable', false); - Umi.Settings.virtualise('soundEnable'); - } - - const meta = UmiSettings.settings; - for(const setting of UmiSettings.settings) - if(setting.watcher) - Umi.Settings.watch(setting.id, setting.watcher); - - - if(!Umi.Settings.get('tmpSkipDomainPopUpThing')) + if(!settings.get('tmpSkipDomainPopUpThing')) await (() => { return new Promise((resolve) => { views.push(new MamiDomainTransition(() => { - for(const setting of UmiSettings.settings) - if(setting.id === 'settingsImport') { - setting.click(); - break; - } + (new MamiSettingsBackup(settings)).importUpload(document.body); }, () => { - Umi.Settings.set('tmpSkipDomainPopUpThing', true); + settings.set('tmpSkipDomainPopUpThing', true); views.pop(); resolve(); })); @@ -154,10 +236,7 @@ const Umi = { UI: {} }; const onHashChange = () => { if(location.hash === '#reset') { - for(const setting of UmiSettings.settings) - if(setting.emergencyReset) - Umi.Settings.remove(setting.id); - + settings.clear(true); location.assign('/'); } }; @@ -182,13 +261,61 @@ const Umi = { UI: {} }; Umi.UI.Hooks.AddChannelHooks(); Umi.UI.Hooks.AddTextHooks(); + settings.watch('style', (v, n, i) => { if(!i) Umi.UI.View.AccentReload(); }); + settings.watch('compactView', (v, n, i) => { if(!i) Umi.UI.View.AccentReload(); }); + settings.watch('preventOverflow', v => document.body.classList.toggle('prevent-overflow', v)); + settings.watch('tmpDisableOldThemeSys', (v, n, i) => { if(!i) Umi.UI.View.AccentReload(); }); + const mcPortalSnd = 'minecraft:nether:enter'; - if(Umi.Settings.get('minecraft') !== 'no' && ctx.hasSound() && sndLib.hasSound(mcPortalSnd)) + if(settings.get('minecraft') !== 'no' && ctx.hasSound() && sndLib.hasSound(mcPortalSnd)) ctx.getSound().load(mcPortalSnd, sndLib.getSound(mcPortalSnd).getSources(), function(success, buffer) { if(success) buffer.createSource().play(); }); + settings.watch('minecraft', (v, n, i) => { + if(!i) + Umi.Sound.Play('join'); + }); + + settings.watch('enableNotifications', v => { + if(!v || !('Notification' in window) + || (Notification.permission === 'granted' && Notification.permission !== 'denied')) + return; + + Notification.requestPermission() + .then(perm => { + if(perm !== 'granted') + settings.set('enableNotifications', false); + }); + }); + + settings.watch('playJokeSounds', v => { + if(!v) return; + + const triggers = mami.getTextTriggers(); + if(!triggers.hasTriggers()) + futami.getJson('texttriggers').then(trigInfos => triggers.addTriggers(trigInfos)); + }); + + settings.watch('weeaboo', v => { + if(v) Weeaboo.init(); + }); + + settings.watch('osuKeysV2', (v, n, i) => { + // migrate old value + if(i) { + if(settings.has('osuKeys')) { + settings.set('osuKeysV2', settings.get('osuKeys') ? 'yes' : 'no'); + settings.delete('osuKeys'); + return; + } + } + + OsuKeys.setEnable(v !== 'no'); + OsuKeys.setRandomRate(v === 'rng'); + }); + lo.setMessage('Loading EEPROM...'); try { @@ -205,7 +332,7 @@ const Umi = { UI: {} }; lo.setMessage('Building menus...'); Umi.UI.Menus.Add('users', 'Users'); - Umi.UI.Menus.Add('channels', 'Channels', !Umi.Settings.get('showChannelList')); + Umi.UI.Menus.Add('channels', 'Channels', !settings.get('showChannelList')); Umi.UI.Menus.Add('settings', 'Settings'); let sidebarAnimation = null; @@ -254,10 +381,10 @@ const Umi = { UI: {} }; Umi.UI.Toggles.Add('scroll', { 'click': function() { - Umi.Settings.toggle('autoScroll'); + settings.toggle('autoScroll'); } }, 'Autoscroll'); - Umi.Settings.watch('autoScroll', function(value) { + settings.watch('autoScroll', function(value) { Umi.UI.Toggles.Get('scroll').classList[value ? 'remove' : 'add']('sidebar__selector-mode--scroll-off'); }); @@ -266,10 +393,10 @@ const Umi = { UI: {} }; Umi.UI.Toggles.Add('audio', { 'click': function() { - Umi.Settings.toggle('soundEnable'); + settings.toggle('soundEnable'); } }, 'Sounds'); - Umi.Settings.watch('soundEnable', function(value) { + settings.watch('soundEnable', function(value) { Umi.UI.Toggles.Get('audio').classList[value ? 'remove' : 'add']('sidebar__selector-mode--audio-off'); }); @@ -284,7 +411,7 @@ const Umi = { UI: {} }; Umi.UI.Toggles.Add('clear', { 'click': function() { if(confirm('ARE YOU SURE ABOUT THAT???')) { - const limit = Umi.Settings.get('explosionRadius'); + const limit = settings.get('explosionRadius'); const explode = $e({ tag: 'img', attrs: { @@ -402,7 +529,7 @@ const Umi = { UI: {} }; } - if(Umi.Settings.get('eepromAutoInsert')) + if(settings.get('eepromAutoInsert')) Umi.UI.Markup.InsertRaw(insertText, ''); }; @@ -457,8 +584,8 @@ const Umi = { UI: {} }; if(ev.dataTransfer && ev.dataTransfer.files.length > 0) for(const file of ev.dataTransfer.files) { if(file.name.slice(-5) === '.mami' - && confirm('This file appears to be a settings export. Do you want to import it?')) { - Umi.Settings.importFile(file); + && confirm('This file appears to be a settings export. Do you want to import it? This will overwrite your existing settings!')) { + (new MamiSettingsBackup(settings)).importFile(file); return; } @@ -471,7 +598,7 @@ const Umi = { UI: {} }; Umi.UI.InputMenus.Add('emotes', 'Emoticons'); window.addEventListener('beforeunload', function(ev) { - if(Umi.Settings.get('closeTabConfirm')) { + if(settings.get('closeTabConfirm')) { ev.preventDefault(); return ev.returnValue = 'Are you sure you want to close the tab?'; } @@ -479,7 +606,7 @@ const Umi = { UI: {} }; lo.setMessage('Connecting...'); - Umi.Server.open(views); + Umi.Server.open(views, settings); if(window.dispatchEvent) window.dispatchEvent(new Event('umi:connect')); diff --git a/src/mami.js/parsing.js b/src/mami.js/parsing.js index e472edc..ef58a2c 100644 --- a/src/mami.js/parsing.js +++ b/src/mami.js/parsing.js @@ -1,5 +1,4 @@ #include messages.js -#include settings.js #include utility.js #include ui/markup.js @@ -226,7 +225,7 @@ Umi.Parsing = (function() { }, }); - if(Umi.Settings.get('motivationalImages')) + if(mami.getSettings().get('motivationalImages')) html = motivFrame( extractMotiv(element), html @@ -307,7 +306,7 @@ Umi.Parsing = (function() { }, }); - if(Umi.Settings.get('motivationalVideos')) + if(mami.getSettings().get('motivationalVideos')) html = motivFrame( extractMotiv(element), html diff --git a/src/mami.js/settings.js b/src/mami.js/settings.js deleted file mode 100644 index a2c13a6..0000000 --- a/src/mami.js/settings.js +++ /dev/null @@ -1,846 +0,0 @@ -#include common.js -#include emotes.js -#include txtrigs.js -#include utility.js -#include weeb.js -#include ui/emotes.js -#include ui/view.js -#include sound/sndpacks.js -#include sound/umisound.js -#include sound/osukeys.js - -// Add anything you use Umi.Settings with here, lookups should probably be restricted or something to make sure -const UmiSettings = { - categories: [ - { - id: 'interface', - name: 'Interface', - }, - { - id: 'text', - name: 'Text', - }, - { - id: 'notification', - name: 'Notification', - }, - { - id: 'sounds', - name: 'Sound', - }, - { - id: 'misc', - name: 'Misc', - }, - { - id: 'settings', - name: 'Settings', - }, - { - id: 'debug', - name: 'Debug', - collapse: true, - 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.", - } - ], - settings: [ - { - id: 'style', - name: 'Style', - category: 'interface', - type: 'select', - data: function() { return Umi.UI.View.AccentColours; }, - dataType: 'call', - mutable: true, - default: 'dark', - watcher: function(v, n, i) { - if(!i) Umi.UI.View.AccentReload(); - }, - }, - { - id: 'compactView', - name: 'Compact view', - category: 'interface', - type: 'checkbox', - mutable: true, - default: false, - watcher: function(v, n, i) { - if(!i) Umi.UI.View.AccentReload(); - }, - }, - { - id: 'autoScroll', - name: 'Scroll to latest message', - category: 'interface', - type: 'checkbox', - mutable: true, - default: true, - }, - { - id: 'closeTabConfirm', - name: 'Confirm tab close', - category: 'interface', - type: 'checkbox', - mutable: true, - default: false, - }, - { - id: 'showChannelList', - name: 'Show channel list', - category: 'interface', - type: 'checkbox', - mutable: true, - default: false, - }, - { - id: 'fancyInfo', - name: 'Fancy server messages', - category: 'interface', - type: 'checkbox', - mutable: true, - default: true, - }, - { - id: 'autoCloseUserContext', - name: 'Auto-close user menus', - category: 'interface', - type: 'checkbox', - mutable: true, - default: true, - }, - { - id: 'enableParser', - name: 'Parse markup', - category: 'text', - type: 'checkbox', - mutable: true, - default: true, - }, - { - id: 'enableEmoticons', - name: 'Parse emoticons', - category: 'text', - type: 'checkbox', - mutable: true, - default: true, - }, - { - id: 'autoParseUrls', - name: 'Auto detect links', - category: 'text', - type: 'checkbox', - mutable: true, - default: true, - }, - { - id: 'preventOverflow', - name: 'Prevent overflow', - category: 'text', - type: 'checkbox', - mutable: true, - default: false, - watcher: function(v) { - document.body.classList[v ? 'add' : 'remove']('prevent-overflow'); - }, - }, - { - id: 'expandTextBox', - name: 'Grow input box', - category: 'text', - type: 'checkbox', - mutable: true, - default: false, - }, - { - id: 'eepromAutoInsert', - name: 'Auto-insert uploads', - category: 'text', - type: 'checkbox', - mutable: true, - default: true, - }, - { - id: 'autoEmbedV1', - name: 'Auto-embed media', - category: 'text', - type: 'checkbox', - mutable: true, - default: false, - }, - { - id: 'soundEnable', - name: 'Enable sound', - category: 'sounds', - type: 'checkbox', - mutable: true, - default: true, - //virtual: true, only when no autoplay - watcher: function(v, n, i) { - if(v && !mami.hasSound()) { - mami.initSound(); - Umi.Settings.touch('soundVolume'); - Umi.Settings.touch('soundPack', true); - } - - if(mami.hasAudio()) - mami.getAudio().setMuted(!v); - }, - }, - { - id: 'soundPack', - name: 'Sound pack', - category: 'sounds', - type: 'select', - data: function() { - const packs = {}; - mami.getSoundPacks().forEachPack(function(pack) { - packs[pack.getName()] = pack.getTitle(); - }); - return packs; - }, - dataType: 'call', - mutable: true, - default: 'ajax-chat', - watcher: function(v, n, i) { - const packs = mami.getSoundPacks(); - if(!packs.hasPack(v)) { - Umi.Settings.remove(n); - return; - } - - const player = mami.getSoundPackPlayer(); - if(player !== null) { - player.loadPack(packs.getPack(v)); - if(!i) player.playEvent('server'); - } - }, - }, - { - id: 'soundVolume', - name: 'Sound volume', - category: 'sounds', - type: 'range', - mutable: true, - default: 80, - watcher: function(v, n, i) { - if(mami.hasAudio()) - mami.getAudio().setVolume(v / 100); - }, - }, - { - id: 'soundEnableJoin', - name: 'Play join sound', - category: 'sounds', - type: 'checkbox', - mutable: true, - default: true, - }, - { - id: 'soundEnableLeave', - name: 'Play leave sound', - category: 'sounds', - type: 'checkbox', - mutable: true, - default: true, - }, - { - id: 'soundEnableError', - name: 'Play error sound', - category: 'sounds', - type: 'checkbox', - mutable: true, - default: true, - }, - { - id: 'soundEnableServer', - name: 'Play server message sound', - category: 'sounds', - type: 'checkbox', - mutable: true, - default: true, - }, - { - id: 'soundEnableIncoming', - name: 'Play receive message sound', - category: 'sounds', - type: 'checkbox', - mutable: true, - default: true, - }, - { - id: 'onlySoundOnMention', - name: 'Only play receive sound on mention', - category: 'sounds', - type: 'checkbox', - mutable: true, - default: false, - }, - { - id: 'soundEnableOutgoing', - name: 'Play send message sound', - category: 'sounds', - type: 'checkbox', - mutable: true, - default: true, - }, - { - id: 'soundEnablePrivate', - name: 'Play private message sound', - category: 'sounds', - type: 'checkbox', - mutable: true, - default: true, - }, - { - id: 'soundEnableForceLeave', - name: 'Play kick sound', - category: 'sounds', - type: 'checkbox', - mutable: true, - default: true, - }, - { - id: 'minecraft', - name: 'Minecraft', - category: 'sounds', - type: 'select', - mutable: true, - dataType: 'object', - data: { - 'no': 'No Minecraft', - 'yes': 'Yes Minecraft', - 'old': 'Old Minecraft', - }, - default: 'no', - watcher: function(v, n, i) { - if(!i) Umi.Sound.Play('join'); - }, - }, - { - id: 'playSoundOnConnect', - name: 'Play join sound on connect', - category: 'sounds', - type: 'checkbox', - mutable: true, - default: false, - }, - { - id: 'windowsLiveMessenger', - name: 'Windows Live Messenger', - category: 'sounds', - type: 'checkbox', - mutable: true, - default: false, - }, - { - id: 'seinfeld', - name: 'Seinfeld', - category: 'sounds', - type: 'checkbox', - mutable: true, - default: false, - }, - { - id: 'flashTitle', - name: 'Strobe title on new message', - category: 'notification', - type: 'checkbox', - mutable: true, - default: true, - }, - { - id: 'showServerMsgInTitle', - name: 'Show server message in title', - category: 'notification', - type: 'checkbox', - mutable: true, - default: true, - }, - { - id: 'enableNotifications', - name: 'Show notifications', - category: 'notification', - type: 'checkbox', - mutable: (() => 'Notification' in window)(), - default: false, - watcher: function(v, n, i) { - if(!v || !('Notification' in window) - || (Notification.permission === 'granted' && Notification.permission !== 'denied')) - return; - - Notification.requestPermission() - .then(perm => { - if(perm !== 'granted') - Umi.Settings.set('enableNotifications', false); - }); - }, - }, - { - id: 'notificationShowMessage', - name: 'Show contents of message', - category: 'notification', - type: 'checkbox', - mutable: (() => 'Notification' in window)(), - default: false, - }, - { - id: 'notificationTriggers', - name: 'Triggers', - category: 'notification', - type: 'text', - mutable: (() => 'Notification' in window)(), - default: '', - }, - { - id: 'playJokeSounds', - name: 'Run joke triggers', - category: 'misc', - type: 'checkbox', - mutable: true, - default: true, - watcher: function(v, n, i) { - if(v) { - const triggers = mami.getTextTriggers(); - if(!triggers.hasTriggers()) - futami.getJson('texttriggers').then(trigInfos => triggers.addTriggers(trigInfos)); - } - }, - }, - { - id: 'weeaboo', - name: 'Weeaboo', - category: 'misc', - type: 'checkbox', - mutable: true, - default: false, - watcher: function(v, n, i) { - if(v) Weeaboo.init(); - }, - }, - { - id: 'motivationalImages', - name: 'Make images motivational', - category: 'misc', - type: 'checkbox', - mutable: true, - default: false, - }, - { - id: 'motivationalVideos', - name: 'Make videos motivational', - category: 'misc', - type: 'checkbox', - mutable: true, - default: false, - }, - { - id: 'osuKeysV2', - name: 'osu! keyboard sounds', - category: 'misc', - type: 'select', - mutable: true, - dataType: 'object', - data: { - 'no': 'Off', - 'yes': 'On', - 'rng': 'On, random pitch', - }, - default: 'no', - watcher: function(v, n, i) { - // migrate old value - if(i && Umi.Settings.has('osuKeys')) { - Umi.Settings.set('osuKeysV2', Umi.Settings.get('osuKeys') ? 'yes' : 'no'); - Umi.Settings.remove('osuKeys'); - return; - } - - OsuKeys.setEnable(v !== 'no'); - OsuKeys.setRandomRate(v === 'rng'); - }, - }, - { - id: 'explosionRadius', - name: 'Messages to keep on clear', - category: 'misc', - type: 'number', - mutable: true, - default: 20, - }, - { - id: 'reloadEmoticons', - name: 'Reload emoticons', - category: 'misc', - type: 'button', - mutable: true, - dataType: 'void', - click: function() { - const emotes = futami.get('emotes'); - setTimeout(function() { - this.disabled = true; - - futami.getJson('emotes', true) - .then(emotes => { - MamiEmotes.clear(); - MamiEmotes.loadLegacy(emotes); - }) - .finally(() => { - Umi.UI.Emoticons.Init(); - this.disabled = false; - }); - }, 200); - }, - }, - { - id: 'reloadJokeTriggers', - name: 'Reload joke triggers', - category: 'misc', - type: 'button', - mutable: true, - dataType: 'void', - click: function() { - this.disabled = true; - - const triggers = mami.getTextTriggers(); - triggers.clearTriggers(); - - if(Umi.Settings.get('playJokeSounds')) - futami.getJson('texttriggers', true) - .then(trigInfos => triggers.addTriggers(trigInfos)) - .finally(() => this.disabled = false); - }, - }, - { - id: 'dumpPackets', - name: 'Dump packets to console', - category: 'debug', - type: 'checkbox', - mutable: true, - default: FUTAMI_DEBUG, - }, - { - id: 'openCompatClient', - name: 'Open compatibility client', - category: 'misc', - type: 'button', - mutable: true, - dataType: 'void', - click: function() { - const meow = $e('a', { href: window.AMI_URL, target: '_blank', style: { display: 'none' } }); - document.body.appendChild(meow); - meow.click(); - $r(meow); - }, - }, - { - id: 'neverUseWorker', - name: 'Never use Worker for connection', - category: 'debug', - type: 'checkbox', - mutable: true, - default: false, - emergencyReset: true, - confirm: "If you're here it likely means that you mistakenly believe that your browser doesn't suck. You may go ahead but if disabling this causes any annoyances for other users you will be expunged.", - }, - { - id: 'forceUseWorker', - name: 'Always use Worker for connection', - category: 'debug', - type: 'checkbox', - mutable: true, - default: false, - emergencyReset: true, - }, - { - id: 'marqueeAllNames', - name: 'Apply marquee on everyone', - category: 'debug', - type: 'checkbox', - mutable: true, - default: false, - }, - { - id: 'tmpDisableOldThemeSys', - name: 'Disable Old Theme System', - category: 'debug', - type: 'checkbox', - mutable: true, - default: false, - emergencyReset: true, - watcher: function(v, n, i) { - if(!i) Umi.UI.View.AccentReload(); - }, - }, - { - id: 'tmpSkipDomainPopUpThing', - name: 'Skip domain pop up thing', - category: 'debug', - type: 'checkbox', - mutable: true, - default: false, - emergencyReset: true, - }, - { - id: 'settingsImport', - name: 'Import settings', - category: 'settings', - type: 'button', - mutable: true, - dataType: 'void', - click: function() { - $ri('-mami-settings-import-field'); - - imp = $e('input', { - id: '-mami-settings-import-field', - type: 'file', - accept: '.mami', - style: { display: 'none' }, - }); - imp.addEventListener('change', function() { - if(imp.files.length > 0) - Umi.Settings.importFile(imp.files[0]); - - $r(imp); - }); - document.body.appendChild(imp); - imp.click(); - }, - }, - { - id: 'settingsExport', - name: 'Export settings', - category: 'settings', - type: 'button', - mutable: true, - dataType: 'void', - click: function() { - const data = { - a: 'Mami Settings Export', - v: 1, - d: [], - }; - - for(const setting of UmiSettings.settings) - if(setting.mutable && setting.type !== 'button') - data.d.push({ - i: setting.id, - v: Umi.Settings.get(setting.id) - }); - - const user = Umi.User.getCurrentUser(); - let fileName = 'settings.mami'; - - if(user !== null) - fileName = user.getName() + '\'s settings.mami'; - - const exp = $e('a', { - href: URL.createObjectURL(new Blob( - [btoa(JSON.stringify(data))], - { type: 'application/octet-stream' } - )), - download: fileName, - target: '_blank', - style: { display: 'none' } - }); - document.body.appendChild(exp); - exp.click(); - $r(exp); - }, - }, - { - id: 'settingsReset', - name: 'Reset settings', - category: 'settings', - type: 'button', - mutable: true, - dataType: 'void', - click: function() { - if(!confirm('This will reset all your settings to their defaults values. Are you sure you want to do this?')) - return; - - for(const setting of UmiSettings.settings) - if(setting.mutable) - Umi.Settings.remove(setting.id); - }, - }, - ], -}; - -Umi.Settings = function(metaData) { - let getRaw = null, setRaw = null, removeRaw = null; - const valid = [], mutable = [], locked = [], virtual = []; - const watchers = new Map, defaults = new Map, virtuals = new Map; - const prefix = 'umi-'; - - for(const setting of metaData) { - valid.push(setting.id); - if(setting.mutable && setting.dataType !== 'void') - mutable.push(setting.id); - if(setting.virtual) - virtual.push(setting.id); - if(setting.default) - defaults.set(setting.id, setting.default); - } - - getRaw = function(name) { - let value = null; - if(virtual.includes(name)) - value = virtuals.get(name); - else - value = localStorage.getItem(prefix + name); - return value === undefined ? null : JSON.parse(value); - }; - setRaw = function(name, value) { - value = JSON.stringify(value); - - if(virtual.includes(name)) - virtuals.set(name, value); - else - localStorage.setItem(prefix + name, value); - }; - removeRaw = function(name) { - virtuals.delete(name); - localStorage.removeItem(prefix + name); - }; - - const hasValue = function(name) { - if(!mutable.includes(name)) - return false; - - const value = getRaw(name); - return value !== null && value !== undefined; - }; - - const getValue = function(name) { - if(!valid.includes(name)) - return null; - - const value = mutable.includes(name) ? getRaw(name) : null; - if(value === null || value === undefined) - return defaults.get(name) || null; - - return value; - }; - - const setValue = function(name, value) { - if(!mutable.includes(name)) - return; - - if(locked.includes(name)) - return; - locked.push(name); - - if(getValue(name) !== value) { - if(value === defaults.get(name)) - removeRaw(name); - else - setRaw(name, value); - callWatcher(name); - } - - $ari(locked, name); - }; - - const callWatcher = function(name, initial) { - if(watchers.has(name)) { - const w = watchers.get(name), - v = getValue(name); - initial = !!initial; - for(const f of w) - f(v, name, initial); - } - }; - - return { - has: hasValue, - get: getValue, - set: setValue, - remove: function(name) { - if(!mutable.includes(name)) - return; - - if(locked.includes(name)) - return; - locked.push(name); - - removeRaw(name); - callWatcher(name); - - $ari(locked, name); - }, - toggle: function(name) { - setValue(name, !getValue(name)); - }, - touch: callWatcher, - watch: function(name, callback) { - if(!mutable.includes(name)) - return; - if(!watchers.has(name)) - watchers.set(name, []); - const callbacks = watchers.get(name); - if(!callbacks.includes(callback)) - callbacks.push(callback); - callback(getValue(name), name, true); - }, - unwatch: function(name, callback) { - if(!watchers.get(name)) - return; - $ari(watchers.get(name), callback); - }, - virtualise: function(name) { - if(mutable.includes(name) && !virtual.includes(name)) { - const value = getRaw(name); - virtual.push(name); - - if(value !== null && value !== undefined) - setRaw(name, value); - } - }, - importFile: function(file) { - const reader = new FileReader; - reader.addEventListener('load', function() { - let data = atob(reader.result); - if(!data) { - alert('This is not a settings export. (1)'); - return; - } - - data = JSON.parse(data); - if(!data) { - alert('This is not a settings export. (2)'); - return; - } - - if(!data.a || !data.v || data.a !== 'Mami Settings Export') { - alert('This is not a settings export. (3)'); - return; - } - - if(data.v < 1) { - alert('Version of this settings export cannot be interpreted.'); - return; - } - if(data.v > 1) { - alert('This settings export is not compatible with this version of the chat client.'); - return; - } - - if(!Array.isArray(data.d)) { - alert('Settings export contains invalid data.'); - return; - } - - if(confirm('Your current settings will be replaced with the ones in the export. Are you sure you want to continue?')) { - const settings = {}; - for(const setting of data.d) - if(setting.i) - settings[setting.i] = setting.v; - - for(const setting of UmiSettings.settings) - if(setting.mutable && setting.type !== 'button' && setting.id in settings) - setValue(setting.id, settings[setting.id]); - } - }); - reader.readAsText(file); - }, - }; -}; diff --git a/src/mami.js/settings/backup.js b/src/mami.js/settings/backup.js new file mode 100644 index 0000000..cbaf519 --- /dev/null +++ b/src/mami.js/settings/backup.js @@ -0,0 +1,126 @@ +#include utility.js + +const MamiSettingsBackup = function(settings) { + const header = 'Mami Settings Export'; + const version = 1; + const minVersion = 1; + const maxVersion = 1; + + const exportData = () => { + const names = settings.names(); + const data = { a: header, v: version, d: [] }; + + for(const name of names) { + const info = settings.info(name); + if(info.immutable) + continue; + + data.d.push({ + i: info.name, + v: settings.get(info.name), + }); + } + + return btoa(JSON.stringify(data)); + }; + + const importData = (data, clear) => { + try { + data = JSON.parse(atob(data)); + } catch(ex) { + throw 'Settings export data is invalid.'; + } + + if(typeof data !== 'object') + throw 'Settings export data is not an object.'; + + if(data.a !== header) + throw 'Provided data is not settings export.'; + + if(data.v < minVersion) + throw 'Settings export is too old and no longer compatible with this version of the chat client.'; + if(data.v > maxVersion) + throw 'Settings export is too new and not compatible with this version of the chat client.'; + + if(!Array.isArray(data.d)) + throw 'Values are missing from settings export.'; + + if(clear === true) + settings.clear(); + + let success = true; + + for(const exportInfo of data.d) { + if(typeof exportInfo.i !== 'string') + continue; + + try { + settings.set(exportInfo.i, exportInfo.v); + } catch(ex) { + success = false; + } + } + + return success; + }; + + const importFile = file => { + return new Promise((resolve, reject) => { + const reader = new FileReader; + reader.onerror = () => reject(); + reader.onload = () => { + try { + resolve(importData(reader.result)); + } catch(ex) { + reject(ex); + } + }; + reader.readAsText(file); + }); + }; + + return { + export: exportData, + exportDownload: async (target, fileName) => { + if(!(target instanceof Element)) + throw 'target must be an instance of Element'; + if(typeof fileName !== 'string') + fileName = 'settings.mami'; + + const data = exportData(); + const html = $e('a', { + href: URL.createObjectURL(new Blob([data], { type: 'application/octet-stream' })), + download: fileName, + target: '_blank', + style: { display: 'none' }, + }); + target.appendChild(html); + html.click(); + target.removeChild(html); + }, + import: importData, + importFile: importFile, + importUpload: (target) => { + return new Promise((resolve, reject) => { + const html = $e('input', { + type: 'file', + accept: '.mami', + style: { display: 'none' }, + }); + html.addEventListener('change', () => { + if(html.files.length > 0) { + importFile(html.files[0]) + .then(result => resolve(result)) + .catch(ex => reject(ex)) + .finally(() => target.removeChild(html)); + } else { + target.removeChild(html); + reject(); + } + }); + target.appendChild(html); + html.click(); + }); + }, + }; +}; diff --git a/src/mami.js/settings/scoped.js b/src/mami.js/settings/scoped.js new file mode 100644 index 0000000..913a0c9 --- /dev/null +++ b/src/mami.js/settings/scoped.js @@ -0,0 +1,37 @@ +const MamiSettingsScoped = function(settings, prefix) { + if(typeof settings !== 'object') + throw 'settings must be an object'; + if(typeof prefix !== 'string') + throw 'prefix must be a string'; + if(prefix.length < 1) + throw 'prefix may not be empty'; + + if(!prefix.endsWith(':')) + prefix += ':'; + + return { + define: (name, type, fallback, immutable, critical) => settings.define(prefix + name, type, fallback, immutable, critical), + info: name => settings.info(prefix + name), + names: () => { + const filtered = []; + const names = settings.names(); + + for(const name in names) + if(name.startsWith(prefix)) + filtered.push(name.substring(prefix.length)); + + return filtered; + }, + has: name => settings.has(prefix + name), + get: name => settings.get(prefix + name), + set: (name, value) => settings.set(prefix + name, value), + delete: name => settings.delete(prefix + name), + toggle: name => settings.toggle(prefix + name), + touch: name => settings.touch(prefix + name), + clear: (criticalOnly, pfx) => settings.clear(criticalOnly, prefix + pfx), + watch: (name, handler) => settings.watch(prefix + name, handler), + unwatch: (name, handler) => settings.unwatch(prefix + name, handler), + virtualise: name => settings.virtualise(prefix + name), + scope: name => settings.scope(prefix + name), + }; +}; diff --git a/src/mami.js/settings/settings.js b/src/mami.js/settings/settings.js new file mode 100644 index 0000000..735d8be --- /dev/null +++ b/src/mami.js/settings/settings.js @@ -0,0 +1,151 @@ +#include watcher.js +#include settings/scoped.js +#include settings/virtual.js +#include settings/webstorage.js + +const MamiSettings = function(storageOrPrefix) { + if(typeof storageOrPrefix === 'string') + storageOrPrefix = new MamiSettingsWebStorage(window.localStorage, storageOrPrefix); + else if(typeof storageOrPrefix !== 'object') + throw 'storageOrPrefix must be a prefix string or an object'; + + if(typeof storageOrPrefix.get !== 'function' + || typeof storageOrPrefix.set !== 'function' + || typeof storageOrPrefix.delete !== 'function') + throw 'required methods do not exist in storageOrPrefix object'; + + const storage = new MamiSettingsVirtualStorage(storageOrPrefix), + watchers = new MamiWatchers, + settings = new Map; + + const getSetting = name => { + const setting = settings.get(name); + if(setting === undefined) + throw `setting ${name} is undefined`; + return setting; + }; + + const getValue = setting => { + if(setting.immutable) + return setting.fallback; + + const value = storage.get(setting.name); + return value === null ? setting.fallback : value; + }; + + const deleteValue = setting => { + if(setting.immutable) + return; + + storage.delete(setting.name); + }; + + const setValue = (setting, value) => { + if(value !== null) { + if(value === undefined) + value = null; + else if('type' in setting) { + if(Array.isArray(setting.type)) { + if(!setting.type.includes(value)) + throw `setting ${setting.name} must match an enum value`; + } else { + const type = typeof value; + let resolved = false; + + if(type !== setting.type) { + if(type === 'string') { + if(setting.type === 'number') { + value = parseFloat(value); + resolved = true; + } else if(setting.type === 'boolean') { + value = !!value; + resolved = true; + } + } else if(setting.type === 'string') { + value = value.toString(); + resolved = true; + } + } else resolved = true; + + if(!resolved) + throw `setting ${setting.name} must be of type ${setting.type}`; + } + } + } + + if(setting.immutable) + return; + + if(value === null || value === setting.fallback) { + value = setting.fallback; + storage.delete(setting.name); + } else + storage.set(setting.name, value); + + watchers.call(setting.name, value, setting.name); + }; + + const pub = { + define: (name, type, fallback, immutable, critical, virtual) => { + if(typeof name !== 'string') + throw 'setting name must be a string'; + if(typeof type !== 'string' && !Array.isArray(type)) + throw 'type must be a javascript type or array of valid string values.'; + if(settings.has(name)) + throw `setting ${name} has already been defined`; + + settings.set(name, Object.freeze({ + name: name, + type: type === null ? undefined : type, + fallback: fallback === undefined ? null : fallback, + immutable: immutable === true, + critical: critical === true, + })); + + if(virtual === true) + storage.virtualise(name); + + watchers.define(name); + }, + info: name => getSetting(name), + names: () => Array.from(settings.keys()), + has: name => { + const setting = settings.get(name); + return setting !== undefined + && !setting.immutable + && storage.get(setting.name) !== null; + }, + get: name => getValue(getSetting(name)), + set: (name, value) => setValue(getSetting(name), value), + delete: name => { + const setting = getSetting(name); + if(!setting.immutable) + storage.delete(setting.name) + }, + toggle: name => { + const setting = getSetting(name); + if(!setting.immutable) + setValue(setting, !getValue(setting)); + }, + touch: name => { + const setting = getSetting(name); + watchers.call(setting.name, getValue(setting), setting.name); + }, + clear: (criticalOnly, prefix) => { + for(const setting of settings.values()) + if((prefix === undefined || setting.name.startsWith(prefix)) && (!criticalOnly || setting.critical)) + storage.delete(setting.name); + }, + watch: (name, handler) => { + const setting = getSetting(name); + watchers.watch(setting.name, handler, getValue(setting), setting.name); + }, + unwatch: (name, handler) => { + watchers.unwatch(getSetting(name).name, handler); + }, + virtualise: name => storage.virtualise(getSetting(name).name), + scope: name => new MamiSettingsScoped(pub, name), + }; + + return pub; +}; diff --git a/src/mami.js/settings/virtual.js b/src/mami.js/settings/virtual.js new file mode 100644 index 0000000..f4b4466 --- /dev/null +++ b/src/mami.js/settings/virtual.js @@ -0,0 +1,29 @@ +const MamiSettingsVirtualStorage = function(storage) { + const virtuals = new Map; + + return { + virtualise: name => virtuals.set(name, storage.get(name)), + get: name => { + if(virtuals.has(name)) + try { + return JSON.parse(virtuals.get(name)); + } catch(ex) { + return null; + } + + return storage.get(name); + }, + delete: name => { + if(virtuals.has(name)) + virtuals.set(name, null); + else + storage.delete(name); + }, + set: (name, value) => { + if(virtuals.has(name)) + virtuals.set(name, value === undefined ? null : JSON.stringify(value)); + else + storage.set(name, value); + }, + }; +}; diff --git a/src/mami.js/settings/webstorage.js b/src/mami.js/settings/webstorage.js new file mode 100644 index 0000000..be44cb7 --- /dev/null +++ b/src/mami.js/settings/webstorage.js @@ -0,0 +1,18 @@ +const MamiSettingsWebStorage = function(storage, prefix) { + if(!(storage instanceof Storage)) + throw 'storage must be an instance of window.Storage'; + if(typeof prefix !== 'string') + prefix = ''; + + return { + delete: name => storage.removeItem(prefix + name), + set: (name, value) => storage.setItem(prefix + name, value === undefined ? null : JSON.stringify(value)), + get: name => { + try { + return JSON.parse(storage.getItem(prefix + name)); + } catch(ex) { + return null; + } + }, + }; +}; diff --git a/src/mami.js/sockchat_old.js b/src/mami.js/sockchat_old.js index 1b89a3d..97ff380 100644 --- a/src/mami.js/sockchat_old.js +++ b/src/mami.js/sockchat_old.js @@ -8,7 +8,6 @@ #include users.js #include parsing.js #include servers.js -#include settings.js #include txtrigs.js #include websock.js #include ui/emotes.js @@ -23,7 +22,7 @@ if(!Umi.Protocol) Umi.Protocol = {}; if(!Umi.Protocol.SockChat) Umi.Protocol.SockChat = {}; if(!Umi.Protocol.SockChat.Protocol) Umi.Protocol.SockChat.Protocol = {}; -Umi.Protocol.SockChat.Protocol = function(views) { +Umi.Protocol.SockChat.Protocol = function(views, settings) { const pub = {}; Umi.Protocol.SockChat.Protocol.Instance = pub; @@ -44,7 +43,7 @@ Umi.Protocol.SockChat.Protocol = function(views) { if(!sock) return; let msg = opcode; if(data) msg += "\t" + data.join("\t"); - if(Umi.Settings.get('dumpPackets')) + if(settings.get('dumpPackets')) console.log(msg); sock.send(msg); }; @@ -146,7 +145,7 @@ Umi.Protocol.SockChat.Protocol = function(views) { }; const onOpen = function(ev) { - if(Umi.Settings.get('dumpPackets')) + if(settings.get('dumpPackets')) console.log(ev); wasKicked = false; @@ -175,7 +174,7 @@ Umi.Protocol.SockChat.Protocol = function(views) { }; const onClose = function(ev) { - if(Umi.Settings.get('dumpPackets')) + if(settings.get('dumpPackets')) console.log(ev); userId = null; @@ -436,7 +435,7 @@ Umi.Protocol.SockChat.Protocol = function(views) { const onMessage = function(ev) { const data = ev.data.split("\t"); - if(Umi.Settings.get('dumpPackets')) + if(settings.get('dumpPackets')) console.log(data); switch(data[0]) { @@ -470,7 +469,7 @@ Umi.Protocol.SockChat.Protocol = function(views) { startKeepAlive(); - if(Umi.Settings.get('playSoundOnConnect')) + if(settings.get('playSoundOnConnect')) Umi.Sound.Play('join'); } else { switch (data[2]) { @@ -589,7 +588,7 @@ Umi.Protocol.SockChat.Protocol = function(views) { if(muser.getId() === userId) sound = 'outgoing'; - if(Umi.Settings.get('playJokeSounds')) + if(settings.get('playJokeSounds')) try { const trigger = mami.getTextTriggers().getTrigger(text); if(trigger.isSoundType()) { @@ -614,7 +613,7 @@ Umi.Protocol.SockChat.Protocol = function(views) { isAction )); - if(!Umi.Settings.get('onlySoundOnMention') && sound !== '') + if(!settings.get('onlySoundOnMention') && sound !== '') Umi.Sound.Play(sound); break; @@ -921,7 +920,7 @@ Umi.Protocol.SockChat.Protocol = function(views) { getLoadingOverlay('spinner', 'Loading...', str); UmiServers.getServer(function(server) { - if(Umi.Settings.get('dumpPackets')) + if(settings.get('dumpPackets')) console.log('Connecting to ' + server); sock = new UmiWebSocket(server, function(ev) { diff --git a/src/mami.js/sound/sndpacks.js b/src/mami.js/sound/sndpacks.js index 40a837d..b9e8ef3 100644 --- a/src/mami.js/sound/sndpacks.js +++ b/src/mami.js/sound/sndpacks.js @@ -1,4 +1,3 @@ -#include settings.js #include sound/sndlibrary.js const MamiSoundPack = function(name, isReadOnly, title, events) { diff --git a/src/mami.js/sound/umisound.js b/src/mami.js/sound/umisound.js index fb25142..11aa5c7 100644 --- a/src/mami.js/sound/umisound.js +++ b/src/mami.js/sound/umisound.js @@ -1,4 +1,3 @@ -#include settings.js #include audio/context.js #include sound/sndpacks.js #include sound/seinfeld.js @@ -9,19 +8,20 @@ Umi.Sound = (function() { if(!sound || sound === 'none' || !mami.hasSound()) return; + const settings = mami.getSettings(); const sndPackPlay = mami.getSoundPackPlayer(); switch(sound) { case 'join': - if(!Umi.Settings.get('soundEnableJoin')) + if(!settings.get('soundEnableJoin')) return; - if(Umi.Settings.get('seinfeld')) { + if(settings.get('seinfeld')) { mami.playUrlSound(Seinfeld.getRandom()); break; } - switch(Umi.Settings.get('minecraft')) { + switch(settings.get('minecraft')) { case 'yes': mami.playLibrarySound('minecraft:door:open'); break; @@ -37,10 +37,10 @@ Umi.Sound = (function() { break; case 'leave': - if(!Umi.Settings.get('soundEnableLeave')) + if(!settings.get('soundEnableLeave')) return; - switch(Umi.Settings.get('minecraft')) { + switch(settings.get('minecraft')) { case 'yes': mami.playLibrarySound('minecraft:door:close'); break; @@ -56,31 +56,31 @@ Umi.Sound = (function() { break; case 'error': - if(!Umi.Settings.get('soundEnableError')) + if(!settings.get('soundEnableError')) return; sndPackPlay.playEvent('error'); break; case 'server': - if(!Umi.Settings.get('soundEnableServer')) + if(!settings.get('soundEnableServer')) return; sndPackPlay.playEvent('server'); break; case 'unban': - if(!Umi.Settings.get('soundEnableServer')) + if(!settings.get('soundEnableServer')) return; sndPackPlay.playEvent(['unban', 'server']); break; case 'incoming': - if(!Umi.Settings.get('soundEnableIncoming')) + if(!settings.get('soundEnableIncoming')) return; - if(Umi.Settings.get('windowsLiveMessenger')) { + if(settings.get('windowsLiveMessenger')) { mami.playLibrarySound('msn:incoming'); } else { sndPackPlay.playEvent('incoming'); @@ -88,7 +88,7 @@ Umi.Sound = (function() { break; case 'outgoing': - if(!Umi.Settings.get('soundEnableOutgoing')) + if(!settings.get('soundEnableOutgoing')) return; sndPackPlay.playEvent('outgoing'); @@ -96,21 +96,21 @@ Umi.Sound = (function() { case 'private': case 'incoming-priv': - if(!Umi.Settings.get('soundEnablePrivate')) + if(!settings.get('soundEnablePrivate')) return; sndPackPlay.playEvent(['incoming-priv', 'incoming']); break; case 'flood': - if(!Umi.Settings.get('soundEnableForceLeave')) + if(!settings.get('soundEnableForceLeave')) return; sndPackPlay.playEvent(['flood', 'kick', 'leave']); break; case 'timeout': - if(!Umi.Settings.get('soundEnableForceLeave')) + if(!settings.get('soundEnableForceLeave')) return; sndPackPlay.playEvent(['timeout', 'leave']); @@ -118,7 +118,7 @@ Umi.Sound = (function() { case 'kick': case 'forceLeave': - if(!Umi.Settings.get('soundEnableForceLeave')) + if(!settings.get('soundEnableForceLeave')) return; sndPackPlay.playEvent(['kick', 'leave']); diff --git a/src/mami.js/ui/emotes.js b/src/mami.js/ui/emotes.js index 2770d16..640ff6a 100644 --- a/src/mami.js/ui/emotes.js +++ b/src/mami.js/ui/emotes.js @@ -33,7 +33,7 @@ Umi.UI.Emoticons = (function() { }); }, Parse: function(element, message) { - if(!Umi.Settings.get('enableEmoticons')) + if(!mami.getSettings().get('enableEmoticons')) return element; let inner = element.innerHTML; diff --git a/src/mami.js/ui/hooks.js b/src/mami.js/ui/hooks.js index 346ffb1..993813f 100644 --- a/src/mami.js/ui/hooks.js +++ b/src/mami.js/ui/hooks.js @@ -1,6 +1,5 @@ #include channels.js #include common.js -#include settings.js #include user.js #include sound/umisound.js #include ui/channels.js @@ -33,12 +32,14 @@ Umi.UI.Hooks = (function() { Umi.UI.Channels.Unread(msg.getChannel()); Umi.UI.Messages.Add(msg); - if(!document.hidden && Umi.Settings.get('flashTitle')) + const settings = mami.getSettings(); + + if(!document.hidden && settings.get('flashTitle')) return; let title = ' ' + msg.getUser().getName(), channel = Umi.Channels.Current() || null; - if(msg.getUser().isBot() && Umi.Settings.get('showServerMsgInTitle')) + if(msg.getUser().isBot() && settings.get('showServerMsgInTitle')) title = ' ' + msg.getText(); if(channel !== null && channel.getName() !== msg.getChannel()) @@ -46,14 +47,14 @@ Umi.UI.Hooks = (function() { Umi.UI.Title.Flash(['[ @]' + title, '[@ ]' + title]); - if(Umi.Settings.get('enableNotifications') && Umi.User.getCurrentUser() !== null) { - const triggers = (Umi.Settings.get('notificationTriggers') || '').toLowerCase().split(' '), + if(settings.get('enableNotifications') && Umi.User.getCurrentUser() !== null) { + const triggers = (settings.get('notificationTriggers') || '').toLowerCase().split(' '), options = {}; triggers.push((Umi.User.getCurrentUser() || { getName: function() { return ''; } }).getName().toLowerCase()); options.body = 'Click here to see what they said.'; - if(Umi.Settings.get('notificationShowMessage')) + if(settings.get('notificationShowMessage')) options.body += "\n" + msg.getText(); const avatarUrl = futami.get('avatar'); @@ -68,7 +69,7 @@ Umi.UI.Hooks = (function() { if(message.toLowerCase().indexOf(' ' + trigger + ' ') >= 0) { new Notification('{0} mentioned you!'.replace('{0}', msg.getUser().getName()), options); - if(Umi.Settings.get('onlySoundOnMention')) + if(settings.get('onlySoundOnMention')) Umi.Sound.Play('incoming'); break; } @@ -128,7 +129,7 @@ Umi.UI.Hooks = (function() { const elemInput = Umi.UI.Elements.MessageInput, elemParent = elemInput.parentNode; let height = 40; - if(Umi.Settings.get('expandTextBox') && elemInput.scrollHeight > elemInput.clientHeight) { + if(mami.getSettings().get('expandTextBox') && elemInput.scrollHeight > elemInput.clientHeight) { /*const cols = Math.floor(elemInput.clientWidth / 8), rows = Math.floor(elemInput.textLength / cols); diff --git a/src/mami.js/ui/messages.jsx b/src/mami.js/ui/messages.jsx index 1e18367..4188f1a 100644 --- a/src/mami.js/ui/messages.jsx +++ b/src/mami.js/ui/messages.jsx @@ -1,7 +1,6 @@ #include channels.js #include common.js #include parsing.js -#include settings.js #include url.js #include users.js #include utility.js @@ -30,6 +29,7 @@ Umi.UI.Messages = (function() { eMeta = null, eUser = null; + const settings = mami.getSettings(); const sender = msg.getUser(); let avatarUser = sender, avatarSize = '80'; @@ -61,7 +61,7 @@ Umi.UI.Messages = (function() { + ':' + msgDateTimeObj.getMinutes().toString().padStart(2, '0') + ':' + msgDateTimeObj.getSeconds().toString().padStart(2, '0'); - if(sender.isBot() && Umi.Settings.get('fancyInfo')) { + if(sender.isBot() && settings.get('fancyInfo')) { const botInfo = msg.getBotInfo(); if(botInfo) { @@ -153,7 +153,7 @@ Umi.UI.Messages = (function() { const urls = []; - if(Umi.Settings.get('autoParseUrls')) { + if(settings.get('autoParseUrls')) { const textSplit = eText.innerText.split(' '); for(const textPart of textSplit) { const uri = Umi.URI.Parse(textPart); @@ -177,7 +177,7 @@ Umi.UI.Messages = (function() { } } - if(Umi.Settings.get('weeaboo')) { + if(settings.get('weeaboo')) { eText.appendChild($t(Weeaboo.getTextSuffix(sender))); const kaomoji = Weeaboo.getRandomKaomoji(true, msg); @@ -187,7 +187,7 @@ Umi.UI.Messages = (function() { } } - if(Umi.Settings.get('weeaboo')) + if(settings.get('weeaboo')) eUser.appendChild($t(Weeaboo.getNameSuffix(sender))); } @@ -208,14 +208,14 @@ Umi.UI.Messages = (function() { lastMsgUser = sender.getId(); lastMsgChannel = msg.getChannel(); - if(Umi.Settings.get('autoEmbedV1')) { + if(settings.get('autoEmbedV1')) { const callEmbedOn = eBase.querySelectorAll('a[onclick^="Umi.Parser.SockChatBBcode.Embed"]'); for(const embedElem of callEmbedOn) if(embedElem.dataset.embed !== '1') embedElem.click(); } - if(Umi.Settings.get('autoScroll')) + if(settings.get('autoScroll')) Umi.UI.Elements.Messages.scrollTop = Umi.UI.Elements.Messages.scrollHeight; if(window.CustomEvent) diff --git a/src/mami.js/ui/settings.js b/src/mami.js/ui/settings.js deleted file mode 100644 index d4acc93..0000000 --- a/src/mami.js/ui/settings.js +++ /dev/null @@ -1,280 +0,0 @@ -#include settings.js -#include utility.js -#include ui/menus.js - -Umi.UI.Settings = (function() { - let copyright = null; - const createCopyright = function() { - if(copyright !== null) - return; - - copyright = $e({ - attrs: { - className: 'mami-copyright', - }, - child: [ - 'Mami', - ' © ', - { - tag: 'a', - child: 'flash.moe', - attrs: { - href: '//flash.moe', - target: '_blank', - }, - }, - { tag: 'br', }, - { - tag: 'a', - child: 'Sock Chat Documentation', - attrs: { - href: '//railgun.sh/sockchat', - target: '_blank', - }, - }, - ], - }); - - Umi.UI.Menus.Get('settings').appendChild(copyright); - }; - - const addCategory = function(category) { - const catBody = $e({ - attrs: { - id: Umi.UI.Menus.Get('settings').id + '-category-' + category.id, - classList: ['setting__category', 'setting__category--' + category.id], - style: { overflow: 'hidden' }, - }, - }), - catHeader = $e({ - attrs: { - classList: ['setting__category-title', 'setting__category-title--' + category.id], - style: { - cursor: 'pointer', - }, - onclick: function() { - if(catBody.dataset.mamiClosed) { - delete catBody.dataset.mamiClosed; - catBody.style.maxHeight = null; - const meow = catBody.clientHeight; - catBody.style.maxHeight = '0'; - setTimeout(function() { - catBody.style.maxHeight = meow.toString() + 'px'; - }, 50); - } else { - catBody.dataset.mamiClosed = 1; - if(!catBody.style.maxHeight) { - catBody.style.maxHeight = catBody.clientHeight.toString() + 'px'; - setTimeout(function() { - catBody.style.maxHeight = '0'; - }, 50); - } else catBody.style.maxHeight = '0'; - } - }, - }, - child: category.name, - }); - - $ib(copyright, catHeader); - $ib(copyright, catBody); - - if(category.collapse) { - catBody.dataset.mamiClosed = 1; - catBody.style.maxHeight = '0'; - } - - if(category.warning) - catBody.appendChild($e({ - attrs: { - style: { - fontSize: '.9em', - lineHeight: '1.4em', - margin: '5px', - padding: '5px', - backgroundColor: 'darkred', - border: '2px solid red', - borderRadius: '5px', - }, - }, - child: category.warning, - })); - }; - - const addSetting = function(setting) { - if(!setting.category) - return; - - let entry = null; - const settingsHtml = Umi.UI.Menus.Get('settings'), - typeAlias = setting.type === 'url' ? 'text' : setting.type, - container = $e({ - attrs: { - 'class': 'setting__container setting__container--' + typeAlias, - id: settingsHtml.id + '-' + setting.id - } - }); - - switch(setting.type) { - case 'select': - entry = $e({ tag: 'select', attrs: { 'class': 'setting__input' } }); - entry.disabled = !setting.mutable; - - let data = {}; - const dataType = setting.dataType || 'setting'; - - switch(dataType) { - case 'object': - data = setting.data; - break; - case 'call': - data = setting.data(); - break; - } - - for(const _i in data) { - const _j = data[_i], - _x = $e({ tag: 'option', attrs: { 'class': 'setting__style setting__style--' + setting.id + '-' + _i } }); - _x.value = _i; - _x.appendChild($t(_j)); - entry.appendChild(_x); - } - - Umi.Settings.watch(setting.id, function(v) { - const keys = Object.keys(data); - for(let i = 0; i < keys.length; ++i) { - if(keys[i] === v) { - entry.selectedIndex = i; - break; - } - } - }); - - entry.addEventListener('change', function(ev) { - Umi.Settings.set(setting.id, entry.value); - }); - - container.appendChild($e({ - tag: 'label', - attrs: { - className: 'setting__label', - }, - child: [ - { child: setting.name }, - entry, - ], - })); - break; - - case 'checkbox': - entry = $e({ tag: 'input', attrs: { type: 'checkbox', 'class': 'setting__input' } }); - entry.disabled = !setting.mutable; - - entry.addEventListener('click', function(ev) { - if(setting.confirm && !confirm(setting.confirm)) { - entry.checked = !entry.checked; - return; - } - - Umi.Settings.toggle(setting.id); - }); - - Umi.Settings.watch(setting.id, function(v) { - entry.checked = v; - }); - - container.appendChild($e({ - tag: 'label', - attrs: { - className: 'setting__label', - }, - child: [ - entry, - { child: setting.name }, - ], - })); - break; - - case 'button': // this really shouldn't probably be here i think - entry = $e({ tag: 'input', attrs: { 'class': 'setting__input' } }); - entry.disabled = !setting.mutable; - entry.value = setting.name; - entry.type = setting.type; - entry.addEventListener('click', setting.click); - - container.appendChild($e({ - tag: 'label', - attrs: { - className: 'setting__label', - }, - child: entry, - })); - break; - - case 'url': - case 'text': - case 'number': - entry = $e({ tag: 'input', attrs: { 'class': 'setting__input' } }); - entry.disabled = !setting.mutable; - entry.type = setting.type; - - Umi.Settings.watch(setting.id, function(v) { - if(entry.value !== v) - entry.value = v; - }); - entry.addEventListener('keyup', function(ev) { - Umi.Settings.set(setting.id, entry.value); - }); - - container.appendChild($e({ - tag: 'label', - attrs: { - className: 'setting__label', - }, - child: [ - { child: setting.name }, - entry, - ], - })); - break; - - case 'range': - entry = $e({ tag: 'input', attrs: { 'class': 'setting__input' } }); - entry.disabled = !setting.mutable; - entry.type = setting.type; - - Umi.Settings.watch(setting.id, function(v) { - entry.value = v; - }); - entry.addEventListener('change', function(ev) { - Umi.Settings.set(setting.id, entry.value); - }); - - container.appendChild($e({ - tag: 'label', - attrs: { - className: 'setting__label', - }, - child: [ - { child: setting.name }, - entry, - ], - })); - break; - } - - $i(settingsHtml.id + '-category-' + setting.category).appendChild(container); - }; - - return { - Init: function() { - createCopyright(); - - for(const category of UmiSettings.categories) - addCategory(category); - for(const setting of UmiSettings.settings) - addSetting(setting); - }, - Add: addSetting, - AddCat: addCategory, - }; -})(); diff --git a/src/mami.js/ui/settings.jsx b/src/mami.js/ui/settings.jsx new file mode 100644 index 0000000..507c789 --- /dev/null +++ b/src/mami.js/ui/settings.jsx @@ -0,0 +1,515 @@ +#include common.js +#include emotes.js +#include utility.js +#include settings/backup.js +#include ui/emotes.js +#include ui/menus.js +#include ui/view.js + +Umi.UI.Settings = (function() { + const items = [ + { + name: 'interface', + title: 'Interface', + items: [ + { + name: 'style', + title: 'Style', + type: 'select', + options: () => Umi.UI.View.AccentColours, + }, + { + name: 'compactView', + title: 'Compact view', + type: 'checkbox', + }, + { + name: 'autoScroll', + title: 'Scroll to latest message', + type: 'checkbox', + }, + { + name: 'closeTabConfirm', + title: 'Confirm tab close', + type: 'checkbox', + }, + { + name: 'showChannelList', + title: 'Show channel list', + type: 'checkbox', + }, + { + name: 'fancyInfo', + title: 'Fancy server messages', + type: 'checkbox', + }, + { + name: 'autoCloseUserContext', + title: 'Auto-close user menus', + type: 'checkbox', + }, + ], + }, + { + name: 'text', + title: 'Text', + items: [ + { + name: 'enableParser', + title: 'Parse markup', + type: 'checkbox', + }, + { + name: 'enableEmoticons', + title: 'Parse emoticons', + type: 'checkbox', + }, + { + name: 'autoParseUrls', + title: 'Auto detect links', + type: 'checkbox', + }, + { + name: 'preventOverflow', + title: 'Prevent overflow', + type: 'checkbox', + }, + { + name: 'expandTextBox', + title: 'Grow input box', + type: 'checkbox', + }, + { + name: 'eepromAutoInsert', + title: 'Auto-insert uploads', + type: 'checkbox', + }, + { + name: 'autoEmbedV1', + title: 'Auto-embed media', + type: 'checkbox', + }, + ], + }, + { + name: 'notification', + title: 'Notification', + items: [ + { + name: 'flashTitle', + title: 'Strobe title on new message', + type: 'checkbox', + }, + { + name: 'showServerMsgInTitle', + title: 'Show server message in title', + type: 'checkbox', + }, + { + name: 'enableNotifications', + title: 'Show notifications', + type: 'checkbox', + }, + { + name: 'notificationShowMessage', + title: 'Show contents of message', + type: 'checkbox', + }, + { + name: 'notificationTriggers', + title: 'Triggers', + type: 'text', + }, + ], + }, + { + name: 'sounds', + title: 'Sound', + items: [ + { + name: 'soundEnable', + title: 'Enable sound', + type: 'checkbox', + }, + { + name: 'soundPack', + title: 'Sound pack', + type: 'select', + options: () => { + const packs = {}; + mami.getSoundPacks().forEachPack(function(pack) { + packs[pack.getName()] = pack.getTitle(); + }); + return packs; + }, + }, + { + name: 'soundVolume', + title: 'Sound volume', + type: 'range', + }, + { + name: 'soundEnableJoin', + title: 'Play join sound', + type: 'checkbox', + }, + { + name: 'soundEnableLeave', + title: 'Play leave sound', + type: 'checkbox', + }, + { + name: 'soundEnableError', + title: 'Play error sound', + type: 'checkbox', + }, + { + name: 'soundEnableServer', + title: 'Play server message sound', + type: 'checkbox', + }, + { + name: 'soundEnableIncoming', + title: 'Play receive message sound', + type: 'checkbox', + }, + { + name: 'onlySoundOnMention', + title: 'Only play receive sound on mention', + type: 'checkbox', + }, + { + name: 'soundEnableOutgoing', + title: 'Play send message sound', + type: 'checkbox', + }, + { + name: 'soundEnablePrivate', + title: 'Play private message sound', + type: 'checkbox', + }, + { + name: 'soundEnableForceLeave', + title: 'Play kick sound', + type: 'checkbox', + }, + { + name: 'minecraft', + title: 'Minecraft', + type: 'select', + options: () => { + return { + 'no': 'No Minecraft', + 'yes': 'Yes Minecraft', + 'old': 'Old Minecraft', + }; + }, + }, + { + name: 'playSoundOnConnect', + title: 'Play join sound on connect', + type: 'checkbox', + }, + { + name: 'windowsLiveMessenger', + title: 'Windows Live Messenger', + type: 'checkbox', + }, + { + name: 'seinfeld', + title: 'Seinfeld', + type: 'checkbox', + }, + ], + }, + { + name: 'misc', + title: 'Misc', + items: [ + { + name: 'playJokeSounds', + title: 'Run joke triggers', + type: 'checkbox', + }, + { + name: 'weeaboo', + title: 'Weeaboo', + type: 'checkbox', + }, + { + name: 'motivationalImages', + title: 'Make images motivational', + type: 'checkbox', + }, + { + name: 'motivationalVideos', + title: 'Make videos motivational', + type: 'checkbox', + }, + { + name: 'osuKeysV2', + title: 'osu! keyboard sounds', + type: 'select', + options: () => { + return { + 'no': 'Off', + 'yes': 'On', + 'rng': 'On, random pitch', + }; + }, + }, + { + name: 'explosionRadius', + title: 'Messages to keep on clear', + type: 'number', + }, + { + title: 'Reload emoticons', + type: 'button', + invoke: button => { + const emotes = futami.get('emotes'); + setTimeout(() => { + button.disabled = true; + + futami.getJson('emotes', true) + .then(emotes => { + MamiEmotes.clear(); + MamiEmotes.loadLegacy(emotes); + }) + .finally(() => { + Umi.UI.Emoticons.Init(); + button.disabled = false; + }); + }, 200); + }, + }, + { + title: 'Reload joke triggers', + type: 'button', + invoke: button => { + button.disabled = true; + + const triggers = mami.getTextTriggers(); + triggers.clearTriggers(); + + if(mami.getSettings().get('playJokeSounds')) + futami.getJson('texttriggers', true) + .then(trigInfos => triggers.addTriggers(trigInfos)) + .finally(() => button.disabled = false); + }, + }, + { + title: 'Open compatibility client', + type: 'button', + invoke: () => { + const meow = $e('a', { href: window.AMI_URL, target: '_blank', style: { display: 'none' } }); + document.body.appendChild(meow); + meow.click(); + $r(meow); + }, + }, + ], + }, + { + name: 'settings', + title: 'Settings', + items: [ + { + title: 'Import settings', + type: 'button', + 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.getSettings())).importUpload(document.body); + }, + }, + { + title: 'Export settings', + type: 'button', + invoke: () => { + const user = Umi.User.getCurrentUser(); + let fileName; + if(user !== null) + fileName = `${user.getName()}'s settings.mami`; + + (new MamiSettingsBackup(mami.getSettings())).exportDownload(document.body, fileName); + }, + }, + { + title: 'Reset settings', + type: 'button', + invoke: () => { + if(!confirm('This will reset all your settings to their defaults values. Are you sure you want to do this?')) + return; + + mami.getSettings().clear(); + }, + }, + ], + }, + { + name: 'debug', + title: 'Debug', + collapse: true, + 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.", + items: [ + { + name: 'dumpPackets', + title: 'Dump packets to console', + type: 'checkbox', + }, + { + name: 'neverUseWorker', + title: 'Never use Worker for connection', + type: 'checkbox', + confirm: "If you're here it likely means that you mistakenly believe that your browser doesn't suck. You may go ahead but if disabling this causes any annoyances for other users you will be expunged.", + }, + { + name: 'forceUseWorker', + title: 'Always use Worker for connection', + type: 'checkbox', + }, + { + name: 'marqueeAllNames', + title: 'Apply marquee on everyone', + type: 'checkbox', + }, + { + name: 'tmpDisableOldThemeSys', + title: 'Disable Old Theme System', + type: 'checkbox', + }, + { + name: 'tmpSkipDomainPopUpThing', + title: 'Skip domain pop up thing', + type: 'checkbox', + }, + ], + } + ]; + + const createCopyright = function() { + return ; + }; + + const createSetting = function(display) { + const settings = mami.getSettings(); + let setting; + + if('name' in display) + setting = settings.info(display.name); + + let input = display.type === 'select' + ? ; + + if(display.disabled === true) + input.disabled = true; + + if(display.type === 'select') { + const options = display.options(); + for(const name in options) + input.appendChild(); + } else if(display.type === 'button') { + input.value = display.title; + input.addEventListener('click', () => display.invoke(input)); + } + + if(setting !== undefined) { + if(!input.disabled && setting.immutable) + input.disabled = true; + + if(display.type === 'checkbox') { + settings.watch(setting.name, v => input.checked = v); + input.addEventListener('change', () => { + if(display.confirm !== undefined && input.checked !== setting.fallback && !confirm(display.confirm)) { + input.checked = setting.fallback; + return; + } + + settings.toggle(setting.name); + }); + } else { + settings.watch(setting.name, v => input.value = v); + input.addEventListener('change', () => settings.set(setting.name, input.value)); + } + } + + let label = input; + if(display.type === 'checkbox') { + label = ; + } else if(display.type === 'button') { + label = ; + } else { + label = ; + } + + return
{label}
; + }; + + const createCategory = function(category) { + const catHeader =
{category.title}
; + const catBody =
; + + catHeader.onclick = () => { + if(catBody.dataset.mamiClosed) { + delete catBody.dataset.mamiClosed; + catBody.style.maxHeight = null; + const meow = catBody.clientHeight; + catBody.style.maxHeight = '0'; + setTimeout(function() { + catBody.style.maxHeight = meow.toString() + 'px'; + }, 50); + } else { + catBody.dataset.mamiClosed = 1; + if(!catBody.style.maxHeight) { + catBody.style.maxHeight = catBody.clientHeight.toString() + 'px'; + setTimeout(function() { + catBody.style.maxHeight = '0'; + }, 50); + } else catBody.style.maxHeight = '0'; + } + }; + + if(category.collapse) { + catBody.dataset.mamiClosed = 1; + catBody.style.maxHeight = '0'; + } + + if(category.warning) + catBody.appendChild(
{category.warning}
); + + if(category.items) + for(const item of category.items) + catBody.appendChild(createSetting(item)); + + return
+ {catHeader} + {catBody} +
; + }; + + return { + Init: function() { + const html = Umi.UI.Menus.Get('settings'); + $rc(html); + + for(const category of items) + html.appendChild(createCategory(category)); + + html.appendChild(createCopyright()); + }, + }; +})(); diff --git a/src/mami.js/ui/users.js b/src/mami.js/ui/users.js index fe590f7..ef56185 100644 --- a/src/mami.js/ui/users.js +++ b/src/mami.js/ui/users.js @@ -1,6 +1,5 @@ #include animate.js #include common.js -#include settings.js #include user.js #include utility.js #include ui/menus.js @@ -24,9 +23,9 @@ Umi.UI.Users = (function() { } if(isClosed) { - if(Umi.Settings.get('autoCloseUserContext')) + if(mami.getSettings().get('autoCloseUserContext')) toggleTimeouts[prefix] = setTimeout(function() { - if(Umi.Settings.get('autoCloseUserContext')) + if(mami.getSettings().get('autoCloseUserContext')) toggleUser(id); }, 300000); @@ -146,7 +145,7 @@ Umi.UI.Users = (function() { if(isAFK) uName.appendChild($e({ attrs: { 'class': 'user-sidebar-afk' }, child: afkText })); - if(sbUserName.length > 16 || Umi.Settings.get('marqueeAllNames')) { + if(sbUserName.length > 16 || mami.getSettings().get('marqueeAllNames')) { uName.appendChild($e({ tag: 'marquee', attrs: { @@ -196,7 +195,7 @@ Umi.UI.Users = (function() { if(isAFK) uName.appendChild($e({ attrs: { 'class': 'user-sidebar-afk' }, child: afkText })); - if(sbUserName.length > 16 || Umi.Settings.get('marqueeAllNames')) { + if(sbUserName.length > 16 || mami.getSettings().get('marqueeAllNames')) { uName.appendChild($e({ tag: 'marquee', attrs: { diff --git a/src/mami.js/ui/view.js b/src/mami.js/ui/view.js index ad06129..175d5cd 100644 --- a/src/mami.js/ui/view.js +++ b/src/mami.js/ui/view.js @@ -1,4 +1,3 @@ -#include settings.js #include themes.js #include ui/elems.js @@ -28,8 +27,9 @@ Umi.UI.View = (function() { return { AccentColours: accentColours, AccentReload: function() { - const available = Object.keys(accentColours), - name = Umi.Settings.get('style'), + const settings = mami.getSettings(), + available = Object.keys(accentColours), + name = settings.get('style'), compact = 'chat--compact', classes = ['umi']; @@ -40,7 +40,7 @@ Umi.UI.View = (function() { // the entire AccentReload function should probably be axed UmiThemeApply(name); - if(!Umi.Settings.get('tmpDisableOldThemeSys')) + if(!settings.get('tmpDisableOldThemeSys')) classes.push('umi--' + name); if(Umi.UI.Elements.Chat.className.indexOf('hidden') >= 0) @@ -48,13 +48,13 @@ Umi.UI.View = (function() { Umi.UI.Elements.Chat.className = ''; Umi.UI.Elements.Chat.classList.add.apply(Umi.UI.Elements.Chat.classList, classes); - if(Umi.Settings.get('compactView')) { + if(settings.get('compactView')) { if(Umi.UI.Elements.Chat.className.indexOf(compact) < 0) Umi.UI.Elements.Messages.classList.add(compact); } else Umi.UI.Elements.Messages.classList.remove(compact); - if(Umi.Settings.get('autoScroll')) + if(settings.get('autoScroll')) Umi.UI.Elements.Messages.scrollTop = Umi.UI.Elements.Messages.scrollHeight; }, Focus: function() { diff --git a/src/mami.js/watcher.js b/src/mami.js/watcher.js new file mode 100644 index 0000000..1c52046 --- /dev/null +++ b/src/mami.js/watcher.js @@ -0,0 +1,67 @@ +#include utility.js + +const MamiWatcher = function() { + const handlers = []; + + const watch = (handler, ...args) => { + if(typeof handler !== 'function') + throw 'handler must be a function'; + if(handlers.includes(handler)) + throw 'handler already registered'; + + handlers.push(handler); + args.push(true); + handler(...args); + }; + + const unwatch = handler => { + $ari(handlers, handler); + }; + + return { + watch: watch, + unwatch: unwatch, + call: (...args) => { + args.push(false); + + for(const handler of handlers) + handler(...args); + }, + }; +}; + +const MamiWatchers = function() { + const watchers = new Map; + + const getWatcher = name => { + const watcher = watchers.get(name); + if(watcher === undefined) + throw 'undefined watcher name'; + return watcher; + }; + + const watch = (name, handler, ...args) => { + getWatcher(name).watch(handler, ...args); + }; + + const unwatch = (name, handler) => { + getWatcher(name).unwatch(handler); + }; + + return { + watch: watch, + unwatch: unwatch, + define: names => { + if(typeof names === 'string') + watchers.set(names, new MamiWatcher); + else if(Array.isArray(names)) + for(const name of names) + watchers.set(name, new MamiWatcher); + else + throw 'names must be an array of names or a single name'; + }, + call: (name, ...args) => { + getWatcher(name).call(...args); + }, + }; +}; diff --git a/src/mami.js/websock.js b/src/mami.js/websock.js index fe8fdac..4338cf9 100644 --- a/src/mami.js/websock.js +++ b/src/mami.js/websock.js @@ -1,12 +1,10 @@ -#include settings.js - const UmiWebSocket = function(server, message, useWorker) { if(typeof useWorker === 'undefined') useWorker = (function() { // Overrides - if(Umi.Settings.get('neverUseWorker')) + if(mami.getSettings().get('neverUseWorker')) return false; - if(Umi.Settings.get('forceUseWorker')) + if(mami.getSettings().get('forceUseWorker')) return true; // Detect chromosomes