diff --git a/src/components/video-editor/CropControl.tsx b/src/components/video-editor/CropControl.tsx index 036116c..401c4d5 100644 --- a/src/components/video-editor/CropControl.tsx +++ b/src/components/video-editor/CropControl.tsx @@ -1,5 +1,6 @@ import { useEffect, useRef, useState } from "react"; import { cn } from "@/lib/utils"; +import { type AspectRatio, formatAspectRatioForCSS } from "@/utils/aspectRatioUtils"; interface CropRegion { x: number; // 0-1 normalized @@ -12,11 +13,12 @@ interface CropControlProps { videoElement: HTMLVideoElement | null; cropRegion: CropRegion; onCropChange: (region: CropRegion) => void; + aspectRatio: AspectRatio; } type DragHandle = 'top' | 'right' | 'bottom' | 'left' | null; -export function CropControl({ videoElement, cropRegion, onCropChange }: CropControlProps) { +export function CropControl({ videoElement, cropRegion, onCropChange, aspectRatio }: CropControlProps) { const canvasRef = useRef(null); const containerRef = useRef(null); const [isDragging, setIsDragging] = useState(null); @@ -114,12 +116,22 @@ export function CropControl({ videoElement, cropRegion, onCropChange }: CropCont const cropPixelY = cropRegion.y * 100; const cropPixelWidth = cropRegion.width * 100; const cropPixelHeight = cropRegion.height * 100; + const videoAspectRatio = videoElement ? videoElement.videoWidth / videoElement.videoHeight : 16/9; + const isVideoPortrait = videoAspectRatio < 1; + const maxContainerWidth = isVideoPortrait ? '40vw' : '75vw'; + const maxContainerHeight = '75vh'; return (
`wallpapers/wallpaper${i + 1}.jpg`); @@ -63,6 +64,7 @@ interface SettingsPanelProps { onPaddingChange?: (padding: number) => void; cropRegion?: CropRegion; onCropChange?: (region: CropRegion) => void; + aspectRatio: AspectRatio; videoElement?: HTMLVideoElement | null; onExport?: () => void; } @@ -78,7 +80,7 @@ const ZOOM_DEPTH_OPTIONS: Array<{ depth: ZoomDepth; label: string }> = [ { depth: 6, label: "5×" }, ]; -export function SettingsPanel({ selected, onWallpaperChange, selectedZoomDepth, onZoomDepthChange, selectedZoomId, onZoomDelete, shadowIntensity = 0, onShadowChange, showBlur, onBlurChange, motionBlurEnabled = true, onMotionBlurChange, borderRadius = 0, onBorderRadiusChange, padding = 50, onPaddingChange, cropRegion, onCropChange, videoElement, onExport }: SettingsPanelProps) { +export function SettingsPanel({ selected, onWallpaperChange, selectedZoomDepth, onZoomDepthChange, selectedZoomId, onZoomDelete, shadowIntensity = 0, onShadowChange, showBlur, onBlurChange, motionBlurEnabled = true, onMotionBlurChange, borderRadius = 0, onBorderRadiusChange, padding = 50, onPaddingChange, cropRegion, onCropChange, aspectRatio, videoElement, onExport }: SettingsPanelProps) { const [wallpaperPaths, setWallpaperPaths] = useState([]); const [customImages, setCustomImages] = useState([]); const fileInputRef = useRef(null); @@ -300,7 +302,7 @@ export function SettingsPanel({ selected, onWallpaperChange, selectedZoomDepth, className="fixed inset-0 bg-black/80 backdrop-blur-sm z-50 animate-in fade-in duration-200" onClick={() => setShowCropDropdown(false)} /> -
+
Crop Video @@ -319,6 +321,7 @@ export function SettingsPanel({ selected, onWallpaperChange, selectedZoomDepth, videoElement={videoElement || null} cropRegion={cropRegion} onCropChange={onCropChange} + aspectRatio={aspectRatio} />
+
+ + + + + + {(['16:9', '9:16', '1:1', '4:3', '4:5'] as AspectRatio[]).map((ratio) => ( + onAspectRatioChange(ratio)} + className="text-slate-300 hover:text-white hover:bg-white/10 cursor-pointer flex items-center justify-between gap-3" + > + {getAspectRatioLabel(ratio)} + {aspectRatio === ratio && } + + ))} + + +
diff --git a/src/utils/aspectRatioUtils.ts b/src/utils/aspectRatioUtils.ts new file mode 100644 index 0000000..aaad804 --- /dev/null +++ b/src/utils/aspectRatioUtils.ts @@ -0,0 +1,31 @@ +export type AspectRatio = '16:9' | '9:16' | '1:1' | '4:3' | '4:5'; + +export function getAspectRatioValue(aspectRatio: AspectRatio): number { + switch (aspectRatio) { + case '16:9': return 16 / 9; + case '9:16': return 9 / 16; + case '1:1': return 1; + case '4:3': return 4 / 3; + case '4:5': return 4 / 5; + } +} + +export function getAspectRatioDimensions( + aspectRatio: AspectRatio, + baseWidth: number +): { width: number; height: number } { + const ratio = getAspectRatioValue(aspectRatio); + return { + width: baseWidth, + height: baseWidth / ratio, + }; +} + +export function getAspectRatioLabel(aspectRatio: AspectRatio): string { + return aspectRatio; +} + + +export function formatAspectRatioForCSS(aspectRatio: AspectRatio): string { + return aspectRatio.replace(':', '/'); +} \ No newline at end of file