mami/src/mami.js/settings/settings.js
flash cf71bab92d Rewrote connection handling.
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.
2024-04-17 15:42:50 +00:00

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;
};