1.0.2: persist editor-session UI state with the project

Canvas zoom + pan, crop / mask-preview / stage-preview / centre-
crosshair overlays, and timeline snap / show-locked / displayMode
/ px-per-sec / auto-fit now ride along in the saved project under
a new optional `editor` block. Reopening a project restores the
workbench exactly how the user left it — zoom level, pan offset,
HUD toggles, timeline density — instead of snapping back to
defaults.

Editor state lives on the store as a sibling of `this.project`
rather than inside it, so `JSON.stringify(store.project)` (the
undo/redo snapshot) still only captures content. The new
`projectForSave` getter stitches the two on the way to disk;
`loadProject` reads them back apart on the way in. Saves through
App.vue, the rename flow, and IDB recents all go through
`projectForSave`.

CanvasStage skips its first auto-fit when a non-default
canvas zoom/pan was restored so the user's exact view survives
mount. Frame-based export options pick up the same source of
truth for a consistent starting point.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
thandClaude Opus 4.7 committed 2026-04-24 19:47:44 +02:00
1 parent cd9cf91ef0
commit 647f09b287
9 files changed
+285 -19

No files matched your search

+45 -8
View File
@@ -52,19 +52,45 @@ const stageRef = ref<{ getStage(): Konva.Stage } | null>(null)
const bgVideoEl = ref<HTMLVideoElement | null>(null)
const transformerRef = ref<{ getNode(): Konva.Transformer } | null>(null)
const zoom = ref(1)
const viewX = ref(0)
const viewY = ref(0)
const cropObjects = ref(false)
const maskPreview = ref(false)
// Canvas zoom / pan / overlay toggles live on the store so the
// project file can round-trip the workbench state. Thin
// writableComputed shims keep the rest of this component's code
// (which reads/writes `zoom.value`, `cropObjects.value = …`, etc.)
// unchanged.
const zoom = computed<number>({
get: () => store.editor.canvasZoom,
set: (v) => { store.editor.canvasZoom = v },
})
const viewX = computed<number>({
get: () => store.editor.canvasViewX,
set: (v) => { store.editor.canvasViewX = v },
})
const viewY = computed<number>({
get: () => store.editor.canvasViewY,
set: (v) => { store.editor.canvasViewY = v },
})
const cropObjects = computed<boolean>({
get: () => store.editor.cropObjects,
set: (v) => { store.editor.cropObjects = v },
})
const maskPreview = computed<boolean>({
get: () => store.editor.maskPreview,
set: (v) => { store.editor.maskPreview = v },
})
// Dashed horizontal + vertical lines through the center of the
// effective canvas — a composition guide the user can toggle on/off
// from the HUD. Not persisted; local to this editor session.
const centerCrosshair = ref(false)
// from the HUD.
const centerCrosshair = computed<boolean>({
get: () => store.editor.centerCrosshair,
set: (v) => { store.editor.centerCrosshair = v },
})
// Stage-preview mode: when false (default) buttons behave like normal
// assets — draggable, no hover/click interaction. When true the canvas
// turns into a live preview with buttons responding to input.
const stagePreview = ref(false)
const stagePreview = computed<boolean>({
get: () => store.editor.stagePreview,
set: (v) => { store.editor.stagePreview = v },
})
const previewCanvasEl = ref<HTMLCanvasElement | null>(null)
const previewImages = ref<Record<string, HTMLImageElement>>({})
let didInitialFit = false
@@ -3039,6 +3065,17 @@ function zoomBy(factor: number) {
viewY.value = sy - logY * newZoom
}
// Project-loaded zoom/pan? Treat a non-default editor state as an
// explicit user choice and skip the first auto-fit so the workbench
// restores exactly where the user left off.
if (
store.editor.canvasZoom !== 1 ||
store.editor.canvasViewX !== 0 ||
store.editor.canvasViewY !== 0
) {
didInitialFit = true
}
onMounted(() => {
if (!container.value) return
ro = new ResizeObserver((entries) => {