Async setting and category events

- Async setting and category events
- Added deepfreeze utility
- Fix dropdown staying open after selecting an option
This commit is contained in:
Samuel Elliott 2018-03-04 00:22:05 +00:00
parent 91275f4332
commit 00d909f16c
No known key found for this signature in database
GPG Key ID: 8420C7CDE43DC4D6
11 changed files with 195 additions and 72 deletions

View File

@ -9,7 +9,7 @@
*/
import Globals from './globals';
import { FileUtils, ClientLogger as Logger } from 'common';
import { Utils, FileUtils, ClientLogger as Logger } from 'common';
import path from 'path';
import { Events } from 'modules';
import { SettingsSet, ErrorEvent } from 'structs';
@ -176,33 +176,41 @@ export default class {
const readConfig = await this.readConfig(contentPath);
const mainPath = path.join(contentPath, readConfig.main);
const defaultConfig = new SettingsSet({
settings: readConfig.defaultConfig,
schemes: readConfig.configSchemes
});
const userConfig = {
enabled: false,
config: new SettingsSet({
settings: readConfig.defaultConfig,
schemes: readConfig.configSchemes
})
config: undefined,
data: {}
};
for (let category of userConfig.config.settings) {
for (let setting of category.settings) {
setting.setContentPath(contentPath);
}
}
try {
const readUserConfig = await this.readUserConfig(contentPath);
userConfig.enabled = readUserConfig.enabled || false;
userConfig.config.merge({ settings: readUserConfig.config });
userConfig.config.setSaved();
// await userConfig.config.merge({ settings: readUserConfig.config });
// userConfig.config.setSaved();
// userConfig.config = userConfig.config.clone({ settings: readUserConfig.config });
userConfig.config = readUserConfig.config;
userConfig.data = readUserConfig.data || {};
} catch (err) { /*We don't care if this fails it either means that user config doesn't exist or there's something wrong with it so we revert to default config*/
console.info(`Failed reading config for ${this.contentType} ${readConfig.info.name} in ${dirName}`);
console.error(err);
}
userConfig.config = defaultConfig.clone({ settings: userConfig.config });
userConfig.config.setSaved();
for (let setting of userConfig.config.findSettings(() => true)) {
setting.setContentPath(contentPath);
}
Utils.deepfreeze(defaultConfig);
const configs = {
defaultConfig: readConfig.defaultConfig,
defaultConfig,
schemes: userConfig.schemes,
userConfig
};

View File

@ -83,7 +83,8 @@ export default class PluginApi {
tryParseJson: () => Utils.tryParseJson.apply(Utils, arguments),
toCamelCase: () => Utils.toCamelCase.apply(Utils, arguments),
compare: () => Utils.compare.apply(Utils, arguments),
deepclone: () => Utils.deepclone.apply(Utils, arguments)
deepclone: () => Utils.deepclone.apply(Utils, arguments),
deepfreeze: () => Utils.deepfreeze.apply(Utils, arguments)
};
}

View File

@ -9,30 +9,31 @@
*/
import Setting from './setting';
import EventEmitter from 'events';
import { ClientLogger as Logger } from 'common';
import { ClientLogger as Logger, AsyncEventEmitter } from 'common';
import { SettingUpdatedEvent, SettingsUpdatedEvent } from 'structs';
export default class SettingsCategory {
constructor(args) {
this.emitter = new EventEmitter();
constructor(args, ...merge) {
this.emitter = new AsyncEventEmitter();
this.args = args.args || args;
this.args.settings = this.settings.map(setting => new Setting(setting));
for (let newCategory of merge) {
this._merge(newCategory);
}
for (let setting of this.settings) {
setting.on('setting-updated', ({ value, old_value }) => {
this.emit('setting-updated', new SettingUpdatedEvent({
category: this, category_id: this.id,
setting, setting_id: setting.id,
value, old_value
}));
});
setting.on('setting-updated', ({ value, old_value }) => this.emit('setting-updated', new SettingUpdatedEvent({
category: this, category_id: this.id,
setting, setting_id: setting.id,
value, old_value
})));
setting.on('settings-updated', ({ updatedSettings }) => this.emit('settings-updated', new SettingsUpdatedEvent({
updatedSettings: updatedSettings.map(updatedSetting => Object.assign({
updatedSettings: updatedSettings.map(updatedSetting => new SettingUpdatedEvent(Object.assign({
category: this, category_id: this.id
}, updatedSetting))
}, updatedSetting)))
})));
}
}
@ -78,7 +79,11 @@ export default class SettingsCategory {
return this.findSetting(setting => setting.id === id);
}
merge(newCategory, emit_multi = true) {
/**
* Merges a category into this category without emitting events (and therefore synchronously).
* Only exists for use by SettingsSet.
*/
_merge(newCategory) {
let updatedSettings = [];
for (let newSetting of newCategory.settings) {
@ -88,7 +93,29 @@ export default class SettingsCategory {
continue;
}
const updatedSetting = setting.merge(newSetting, false);
const updatedSetting = setting._merge(newSetting);
if (!updatedSetting) continue;
updatedSettings = updatedSettings.concat(updatedSetting.map(({ setting, value, old_value }) => ({
category: this, category_id: this.id,
setting, setting_id: setting.id,
value, old_value
})));
}
return updatedSettings;
}
async merge(newCategory, emit_multi = true) {
let updatedSettings = [];
for (let newSetting of newCategory.settings) {
const setting = this.settings.find(setting => setting.id === newSetting.id);
if (!setting) {
Logger.warn('SettingsCategory', `Trying to merge setting ${this.id}/${newSetting.id}, which does not exist.`);
continue;
}
const updatedSetting = await setting._merge(newSetting, false);
if (!updatedSetting) continue;
updatedSettings = updatedSettings.concat(updatedSetting.map(({ setting, value, old_value }) => ({
category: this, category_id: this.id,
@ -98,9 +125,10 @@ export default class SettingsCategory {
}
if (emit_multi)
this.emit('settings-updated', new SettingsUpdatedEvent({
await this.emit('settings-updated', new SettingsUpdatedEvent({
updatedSettings
}));
return updatedSettings;
}
@ -117,7 +145,7 @@ export default class SettingsCategory {
};
}
clone() {
clone(...merge) {
return new SettingsCategory({
id: this.id,
category: this.id,
@ -125,7 +153,7 @@ export default class SettingsCategory {
category_name: this.category_name,
type: this.type,
settings: this.settings.map(setting => setting.clone())
});
}, ...merge);
}
on(...args) { return this.emitter.on(...args); }

View File

@ -16,14 +16,18 @@ import { Modals } from 'ui';
export default class SettingsSet {
constructor(args) {
constructor(args, ...merge) {
this.emitter = new AsyncEventEmitter();
this.args = args.args || args;
this.args.settings = this.settings.map(category => new SettingsCategory(category));
this.args.categories = this.categories.map(category => new SettingsCategory(category));
this.args.schemes = this.schemes.map(scheme => new SettingsScheme(scheme));
for (let category of this.settings) {
for (let newSet of merge) {
this._merge(newSet);
}
for (let category of this.categories) {
category.on('setting-updated', ({ setting, value, old_value }) => this.emit('setting-updated', new SettingUpdatedEvent({
set: this, set_id: this.id,
category, category_id: category.id,
@ -31,9 +35,9 @@ export default class SettingsSet {
value, old_value
})));
category.on('settings-updated', ({ updatedSettings }) => this.emit('settings-updated', new SettingsUpdatedEvent({
updatedSettings: updatedSettings.map(updatedSetting => Object.assign({
updatedSettings: updatedSettings.map(updatedSetting => new SettingUpdatedEvent(Object.assign({
set: this, set_id: this.id
}, updatedSetting))
}, updatedSetting)))
})));
}
}
@ -55,7 +59,7 @@ export default class SettingsSet {
}
get categories() {
return this.args.settings || [];
return this.args.categories || this.args.settings || [];
}
get settings() {
@ -91,10 +95,7 @@ export default class SettingsSet {
}
findSettings(f) {
for (let category of this.categories) {
const setting = category.findSettings(f);
if (setting) return setting;
}
return this.findSettingsInCategory(() => true, f);
}
findSettingInCategory(cf, f) {
@ -105,10 +106,11 @@ export default class SettingsSet {
}
findSettingsInCategory(cf, f) {
let settings = [];
for (let category of this.categories.filter(cf)) {
const setting = category.findSettings(f);
if (setting) return setting;
settings = settings.concat(category.findSettings(f));
}
return settings;
}
getSetting(id, sid) {
@ -125,9 +127,14 @@ export default class SettingsSet {
Modals.settings(this, headertext ? headertext : this.headertext);
}
async merge(newSet, emit_multi = true) {
/**
* Merges a set into this set without emitting events (and therefore synchronously).
* Only exists for use by the constructor.
*/
_merge(newSet, emit_multi = true) {
let updatedSettings = [];
const categories = newSet instanceof Array ? newSet : newSet.settings;
// const categories = newSet instanceof Array ? newSet : newSet.settings;
const categories = newSet && newSet.args ? newSet.args.settings : newSet ? newSet.settings : newSet;
if (!categories) return [];
for (let newCategory of categories) {
@ -137,7 +144,33 @@ export default class SettingsSet {
continue;
}
const updatedSetting = category.merge(newCategory, false);
const updatedSetting = category._merge(newCategory, false);
if (!updatedSetting) continue;
updatedSettings = updatedSettings.concat(updatedSetting.map(({ category, setting, value, old_value }) => ({
set: this, set_id: this.id,
category, category_id: category.id,
setting, setting_id: setting.id,
value, old_value
})));
}
return updatedSettings;
}
async merge(newSet, emit_multi = true) {
let updatedSettings = [];
// const categories = newSet instanceof Array ? newSet : newSet.settings;
const categories = newSet && newSet.args ? newSet.args.settings : newSet ? newSet.settings : newSet;
if (!categories) return [];
for (let newCategory of categories) {
const category = this.find(category => category.category === newCategory.category);
if (!category) {
Logger.warn('SettingsCategory', `Trying to merge category ${newCategory.id}, which does not exist.`);
continue;
}
const updatedSetting = await category.merge(newCategory, false);
if (!updatedSetting) continue;
updatedSettings = updatedSettings.concat(updatedSetting.map(({ category, setting, value, old_value }) => ({
set: this, set_id: this.id,
@ -168,14 +201,14 @@ export default class SettingsSet {
return stripped;
}
clone() {
clone(...merge) {
return new SettingsSet({
id: this.id,
text: this.text,
headertext: this.headertext,
settings: this.categories.map(category => category.clone()),
schemes: this.schemes.map(scheme => scheme.clone())
});
}, ...merge);
}
on(...args) { return this.emitter.on(...args); }

View File

@ -17,8 +17,8 @@ import SettingsScheme from '../settingsscheme';
export default class ArraySetting extends Setting {
constructor(args) {
super(args);
constructor(args, ...merge) {
super(args, ...merge);
this.args.settings = this.settings.map(category => new SettingsCategory(category));
this.args.schemes = this.schemes.map(scheme => new SettingsScheme(scheme));
@ -85,9 +85,9 @@ export default class ArraySetting extends Setting {
const set = new SettingsSet({
settings: Utils.deepclone(this.settings),
schemes: this.schemes
});
}, item ? item.args || item : undefined);
if (item) set.merge(item.args || item);
// if (item) set.merge(item.args || item);
set.setSaved();
set.on('settings-updated', () => this.updateValue());
return set;
@ -124,8 +124,8 @@ export default class ArraySetting extends Setting {
return maps.length ? maps.join(', ') + ',' : '()';
}
clone() {
return new ArraySetting(Utils.deepclone(this.args));
}
// clone() {
// return new ArraySetting(Utils.deepclone(this.args));
// }
}

View File

@ -9,13 +9,12 @@
*/
import { ThemeManager } from 'modules';
import { Utils } from 'common';
import { Utils, AsyncEventEmitter } from 'common';
import { SettingUpdatedEvent, SettingsUpdatedEvent } from 'structs';
import EventEmitter from 'events';
export default class Setting {
constructor(args) {
constructor(args, ...merge) {
this.args = args.args || args;
if (!this.args.hasOwnProperty('value'))
@ -23,7 +22,11 @@ export default class Setting {
if (!this.args.hasOwnProperty('saved_value'))
this.args.saved_value = this.args.value;
this.emitter = new EventEmitter();
for (let newSetting of merge) {
this._merge(newSetting);
}
this.emitter = new AsyncEventEmitter();
this.changed = !Utils.compare(this.args.value, this.args.saved_value);
}
@ -67,8 +70,44 @@ export default class Setting {
return this.args.fullwidth || false;
}
merge(newSetting, emit_multi = true) {
return this.setValue(newSetting.args ? newSetting.args.value : newSetting.value, emit_multi);
/**
* Merges a setting into this setting without emitting events (and therefore synchronously).
* Only exists for use by SettingsCategory.
*/
_merge(newSetting) {
const value = newSetting.args ? newSetting.args.value : newSetting.value;
const old_value = this.args.value;
if (Utils.compare(value, old_value)) return [];
this.args.value = value;
this.changed = !Utils.compare(this.args.value, this.args.saved_value);
return [{
setting: this, setting_id: this.id,
value, old_value
}];
}
async merge(newSetting, emit_multi = true) {
const value = newSetting.args ? newSetting.args.value : newSetting.value;
const old_value = this.args.value;
if (Utils.compare(value, old_value)) return [];
this.args.value = value;
this.changed = !Utils.compare(this.args.value, this.args.saved_value);
const updatedSetting = {
setting: this, setting_id: this.id,
value, old_value
};
if (emit)
await this.emit('setting-updated', new SettingUpdatedEvent(updatedSetting));
if (emit_multi)
await this.emit('settings-updated', new SettingsUpdatedEvent({
updatedSettings: [updatedSetting]
}));
return [updatedSetting];
}
setValue(value, emit_multi = true, emit = true) {
@ -109,8 +148,8 @@ export default class Setting {
};
}
clone() {
return new this.constructor(Utils.deepclone(this.args));
clone(...merge) {
return new this.constructor(Utils.deepclone(this.args), ...merge);
}
toSCSS() {

View File

@ -15,8 +15,8 @@ import path from 'path';
export default class CustomSetting extends Setting {
constructor(args) {
super(args);
constructor(args, ...merge) {
super(args, ...merge);
if (this.args.class_file && this.path)
this.setClass(this.args.class_file, this.args.class);

View File

@ -13,8 +13,8 @@ import MultipleChoiceOption from '../multiplechoiceoption';
export default class DropdownSetting extends Setting {
constructor(args) {
super(args);
constructor(args, ...merge) {
super(args, ...merge);
this.args.options = this.options.map(option => new MultipleChoiceOption(option));
}

View File

@ -13,8 +13,8 @@ import MultipleChoiceOption from '../multiplechoiceoption';
export default class RadioSetting extends Setting {
constructor(args) {
super(args);
constructor(args, ...merge) {
super(args, ...merge);
this.args.options = this.options.map(option => new MultipleChoiceOption(option));
}

View File

@ -17,7 +17,7 @@
</span>
</div>
<div class="bd-dropdown-options bd-flex bd-flex-col" ref="options" v-if="active">
<div class="bd-dropdown-option" v-for="option in options" :class="{'bd-dropdown-option-selected': selected === option.value}" @click="change(option.value)">{{ option.text }}</div>
<div class="bd-dropdown-option" v-for="option in options" :class="{'bd-dropdown-option-selected': selected === option.value}" @click="change(option.value); active = false">{{ option.text }}</div>
</div>
</div>
</template>

View File

@ -83,6 +83,20 @@ export class Utils {
return value;
}
static deepfreeze(object) {
if (typeof object === 'object' && object !== null) {
const properties = Object.getOwnPropertyNames(object);
for (let property of properties) {
this.deepfreeze(object[property]);
}
Object.freeze(object);
}
return object;
}
}
export class FileUtils {