130 lines
5.5 KiB
JavaScript
130 lines
5.5 KiB
JavaScript
/*
|
|
* Copyright (C) 2026 Mateusz Gruszczyński @linuxiarz.pl
|
|
* Source-Available Code / Dual-Licensed.
|
|
*
|
|
* Free for non-commercial and evaluation use under terms of BSL/GPLv3.
|
|
* Commercial or production use requires a valid paid license.
|
|
* See LICENSE file in repository root for details.
|
|
*/
|
|
|
|
(() => {
|
|
const order = ["assets", "language", "libraries", "access", "files"];
|
|
const resourceParts = new Set();
|
|
const locale = String(document.documentElement.lang || "en").toLowerCase().startsWith("pl") ? "pl" : "en";
|
|
const strings = {
|
|
en: {
|
|
assets: "Preparing the interface", language: "Setting up language",
|
|
libraries: "Starting the editor", access: "Checking access", files: "Opening the note",
|
|
complete: "Ready", error: "The note could not be opened", retry: "Reload page",
|
|
errorDetails: "Check your connection and try again.", progress: "{done} of {total} stages completed",
|
|
timeout: "Still waiting for the note. Check the connection or reload."
|
|
},
|
|
pl: {
|
|
assets: "Przygotowanie interfejsu", language: "Ustawianie j\u0119zyka",
|
|
libraries: "Uruchamianie edytora", access: "Sprawdzanie dost\u0119pu", files: "Otwieranie notatki",
|
|
complete: "Gotowe", error: "Nie uda\u0142o si\u0119 otworzy\u0107 notatki", retry: "Za\u0142aduj ponownie",
|
|
errorDetails: "Sprawd\u017a po\u0142\u0105czenie i spr\u00f3buj ponownie.", progress: "Uko\u0144czono {done} z {total} etap\u00f3w",
|
|
timeout: "Nadal oczekujemy na notatk\u0119. Sprawd\u017a po\u0142\u0105czenie lub od\u015bwie\u017c stron\u0119."
|
|
}
|
|
}[locale];
|
|
const completed = new Set();
|
|
const loadedStyleElements = new WeakSet();
|
|
let loadedStyleCount = 0;
|
|
let readyRequested = false;
|
|
let dismissed = false;
|
|
let failure = "";
|
|
const element = id => document.getElementById(id);
|
|
|
|
function update() {
|
|
const overlay = element("app-boot-overlay");
|
|
if (!overlay || dismissed) return;
|
|
const count = completed.size;
|
|
const progress = Math.round((count / order.length) * 100);
|
|
const next = order.find(stage => !completed.has(stage));
|
|
const heading = element("app-boot-heading");
|
|
if (heading) heading.textContent = locale === "pl" ? "Ładowanie…" : "Loading…";
|
|
const stageText = element("app-boot-stage");
|
|
const stageCount = element("app-boot-count");
|
|
const bar = element("app-boot-progress");
|
|
const fill = element("app-boot-progress-fill");
|
|
const retry = element("app-boot-retry");
|
|
if (stageText) stageText.textContent = failure ? strings.error : (next ? strings[next] : strings.complete);
|
|
if (stageCount) stageCount.textContent = failure || strings.progress.replace("{done}", count).replace("{total}", order.length);
|
|
if (bar) bar.setAttribute("aria-valuenow", String(progress));
|
|
if (fill) fill.style.width = `${progress}%`;
|
|
if (retry) { retry.hidden = !failure; retry.textContent = strings.retry; }
|
|
overlay.classList.toggle("has-error", Boolean(failure));
|
|
for (const stage of order) {
|
|
const node = element(`app-boot-step-${stage}`);
|
|
if (!node) continue;
|
|
const isDone = completed.has(stage);
|
|
node.dataset.status = isDone ? "done" : (stage === next && !failure ? "active" : "waiting");
|
|
node.querySelector("span")?.replaceChildren(document.createTextNode(strings[stage]));
|
|
}
|
|
if (readyRequested && count === order.length && !failure) dismiss();
|
|
}
|
|
|
|
function dismiss() {
|
|
if (dismissed) return;
|
|
dismissed = true;
|
|
const overlay = element("app-boot-overlay");
|
|
if (!overlay) return;
|
|
overlay.classList.add("is-ready");
|
|
const remove = () => overlay.remove();
|
|
overlay.addEventListener("transitionend", remove, { once: true });
|
|
setTimeout(remove, 300);
|
|
}
|
|
|
|
function complete(stage) {
|
|
if (dismissed || failure) return;
|
|
if (stage === "css" || stage === "js") {
|
|
resourceParts.add(stage);
|
|
if (resourceParts.has("css") && resourceParts.has("js")) completed.add("assets");
|
|
} else if (order.includes(stage)) {
|
|
completed.add(stage);
|
|
} else {
|
|
return;
|
|
}
|
|
update();
|
|
}
|
|
|
|
function fail(reason) {
|
|
if (dismissed || failure) return;
|
|
const safeMessage = reason instanceof Error ? reason.message : typeof reason === "string" ? reason : "";
|
|
failure = safeMessage && safeMessage.length < 180 ? `${strings.error}: ${safeMessage}` : strings.errorDetails;
|
|
update();
|
|
}
|
|
|
|
document.addEventListener("load", event => {
|
|
if (!(event.target instanceof HTMLLinkElement) || !event.target.matches("link[data-rustpad-shell-css]")) return;
|
|
if (loadedStyleElements.has(event.target)) return;
|
|
loadedStyleElements.add(event.target);
|
|
loadedStyleCount += 1;
|
|
const expected = document.querySelectorAll("link[data-rustpad-shell-css]").length;
|
|
if (expected > 0 && loadedStyleCount >= expected) complete("css");
|
|
}, true);
|
|
document.addEventListener("error", event => {
|
|
if (event.target instanceof HTMLLinkElement && event.target.matches("link[data-rustpad-shell-css]")) {
|
|
fail(strings.errorDetails);
|
|
}
|
|
}, true);
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
element("app-boot-retry")?.addEventListener("click", () => location.reload());
|
|
update();
|
|
}, { once: true });
|
|
|
|
const timeout = setTimeout(() => {
|
|
if (!dismissed) fail(strings.timeout);
|
|
}, 45000);
|
|
|
|
window.__RUSTPAD_LOADING__ = Object.freeze({
|
|
complete,
|
|
skip: complete,
|
|
ready() { readyRequested = true; update(); if (dismissed) clearTimeout(timeout); },
|
|
fail,
|
|
stageDone(stage) {
|
|
return stage === "css" || stage === "js" ? resourceParts.has(stage) : completed.has(stage);
|
|
}
|
|
});
|
|
})();
|