diff --git a/static/add_instance_flow.mts b/static/add_instance_flow.mts index ffd78ae..1877a87 100644 --- a/static/add_instance_flow.mts +++ b/static/add_instance_flow.mts @@ -1,69 +1,78 @@ import { AddInstanceDialog } from "./add_an_instance.mjs"; import { initializeInstanceDetailsDialog } from "./confirm_instance_details.mjs"; +import { Dialog } from "./dialog.mjs"; import storageManager, { Instance } from "./storage_manager.mjs"; -export function initializeAddInstanceFlow( - detailsDialog: HTMLDialogElement, - addDialog: HTMLDialogElement -): { - showAddInstanceDialog: () => void, - hideAddInstanceDialog: () => void -} { - 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 +export class AddInstanceFlow { + addDialog: AddInstanceDialog; + spinnerDialog: Dialog; + detailsDialog: HTMLDialogElement; + + constructor( + addDialog: AddInstanceDialog | HTMLDialogElement, + spinnerDialog: HTMLDialogElement, + detailsDialog: HTMLDialogElement, + ) { + if (addDialog instanceof AddInstanceDialog) + this.addDialog = addDialog; + else + this.addDialog = new AddInstanceDialog(addDialog, true); + this.spinnerDialog = new Dialog(spinnerDialog); + this.detailsDialog = detailsDialog; + } + + 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); }; - storageManager.storage.instances.push(instance); - storageManager.save(); - console.log("Successfully added new instance:", instance); - }; - const { - showInstanceDetailsDialog, - hideInstanceDetailsDialog, - populateInstanceDetailsDialog - } = initializeInstanceDetailsDialog(detailsDialog, instanceDetailsDialogCallback); + const { + showInstanceDetailsDialog, + hideInstanceDetailsDialog, + populateInstanceDetailsDialog + } = initializeInstanceDetailsDialog(this.detailsDialog, instanceDetailsDialogCallback); + + const { + autoQueryMetadata, + host, + secure, + } = await this.addDialog.prompt(); - const addInstanceDialogCallback = async ( - host: string, - secure: boolean, - autoQueryMetadata: boolean, - ) => { try { - if (!autoQueryMetadata) throw new Error("Don't"); + if (!autoQueryMetadata) throw null; // Skip to catch block + + this.spinnerDialog.open(); + const { name, software, iconURL } = await fetch(`/api/instance_info/${secure}/${encodeURIComponent(host)}`) .then(r => r.json()); if ( typeof name !== "string" || typeof software !== "string" - || !(typeof iconURL === "string" || iconURL === null) + || !(typeof iconURL === "string" || iconURL === null) // I guess TS is too stupid to understand this? ) throw new Error("Invalid API response"); + populateInstanceDetailsDialog(name, host, secure, software, iconURL as string | null); } catch { populateInstanceDetailsDialog(host, host, secure, "", null); } finally { + this.spinnerDialog.close(); showInstanceDetailsDialog(); } } - - const { - showAddInstanceDialog, - hideAddInstanceDialog - } = initializeAddInstanceDialog(addDialog, addInstanceDialogCallback); - - return { - showAddInstanceDialog, - hideAddInstanceDialog - }; } diff --git a/static/crossroad.html b/static/crossroad.html index 2274d20..e431a81 100644 --- a/static/crossroad.html +++ b/static/crossroad.html @@ -29,7 +29,7 @@

You currently don't have any instances. You should add one!


- +
@@ -108,6 +108,7 @@ Unchecking this is not recommended, and this option only exists for exceptional + \ No newline at end of file diff --git a/static/crossroad.mts b/static/crossroad.mts index 5cccef9..761e824 100644 --- a/static/crossroad.mts +++ b/static/crossroad.mts @@ -1,20 +1,22 @@ -import { initializeAddInstanceFlow } from "./add_instance_flow.mjs"; +import { AddInstanceFlow } from "./add_instance_flow.mjs"; import { findButtonOrFail, findDialogOrFail, findFormOrFail, findInputOrFail, findParagraphOrFail, findPreOrFail } from "./dom.mjs"; import knownSoftware from "./known_software.mjs"; import storageManager from "./storage_manager.mjs"; const radioButtonName = "instanceSelect"; +let addInstanceFlow: AddInstanceFlow | 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 instanceSelectForm = findFormOrFail(document.body, "#instanceSelectForm"); const redirectButton = findButtonOrFail(document.body, "#redirect"); const redirectAlwaysButton = findButtonOrFail(document.body, "#redirectAlways"); const pathText = findPreOrFail(document.body, "#path"); -showAddInstanceDialogButton.addEventListener("click", e => showAddInstanceDialog()); +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 @@ -28,9 +30,6 @@ redirectAlwaysButton.addEventListener("click", e => { redirect(option); }); -let showAddInstanceDialog = () => { }; -let hideAddInstanceDialog = () => { }; - // Don't bother initializing if we're performing autoredirect if (!autoRedirect()) { createInstanceSelectOptions(); @@ -40,10 +39,7 @@ if (!autoRedirect()) { pathText.innerText = getTargetPath(); - ({ - showAddInstanceDialog, - hideAddInstanceDialog - } = initializeAddInstanceFlow(detailsDialog, addDialog)); + addInstanceFlow = new AddInstanceFlow(addDialog, spinnerDialog, detailsDialog); mainDialog.show(); }; diff --git a/static/dialog.mts b/static/dialog.mts index 37119ad..84dc3f1 100644 --- a/static/dialog.mts +++ b/static/dialog.mts @@ -12,7 +12,7 @@ export class Dialog { */ protected initializeDOM() { } - protected open() { + open() { this.dialog.showModal(); } diff --git a/static/main.css b/static/main.css index 91ef307..e60921f 100644 --- a/static/main.css +++ b/static/main.css @@ -1,3 +1,5 @@ +@import url("/static/spinner.css"); + :root { --red: #cb0b0b; --blue: #2081c3; diff --git a/static/spinner.css b/static/spinner.css new file mode 100644 index 0000000..68d0df5 --- /dev/null +++ b/static/spinner.css @@ -0,0 +1,78 @@ +/* Sourced and modified from https://cssloaders.github.io/ */ + +.spinner { + display: block; + animation: rotate 1s infinite; + height: 50px; + width: 50px; +} + +.spinner:before, +.spinner:after { + border-radius: 50%; + content: ""; + display: block; + height: 20px; + width: 20px; +} + +.spinner:before { + animation: ball1 1s infinite; + background-color: var(--red); + box-shadow: 30px 0 0 var(--blue); + margin-bottom: 10px; +} + +.spinner:after { + animation: ball2 1s infinite; + background-color: var(--blue); + box-shadow: 30px 0 0 var(--red); +} + +@keyframes rotate { + 0% { + transform: rotate(0deg) scale(0.8) + } + + 50% { + transform: rotate(360deg) scale(1.2) + } + + 100% { + transform: rotate(720deg) scale(0.8) + } +} + +@keyframes ball1 { + 0% { + box-shadow: 30px 0 0 var(--blue); + } + + 50% { + box-shadow: 0 0 0 var(--blue); + margin-bottom: 0; + transform: translate(15px, 15px); + } + + 100% { + box-shadow: 30px 0 0 var(--blue); + margin-bottom: 10px; + } +} + +@keyframes ball2 { + 0% { + box-shadow: 30px 0 0 var(--red); + } + + 50% { + box-shadow: 0 0 0 var(--red); + margin-top: -20px; + transform: translate(15px, 15px); + } + + 100% { + box-shadow: 30px 0 0 var(--red); + margin-top: 0; + } +} \ No newline at end of file