Smooth persisted app shell loading
This commit is contained in:
parent
7ae37d0bb7
commit
2fc8ad8ce7
145
static/app.js
145
static/app.js
|
|
@ -256,12 +256,30 @@ async function loadDocumentTypes() {
|
||||||
|
|
||||||
defaultDocumentTypes = json.document_types || [];
|
defaultDocumentTypes = json.document_types || [];
|
||||||
renderDefaultDocumentOptions();
|
renderDefaultDocumentOptions();
|
||||||
|
await loadUploadedJsonOptions();
|
||||||
|
|
||||||
|
const savedSelectionRaw = localStorage.getItem("utilityAppSelectedDocument");
|
||||||
|
let savedSelection = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
savedSelection = savedSelectionRaw ? JSON.parse(savedSelectionRaw) : null;
|
||||||
|
} catch {
|
||||||
|
savedSelection = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (savedSelection?.kind === "uploaded" && savedSelection.id) {
|
||||||
|
await loadUploadedJson(savedSelection.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (savedSelection?.kind === "default" && savedSelection.id) {
|
||||||
|
await loadDefaultDocumentType(savedSelection.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (defaultDocumentTypes.length > 0) {
|
if (defaultDocumentTypes.length > 0) {
|
||||||
await loadDefaultDocumentType(defaultDocumentTypes[0].id);
|
await loadDefaultDocumentType(defaultDocumentTypes[0].id);
|
||||||
}
|
}
|
||||||
|
|
||||||
await loadUploadedJsonOptions();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderDefaultDocumentOptions() {
|
function renderDefaultDocumentOptions() {
|
||||||
|
|
@ -292,6 +310,14 @@ async function loadDefaultDocumentType(documentTypeId) {
|
||||||
renderDocumentType(currentDocumentType);
|
renderDocumentType(currentDocumentType);
|
||||||
setActivePicker("default", documentTypeId, currentDocumentType.name || documentTypeId);
|
setActivePicker("default", documentTypeId, currentDocumentType.name || documentTypeId);
|
||||||
closeDocumentPicker();
|
closeDocumentPicker();
|
||||||
|
|
||||||
|
localStorage.setItem("utilityAppSelectedDocument", JSON.stringify({
|
||||||
|
kind: "default",
|
||||||
|
id: documentTypeId,
|
||||||
|
label: currentDocumentType.name || documentTypeId
|
||||||
|
}));
|
||||||
|
|
||||||
|
restoreSavedFormData();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadUploadedJsonOptions() {
|
async function loadUploadedJsonOptions() {
|
||||||
|
|
@ -375,6 +401,14 @@ async function loadUploadedJson(filename) {
|
||||||
loadPresetObject(result.json);
|
loadPresetObject(result.json);
|
||||||
setActivePicker("uploaded", filename, filename);
|
setActivePicker("uploaded", filename, filename);
|
||||||
closeDocumentPicker();
|
closeDocumentPicker();
|
||||||
|
|
||||||
|
localStorage.setItem("utilityAppSelectedDocument", JSON.stringify({
|
||||||
|
kind: "uploaded",
|
||||||
|
id: filename,
|
||||||
|
label: filename
|
||||||
|
}));
|
||||||
|
|
||||||
|
restoreSavedFormData();
|
||||||
setStatus(`Loaded uploaded JSON: ${filename}`);
|
setStatus(`Loaded uploaded JSON: ${filename}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -484,7 +518,9 @@ clearFormButton.addEventListener("click", () => {
|
||||||
const el = document.getElementById(field.name);
|
const el = document.getElementById(field.name);
|
||||||
if (el) el.value = "";
|
if (el) el.value = "";
|
||||||
}
|
}
|
||||||
setStatus("");
|
|
||||||
|
localStorage.removeItem(getFormStorageKey());
|
||||||
|
setStatus("Cleared form data.");
|
||||||
});
|
});
|
||||||
|
|
||||||
presetFileInput.addEventListener("change", async event => {
|
presetFileInput.addEventListener("change", async event => {
|
||||||
|
|
@ -611,3 +647,106 @@ document.addEventListener("click", event => {
|
||||||
closeDocumentPicker();
|
closeDocumentPicker();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function showView(viewId) {
|
||||||
|
document.querySelectorAll(".app-view").forEach(view => {
|
||||||
|
view.classList.toggle("active", view.id === viewId);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll(".nav-button").forEach(button => {
|
||||||
|
button.classList.toggle("active", button.dataset.view === viewId);
|
||||||
|
});
|
||||||
|
|
||||||
|
localStorage.setItem("utilityAppActiveView", viewId);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelectorAll(".nav-button").forEach(button => {
|
||||||
|
button.addEventListener("click", () => {
|
||||||
|
showView(button.dataset.view);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const mainPageContent = document.getElementById("mainPageContent");
|
||||||
|
const saveMainPageButton = document.getElementById("saveMainPageButton");
|
||||||
|
const resetMainPageButton = document.getElementById("resetMainPageButton");
|
||||||
|
|
||||||
|
function loadMainPageContent() {
|
||||||
|
const saved = localStorage.getItem("utilityAppMainPageContent");
|
||||||
|
if (saved) {
|
||||||
|
mainPageContent.innerHTML = saved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
saveMainPageButton.addEventListener("click", () => {
|
||||||
|
localStorage.setItem("utilityAppMainPageContent", mainPageContent.innerHTML);
|
||||||
|
setStatus("Saved main page text in this browser.");
|
||||||
|
});
|
||||||
|
|
||||||
|
resetMainPageButton.addEventListener("click", () => {
|
||||||
|
localStorage.removeItem("utilityAppMainPageContent");
|
||||||
|
mainPageContent.innerHTML = `
|
||||||
|
<p>Select a tool above. This main page text is editable in the browser for quick notes, instructions, or workflow reminders.</p>
|
||||||
|
<p><strong>Current tools:</strong> Document Generator and Document Processor.</p>
|
||||||
|
`;
|
||||||
|
setStatus("Reset main page text.");
|
||||||
|
});
|
||||||
|
|
||||||
|
loadMainPageContent();
|
||||||
|
|
||||||
|
|
||||||
|
function getFormStorageKey() {
|
||||||
|
const selectedRaw = localStorage.getItem("utilityAppSelectedDocument");
|
||||||
|
let selected = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
selected = selectedRaw ? JSON.parse(selectedRaw) : null;
|
||||||
|
} catch {
|
||||||
|
selected = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const kind = selected?.kind || activePickerKind || "default";
|
||||||
|
const id = selected?.id || activePickerId || currentDocumentType?.id || "unknown";
|
||||||
|
return `utilityAppFormData:${kind}:${id}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveCurrentFormData() {
|
||||||
|
if (!currentFields || currentFields.length === 0) return;
|
||||||
|
|
||||||
|
const data = getFormData(false);
|
||||||
|
localStorage.setItem(getFormStorageKey(), JSON.stringify(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
function restoreSavedFormData() {
|
||||||
|
const saved = localStorage.getItem(getFormStorageKey());
|
||||||
|
if (!saved) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(saved);
|
||||||
|
applyDataToForm(data);
|
||||||
|
} catch {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldsContainer.addEventListener("input", () => {
|
||||||
|
saveCurrentFormData();
|
||||||
|
});
|
||||||
|
|
||||||
|
fieldsContainer.addEventListener("change", () => {
|
||||||
|
saveCurrentFormData();
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener("beforeunload", () => {
|
||||||
|
saveCurrentFormData();
|
||||||
|
});
|
||||||
|
|
||||||
|
const savedActiveView = localStorage.getItem("utilityAppActiveView");
|
||||||
|
if (savedActiveView) {
|
||||||
|
showView(savedActiveView);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
document.body.classList.remove("app-loading");
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<title>Utility App</title>
|
<title>Utility App</title>
|
||||||
<link rel="stylesheet" href="/static/styles.css?v=shell1">
|
<link rel="stylesheet" href="/static/styles.css?v=shell1">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body class="app-loading">
|
||||||
<header class="app-header">
|
<header class="app-header">
|
||||||
<div class="app-header-inner">
|
<div class="app-header-inner">
|
||||||
<div class="app-title">Utility App</div>
|
<div class="app-title">Utility App</div>
|
||||||
|
|
|
||||||
|
|
@ -419,3 +419,13 @@ label.tool-action-button {
|
||||||
padding: 5px 6px;
|
padding: 5px 6px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
body.app-loading .container {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
transition: opacity 120ms ease-out;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue