Now sorting
All checks were successful
Build & Test / build-run (push) Successful in 43s

This commit is contained in:
CenTdemeern1 2025-02-13 01:41:20 +01:00
parent bc7e388f04
commit 027faf8371
4 changed files with 138 additions and 29 deletions

View file

@ -30,7 +30,17 @@
instance.<br> instance.<br>
<img src="/static/down_arrow.svg" alt="" class="medium-height" /> <img src="/static/down_arrow.svg" alt="" class="medium-height" />
<p id="noInstance">You currently don't have any instances. You should add one!</p> <p id="noInstance">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">
<ol id="preferredList" class="margin-none" hidden></ol>
<div id="forks" hidden>
<p class="align-center margin-none-bottom">Other <span id="forkOf"></span> forks</p>
<ol id="forksList" class="margin-none"></ol>
</div>
<div id="others" hidden>
<p class="align-center margin-none-bottom">Other instances</p>
<ol id="othersList" class="margin-none"></ol>
</div>
</form>
<br> <br>
<button id="startAddInstanceFlow">Add an instance</button> <button id="startAddInstanceFlow">Add an instance</button>
</center> </center>

View file

@ -1,7 +1,7 @@
import { AddInstanceFlow } from "./add_instance_flow.mjs"; import { AddInstanceFlow } from "./add_instance_flow.mjs";
import { findButtonOrFail, findDialogOrFail, findFormOrFail, findInputOrFail, findParagraphOrFail, findPreOrFail, findSpanOrFail } from "./dom.mjs"; import { findButtonOrFail, findDialogOrFail, findDivOrFail, findFormOrFail, findInputOrFail, findOlOrFail, findParagraphOrFail, findPreOrFail, findSpanOrFail } from "./dom.mjs";
import knownSoftware, { getName } from "./known_software.mjs"; import knownSoftware, { getName } from "./known_software.mjs";
import storageManager from "./storage_manager.mjs"; import storageManager, { Instance } from "./storage_manager.mjs";
const RADIO_BUTTON_NAME = "instanceSelect"; const RADIO_BUTTON_NAME = "instanceSelect";
@ -13,6 +13,12 @@ const addDialog = findDialogOrFail(document.body, "#addInstance");
const spinnerDialog = findDialogOrFail(document.body, "#spinner"); const spinnerDialog = findDialogOrFail(document.body, "#spinner");
const detailsDialog = findDialogOrFail(document.body, "#instanceDetails"); const detailsDialog = findDialogOrFail(document.body, "#instanceDetails");
const instanceSelectForm = findFormOrFail(document.body, "#instanceSelectForm"); const instanceSelectForm = findFormOrFail(document.body, "#instanceSelectForm");
const preferredList = findOlOrFail(document.body, "#preferredList");
const forksDiv = findDivOrFail(document.body, "#forks");
const forkOfSpan = findSpanOrFail(document.body, "#forkOf");
const forksList = findOlOrFail(document.body, "#forksList");
const othersDiv = findDivOrFail(document.body, "#others");
const othersList = findOlOrFail(document.body, "#othersList");
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 noInstanceParagraph = findParagraphOrFail(document.body, "#noInstance"); const noInstanceParagraph = findParagraphOrFail(document.body, "#noInstance");
@ -58,34 +64,112 @@ function updateNoInstanceHint() {
storageManager.storage.instances.length > 0; storageManager.storage.instances.length > 0;
} }
function createInstanceSelectOptions() { type PreferenceGroups = {
instanceSelectForm.replaceChildren(); // Erase all child nodes preferred: Instance[],
for (const instance of storageManager.storage.instances) { forks?: {
const div = document.createElement("div"); list: Instance[],
div.setAttribute("x-option", instance.origin); of: string,
const radio = document.createElement("input"); },
radio.id = instance.origin; others: Instance[],
radio.value = instance.origin; };
radio.type = "radio";
radio.name = RADIO_BUTTON_NAME; function sortInstancesIntoPreferenceGroups(): PreferenceGroups {
const label = document.createElement("label"); const targetID = getTargetSoftwareOrGroup();
label.htmlFor = instance.origin; const pGroups: PreferenceGroups = {
label.innerText = instance.name + " "; preferred: [],
if (instance.iconURL) { others: [],
const img = new Image(); };
img.src = instance.iconURL; // If targetID is a group
img.alt = `${instance.name} icon`; if (knownSoftware.groups[targetID]) {
img.className = "inlineIcon medium-height"; for (const instance of storageManager.storage.instances) {
label.append(img, " "); const software = knownSoftware.software[instance.software];
// If the instance's software is in the target group
if (software.groups.includes(targetID)) {
pGroups.preferred.push(instance);
} else {
pGroups.others.push(instance);
}
}
} else {
const isFork = knownSoftware.software[targetID].forkOf !== undefined;
const forkOf = knownSoftware.software[targetID].forkOf ?? targetID;
const hasForks = isFork || Object.values(knownSoftware.software).some(s => s.forkOf === forkOf);
if (hasForks) {
pGroups.forks = {
list: [],
of: forkOf,
};
}
for (const instance of storageManager.storage.instances) {
if (instance.software === targetID) {
pGroups.preferred.push(instance);
continue;
}
const software = knownSoftware.software[instance.software];
// Checking pGroups.forks is the TypeScript safe way of checking hasForks
if (pGroups.forks && (instance.software === forkOf || software.forkOf === forkOf)) {
pGroups.forks.list.push(instance);
continue;
}
pGroups.others.push(instance);
} }
const small = document.createElement("small");
const softwareName = knownSoftware.software[instance.software].name;
small.innerText = `(${softwareName})`;
label.appendChild(small);
div.appendChild(radio);
div.appendChild(label);
instanceSelectForm.appendChild(div);
} }
return pGroups;
}
function constructOptionFromInstance(instance: Instance): HTMLDivElement {
const div = document.createElement("div");
div.setAttribute("x-option", instance.origin);
const radio = document.createElement("input");
radio.id = instance.origin;
radio.value = instance.origin;
radio.type = "radio";
radio.name = RADIO_BUTTON_NAME;
const label = document.createElement("label");
label.htmlFor = instance.origin;
label.innerText = instance.name + " ";
if (instance.iconURL) {
const img = new Image();
img.src = instance.iconURL;
img.alt = `${instance.name} icon`;
img.className = "inlineIcon medium-height";
label.append(img, " ");
}
const small = document.createElement("small");
const softwareName = knownSoftware.software[instance.software].name;
small.innerText = `(${softwareName})`;
label.appendChild(small);
div.appendChild(radio);
div.appendChild(label);
return div;
}
function createInstanceSelectOptions() {
// Erase all child nodes
preferredList.replaceChildren();
forksList.replaceChildren();
othersList.replaceChildren();
const { preferred, forks, others } = sortInstancesIntoPreferenceGroups();
preferredList.hidden = preferred.length === 0;
for (const instance of preferred) {
preferredList.appendChild(constructOptionFromInstance(instance));
}
forksDiv.hidden = forks === undefined;
if (forks) {
forkOfSpan.innerText = getName(knownSoftware, forks.of) ?? forks.of;
for (const instance of forks.list) {
forksList.appendChild(constructOptionFromInstance(instance));
}
}
othersDiv.hidden = others.length === 0;
for (const instance of others) {
othersList.appendChild(constructOptionFromInstance(instance));
}
const firstInput = instanceSelectForm.querySelector("input"); const firstInput = instanceSelectForm.querySelector("input");
if (firstInput) firstInput.checked = true; if (firstInput) firstInput.checked = true;
setRedirectButtonState(firstInput !== null); setRedirectButtonState(firstInput !== null);

View file

@ -1,6 +1,13 @@
// I would've LOVED to use generics for this but unfortunately that's not possible. // I would've LOVED to use generics for this but unfortunately that's not possible.
// Type safety, but at what cost... >~< thanks TypeScript // Type safety, but at what cost... >~< thanks TypeScript
export function findDivOrFail(on: Element, selector: string): HTMLDivElement {
const element = on.querySelector(selector);
if (!(element instanceof HTMLDivElement))
throw new Error(`${selector} isn't a div`);
return element;
}
export function findSpanOrFail(on: Element, selector: string): HTMLSpanElement { export function findSpanOrFail(on: Element, selector: string): HTMLSpanElement {
const element = on.querySelector(selector); const element = on.querySelector(selector);
if (!(element instanceof HTMLSpanElement)) if (!(element instanceof HTMLSpanElement))

View file

@ -61,6 +61,10 @@ abbr[title] {
text-align: start; text-align: start;
} }
.align-center {
text-align: center;
}
.inline-block { .inline-block {
display: inline-block; display: inline-block;
} }
@ -157,6 +161,10 @@ abbr[title] {
margin-bottom: var(--large); margin-bottom: var(--large);
} }
.margin-none-bottom {
margin-bottom: 0;
}
.square { .square {
aspect-ratio: 1; aspect-ratio: 1;
} }