flash
cf71bab92d
This has been in the works for over a month and might break things because it's a very radical change. If it causes you to be unable to join chat, report it on the forum or try joining using the legacy chat on https://sockchat.flashii.net.
170 lines
6.1 KiB
JavaScript
170 lines
6.1 KiB
JavaScript
#include settings/scoped.js
|
|
#include settings/virtual.js
|
|
#include settings/webstorage.js
|
|
|
|
const MamiSettings = function(storageOrPrefix, eventTarget) {
|
|
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);
|
|
const settings = new Map;
|
|
|
|
const createUpdateEvent = (name, value, initial) => eventTarget.create(name, {
|
|
name: name,
|
|
value: value,
|
|
initial: !!initial,
|
|
});
|
|
const dispatchUpdate = (name, value) => eventTarget.dispatch(createUpdateEvent(name, value));
|
|
|
|
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 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);
|
|
dispatchUpdate(setting.name, setting.fallback);
|
|
broadcastUpdate(setting.name, setting.fallback);
|
|
};
|
|
|
|
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);
|
|
|
|
dispatchUpdate(setting.name, value);
|
|
broadcastUpdate(setting.name, value);
|
|
};
|
|
|
|
broadcast.onmessage = ev => {
|
|
if(typeof ev.data !== 'object' || typeof ev.data.act !== 'string')
|
|
return;
|
|
|
|
if(ev.data.act === 'update' && typeof ev.data.name === 'string') {
|
|
dispatchUpdate(ev.data.name, ev.data.value);
|
|
return;
|
|
}
|
|
};
|
|
|
|
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);
|
|
},
|
|
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 => deleteValue(getSetting(name)),
|
|
toggle: name => {
|
|
const setting = getSetting(name);
|
|
if(!setting.immutable)
|
|
setValue(setting, !getValue(setting));
|
|
},
|
|
touch: name => {
|
|
const setting = getSetting(name);
|
|
dispatchUpdate(setting.name, getValue(setting));
|
|
},
|
|
clear: (criticalOnly, prefix) => {
|
|
for(const setting of settings.values())
|
|
if((prefix === undefined || setting.name.startsWith(prefix)) && (!criticalOnly || setting.critical))
|
|
deleteValue(setting);
|
|
},
|
|
watch: (name, handler) => {
|
|
const setting = getSetting(name);
|
|
eventTarget.watch(setting.name, handler);
|
|
handler(createUpdateEvent(setting.name, getValue(setting), true));
|
|
},
|
|
unwatch: (name, handler) => {
|
|
eventTarget.unwatch(name, handler);
|
|
},
|
|
virtualise: name => storage.virtualise(getSetting(name).name),
|
|
scope: name => new MamiSettingsScoped(pub, name),
|
|
};
|
|
|
|
return pub;
|
|
};
|