diff --git a/src/mami.js/main.js b/src/mami.js/main.js index fe4bf01..543ad44 100644 --- a/src/mami.js/main.js +++ b/src/mami.js/main.js @@ -50,10 +50,10 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } }; try { window.futami = await FutamiCommon.load(); } catch(ex) { - console.error(ex); + console.error('Failed to load common settings.', ex); loadingOverlay.setIcon('cross'); loadingOverlay.setHeader('Failed!'); - loadingOverlay.setMessage('Failed to load common settings!'); + loadingOverlay.setMessage('Failed to load common settings.'); return; } @@ -97,7 +97,7 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } }; settings.define('eepromAutoInsert').default(true).create(); settings.define('autoEmbedV1').default(false).create(); settings.define('soundEnable').default(true).critical().create(); - settings.define('soundPack').default('ajax-chat').create(); + settings.define('soundPack').default('').create(); settings.define('soundVolume').default(80).create(); settings.define('soundEnableJoin').default(true).create(); settings.define('soundEnableLeave').default(true).create(); @@ -136,20 +136,25 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } }; const soundCtx = new MamiSoundContext; ctx.sound = soundCtx; - 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); - } catch(ex) { - console.error(ex); - } + 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(!await MamiDetectAutoPlay()) { - settings.set('soundEnable', false); - settings.virtualise('soundEnable'); - } + 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) { @@ -157,9 +162,11 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } }; soundCtx.reset(); settings.touch('soundVolume'); - settings.touch('soundPack'); + settings.touch('soundPack', true); - soundCtx.library.play(soundCtx.pack.getEventSound('server')); + // 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; @@ -167,13 +174,21 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } }; settings.watch('soundPack', ev => { const packs = soundCtx.packs; - if(!packs.has(ev.detail.value)) { - settings.delete(ev.detail.name); - return; - } + let packName = ev.detail.value; - soundCtx.pack = packs.get(ev.detail.value); - if(!ev.detail.initial) soundCtx.library.play(soundCtx.pack.getEventSound('server')); + 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 => { @@ -181,12 +196,18 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } }; }) + // loading these asynchronously makes them not show up in the backlog + // revisit when emote reparsing is implemented loadingOverlay.setMessage('Loading emoticons...'); try { const emotes = await futami.getJson('emotes'); MamiEmotes.loadLegacy(emotes); } catch(ex) { - console.error(ex); + console.error('Failed to load emoticons.', ex); + } finally { + // this is currently called in the sock chat handlers + // does a permissions check which it can't do at this point + //Umi.UI.Emoticons.Init(); } @@ -206,16 +227,6 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } }; }); - loadingOverlay.setMessage('Loading EEPROM...'); - try { - await MamiEEPROM.init(); - ctx.eeprom = new EEPROM('1', futami.get('eeprom2'), MamiMisuzuAuth.getLine); - } catch(ex) { - console.error(ex); - ctx.eeprom = undefined; - } - - loadingOverlay.setMessage('Preparing UI...'); ctx.textTriggers = new MamiTextTriggers; @@ -407,115 +418,116 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } }; }, 'Ready~'); pingToggle.appendChild(pingIndicator.getElement()); - - if(ctx.eeprom !== undefined) { - Umi.UI.Menus.Add('uploads', 'Upload History', !FUTAMI_DEBUG); - - const doUpload = async file => { - const uploadEntry = Umi.UI.Uploads.create(file.name); - const uploadTask = ctx.eeprom.create(file); - - uploadTask.onProgress(prog => uploadEntry.setProgress(prog.progress)); - uploadEntry.addOption('Cancel', () => uploadTask.abort()); - - try { - const fileInfo = await uploadTask.start(); - - uploadEntry.hideOptions(); - uploadEntry.clearOptions(); - uploadEntry.removeProgress(); - - uploadEntry.addOption('Open', fileInfo.url); - uploadEntry.addOption('Insert', () => Umi.UI.Markup.InsertRaw(insertText, '')); - uploadEntry.addOption('Delete', () => { - ctx.eeprom.delete(fileInfo) - .then(() => uploadEntry.remove()) - .catch(ex => { - console.error(ex); - alert(ex); - }); - }); - - let insertText; - - if(fileInfo.isImage()) { - insertText = `[img]${fileInfo.url}[/img]`; - uploadEntry.setThumbnail(fileInfo.thumb); - } else if(fileInfo.isAudio()) { - insertText = `[audio]${fileInfo.url}[/audio]`; - uploadEntry.setThumbnail(fileInfo.thumb); - } else if(fileInfo.isVideo()) { - insertText = `[video]${fileInfo.url}[/video]`; - uploadEntry.setThumbnail(fileInfo.thumb); - } else - insertText = location.protocol + fileInfo.url; - - if(settings.get('eepromAutoInsert')) - Umi.UI.Markup.InsertRaw(insertText, ''); - } catch(ex) { - if(!ex.aborted) { - console.error(ex); - alert(ex); - } - - uploadEntry.remove(); - } - }; - - const uploadForm = $e({ - tag: 'input', - attrs: { - type: 'file', - multiple: true, - style: { display: 'none' }, - onchange: ev => { - for(const file of ev.target.files) - doUpload(file); - }, - }, - }); - document.body.appendChild(uploadForm); - - Umi.UI.InputMenus.AddButton('upload', 'Upload', () => uploadForm.click()); - - $i('umi-msg-text').onpaste = ev => { - if(ev.clipboardData && ev.clipboardData.files.length > 0) - for(const file of ev.clipboardData.files) - doUpload(file); - }; - - document.body.ondragenter = ev => { - ev.preventDefault(); - ev.stopPropagation(); - }; - document.body.ondragover = ev => { - ev.preventDefault(); - ev.stopPropagation(); - }; - document.body.ondragleave = ev => { - ev.preventDefault(); - ev.stopPropagation(); - }; - document.body.ondrop = ev => { - ev.preventDefault(); - ev.stopPropagation(); - - 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? This will overwrite your existing settings!')) { - (new MamiSettingsBackup(settings)).importFile(file); - return; - } - - doUpload(file); - } - }; - } - Umi.UI.InputMenus.Add('markup', 'BB Code'); Umi.UI.InputMenus.Add('emotes', 'Emoticons'); + let doUpload; + MamiEEPROM.init() + .catch(ex => { + console.log('Failed to initialise EEPROM.', ex); + ctx.eeprom = undefined; + }) + .then(() => { + ctx.eeprom = new EEPROM('1', futami.get('eeprom2'), MamiMisuzuAuth.getLine); + + Umi.UI.Menus.Add('uploads', 'Upload History', !FUTAMI_DEBUG); + + doUpload = async file => { + const uploadEntry = Umi.UI.Uploads.create(file.name); + const uploadTask = ctx.eeprom.create(file); + + uploadTask.onProgress(prog => uploadEntry.setProgress(prog.progress)); + uploadEntry.addOption('Cancel', () => uploadTask.abort()); + + try { + const fileInfo = await uploadTask.start(); + + uploadEntry.hideOptions(); + uploadEntry.clearOptions(); + uploadEntry.removeProgress(); + + uploadEntry.addOption('Open', fileInfo.url); + uploadEntry.addOption('Insert', () => Umi.UI.Markup.InsertRaw(insertText, '')); + uploadEntry.addOption('Delete', () => { + ctx.eeprom.delete(fileInfo) + .then(() => uploadEntry.remove()) + .catch(ex => { + console.error(ex); + alert(ex); + }); + }); + + let insertText; + + if(fileInfo.isImage()) { + insertText = `[img]${fileInfo.url}[/img]`; + uploadEntry.setThumbnail(fileInfo.thumb); + } else if(fileInfo.isAudio()) { + insertText = `[audio]${fileInfo.url}[/audio]`; + uploadEntry.setThumbnail(fileInfo.thumb); + } else if(fileInfo.isVideo()) { + insertText = `[video]${fileInfo.url}[/video]`; + uploadEntry.setThumbnail(fileInfo.thumb); + } else + insertText = location.protocol + fileInfo.url; + + if(settings.get('eepromAutoInsert')) + Umi.UI.Markup.InsertRaw(insertText, ''); + } catch(ex) { + if(!ex.aborted) { + console.error(ex); + alert(ex); + } + + uploadEntry.remove(); + } + }; + + const uploadForm = $e({ + tag: 'input', + attrs: { + type: 'file', + multiple: true, + style: { display: 'none' }, + onchange: ev => { + for(const file of ev.target.files) + doUpload(file); + }, + }, + }); + document.body.appendChild(uploadForm); + + Umi.UI.InputMenus.AddButton('upload', 'Upload', () => uploadForm.click(), 'markup'); + + $i('umi-msg-text').onpaste = ev => { + if(ev.clipboardData && ev.clipboardData.files.length > 0) + for(const file of ev.clipboardData.files) + doUpload(file); + }; + }); + + // figure out how to display a UI for this someday + //document.body.addEventListener('dragenter', ev => { console.info('dragenter', ev); }); + //document.body.addEventListener('dragleave', ev => { console.info('dragleave', ev); }); + document.body.addEventListener('dragover', ev => { ev.preventDefault(); }); + document.body.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' + && 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; + } + + if(doUpload !== undefined) + doUpload(file); + } + }); + window.addEventListener('beforeunload', function(ev) { if(settings.get('closeTabConfirm')) { ev.preventDefault(); diff --git a/src/mami.js/settings/scoped.js b/src/mami.js/settings/scoped.js index c43feb1..687851e 100644 --- a/src/mami.js/settings/scoped.js +++ b/src/mami.js/settings/scoped.js @@ -24,12 +24,12 @@ const MamiSettingsScoped = function(settings, prefix) { }, 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), + set: (name, value, silent) => settings.set(prefix + name, value, silent), + delete: (name, silent) => settings.delete(prefix + name, silent), + toggle: (name, silent) => settings.toggle(prefix + name, silent), + touch: (name, silent) => settings.touch(prefix + name, silent), clear: (criticalOnly, pfx) => settings.clear(criticalOnly, prefix + pfx), - watch: (name, handler) => settings.watch(prefix + name, handler), + watch: (name, handler, silent) => settings.watch(prefix + name, handler, silent), 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 index b739757..2896966 100644 --- a/src/mami.js/settings/settings.js +++ b/src/mami.js/settings/settings.js @@ -16,16 +16,18 @@ const MamiSettings = function(storageOrPrefix, eventTarget) { const storage = new MamiSettingsVirtualStorage(storageOrPrefix); const settings = new Map; - const createUpdateEvent = (name, value, initial) => eventTarget.create(name, { + const createUpdateEvent = (name, value, initial, silent, local) => eventTarget.create(name, { name: name, value: value, initial: !!initial, + silent: !!silent, + local: !!local, }); - const dispatchUpdate = (name, value) => eventTarget.dispatch(createUpdateEvent(name, value)); + const dispatchUpdate = (name, value, silent, local) => eventTarget.dispatch(createUpdateEvent(name, value, false, silent, local)); const broadcast = new BroadcastChannel(`${MAMI_MAIN_JS}:settings:${storage.name()}`); - const broadcastUpdate = (name, value) => { - setTimeout(() => broadcast.postMessage({ act: 'update', name: name, value: value }), 0); + const broadcastUpdate = (name, value, silent) => { + setTimeout(() => broadcast.postMessage({ act: 'update', name: name, value: value, silent: !!silent }), 0); }; const getSetting = name => { @@ -43,16 +45,16 @@ const MamiSettings = function(storageOrPrefix, eventTarget) { return value === null ? setting.fallback : value; }; - const deleteValue = setting => { + const deleteValue = (setting, silent) => { if(setting.immutable) return; storage.delete(setting.name); - dispatchUpdate(setting.name, setting.fallback); - broadcastUpdate(setting.name, setting.fallback); + dispatchUpdate(setting.name, setting.fallback, silent, true); + broadcastUpdate(setting.name, setting.fallback, silent); }; - const setValue = (setting, value) => { + const setValue = (setting, value, silent) => { if(value !== null) { if(value === undefined) value = null; @@ -94,8 +96,8 @@ const MamiSettings = function(storageOrPrefix, eventTarget) { } else storage.set(setting.name, value); - dispatchUpdate(setting.name, value); - broadcastUpdate(setting.name, value); + dispatchUpdate(setting.name, value, silent, true); + broadcastUpdate(setting.name, value, silent); }; broadcast.onmessage = ev => { @@ -103,7 +105,7 @@ const MamiSettings = function(storageOrPrefix, eventTarget) { return; if(ev.data.act === 'update' && typeof ev.data.name === 'string') { - dispatchUpdate(ev.data.name, ev.data.value); + dispatchUpdate(ev.data.name, ev.data.value, ev.data.silent, false); return; } }; @@ -196,26 +198,26 @@ const MamiSettings = function(storageOrPrefix, eventTarget) { && storage.get(setting.name) !== null; }, get: name => getValue(getSetting(name)), - set: (name, value) => setValue(getSetting(name), value), - delete: name => deleteValue(getSetting(name)), - toggle: name => { + set: (name, value, silent) => setValue(getSetting(name), value, silent), + delete: (name, silent) => deleteValue(getSetting(name), silent), + toggle: (name, silent) => { const setting = getSetting(name); if(!setting.immutable) - setValue(setting, !getValue(setting)); + setValue(setting, !getValue(setting), silent); }, - touch: name => { + touch: (name, silent) => { const setting = getSetting(name); - dispatchUpdate(setting.name, getValue(setting)); + dispatchUpdate(setting.name, getValue(setting), silent, true); }, clear: (criticalOnly, prefix) => { for(const setting of settings.values()) if((prefix === undefined || setting.name.startsWith(prefix)) && (!criticalOnly || setting.critical)) deleteValue(setting); }, - watch: (name, handler) => { + watch: (name, handler, silent) => { const setting = getSetting(name); eventTarget.watch(setting.name, handler); - handler(createUpdateEvent(setting.name, getValue(setting), true)); + handler(createUpdateEvent(setting.name, getValue(setting), true, silent, true)); }, unwatch: (name, handler) => { eventTarget.unwatch(name, handler); diff --git a/src/mami.js/ui/input-menus.js b/src/mami.js/ui/input-menus.js index b6032f1..30707dd 100644 --- a/src/mami.js/ui/input-menus.js +++ b/src/mami.js/ui/input-menus.js @@ -8,9 +8,11 @@ Umi.UI.InputMenus = (function() { const inputMenuActive = 'input__menu--active'; const inputButtonActive = 'input__button--active'; + const createButtonId = id => `umi-msg-menu-btn-${id}`; + const toggle = function(baseId) { - const button = 'umi-msg-menu-btn-' + baseId, - menu = 'umi-msg-menu-sub-' + baseId; + const button = createButtonId(baseId); + const menu = 'umi-msg-menu-sub-' + baseId; if($c(inputMenuActive).length) $c(inputMenuActive)[0].classList.remove(inputMenuActive); @@ -30,7 +32,7 @@ Umi.UI.InputMenus = (function() { tag: 'button', attrs: { type: 'button', - id: 'umi-msg-menu-btn-' + id, + id: createButtonId(id), classList: ['input__button', 'input__button--' + id], title: title, onclick: onClick || (function() { @@ -41,20 +43,34 @@ Umi.UI.InputMenus = (function() { }; return { - Add: function(baseId, title) { - if(ids.indexOf(baseId) < 0) { - ids.push(baseId); - $i('umi-msg-container').insertBefore(createButton(baseId, title), $i('umi-msg-send')); - $i('umi-msg-menu').appendChild( - $e({ attrs: { 'class': ['input__menu', 'input__menu--' + baseId], id: 'umi-msg-menu-sub-' + baseId } }) - ); - } + Add: function(baseId, title, beforeButtonId) { + if(ids.includes(baseId)) + return; + ids.push(baseId); + + let beforeButton; + if(typeof beforeButtonId === 'string') + beforeButton = $i(createButtonId(beforeButtonId)) + if(!(beforeButton instanceof Element)) + beforeButton = $i('umi-msg-send'); + + $i('umi-msg-container').insertBefore(createButton(baseId, title), beforeButton); + $i('umi-msg-menu').appendChild( + $e({ attrs: { 'class': ['input__menu', 'input__menu--' + baseId], id: 'umi-msg-menu-sub-' + baseId } }) + ); }, - AddButton: function(baseId, title, onClick) { - if(ids.indexOf(baseId) < 0) { - ids.push(baseId); - $i('umi-msg-container').insertBefore(createButton(baseId, title, onClick), $i('umi-msg-send')); - } + AddButton: function(baseId, title, onClick, beforeButtonId) { + if(ids.includes(baseId)) + return; + ids.push(baseId); + + let beforeButton; + if(typeof beforeButtonId === 'string') + beforeButton = $i(createButtonId(beforeButtonId)) + if(!(beforeButton instanceof Element)) + beforeButton = $i('umi-msg-send'); + + $i('umi-msg-container').insertBefore(createButton(baseId, title, onClick), beforeButton); }, Get: function(baseId, button) { const id = 'umi-msg-menu-' + (button ? 'btn' : 'sub') + '-' + baseId; @@ -63,7 +79,7 @@ Umi.UI.InputMenus = (function() { return null; }, Remove: function(baseId) { - $ri('umi-msg-menu-btn-' + baseId); + $ri(createButtonId(baseId)); $ri('umi-msg-menu-sub-' + baseId); }, Toggle: toggle, diff --git a/src/mami.js/ui/settings.jsx b/src/mami.js/ui/settings.jsx index 4a87327..ee33f95 100644 --- a/src/mami.js/ui/settings.jsx +++ b/src/mami.js/ui/settings.jsx @@ -141,7 +141,7 @@ Umi.UI.Settings = (function() { type: 'select', options: () => { const packs = mami.sound.packs; - const options = {}; + const options = { '': 'Default' }; for(const name of packs.names()) options[name] = packs.info(name).getTitle();