FeDirect/static/crossroad.mts
CenTdemeern1 1057bf80dc
All checks were successful
Build & Test / build-run (push) Successful in 40s
Hotfix forks text showing when it shouldn't
2025-02-13 03:27:39 +01:00

229 lines
8.8 KiB
TypeScript

import { AddInstanceFlow } from "./add_instance_flow.mjs";
import { findButtonOrFail, findDialogOrFail, findDivOrFail, findFormOrFail, findInputOrFail, findOlOrFail, findParagraphOrFail, findPreOrFail, findSpanOrFail } from "./dom.mjs";
import knownSoftware, { getName } from "./known_software.mjs";
import storageManager, { Instance } from "./storage_manager.mjs";
const RADIO_BUTTON_NAME = "instanceSelect";
let addInstanceFlow: AddInstanceFlow | undefined;
const mainDialog = findDialogOrFail(document.body, "#mainDialog");
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 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 redirectAlwaysButton = findButtonOrFail(document.body, "#redirectAlways");
const noInstanceParagraph = findParagraphOrFail(document.body, "#noInstance");
const pathText = findPreOrFail(document.body, "#path");
const aOrAnText = findSpanOrFail(document.body, "#aOrAn");
const destinationText = findSpanOrFail(document.body, "#destination");
// Don't bother initializing if we're performing autoredirect
if (!autoRedirect()) {
createInstanceSelectOptions();
storageManager.addSaveCallback(createInstanceSelectOptions);
updateNoInstanceHint();
storageManager.addSaveCallback(updateNoInstanceHint);
pathText.innerText = getTargetPath();
const targetID = getTargetSoftwareOrGroup();
const targetName = getName(knownSoftware, targetID) ?? targetID;
aOrAnText.innerText = "aeiou".includes(targetName[0].toLowerCase()) ? "an" : "a";
destinationText.innerText = targetName;
addInstanceFlow = new AddInstanceFlow(addDialog, spinnerDialog, detailsDialog);
mainDialog.show();
};
startAddInstanceFlowButton.addEventListener("click", e => addInstanceFlow?.start(true));
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
redirect(getSelectedOption()!);
});
redirectAlwaysButton.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
const option = getSelectedOption()!;
setAutoRedirect(option);
redirect(option);
});
function updateNoInstanceHint() {
noInstanceParagraph.hidden =
storageManager.storage.instances.length > 0;
}
type PreferenceGroups = {
preferred: Instance[],
forks?: {
list: Instance[],
of: string,
},
others: Instance[],
};
function sortInstancesIntoPreferenceGroups(): PreferenceGroups {
const targetID = getTargetSoftwareOrGroup();
const pGroups: PreferenceGroups = {
preferred: [],
others: [],
};
// If targetID is a group
if (knownSoftware.groups[targetID]) {
for (const instance of storageManager.storage.instances) {
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);
}
}
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 || forks?.list.length === 0;
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");
if (firstInput) firstInput.checked = true;
setRedirectButtonState(firstInput !== null);
}
function setRedirectButtonState(enabled: boolean) {
redirectButton.disabled = !enabled;
redirectAlwaysButton.disabled = !enabled;
}
function getTargetSoftwareOrGroup(): string {
const currentURL = URL.parse(location.href)!;
const target = currentURL.pathname.match(/\/+([^\/]*)\/?/)?.[1];
if (target == null) throw new Error("Crossroad was served on an invalid path (likely a backend routing mistake)");
const softwareName = Object.entries(knownSoftware.software).find(([name, software]) => software.aliases.includes(target))?.[0];
if (softwareName) return softwareName;
const groupName = Object.entries(knownSoftware.groups).find(([name, group]) => group.aliases.includes(target))?.[0];
if (groupName) return groupName;
throw new Error("Could not identify target software or group");
}
function getTargetPath(): string {
const currentURL = URL.parse(location.href)!;
return currentURL.pathname.replace(/\/+[^\/]*\/?/, "/");
}
function getSelectedOption(): string | null {
try {
return findInputOrFail(instanceSelectForm, `input[name="${RADIO_BUTTON_NAME}"]:checked`).value;
} catch {
return null;
}
}
function autoRedirect(): boolean {
const targetSoftware = getTargetSoftwareOrGroup();
const preferredFor = storageManager.storage.instances.find(instance => instance.preferredFor?.includes(targetSoftware));
if (preferredFor) {
redirect(preferredFor.origin);
return true;
}
return false;
}
function setAutoRedirect(option: string) {
const instance = storageManager.storage.instances.find(e => e.origin === option);
if (!instance) throw new Error("Invalid argument");
instance.preferredFor.push(getTargetSoftwareOrGroup());
storageManager.save();
}
function redirect(to: string) {
const url = URL.parse(to);
if (url === null) throw new Error("Couldn't parse destination");
url.pathname = getTargetPath();
location.href = url.toString();
}