feat: implement automatic canvas size adjustment based on first media item aspect ratio
This commit is contained in:
@ -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<HTMLDivElement>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(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 (
|
||||
<div className="h-full w-full flex flex-col min-h-0 min-w-0">
|
||||
{/* Controls */}
|
||||
|
@ -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 });
|
||||
},
|
||||
}));
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
Reference in New Issue
Block a user