Add confirm instance details dialog
This commit is contained in:
parent
790a9f4a7d
commit
0f935f5453
5 changed files with 192 additions and 10 deletions
|
@ -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,
|
showAddInstanceDialog: () => void,
|
||||||
hideAddInstanceDialog: () => void,
|
hideAddInstanceDialog: () => void,
|
||||||
} {
|
} {
|
||||||
|
@ -32,12 +39,14 @@ export function initializeAddInstanceDialog(dialog: HTMLDialogElement): {
|
||||||
instanceHost.setCustomValidity("");
|
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
|
// A sane browser doesn't allow for submitting the form if the above validation fails
|
||||||
const { host, secure } = parseHost(instanceHost.value)!;
|
const { host, secure } = parseHost(instanceHost.value)!;
|
||||||
console.log(
|
callback(host, secure, autoQueryMetadata.checked);
|
||||||
await fetch(`/api/instance_info/${secure}/${encodeURI(host)}`).then(r => r.json())
|
|
||||||
);
|
|
||||||
form.reset();
|
form.reset();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
70
static/confirm_instance_details.mts
Normal file
70
static/confirm_instance_details.mts
Normal 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
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
:root {
|
:root {
|
||||||
--red: #cb0b0b;
|
--red: #cb0b0b;
|
||||||
--blue: #2081c3;
|
--blue: #2081c3;
|
||||||
|
--transparent-black: #0008;
|
||||||
--large: 2em;
|
--large: 2em;
|
||||||
--medium: 1em;
|
--medium: 1em;
|
||||||
}
|
}
|
||||||
|
@ -25,7 +26,7 @@ dialog {
|
||||||
|
|
||||||
dialog::backdrop {
|
dialog::backdrop {
|
||||||
backdrop-filter: blur(5px);
|
backdrop-filter: blur(5px);
|
||||||
background-color: #0008;
|
background-color: var(--transparent-black);
|
||||||
transition: background-color 0.125s ease-out;
|
transition: background-color 0.125s ease-out;
|
||||||
|
|
||||||
@starting-style {
|
@starting-style {
|
||||||
|
@ -65,6 +66,16 @@ abbr[title] {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flex-row-reverse {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-column-reverse {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
.half-width {
|
.half-width {
|
||||||
min-width: 50%;
|
min-width: 50%;
|
||||||
}
|
}
|
||||||
|
@ -73,8 +84,12 @@ abbr[title] {
|
||||||
min-height: 50%;
|
min-height: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.full-height {
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.separator-bottom {
|
.separator-bottom {
|
||||||
border-bottom: solid 1px #0008;
|
border-bottom: solid 1px var(--transparent-black);
|
||||||
}
|
}
|
||||||
|
|
||||||
.margin-auto-top {
|
.margin-auto-top {
|
||||||
|
@ -84,3 +99,24 @@ abbr[title] {
|
||||||
.margin-large-bottom {
|
.margin-large-bottom {
|
||||||
margin-bottom: var(--large);
|
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%;
|
||||||
|
}
|
|
@ -58,6 +58,38 @@ We do not track or save any requests or data.">
|
||||||
<button type="reset" class="close">Cancel</button>
|
<button type="reset" class="close">Cancel</button>
|
||||||
</form>
|
</form>
|
||||||
</dialog>
|
</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>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
|
@ -1,4 +1,5 @@
|
||||||
import { initializeAddInstanceDialog } from "./add_an_instance.mjs";
|
import { initializeAddInstanceDialog } from "./add_an_instance.mjs";
|
||||||
|
import { initializeInstanceDetailsDialog } from "./confirm_instance_details.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";
|
||||||
console.log(knownSoftware);
|
console.log(knownSoftware);
|
||||||
|
@ -9,7 +10,41 @@ export function getMainDialog(): HTMLDialogElement {
|
||||||
return document.getElementById('mainDialog') as HTMLDialogElement;
|
return document.getElementById('mainDialog') as HTMLDialogElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dialog = document.querySelector("#addInstance");
|
const detailsDialog = document.querySelector("#instanceDetails");
|
||||||
if (!(dialog instanceof HTMLDialogElement))
|
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");
|
throw new Error("Couldn't find addInstance dialog");
|
||||||
export const { showAddInstanceDialog, hideAddInstanceDialog } = initializeAddInstanceDialog(dialog);
|
export const {
|
||||||
|
showAddInstanceDialog,
|
||||||
|
hideAddInstanceDialog
|
||||||
|
} = initializeAddInstanceDialog(addDialog, addInstanceDialogCallback);
|
||||||
|
|
Loading…
Add table
Reference in a new issue