summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2025-06-22 22:10:50 +0200
committerMaze Winther <mazewinther@gmail.com>2025-06-22 22:10:50 +0200
commit7e3a80eb74af23a36cfff43aa61175e1630222e1 (patch)
tree69ae37ec8a45a5d638d18f12afe661881f021afc
parentce49c5ff5f75c32ecb74673ab88447f3574c61fa (diff)
downloadopencut-7e3a80eb74af23a36cfff43aa61175e1630222e1.tar.gz
improve lots of stuff around the editor
-rw-r--r--apps/web/src/components/editor-header.tsx15
-rw-r--r--apps/web/src/components/editor/preview-panel.tsx28
-rw-r--r--apps/web/src/components/editor/timeline.tsx23
-rw-r--r--apps/web/src/stores/timeline-store.ts18
4 files changed, 59 insertions, 25 deletions
diff --git a/apps/web/src/components/editor-header.tsx b/apps/web/src/components/editor-header.tsx
index f59193d0..c6cecbe8 100644
--- a/apps/web/src/components/editor-header.tsx
+++ b/apps/web/src/components/editor-header.tsx
@@ -4,20 +4,25 @@ import Link from "next/link";
import { Button } from "./ui/button";
import { ChevronLeft, Download } from "lucide-react";
import { useProjectStore } from "@/stores/project-store";
-import { useMediaStore } from "@/stores/media-store";
import { useTimelineStore } from "@/stores/timeline-store";
import { HeaderBase } from "./header-base";
export function EditorHeader() {
const { activeProject } = useProjectStore();
- const { mediaItems } = useMediaStore();
- const { tracks } = useTimelineStore();
+ const { getTotalDuration } = useTimelineStore();
const handleExport = () => {
// TODO: Implement export functionality
console.log("Export project");
};
+ // Format duration from seconds to MM:SS format
+ const formatDuration = (seconds: number): string => {
+ const minutes = Math.floor(seconds / 60);
+ const remainingSeconds = Math.floor(seconds % 60);
+ return `${minutes}:${remainingSeconds.toString().padStart(2, "0")}`;
+ };
+
const leftContent = (
<Link
href="/"
@@ -30,9 +35,7 @@ export function EditorHeader() {
const centerContent = (
<div className="flex items-center gap-2 text-xs text-muted-foreground">
- <span>{mediaItems.length} media</span>
- <span>•</span>
- <span>{tracks.length} tracks</span>
+ <span>{formatDuration(getTotalDuration())}</span>
</div>
);
diff --git a/apps/web/src/components/editor/preview-panel.tsx b/apps/web/src/components/editor/preview-panel.tsx
index 488e1fc9..16c808f4 100644
--- a/apps/web/src/components/editor/preview-panel.tsx
+++ b/apps/web/src/components/editor/preview-panel.tsx
@@ -18,6 +18,9 @@ export function PreviewPanel() {
? mediaItems.find((item) => item.id === firstClip.mediaId)
: null;
+ // Calculate dynamic aspect ratio - default to 16:9 if no media
+ const aspectRatio = firstMediaItem?.aspectRatio || 16 / 9;
+
const renderPreviewContent = () => {
if (!firstMediaItem) {
return (
@@ -32,7 +35,7 @@ export function PreviewPanel() {
<ImageTimelineTreatment
src={firstMediaItem.url}
alt={firstMediaItem.name}
- targetAspectRatio={16 / 9}
+ targetAspectRatio={aspectRatio}
className="w-full h-full rounded-lg"
backgroundType="blur"
/>
@@ -68,8 +71,17 @@ export function PreviewPanel() {
};
return (
- <div className="h-full flex flex-col items-center justify-center p-4">
- <div className="aspect-video bg-black/90 w-full max-w-4xl rounded-lg shadow-lg relative group overflow-hidden">
+ <div className="h-full flex flex-col items-center justify-center p-4 overflow-hidden">
+ <div
+ className="bg-black/90 rounded-lg shadow-lg relative group overflow-hidden flex-shrink"
+ style={{
+ aspectRatio: aspectRatio.toString(),
+ width: aspectRatio > 1 ? "100%" : "auto",
+ height: aspectRatio <= 1 ? "100%" : "auto",
+ maxWidth: "100%",
+ maxHeight: "100%",
+ }}
+ >
{renderPreviewContent()}
{/* Playback Controls Overlay */}
@@ -103,6 +115,16 @@ export function PreviewPanel() {
Preview: {firstMediaItem.name}
{firstMediaItem.type === "image" &&
" (with CapCut-style treatment)"}
+ <br />
+ <span className="text-xs text-muted-foreground/70">
+ Aspect Ratio: {aspectRatio.toFixed(2)} (
+ {aspectRatio > 1
+ ? "Landscape"
+ : aspectRatio < 1
+ ? "Portrait"
+ : "Square"}
+ )
+ </span>
</p>
</div>
)}
diff --git a/apps/web/src/components/editor/timeline.tsx b/apps/web/src/components/editor/timeline.tsx
index 2e23bfba..4ae42660 100644
--- a/apps/web/src/components/editor/timeline.tsx
+++ b/apps/web/src/components/editor/timeline.tsx
@@ -472,19 +472,12 @@ function TimelineTrackComponent({ track }: { track: TimelineTrack }) {
if (mediaItem.type === "image") {
return (
- <div className="w-full h-full flex items-center gap-2">
- <div className="w-16 h-12 flex-shrink-0">
- <ImageTimelineTreatment
- src={mediaItem.url}
- alt={mediaItem.name}
- targetAspectRatio={16 / 9}
- className="rounded-sm"
- backgroundType="mirror"
- />
- </div>
- <span className="text-xs text-foreground/80 truncate flex-1">
- {clip.name}
- </span>
+ <div className="w-full h-full flex items-center justify-center">
+ <img
+ src={mediaItem.url}
+ alt={mediaItem.name}
+ className="w-full h-full object-cover"
+ />
</div>
);
}
@@ -536,9 +529,9 @@ function TimelineTrackComponent({ track }: { track: TimelineTrack }) {
track.clips.map((clip, index) => (
<div
key={clip.id}
- className={`timeline-clip h-full rounded-sm border cursor-grab active:cursor-grabbing transition-colors ${getTrackColor(track.type)} flex items-center px-2 min-w-[80px] overflow-hidden`}
+ className={`timeline-clip h-full rounded-sm border cursor-grab active:cursor-grabbing transition-colors ${getTrackColor(track.type)} flex items-center py-3 min-w-[80px] overflow-hidden`}
style={{
- width: `${Math.max(80, (clip.duration / 30) * 400)}px`,
+ width: `${Math.max(80, clip.duration * 50)}px`,
}}
draggable={true}
onDragStart={(e) => handleClipDragStart(e, clip)}
diff --git a/apps/web/src/stores/timeline-store.ts b/apps/web/src/stores/timeline-store.ts
index 243ed220..12129afa 100644
--- a/apps/web/src/stores/timeline-store.ts
+++ b/apps/web/src/stores/timeline-store.ts
@@ -33,9 +33,12 @@ interface TimelineStore {
clipId: string,
newIndex: number
) => void;
+
+ // Computed values
+ getTotalDuration: () => number;
}
-export const useTimelineStore = create<TimelineStore>((set) => ({
+export const useTimelineStore = create<TimelineStore>((set, get) => ({
tracks: [],
addTrack: (type) => {
@@ -134,4 +137,17 @@ export const useTimelineStore = create<TimelineStore>((set) => ({
}),
}));
},
+
+ getTotalDuration: () => {
+ const { tracks } = get();
+ if (tracks.length === 0) return 0;
+
+ // Calculate the duration of each track (sum of all clips in that track)
+ const trackDurations = tracks.map((track) =>
+ track.clips.reduce((total, clip) => total + clip.duration, 0)
+ );
+
+ // Return the maximum track duration (longest track determines project duration)
+ return Math.max(...trackDurations, 0);
+ },
}));