diff --git a/apps/web/src/components/editor/preview-panel.tsx b/apps/web/src/components/editor/preview-panel.tsx index 0eee600..0669292 100644 --- a/apps/web/src/components/editor/preview-panel.tsx +++ b/apps/web/src/components/editor/preview-panel.tsx @@ -7,6 +7,7 @@ import { } from "@/stores/timeline-store"; import { useMediaStore, type MediaItem } from "@/stores/media-store"; import { usePlaybackStore } from "@/stores/playback-store"; +import { useEditorStore } from "@/stores/editor-store"; import { VideoPlayer } from "@/components/ui/video-player"; import { Button } from "@/components/ui/button"; import { Play, Pause, Volume2, VolumeX, Plus } from "lucide-react"; @@ -22,7 +23,7 @@ export function PreviewPanel() { const { tracks } = useTimelineStore(); const { mediaItems } = useMediaStore(); const { currentTime, muted, toggleMute, volume } = usePlaybackStore(); - const [canvasSize, setCanvasSize] = useState({ width: 1920, height: 1080 }); + const { canvasSize, canvasPresets, setCanvasSize } = useEditorStore(); const previewRef = useRef(null); const containerRef = useRef(null); const [previewDimensions, setPreviewDimensions] = useState({ @@ -183,15 +184,6 @@ export function PreviewPanel() { return null; }; - // Canvas presets - const canvasPresets = [ - { 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 }, - ]; - return (
{/* Controls */} diff --git a/apps/web/src/stores/editor-store.ts b/apps/web/src/stores/editor-store.ts index e8dd8e2..1ce870c 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; + 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((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((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 7362849..9f98b32 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((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((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 diff --git a/apps/web/src/types/editor.ts b/apps/web/src/types/editor.ts index bf1dae2..dce7ac5 100644 --- a/apps/web/src/types/editor.ts +++ b/apps/web/src/types/editor.ts @@ -1 +1,12 @@ export type BackgroundType = "blur" | "mirror" | "color"; + +export interface CanvasSize { + width: number; + height: number; +} + +export interface CanvasPreset { + name: string; + width: number; + height: number; +}