settings: de-dupe settings

all DOM elements now based off DOMSetting, which encompasses most
functionality. Extending classes (i forgot the terminology) pretty much just pass a
custom "input" element, "hider" element (the one to unfocus). DOMList
and DOMSelect remain slightly more complicated, but are much cleaner
now. Some CSS stuff has been adjusted too.
pull/297/head
Harvey Tindall 5 months ago
parent 32161139b2
commit a7aa3fd53e
No known key found for this signature in database
GPG Key ID: BBC65952848FB1A2

@ -127,6 +127,8 @@ func (app *appContext) loadConfig() error {
app.MustSetValue("user_expiry", "adjustment_email_html", "jfa-go:"+"expiry-adjusted.html") app.MustSetValue("user_expiry", "adjustment_email_html", "jfa-go:"+"expiry-adjusted.html")
app.MustSetValue("user_expiry", "adjustment_email_text", "jfa-go:"+"expiry-adjusted.txt") app.MustSetValue("user_expiry", "adjustment_email_text", "jfa-go:"+"expiry-adjusted.txt")
app.MustSetValue("email", "collect", "true")
app.MustSetValue("matrix", "topic", "Jellyfin notifications") app.MustSetValue("matrix", "topic", "Jellyfin notifications")
app.MustSetValue("matrix", "show_on_reg", "true") app.MustSetValue("matrix", "show_on_reg", "true")

@ -896,11 +896,20 @@
"value": false, "value": false,
"description": "Send emails as plain text instead of HTML." "description": "Send emails as plain text instead of HTML."
}, },
"collect": {
"name": "Collect on sign-up",
"required": false,
"requires_restart": false,
"depends_true": "method",
"type": "bool",
"value": true,
"description": "Ask for an email address on the sign-up form."
},
"required": { "required": {
"name": "Require on sign-up", "name": "Require on sign-up",
"required": false, "required": false,
"requires_restart": false, "requires_restart": false,
"depends_true": "method", "depends_true": "collect",
"type": "bool", "type": "bool",
"value": false, "value": false,
"description": "Require an email address on sign-up." "description": "Require an email address on sign-up."
@ -909,6 +918,7 @@
"name": "Require unique address", "name": "Require unique address",
"required": false, "required": false,
"requires_restart": true, "requires_restart": true,
"depends_true": "method",
"type": "bool", "type": "bool",
"value": false, "value": false,
"description": "Disables using the same address on multiple accounts." "description": "Disables using the same address on multiple accounts."

@ -312,25 +312,27 @@
<form class="relative mx-auto my-[10%] w-4/5 lg:w-2/3 content card" id="form-editor" href=""> <form class="relative mx-auto my-[10%] w-4/5 lg:w-2/3 content card" id="form-editor" href="">
<span class="heading"><span id="header-editor"></span> <span class="modal-close">&times;</span></span> <span class="heading"><span id="header-editor"></span> <span class="modal-close">&times;</span></span>
<div class="row"> <div class="row">
<div class="col card ~neutral @low"> <div class="col card ~neutral @low flex flex-col gap-2 justify-between">
<aside class="aside sm ~urge dark:~d_info mb-2 @low" id="aside-editor"></aside> <div class="flex flex-col gap-2">
<span class="label supra" for="editor-variables" id="label-editor-variables">{{ .strings.variables }}</span> <aside class="aside sm ~urge dark:~d_info @low" id="aside-editor"></aside>
<div id="editor-variables" class="mt-4"></div> <label class="label supra" for="editor-variables" id="label-editor-variables">{{ .strings.variables }}</label>
<span class="label supra" for="editor-conditionals" id="label-editor-conditionals">{{ .strings.conditionals }}</span> <div id="editor-variables" class="flex flex-row gap-2 flex-wrap"></div>
<div id="editor-conditionals"></div> <span class="label supra" for="editor-conditionals" id="label-editor-conditionals">{{ .strings.conditionals }}</span>
<label class="label supra" for="textarea-editor">{{ .strings.message }}</label> <div id="editor-conditionals"></div>
<textarea id="textarea-editor" class="textarea full-width flex-auto ~neutral @low mt-4 font-mono"></textarea> <label class="label supra" for="textarea-editor">{{ .strings.message }}</label>
<p class="support mt-4 mb-2">{{ .strings.markdownSupported }}</p> <textarea id="textarea-editor" class="textarea full-width flex-auto ~neutral @low font-mono"></textarea>
<div class="flex-row"> </div>
<label class="full-width ml-2"> <div class="flex flex-col gap-2">
<p class="support">{{ .strings.markdownSupported }}</p>
<label class="w-full">
<input type="submit" class="unfocused"> <input type="submit" class="unfocused">
<span class="button ~urge @low full-width center supra submit">{{ .strings.submit }}</span> <span class="button ~urge @low w-full supra submit">{{ .strings.submit }}</span>
</label> </label>
</div> </div>
</div> </div>
<div class="col card ~neutral @low"> <div class="col card ~neutral @low flex flex-col gap-2">
<span class="subheading supra">{{ .strings.preview }}</span> <span class="subheading supra">{{ .strings.preview }}</span>
<div class="mt-8" id="editor-preview"></div> <div id="editor-preview"></div>
</div> </div>
</div> </div>
</form> </form>

@ -2,6 +2,12 @@ import { _get, _post, _delete, _download, _upload, toggleLoader, addLoader, remo
import { Marked } from "@ts-stack/markdown"; import { Marked } from "@ts-stack/markdown";
import { stripMarkdown } from "../modules/stripmd.js"; import { stripMarkdown } from "../modules/stripmd.js";
const toBool = (s: string): boolean => {
let b = Boolean(s);
if (s == "false") b = false;
return b;
}
interface BackupDTO { interface BackupDTO {
size: string; size: string;
name: string; name: string;
@ -9,8 +15,8 @@ interface BackupDTO {
date: number; date: number;
} }
interface settingsBoolEvent extends Event { interface settingsChangedEvent extends Event {
detail: boolean; detail: string;
} }
interface Meta { interface Meta {
@ -52,7 +58,8 @@ const splitDependant = (section: string, dep: string): string[] => {
return parts return parts
}; };
class DOMInput { class DOMSetting {
protected _hideEl: HTMLElement;
protected _input: HTMLInputElement; protected _input: HTMLInputElement;
protected _container: HTMLDivElement; protected _container: HTMLDivElement;
protected _tooltip: HTMLDivElement; protected _tooltip: HTMLDivElement;
@ -63,19 +70,19 @@ class DOMInput {
protected _name: string; protected _name: string;
hide = () => { hide = () => {
this._input.parentElement.classList.add("unfocused"); this._hideEl.classList.add("unfocused");
const event = new CustomEvent(`settings-${this._section}-${this._name}`, { "detail": false }) const event = new CustomEvent(`settings-${this._section}-${this._name}`, { "detail": false })
document.dispatchEvent(event); document.dispatchEvent(event);
}; };
show = () => { show = () => {
this._input.parentElement.classList.remove("unfocused"); this._hideEl.classList.remove("unfocused");
const event = new CustomEvent(`settings-${this._section}-${this._name}`, { "detail": this.valueAsString() }) const event = new CustomEvent(`settings-${this._section}-${this._name}`, { "detail": this.valueAsString() })
document.dispatchEvent(event); document.dispatchEvent(event);
}; };
private _advancedListener = (event: settingsBoolEvent) => { private _advancedListener = (event: settingsChangedEvent) => {
if (!Boolean(event.detail)) { if (!toBool(event.detail)) {
this.hide(); this.hide();
} else { } else {
this.show(); this.show();
@ -109,9 +116,11 @@ class DOMInput {
get required(): boolean { return this._required.classList.contains("badge"); } get required(): boolean { return this._required.classList.contains("badge"); }
set required(state: boolean) { set required(state: boolean) {
if (state) { if (state) {
this._required.classList.remove("unfocused");
this._required.classList.add("badge", "~critical"); this._required.classList.add("badge", "~critical");
this._required.textContent = "*"; this._required.textContent = "*";
} else { } else {
this._required.classList.add("unfocused");
this._required.classList.remove("badge", "~critical"); this._required.classList.remove("badge", "~critical");
this._required.textContent = ""; this._required.textContent = "";
} }
@ -120,9 +129,11 @@ class DOMInput {
get requires_restart(): boolean { return this._restart.classList.contains("badge"); } get requires_restart(): boolean { return this._restart.classList.contains("badge"); }
set requires_restart(state: boolean) { set requires_restart(state: boolean) {
if (state) { if (state) {
this._restart.classList.remove("unfocused");
this._restart.classList.add("badge", "~info", "dark:~d_warning"); this._restart.classList.add("badge", "~info", "dark:~d_warning");
this._restart.textContent = "R"; this._restart.textContent = "R";
} else { } else {
this._restart.classList.add("unfocused");
this._restart.classList.remove("badge", "~info", "dark:~d_warning"); this._restart.classList.remove("badge", "~info", "dark:~d_warning");
this._restart.textContent = ""; this._restart.textContent = "";
} }
@ -138,35 +149,38 @@ class DOMInput {
if (this.requires_restart) { document.dispatchEvent(new CustomEvent("settings-requires-restart")); } if (this.requires_restart) { document.dispatchEvent(new CustomEvent("settings-requires-restart")); }
}; };
constructor(inputType: string, setting: Setting, section: string, name: string, customInput?: string) { constructor(input: string, setting: Setting, section: string, name: string, inputOnTop: boolean = false) {
this._section = section; this._section = section;
this._name = name; this._name = name;
this._container = document.createElement("div"); this._container = document.createElement("div");
this._container.classList.add("setting"); this._container.classList.add("setting");
this._container.setAttribute("data-name", name) this._container.setAttribute("data-name", name);
const defaultInput = `
<input type="${inputType}" class="input setting-input ~neutral @low mt-2 mb-2">
`;
this._container.innerHTML = ` this._container.innerHTML = `
<label class="label"> <label class="label flex flex-col gap-2">
<span class="setting-label"></span> <span class="setting-required"></span> <span class="setting-restart"></span> ${inputOnTop ? input : ""}
<div class="setting-tooltip tooltip right unfocused"> <div class="flex flex-row gap-2 items-baseline">
<i class="icon ri-information-line"></i> <span class="setting-label"></span>
<span class="content sm"></span> <div class="setting-tooltip tooltip right unfocused">
<i class="icon ri-information-line align-baseline"></i>
<span class="content sm"></span>
</div>
<span class="setting-required unfocused"></span>
<span class="setting-restart unfocused"></span>
</div> </div>
${customInput ? customInput : defaultInput} ${inputOnTop ? "" : input}
</label> </label>
`; `;
this._tooltip = this._container.querySelector("div.setting-tooltip") as HTMLDivElement; this._tooltip = this._container.querySelector("div.setting-tooltip") as HTMLDivElement;
this._required = this._container.querySelector("span.setting-required") as HTMLSpanElement; this._required = this._container.querySelector("span.setting-required") as HTMLSpanElement;
this._restart = this._container.querySelector("span.setting-restart") as HTMLSpanElement; this._restart = this._container.querySelector("span.setting-restart") as HTMLSpanElement;
// "input" variable should supply the HTML of an element with class "setting-input"
this._input = this._container.querySelector(".setting-input") as HTMLInputElement; this._input = this._container.querySelector(".setting-input") as HTMLInputElement;
if (setting.depends_false || setting.depends_true) { if (setting.depends_false || setting.depends_true) {
let dependant = splitDependant(section, setting.depends_true || setting.depends_false); let dependant = splitDependant(section, setting.depends_true || setting.depends_false);
let state = true; let state = true;
if (setting.depends_false) { state = false; } if (setting.depends_false) { state = false; }
document.addEventListener(`settings-${dependant[0]}-${dependant[1]}`, (event: settingsBoolEvent) => { document.addEventListener(`settings-${dependant[0]}-${dependant[1]}`, (event: settingsChangedEvent) => {
if (Boolean(event.detail) !== state) { if (toBool(event.detail) !== state) {
this.hide(); this.hide();
} else { } else {
this.show(); this.show();
@ -174,13 +188,14 @@ class DOMInput {
}); });
} }
this._input.onchange = this.onValueChange; this._input.onchange = this.onValueChange;
this.update(setting); document.addEventListener(`settings-loaded`, this.onValueChange);
this._hideEl = this._container;
} }
get value(): any { return this._input.value; } get value(): any { return this._input.value; }
set value(v: any) { this._input.value = v; } set value(v: any) { this._input.value = v; }
update = (s: Setting) => { update(s: Setting) {
this.name = s.name; this.name = s.name;
this.description = s.description; this.description = s.description;
this.required = s.required; this.required = s.required;
@ -192,6 +207,17 @@ class DOMInput {
asElement = (): HTMLDivElement => { return this._container; } asElement = (): HTMLDivElement => { return this._container; }
} }
class DOMInput extends DOMSetting {
constructor(inputType: string, setting: Setting, section: string, name: string) {
super(
`<input type="${inputType}" class="input setting-input ~neutral @low">`,
setting, section, name,
);
// this._hideEl = this._input.parentElement;
this.update(setting);
}
}
interface SText extends Setting { interface SText extends Setting {
value: string; value: string;
} }
@ -202,11 +228,40 @@ class DOMText extends DOMInput implements SText {
set value(v: string) { this._input.value = v; } set value(v: string) { this._input.value = v; }
} }
interface SPassword extends Setting {
value: string;
}
class DOMPassword extends DOMInput implements SPassword {
constructor(setting: Setting, section: string, name: string) { super("password", setting, section, name); }
type: string = "password";
get value(): string { return this._input.value }
set value(v: string) { this._input.value = v; }
}
interface SEmail extends Setting {
value: string;
}
class DOMEmail extends DOMInput implements SEmail {
constructor(setting: Setting, section: string, name: string) { super("email", setting, section, name); }
type: string = "email";
get value(): string { return this._input.value }
set value(v: string) { this._input.value = v; }
}
interface SNumber extends Setting {
value: number;
}
class DOMNumber extends DOMInput implements SNumber {
constructor(setting: Setting, section: string, name: string) { super("number", setting, section, name); }
type: string = "number";
get value(): number { return +this._input.value; }
set value(v: number) { this._input.value = ""+v; }
}
interface SList extends Setting { interface SList extends Setting {
value: string[]; value: string[];
} }
class DOMList extends DOMSetting implements SList {
class DOMList extends DOMInput implements SList {
protected _inputs: HTMLDivElement; protected _inputs: HTMLDivElement;
type: string = "list"; type: string = "list";
@ -234,9 +289,11 @@ class DOMList extends DOMInput implements SList {
if (!(input.value)) return; if (!(input.value)) return;
addDummy(); addDummy();
input.removeEventListener("change", onDummyChange); input.removeEventListener("change", onDummyChange);
input.removeEventListener("keyup", onDummyChange);
input.placeholder = ``; input.placeholder = ``;
} }
input.addEventListener("change", onDummyChange); input.addEventListener("change", onDummyChange);
input.addEventListener("keyup", onDummyChange);
this._input.appendChild(dummyRow); this._input.appendChild(dummyRow);
}; };
addDummy(); addDummy();
@ -262,284 +319,46 @@ class DOMList extends DOMInput implements SList {
} }
return container; return container;
} }
update = (s: Setting) => {
this.name = s.name;
this.description = s.description;
this.required = s.required;
this.requires_restart = s.requires_restart;
this.value = s.value as string[];
this.advanced = s.advanced;
}
asElement = (): HTMLDivElement => { return this._container; }
constructor(setting: Setting, section: string, name: string) { constructor(setting: Setting, section: string, name: string) {
super("list", setting, section, name, super(
`<div class="setting-input flex flex-col gap-2 mt-2 mb-2"></div>` `<div class="setting-input flex flex-col gap-2"></div>`,
setting, section, name,
); );
// this._hideEl = this._input.parentElement;
this.update(setting);
} }
} }
interface SPassword extends Setting {
value: string;
}
class DOMPassword extends DOMInput implements SPassword {
constructor(setting: Setting, section: string, name: string) { super("password", setting, section, name); }
type: string = "password";
get value(): string { return this._input.value }
set value(v: string) { this._input.value = v; }
}
interface SEmail extends Setting {
value: string;
}
class DOMEmail extends DOMInput implements SEmail {
constructor(setting: Setting, section: string, name: string) { super("email", setting, section, name); }
type: string = "email";
get value(): string { return this._input.value }
set value(v: string) { this._input.value = v; }
}
interface SNumber extends Setting {
value: number;
}
class DOMNumber extends DOMInput implements SNumber {
constructor(setting: Setting, section: string, name: string) { super("number", setting, section, name); }
type: string = "number";
get value(): number { return +this._input.value; }
set value(v: number) { this._input.value = ""+v; }
}
interface SBool extends Setting { interface SBool extends Setting {
value: boolean; value: boolean;
} }
class DOMBool implements SBool { class DOMBool extends DOMSetting implements SBool {
protected _input: HTMLInputElement;
private _container: HTMLDivElement;
private _tooltip: HTMLDivElement;
private _required: HTMLSpanElement;
private _restart: HTMLSpanElement;
type: string = "bool"; type: string = "bool";
private _advanced: boolean;
protected _section: string;
protected _name: string;
hide = () => {
this._input.parentElement.classList.add("unfocused");
const event = new CustomEvent(`settings-${this._section}-${this._name}`, { "detail": false })
document.dispatchEvent(event);
};
show = () => {
this._input.parentElement.classList.remove("unfocused");
const event = new CustomEvent(`settings-${this._section}-${this._name}`, { "detail": this.valueAsString() })
document.dispatchEvent(event);
};
private _advancedListener = (event: settingsBoolEvent) => {
if (!Boolean(event.detail)) {
this.hide();
} else {
this.show();
}
}
get advanced(): boolean { return this._advanced; }
set advanced(advanced: boolean) {
this._advanced = advanced;
if (advanced) {
document.addEventListener("settings-advancedState", this._advancedListener);
} else {
document.removeEventListener("settings-advancedState", this._advancedListener);
}
}
get name(): string { return this._container.querySelector("span.setting-label").textContent; }
set name(n: string) { this._container.querySelector("span.setting-label").textContent = n; }
get description(): string { return this._tooltip.querySelector("span.content").textContent; }
set description(d: string) {
const content = this._tooltip.querySelector("span.content") as HTMLSpanElement;
content.textContent = d;
if (d == "") {
this._tooltip.classList.add("unfocused");
} else {
this._tooltip.classList.remove("unfocused");
}
}
get required(): boolean { return this._required.classList.contains("badge"); }
set required(state: boolean) {
if (state) {
this._required.classList.add("badge", "~critical");
this._required.textContent = "*";
} else {
this._required.classList.remove("badge", "~critical");
this._required.textContent = "";
}
}
get requires_restart(): boolean { return this._restart.classList.contains("badge"); }
set requires_restart(state: boolean) {
if (state) {
this._restart.classList.add("badge", "~info", "dark:~d_warning");
this._restart.textContent = "R";
} else {
this._restart.classList.remove("badge", "~info", "dark:~d_warning");
this._restart.textContent = "";
}
}
valueAsString = (): string => { return ""+this.value; };
get value(): boolean { return this._input.checked; } get value(): boolean { return this._input.checked; }
set value(state: boolean) { this._input.checked = state; } set value(state: boolean) { this._input.checked = state; }
constructor(setting: SBool, section: string, name: string) { constructor(setting: SBool, section: string, name: string) {
this._section = section; super(
this._name = name; `<input type="checkbox" class="setting-input">`,
this._container = document.createElement("div"); setting, section, name, true,
this._container.classList.add("setting"); );
this._container.setAttribute("data-name", name) const label = this._container.getElementsByTagName("LABEL")[0];
this._container.innerHTML = ` label.classList.remove("flex-col");
<label class="switch mb-2"> label.classList.add("flex-row");
<input type="checkbox"> // this._hideEl = this._input.parentElement;
<span class="setting-label"></span> <span class="setting-required"></span> <span class="setting-restart"></span>
<div class="setting-tooltip tooltip right unfocused">
<i class="icon ri-information-line"></i>
<span class="content sm"></span>
</div>
</label>
`;
this._tooltip = this._container.querySelector("div.setting-tooltip") as HTMLDivElement;
this._required = this._container.querySelector("span.setting-required") as HTMLSpanElement;
this._restart = this._container.querySelector("span.setting-restart") as HTMLSpanElement;
this._input = this._container.querySelector("input[type=checkbox]") as HTMLInputElement;
const onValueChange = () => {
const event = new CustomEvent(`settings-${section}-${name}`, { "detail": this.valueAsString() })
const setEvent = new CustomEvent(`settings-set-${section}-${name}`, { "detail": this.valueAsString() })
document.dispatchEvent(event);
document.dispatchEvent(setEvent);
};
this._input.onchange = () => {
onValueChange();
if (this.requires_restart) { document.dispatchEvent(new CustomEvent("settings-requires-restart")); }
};
document.addEventListener(`settings-loaded`, onValueChange);
if (setting.depends_false || setting.depends_true) {
let dependant = splitDependant(section, setting.depends_true || setting.depends_false);
let state = true;
if (setting.depends_false) { state = false; }
console.log(`I, ${section}-${name}, am adding a listener for ${dependant[0]}-${dependant[1]}`);
document.addEventListener(`settings-${dependant[0]}-${dependant[1]}`, (event: settingsBoolEvent) => {
console.log(`I, ${section}-${name}, was triggered by a listener for ${dependant[0]}-${dependant[1]}`);
if (Boolean(event.detail) !== state) {
this.hide();
} else {
this.show();
}
});
}
this.update(setting); this.update(setting);
} }
update = (s: SBool) => {
this.name = s.name;
this.description = s.description;
this.required = s.required;
this.requires_restart = s.requires_restart;
this.value = s.value;
this.advanced = s.advanced;
}
asElement = (): HTMLDivElement => { return this._container; }
} }
interface SSelect extends Setting { interface SSelect extends Setting {
options: string[][]; options: string[][];
value: string; value: string;
} }
class DOMSelect implements SSelect { class DOMSelect extends DOMSetting implements SSelect {
protected _select: HTMLSelectElement;
private _container: HTMLDivElement;
private _tooltip: HTMLDivElement;
private _required: HTMLSpanElement;
private _restart: HTMLSpanElement;
private _options: string[][];
type: string = "bool"; type: string = "bool";
private _advanced: boolean; private _options: string[][];
protected _section: string;
protected _name: string;
hide = () => {
this._container.classList.add("unfocused");
const event = new CustomEvent(`settings-${this._section}-${this._name}`, { "detail": false })
document.dispatchEvent(event);
};
show = () => {
this._container.classList.remove("unfocused");
const event = new CustomEvent(`settings-${this._section}-${this._name}`, { "detail": this.valueAsString() })
document.dispatchEvent(event);
};
private _advancedListener = (event: settingsBoolEvent) => {
if (!Boolean(event.detail)) {
this.hide();
} else {
this.show();
}
}
get advanced(): boolean { return this._advanced; }
set advanced(advanced: boolean) {
this._advanced = advanced;
if (advanced) {
document.addEventListener("settings-advancedState", this._advancedListener);
} else {
document.removeEventListener("settings-advancedState", this._advancedListener);
}
}
get name(): string { return this._container.querySelector("span.setting-label").textContent; }
set name(n: string) { this._container.querySelector("span.setting-label").textContent = n; }
get description(): string { return this._tooltip.querySelector("span.content").textContent; }
set description(d: string) {
const content = this._tooltip.querySelector("span.content") as HTMLSpanElement;
content.textContent = d;
if (d == "") {
this._tooltip.classList.add("unfocused");
} else {
this._tooltip.classList.remove("unfocused");
}
}
get required(): boolean { return this._required.classList.contains("badge"); }
set required(state: boolean) {
if (state) {
this._required.classList.add("badge", "~critical");
this._required.textContent = "*";
} else {
this._required.classList.remove("badge", "~critical");
this._required.textContent = "";
}
}
get requires_restart(): boolean { return this._restart.classList.contains("badge"); }
set requires_restart(state: boolean) {
if (state) {
this._restart.classList.add("badge", "~info", "dark:~d_warning");
this._restart.textContent = "R";
} else {
this._restart.classList.remove("badge", "~info", "dark:~d_warning");
this._restart.textContent = "";
}
}
valueAsString = (): string => { return ""+this.value; };
get value(): string { return this._select.value; }
set value(v: string) { this._select.value = v; }
get options(): string[][] { return this._options; } get options(): string[][] { return this._options; }
set options(opt: string[][]) { set options(opt: string[][]) {
@ -548,98 +367,47 @@ class DOMSelect implements SSelect {
for (let option of this._options) { for (let option of this._options) {
innerHTML += `<option value="${option[0]}">${option[1]}</option>`; innerHTML += `<option value="${option[0]}">${option[1]}</option>`;
} }
this._select.innerHTML = innerHTML; this._input.innerHTML = innerHTML;
} }
constructor(setting: SSelect, section: string, name: string) { update(s: SSelect) {
this._section = section; this.options = s.options;
this._name = name; super.update(s);
this._options = []; };
this._container = document.createElement("div");
this._container.classList.add("setting");
this._container.setAttribute("data-name", name)
this._container.innerHTML = `
<label class="label">
<span class="setting-label"></span> <span class="setting-required"></span> <span class="setting-restart"></span>
<div class="setting-tooltip tooltip right unfocused">
<i class="icon ri-information-line"></i>
<span class="content sm"></span>
</div>
<div class="select ~neutral @low mt-2 mb-2">
<select class="settings-select"></select>
</div>
</label>
`;
this._tooltip = this._container.querySelector("div.setting-tooltip") as HTMLDivElement;
this._required = this._container.querySelector("span.setting-required") as HTMLSpanElement;
this._restart = this._container.querySelector("span.setting-restart") as HTMLSpanElement;
this._select = this._container.querySelector("select.settings-select") as HTMLSelectElement;
if (setting.depends_false || setting.depends_true) {
let dependant = splitDependant(section, setting.depends_true || setting.depends_false);
let state = true;
if (setting.depends_false) { state = false; }
document.addEventListener(`settings-${dependant[0]}-${dependant[1]}`, (event: settingsBoolEvent) => {
if (Boolean(event.detail) !== state) {
this.hide();
} else {
this.show();
}
});
}
const onValueChange = () => {
const event = new CustomEvent(`settings-${section}-${name}`, { "detail": this.valueAsString() });
const setEvent = new CustomEvent(`settings-${section}-${name}`, { "detail": this.valueAsString() });
document.dispatchEvent(event);
document.dispatchEvent(setEvent);
if (this.requires_restart) { document.dispatchEvent(new CustomEvent("settings-requires-restart")); }
};
this._select.onchange = onValueChange;
document.addEventListener(`settings-loaded`, onValueChange);
const message = document.getElementById("settings-message") as HTMLElement; constructor(setting: SSelect, section: string, name: string) {
message.innerHTML = window.lang.var("strings", super(
"settingsRequiredOrRestartMessage", `<div class="select ~neutral @low">
`<span class="badge ~critical">*</span>`, <select class="setting-select setting-input"></select>
`<span class="badge ~info dark:~d_warning">R</span>` </div>`,
setting, section, name,
); );
this._options = [];
// this._hideEl = this._container;
this.update(setting); this.update(setting);
} }
update = (s: SSelect) => {
this.name = s.name;
this.description = s.description;
this.required = s.required;
this.requires_restart = s.requires_restart;
this.options = s.options;
this.value = s.value;
}
asElement = (): HTMLDivElement => { return this._container; }
} }
interface SNote extends Setting { interface SNote extends Setting {
value: string; value: string;
style?: string; style?: string;
} }
class DOMNote implements SNote { class DOMNote extends DOMSetting implements SNote {
private _container: HTMLDivElement; private _nameEl: HTMLElement;
private _aside: HTMLElement;
private _name: HTMLElement;
private _description: HTMLElement; private _description: HTMLElement;
type: string = "note"; type: string = "note";
private _style: string; private _style: string;
// We're a note, no one depends on us so we don't need to broadcast a state change.
hide = () => { hide = () => {
this._container.classList.add("unfocused"); this._container.classList.add("unfocused");
// We're a note, no one depends on us so we don't need to broadcast a state change.
}; };
show = () => { show = () => {
this._container.classList.remove("unfocused"); this._container.classList.remove("unfocused");
// We're a note, no one depends on us so we don't need to broadcast a state change.
}; };
get name(): string { return this._name.textContent; } get name(): string { return this._nameEl.textContent; }
set name(n: string) { this._name.textContent = n; } set name(n: string) { this._nameEl.textContent = n; }
get description(): string { return this._description.textContent; } get description(): string { return this._description.textContent; }
set description(d: string) { set description(d: string) {
@ -649,51 +417,37 @@ class DOMNote implements SNote {
valueAsString = (): string => { return ""; }; valueAsString = (): string => { return ""; };
get value(): string { return ""; } get value(): string { return ""; }
set value(v: string) { return; } set value(_: string) { return; }
get required(): boolean { return false; } get required(): boolean { return false; }
set required(v: boolean) { return; } set required(_: boolean) { return; }
get requires_restart(): boolean { return false; } get requires_restart(): boolean { return false; }
set requires_restart(v: boolean) { return; } set requires_restart(_: boolean) { return; }
get style(): string { return this._style; } get style(): string { return this._style; }
set style(s: string) { set style(s: string) {
this._aside.classList.remove("~" + this._style); this._input.classList.remove("~" + this._style);
this._style = s; this._style = s;
this._aside.classList.add("~" + this._style); this._input.classList.add("~" + this._style);
} }
constructor(setting: SNote, section: string) { constructor(setting: SNote, section: string) {
this._container = document.createElement("div"); super(
this._container.classList.add("setting"); `
this._container.innerHTML = ` <aside class="aside setting-input">
<aside class="aside my-2"> <span class="font-bold setting-name"></span>
<span class="font-bold setting-name"></span> <span class="content setting-description">
<span class="content setting-description"> </aside>
</aside> `, setting, section, "",
`; );
this._name = this._container.querySelector(".setting-name"); // this._hideEl = this._container;
this._nameEl = this._container.querySelector(".setting-name");
this._description = this._container.querySelector(".setting-description"); this._description = this._container.querySelector(".setting-description");
this._aside = this._container.querySelector("aside");
if (setting.depends_false || setting.depends_true) {
let dependant = splitDependant(section, setting.depends_true || setting.depends_false);
let state = true;
if (setting.depends_false) { state = false; }
document.addEventListener(`settings-${dependant[0]}-${dependant[1]}`, (event: settingsBoolEvent) => {
if (Boolean(event.detail) !== state) {
this.hide();
} else {
this.show();
}
});
}
this.update(setting); this.update(setting);
} }
update = (s: SNote) => { update(s: SNote) {
this.name = s.name; this.name = s.name;
this.description = s.description; this.description = s.description;
this.style = ("style" in s && s.style) ? s.style : "info"; this.style = ("style" in s && s.style) ? s.style : "info";
@ -718,7 +472,7 @@ class sectionPanel {
this._sectionName = sectionName; this._sectionName = sectionName;
this._settings = {}; this._settings = {};
this._section = document.createElement("div") as HTMLDivElement; this._section = document.createElement("div") as HTMLDivElement;
this._section.classList.add("settings-section", "unfocused"); this._section.classList.add("settings-section", "unfocused", "flex", "flex-col", "gap-2");
this._section.setAttribute("data-section", sectionName); this._section.setAttribute("data-section", sectionName);
let innerHTML = ` let innerHTML = `
<div class="flex flex-row justify-between"> <div class="flex flex-row justify-between">
@ -730,7 +484,7 @@ class sectionPanel {
innerHTML += ` innerHTML += `
</div> </div>
<p class="support lg my-2 settings-section-description">${s.meta.description}</p> <p class="support lg settings-section-description">${s.meta.description}</p>
`; `;
this._section.innerHTML = innerHTML; this._section.innerHTML = innerHTML;
@ -840,9 +594,8 @@ export class settingsList {
let dependant = splitDependant(name, s.meta.depends_true || s.meta.depends_false); let dependant = splitDependant(name, s.meta.depends_true || s.meta.depends_false);
let state = true; let state = true;
if (s.meta.depends_false) { state = false; } if (s.meta.depends_false) { state = false; }
document.addEventListener(`settings-${dependant[0]}-${dependant[1]}`, (event: settingsBoolEvent) => { document.addEventListener(`settings-${dependant[0]}-${dependant[1]}`, (event: settingsChangedEvent) => {
if (toBool(event.detail) !== state) {
if (Boolean(event.detail) !== state) {
button.classList.add("unfocused"); button.classList.add("unfocused");
document.dispatchEvent(new CustomEvent(`settings-${name}`, { detail: false })); document.dispatchEvent(new CustomEvent(`settings-${name}`, { detail: false }));
} else { } else {
@ -850,16 +603,16 @@ export class settingsList {
document.dispatchEvent(new CustomEvent(`settings-${name}`, { detail: true })); document.dispatchEvent(new CustomEvent(`settings-${name}`, { detail: true }));
} }
}); });
document.addEventListener(`settings-${dependant[0]}`, (event: settingsBoolEvent) => { document.addEventListener(`settings-${dependant[0]}`, (event: settingsChangedEvent) => {
if (Boolean(event.detail) !== state) { if (toBool(event.detail) !== state) {
button.classList.add("unfocused"); button.classList.add("unfocused");
document.dispatchEvent(new CustomEvent(`settings-${name}`, { detail: false })); document.dispatchEvent(new CustomEvent(`settings-${name}`, { detail: false }));
} }
}); });
} }
if (s.meta.advanced) { if (s.meta.advanced) {
document.addEventListener("settings-advancedState", (event: settingsBoolEvent) => { document.addEventListener("settings-advancedState", (event: settingsChangedEvent) => {
if (!Boolean(event.detail)) { if (!toBool(event.detail)) {
button.classList.add("unfocused"); button.classList.add("unfocused");
} else { } else {
button.classList.remove("unfocused"); button.classList.remove("unfocused");
@ -1051,6 +804,14 @@ export class settingsList {
this._searchbox.oninput(null); this._searchbox.oninput(null);
}; };
}; };
// What possessed me to put this in the DOMSelect constructor originally? like what????????
const message = document.getElementById("settings-message") as HTMLElement;
message.innerHTML = window.lang.var("strings",
"settingsRequiredOrRestartMessage",
`<span class="badge ~critical">*</span>`,
`<span class="badge ~info dark:~d_warning">R</span>`
);
} }
private _addMatrix = () => { private _addMatrix = () => {
@ -1327,7 +1088,7 @@ class MessageEditor {
let innerHTML = ''; let innerHTML = '';
for (let i = 0; i < this._templ.variables.length; i++) { for (let i = 0; i < this._templ.variables.length; i++) {
let ci = i % colors.length; let ci = i % colors.length;
innerHTML += '<span class="button ~' + colors[ci] +' @low mb-4" style="margin-left: 0.25rem; margin-right: 0.25rem;"></span>' innerHTML += '<span class="button ~' + colors[ci] +' @low"></span>'
} }
if (this._templ.variables.length == 0) { if (this._templ.variables.length == 0) {
this._variablesLabel.classList.add("unfocused"); this._variablesLabel.classList.add("unfocused");

Loading…
Cancel
Save