From 10f3f1008062be1ad5618e733c4328aebb7916ad Mon Sep 17 00:00:00 2001 From: EloiMusk Date: Thu, 9 Mar 2023 11:19:51 +0100 Subject: [PATCH] feat(setting-tab): general settings now working --- main.ts | 9 --- styles.css | 57 ++++++++++++-- utils/SettingTab.ts | 181 ++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 225 insertions(+), 22 deletions(-) diff --git a/main.ts b/main.ts index 0480ce6..314dcbd 100644 --- a/main.ts +++ b/main.ts @@ -109,15 +109,6 @@ export default class PrioPlugin extends Plugin { // This adds a settings tab so the user can configure various aspects of the plugin this.addSettingTab(new SettingTab(this.app, this)); - - // If the plugin hooks up any global DOM events (on parts of the app that doesn't belong to this plugin) - // Using this function will automatically remove the event listener when this plugin is disabled. - // this.registerDomEvent(document, 'click', (evt: MouseEvent) => { - // console.log('click', evt); - // }); - - // When registering intervals, this function will automatically clear the interval when the plugin is disabled. - this.registerInterval(window.setInterval(() => console.log('setInterval'), 5 * 60 * 1000)); } onunload() { diff --git a/styles.css b/styles.css index fa0aefc..740ac44 100644 --- a/styles.css +++ b/styles.css @@ -1,11 +1,4 @@ -/* - -This CSS file will be included with your plugin, and -available in the app when your plugin is enabled. - -If your plugin does not need CSS, delete this file. - -*/ +/*Preset list setting*/ .preset-list { display: flex; flex-wrap: nowrap; @@ -30,10 +23,27 @@ If your plugin does not need CSS, delete this file. background-color: var(--background-modifier-hover); } +.mod-danger { + background-color: var(--background-modifier-error); + color: var(--text-error); +} + +.mod-danger:hover { + background-color: var(--background-modifier-error-hover); + color: var(--text-faint); +} + .btn { margin: 5px; } +.btn:disabled { + background-color: var(--color-base-10) !important; + color: var(--color-base-60) !important; + border: none !important; + box-shadow: none !important; +} + .btn-primary { background-color: var(--interactive-accent); } @@ -42,7 +52,38 @@ If your plugin does not need CSS, delete this file. background-color: var(--interactive-accent-hover); } +/*---------------------*/ + +/*Level Slider setting*/ .level-text { max-width: 50px; margin: 5px; } + +/*---------------------*/ + +/*Level aliases list setting*/ +.level-aliases-list { + display: flex; + flex-flow: column nowrap; + width: 100%; + padding: 10px; +} + +.level-aliases-list-item { + align-items: center; + display: flex; + flex-direction: row; + justify-content: space-between; + width: 100%; + padding: 5px; + border-top: 1px solid var(--background-modifier-border); + border-bottom: 1px solid var(--background-modifier-border); +} + +.level-aliases-list-item-input { + margin-left: 5px; + max-width: 80%; +} + +/*---------------------*/ diff --git a/utils/SettingTab.ts b/utils/SettingTab.ts index 60d82ea..7c9a420 100644 --- a/utils/SettingTab.ts +++ b/utils/SettingTab.ts @@ -1,22 +1,43 @@ -import {App, PluginSettingTab, Setting, SliderComponent} from "obsidian"; +import {App, Modal, Notice, PluginSettingTab, Setting, SliderComponent} from "obsidian"; import PrioPlugin from "../main"; import {Preset} from "./Presets"; +import {PrioPluginSettings} from "./Settings"; export class SettingTab extends PluginSettingTab { plugin: PrioPlugin; + saveConfirm: SaveConfirm; + + previousSettings: PrioPluginSettings; + + modified = false; constructor(app: App, plugin: PrioPlugin) { super(app, plugin); this.plugin = plugin; + this.saveConfirm = new SaveConfirm(this); + } + + hide(): any { + if (this.modified) { + this.saveConfirm.open(); + return; + } + return super.hide(); } display(): void { + this.previousSettings = Object.assign({}, this.plugin.settings); const {containerEl} = this; + this.plugin.registerDomEvent(document, 'change', () => { + this.modified = true; + }); containerEl.empty(); let presets: Preset[] = this.plugin.settings.presets ?? []; + const saveButton = createEl('button', {text: 'Save', cls: ['mod-cta', 'mod-primary', 'btn']}); + containerEl.createEl('h2', {text: 'General Settings'}); @@ -68,6 +89,8 @@ export class SettingTab extends PluginSettingTab { value = 10; } levelSlider.setValue(value); + this.plugin.settings.levels = value; + this.generateLevelAliasList(this.plugin.settings, levelAliasesList, [saveButton]); }); this.generatePresetList(presets, presetList); @@ -83,10 +106,70 @@ export class SettingTab extends PluginSettingTab { .onChange(async (value) => { this.plugin.settings.levels = value; levelText.value = value.toString(); + this.generateLevelAliasList(this.plugin.settings, levelAliasesList, [saveButton]); }) .setDynamicTooltip() - }) + }); + new Setting(containerEl).setName('Level Aliases').setDesc('Set the aliases for each level.'); + + const levelAliasesContainer = containerEl.createEl('div'); + + const levelAliasesList = createEl('ol', { + cls: 'level-aliases-list', + parent: levelAliasesContainer + }); + + this.generateLevelAliasList(this.plugin.settings, levelAliasesList, [saveButton]); + + containerEl.append(saveButton); + saveButton.addEventListener('click', () => { + if (!this.isValid(levelAliasesList, [saveButton])) { + return; + } + this.setAliases(this.plugin.settings, levelAliasesList); + this.plugin.saveSettings().then(() => { + this.plugin.loadSettings().then(() => { + new Notice('Settings saved successfully!'); + }); + }); + }); + } + + setAliases = (settings: PrioPluginSettings, levelAliasesList: HTMLOListElement) => { + const inputs = levelAliasesList.querySelectorAll('input'); + const aliases: string[] = []; + inputs.forEach(input => { + aliases.push((input as HTMLInputElement).value); + }); + aliases.map((alias, index) => { + this.plugin.settings.levelAliases[index] = alias; + }); + } + + generateLevelAliasList = (settings: PrioPluginSettings, levelAliasesList: HTMLOListElement, buttons: HTMLButtonElement[]) => { + levelAliasesList.empty(); + const els: HTMLElement[] = []; + while (settings.levels > levelAliasesList.children.length) { + const el = createEl('li', { + text: `${levelAliasesList.children.length + 1}`, + cls: 'level-aliases-list-item', + parent: levelAliasesList + }); + const input = el.createEl('input', { + cls: 'level-aliases-list-item-input', + value: settings.levelAliases[levelAliasesList.children.length - 1] ?? '', + }) + input.addEventListener('blur', (event) => { + const valid = this.validateLevelAlias((event.target as HTMLInputElement).value, parseInt((event.target as HTMLInputElement).parentElement?.textContent ?? '') - 1, settings.levelAliases.slice(0, settings.levels)); + for (const button of buttons) { + button.disabled = !valid; + } + }); + els.push(el); + } + + return els; } generatePresetList = (presets: Preset[], presetList: HTMLOListElement) => (presets || []).map(preset => { @@ -100,14 +183,14 @@ export class SettingTab extends PluginSettingTab { }); btnGroup.createEl('button', { - text: 'Select', - cls: ['preset-list-item-select', 'btn', 'btn-primary'], + text: 'Apply', + cls: ['preset-list-item-apply', 'btn', 'btn-primary'], attr: { 'onclick': 'alert("Preset Selected")' } }) btnGroup.createEl('button', { - text: 'Save', + text: 'Overwrite', cls: ['preset-list-item-save', 'btn', 'btn-primary'], attr: { 'onclick': 'alert("Save Preset")' @@ -115,4 +198,92 @@ export class SettingTab extends PluginSettingTab { }) return el; }); + + isValid = (levelAliasesList: HTMLOListElement, buttons: HTMLButtonElement[]) => { + const inputs = levelAliasesList.querySelectorAll('input'); + const aliases: string[] = []; + inputs.forEach(input => { + aliases.push((input as HTMLInputElement).value); + }); + if (aliases.length !== new Set(aliases).size) { + new Notice('Level aliases must be unique.'); + return false; + } + if (aliases.some(alias => alias.length === 0)) { + new Notice('Level aliases must not be empty.'); + return false; + } + return true; + } + + validateLevelAlias = (alias: string, index: number, aliases: string[]) => { + if (aliases.indexOf(alias) != index && aliases.includes(alias)) { + new Notice('Alias already exists!'); + return false; + } + if (alias.length < 1) { + new Notice('Alias must be at least 1 character long!'); + return false; + } + if (alias.match(/[^a-zA-Z0-9]/)) { + new Notice('Alias must only contain alphanumeric characters!'); + return false; + } + return true; + } +} + +class SaveConfirm extends Modal { + private settingsTab: SettingTab; + + constructor(settingsTab: SettingTab) { + super(settingsTab.app); + this.settingsTab = settingsTab; + } + + open() { + super.open(); + } + + onOpen() { + const {contentEl} = this; + contentEl.setText('You have unsaved changes. Would you like to save them?'); + + const btnGroup = contentEl.createEl('div', { + cls: 'save-btn-group' + }); + + const saveButton = createEl('button', { + text: 'Save', + cls: ['btn', 'mod-cta'], + }); + const discardButton = createEl('button', { + text: 'Discard', + cls: ['btn', 'mod-danger'], + }); + + saveButton.addEventListener('click', () => { + this.settingsTab.plugin.saveSettings().then(() => { + this.settingsTab.plugin.loadSettings().then(() => { + new Notice('Settings saved successfully!'); + }); + }); + this.close(); + }); + + discardButton.addEventListener('click', () => { + this.settingsTab.plugin.loadSettings().then(() => { + new Notice('Settings discarded successfully!'); + }); + this.close(); + }); + + btnGroup.append(saveButton); + btnGroup.append(discardButton); + } + + onClose() { + const {contentEl} = this; + contentEl.empty(); + } }