Rewrite AddInstanceFlow

Now with spinner!
This commit is contained in:
CenTdemeern1 2025-02-03 03:52:11 +01:00
parent 2be0658ed9
commit bfd61c2e50
6 changed files with 144 additions and 58 deletions

View file

@ -1,14 +1,27 @@
import { AddInstanceDialog } from "./add_an_instance.mjs"; import { AddInstanceDialog } from "./add_an_instance.mjs";
import { initializeInstanceDetailsDialog } from "./confirm_instance_details.mjs"; import { initializeInstanceDetailsDialog } from "./confirm_instance_details.mjs";
import { Dialog } from "./dialog.mjs";
import storageManager, { Instance } from "./storage_manager.mjs"; import storageManager, { Instance } from "./storage_manager.mjs";
export function initializeAddInstanceFlow( export class AddInstanceFlow {
addDialog: AddInstanceDialog;
spinnerDialog: Dialog;
detailsDialog: HTMLDialogElement;
constructor(
addDialog: AddInstanceDialog | HTMLDialogElement,
spinnerDialog: HTMLDialogElement,
detailsDialog: HTMLDialogElement, detailsDialog: HTMLDialogElement,
addDialog: HTMLDialogElement ) {
): { if (addDialog instanceof AddInstanceDialog)
showAddInstanceDialog: () => void, this.addDialog = addDialog;
hideAddInstanceDialog: () => void else
} { this.addDialog = new AddInstanceDialog(addDialog, true);
this.spinnerDialog = new Dialog(spinnerDialog);
this.detailsDialog = detailsDialog;
}
async start() {
const instanceDetailsDialogCallback = ( const instanceDetailsDialogCallback = (
name: string, name: string,
host: string, host: string,
@ -31,39 +44,35 @@ export function initializeAddInstanceFlow(
showInstanceDetailsDialog, showInstanceDetailsDialog,
hideInstanceDetailsDialog, hideInstanceDetailsDialog,
populateInstanceDetailsDialog populateInstanceDetailsDialog
} = initializeInstanceDetailsDialog(detailsDialog, instanceDetailsDialogCallback); } = initializeInstanceDetailsDialog(this.detailsDialog, instanceDetailsDialogCallback);
const {
autoQueryMetadata,
host,
secure,
} = await this.addDialog.prompt();
const addInstanceDialogCallback = async (
host: string,
secure: boolean,
autoQueryMetadata: boolean,
) => {
try { try {
if (!autoQueryMetadata) throw new Error("Don't"); if (!autoQueryMetadata) throw null; // Skip to catch block
this.spinnerDialog.open();
const { name, software, iconURL } = const { name, software, iconURL } =
await fetch(`/api/instance_info/${secure}/${encodeURIComponent(host)}`) await fetch(`/api/instance_info/${secure}/${encodeURIComponent(host)}`)
.then(r => r.json()); .then(r => r.json());
if ( if (
typeof name !== "string" typeof name !== "string"
|| typeof software !== "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"); throw new Error("Invalid API response");
populateInstanceDetailsDialog(name, host, secure, software, iconURL as string | null); populateInstanceDetailsDialog(name, host, secure, software, iconURL as string | null);
} catch { } catch {
populateInstanceDetailsDialog(host, host, secure, "", null); populateInstanceDetailsDialog(host, host, secure, "", null);
} finally { } finally {
this.spinnerDialog.close();
showInstanceDetailsDialog(); showInstanceDetailsDialog();
} }
} }
const {
showAddInstanceDialog,
hideAddInstanceDialog
} = initializeAddInstanceDialog(addDialog, addInstanceDialogCallback);
return {
showAddInstanceDialog,
hideAddInstanceDialog
};
} }

View file

@ -29,7 +29,7 @@
<p id="no-instance">You currently don't have any instances. You should add one!</p> <p id="no-instance">You currently don't have any instances. You should add one!</p>
<form id="instanceSelectForm" class="align-start wfit-content"></form> <form id="instanceSelectForm" class="align-start wfit-content"></form>
<br> <br>
<button id="showAddInstanceDialog">Add an instance</button> <button id="startAddInstanceFlow">Add an instance</button>
</center> </center>
</div> </div>
<div class="half-width align-self-start"> <div class="half-width align-self-start">
@ -108,6 +108,7 @@ Unchecking this is not recommended, and this option only exists for exceptional
<button type="reset" class="close">Cancel</button> <button type="reset" class="close">Cancel</button>
</form> </form>
</dialog> </dialog>
<dialog id="spinner"><span class="spinner"></span></dialog>
</body> </body>
</html> </html>

View file

@ -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 { findButtonOrFail, findDialogOrFail, findFormOrFail, findInputOrFail, findParagraphOrFail, findPreOrFail } from "./dom.mjs";
import knownSoftware from "./known_software.mjs"; import knownSoftware from "./known_software.mjs";
import storageManager from "./storage_manager.mjs"; import storageManager from "./storage_manager.mjs";
const radioButtonName = "instanceSelect"; const radioButtonName = "instanceSelect";
let addInstanceFlow: AddInstanceFlow | undefined;
const mainDialog = findDialogOrFail(document.body, "#mainDialog"); const mainDialog = findDialogOrFail(document.body, "#mainDialog");
const showAddInstanceDialogButton = findButtonOrFail(document.body, "#showAddInstanceDialog"); const startAddInstanceFlowButton = findButtonOrFail(document.body, "#startAddInstanceFlow");
const detailsDialog = findDialogOrFail(document.body, "#instanceDetails");
const addDialog = findDialogOrFail(document.body, "#addInstance"); 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 instanceSelectForm = findFormOrFail(document.body, "#instanceSelectForm");
const redirectButton = findButtonOrFail(document.body, "#redirect"); const redirectButton = findButtonOrFail(document.body, "#redirect");
const redirectAlwaysButton = findButtonOrFail(document.body, "#redirectAlways"); const redirectAlwaysButton = findButtonOrFail(document.body, "#redirectAlways");
const pathText = findPreOrFail(document.body, "#path"); const pathText = findPreOrFail(document.body, "#path");
showAddInstanceDialogButton.addEventListener("click", e => showAddInstanceDialog()); startAddInstanceFlowButton.addEventListener("click", e => addInstanceFlow?.start());
redirectButton.addEventListener("click", e => { 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 // 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); redirect(option);
}); });
let showAddInstanceDialog = () => { };
let hideAddInstanceDialog = () => { };
// Don't bother initializing if we're performing autoredirect // Don't bother initializing if we're performing autoredirect
if (!autoRedirect()) { if (!autoRedirect()) {
createInstanceSelectOptions(); createInstanceSelectOptions();
@ -40,10 +39,7 @@ if (!autoRedirect()) {
pathText.innerText = getTargetPath(); pathText.innerText = getTargetPath();
({ addInstanceFlow = new AddInstanceFlow(addDialog, spinnerDialog, detailsDialog);
showAddInstanceDialog,
hideAddInstanceDialog
} = initializeAddInstanceFlow(detailsDialog, addDialog));
mainDialog.show(); mainDialog.show();
}; };

View file

@ -12,7 +12,7 @@ export class Dialog {
*/ */
protected initializeDOM() { } protected initializeDOM() { }
protected open() { open() {
this.dialog.showModal(); this.dialog.showModal();
} }

View file

@ -1,3 +1,5 @@
@import url("/static/spinner.css");
:root { :root {
--red: #cb0b0b; --red: #cb0b0b;
--blue: #2081c3; --blue: #2081c3;

78
static/spinner.css Normal file
View file

@ -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;
}
}