summaryrefslogtreecommitdiff
path: root/apps/web/src/stores
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2025-08-04 20:45:48 +0200
committerMaze Winther <mazewinther@gmail.com>2025-08-04 20:45:48 +0200
commitb0329f1372f10b887ad056b9220bf49a4e822e0a (patch)
tree59d620989cbe06239e458198edd981761ac3acf5 /apps/web/src/stores
parentf0dd30ebfc16a61c2869da8407101e8533e584f0 (diff)
downloadopencut-b0329f1372f10b887ad056b9220bf49a4e822e0a.tar.gz
refactor: move canvas size to be a project-level setting instead of editor-level
Diffstat (limited to 'apps/web/src/stores')
-rw-r--r--apps/web/src/stores/editor-store.ts141
-rw-r--r--apps/web/src/stores/project-store.ts53
-rw-r--r--apps/web/src/stores/timeline-store.ts10
3 files changed, 94 insertions, 110 deletions
diff --git a/apps/web/src/stores/editor-store.ts b/apps/web/src/stores/editor-store.ts
index a9c8f2ca..9923aa87 100644
--- a/apps/web/src/stores/editor-store.ts
+++ b/apps/web/src/stores/editor-store.ts
@@ -1,7 +1,7 @@
import { create } from "zustand";
-import { CanvasSize, CanvasPreset } from "@/types/editor";
+import { persist } from "zustand/middleware";
+import { CanvasPreset } from "@/types/editor";
-type CanvasMode = "preset" | "original" | "custom";
export type PlatformLayout = "tiktok";
export const PLATFORM_LAYOUTS: Record<PlatformLayout, string> = {
@@ -17,9 +17,7 @@ interface EditorState {
isInitializing: boolean;
isPanelsReady: boolean;
- // Canvas/Project settings
- canvasSize: CanvasSize;
- canvasMode: CanvasMode;
+ // Editor UI settings
canvasPresets: CanvasPreset[];
layoutGuide: LayoutGuideSettings;
@@ -27,9 +25,6 @@ interface EditorState {
setInitializing: (loading: boolean) => void;
setPanelsReady: (ready: boolean) => void;
initializeApp: () => Promise<void>;
- setCanvasSize: (size: CanvasSize) => void;
- setCanvasSizeToOriginal: (aspectRatio: number) => void;
- setCanvasSizeFromAspectRatio: (aspectRatio: number) => void;
setLayoutGuide: (settings: Partial<LayoutGuideSettings>) => void;
toggleLayoutGuide: (platform: PlatformLayout) => void;
}
@@ -41,96 +36,56 @@ const DEFAULT_CANVAS_PRESETS: CanvasPreset[] = [
{ name: "4:3", width: 1440, height: 1080 },
];
-// Helper function to find the best matching canvas preset for an aspect ratio
-const findBestCanvasPreset = (aspectRatio: number): CanvasSize => {
- // Calculate aspect ratio for each preset and find the closest match
- let bestMatch = DEFAULT_CANVAS_PRESETS[0]; // Default to 16:9 HD
- let smallestDifference = Math.abs(
- aspectRatio - bestMatch.width / bestMatch.height
- );
-
- for (const preset of DEFAULT_CANVAS_PRESETS) {
- const presetAspectRatio = preset.width / preset.height;
- const difference = Math.abs(aspectRatio - presetAspectRatio);
-
- if (difference < smallestDifference) {
- smallestDifference = difference;
- bestMatch = preset;
- }
- }
-
- // If the difference is still significant (> 0.1), create a custom size
- // based on the media aspect ratio with a reasonable resolution
- const bestAspectRatio = bestMatch.width / bestMatch.height;
- if (Math.abs(aspectRatio - bestAspectRatio) > 0.1) {
- // Create custom dimensions based on the aspect ratio
- if (aspectRatio > 1) {
- // Landscape - use 1920 width
- return { width: 1920, height: Math.round(1920 / aspectRatio) };
- }
- // Portrait or square - use 1080 height
- return { width: Math.round(1080 * aspectRatio), height: 1080 };
- }
-
- return { width: bestMatch.width, height: bestMatch.height };
-};
-
-export const useEditorStore = create<EditorState>((set, get) => ({
- // Initial states
- isInitializing: true,
- isPanelsReady: false,
- canvasSize: { width: 1920, height: 1080 }, // Default 16:9 HD
- canvasMode: "preset" as CanvasMode,
- canvasPresets: DEFAULT_CANVAS_PRESETS,
- layoutGuide: {
- platform: null,
- },
-
- // Actions
- setInitializing: (loading) => {
- set({ isInitializing: loading });
- },
-
- setPanelsReady: (ready) => {
- set({ isPanelsReady: ready });
- },
-
- initializeApp: async () => {
- console.log("Initializing video editor...");
- set({ isInitializing: true, isPanelsReady: false });
+export const useEditorStore = create<EditorState>()(
+ persist(
+ (set) => ({
+ // Initial states
+ isInitializing: true,
+ isPanelsReady: false,
+ canvasPresets: DEFAULT_CANVAS_PRESETS,
+ layoutGuide: {
+ platform: null,
+ },
- set({ isPanelsReady: true, isInitializing: false });
- console.log("Video editor ready");
- },
+ // Actions
+ setInitializing: (loading) => {
+ set({ isInitializing: loading });
+ },
- setCanvasSize: (size) => {
- set({ canvasSize: size, canvasMode: "preset" });
- },
+ setPanelsReady: (ready) => {
+ set({ isPanelsReady: ready });
+ },
- setCanvasSizeToOriginal: (aspectRatio) => {
- const newCanvasSize = findBestCanvasPreset(aspectRatio);
- set({ canvasSize: newCanvasSize, canvasMode: "original" });
- },
+ initializeApp: async () => {
+ console.log("Initializing video editor...");
+ set({ isInitializing: true, isPanelsReady: false });
- setCanvasSizeFromAspectRatio: (aspectRatio) => {
- const newCanvasSize = findBestCanvasPreset(aspectRatio);
- set({ canvasSize: newCanvasSize, canvasMode: "custom" });
- },
+ set({ isPanelsReady: true, isInitializing: false });
+ console.log("Video editor ready");
+ },
- setLayoutGuide: (settings) => {
- set((state) => ({
- layoutGuide: {
- ...state.layoutGuide,
- ...settings,
+ setLayoutGuide: (settings) => {
+ set((state) => ({
+ layoutGuide: {
+ ...state.layoutGuide,
+ ...settings,
+ },
+ }));
},
- }));
- },
- toggleLayoutGuide: (platform) => {
- set((state) => ({
- layoutGuide: {
- platform: state.layoutGuide.platform === platform ? null : platform,
+ toggleLayoutGuide: (platform) => {
+ set((state) => ({
+ layoutGuide: {
+ platform: state.layoutGuide.platform === platform ? null : platform,
+ },
+ }));
},
- }));
- },
-}));
+ }),
+ {
+ name: "editor-settings",
+ partialize: (state) => ({
+ layoutGuide: state.layoutGuide,
+ }),
+ }
+ )
+);
diff --git a/apps/web/src/stores/project-store.ts b/apps/web/src/stores/project-store.ts
index 5c617dff..db6d014a 100644
--- a/apps/web/src/stores/project-store.ts
+++ b/apps/web/src/stores/project-store.ts
@@ -5,6 +5,22 @@ import { toast } from "sonner";
import { useMediaStore } from "./media-store";
import { useTimelineStore } from "./timeline-store";
import { generateUUID } from "@/lib/utils";
+import { CanvasSize, CanvasMode } from "@/types/editor";
+
+const DEFAULT_PROJECT: TProject = {
+ id: generateUUID(),
+ name: "Untitled",
+ thumbnail: "",
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ backgroundColor: "#000000",
+ backgroundType: "color",
+ blurIntensity: 8,
+ bookmarks: [],
+ fps: 30,
+ canvasSize: { width: 1920, height: 1080 },
+ canvasMode: "preset",
+};
interface ProjectStore {
activeProject: TProject | null;
@@ -28,6 +44,7 @@ interface ProjectStore {
options?: { backgroundColor?: string; blurIntensity?: number }
) => Promise<void>;
updateProjectFps: (fps: number) => Promise<void>;
+ updateCanvasSize: (size: CanvasSize, mode: CanvasMode) => Promise<void>;
// Bookmark methods
toggleBookmark: (time: number) => Promise<void>;
@@ -144,18 +161,7 @@ export const useProjectStore = create<ProjectStore>((set, get) => ({
},
createNewProject: async (name: string) => {
- const newProject: TProject = {
- id: generateUUID(),
- name,
- thumbnail: "",
- createdAt: new Date(),
- updatedAt: new Date(),
- backgroundColor: "#000000",
- backgroundType: "color",
- blurIntensity: 8,
- bookmarks: [],
- fps: 30,
- };
+ const newProject: TProject = { ...DEFAULT_PROJECT, name };
set({ activeProject: newProject });
@@ -428,6 +434,29 @@ export const useProjectStore = create<ProjectStore>((set, get) => ({
}
},
+ updateCanvasSize: async (size: CanvasSize, mode: CanvasMode) => {
+ const { activeProject } = get();
+ if (!activeProject) return;
+
+ const updatedProject = {
+ ...activeProject,
+ canvasSize: size,
+ canvasMode: mode,
+ updatedAt: new Date(),
+ };
+
+ try {
+ await storageService.saveProject(updatedProject);
+ set({ activeProject: updatedProject });
+ await get().loadAllProjects(); // Refresh the list
+ } catch (error) {
+ console.error("Failed to update canvas size:", error);
+ toast.error("Failed to update canvas size", {
+ description: "Please try again",
+ });
+ }
+ },
+
getFilteredAndSortedProjects: (searchQuery: string, sortOption: string) => {
const { savedProjects } = get();
diff --git a/apps/web/src/stores/timeline-store.ts b/apps/web/src/stores/timeline-store.ts
index 71ddedca..ed2ed612 100644
--- a/apps/web/src/stores/timeline-store.ts
+++ b/apps/web/src/stores/timeline-store.ts
@@ -10,17 +10,16 @@ import {
ensureMainTrack,
validateElementTrackCompatibility,
} from "@/types/timeline";
-import { useEditorStore } from "./editor-store";
import {
useMediaStore,
getMediaAspectRatio,
type MediaItem,
} from "./media-store";
+import { findBestCanvasPreset } from "@/lib/editor-utils";
import { storageService } from "@/lib/storage/storage-service";
import { useProjectStore } from "./project-store";
import { generateUUID } from "@/lib/utils";
import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants";
-import { toast } from "sonner";
import { checkElementOverlaps, resolveElementOverlaps } from "@/lib/timeline";
// Helper function to manage element naming with suffixes
@@ -533,9 +532,10 @@ export const useTimelineStore = create<TimelineStore>((set, get) => {
mediaItem &&
(mediaItem.type === "image" || mediaItem.type === "video")
) {
- const editorStore = useEditorStore.getState();
- editorStore.setCanvasSizeFromAspectRatio(
- getMediaAspectRatio(mediaItem)
+ const projectStore = useProjectStore.getState();
+ projectStore.updateCanvasSize(
+ findBestCanvasPreset(getMediaAspectRatio(mediaItem)),
+ "original"
);
}