Restore default template binding across browser sessions
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { attachScene, createLayer, cloneLayer, cloneScene, composeScene, clipTextPixels, translatedOverflow, clipBrushPixels, textClipAt, normalizeTextElement, layerToStorage } from "./scene-model.js";
|
||||
import { announce, apiBlob, isAppStale } from "./core.js";
|
||||
import { announce, apiBlob, apiJson, isAppStale } from "./core.js";
|
||||
import { readBoardSession, writeBoardSession, loadDefaultBoard, initializeDefaultBoard } from "./board-startup-model.js";
|
||||
import { renderUiCopy, renderUiValue } from "./ui-copy-runtime.js";
|
||||
import {
|
||||
RGB_BYTE_LENGTH,
|
||||
@@ -102,10 +103,61 @@ let lastExactRgb = null;
|
||||
let sceneSource = null;
|
||||
let draftConflict = false;
|
||||
let externalDraftValue = null;
|
||||
let externalDraftContext = null;
|
||||
let draftPersistenceBlocked = !draftLoad.canPersist;
|
||||
let draftProtectionNotice = (
|
||||
draftPersistenceBlocked || draftLoad.status === "cleanup-warning"
|
||||
) ? draftLoad.notice : "";
|
||||
const deviceBootId = document.querySelector('meta[name="device-boot-id"]')?.content || "";
|
||||
let boardBootId = "";
|
||||
let boardMutation = 0;
|
||||
let startupBoardPromise = null;
|
||||
if (draftLoad.canPersist && deviceBootId) {
|
||||
try {
|
||||
const session = readBoardSession(draftStorage, serializeScene(scene), deviceBootId);
|
||||
boardBootId = session.bootId || "";
|
||||
templateEdit = session.context;
|
||||
if (templateEdit && !deserializeScene(templateEdit.baseline)) throw new Error("画板保存基线无效;原草稿已保留。");
|
||||
if (templateEdit && !sceneNeedsTemplateSave(templateEdit, serializeScene(scene)) && templateEdit.type === "template") {
|
||||
sceneSource = { type: "template", id: templateEdit.id, revision: templateEdit.revision };
|
||||
}
|
||||
} catch (error) {
|
||||
templateEdit = null;
|
||||
draftPersistenceBlocked = true;
|
||||
draftProtectionNotice = error.message;
|
||||
}
|
||||
}
|
||||
|
||||
export function initializeStartupBoard() {
|
||||
if (startupBoardPromise) return startupBoardPromise;
|
||||
const mutation = boardMutation;
|
||||
const canLoad = () => Boolean(deviceBootId) && boardBootId !== deviceBootId
|
||||
&& !draftPersistenceBlocked && !draftConflict && !animationEdit && boardMutation === mutation;
|
||||
const retain = () => {
|
||||
if (draftPersistenceBlocked || draftConflict || animationEdit) return;
|
||||
boardBootId = deviceBootId;
|
||||
persistScene();
|
||||
};
|
||||
startupBoardPromise = initializeDefaultBoard({
|
||||
canLoad,
|
||||
load: () => loadDefaultBoard(apiJson),
|
||||
apply: (content) => {
|
||||
// Validate before changing either the session marker or the current draft.
|
||||
if (!deserializeScene(content.scene)) throw new Error("默认画面场景无效");
|
||||
boardBootId = deviceBootId;
|
||||
replaceSceneFromStorage(content.scene, {
|
||||
reason: "startup-default-load",
|
||||
source: content.type === "template" ? { type: "template", id: content.id, revision: content.revision } : null,
|
||||
templateEditContext: content,
|
||||
});
|
||||
},
|
||||
retain,
|
||||
}).catch((error) => {
|
||||
announce(`默认画面读取失败,已保留当前草稿:${error.message}`, true);
|
||||
return false;
|
||||
});
|
||||
return startupBoardPromise;
|
||||
}
|
||||
|
||||
const draftConflictNotice = document.getElementById("draft-conflict-notice");
|
||||
const loadExternalDraftButton = document.getElementById("load-external-draft");
|
||||
@@ -122,6 +174,7 @@ function blockDraftPersistence(message) {
|
||||
}
|
||||
|
||||
export function persistScene() {
|
||||
boardMutation += 1;
|
||||
if (animationEdit) {
|
||||
animationEdit.dirty = serializeScene(scene) !== animationEdit.baseline;
|
||||
updateBoardUi();
|
||||
@@ -131,7 +184,10 @@ export function persistScene() {
|
||||
if (draftConflict || draftPersistenceBlocked) return false;
|
||||
try {
|
||||
if (!draftStorage) throw new Error("browser storage unavailable");
|
||||
draftStorage.setItem(SCENE_STORAGE_KEY, serializeScene(scene));
|
||||
const draft = serializeScene(scene);
|
||||
// Commit metadata first: a torn pair never restores the wrong binding.
|
||||
writeBoardSession(draftStorage, boardBootId, draft, templateEdit);
|
||||
draftStorage.setItem(SCENE_STORAGE_KEY, draft);
|
||||
return true;
|
||||
} catch (_error) {
|
||||
blockDraftPersistence("浏览器无法保存统一画板草稿;原草稿已保留,本页不会继续覆盖或应用它。");
|
||||
@@ -157,6 +213,12 @@ window.addEventListener("storage", (event) => {
|
||||
return;
|
||||
}
|
||||
if (external.status !== "valid") return;
|
||||
try {
|
||||
externalDraftContext = readBoardSession(draftStorage, event.newValue, deviceBootId).context;
|
||||
} catch (error) {
|
||||
blockDraftPersistence(error.message);
|
||||
return;
|
||||
}
|
||||
externalDraftValue = event.newValue;
|
||||
setDraftConflict(true);
|
||||
announce("另一标签页修改了画板草稿,请选择要保留的版本", true);
|
||||
@@ -164,11 +226,12 @@ window.addEventListener("storage", (event) => {
|
||||
|
||||
loadExternalDraftButton.addEventListener("click", async () => {
|
||||
const value = externalDraftValue;
|
||||
const context = externalDraftContext;
|
||||
if (!value) return;
|
||||
if (animationEdit && !await sceneReplacementGuard()) return;
|
||||
externalDraftValue = null;
|
||||
setDraftConflict(false);
|
||||
replaceSceneFromStorage(value, { persist: false, reason: "tab-sync" });
|
||||
replaceSceneFromStorage(value, { persist: false, reason: "tab-sync", templateEditContext: context });
|
||||
announce("已载入另一标签页的最新画板草稿");
|
||||
});
|
||||
|
||||
@@ -670,6 +733,7 @@ export function replaceSceneFromStorage(
|
||||
templateEditContext = null,
|
||||
} = {},
|
||||
) {
|
||||
boardMutation += 1;
|
||||
const restored = deserializeScene(value);
|
||||
if (!restored) throw new Error("模板场景无效,无法载入");
|
||||
textLayers.forEach((entry) => {
|
||||
@@ -719,6 +783,8 @@ export function markTemplateSaved(template) {
|
||||
clearEditHistory();
|
||||
templateEdit = createTemplateEditContext(template, serializeScene(scene));
|
||||
sceneSource = { type: "template", id: template.id, revision: template.revision };
|
||||
boardBootId = deviceBootId;
|
||||
persistScene();
|
||||
updateBoardUi();
|
||||
document.dispatchEvent(new CustomEvent("matrix:template-edit-change", { detail: templateEditState() }));
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user