@hyperframes/studio 0.8.75 → 0.8.77

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/dist/assets/hyperframes-player-BSDTJ9EF.js +464 -0
  2. package/dist/assets/{index-BlUYw1MB.js → index-2GgHjQq-.js} +163 -163
  3. package/dist/assets/{index-0w6iIlwZ.css → index-BWJqxeaH.css} +1 -1
  4. package/dist/assets/{index-BiFKcNjs.js → index-CsFCRdMo.js} +1 -1
  5. package/dist/assets/{index-HjexPsny.js → index-DcxWQ7Za.js} +1 -1
  6. package/dist/{chunk-7BP32G62.js → chunk-5VANA2TG.js} +12 -1
  7. package/dist/chunk-5VANA2TG.js.map +1 -0
  8. package/dist/{domEditingLayers-UKMVYXT6.js → domEditingLayers-IGPLTI5E.js} +4 -2
  9. package/dist/index.d.ts +34 -69
  10. package/dist/index.html +2 -2
  11. package/dist/index.js +5287 -5277
  12. package/dist/index.js.map +1 -1
  13. package/dist/styles.css +1 -1
  14. package/package.json +7 -7
  15. package/src/components/StudioRightPanels.types.ts +0 -2
  16. package/src/components/editor/DomEditOverlay.test.ts +5 -0
  17. package/src/components/editor/LayersPanel.tsx +3 -14
  18. package/src/components/editor/PreviewOverlayProvider.test.tsx +6 -1
  19. package/src/components/editor/domEditing.ts +1 -0
  20. package/src/components/editor/domEditingElement.ts +13 -0
  21. package/src/components/editor/domEditingLayers.test.ts +27 -0
  22. package/src/components/editor/domEditingLayers.ts +9 -0
  23. package/src/components/editor/gsapLivePreview.test.ts +63 -0
  24. package/src/components/editor/gsapLivePreview.ts +2 -14
  25. package/src/components/editor/offCanvasIndicatorRefresh.test.tsx +57 -35
  26. package/src/components/editor/overlayFrameLoop.test.ts +16 -0
  27. package/src/components/editor/overlayFrameLoop.ts +11 -0
  28. package/src/components/editor/propertyPanelHelpers.ts +4 -6
  29. package/src/components/editor/usePreviewCompositionRect.test.tsx +6 -1
  30. package/src/components/nle/NLEPreview.test.ts +202 -22
  31. package/src/components/nle/NLEPreview.tsx +65 -46
  32. package/src/components/nle/PreviewPoster.tsx +99 -0
  33. package/src/components/nle/PreviewZoomOverlay.tsx +111 -0
  34. package/src/components/nle/previewZoom.test.ts +42 -0
  35. package/src/components/nle/previewZoom.ts +33 -0
  36. package/src/components/nle/useCanvasZOrderTimelineMirror.test.tsx +30 -49
  37. package/src/components/panels/VariablesPanel.tsx +0 -2
  38. package/src/components/sidebar/CompositionsTab.drag.test.tsx +5 -1
  39. package/src/components/sidebar/CompositionsTab.rootBadge.test.tsx +14 -0
  40. package/src/components/sidebar/CompositionsTab.test.ts +4 -6
  41. package/src/components/sidebar/CompositionsTab.tsx +28 -21
  42. package/src/components/studioColorGradingScope.ts +0 -3
  43. package/src/hooks/domEditDeleteMembers.ts +0 -2
  44. package/src/hooks/gsapScriptCommitTypes.ts +0 -2
  45. package/src/hooks/keyframeCacheAstLoad.test.ts +31 -1
  46. package/src/hooks/keyframeCacheAstLoad.ts +2 -1
  47. package/src/hooks/timelineAudioGroupCreate.ts +0 -1
  48. package/src/hooks/timelineEditingHelpers.ts +0 -3
  49. package/src/hooks/timelineTimingSync.ts +0 -1
  50. package/src/hooks/timelineTrackVisibility.ts +0 -1
  51. package/src/hooks/useBlockHandlers.ts +0 -2
  52. package/src/hooks/useClipboard.test.ts +69 -10
  53. package/src/hooks/useClipboard.ts +28 -19
  54. package/src/hooks/useDomEditCommits.test.tsx +0 -1
  55. package/src/hooks/useDomEditCommits.ts +0 -4
  56. package/src/hooks/useDomEditWiring.ts +1 -3
  57. package/src/hooks/useEditHistoryActions.test.tsx +31 -14
  58. package/src/hooks/useEditHistoryActions.ts +12 -7
  59. package/src/hooks/useEditorSave.ts +0 -3
  60. package/src/hooks/useElementLifecycleOps.ts +0 -1
  61. package/src/hooks/useEnableKeyframes.test.ts +9 -1
  62. package/src/hooks/useFileManager.ts +0 -2
  63. package/src/hooks/useGestureRecording.test.tsx +1 -1
  64. package/src/hooks/useGestureRecording.ts +6 -10
  65. package/src/hooks/useGroupCommits.ts +0 -1
  66. package/src/hooks/useGsapScriptCommits.ts +0 -1
  67. package/src/hooks/useGsapTweenCache.element.test.tsx +39 -0
  68. package/src/hooks/useGsapTweenCache.ts +5 -13
  69. package/src/hooks/useLintModal.test.tsx +34 -1
  70. package/src/hooks/useLintModal.ts +32 -30
  71. package/src/hooks/usePersistentEditHistory.test.ts +221 -315
  72. package/src/hooks/usePersistentEditHistory.ts +155 -342
  73. package/src/hooks/usePopulateKeyframeCacheForFile.test.tsx +2 -0
  74. package/src/hooks/usePreviewPersistence.ts +0 -2
  75. package/src/hooks/useProjectCompositionVariables.ts +1 -3
  76. package/src/hooks/useRazorSplit.history.test.tsx +46 -67
  77. package/src/hooks/useRazorSplit.test.ts +17 -47
  78. package/src/hooks/useRenderClipContent.test.ts +29 -0
  79. package/src/hooks/useRenderClipContent.ts +6 -3
  80. package/src/hooks/useSdkSession.lifecycle.test.tsx +34 -4
  81. package/src/hooks/useSdkSession.ts +8 -2
  82. package/src/hooks/useSlideshowPersist.ts +0 -2
  83. package/src/hooks/useStudioContextValue.ts +6 -1
  84. package/src/hooks/useTimelineAssetDropOps.ts +0 -1
  85. package/src/hooks/useTimelineDeleteOps.ts +0 -1
  86. package/src/hooks/useTimelineEditing.test.tsx +0 -1
  87. package/src/hooks/useTimelineEditingTypes.ts +0 -2
  88. package/src/hostMount.test.tsx +53 -22
  89. package/src/main.tsx +3 -0
  90. package/src/player/components/CompositionThumbnail.tsx +14 -0
  91. package/src/player/components/Player.test.ts +49 -0
  92. package/src/player/components/Player.tsx +21 -7
  93. package/src/player/components/timelineClipDragCommit.test.ts +7 -39
  94. package/src/player/hooks/timelineDomBinding.test.ts +50 -0
  95. package/src/player/hooks/timelineSyncHydration.test.ts +25 -1
  96. package/src/player/hooks/timelineSyncHydration.ts +3 -3
  97. package/src/player/hooks/useShadowPreviewReload.ts +1 -0
  98. package/src/player/hooks/useTimelinePlayer.play.test.ts +120 -0
  99. package/src/player/hooks/useTimelinePlayer.ts +1 -0
  100. package/src/player/hooks/useTimelineSyncCallbacks.ts +2 -0
  101. package/src/player/lib/automationStoreSync.test.ts +24 -0
  102. package/src/player/lib/automationStoreSync.ts +3 -9
  103. package/src/player/lib/playbackScrub.ts +1 -1
  104. package/src/player/lib/timelineDOM.test.ts +28 -0
  105. package/src/player/lib/timelineDOM.ts +4 -2
  106. package/src/player/lib/timelineElementHelpers.ts +78 -9
  107. package/src/player/lib/timelineIframeHelpers.test.ts +49 -5
  108. package/src/player/lib/timelineIframeHelpers.ts +13 -12
  109. package/src/player/store/playerStore.test.ts +35 -1
  110. package/src/player/store/playerStore.ts +29 -0
  111. package/src/player/store/readinessSlice.ts +34 -6
  112. package/src/utils/blockInstaller.ts +0 -3
  113. package/src/utils/previewPrefetch.test.ts +22 -0
  114. package/src/utils/previewPrefetch.ts +14 -0
  115. package/src/utils/razorSplitTransaction.test.ts +0 -1
  116. package/src/utils/razorSplitTransaction.ts +1 -1
  117. package/src/utils/sdkEditTransaction.ts +0 -3
  118. package/src/utils/studioFileHistory.test.ts +0 -9
  119. package/src/utils/studioFileHistory.ts +1 -5
  120. package/src/utils/studioUiPreferences.test.ts +9 -3
  121. package/src/utils/studioUiPreferences.ts +0 -20
  122. package/src/utils/timelineCompositionInsert.ts +0 -1
  123. package/dist/assets/hyperframes-player-WbBVbWuO.js +0 -464
  124. package/dist/chunk-7BP32G62.js.map +0 -1
  125. package/src/hooks/usePersistentEditHistory.projectOwnership.test.tsx +0 -60
  126. package/src/utils/editHistory.test.ts +0 -534
  127. package/src/utils/editHistory.ts +0 -236
  128. package/src/utils/editHistoryStorage.test.ts +0 -37
  129. package/src/utils/editHistoryStorage.ts +0 -99
  130. /package/dist/{domEditingLayers-UKMVYXT6.js.map → domEditingLayers-IGPLTI5E.js.map} +0 -0
@@ -1,236 +0,0 @@
1
- export type EditHistoryKind = "manual" | "motion" | "timeline" | "source";
2
-
3
- export interface EditHistoryFileSnapshot {
4
- before: string;
5
- after: string;
6
- beforeHash: string;
7
- afterHash: string;
8
- }
9
-
10
- export interface EditHistoryEntry {
11
- id: string;
12
- projectId: string;
13
- label: string;
14
- kind: EditHistoryKind;
15
- coalesceKey?: string;
16
- /** Per-entry coalesce window override (ms). Falls back to the reducer default. */
17
- coalesceMs?: number;
18
- createdAt: number;
19
- files: Record<string, EditHistoryFileSnapshot>;
20
- }
21
-
22
- export interface EditHistoryState {
23
- version: 1;
24
- updatedAt: number;
25
- undo: EditHistoryEntry[];
26
- redo: EditHistoryEntry[];
27
- }
28
-
29
- export interface EditHistoryOptions {
30
- maxEntries?: number;
31
- coalesceMs?: number;
32
- }
33
-
34
- export interface BuildEditHistoryEntryInput {
35
- id: string;
36
- projectId: string;
37
- label: string;
38
- kind?: EditHistoryKind;
39
- coalesceKey?: string;
40
- coalesceMs?: number;
41
- now: number;
42
- files: Record<string, { before: string; after: string }>;
43
- }
44
-
45
- export type EditHistoryDirection = "undo" | "redo";
46
-
47
- export type EditHistoryApplyCheck =
48
- | { ok: true }
49
- | { ok: false; reason: "content-mismatch"; path: string };
50
-
51
- export type EditHistoryTransitionResult =
52
- | {
53
- ok: true;
54
- state: EditHistoryState;
55
- entry: EditHistoryEntry;
56
- filesToWrite: Record<string, string>;
57
- }
58
- | {
59
- ok: false;
60
- reason: "empty" | "content-mismatch";
61
- state: EditHistoryState;
62
- filesToWrite: Record<string, string>;
63
- path?: string;
64
- };
65
-
66
- const DEFAULT_MAX_ENTRIES = 100;
67
- const DEFAULT_COALESCE_MS = 300;
68
-
69
- export function hashEditHistoryContent(content: string): string {
70
- let hash = 2166136261;
71
- for (let index = 0; index < content.length; index += 1) {
72
- hash ^= content.charCodeAt(index);
73
- hash = Math.imul(hash, 16777619);
74
- }
75
- return (hash >>> 0).toString(16).padStart(8, "0");
76
- }
77
-
78
- export function createEmptyEditHistory(_options?: EditHistoryOptions): EditHistoryState {
79
- return {
80
- version: 1,
81
- updatedAt: 0,
82
- undo: [],
83
- redo: [],
84
- };
85
- }
86
-
87
- export function buildEditHistoryEntry(input: BuildEditHistoryEntryInput): EditHistoryEntry {
88
- const files: Record<string, EditHistoryFileSnapshot> = {};
89
- for (const [path, snapshot] of Object.entries(input.files)) {
90
- if (snapshot.before === snapshot.after) continue;
91
- files[path] = {
92
- before: snapshot.before,
93
- after: snapshot.after,
94
- beforeHash: hashEditHistoryContent(snapshot.before),
95
- afterHash: hashEditHistoryContent(snapshot.after),
96
- };
97
- }
98
-
99
- return {
100
- id: input.id,
101
- projectId: input.projectId,
102
- label: input.label,
103
- kind: input.kind ?? "manual",
104
- coalesceKey: input.coalesceKey,
105
- coalesceMs: input.coalesceMs,
106
- createdAt: input.now,
107
- files,
108
- };
109
- }
110
-
111
- export function pushEditHistoryEntry(
112
- state: EditHistoryState,
113
- entry: EditHistoryEntry,
114
- options?: EditHistoryOptions,
115
- ): EditHistoryState {
116
- if (Object.keys(entry.files).length === 0) return state;
117
-
118
- // The incoming entry's own window wins so a caller can guarantee a merge even when a
119
- // slow async step (e.g. a server GSAP rewrite) sits between the two records.
120
- const coalesceMs = entry.coalesceMs ?? options?.coalesceMs ?? DEFAULT_COALESCE_MS;
121
- const maxEntries = options?.maxEntries ?? DEFAULT_MAX_ENTRIES;
122
- const previous = state.undo[state.undo.length - 1];
123
- let undo = state.undo;
124
-
125
- if (
126
- previous &&
127
- previous.coalesceKey &&
128
- previous.coalesceKey === entry.coalesceKey &&
129
- entry.createdAt - previous.createdAt <= coalesceMs &&
130
- // A shared key/window is not enough: bytes from an external writer may
131
- // have landed between the two Studio-owned edits. Only fold a path when
132
- // the follow-up starts from the exact output the previous entry recorded.
133
- // Otherwise keep both entries so undo restores the foreign bytes first and
134
- // the older entry's content check safely blocks at that ownership boundary.
135
- Object.entries(entry.files).every(([path, snapshot]) => {
136
- const previousSnapshot = previous.files[path];
137
- return !previousSnapshot || previousSnapshot.after === snapshot.before;
138
- })
139
- ) {
140
- // A follow-up may touch only a subset of the files in the original edit
141
- // (for example, only one file in a multi-file timing move has GSAP). Keep
142
- // previous-only paths in the single coalesced undo entry.
143
- const files: Record<string, EditHistoryFileSnapshot> = { ...previous.files };
144
- for (const [path, snapshot] of Object.entries(entry.files)) {
145
- const previousSnapshot = previous.files[path];
146
- files[path] = previousSnapshot
147
- ? {
148
- before: previousSnapshot.before,
149
- after: snapshot.after,
150
- beforeHash: previousSnapshot.beforeHash,
151
- afterHash: snapshot.afterHash,
152
- }
153
- : snapshot;
154
- }
155
- undo = [...state.undo.slice(0, -1), { ...entry, files }];
156
- } else {
157
- undo = [...state.undo, entry];
158
- }
159
-
160
- return {
161
- version: 1,
162
- updatedAt: entry.createdAt,
163
- undo: undo.slice(Math.max(0, undo.length - maxEntries)),
164
- redo: [],
165
- };
166
- }
167
-
168
- export function canApplyEditHistoryEntry(
169
- entry: EditHistoryEntry,
170
- direction: EditHistoryDirection,
171
- currentHashes: Record<string, string>,
172
- ): EditHistoryApplyCheck {
173
- for (const [path, snapshot] of Object.entries(entry.files)) {
174
- const expected = direction === "undo" ? snapshot.afterHash : snapshot.beforeHash;
175
- if (currentHashes[path] !== expected) {
176
- return { ok: false, reason: "content-mismatch", path };
177
- }
178
- }
179
- return { ok: true };
180
- }
181
-
182
- export function undoEditHistory(
183
- state: EditHistoryState,
184
- currentHashes: Record<string, string>,
185
- now: number,
186
- ): EditHistoryTransitionResult {
187
- const entry = state.undo[state.undo.length - 1];
188
- if (!entry) return { ok: false, reason: "empty", state, filesToWrite: {} };
189
-
190
- const check = canApplyEditHistoryEntry(entry, "undo", currentHashes);
191
- if (!check.ok) {
192
- return { ok: false, reason: check.reason, path: check.path, state, filesToWrite: {} };
193
- }
194
-
195
- return {
196
- ok: true,
197
- entry,
198
- filesToWrite: Object.fromEntries(
199
- Object.entries(entry.files).map(([path, snapshot]) => [path, snapshot.before]),
200
- ),
201
- state: {
202
- version: 1,
203
- updatedAt: now,
204
- undo: state.undo.slice(0, -1),
205
- redo: [...state.redo, entry],
206
- },
207
- };
208
- }
209
-
210
- export function redoEditHistory(
211
- state: EditHistoryState,
212
- currentHashes: Record<string, string>,
213
- now: number,
214
- ): EditHistoryTransitionResult {
215
- const entry = state.redo[state.redo.length - 1];
216
- if (!entry) return { ok: false, reason: "empty", state, filesToWrite: {} };
217
-
218
- const check = canApplyEditHistoryEntry(entry, "redo", currentHashes);
219
- if (!check.ok) {
220
- return { ok: false, reason: check.reason, path: check.path, state, filesToWrite: {} };
221
- }
222
-
223
- return {
224
- ok: true,
225
- entry,
226
- filesToWrite: Object.fromEntries(
227
- Object.entries(entry.files).map(([path, snapshot]) => [path, snapshot.after]),
228
- ),
229
- state: {
230
- version: 1,
231
- updatedAt: now,
232
- undo: [...state.undo, entry],
233
- redo: state.redo.slice(0, -1),
234
- },
235
- };
236
- }
@@ -1,37 +0,0 @@
1
- import { beforeEach, describe, expect, it } from "vitest";
2
- import { buildEditHistoryEntry, createEmptyEditHistory, pushEditHistoryEntry } from "./editHistory";
3
- import {
4
- createMemoryEditHistoryStorage,
5
- loadEditHistoryState,
6
- saveEditHistoryState,
7
- } from "./editHistoryStorage";
8
-
9
- describe("edit history storage", () => {
10
- let storage: ReturnType<typeof createMemoryEditHistoryStorage>;
11
-
12
- beforeEach(() => {
13
- storage = createMemoryEditHistoryStorage();
14
- });
15
-
16
- it("returns empty history for projects without persisted state", async () => {
17
- const state = await loadEditHistoryState(storage, "project-1");
18
-
19
- expect(state).toEqual(createEmptyEditHistory());
20
- });
21
-
22
- it("saves and loads history per project", async () => {
23
- const entry = buildEditHistoryEntry({
24
- id: "entry-1",
25
- projectId: "project-1",
26
- label: "Move layer",
27
- files: { "index.html": { before: "a", after: "b" } },
28
- now: 100,
29
- });
30
- const state = pushEditHistoryEntry(createEmptyEditHistory(), entry);
31
-
32
- await saveEditHistoryState(storage, "project-1", state);
33
-
34
- expect(await loadEditHistoryState(storage, "project-1")).toEqual(state);
35
- expect(await loadEditHistoryState(storage, "project-2")).toEqual(createEmptyEditHistory());
36
- });
37
- });
@@ -1,99 +0,0 @@
1
- import { createEmptyEditHistory, type EditHistoryState } from "./editHistory";
2
-
3
- export interface EditHistoryStorageAdapter {
4
- get(projectId: string): Promise<EditHistoryState | null>;
5
- set(projectId: string, state: EditHistoryState): Promise<void>;
6
- delete(projectId: string): Promise<void>;
7
- }
8
-
9
- const DB_NAME = "hyperframes-studio-edit-history";
10
- const DB_VERSION = 1;
11
- const STORE_NAME = "project-history";
12
-
13
- export function createMemoryEditHistoryStorage(): EditHistoryStorageAdapter {
14
- const states = new Map<string, EditHistoryState>();
15
- return {
16
- async get(projectId) {
17
- return states.get(projectId) ?? null;
18
- },
19
- async set(projectId, state) {
20
- states.set(projectId, structuredClone(state));
21
- },
22
- async delete(projectId) {
23
- states.delete(projectId);
24
- },
25
- };
26
- }
27
-
28
- function openEditHistoryDb(): Promise<IDBDatabase> {
29
- return new Promise((resolve, reject) => {
30
- if (!globalThis.indexedDB) {
31
- reject(new Error("IndexedDB is not available"));
32
- return;
33
- }
34
-
35
- const request = globalThis.indexedDB.open(DB_NAME, DB_VERSION);
36
- request.onupgradeneeded = () => {
37
- const db = request.result;
38
- if (!db.objectStoreNames.contains(STORE_NAME)) {
39
- db.createObjectStore(STORE_NAME);
40
- }
41
- };
42
- request.onerror = () => reject(request.error ?? new Error("Failed to open edit history db"));
43
- request.onsuccess = () => resolve(request.result);
44
- });
45
- }
46
-
47
- function withStore<T>(
48
- mode: IDBTransactionMode,
49
- callback: (store: IDBObjectStore) => IDBRequest<T>,
50
- ): Promise<T> {
51
- return openEditHistoryDb().then(
52
- (db) =>
53
- new Promise<T>((resolve, reject) => {
54
- const tx = db.transaction(STORE_NAME, mode);
55
- const request = callback(tx.objectStore(STORE_NAME));
56
- request.onerror = () => reject(request.error ?? new Error("IndexedDB request failed"));
57
- request.onsuccess = () => resolve(request.result);
58
- tx.oncomplete = () => db.close();
59
- tx.onerror = () => {
60
- db.close();
61
- reject(tx.error ?? new Error("IndexedDB transaction failed"));
62
- };
63
- }),
64
- );
65
- }
66
-
67
- export function createIndexedDbEditHistoryStorage(): EditHistoryStorageAdapter {
68
- return {
69
- async get(projectId) {
70
- return (
71
- (await withStore<EditHistoryState | undefined>("readonly", (store) =>
72
- store.get(projectId),
73
- )) ?? null
74
- );
75
- },
76
- async set(projectId, state) {
77
- await withStore<IDBValidKey>("readwrite", (store) => store.put(state, projectId));
78
- },
79
- async delete(projectId) {
80
- await withStore<undefined>("readwrite", (store) => store.delete(projectId));
81
- },
82
- };
83
- }
84
-
85
- export async function loadEditHistoryState(
86
- storage: EditHistoryStorageAdapter,
87
- projectId: string,
88
- ): Promise<EditHistoryState> {
89
- const state = await storage.get(projectId);
90
- return state ?? createEmptyEditHistory();
91
- }
92
-
93
- export async function saveEditHistoryState(
94
- storage: EditHistoryStorageAdapter,
95
- projectId: string,
96
- state: EditHistoryState,
97
- ): Promise<void> {
98
- await storage.set(projectId, state);
99
- }