summaryrefslogtreecommitdiff
path: root/apps/web/src/app/editor
diff options
context:
space:
mode:
authorryu <ryumacodes@gmail.com>2025-08-07 19:37:06 +0500
committerGitHub <noreply@github.com>2025-08-07 19:37:06 +0500
commite71788014d137a74f10420601451ddfbb84701bb (patch)
tree2c08f7fbbc7537b12935e842981830aed976ce87 /apps/web/src/app/editor
parent6412158003f295a350fc66800796f30c8f3c534b (diff)
parentd022a78ae2330665e7d763fdc6e831a71f3b9ea1 (diff)
downloadopencut-e71788014d137a74f10420601451ddfbb84701bb.tar.gz
Merge branch 'main' into feat/timeline-return-to-start
Diffstat (limited to 'apps/web/src/app/editor')
-rw-r--r--apps/web/src/app/editor/[project_id]/layout.tsx13
-rw-r--r--apps/web/src/app/editor/[project_id]/page.tsx104
2 files changed, 106 insertions, 11 deletions
diff --git a/apps/web/src/app/editor/[project_id]/layout.tsx b/apps/web/src/app/editor/[project_id]/layout.tsx
new file mode 100644
index 00000000..151f7f94
--- /dev/null
+++ b/apps/web/src/app/editor/[project_id]/layout.tsx
@@ -0,0 +1,13 @@
+"use client";
+
+import { useGlobalPrefetcher } from "@/components/providers/global-prefetcher";
+
+export default function EditorLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ useGlobalPrefetcher();
+
+ return <div>{children}</div>;
+}
diff --git a/apps/web/src/app/editor/[project_id]/page.tsx b/apps/web/src/app/editor/[project_id]/page.tsx
index 26fdcf8a..cae8e779 100644
--- a/apps/web/src/app/editor/[project_id]/page.tsx
+++ b/apps/web/src/app/editor/[project_id]/page.tsx
@@ -32,39 +32,121 @@ export default function Editor() {
setPropertiesPanel,
} = usePanelStore();
- const { activeProject, loadProject, createNewProject } = useProjectStore();
+ const {
+ activeProject,
+ loadProject,
+ createNewProject,
+ isInvalidProjectId,
+ markProjectIdAsInvalid,
+ } = useProjectStore();
const params = useParams();
const router = useRouter();
const projectId = params.project_id as string;
const handledProjectIds = useRef<Set<string>>(new Set());
+ const isInitializingRef = useRef<boolean>(false);
usePlaybackControls();
useEffect(() => {
+ let isCancelled = false;
+
const initProject = async () => {
- if (!projectId) return;
+ if (!projectId) {
+ return;
+ }
+ // Prevent duplicate initialization
+ if (isInitializingRef.current) {
+ return;
+ }
+
+ // Check if project is already loaded
if (activeProject?.id === projectId) {
return;
}
+ // Check global invalid tracking first (most important for preventing duplicates)
+ if (isInvalidProjectId(projectId)) {
+ return;
+ }
+
+ // Check if we've already handled this project ID locally
if (handledProjectIds.current.has(projectId)) {
return;
}
+ // Mark as initializing to prevent race conditions
+ isInitializingRef.current = true;
+ handledProjectIds.current.add(projectId);
+
try {
await loadProject(projectId);
+
+ // Check if component was unmounted during async operation
+ if (isCancelled) {
+ return;
+ }
+
+ // Project loaded successfully
+ isInitializingRef.current = false;
} catch (error) {
- handledProjectIds.current.add(projectId);
+ // Check if component was unmounted during async operation
+ if (isCancelled) {
+ return;
+ }
- const newProjectId = await createNewProject("Untitled Project");
- router.replace(`/editor/${newProjectId}`);
- return;
+ // More specific error handling - only create new project for actual "not found" errors
+ const isProjectNotFound =
+ error instanceof Error &&
+ (error.message.includes("not found") ||
+ error.message.includes("does not exist") ||
+ error.message.includes("Project not found"));
+
+ if (isProjectNotFound) {
+ // Mark this project ID as invalid globally BEFORE creating project
+ markProjectIdAsInvalid(projectId);
+
+ try {
+ const newProjectId = await createNewProject("Untitled Project");
+
+ // Check again if component was unmounted
+ if (isCancelled) {
+ return;
+ }
+
+ router.replace(`/editor/${newProjectId}`);
+ } catch (createError) {
+ console.error("Failed to create new project:", createError);
+ }
+ } else {
+ // For other errors (storage issues, corruption, etc.), don't create new project
+ console.error(
+ "Project loading failed with recoverable error:",
+ error
+ );
+ // Remove from handled set so user can retry
+ handledProjectIds.current.delete(projectId);
+ }
+
+ isInitializingRef.current = false;
}
};
initProject();
- }, [projectId, activeProject?.id, loadProject, createNewProject, router]);
+
+ // Cleanup function to cancel async operations
+ return () => {
+ isCancelled = true;
+ isInitializingRef.current = false;
+ };
+ }, [
+ projectId,
+ loadProject,
+ createNewProject,
+ router,
+ isInvalidProjectId,
+ markProjectIdAsInvalid,
+ ]);
return (
<EditorProvider>
@@ -85,7 +167,7 @@ export default function Editor() {
{/* Main content area */}
<ResizablePanelGroup
direction="horizontal"
- className="h-full w-full gap-[0.19rem] px-2"
+ className="h-full w-full gap-[0.19rem] px-3"
>
{/* Tools Panel */}
<ResizablePanel
@@ -93,7 +175,7 @@ export default function Editor() {
minSize={15}
maxSize={40}
onResize={setToolsPanel}
- className="min-w-0"
+ className="min-w-0 rounded-sm"
>
<MediaPanel />
</ResizablePanel>
@@ -117,7 +199,7 @@ export default function Editor() {
minSize={15}
maxSize={40}
onResize={setPropertiesPanel}
- className="min-w-0"
+ className="min-w-0 rounded-sm"
>
<PropertiesPanel />
</ResizablePanel>
@@ -132,7 +214,7 @@ export default function Editor() {
minSize={15}
maxSize={70}
onResize={setTimeline}
- className="min-h-0 px-2 pb-2"
+ className="min-h-0 px-3 pb-3"
>
<Timeline />
</ResizablePanel>