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

+20 -4
View File
@@ -289,8 +289,18 @@ const viewportWidth = ref(0)
// scrub-handle behind the sticky name-spacer when the playhead has
// scrolled off the left edge of the visible ruler area.
const scrollLeft = ref(0)
const pxPerSec = ref(0)
const autoFit = ref(true)
// pxPerSec / autoFit mirror the store's editor.timeline* fields so
// the timeline zoom / fit preference round-trips with the project
// file. Thin writableComputed shims keep the rest of this
// component's code (`pxPerSec.value = …`, `autoFit.value`) unchanged.
const pxPerSec = computed<number>({
get: () => store.editor.timelinePxPerSec,
set: (v) => { store.editor.timelinePxPerSec = v },
})
const autoFit = computed<boolean>({
get: () => store.editor.timelineAutoFit,
set: (v) => { store.editor.timelineAutoFit = v },
})
function clamp(v: number, lo: number, hi: number) {
return Math.max(lo, Math.min(hi, v))
@@ -314,8 +324,14 @@ const laneWidth = computed(() => duration.value * effectivePxPerSec.value)
const totalWidth = computed(() => trackNameWidth.value + laneWidth.value)
type DisplayMode = 'seconds' | 'frames'
const displayMode = ref<DisplayMode>('seconds')
const snapEnabled = ref(true)
const displayMode = computed<DisplayMode>({
get: () => store.editor.timelineDisplayMode,
set: (v) => { store.editor.timelineDisplayMode = v },
})
const snapEnabled = computed<boolean>({
get: () => store.editor.timelineSnap,
set: (v) => { store.editor.timelineSnap = v },
})
const SNAP_PX = 8