Rewrite InstanceDetailsDialog
Also misc cleanup. Almost done with this
This commit is contained in:
parent
bfd61c2e50
commit
f451b1fbc3
7 changed files with 140 additions and 145 deletions
|
@ -14,7 +14,7 @@ export function parseHost(host: string): { host: string, secure: boolean } | nul
|
|||
};
|
||||
}
|
||||
|
||||
type AddInstanceDialogData = {
|
||||
export type AddInstanceDialogData = {
|
||||
host: string,
|
||||
secure: boolean,
|
||||
autoQueryMetadata: boolean,
|
||||
|
@ -27,6 +27,7 @@ export class AddInstanceDialog extends FormDialog {
|
|||
|
||||
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");
|
||||
|
@ -34,6 +35,13 @@ export class AddInstanceDialog extends FormDialog {
|
|||
if (initializeDOM) this.initializeDOM();
|
||||
}
|
||||
|
||||
protected override initializeDOM() {
|
||||
super.initializeDOM();
|
||||
|
||||
this.instanceHost.addEventListener("input", e => this.#getDataIfValid());
|
||||
this.closeButton.addEventListener("click", e => this.close());
|
||||
}
|
||||
|
||||
#getDataIfValid(): AddInstanceDialogData | null {
|
||||
const parsedHost = parseHost(this.instanceHost.value);
|
||||
if (parsedHost === null) {
|
||||
|
@ -62,16 +70,9 @@ export class AddInstanceDialog extends FormDialog {
|
|||
}, ONCE);
|
||||
}
|
||||
|
||||
protected override initializeDOM() {
|
||||
super.initializeDOM();
|
||||
|
||||
this.instanceHost.addEventListener("input", e => this.#getDataIfValid());
|
||||
this.closeButton.addEventListener("click", e => this.close());
|
||||
}
|
||||
|
||||
async prompt(): Promise<AddInstanceDialogData> {
|
||||
async present(): Promise<AddInstanceDialogData> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.dialog.addEventListener("close", e => reject(), ONCE);
|
||||
this.cancelOnceClosed(reject);
|
||||
this.#handleSubmit(resolve);
|
||||
this.open();
|
||||
});
|
||||
|
|
|
@ -1,56 +1,45 @@
|
|||
import { AddInstanceDialog } from "./add_an_instance.mjs";
|
||||
import { initializeInstanceDetailsDialog } from "./confirm_instance_details.mjs";
|
||||
import { InstanceDetailsDialog, InstanceDetailsDialogData } from "./confirm_instance_details.mjs";
|
||||
import { Dialog } from "./dialog.mjs";
|
||||
import storageManager, { Instance } from "./storage_manager.mjs";
|
||||
|
||||
export class AddInstanceFlow {
|
||||
addDialog: AddInstanceDialog;
|
||||
spinnerDialog: Dialog;
|
||||
detailsDialog: HTMLDialogElement;
|
||||
detailsDialog: InstanceDetailsDialog;
|
||||
|
||||
constructor(
|
||||
addDialog: AddInstanceDialog | HTMLDialogElement,
|
||||
spinnerDialog: HTMLDialogElement,
|
||||
detailsDialog: HTMLDialogElement,
|
||||
detailsDialog: InstanceDetailsDialog | HTMLDialogElement,
|
||||
) {
|
||||
if (addDialog instanceof AddInstanceDialog)
|
||||
this.addDialog = addDialog;
|
||||
else
|
||||
this.addDialog = new AddInstanceDialog(addDialog, true);
|
||||
|
||||
this.spinnerDialog = new Dialog(spinnerDialog);
|
||||
|
||||
if (detailsDialog instanceof InstanceDetailsDialog)
|
||||
this.detailsDialog = detailsDialog;
|
||||
else
|
||||
this.detailsDialog = new InstanceDetailsDialog(detailsDialog, true);
|
||||
}
|
||||
|
||||
async start() {
|
||||
const instanceDetailsDialogCallback = (
|
||||
name: string,
|
||||
host: string,
|
||||
hostSecure: boolean,
|
||||
software: string,
|
||||
icon: string | null
|
||||
) => {
|
||||
const instance: Instance = {
|
||||
name,
|
||||
origin: `http${hostSecure ? "s" : ""}://${host}`,
|
||||
software,
|
||||
iconURL: icon ?? undefined
|
||||
};
|
||||
storageManager.storage.instances.push(instance);
|
||||
storageManager.save();
|
||||
console.log("Successfully added new instance:", instance);
|
||||
};
|
||||
|
||||
const {
|
||||
showInstanceDetailsDialog,
|
||||
hideInstanceDetailsDialog,
|
||||
populateInstanceDetailsDialog
|
||||
} = initializeInstanceDetailsDialog(this.detailsDialog, instanceDetailsDialogCallback);
|
||||
|
||||
const {
|
||||
autoQueryMetadata,
|
||||
host,
|
||||
secure,
|
||||
} = await this.addDialog.prompt();
|
||||
} = await this.addDialog.present();
|
||||
|
||||
let detailsDialogData: InstanceDetailsDialogData = {
|
||||
name: host,
|
||||
host,
|
||||
hostSecure: secure,
|
||||
software: "",
|
||||
iconURL: null,
|
||||
};
|
||||
|
||||
try {
|
||||
if (!autoQueryMetadata) throw null; // Skip to catch block
|
||||
|
@ -67,12 +56,23 @@ export class AddInstanceFlow {
|
|||
)
|
||||
throw new Error("Invalid API response");
|
||||
|
||||
populateInstanceDetailsDialog(name, host, secure, software, iconURL as string | null);
|
||||
} catch {
|
||||
populateInstanceDetailsDialog(host, host, secure, "", null);
|
||||
} finally {
|
||||
detailsDialogData.name = name;
|
||||
detailsDialogData.software = software;
|
||||
detailsDialogData.iconURL = iconURL as string | null;
|
||||
} catch { }
|
||||
this.spinnerDialog.close();
|
||||
showInstanceDetailsDialog();
|
||||
}
|
||||
|
||||
detailsDialogData = await this.detailsDialog.present(detailsDialogData);
|
||||
|
||||
const instance: Instance = {
|
||||
name: detailsDialogData.name,
|
||||
origin: `http${detailsDialogData.hostSecure ? "s" : ""}://${detailsDialogData.host}`,
|
||||
software: detailsDialogData.software,
|
||||
iconURL: detailsDialogData.iconURL ?? undefined
|
||||
};
|
||||
|
||||
storageManager.storage.instances.push(instance);
|
||||
storageManager.save();
|
||||
console.log("Successfully added new instance:", instance);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
<center class="half-width">
|
||||
<ol id="instanceList" class="align-start wfit-content"></ol>
|
||||
<br>
|
||||
<button id="showAddInstanceDialog">Add an instance</button>
|
||||
<button id="startAddInstanceFlow">Add an instance</button>
|
||||
</center>
|
||||
</div>
|
||||
<div class="half-width align-self-start">
|
||||
|
@ -110,6 +110,7 @@ Unchecking this is not recommended, and this option only exists for exceptional
|
|||
<button type="reset" class="close">Cancel</button>
|
||||
</form>
|
||||
</dialog>
|
||||
<dialog id="spinner"><span class="spinner"></span></dialog>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,6 +1,6 @@
|
|||
import { parseHost } from "./add_an_instance.mjs";
|
||||
import { initializeAddInstanceFlow } from "./add_instance_flow.mjs";
|
||||
import { initializeInstanceDetailsDialog } from "./confirm_instance_details.mjs";
|
||||
import { AddInstanceFlow } from "./add_instance_flow.mjs";
|
||||
import { InstanceDetailsDialog } from "./confirm_instance_details.mjs";
|
||||
import { findButtonOrFail, findDialogOrFail, findOlOrFail } from "./dom.mjs";
|
||||
import storageManager from "./storage_manager.mjs";
|
||||
|
||||
|
@ -9,14 +9,18 @@ let reordering = false;
|
|||
let elementBeingDragged: HTMLLIElement | undefined;
|
||||
|
||||
const mainDialog = findDialogOrFail(document.body, "#mainDialog");
|
||||
const showAddInstanceDialogButton = findButtonOrFail(document.body, "#showAddInstanceDialog");
|
||||
const detailsDialog = findDialogOrFail(document.body, "#instanceDetails");
|
||||
const startAddInstanceFlowButton = findButtonOrFail(document.body, "#startAddInstanceFlow");
|
||||
const addDialog = findDialogOrFail(document.body, "#addInstance");
|
||||
const spinnerDialog = findDialogOrFail(document.body, "#spinner");
|
||||
const detailsDialog = findDialogOrFail(document.body, "#instanceDetails");
|
||||
const instanceList = findOlOrFail(document.body, "#instanceList");
|
||||
const saveButton = findButtonOrFail(document.body, "#save");
|
||||
const reorderButton = findButtonOrFail(document.body, "#reorder");
|
||||
|
||||
showAddInstanceDialogButton.addEventListener("click", e => showAddInstanceDialog());
|
||||
let instanceDetailsDialog = new InstanceDetailsDialog(detailsDialog, true);
|
||||
let addInstanceFlow = new AddInstanceFlow(addDialog, spinnerDialog, instanceDetailsDialog);
|
||||
|
||||
startAddInstanceFlowButton.addEventListener("click", e => addInstanceFlow.start());
|
||||
|
||||
saveButton.addEventListener("click", e => {
|
||||
storageManager.save();
|
||||
|
@ -29,17 +33,6 @@ reorderButton.addEventListener("click", () => {
|
|||
reorderButton.innerText = reordering ? "Finish reordering" : "Reorder";
|
||||
});
|
||||
|
||||
const {
|
||||
showInstanceDetailsDialog,
|
||||
hideInstanceDetailsDialog,
|
||||
populateInstanceDetailsDialog,
|
||||
} = initializeInstanceDetailsDialog(detailsDialog, () => { });
|
||||
|
||||
const {
|
||||
showAddInstanceDialog,
|
||||
hideAddInstanceDialog
|
||||
} = initializeAddInstanceFlow(detailsDialog, addDialog);
|
||||
|
||||
updateInstanceList();
|
||||
storageManager.addSaveCallback(updateInstanceList);
|
||||
|
||||
|
|
|
@ -1,81 +1,75 @@
|
|||
// This file handles the "Confirm instance details" dialog
|
||||
|
||||
import { FormDialog, ONCE } from "./dialog.mjs";
|
||||
import { findButtonOrFail, findFormOrFail, findImageOrFail, findInputOrFail, findSelectOrFail } from "./dom.mjs";
|
||||
import knownSoftware from "./known_software.mjs";
|
||||
|
||||
const blankImage = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
|
||||
|
||||
export function initializeInstanceDetailsDialog(
|
||||
dialog: HTMLDialogElement,
|
||||
callback: (
|
||||
instanceName: string,
|
||||
instanceHost: string,
|
||||
instanceHostSecure: boolean,
|
||||
instanceSoftware: string,
|
||||
instanceIcon: string | null
|
||||
) => void
|
||||
): {
|
||||
showInstanceDetailsDialog: () => void,
|
||||
hideInstanceDetailsDialog: () => void,
|
||||
populateInstanceDetailsDialog: (
|
||||
instanceNameValue: string,
|
||||
instanceHostValue: string,
|
||||
instanceHostSecureValue: boolean,
|
||||
instanceSoftwareValue: string,
|
||||
instanceIconValue: string | null
|
||||
) => void
|
||||
} {
|
||||
const showInstanceDetailsDialog = () => dialog.showModal();
|
||||
const hideInstanceDetailsDialog = () => dialog.close();
|
||||
export type InstanceDetailsDialogData = {
|
||||
name: string,
|
||||
host: string,
|
||||
hostSecure: boolean,
|
||||
software: string,
|
||||
iconURL: string | null
|
||||
};
|
||||
|
||||
const form = findFormOrFail(dialog, ".instanceDetailsForm");
|
||||
const instanceName = findInputOrFail(form, "#instanceName");
|
||||
const instanceHost = findInputOrFail(form, "#instanceHost");
|
||||
const instanceHostSecure = findInputOrFail(form, "#instanceHostSecure");
|
||||
const instanceSoftware = findSelectOrFail(form, "#instanceSoftware");
|
||||
const instanceIcon = findImageOrFail(form, "#instanceIcon");
|
||||
const closeButton = findButtonOrFail(form, ".close");
|
||||
export class InstanceDetailsDialog extends FormDialog {
|
||||
protected instanceName: HTMLInputElement;
|
||||
protected instanceHost: HTMLInputElement;
|
||||
protected instanceHostSecure: HTMLInputElement;
|
||||
protected instanceSoftware: HTMLSelectElement;
|
||||
protected instanceIcon: HTMLImageElement;
|
||||
protected closeButton: HTMLButtonElement;
|
||||
|
||||
constructor(dialog: HTMLDialogElement, initializeDOM: boolean = true) {
|
||||
super(dialog, findFormOrFail(dialog, ".instanceDetailsForm"));
|
||||
|
||||
this.instanceName = findInputOrFail(this.form, "#instanceName");
|
||||
this.instanceHost = findInputOrFail(this.form, "#instanceHost");
|
||||
this.instanceHostSecure = findInputOrFail(this.form, "#instanceHostSecure");
|
||||
this.instanceSoftware = findSelectOrFail(this.form, "#instanceSoftware");
|
||||
this.instanceIcon = findImageOrFail(this.form, "#instanceIcon");
|
||||
this.closeButton = findButtonOrFail(this.form, ".close");
|
||||
|
||||
if (initializeDOM) this.initializeDOM();
|
||||
}
|
||||
|
||||
protected override initializeDOM() {
|
||||
super.initializeDOM();
|
||||
|
||||
for (const [name, software] of Object.entries(knownSoftware.software)) {
|
||||
const option = new Option(software.name, name);
|
||||
instanceSoftware.appendChild(option);
|
||||
this.instanceSoftware.appendChild(option);
|
||||
}
|
||||
|
||||
instanceIcon.src = blankImage;
|
||||
this.instanceIcon.src = blankImage;
|
||||
|
||||
const populateInstanceDetailsDialog = (
|
||||
instanceNameValue: string,
|
||||
instanceHostValue: string,
|
||||
instanceHostSecureValue: boolean,
|
||||
instanceSoftwareValue: string,
|
||||
instanceIconValue: string | null
|
||||
) => {
|
||||
instanceName.value = instanceNameValue;
|
||||
instanceHost.value = instanceHostValue;
|
||||
instanceHostSecure.checked = instanceHostSecureValue;
|
||||
instanceSoftware.value = instanceSoftwareValue;
|
||||
instanceIcon.src = instanceIconValue ?? blankImage;
|
||||
};
|
||||
|
||||
form.addEventListener("submit", e => {
|
||||
callback(
|
||||
instanceName.value,
|
||||
instanceHost.value,
|
||||
instanceHostSecure.checked,
|
||||
instanceSoftware.value,
|
||||
instanceIcon.src
|
||||
);
|
||||
form.reset();
|
||||
});
|
||||
|
||||
closeButton.addEventListener("click", e => {
|
||||
instanceIcon.src = blankImage;
|
||||
hideInstanceDetailsDialog();
|
||||
});
|
||||
|
||||
return {
|
||||
showInstanceDetailsDialog,
|
||||
hideInstanceDetailsDialog,
|
||||
populateInstanceDetailsDialog
|
||||
};
|
||||
this.closeButton.addEventListener("click", e => this.close());
|
||||
}
|
||||
|
||||
#handleSubmit(data: InstanceDetailsDialogData, resolve: (data: InstanceDetailsDialogData) => void) {
|
||||
this.form.addEventListener("submit", e => {
|
||||
data.name = this.instanceName.value;
|
||||
data.host = this.instanceHost.value;
|
||||
data.hostSecure = this.instanceHostSecure.checked;
|
||||
data.software = this.instanceSoftware.value;
|
||||
resolve(data);
|
||||
this.close();
|
||||
}, ONCE);
|
||||
}
|
||||
|
||||
async present(data: InstanceDetailsDialogData): Promise<InstanceDetailsDialogData> {
|
||||
this.instanceName.value = data.name;
|
||||
this.instanceHost.value = data.host;
|
||||
this.instanceHostSecure.checked = data.hostSecure;
|
||||
this.instanceSoftware.value = data.software;
|
||||
this.instanceIcon.src = data.iconURL ?? blankImage;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.cancelOnceClosed(reject);
|
||||
this.#handleSubmit(data, resolve);
|
||||
this.open();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,10 @@ import { findButtonOrFail, findDialogOrFail, findFormOrFail, findInputOrFail, fi
|
|||
import knownSoftware from "./known_software.mjs";
|
||||
import storageManager from "./storage_manager.mjs";
|
||||
|
||||
const radioButtonName = "instanceSelect";
|
||||
const RADIO_BUTTON_NAME = "instanceSelect";
|
||||
|
||||
let addInstanceFlow: AddInstanceFlow | undefined;
|
||||
|
||||
const mainDialog = findDialogOrFail(document.body, "#mainDialog");
|
||||
const startAddInstanceFlowButton = findButtonOrFail(document.body, "#startAddInstanceFlow");
|
||||
const addDialog = findDialogOrFail(document.body, "#addInstance");
|
||||
|
@ -16,20 +17,6 @@ const redirectButton = findButtonOrFail(document.body, "#redirect");
|
|||
const redirectAlwaysButton = findButtonOrFail(document.body, "#redirectAlways");
|
||||
const pathText = findPreOrFail(document.body, "#path");
|
||||
|
||||
startAddInstanceFlowButton.addEventListener("click", e => addInstanceFlow?.start());
|
||||
|
||||
redirectButton.addEventListener("click", e => {
|
||||
// Can be assumed to not fail because the button is disabled if there are no options and the first one is selected by default
|
||||
redirect(getSelectedOption()!);
|
||||
});
|
||||
|
||||
redirectAlwaysButton.addEventListener("click", e => {
|
||||
// Can be assumed to not fail because the button is disabled if there are no options and the first one is selected by default
|
||||
const option = getSelectedOption()!;
|
||||
setAutoRedirect(option);
|
||||
redirect(option);
|
||||
});
|
||||
|
||||
// Don't bother initializing if we're performing autoredirect
|
||||
if (!autoRedirect()) {
|
||||
createInstanceSelectOptions();
|
||||
|
@ -44,6 +31,20 @@ if (!autoRedirect()) {
|
|||
mainDialog.show();
|
||||
};
|
||||
|
||||
startAddInstanceFlowButton.addEventListener("click", e => addInstanceFlow?.start());
|
||||
|
||||
redirectButton.addEventListener("click", e => {
|
||||
// Can be assumed to not fail because the button is disabled if there are no options and the first one is selected by default
|
||||
redirect(getSelectedOption()!);
|
||||
});
|
||||
|
||||
redirectAlwaysButton.addEventListener("click", e => {
|
||||
// Can be assumed to not fail because the button is disabled if there are no options and the first one is selected by default
|
||||
const option = getSelectedOption()!;
|
||||
setAutoRedirect(option);
|
||||
redirect(option);
|
||||
});
|
||||
|
||||
function updateNoInstanceHint() {
|
||||
findParagraphOrFail(document.body, "#no-instance").style.display =
|
||||
storageManager.storage.instances.length > 0
|
||||
|
@ -60,7 +61,7 @@ function createInstanceSelectOptions() {
|
|||
radio.id = instance.origin;
|
||||
radio.value = instance.origin;
|
||||
radio.type = "radio";
|
||||
radio.name = radioButtonName;
|
||||
radio.name = RADIO_BUTTON_NAME;
|
||||
const label = document.createElement("label");
|
||||
label.htmlFor = instance.origin;
|
||||
label.innerText = instance.name + " ";
|
||||
|
@ -107,7 +108,7 @@ function getTargetPath(): string {
|
|||
|
||||
function getSelectedOption(): string | null {
|
||||
try {
|
||||
return findInputOrFail(instanceSelectForm, `input[name="${radioButtonName}"]:checked`).value;
|
||||
return findInputOrFail(instanceSelectForm, `input[name="${RADIO_BUTTON_NAME}"]:checked`).value;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
export const ONCE = { once: true };
|
||||
export const CANCELLED = Symbol("Cancelled");
|
||||
|
||||
export class Dialog {
|
||||
protected dialog: HTMLDialogElement;
|
||||
|
@ -19,6 +20,10 @@ export class Dialog {
|
|||
close() {
|
||||
this.dialog.close();
|
||||
}
|
||||
|
||||
protected cancelOnceClosed(reject: (reason?: any) => void) {
|
||||
this.dialog.addEventListener("close", e => reject(CANCELLED), ONCE);
|
||||
}
|
||||
};
|
||||
|
||||
export class FormDialog extends Dialog {
|
||||
|
|
Loading…
Add table
Reference in a new issue