Add confirm instance details dialog

This commit is contained in:
CenTdemeern1 2025-01-14 10:49:44 +01:00
parent 790a9f4a7d
commit 0f935f5453
5 changed files with 192 additions and 10 deletions

View file

@ -10,7 +10,14 @@ export function parseHost(host: string): { host: string, secure: boolean } | nul
};
}
export function initializeAddInstanceDialog(dialog: HTMLDialogElement): {
export function initializeAddInstanceDialog(
dialog: HTMLDialogElement,
callback: (
host: string,
secure: boolean,
autoQueryMetadata: boolean,
) => void
): {
showAddInstanceDialog: () => void,
hideAddInstanceDialog: () => void,
} {
@ -32,12 +39,14 @@ export function initializeAddInstanceDialog(dialog: HTMLDialogElement): {
instanceHost.setCustomValidity("");
});
form.addEventListener("submit", async e => {
const autoQueryMetadata = form.querySelector("#autoQueryMetadata");
if (!(autoQueryMetadata instanceof HTMLInputElement))
throw new Error("#autoQueryMetadata isn't an input");
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)!;
console.log(
await fetch(`/api/instance_info/${secure}/${encodeURI(host)}`).then(r => r.json())
);
callback(host, secure, autoQueryMetadata.checked);
form.reset();
});

View file

@ -0,0 +1,70 @@
// This file handles the "Confirm instance details" dialog
import knownSoftware from "./known_software.mjs";
const blankImage = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
export function initializeInstanceDetailsDialog(dialog: HTMLDialogElement): {
showInstanceDetailsDialog: () => void,
hideInstanceDetailsDialog: () => void,
populateInstanceDetailsDialog: (
instanceNameValue: string,
instanceSoftwareValue: string,
instanceIconValue: string | null
) => void,
} {
const showInstanceDetailsDialog = () => dialog.showModal();
const hideInstanceDetailsDialog = () => dialog.close();
const form = dialog.querySelector(".instanceDetailsForm");
if (!(form instanceof HTMLFormElement))
throw new Error(".instanceDetailsForm isn't a form");
const instanceName = form.querySelector("#instanceName");
if (!(instanceName instanceof HTMLInputElement))
throw new Error("#instanceName isn't an input");
const instanceSoftware = form.querySelector("#instanceSoftware");
if (!(instanceSoftware instanceof HTMLSelectElement))
throw new Error("#instanceSoftware isn't a select");
for (const [name, software] of Object.entries(knownSoftware.software)) {
const option = new Option(software.name, name);
instanceSoftware.appendChild(option);
}
const instanceIcon = form.querySelector("#instanceIcon");
if (!(instanceIcon instanceof HTMLImageElement))
throw new Error("#instanceIcon isn't an image");
instanceIcon.src = blankImage;
const populateInstanceDetailsDialog = (
instanceNameValue: string,
instanceSoftwareValue: string,
instanceIconValue: string | null
) => {
instanceName.value = instanceNameValue;
instanceSoftware.value = instanceSoftwareValue;
instanceIcon.src = instanceIconValue ?? blankImage;
};
form.addEventListener("submit", e => {
form.reset();
});
const closeButton = form.querySelector(".close");
if (!(closeButton instanceof HTMLButtonElement))
throw new Error(".close isn't a button");
closeButton.addEventListener("click", e => {
instanceIcon.src = blankImage;
hideInstanceDetailsDialog();
});
return {
showInstanceDetailsDialog,
hideInstanceDetailsDialog,
populateInstanceDetailsDialog
};
}

View file

@ -1,6 +1,7 @@
:root {
--red: #cb0b0b;
--blue: #2081c3;
--transparent-black: #0008;
--large: 2em;
--medium: 1em;
}
@ -25,7 +26,7 @@ dialog {
dialog::backdrop {
backdrop-filter: blur(5px);
background-color: #0008;
background-color: var(--transparent-black);
transition: background-color 0.125s ease-out;
@starting-style {
@ -65,6 +66,16 @@ abbr[title] {
flex-direction: row;
}
.flex-row-reverse {
display: flex;
flex-direction: row-reverse;
}
.flex-column-reverse {
display: flex;
flex-direction: column-reverse;
}
.half-width {
min-width: 50%;
}
@ -73,8 +84,12 @@ abbr[title] {
min-height: 50%;
}
.full-height {
min-height: 100%;
}
.separator-bottom {
border-bottom: solid 1px #0008;
border-bottom: solid 1px var(--transparent-black);
}
.margin-auto-top {
@ -83,4 +98,25 @@ abbr[title] {
.margin-large-bottom {
margin-bottom: var(--large);
}
.square {
aspect-ratio: 1;
}
.iconContainer {
width: 64px;
height: 64px;
padding: 1px;
border: solid 1px var(--transparent-black);
}
.icon {
position: relative;
max-width: 64px;
max-height: 64px;
margin: auto;
top: 50%;
left: 50%;
translate: -50% -50%;
}

View file

@ -58,6 +58,38 @@ We do not track or save any requests or data.">
<button type="reset" class="close">Cancel</button>
</form>
</dialog>
<dialog id="instanceDetails">
<h1>Confirm instance details</h1>
<form method="dialog" class="instanceDetailsForm">
<div class="flex-row">
<div class="half-width">
<label for="instanceName">Instance name</label>
<br>
<input id="instanceName" type="text" name="instanceName" />
<br><br>
<label for="instanceSoftware">Instance software</label>
<br>
<select id="instanceSoftware" type="text" name="instanceSoftware" required>
<option value="">(Please select)</option>
</select>
</div>
<div class="half-width flex-row-reverse">
<div class="full-height flex-column-reverse">
<div>
<label for="iconContainer">Instance icon</label>
<div id="iconContainer" class="square iconContainer">
<!-- This data URI is for a transparent gif image -->
<img id="instanceIcon" alt="Icon for the selected instance" class="icon" />
</div>
</div>
</div>
</div>
</div>
<br>
<button type="submit">OK</button>
<button type="reset" class="close">Cancel</button>
</form>
</dialog>
</body>
</html>

View file

@ -1,4 +1,5 @@
import { initializeAddInstanceDialog } from "./add_an_instance.mjs";
import { initializeInstanceDetailsDialog } from "./confirm_instance_details.mjs";
import knownSoftware from "./known_software.mjs";
import storageManager from "./storage_manager.mjs";
console.log(knownSoftware);
@ -9,7 +10,41 @@ export function getMainDialog(): HTMLDialogElement {
return document.getElementById('mainDialog') as HTMLDialogElement;
}
const dialog = document.querySelector("#addInstance");
if (!(dialog instanceof HTMLDialogElement))
const detailsDialog = document.querySelector("#instanceDetails");
if (!(detailsDialog instanceof HTMLDialogElement))
throw new Error("Couldn't find instanceDetails dialog");
export const {
showInstanceDetailsDialog,
hideInstanceDetailsDialog,
populateInstanceDetailsDialog
} = initializeInstanceDetailsDialog(detailsDialog);
const addInstanceDialogCallback = async (
host: string,
secure: boolean,
autoQueryMetadata: boolean,
) => {
if (!autoQueryMetadata) {
showInstanceDetailsDialog();
return;
}
const { name, software, iconURL } =
await fetch(`/api/instance_info/${secure}/${encodeURI(host)}`)
.then(r => r.json());
if (
typeof name !== "string"
|| typeof software !== "string"
|| !(typeof iconURL === "string" || iconURL === null)
)
throw new Error("Invalid API response");
populateInstanceDetailsDialog(name, software, iconURL as string | null);
showInstanceDetailsDialog();
}
const addDialog = document.querySelector("#addInstance");
if (!(addDialog instanceof HTMLDialogElement))
throw new Error("Couldn't find addInstance dialog");
export const { showAddInstanceDialog, hideAddInstanceDialog } = initializeAddInstanceDialog(dialog);
export const {
showAddInstanceDialog,
hideAddInstanceDialog
} = initializeAddInstanceDialog(addDialog, addInstanceDialogCallback);