fix: address review feedback on JSON export/import
- Use storageService.loadAllMediaAssets() in exportProjectAsJSON() for complete asset coverage instead of in-memory-only getAssets() - Validate schema_version on import, rejecting mismatched versions - Reject empty scenes array during import validation - Refactor ImportProjectButton to use existing useFileUpload hook
This commit is contained in:
parent
bf2caa8193
commit
2f68b3e906
|
|
@ -4,7 +4,7 @@ import Image from "next/image";
|
|||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import type { KeyboardEvent, MouseEvent } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import type { EditorCore } from "@/core";
|
||||
import { MigrationDialog } from "@/components/editor/dialogs/migration-dialog";
|
||||
|
|
@ -15,6 +15,7 @@ import { Checkbox } from "@/components/ui/checkbox";
|
|||
import { Input } from "@/components/ui/input";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { useEditor } from "@/hooks/use-editor";
|
||||
import { useFileUpload } from "@/hooks/use-file-upload";
|
||||
import { useProjectsStore } from "./store";
|
||||
import type {
|
||||
TProjectMetadata,
|
||||
|
|
@ -532,45 +533,37 @@ function NewProjectButton() {
|
|||
function ImportProjectButton() {
|
||||
const editor = useEditor();
|
||||
const router = useRouter();
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const handleImport = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0];
|
||||
if (!file) return;
|
||||
const { openFilePicker, fileInputProps } = useFileUpload({
|
||||
accept: ".json,.opencut.json",
|
||||
multiple: false,
|
||||
onFilesSelected: async (files) => {
|
||||
const file = files[0];
|
||||
if (!file) return;
|
||||
|
||||
try {
|
||||
const json = await file.text();
|
||||
const projectId = await editor.project.importProjectFromJSON({ json });
|
||||
if (projectId) {
|
||||
router.push(`/editor/${projectId}`);
|
||||
try {
|
||||
const json = await file.text();
|
||||
const projectId = await editor.project.importProjectFromJSON({ json });
|
||||
if (projectId) {
|
||||
router.push(`/editor/${projectId}`);
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error("Failed to read file", {
|
||||
description:
|
||||
error instanceof Error ? error.message : "Please try again",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error("Failed to read file", {
|
||||
description:
|
||||
error instanceof Error ? error.message : "Please try again",
|
||||
});
|
||||
}
|
||||
|
||||
// Reset the input so the same file can be re-selected
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.value = "";
|
||||
}
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
accept=".json,.opencut.json"
|
||||
className="hidden"
|
||||
onChange={handleImport}
|
||||
/>
|
||||
<input {...fileInputProps} />
|
||||
<Button
|
||||
size="lg"
|
||||
variant="outline"
|
||||
className="flex px-5 md:px-6"
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
onClick={openFilePicker}
|
||||
>
|
||||
<HugeiconsIcon icon={Upload01Icon} className="size-4" />
|
||||
<span className="text-sm font-medium hidden md:block">Import</span>
|
||||
|
|
|
|||
|
|
@ -715,7 +715,9 @@ export class ProjectManager {
|
|||
timelineViewState: project.timelineViewState,
|
||||
};
|
||||
|
||||
const mediaAssets = this.editor.media.getAssets();
|
||||
const mediaAssets = await storageService.loadAllMediaAssets({
|
||||
projectId: project.metadata.id,
|
||||
});
|
||||
const mediaManifest: ExportedMediaManifestEntry[] = mediaAssets.map(
|
||||
(asset) => ({
|
||||
mediaId: asset.id,
|
||||
|
|
@ -864,7 +866,8 @@ export class ProjectManager {
|
|||
if (
|
||||
!parsed.project ||
|
||||
!parsed.project.metadata ||
|
||||
!parsed.project.scenes
|
||||
!parsed.project.scenes ||
|
||||
parsed.project.scenes.length === 0
|
||||
) {
|
||||
toast.error("Invalid project file", {
|
||||
description: "The file does not contain a valid OpenCut project.",
|
||||
|
|
@ -872,6 +875,13 @@ export class ProjectManager {
|
|||
return null;
|
||||
}
|
||||
|
||||
if (parsed.schema_version !== EXPORT_SCHEMA_VERSION) {
|
||||
toast.error("Incompatible project file", {
|
||||
description: `Unsupported schema version ${parsed.schema_version}, expected ${EXPORT_SCHEMA_VERSION}.`,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
const imported = parsed.project;
|
||||
|
||||
const newProjectId = generateUUID();
|
||||
|
|
|
|||
Loading…
Reference in New Issue