diff options
| author | Maze Winther <mazewinther@gmail.com> | 2025-06-30 23:19:37 +0200 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2025-06-30 23:19:37 +0200 |
| commit | 3e916f0f00147b67fb1301d6057798fd39e5340f (patch) | |
| tree | 661424779fdc5587ce1591f374fa471102c99cdc /apps/web/src/stores | |
| parent | d50cd0b40d1310e2f4fc1f0d981435fdc861f50e (diff) | |
| download | opencut-3e916f0f00147b67fb1301d6057798fd39e5340f.tar.gz | |
feat: implement automatic canvas size adjustment based on first media item aspect ratio
Diffstat (limited to 'apps/web/src/stores')
| -rw-r--r-- | apps/web/src/stores/editor-store.ts | 65 | ||||
| -rw-r--r-- | apps/web/src/stores/timeline-store.ts | 29 |
2 files changed, 94 insertions, 0 deletions
diff --git a/apps/web/src/stores/editor-store.ts b/apps/web/src/stores/editor-store.ts index e8dd8e21..1ce870c4 100644 --- a/apps/web/src/stores/editor-store.ts +++ b/apps/web/src/stores/editor-store.ts @@ -1,20 +1,72 @@ import { create } from "zustand";
+import { CanvasSize, CanvasPreset } from "@/types/editor";
interface EditorState {
// Loading states
isInitializing: boolean;
isPanelsReady: boolean;
+ // Canvas/Project settings
+ canvasSize: CanvasSize;
+ canvasPresets: CanvasPreset[];
+
// Actions
setInitializing: (loading: boolean) => void;
setPanelsReady: (ready: boolean) => void;
initializeApp: () => Promise<void>;
+ setCanvasSize: (size: CanvasSize) => void;
+ setCanvasSizeFromAspectRatio: (aspectRatio: number) => void;
}
+const DEFAULT_CANVAS_PRESETS: CanvasPreset[] = [
+ { name: "16:9 HD", width: 1920, height: 1080 },
+ { name: "16:9 4K", width: 3840, height: 2160 },
+ { name: "9:16 Mobile", width: 1080, height: 1920 },
+ { name: "1:1 Square", width: 1080, height: 1080 },
+ { name: "4:3 Standard", 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) };
+ } else {
+ // 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
+ canvasPresets: DEFAULT_CANVAS_PRESETS,
// Actions
setInitializing: (loading) => {
@@ -32,4 +84,17 @@ export const useEditorStore = create<EditorState>((set, get) => ({ set({ isPanelsReady: true, isInitializing: false });
console.log("Video editor ready");
},
+
+ setCanvasSize: (size) => {
+ set({ canvasSize: size });
+ },
+
+ setCanvasSizeFromAspectRatio: (aspectRatio) => {
+ const newCanvasSize = findBestCanvasPreset(aspectRatio);
+ console.log(
+ `Setting canvas size based on aspect ratio ${aspectRatio}:`,
+ newCanvasSize
+ );
+ set({ canvasSize: newCanvasSize });
+ },
}));
diff --git a/apps/web/src/stores/timeline-store.ts b/apps/web/src/stores/timeline-store.ts index 73628497..9f98b320 100644 --- a/apps/web/src/stores/timeline-store.ts +++ b/apps/web/src/stores/timeline-store.ts @@ -1,5 +1,8 @@ import { create } from "zustand"; import type { TrackType } from "@/types/timeline"; +import { useEditorStore } from "./editor-store"; +import { useMediaStore } from "./media-store"; +import { toast } from "sonner"; // Helper function to manage clip naming with suffixes const getClipNameWithSuffix = ( @@ -199,6 +202,15 @@ export const useTimelineStore = create<TimelineStore>((set, get) => ({ addClipToTrack: (trackId, clipData) => { get().pushHistory(); + + // Check if this is the first clip being added to the timeline + const currentState = get(); + const totalClipsInTimeline = currentState.tracks.reduce( + (total, track) => total + track.clips.length, + 0 + ); + const isFirstClip = totalClipsInTimeline === 0; + const newClip: TimelineClip = { ...clipData, id: crypto.randomUUID(), @@ -207,6 +219,23 @@ export const useTimelineStore = create<TimelineStore>((set, get) => ({ trimEnd: 0, }; + // If this is the first clip, automatically set the project canvas size + // to match the media's aspect ratio + if (isFirstClip && clipData.mediaId) { + const mediaStore = useMediaStore.getState(); + const mediaItem = mediaStore.mediaItems.find( + (item) => item.id === clipData.mediaId + ); + + if ( + mediaItem && + (mediaItem.type === "image" || mediaItem.type === "video") + ) { + const editorStore = useEditorStore.getState(); + editorStore.setCanvasSizeFromAspectRatio(mediaItem.aspectRatio); + } + } + set((state) => ({ tracks: state.tracks.map((track) => track.id === trackId |
