Projektstand liegt im Repo statt nur im Browser
Der Stand hing am localStorage und damit an einem Rechner. Wer das Projekt woanders zog, sah einen anderen Stand. Jede Aenderung geht jetzt zusaetzlich in state/<projekt>.json, gebuendelt nach 600 ms. Beim Laden gewinnt die Datei, der localStorage ist nur noch der Rueckfall fuer den Fall, dass der Server nicht laeuft. Gibt es die Datei noch nicht, wird der vorhandene Browser-Stand einmalig hineingeschrieben. Ein Reload verschluckt nichts: was im Timer haengt, geht per sendBeacon raus. Der Server nimmt den Stand unter POST /state entgegen und loescht ihn unter DELETE /state. Der Projektname wird gegen ein enges Muster geprueft und der Zielpfad zusaetzlich gegen state/ gegengerechnet. Geschrieben wird erst in eine Nebendatei und dann umbenannt, eingerueckt und mit sortierten Schluesseln, damit git einen lesbaren Unterschied zeigt. state/*.json gehoeren ab jetzt in jeden Commit, sonst sieht die andere Maschine die Aenderung nicht. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
f046f6d6f0
commit
1359c6eba8
6 files changed
+420
-47
No files matched your search
+62
-13
@@ -1025,20 +1025,64 @@
|
||||
const PROJECT = "post1-alt";
|
||||
const STORE = "hivegrid.projekt." + PROJECT;
|
||||
|
||||
function saveState() {
|
||||
try {
|
||||
localStorage.setItem(STORE, JSON.stringify({
|
||||
v: 1, variant, tilt, superSample, continuous, view, tagOut, frameStyle,
|
||||
current, layoutState, bgState, edits,
|
||||
}));
|
||||
markSaved();
|
||||
} catch (err) { console.warn("Speichern fehlgeschlagen", err); }
|
||||
const STATE_URL = "./state/" + PROJECT + ".json";
|
||||
|
||||
function currentState() {
|
||||
return {
|
||||
v: 1, variant, tilt, superSample, continuous, view, tagOut, frameStyle,
|
||||
current, layoutState, bgState, edits,
|
||||
};
|
||||
}
|
||||
|
||||
function loadState() {
|
||||
let d = null;
|
||||
try { d = JSON.parse(localStorage.getItem(STORE) || "null"); } catch (_) {}
|
||||
/* Der Stand geht in zwei Ablagen. Sofort in den localStorage, damit die
|
||||
Rückmeldung nicht auf das Netz wartet, und gebündelt in
|
||||
state/<projekt>.json. Nur die Datei lässt sich pushen, also ist sie die
|
||||
Wahrheit; der localStorage ist der Rückfall, wenn der Server nicht läuft. */
|
||||
let pushTimer = null;
|
||||
function saveState() {
|
||||
const state = currentState();
|
||||
try { localStorage.setItem(STORE, JSON.stringify(state)); }
|
||||
catch (err) { console.warn("Speichern fehlgeschlagen", err); }
|
||||
markSaved();
|
||||
clearTimeout(pushTimer);
|
||||
pushTimer = setTimeout(() => pushState(state), 600);
|
||||
}
|
||||
|
||||
async function pushState(state) {
|
||||
pushTimer = null;
|
||||
try {
|
||||
const res = await fetch("/state?project=" + PROJECT, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(state),
|
||||
});
|
||||
if (!res.ok) throw new Error("HTTP " + res.status);
|
||||
} catch (err) { console.warn("Stand nicht in die Datei geschrieben", err); }
|
||||
}
|
||||
|
||||
// Ein Reload darf keine Änderung verschlucken, die noch im Timer hängt.
|
||||
addEventListener("beforeunload", () => {
|
||||
if (!pushTimer) return;
|
||||
clearTimeout(pushTimer);
|
||||
navigator.sendBeacon("/state?project=" + PROJECT,
|
||||
new Blob([JSON.stringify(currentState())], { type: "application/json" }));
|
||||
});
|
||||
|
||||
async function loadState() {
|
||||
let d = null, fromFile = false;
|
||||
// Erst die Datei aus dem Repo, dann der Browser. Wer das Projekt frisch
|
||||
// gezogen hat, soll den gepushten Stand sehen und nicht seinen eigenen alten.
|
||||
try {
|
||||
const res = await fetch(STATE_URL, { cache: "no-store" });
|
||||
if (res.ok) { d = await res.json(); fromFile = true; }
|
||||
} catch (_) {}
|
||||
if (!d) {
|
||||
try { d = JSON.parse(localStorage.getItem(STORE) || "null"); } catch (_) {}
|
||||
}
|
||||
if (!d || d.v !== 1) return false;
|
||||
// Stand aus dem Browser, aber noch keine Datei: einmalig hinueberschreiben,
|
||||
// sonst bliebe ein ueber Wochen gewachsener Stand ungeteilt liegen.
|
||||
if (!fromFile) setTimeout(() => pushState(currentState()), 0);
|
||||
// Nur übernehmen, was zur aktuellen Slide-Anzahl passt: Änderungen am
|
||||
// Aufbau dürfen keinen kaputten Stand einspielen.
|
||||
if (Array.isArray(d.layoutState) && d.layoutState.length === SLIDES.length) {
|
||||
@@ -1072,8 +1116,13 @@
|
||||
}
|
||||
|
||||
function forgetProject() {
|
||||
clearTimeout(pushTimer); pushTimer = null;
|
||||
try { localStorage.removeItem(STORE); } catch (_) {}
|
||||
location.reload();
|
||||
// Auch die Datei muss weg, sonst spielt sie den alten Stand beim nächsten
|
||||
// Laden wieder ein.
|
||||
fetch("/state?project=" + PROJECT, { method: "DELETE" })
|
||||
.catch(() => {})
|
||||
.then(() => location.reload());
|
||||
}
|
||||
// Per-slide so a single slide can carry a different ground.
|
||||
const bgState = SLIDES.map(() => ({ bg: "formen", int: "dezent" }));
|
||||
@@ -2124,7 +2173,7 @@
|
||||
try {
|
||||
setStatus("Lade Screens ...");
|
||||
await preloadAssets();
|
||||
const restored = loadState();
|
||||
const restored = await loadState();
|
||||
buildDeck();
|
||||
deck.dataset.view = view;
|
||||
press("variants", "v", variant);
|
||||
|
||||
Reference in new issue
Block a user