From 1dffb1f40b7372d8d0dc367c9c9770b906ee0e81 Mon Sep 17 00:00:00 2001 From: Samuel Elliott Date: Sun, 4 Mar 2018 01:33:06 +0000 Subject: [PATCH] Add comments --- client/src/modules/contentmanager.js | 6 +- .../src/structs/settings/settingscategory.js | 50 ++++++++- client/src/structs/settings/settingsset.js | 104 +++++++++++++++++- client/src/structs/settings/types/array.js | 88 +++++++++++++-- .../src/structs/settings/types/basesetting.js | 67 ++++++++++- client/src/structs/settings/types/bool.js | 7 ++ client/src/structs/settings/types/colour.js | 7 ++ client/src/structs/settings/types/custom.js | 26 ++++- client/src/structs/settings/types/dropdown.js | 13 +++ client/src/structs/settings/types/file.js | 14 +++ client/src/structs/settings/types/radio.js | 13 +++ client/src/structs/settings/types/slider.js | 18 +++ client/src/structs/settings/types/text.js | 12 +- 13 files changed, 404 insertions(+), 21 deletions(-) diff --git a/client/src/modules/contentmanager.js b/client/src/modules/contentmanager.js index bf3d3a58..b5620b87 100644 --- a/client/src/modules/contentmanager.js +++ b/client/src/modules/contentmanager.js @@ -196,8 +196,8 @@ export default class { 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); + Logger.info(this.moduleName, `Failed reading config for ${this.contentType} ${readConfig.info.name} in ${dirName}`); + Logger.err(this.moduleName, err); } userConfig.config = defaultConfig.clone({ settings: userConfig.config }); @@ -222,7 +222,7 @@ export default class { }; const content = await this.loadContent(paths, configs, readConfig.info, readConfig.main, readConfig.dependencies, readConfig.permissions); - if (!content) return null; + if (!content) return undefined; if (!reload && this.getContentById(content.id)) throw {message: `A ${this.contentType} with the ID ${content.id} already exists.`}; diff --git a/client/src/structs/settings/settingscategory.js b/client/src/structs/settings/settingscategory.js index e5cce144..345eafaf 100644 --- a/client/src/structs/settings/settingscategory.js +++ b/client/src/structs/settings/settingscategory.js @@ -38,6 +38,9 @@ export default class SettingsCategory { } } + /** + * Category ID + */ get id() { return this.args.id || this.args.category; } @@ -46,6 +49,9 @@ export default class SettingsCategory { return this.id; } + /** + * Category name + */ get name() { return this.args.category_name; } @@ -54,34 +60,59 @@ export default class SettingsCategory { return this.name; } + /** + * Category type + * Currently either "drawer", "static", or undefined. + */ get type() { return this.args.type; } + /** + * An array of settings in this category. + */ get settings() { return this.args.settings || []; } + /** + * Whether any setting in this category has been changed. + */ get changed() { if (this.settings.find(setting => setting.changed)) return true; return false; } + /** + * Returns the first setting where calling {function} returns true. + * @param {Function} function A function to call to filter setting + * @return {Setting} + */ find(f) { return this.settings.find(f); } + /** + * Returns all settings where calling {function} returns true. + * @param {Function} function A function to call to filter settings + * @return {Array} An array of matching Setting objects + */ findSettings(f) { return this.settings.filter(f); } + /** + * Returns the setting with the ID {id}. + * @param {String} id The ID of the setting to look for + * @return {Setting} + */ getSetting(id) { return this.findSetting(setting => setting.id === id); } /** * Merges a category into this category without emitting events (and therefore synchronously). - * Only exists for use by SettingsSet. + * This only exists for use by SettingsSet. */ _merge(newCategory) { let updatedSettings = []; @@ -105,6 +136,11 @@ export default class SettingsCategory { return updatedSettings; } + /** + * Merges another category into this category. + * @param {SettingsCategory} newCategory The category to merge into this category + * @return {Promise} + */ async merge(newCategory, emit_multi = true) { let updatedSettings = []; @@ -132,12 +168,19 @@ export default class SettingsCategory { return updatedSettings; } + /** + * Marks all settings in this set as saved (not changed). + */ setSaved() { for (let setting of this.settings) { setting.setSaved(); } } + /** + * Returns an object that can be stored as JSON and later merged back into a category with settingscategory.merge. + * @return {Object} + */ strip() { return { category: this.category, @@ -145,6 +188,11 @@ export default class SettingsCategory { }; } + /** + * Returns a copy of this category that can be changed and then merged back into a set with settingscategory.merge. + * @param {SettingsCategory} ...merge A set to merge into the new set + * @return {SettingsCategory} + */ clone(...merge) { return new SettingsCategory({ id: this.id, diff --git a/client/src/structs/settings/settingsset.js b/client/src/structs/settings/settingsset.js index e6ad395c..f1694e88 100644 --- a/client/src/structs/settings/settingsset.js +++ b/client/src/structs/settings/settingsset.js @@ -42,22 +42,42 @@ export default class SettingsSet { } } + /** + * Set ID + */ get id() { return this.args.id; } + /** + * Set name + */ get text() { return this.args.text; } + /** + * Text to be displayed with the set. + */ get headertext() { return this.args.headertext || `${this.text} Settings`; } + set headertext(headertext) { + this.args.headertext = headertext; + } + + /** + * Whether this set should be displayed. + * Currently only used in the settings menu. + */ get hidden() { return this.args.hidden || false; } + /** + * An array of SettingsCategory objects in this set. + */ get categories() { return this.args.categories || this.args.settings || []; } @@ -66,27 +86,53 @@ export default class SettingsSet { return this.categories; } + /** + * An array of SettingsScheme objects that can be used in this set. + */ get schemes() { return this.args.schemes || []; } + /** + * Whether any setting in this set has been changed. + */ get changed() { if (this.categories.find(category => category.changed)) return true; return false; } + /** + * Returns the first category where calling {function} returns true. + * @param {Function} function A function to call to filter categories + * @return {SettingsCategory} + */ find(f) { return this.categories.find(f); } + /** + * Returns all categories where calling {function} returns true. + * @param {Function} function A function to call to filter categories + * @return {Array} An array of matching SettingsCategory objects + */ findCategories(f) { return this.categories.filter(f); } + /** + * Returns the category with the ID {id}. + * @param {String} id The ID of the category to look for + * @return {SettingsCategory} + */ getCategory(id) { return this.find(category => category.id === id); } + /** + * Returns the first setting where calling {function} returns true. + * @param {Function} function A function to call to filter settings + * @return {Setting} + */ findSetting(f) { for (let category of this.categories) { const setting = category.findSetting(f); @@ -94,10 +140,21 @@ export default class SettingsSet { } } + /** + * Returns all settings where calling {function} returns true. + * @param {Function} function A function to call to filter settings + * @return {Array} An array of matching Setting objects + */ findSettings(f) { return this.findSettingsInCategory(() => true, f); } + /** + * Returns the first setting where calling {function} returns true. + * @param {Function} categoryFunction A function to call to filter categories + * @param {Function} function A function to call to filter settings + * @return {Array} An array of matching Setting objects + */ findSettingInCategory(cf, f) { for (let category of this.categories.filter(cf)) { const setting = category.find(f); @@ -105,6 +162,12 @@ export default class SettingsSet { } } + /** + * Returns all settings where calling {function} returns true. + * @param {Function} categoryFunction A function to call to filter categories + * @param {Function} function A function to call to filter settings + * @return {Array} An array of matching Setting objects + */ findSettingsInCategory(cf, f) { let settings = []; for (let category of this.categories.filter(cf)) { @@ -113,23 +176,41 @@ export default class SettingsSet { return settings; } + /** + * Returns the setting with the ID {id}. + * @param {String} categoryid The ID of the category to look in (optional) + * @param {String} id The ID of the setting to look for + * @return {Setting} + */ getSetting(id, sid) { if (sid) return this.findSettingInCategory(category => category.id === id, setting => setting.id === sid); return this.findSetting(setting => setting.id === id); } + /** + * Returns the value of the setting with the ID {id}. + * @param {String} categoryid The ID of the category to look in (optional) + * @param {String} id The ID of the setting to look for + * @return {SettingsCategory} + */ get(cid, sid) { const setting = this.getSetting(cid, sid); return setting ? setting.value : undefined; } - showModal(headertext) { - Modals.settings(this, headertext ? headertext : this.headertext); + /** + * Opens this set in a modal. + * @param {String} headertext Text to be displayed in the modal header + * @param {Object} options Additional options to pass to Modals.settings + * @return {Modal} + */ + showModal(headertext, options) { + return Modals.settings(this, headertext ? headertext : this.headertext, options); } /** * Merges a set into this set without emitting events (and therefore synchronously). - * Only exists for use by the constructor. + * This only exists for use by the constructor. */ _merge(newSet, emit_multi = true) { let updatedSettings = []; @@ -157,6 +238,11 @@ export default class SettingsSet { return updatedSettings; } + /** + * Merges another set into this set. + * @param {SettingsSet} newSet The set to merge into this set + * @return {Promise} + */ async merge(newSet, emit_multi = true) { let updatedSettings = []; // const categories = newSet instanceof Array ? newSet : newSet.settings; @@ -188,12 +274,19 @@ export default class SettingsSet { return updatedSettings; } + /** + * Marks all settings in this set as saved (not changed). + */ setSaved() { for (let category of this.categories) { category.setSaved(); } } + /** + * Returns an object that can be stored as JSON and later merged back into a set with settingsset.merge. + * @return {Object} + */ strip() { const stripped = {}; if (this.id) stripped.id = this.id; @@ -201,6 +294,11 @@ export default class SettingsSet { return stripped; } + /** + * Returns a copy of this set that can be changed and then merged back into a set with settingsset.merge. + * @param {SettingsSet} ...merge A set to merge into the new set + * @return {SettingsSet} + */ clone(...merge) { return new SettingsSet({ id: this.id, diff --git a/client/src/structs/settings/types/array.js b/client/src/structs/settings/types/array.js index 73f62373..55744da5 100644 --- a/client/src/structs/settings/types/array.js +++ b/client/src/structs/settings/types/array.js @@ -27,10 +27,16 @@ export default class ArraySetting extends Setting { this.updateValue(false, false); } + /** + * The value to use when the setting doesn't have a value. + */ get defaultValue() { return []; } + /** + * An array of sets currently in this array setting. + */ get items() { return this.args.items || []; } @@ -40,47 +46,90 @@ export default class ArraySetting extends Setting { this.updateValue(); } + /** + * Whether the setting should take the full width of the settings panel. + * This is always false for array settings. + */ get fullwidth() { return false; } - get settings() { - return this.args.settings || []; + /** + * An array of SettingsCategory objects that each set in this setting should have. + */ + get categories() { + return this.args.categories || this.args.settings || []; } + get settings() { + return this.categories; + } + + /** + * An array of SettingsScheme objects that can be used in this array's sets. + */ get schemes() { return this.args.schemes || []; } + /** + * Whether to display this array setting's sets inline instead of opening them in a modal. + */ get inline() { return this.args.inline || false; } + /** + * Whether to allow opening this array setting's sets in a modal. + * This is always true when inline is false. + */ get allow_external() { return this.args.allow_external || !this.inline; } + /** + * The minimum amount of sets the user may create. + * This only restricts deleting sets when there is less or equal sets than this, and does not ensure that this number of items actually exists. + */ get min() { return this.args.min || 0; } + /** + * The maximum amount of sets the user may create. + */ get max() { return this.args.max || null; } + /** + * Adds a new set to this array setting. + * This ignores the maximum value. + * @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.items = this.items; this.updateValue(); return newItem; } + /** + * Removes a set from this array setting. + * This ignores the minimum value. + * @param {SettingsSet} item The set to remove + */ removeItem(item) { this.args.items = this.items.filter(i => i !== item); this.updateValue(); } + /** + * Creates a new set for this array setting. + * @param {SettingsSet} item Values to merge into the new set (optional) + * @return {SettingsSet} The new set + */ createItem(item) { const set = new SettingsSet({ settings: Utils.deepclone(this.settings), @@ -93,10 +142,26 @@ export default class ArraySetting extends Setting { return set; } + /** + * Sets the value of this setting. + * This is only intended for use by settings. + * @param {SettingsSetting} value The new value of this setting + * @param {Boolean} emit_multi Whether to emit a SettingsUpdatedEvent + * @param {Boolean} emit Whether to emit a SettingUpdatedEvent + * @return {Promise} + */ setValue(value, emit_multi = true, emit = true) { - this.items = value; + this.args.items = value ? value.map(item => this.createItem(item)) : []; + this.updateValue(emit_multi, emit); } + /** + * Updates the value of this array setting. + * This only exists for use by array settings. + * @param {Boolean} emit_multi Whether to emit a SettingsUpdatedEvent + * @param {Boolean} emit Whether to emit a SettingUpdatedEvent + * @return {Promise} + */ updateValue(emit_multi = true, emit = true) { return this.__proto__.__proto__.setValue.call(this, this.items.map(item => { if (!item) return; @@ -105,16 +170,25 @@ export default class ArraySetting extends Setting { }), emit_multi, emit); } + /** + * Sets the path of the plugin/theme this setting is part of. + * This is passed to this array setting's settings. + * @param {String} contentPath The plugin/theme's directory path + */ setContentPath(contentPath) { this.args.path = contentPath; - for (let category of this.settings) { + for (let category of this.categories) { for (let setting of category.settings) { setting.setContentPath(contentPath); } } } + /** + * Returns a representation of this setting's value in SCSS. + * @return {Promise} + */ async toSCSS() { const maps = []; for (let item of this.items) @@ -124,8 +198,4 @@ export default class ArraySetting extends Setting { return maps.length ? maps.join(', ') + ',' : '()'; } - // clone() { - // return new ArraySetting(Utils.deepclone(this.args)); - // } - } diff --git a/client/src/structs/settings/types/basesetting.js b/client/src/structs/settings/types/basesetting.js index 09347136..01c3388a 100644 --- a/client/src/structs/settings/types/basesetting.js +++ b/client/src/structs/settings/types/basesetting.js @@ -30,14 +30,24 @@ export default class Setting { this.changed = !Utils.compare(this.args.value, this.args.saved_value); } + /** + * Setting ID + */ get id() { return this.args.id; } + /** + * Setting type + * This defines how this class will be extended. + */ get type() { return this.args.type; } + /** + * The current value. + */ get value() { return this.args.value; } @@ -46,33 +56,54 @@ export default class Setting { this.setValue(value); } + /** + * The value to use when the setting doesn't have a value. + */ get defaultValue() { return undefined; } + /** + * Setting name + */ get text() { return this.args.text; } + /** + * Text to be displayed with the setting. + */ get hint() { return this.args.hint; } + /** + * The path of the plugin/theme this setting is part of. + * Used by settings of type "array", "custom" and "file". + */ get path() { return this.args.path; } + /** + * Whether the user should be able to change the value of the setting. + * This does not prevent the setting being changed by a plugin. + */ get disabled() { return this.args.disabled || false; } + /** + * Whether the setting should take the full width of the settings panel. + * This is only customisable in some setting types. + */ get fullwidth() { return this.args.fullwidth || false; } /** * Merges a setting into this setting without emitting events (and therefore synchronously). - * Only exists for use by SettingsCategory. + * This only exists for use by SettingsCategory. */ _merge(newSetting) { const value = newSetting.args ? newSetting.args.value : newSetting.value; @@ -87,6 +118,11 @@ export default class Setting { }]; } + /** + * Merges another setting into this setting. + * @param {SettingsSetting} newSetting The setting to merge into this setting + * @return {Promise} + */ async merge(newSetting, emit_multi = true) { const value = newSetting.args ? newSetting.args.value : newSetting.value; const old_value = this.args.value; @@ -110,6 +146,14 @@ export default class Setting { return [updatedSetting]; } + /** + * Sets the value of this setting. + * This is only intended for use by settings. + * @param {Any} value The new value of this setting + * @param {Boolean} emit_multi Whether to emit a SettingsUpdatedEvent + * @param {Boolean} emit Whether to emit a SettingUpdatedEvent + * @return {Promise} + */ setValue(value, emit_multi = true, emit = true) { const old_value = this.args.value; if (Utils.compare(value, old_value)) return []; @@ -132,15 +176,27 @@ export default class Setting { return [updatedSetting]; } + /** + * Marks this setting as saved (not changed). + */ setSaved() { this.args.saved_value = this.args.value; this.changed = false; } + /** + * Sets the path of the plugin/theme this setting is part of. + * Used by settings of type "array", "custom" and "file". + * @param {String} contentPath The plugin/theme's directory path + */ setContentPath(contentPath) { this.args.path = contentPath; } + /** + * Returns an object that can be stored as JSON and later merged back into a setting with setting.merge. + * @return {Object} + */ strip() { return { id: this.id, @@ -148,10 +204,19 @@ export default class Setting { }; } + /** + * Returns a copy of this setting that can be changed and then merged back into a set with setting.merge. + * @param {Setting} ...merge A setting to merge into the new setting + * @return {Setting} + */ clone(...merge) { return new this.constructor(Utils.deepclone(this.args), ...merge); } + /** + * Returns a representation of this setting's value in SCSS. + * @return {String|Promise} + */ toSCSS() { if (typeof this.value === 'boolean' || typeof this.value === 'number') { return this.value; diff --git a/client/src/structs/settings/types/bool.js b/client/src/structs/settings/types/bool.js index 0816a7ae..f2559a31 100644 --- a/client/src/structs/settings/types/bool.js +++ b/client/src/structs/settings/types/bool.js @@ -12,10 +12,17 @@ import Setting from './basesetting'; export default class BoolSetting extends Setting { + /** + * The value to use when the setting doesn't have a value. + */ get defaultValue() { return false; } + /** + * Whether the setting should take the full width of the settings panel. + * This is always false for boolean settings. + */ get fullwidth() { return false; } diff --git a/client/src/structs/settings/types/colour.js b/client/src/structs/settings/types/colour.js index 69b5191b..e6c882ff 100644 --- a/client/src/structs/settings/types/colour.js +++ b/client/src/structs/settings/types/colour.js @@ -12,10 +12,17 @@ import Setting from './basesetting'; export default class ColourSetting extends Setting { + /** + * The value to use when the setting doesn't have a value. + */ get defaultValue() { return 'rgba(0, 0, 0, 0)'; } + /** + * Returns a representation of this setting's value in SCSS. + * @return {String|Promise} + */ toSCSS() { return this.value; } diff --git a/client/src/structs/settings/types/custom.js b/client/src/structs/settings/types/custom.js index 83e96134..a44bdb54 100644 --- a/client/src/structs/settings/types/custom.js +++ b/client/src/structs/settings/types/custom.js @@ -22,33 +22,53 @@ export default class CustomSetting extends Setting { this.setClass(this.args.class_file, this.args.class); } + /** + * The file to load the custom setting from. + */ get file() { return this.args.file; } + /** + * The name of a function on the plugin's main object that will be called to get a Vue component or a HTML element. + */ get function() { return this.args.function; } + /** + * The name of an export of {file}, or a Vue component. + */ get component() { return this.args.component; } + /** + * Whether to show a debug view under the custom setting's component. + */ get debug() { return this.args.debug || false; } + /** + * Sets the path of the plugin/theme this setting is part of. + * Used by settings of type "array", "custom" and "file". + * @param {String} contentPath The plugin/theme's directory path + */ setContentPath(_path) { this.args.path = _path; if (this.args.class_file) this.setClass(this.args.class_file, this.args.class); - - console.log(`Custom setting ${this.id}:`, this); } + /** + * Replaces the custom setting's prototype with a new one that extends CustomSetting. + * @param {String} classFile The path of a file relative to the plugin/theme's directory that will be required + * @param {String} classExport The name of a property of the file's exports that will be used (optional) + */ setClass(class_file, class_export) { - const component = window.require(path.join(this.path, this.args.class_file)); + const component = window.require(path.join(this.path, class_file)); const setting_class = class_export ? component[class_export](CustomSetting) : component.default ? component.default(CustomSetting) : component(CustomSetting); if (!(setting_class.prototype instanceof CustomSetting)) diff --git a/client/src/structs/settings/types/dropdown.js b/client/src/structs/settings/types/dropdown.js index 87be413c..25f35aec 100644 --- a/client/src/structs/settings/types/dropdown.js +++ b/client/src/structs/settings/types/dropdown.js @@ -19,6 +19,9 @@ export default class DropdownSetting extends Setting { this.args.options = this.options.map(option => new MultipleChoiceOption(option)); } + /** + * The current value. + */ get value() { const selected = this.selected_option; if (selected) return selected.value; @@ -31,10 +34,16 @@ export default class DropdownSetting extends Setting { else this.setValue(value); } + /** + * An array of MultipleChoiceOption objects. + */ get options() { return this.args.options || []; } + /** + * The currently selected option. + */ get selected_option() { return this.options.find(option => option.id === this.args.value); } @@ -43,6 +52,10 @@ export default class DropdownSetting extends Setting { this.args.value = selected_option.id; } + /** + * Returns a representation of this setting's value in SCSS. + * @return {String} + */ toSCSS() { return this.value; } diff --git a/client/src/structs/settings/types/file.js b/client/src/structs/settings/types/file.js index bc43874d..914fd848 100644 --- a/client/src/structs/settings/types/file.js +++ b/client/src/structs/settings/types/file.js @@ -15,14 +15,24 @@ import path from 'path'; export default class FileSetting extends Setting { + /** + * The value to use when the setting doesn't have a value. + */ get defaultValue() { return []; } + /** + * An object that will be passed to electron.dialog.showOpenDialog. + */ get dialogOptions() { return this.args.dialogOptions || {}; } + /** + * Opens the file selection dialog and sets this file setting's value to an array of selected file paths. + * @return {Promise} + */ async openDialog() { if (this.disabled) return; @@ -31,6 +41,10 @@ export default class FileSetting extends Setting { this.value = filenames; } + /** + * Returns a representation of this setting's value in SCSS. + * @return {String|Promise} + */ async toSCSS() { if (!this.value || !this.value.length) return '()'; diff --git a/client/src/structs/settings/types/radio.js b/client/src/structs/settings/types/radio.js index bb96fcc7..1438de4e 100644 --- a/client/src/structs/settings/types/radio.js +++ b/client/src/structs/settings/types/radio.js @@ -19,6 +19,9 @@ export default class RadioSetting extends Setting { this.args.options = this.options.map(option => new MultipleChoiceOption(option)); } + /** + * The current value. + */ get value() { const selected = this.selected_option; if (selected) return selected.value; @@ -31,10 +34,16 @@ export default class RadioSetting extends Setting { else this.setValue(value); } + /** + * An array of MultipleChoiceOption objects. + */ get options() { return this.args.options || []; } + /** + * The currently selected option. + */ get selected_option() { return this.options.find(option => option.id === this.args.value); } @@ -43,6 +52,10 @@ export default class RadioSetting extends Setting { this.args.value = selected_option.id; } + /** + * Returns a representation of this setting's value in SCSS. + * @return {String} + */ toSCSS() { return this.value; } diff --git a/client/src/structs/settings/types/slider.js b/client/src/structs/settings/types/slider.js index 0e1a99bc..f8343f21 100644 --- a/client/src/structs/settings/types/slider.js +++ b/client/src/structs/settings/types/slider.js @@ -12,26 +12,44 @@ import Setting from './basesetting'; export default class SliderSetting extends Setting { + /** + * The value to use when the setting doesn't have a value. + */ get defaultValue() { return this.min; } + /** + * The smallest number the user may select. + */ get min() { return this.args.min || 0; } + /** + * The largest number the user may select. + */ get max() { return this.args.max || null; } + /** + * How much the user may change the value at once by moving the slider. + */ get step() { return this.args.step || 1; } + /** + * A string that will be displayed with the value. + */ get unit() { return this.args.unit || ''; } + /** + * An object mapping points on the slider to labels. + */ get points() { return this.args.points; } diff --git a/client/src/structs/settings/types/text.js b/client/src/structs/settings/types/text.js index dcd4fcb5..bacbff05 100644 --- a/client/src/structs/settings/types/text.js +++ b/client/src/structs/settings/types/text.js @@ -12,16 +12,26 @@ import Setting from './basesetting'; export default class StringSetting extends Setting { + /** + * The value to use when the setting doesn't have a value. + */ get defaultValue() { return ''; } + /** + * Whether the setting should take the full width of the settings panel. + * This is always true when multiline is true. + */ get fullwidth() { return this.args.fullwidth && !this.multiline; } + /** + * Whether to display a multiline text area instead of a single line text input. + */ get multiline() { - return this.args.multiline || null; + return this.args.multiline || false; } }