mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-08-10 16:10:55 +00:00
4.0.0 Beta 1
This commit is contained in:
parent
c2688646af
commit
262757a20d
187 changed files with 22878 additions and 38882 deletions
287
src/settings/context.js
Normal file
287
src/settings/context.js
Normal file
|
@ -0,0 +1,287 @@
|
|||
'use strict';
|
||||
|
||||
// ============================================================================
|
||||
// Settings Contexts
|
||||
// ============================================================================
|
||||
|
||||
import {EventEmitter} from 'utilities/events';
|
||||
import {has, get as getter, array_equals} from 'utilities/object';
|
||||
|
||||
|
||||
/**
|
||||
* The SettingsContext class provides a context through which to read
|
||||
* settings values in addition to emitting events when settings values
|
||||
* are changed.
|
||||
* @extends EventEmitter
|
||||
*/
|
||||
export default class SettingsContext extends EventEmitter {
|
||||
constructor(manager, context) {
|
||||
super();
|
||||
|
||||
if ( manager instanceof SettingsContext ) {
|
||||
this.parent = manager;
|
||||
this.manager = manager.manager;
|
||||
|
||||
this.parent.on('context_changed', this._rebuildContext, this);
|
||||
|
||||
} else {
|
||||
this.parent = null;
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
this.manager.__contexts.push(this);
|
||||
this._context = context || {};
|
||||
|
||||
this.__cache = new Map;
|
||||
this.__meta = new Map;
|
||||
this.__profiles = [];
|
||||
this.order = [];
|
||||
|
||||
this._rebuildContext();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if ( this.parent )
|
||||
this.parent.off('context_changed', this._rebuildContext, this);
|
||||
|
||||
for(const profile of this.__profiles)
|
||||
profile.off('changed', this._onChanged, this);
|
||||
|
||||
const contexts = this.manager.__contexts,
|
||||
idx = contexts.indexOf(this);
|
||||
|
||||
if ( idx !== -1 )
|
||||
contexts.splice(idx, 1);
|
||||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// State Construction
|
||||
// ========================================================================
|
||||
|
||||
_rebuildContext() {
|
||||
this.__context = this.parent ?
|
||||
Object.assign({}, this.parent._context, this._context) :
|
||||
this._context;
|
||||
|
||||
// Make sure we re-build the cache. Dependency hell.
|
||||
if ( ! this.selectProfiles() )
|
||||
this.rebuildCache();
|
||||
|
||||
this.emit('context_changed');
|
||||
}
|
||||
|
||||
|
||||
selectProfiles() {
|
||||
const new_profiles = [],
|
||||
order = this.order = [];
|
||||
for(const profile of this.manager.__profiles)
|
||||
if ( profile.matches(this.__context) ) {
|
||||
new_profiles.push(profile);
|
||||
order.push(profile.id);
|
||||
}
|
||||
|
||||
if ( array_equals(this.__profiles, new_profiles) )
|
||||
return false;
|
||||
|
||||
const changed_ids = new Set;
|
||||
|
||||
for(const profile of this.__profiles)
|
||||
if ( ! new_profiles.includes(profile) ) {
|
||||
profile.off('changed', this._onChanged, this);
|
||||
changed_ids.add(profile.id);
|
||||
}
|
||||
|
||||
for(const profile of new_profiles)
|
||||
if ( ! this.__profiles.includes(profile) ) {
|
||||
profile.on('changed', this._onChanged, this);
|
||||
changed_ids.add(profile.id);
|
||||
}
|
||||
|
||||
this.__profiles = new_profiles;
|
||||
this.emit('profiles_changed');
|
||||
this.rebuildCache(changed_ids);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
rebuildCache() {
|
||||
const old_cache = this.__cache,
|
||||
old_meta = this.__meta,
|
||||
meta = this.__meta = new Map;
|
||||
|
||||
this.__cache = new Map;
|
||||
|
||||
// TODO: Limit the values we recalculate to ones affected by the change
|
||||
// that happened to the profiles. This is harder because of setting
|
||||
// dependencies.
|
||||
|
||||
for(const [key, old_value] of old_cache) {
|
||||
const new_value = this.get(key),
|
||||
new_m = meta.get(key),
|
||||
old_m = old_meta.get(key),
|
||||
new_uses = new_m ? new_m.uses : null,
|
||||
old_uses = old_m ? old_m.uses : null;
|
||||
|
||||
if ( new_value !== old_value ) {
|
||||
this.emit('changed', key, new_value, old_value);
|
||||
this.emit(`changed:${key}`, new_value, old_value);
|
||||
}
|
||||
|
||||
if ( new_uses !== old_uses ) {
|
||||
this.emit('uses_changed', key, new_uses, old_uses);
|
||||
this.emit(`uses_changed:${key}`, new_uses, old_uses);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Context Control
|
||||
// ========================================================================
|
||||
|
||||
context(context) {
|
||||
return new SettingsContext(this, context);
|
||||
}
|
||||
|
||||
|
||||
updateContext(context) {
|
||||
let changed = false;
|
||||
|
||||
for(const key in context)
|
||||
if ( has(context, key) && context[key] !== this._context[key] ) {
|
||||
this._context[key] = context[key];
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if ( changed )
|
||||
this._rebuildContext();
|
||||
}
|
||||
|
||||
|
||||
setContext(context) {
|
||||
this._context = context;
|
||||
this._rebuildContext();
|
||||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Data Access
|
||||
// ========================================================================
|
||||
|
||||
_onChanged(key) {
|
||||
this._update(key, key, []);
|
||||
}
|
||||
|
||||
_update(key, initial, visited) {
|
||||
if ( ! this.__cache.has(key) )
|
||||
return;
|
||||
|
||||
else if ( visited.includes(key) )
|
||||
throw new Error(`cyclic dependent chain when updating setting "${initial}"`);
|
||||
|
||||
visited.push(key);
|
||||
|
||||
const old_value = this.__cache.get(key),
|
||||
old_meta = this.__meta.get(key),
|
||||
new_value = this._get(key, key, []),
|
||||
new_meta = this.__meta.get(key),
|
||||
|
||||
old_uses = old_meta ? old_meta.uses : null,
|
||||
new_uses = new_meta ? new_meta.uses : null;
|
||||
|
||||
if ( old_uses !== new_uses ) {
|
||||
this.emit('uses_changed', key, new_uses, old_uses);
|
||||
this.emit(`uses_changed:${key}`, new_uses, old_uses);
|
||||
}
|
||||
|
||||
if ( old_value === new_value )
|
||||
return;
|
||||
|
||||
this.emit('changed', key, new_value, old_value);
|
||||
this.emit(`changed:${key}`, new_value, old_value);
|
||||
|
||||
const definition = this.manager.definitions.get(key);
|
||||
if ( definition && definition.required_by )
|
||||
for(const req_key of definition.required_by)
|
||||
if ( ! req_key.startsWith('context.') )
|
||||
this._update(req_key, initial, Array.from(visited));
|
||||
}
|
||||
|
||||
|
||||
_get(key, initial, visited) {
|
||||
if ( visited.includes(key) )
|
||||
throw new Error(`cyclic dependency when resolving setting "${initial}"`);
|
||||
|
||||
visited.push(key);
|
||||
|
||||
const definition = this.manager.definitions.get(key),
|
||||
raw_value = this._getRaw(key),
|
||||
meta = {
|
||||
uses: raw_value ? raw_value[1].id : null
|
||||
};
|
||||
|
||||
let value = raw_value ? raw_value[0] : undefined;
|
||||
|
||||
if ( definition ) {
|
||||
if ( Array.isArray(definition) )
|
||||
throw new Error(`non-existent setting "${key}" required when resolving setting "${initial}"`);
|
||||
|
||||
if ( meta.uses === null ) {
|
||||
const def_default = definition.default;
|
||||
if ( typeof def_default === 'function' )
|
||||
value = def_default(this);
|
||||
else
|
||||
value = def_default;
|
||||
}
|
||||
|
||||
if ( definition.requires )
|
||||
for(const req_key of definition.requires)
|
||||
if ( ! req_key.startsWith('context.') && ! this.__cache.has(req_key) )
|
||||
this._get(req_key, initial, Array.from(visited));
|
||||
|
||||
if ( definition.process )
|
||||
value = definition.process(this, value, meta);
|
||||
}
|
||||
|
||||
this.__cache.set(key, value);
|
||||
this.__meta.set(key, meta);
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
_getRaw(key) {
|
||||
for(const profile of this.__profiles)
|
||||
if ( profile.has(key) )
|
||||
return [profile.get(key), profile]
|
||||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Data Access
|
||||
// ========================================================================
|
||||
|
||||
update(key) {
|
||||
this._update(key, key, []);
|
||||
}
|
||||
|
||||
get(key) {
|
||||
if ( key.startsWith('context.') )
|
||||
return getter(key.slice(8), this.__context);
|
||||
|
||||
if ( this.__cache.has(key) )
|
||||
return this.__cache.get(key);
|
||||
|
||||
return this._get(key, key, []);
|
||||
}
|
||||
|
||||
uses(key) {
|
||||
if ( key.startsWith('context.') )
|
||||
return null;
|
||||
|
||||
if ( ! this.__meta.has(key) )
|
||||
this._get(key, key, []);
|
||||
|
||||
return this.__meta.get(key).uses;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue