From 2be0658ed9bae3c96322c081cd06a00657f75d8c Mon Sep 17 00:00:00 2001 From: CenTdemeern1 Date: Mon, 3 Feb 2025 03:15:14 +0100 Subject: [PATCH] Rewrite AddInstanceDialog Object oriented it is --- static/add_an_instance.mts | 92 +++++++++++++++++++++++------------- static/add_instance_flow.mts | 2 +- static/dialog.mts | 41 ++++++++++++++++ tsconfig.json | 1 + 4 files changed, 101 insertions(+), 35 deletions(-) create mode 100644 static/dialog.mts diff --git a/static/add_an_instance.mts b/static/add_an_instance.mts index c8130fc..4086f8e 100644 --- a/static/add_an_instance.mts +++ b/static/add_an_instance.mts @@ -1,5 +1,6 @@ // This file handles the "Add an instance" dialog +import { FormDialog, ONCE } from "./dialog.mjs"; import { findButtonOrFail, findFormOrFail, findInputOrFail } from "./dom.mjs"; export function parseHost(host: string): { host: string, secure: boolean } | null { @@ -13,43 +14,66 @@ export function parseHost(host: string): { host: string, secure: boolean } | nul }; } -export function initializeAddInstanceDialog( - dialog: HTMLDialogElement, - callback: ( - host: string, - secure: boolean, - autoQueryMetadata: boolean, - ) => void -): { - showAddInstanceDialog: () => void, - hideAddInstanceDialog: () => void, -} { - const showAddInstanceDialog = () => dialog.showModal(); - const hideAddInstanceDialog = () => dialog.close(); +type AddInstanceDialogData = { + host: string, + secure: boolean, + autoQueryMetadata: boolean, +}; - const form = findFormOrFail(dialog, ".addInstanceForm"); - const instanceHost = findInputOrFail(form, "#instanceHost"); - const autoQueryMetadata = findInputOrFail(form, "#autoQueryMetadata"); - const closeButton = findButtonOrFail(form, ".close"); +export class AddInstanceDialog extends FormDialog { + protected instanceHost: HTMLInputElement; + protected autoQueryMetadata: HTMLInputElement; + protected closeButton: HTMLButtonElement; - instanceHost.addEventListener("input", e => { - if (parseHost(instanceHost.value) === null) - instanceHost.setCustomValidity("Invalid instance hostname or URL"); - else - instanceHost.setCustomValidity(""); - }); + constructor(dialog: HTMLDialogElement, initializeDOM: boolean = true) { + super(dialog, findFormOrFail(dialog, ".addInstanceForm")); + this.instanceHost = findInputOrFail(this.form, "#instanceHost"); + this.autoQueryMetadata = findInputOrFail(this.form, "#autoQueryMetadata"); + this.closeButton = findButtonOrFail(this.form, ".close"); - form.addEventListener("submit", e => { - // A sane browser doesn't allow for submitting the form if the above validation fails - const { host, secure } = parseHost(instanceHost.value)!; - callback(host, secure, autoQueryMetadata.checked); - form.reset(); - }); + if (initializeDOM) this.initializeDOM(); + } - closeButton.addEventListener("click", e => hideAddInstanceDialog()); + #getDataIfValid(): AddInstanceDialogData | null { + const parsedHost = parseHost(this.instanceHost.value); + if (parsedHost === null) { + this.instanceHost.setCustomValidity("Invalid instance hostname or URL"); + return null; + } + this.instanceHost.setCustomValidity(""); + return { + host: parsedHost.host, + secure: parsedHost.secure, + autoQueryMetadata: this.autoQueryMetadata.checked + }; + } - return { - showAddInstanceDialog, - hideAddInstanceDialog - }; + #handleSubmit(resolve: (data: AddInstanceDialogData) => void) { + this.form.addEventListener("submit", e => { + const data = this.#getDataIfValid(); + if (data === null) { + // Prevent the user from submitting the form if it's invalid and let them try again + e.preventDefault(); + this.#handleSubmit(resolve); + return; + } + resolve(data); + this.close(); + }, ONCE); + } + + protected override initializeDOM() { + super.initializeDOM(); + + this.instanceHost.addEventListener("input", e => this.#getDataIfValid()); + this.closeButton.addEventListener("click", e => this.close()); + } + + async prompt(): Promise { + return new Promise((resolve, reject) => { + this.dialog.addEventListener("close", e => reject(), ONCE); + this.#handleSubmit(resolve); + this.open(); + }); + } } diff --git a/static/add_instance_flow.mts b/static/add_instance_flow.mts index 2703b55..ffd78ae 100644 --- a/static/add_instance_flow.mts +++ b/static/add_instance_flow.mts @@ -1,4 +1,4 @@ -import { initializeAddInstanceDialog } from "./add_an_instance.mjs"; +import { AddInstanceDialog } from "./add_an_instance.mjs"; import { initializeInstanceDetailsDialog } from "./confirm_instance_details.mjs"; import storageManager, { Instance } from "./storage_manager.mjs"; diff --git a/static/dialog.mts b/static/dialog.mts new file mode 100644 index 0000000..37119ad --- /dev/null +++ b/static/dialog.mts @@ -0,0 +1,41 @@ +export const ONCE = { once: true }; + +export class Dialog { + protected dialog: HTMLDialogElement; + + constructor(dialog: HTMLDialogElement) { + this.dialog = dialog; + } + + /** + * A function that should only be called once that has permanent effects on the DOM + */ + protected initializeDOM() { } + + protected open() { + this.dialog.showModal(); + } + + close() { + this.dialog.close(); + } +}; + +export class FormDialog extends Dialog { + protected form: HTMLFormElement; + + constructor(dialog: HTMLDialogElement, form: HTMLFormElement) { + super(dialog); + this.form = form; + } + + protected override initializeDOM() { + super.initializeDOM(); + + this.dialog.addEventListener("close", e => this.reset()); + } + + reset() { + this.form.reset(); + } +} diff --git a/tsconfig.json b/tsconfig.json index 6d70ce7..a1a2e6a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,7 @@ "allowJs": true, "checkJs": true, "strictNullChecks": true, + "noImplicitOverride": true, }, "include": [ "static/**.mts",