From a3dc8b7e07e840edb52382673b121f5a634142d4 Mon Sep 17 00:00:00 2001 From: Harvey Tindall Date: Mon, 24 Nov 2025 15:17:49 +0000 Subject: [PATCH] settings: render groups a little off at the moment but works, groups show as accordions and can be nested. Maybe add indentation, and probably show them first in the list. Also make sure search works with them. --- ts/modules/settings.ts | 98 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 2 deletions(-) diff --git a/ts/modules/settings.ts b/ts/modules/settings.ts index 2a992b3..5e8a99b 100644 --- a/ts/modules/settings.ts +++ b/ts/modules/settings.ts @@ -472,6 +472,13 @@ class DOMNote extends DOMSetting implements SNote { asElement = (): HTMLDivElement => { return this._container; } } +interface Group { + group: string; + name: string; + description: string; + members: ({ group: string } | { section: string })[]; +} + interface Section { section: string; meta: Meta; @@ -567,6 +574,7 @@ class sectionPanel { } interface Settings { + groups: Group[]; sections: Section[]; } @@ -580,8 +588,12 @@ export class settingsList { private _panel = document.getElementById("settings-panel") as HTMLDivElement; private _sidebar = document.getElementById("settings-sidebar") as HTMLDivElement; private _visibleSection: string; - private _sections: { [name: string]: sectionPanel } - private _buttons: { [name: string]: HTMLSpanElement } + private _sections: { [name: string]: sectionPanel }; + private _buttons: { [name: string]: HTMLSpanElement }; + + private _groups: { [name: string]: Group }; + private _groupButtons: { [name: string]: HTMLSpanElement }; + private _needsRestart: boolean = false; private _messageEditor = new MessageEditor(); private _settings: Settings; @@ -595,6 +607,83 @@ export class settingsList { private _backupSortDirection = document.getElementById("settings-backups-sort-direction") as HTMLButtonElement; private _backupSortAscending = true; + // Must be called -after- all section have been added. + // Takes all groups at once since members might contain each other. + addGroups = (groups: Group[]) => { + groups.forEach((g) => { this._groups[g.group] = g }); + const addGroup = (g: Group): HTMLElement => { + if (g.group in this._groupButtons) return null; + + const container = document.createElement("div") as HTMLDivElement; + container.classList.add("flex", "flex-col", "gap-2"); + + const button = document.createElement("span") as HTMLSpanElement; + container.appendChild(button); + button.classList.add("button", "~neutral", "@low", "settings-section-button", "justify-between"); + button.innerHTML = ` + ${g.name} + + `; + + const icon = button.querySelector("i.icon"); + const check = button.querySelector("input[type=checkbox]") as HTMLInputElement; + + button.onclick = () => { + check.checked = !check.checked; + if (check.checked) { + icon.classList.add("rotated"); + dropdown.classList.remove("unfocused"); + dropdown.style.maxHeight = dropdown.scrollHeight+"px"; + dropdown.style.opacity = "100%"; + } else { + icon.classList.remove("rotated"); + const hide = () => { + dropdown.classList.add("unfocused"); + dropdown.removeEventListener("transitionend", hide); + }; + dropdown.addEventListener("transitionend", hide); + dropdown.style.maxHeight = "0"; + dropdown.style.opacity = "0"; + } + } + + const dropdown = document.createElement("div") as HTMLDivElement; + container.appendChild(dropdown); + dropdown.style.maxHeight = "0"; + dropdown.style.opacity = "0"; + dropdown.classList.add("unfocused", "flex", "flex-col", "gap-2", "flex-1", "max-h-0", "transition-all"); + + for (const member of g.members) { + if ("group" in member) { + let subgroup = addGroup(this._groups[member.group]); + if (!subgroup) { + subgroup = this._groupButtons[member.group]; + // Remove from page + subgroup.remove(); + } + dropdown.appendChild(subgroup); + } else if ("section" in member) { + const subsection = this._buttons[member.section]; + // Remove from page + subsection.remove(); + dropdown.appendChild(subsection); + } + } + + this._groupButtons[g.group] = container; + return container; + } + for (let g of groups) { + const container = addGroup(g); + if (container) { + this._sidebar.appendChild(container); + } + } + } + addSection = (name: string, s: Section, subButton?: HTMLElement) => { const section = new sectionPanel(s, name); this._sections[name] = section; @@ -759,6 +848,8 @@ export class settingsList { }); constructor() { + this._groups = {}; + this._groupButtons = {}; this._sections = {}; this._buttons = {}; document.addEventListener("settings-section-changed", () => this._saveButton.classList.remove("unfocused")); @@ -920,6 +1011,9 @@ export class settingsList { } } } + + this.addGroups(this._settings.groups); + removeLoader(this._loader); for (let i = 0; i < this._loader.children.length; i++) { this._loader.children[i].classList.remove("invisible");