mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-11-10 07:53:57 +00:00
refactor(editor): extract project dirty state
This commit is contained in:
1 parent
fd2879af04
commit
0b577fb84d
3 files changed
+122
-48
No files matched your search
@@ -147,6 +147,7 @@ import {
|
||||
toFileUrl,
|
||||
validateProjectData,
|
||||
} from "./projectPersistence";
|
||||
import { hasUnsavedProjectChanges } from "./projectDirtyState";
|
||||
import { SettingsPanel } from "./SettingsPanel";
|
||||
import { useVideoEditorAudio } from "./audio/useVideoEditorAudio";
|
||||
import {
|
||||
@@ -463,48 +464,6 @@ function getDevOpenRecordingConfig(search: string): DevOpenRecordingConfig {
|
||||
};
|
||||
}
|
||||
|
||||
function isComparableObject(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null;
|
||||
}
|
||||
|
||||
function areDeepEqual(left: unknown, right: unknown): boolean {
|
||||
if (Object.is(left, right)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Array.isArray(left) || Array.isArray(right)) {
|
||||
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let index = 0; index < left.length; index += 1) {
|
||||
if (!areDeepEqual(left[index], right[index])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!isComparableObject(left) || !isComparableObject(right)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const leftKeys = Object.keys(left);
|
||||
const rightKeys = Object.keys(right);
|
||||
if (leftKeys.length !== rightKeys.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const key of leftKeys) {
|
||||
if (!(key in right) || !areDeepEqual(left[key], right[key])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function getErrorMessage(error: unknown): string {
|
||||
if (error instanceof Error) {
|
||||
return error.message;
|
||||
@@ -2276,12 +2235,7 @@ export default function VideoEditor() {
|
||||
}, [buildHistorySnapshot, syncHistoryButtons]);
|
||||
|
||||
const hasUnsavedChanges = useMemo(
|
||||
() =>
|
||||
Boolean(
|
||||
currentProjectSnapshot &&
|
||||
(!lastSavedSnapshot ||
|
||||
!areDeepEqual(currentProjectSnapshot, lastSavedSnapshot)),
|
||||
),
|
||||
() => hasUnsavedProjectChanges(currentProjectSnapshot, lastSavedSnapshot),
|
||||
[currentProjectSnapshot, lastSavedSnapshot],
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { hasUnsavedProjectChanges } from "./projectDirtyState";
|
||||
import type { EditorProjectData } from "./projectPersistence";
|
||||
|
||||
function createProjectData(overrides: Partial<EditorProjectData> = {}): EditorProjectData {
|
||||
return {
|
||||
version: 1,
|
||||
projectId: "project-1",
|
||||
videoPath: "file:///recording.mp4",
|
||||
editor: {
|
||||
wallpaper: "#ffffff",
|
||||
zoomRegions: [],
|
||||
trimRegions: [],
|
||||
clipRegions: [],
|
||||
speedRegions: [],
|
||||
annotationRegions: [],
|
||||
audioRegions: [],
|
||||
autoCaptions: [],
|
||||
},
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("hasUnsavedProjectChanges", () => {
|
||||
it("does not report changes before a project snapshot exists", () => {
|
||||
expect(hasUnsavedProjectChanges(null, createProjectData())).toBe(false);
|
||||
});
|
||||
|
||||
it("reports changes when there is no saved baseline", () => {
|
||||
expect(hasUnsavedProjectChanges(createProjectData(), null)).toBe(true);
|
||||
});
|
||||
|
||||
it("treats deeply equal snapshots as unchanged", () => {
|
||||
expect(hasUnsavedProjectChanges(createProjectData(), createProjectData())).toBe(false);
|
||||
});
|
||||
|
||||
it("detects nested editor changes", () => {
|
||||
const current = createProjectData({
|
||||
editor: {
|
||||
...createProjectData().editor,
|
||||
zoomRegions: [
|
||||
{
|
||||
id: "zoom-1",
|
||||
startMs: 100,
|
||||
endMs: 500,
|
||||
focus: { cx: 0.4, cy: 0.6 },
|
||||
depth: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
expect(hasUnsavedProjectChanges(current, createProjectData())).toBe(true);
|
||||
});
|
||||
|
||||
it("detects array length changes", () => {
|
||||
const current = createProjectData({
|
||||
editor: {
|
||||
...createProjectData().editor,
|
||||
autoCaptions: [{ id: "caption-1", startMs: 0, endMs: 1_000, text: "hello" }],
|
||||
},
|
||||
});
|
||||
|
||||
expect(hasUnsavedProjectChanges(current, createProjectData())).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
import type { EditorProjectData } from "./projectPersistence";
|
||||
|
||||
function isComparableObject(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null;
|
||||
}
|
||||
|
||||
function areDeepEqual(left: unknown, right: unknown): boolean {
|
||||
if (Object.is(left, right)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Array.isArray(left) || Array.isArray(right)) {
|
||||
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let index = 0; index < left.length; index += 1) {
|
||||
if (!areDeepEqual(left[index], right[index])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!isComparableObject(left) || !isComparableObject(right)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const leftKeys = Object.keys(left);
|
||||
const rightKeys = Object.keys(right);
|
||||
if (leftKeys.length !== rightKeys.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const key of leftKeys) {
|
||||
if (!(key in right) || !areDeepEqual(left[key], right[key])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function hasUnsavedProjectChanges(
|
||||
currentProjectSnapshot: EditorProjectData | null,
|
||||
lastSavedSnapshot: EditorProjectData | null,
|
||||
): boolean {
|
||||
return Boolean(
|
||||
currentProjectSnapshot &&
|
||||
(!lastSavedSnapshot || !areDeepEqual(currentProjectSnapshot, lastSavedSnapshot)),
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user