diff --git a/client/src/structs/settings/types/array.js b/client/src/structs/settings/types/array.js index e06ae06f..6b128864 100644 --- a/client/src/structs/settings/types/array.js +++ b/client/src/structs/settings/types/array.js @@ -14,6 +14,7 @@ import Setting from './basesetting'; import SettingsSet from '../settingsset'; import SettingsCategory from '../settingscategory'; import SettingsScheme from '../settingsscheme'; +import { SettingsUpdatedEvent } from 'structs'; export default class ArraySetting extends Setting { @@ -108,21 +109,27 @@ export default class ArraySetting extends Setting { * @param {SettingsSet} item Values to merge into the new set (optional) * @return {SettingsSet} The new set */ - addItem(item) { - const newItem = this.createItem(item); - this.args.items.push(newItem); - this.updateValue(); - return newItem; + async addItem(_item) { + const item = this.createItem(_item); + this.args.items.push(item); + await this.updateValue(); + + await this.emit('item-added', { item }); + + return item; } /** * Removes a set from this array setting. * This ignores the minimum value. * @param {SettingsSet} item The set to remove + * @return {Promise} */ - removeItem(item) { + async removeItem(item) { this.args.items = this.items.filter(i => i !== item); - this.updateValue(); + await this.updateValue(); + + await this.emit('item-removed', { item }); } /** @@ -135,24 +142,84 @@ export default class ArraySetting extends Setting { return item; const set = new SettingsSet({ + id: item ? item.args ? item.args.id : item.id : Math.random(), settings: Utils.deepclone(this.settings), schemes: this.schemes }, item ? item.args || item : undefined); set.setSaved(); - set.on('settings-updated', () => this.updateValue()); + set.on('settings-updated', async event => { + await this.emit('item-updated', { item: set, event, updatedSettings: event.updatedSettings }); + if (event.args.updating_array !== this) await this.updateValue(); + }); return set; } + /** + * Function to be called after the value changes. + * This can be overridden by other settings types. + * This function is used when the value needs to be updated synchronously (basically just in the constructor - so there won't be any events to emit anyway). + * @param {SettingUpdatedEvent} updatedSetting + */ + setValueHookSync(updatedSetting) { + this.args.items = updatedSetting.value ? updatedSetting.value.map(item => this.createItem(item)) : []; + } + /** * Function to be called after the value changes. * This can be overridden by other settings types. * @param {SettingUpdatedEvent} updatedSetting */ - setValueHook(updatedSetting) { - this.args.items = updatedSetting.value ? updatedSetting.value.map(item => this.createItem(item)) : []; + async setValueHook(updatedSetting) { + const newItems = []; + let error; + + for (let newItem of updatedSetting.value) { + try { + const item = this.items.find(i => i.id && i.id === newItem.id); + + if (item) { + // Merge the new item into the original item + newItems.push(item); + const updatedSettings = await item.merge(newItem, false); + if (!updatedSettings.length) continue; + + const event = new SettingsUpdatedEvent({ + updatedSettings, + updating_array: this + }); + + await item.emit('settings-updated', event); + // await this.emit('item-updated', { item, event, updatedSettings }); + } else { + // Add a new item + const item = this.createItem(newItem); + newItems.push(item); + await this.emit('item-added', { item }); + } + } catch (e) { error = e; } + } + + for (let item of this.items) { + if (newItems.includes(item)) continue; + + try { + // Item removed + await this.emit('item-removed', { item }); + } catch (e) { error = e; } + } + + this.args.items = newItems; + + // We can't throw anything before the items array is updated, otherwise the array setting would be in an inconsistent state where the values in this.items wouldn't match the values in this.value + if (error) throw error; } + // emit(...args) { + // console.log('Emitting event', args[0], 'with data', args[1]); + // return this.emitter.emit(...args); + // } + /** * Updates the value of this array setting. * This only exists for use by array settings. diff --git a/client/src/structs/settings/types/basesetting.js b/client/src/structs/settings/types/basesetting.js index 19a74a10..b174bcea 100644 --- a/client/src/structs/settings/types/basesetting.js +++ b/client/src/structs/settings/types/basesetting.js @@ -105,9 +105,9 @@ export default class Setting { * Merges a setting into this setting without emitting events (and therefore synchronously). * This only exists for use by the constructor and SettingsCategory. */ - _merge(newSetting) { + _merge(newSetting, hook = true) { const value = newSetting.args ? newSetting.args.value : newSetting.value; - return this._setValue(value); + return this._setValue(value, hook); } /** @@ -116,12 +116,13 @@ export default class Setting { * @return {Promise} */ async merge(newSetting, emit_multi = true, emit = true) { - const updatedSettings = this._merge(newSetting); + const updatedSettings = this._merge(newSetting, false); if (!updatedSettings.length) return []; - const updatedSetting = updatedSettings[0]; + + await this.setValueHook(updatedSettings[0]); if (emit) - await this.emit('setting-updated', updatedSetting); + await this.emit('setting-updated', updatedSettings[0]); if (emit_multi) await this.emit('settings-updated', new SettingsUpdatedEvent({ @@ -135,7 +136,7 @@ export default class Setting { * Sets the value of this setting. * This only exists for use by the constructor and SettingsCategory. */ - _setValue(value) { + _setValue(value, hook = true) { const old_value = this.args.value; if (Utils.compare(value, old_value)) return []; this.args.value = value; @@ -146,7 +147,8 @@ export default class Setting { value, old_value }); - this.setValueHook(updatedSetting); + if (hook) + this.setValueHookSync(updatedSetting); return [updatedSetting]; } @@ -156,7 +158,8 @@ export default class Setting { * This can be overridden by other settings types. * @param {SettingUpdatedEvent} updatedSetting */ - setValueHook(updatedSetting) {} + async setValueHook(updatedSetting) {} + setValueHookSync(updatedSetting) {} /** * Sets the value of this setting. @@ -164,9 +167,11 @@ export default class Setting { * @return {Promise} */ async setValue(value, emit_multi = true, emit = true) { - const updatedSettings = this._setValue(value); + const updatedSettings = this._setValue(value, false); if (!updatedSettings.length) return []; + await this.setValueHook(updatedSettings[0]); + if (emit) await this.emit('setting-updated', updatedSettings[0]); diff --git a/client/src/ui/components/bd/setting/Array.vue b/client/src/ui/components/bd/setting/Array.vue index 4524d9c5..3386f203 100644 --- a/client/src/ui/components/bd/setting/Array.vue +++ b/client/src/ui/components/bd/setting/Array.vue @@ -48,14 +48,14 @@ MiSettings, MiOpenInNew, MiMinus }, methods: { - addItem(openModal) { + async addItem(openModal) { if (this.setting.disabled || this.setting.max && this.setting.items.length >= this.setting.max) return; - const item = this.setting.addItem(); - if (openModal) this.showModal(item, this.setting.items.length); + const item = await this.setting.addItem(); + if (openModal) this.showModal(item, this.setting.items.length - 1); }, - removeItem(item) { + async removeItem(item) { if (this.setting.disabled || this.setting.min && this.setting.items.length <= this.setting.min) return; - this.setting.removeItem(item); + await this.setting.removeItem(item); }, showModal(item, index) { Modals.settings(item, this.setting.headertext ? this.setting.headertext.replace(/%n/, index + 1) : this.setting.text + ` #${index + 1}`); diff --git a/tests/plugins/Example 4/config.json b/tests/plugins/Example 4/config.json new file mode 100644 index 00000000..c38da573 --- /dev/null +++ b/tests/plugins/Example 4/config.json @@ -0,0 +1,84 @@ +{ + "info": { + "id": "example-plugin-4", + "name": "Example Plugin 4", + "authors": [ + "Samuel Elliott" + ], + "version": 1.0, + "description": "Plugin for testing array setting events as the first example plugin has a lot of stuff in it now." + }, + "main": "index.js", + "type": "plugin", + "defaultConfig": [ + { + "category": "default", + "settings": [ + { + "id": "array-1", + "type": "array", + "text": "Test settings array", + "settings": [ + { + "category": "default", + "settings": [ + { + "id": "default-0", + "type": "bool", + "value": false, + "text": "Bool Test Setting 3", + "hint": "Bool Test Setting Hint 3" + }, + { + "id": "default-1", + "type": "text", + "value": "defaultValue", + "text": "Text Test Setting", + "hint": "Text Test Setting Hint" + } + ] + } + ], + "schemes": [ + { + "id": "scheme-1", + "name": "Test scheme", + "icon_url": "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0c/Cow_female_black_white.jpg/220px-Cow_female_black_white.jpg", + "settings": [ + { + "category": "default", + "settings": [ + { + "id": "default-0", + "value": true + } + ] + } + ] + }, + { + "id": "scheme-2", + "name": "Another test scheme", + "icon_url": "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0c/Cow_female_black_white.jpg/220px-Cow_female_black_white.jpg", + "settings": [ + { + "category": "default", + "settings": [ + { + "id": "default-0", + "value": false + }, + { + "id": "default-1", + "value": "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0c/Cow_female_black_white.jpg/220px-Cow_female_black_white.jpg" + } + ] + } + ] + } + ] + } + ] + } + ] +} diff --git a/tests/plugins/Example 4/index.js b/tests/plugins/Example 4/index.js new file mode 100644 index 00000000..c1105cd4 --- /dev/null +++ b/tests/plugins/Example 4/index.js @@ -0,0 +1,10 @@ +module.exports = (Plugin, { Logger }) => class extends Plugin { + onstart() { + // Some array event examples + const arraySetting = this.settings.getSetting('default', 'array-1'); + Logger.log('Array setting', arraySetting); + arraySetting.on('item-added', event => Logger.log('Item', event.item, 'was added to the array setting')); + arraySetting.on('item-updated', event => Logger.log('Item', event.item, 'of the array setting was updated', event)); + arraySetting.on('item-removed', event => Logger.log('Item', event.item, 'removed from the array setting')); + } +}; diff --git a/tests/plugins/Example/index.js b/tests/plugins/Example/index.js index d94f12f3..38583eba 100644 --- a/tests/plugins/Example/index.js +++ b/tests/plugins/Example/index.js @@ -14,7 +14,7 @@ module.exports = (Plugin, Api, Vendor, Dependencies) => { Events.subscribe('TEST_EVENT', this.eventTest); Logger.log('onStart'); - Logger.log(`Plugin setting "default-0" value: ${this.getSetting('default-0')}`); + Logger.log(`Plugin setting "default-0" value: ${this.settings.get('default-0')}`); this.events.on('setting-updated', event => { console.log('Received plugin setting update:', event); }); @@ -92,14 +92,14 @@ module.exports = (Plugin, Api, Vendor, Dependencies) => { test1() { return 'It works!'; } test2() { return 'This works too!'; } - settingChanged(category, setting_id, value) { + settingChanged(event) { if (!this.enabled) return; - Logger.log(`${category}/${setting_id} changed to ${value}`); + Logger.log(`${event.category_id}/${event.setting_id} changed to ${event.value}`); } - settingsChanged(settings) { + settingsChanged(event) { if (!this.enabled) return; - Logger.log([ 'Settings updated', settings ]); + Logger.log([ 'Settings updated', event.updatedSettings ]); } get settingscomponent() {