improve lots of stuff around the editor

This commit is contained in:
Maze Winther
2025-06-22 22:10:50 +02:00
parent ce49c5ff5f
commit 7e3a80eb74
4 changed files with 59 additions and 25 deletions

View File

@ -4,20 +4,25 @@ import Link from "next/link";
import { Button } from "./ui/button"; import { Button } from "./ui/button";
import { ChevronLeft, Download } from "lucide-react"; import { ChevronLeft, Download } from "lucide-react";
import { useProjectStore } from "@/stores/project-store"; import { useProjectStore } from "@/stores/project-store";
import { useMediaStore } from "@/stores/media-store";
import { useTimelineStore } from "@/stores/timeline-store"; import { useTimelineStore } from "@/stores/timeline-store";
import { HeaderBase } from "./header-base"; import { HeaderBase } from "./header-base";
export function EditorHeader() { export function EditorHeader() {
const { activeProject } = useProjectStore(); const { activeProject } = useProjectStore();
const { mediaItems } = useMediaStore(); const { getTotalDuration } = useTimelineStore();
const { tracks } = useTimelineStore();
const handleExport = () => { const handleExport = () => {
// TODO: Implement export functionality // TODO: Implement export functionality
console.log("Export project"); 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 = ( const leftContent = (
<Link <Link
href="/" href="/"
@ -30,9 +35,7 @@ export function EditorHeader() {
const centerContent = ( const centerContent = (
<div className="flex items-center gap-2 text-xs text-muted-foreground"> <div className="flex items-center gap-2 text-xs text-muted-foreground">
<span>{mediaItems.length} media</span> <span>{formatDuration(getTotalDuration())}</span>
<span></span>
<span>{tracks.length} tracks</span>
</div> </div>
); );

View File

@ -18,6 +18,9 @@ export function PreviewPanel() {
? mediaItems.find((item) => item.id === firstClip.mediaId) ? mediaItems.find((item) => item.id === firstClip.mediaId)
: null; : null;
// Calculate dynamic aspect ratio - default to 16:9 if no media
const aspectRatio = firstMediaItem?.aspectRatio || 16 / 9;
const renderPreviewContent = () => { const renderPreviewContent = () => {
if (!firstMediaItem) { if (!firstMediaItem) {
return ( return (
@ -32,7 +35,7 @@ export function PreviewPanel() {
<ImageTimelineTreatment <ImageTimelineTreatment
src={firstMediaItem.url} src={firstMediaItem.url}
alt={firstMediaItem.name} alt={firstMediaItem.name}
targetAspectRatio={16 / 9} targetAspectRatio={aspectRatio}
className="w-full h-full rounded-lg" className="w-full h-full rounded-lg"
backgroundType="blur" backgroundType="blur"
/> />
@ -68,8 +71,17 @@ export function PreviewPanel() {
}; };
return ( return (
<div className="h-full flex flex-col items-center justify-center p-4"> <div className="h-full flex flex-col items-center justify-center p-4 overflow-hidden">
<div className="aspect-video bg-black/90 w-full max-w-4xl rounded-lg shadow-lg relative group 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()} {renderPreviewContent()}
{/* Playback Controls Overlay */} {/* Playback Controls Overlay */}
@ -103,6 +115,16 @@ export function PreviewPanel() {
Preview: {firstMediaItem.name} Preview: {firstMediaItem.name}
{firstMediaItem.type === "image" && {firstMediaItem.type === "image" &&
" (with CapCut-style treatment)"} " (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> </p>
</div> </div>
)} )}

View File

@ -472,19 +472,12 @@ function TimelineTrackComponent({ track }: { track: TimelineTrack }) {
if (mediaItem.type === "image") { if (mediaItem.type === "image") {
return ( return (
<div className="w-full h-full flex items-center gap-2"> <div className="w-full h-full flex items-center justify-center">
<div className="w-16 h-12 flex-shrink-0"> <img
<ImageTimelineTreatment src={mediaItem.url}
src={mediaItem.url} alt={mediaItem.name}
alt={mediaItem.name} className="w-full h-full object-cover"
targetAspectRatio={16 / 9} />
className="rounded-sm"
backgroundType="mirror"
/>
</div>
<span className="text-xs text-foreground/80 truncate flex-1">
{clip.name}
</span>
</div> </div>
); );
} }
@ -536,9 +529,9 @@ function TimelineTrackComponent({ track }: { track: TimelineTrack }) {
track.clips.map((clip, index) => ( track.clips.map((clip, index) => (
<div <div
key={clip.id} 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={{ style={{
width: `${Math.max(80, (clip.duration / 30) * 400)}px`, width: `${Math.max(80, clip.duration * 50)}px`,
}} }}
draggable={true} draggable={true}
onDragStart={(e) => handleClipDragStart(e, clip)} onDragStart={(e) => handleClipDragStart(e, clip)}

View File

@ -33,9 +33,12 @@ interface TimelineStore {
clipId: string, clipId: string,
newIndex: number newIndex: number
) => void; ) => void;
// Computed values
getTotalDuration: () => number;
} }
export const useTimelineStore = create<TimelineStore>((set) => ({ export const useTimelineStore = create<TimelineStore>((set, get) => ({
tracks: [], tracks: [],
addTrack: (type) => { 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);
},
})); }));