Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 2 additions & 19 deletions MyApp.Client/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,9 @@ export default function RootLayout({
children: React.ReactNode
}) {
return (
<html lang="en" className="h-full">
<html lang="en" className="h-full dark">
<head />
<body className="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 transition-colors duration-200">
<script
dangerouslySetInnerHTML={{
__html: `
(function() {
function getTheme() {
const theme = localStorage.getItem('color-scheme');
if (theme) return theme;
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
const theme = getTheme();
if (theme === 'dark') {
document.documentElement.classList.add('dark');
}
})();
`,
}}
/>
<body className="h-full bg-background text-foreground">
<Providers>{children}</Providers>
</body>
</html>
Expand Down
18 changes: 9 additions & 9 deletions MyApp.Client/components/editor/canvas/playback-controls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ export function PlaybackControls({ project, playerRef }: PlaybackControlsProps)
};

return (
<div className="h-16 bg-white dark:bg-slate-900 border-t border-border px-4 flex items-center gap-4 shadow-sm">
<div className="h-16 bg-slate-900 border-t border-slate-800 px-4 flex items-center gap-4 shadow-lg">
<div className="flex items-center gap-2">
<Button
size="sm"
variant="ghost"
onClick={() => skipBackward(30)}
title="Go back 1 second"
className="h-10 w-10"
className="h-10 w-10 text-white hover:bg-slate-800"
>
<SkipBack className="w-5 h-5" />
</Button>
Expand All @@ -51,7 +51,7 @@ export function PlaybackControls({ project, playerRef }: PlaybackControlsProps)
size="sm"
variant="default"
onClick={togglePlay}
className="w-12 h-12 rounded-full"
className="w-12 h-12 rounded-full bg-blue-600 hover:bg-blue-700"
>
{isPlaying ? (
<Pause className="w-5 h-5" />
Expand All @@ -65,20 +65,20 @@ export function PlaybackControls({ project, playerRef }: PlaybackControlsProps)
variant="ghost"
onClick={() => skipForward(30)}
title="Go forward 1 second"
className="h-10 w-10"
className="h-10 w-10 text-white hover:bg-slate-800"
>
<SkipForward className="w-5 h-5" />
</Button>
</div>

<div className="flex items-center gap-3 text-sm font-mono font-semibold">
<span className="text-foreground">{formatTime(currentFrame)}</span>
<span className="text-muted-foreground">/</span>
<span className="text-muted-foreground">{formatTime(totalFrames)}</span>
<span className="text-white">{formatTime(currentFrame)}</span>
<span className="text-slate-500">/</span>
<span className="text-slate-400">{formatTime(totalFrames)}</span>
</div>

<div className="ml-auto text-sm text-muted-foreground font-medium">
Frame: <span className="text-foreground font-semibold">{currentFrame}</span> / {totalFrames}
<div className="ml-auto text-sm text-slate-400 font-medium">
Frame: <span className="text-white font-semibold">{currentFrame}</span> / {totalFrames}
</div>
</div>
);
Expand Down
111 changes: 90 additions & 21 deletions MyApp.Client/components/editor/canvas/preview-canvas.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { useCallback, useEffect, useRef } from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';
import { Player, PlayerRef } from '@remotion/player';
import { Project } from '@/lib/types/project';
import { useAssetStore } from '@/lib/stores/asset-store';
Expand All @@ -19,37 +19,94 @@ export function PreviewCanvas({ project }: PreviewCanvasProps) {
const { isPlaying, pause } = usePlaybackStore();
const { getTimeline, setCurrentFrame } = useTimelineStore();
const timeline = getTimeline();
const [isPlayerReady, setIsPlayerReady] = useState(false);

// Sync player playback state
// Wait for player to be ready with better detection
useEffect(() => {
if (!playerRef.current) return;
let attempts = 0;
const maxAttempts = 20;
let timeoutId: NodeJS.Timeout;

const checkPlayerReady = () => {
if (playerRef.current) {
try {
// Try to get current frame as a test of readiness
playerRef.current.getCurrentFrame();
console.log('Player is ready!');
setIsPlayerReady(true);
return;
} catch (error) {
console.log('Player not ready yet, attempt:', attempts + 1);
}
}

attempts++;
if (attempts < maxAttempts) {
timeoutId = setTimeout(checkPlayerReady, 100);
} else {
console.error('Player failed to initialize after', maxAttempts, 'attempts');
}
};

if (isPlaying) {
playerRef.current.play();
} else {
playerRef.current.pause();
timeoutId = setTimeout(checkPlayerReady, 100);
return () => clearTimeout(timeoutId);
}, []);

// Sync current frame to player when timeline changes
useEffect(() => {
if (!playerRef.current || !isPlayerReady) {
return;
}
}, [isPlaying]);

// Handle frame updates from player
const handleFrameUpdate = useCallback(
(frame: number) => {
if (timeline && frame !== timeline.currentFrame) {
setCurrentFrame(frame);
try {
const currentPlayerFrame = playerRef.current.getCurrentFrame();
if (timeline && currentPlayerFrame !== timeline.currentFrame) {
playerRef.current.seekTo(timeline.currentFrame);
}
},
[timeline, setCurrentFrame]
);
} catch (error) {
console.error('Error seeking player:', error);
}
}, [timeline?.currentFrame, isPlayerReady]);

// Handle playback end
const handleEnded = useCallback(() => {
pause();
}, [pause]);
// Sync player playback state
useEffect(() => {
if (!playerRef.current || !isPlayerReady) {
console.log('Player not ready for playback control');
return;
}

try {
const player = playerRef.current;
console.log('Current player state:', {
isPlaying,
currentFrame: player.getCurrentFrame(),
isPlayerPlaying: player.isPlaying(),
});

if (isPlaying) {
console.log('Calling play() on player');
player.play();

// Verify playback started
setTimeout(() => {
console.log('Playback verification:', {
isPlayerPlaying: player.isPlaying(),
currentFrame: player.getCurrentFrame(),
});
}, 100);
} else {
console.log('Calling pause() on player');
player.pause();
}
} catch (error) {
console.error('Error controlling playback:', error);
}
}, [isPlaying, isPlayerReady]);

if (!timeline) return null;

return (
<div className="h-full flex flex-col bg-slate-200 dark:bg-slate-900">
<div className="h-full flex flex-col bg-slate-900">
<div className="flex-1 flex items-center justify-center p-4">
<div
className="relative bg-black shadow-2xl rounded-lg overflow-hidden"
Expand Down Expand Up @@ -79,6 +136,18 @@ export function PreviewCanvas({ project }: PreviewCanvasProps) {
clickToPlay={false}
doubleClickToFullscreen={false}
spaceKeyToPlayOrPause={false}
autoPlay={false}
loop={false}
renderLoading={() => (
<div className="flex items-center justify-center h-full bg-black">
<div className="text-white">Loading...</div>
</div>
)}
errorFallback={({ error }) => (
<div className="flex items-center justify-center h-full bg-red-900/20">
<div className="text-red-400">Error: {error.message}</div>
</div>
)}
/>
</div>
</div>
Expand Down
14 changes: 7 additions & 7 deletions MyApp.Client/components/editor/editor-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ interface EditorLayoutProps {

export function EditorLayout({ project }: EditorLayoutProps) {
return (
<div className="h-screen flex flex-col bg-background text-foreground">
<div className="h-screen flex flex-col bg-slate-950 text-white">
{/* Header */}
<div className="h-14 bg-slate-100 dark:bg-slate-900 border-b border-border flex items-center px-4 shadow-sm">
<h1 className="text-lg font-semibold text-foreground">{project.name}</h1>
<div className="h-14 bg-slate-900 border-b border-slate-800 flex items-center px-4 shadow-lg">
<h1 className="text-lg font-semibold text-white">{project.name}</h1>
<div className="ml-auto flex items-center gap-2">
<span className="text-sm text-muted-foreground font-medium">
<span className="text-sm text-slate-400 font-medium">
{project.settings.width}x{project.settings.height} • {project.settings.fps}fps
</span>
</div>
Expand All @@ -26,7 +26,7 @@ export function EditorLayout({ project }: EditorLayoutProps) {
{/* Main Content */}
<div className="flex-1 flex overflow-hidden">
{/* Left Sidebar - Asset Library */}
<div className="w-80 bg-slate-50 dark:bg-slate-900 border-r border-border overflow-y-auto">
<div className="w-80 bg-slate-900 border-r border-slate-800 overflow-y-auto">
<AssetLibrary projectId={project.id} />
</div>

Expand All @@ -36,10 +36,10 @@ export function EditorLayout({ project }: EditorLayoutProps) {
<PreviewCanvas project={project} />
</div>

<Separator />
<Separator className="bg-slate-800" />

{/* Timeline */}
<div className="h-80 bg-slate-100 dark:bg-slate-950">
<div className="h-80 bg-slate-950">
<TimelineContainer project={project} />
</div>
</div>
Expand Down
14 changes: 7 additions & 7 deletions MyApp.Client/components/editor/timeline/timeline-container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,18 +50,18 @@ export function TimelineContainer({ project }: TimelineContainerProps) {
};

return (
<div className="h-full flex flex-col bg-background">
<div className="h-full flex flex-col bg-slate-950">
{/* Toolbar */}
<div className="h-12 bg-slate-50 dark:bg-slate-900 border-b border-border px-4 flex items-center gap-2 shadow-sm">
<Button size="sm" variant="ghost" onClick={() => addTrack('video')} className="font-medium">
<div className="h-12 bg-slate-900 border-b border-slate-800 px-4 flex items-center gap-2 shadow-sm">
<Button size="sm" variant="ghost" onClick={() => addTrack('video')} className="font-medium text-white hover:bg-slate-800">
<Video className="w-4 h-4 mr-1" />
Add Video Track
</Button>
<Button size="sm" variant="ghost" onClick={() => addTrack('audio')} className="font-medium">
<Button size="sm" variant="ghost" onClick={() => addTrack('audio')} className="font-medium text-white hover:bg-slate-800">
<Music className="w-4 h-4 mr-1" />
Add Audio Track
</Button>
<Button size="sm" variant="ghost" onClick={() => addTrack('text')} className="font-medium">
<Button size="sm" variant="ghost" onClick={() => addTrack('text')} className="font-medium text-white hover:bg-slate-800">
<Type className="w-4 h-4 mr-1" />
Add Text Track
</Button>
Expand All @@ -71,9 +71,9 @@ export function TimelineContainer({ project }: TimelineContainerProps) {
<TimelineRuler fps={project.settings.fps} totalFrames={project.settings.durationInFrames} />

{/* Tracks */}
<div className="flex-1 overflow-y-auto bg-slate-50 dark:bg-slate-950">
<div className="flex-1 overflow-y-auto bg-slate-950">
{timeline.tracks.length === 0 ? (
<div className="h-full flex items-center justify-center text-muted-foreground">
<div className="h-full flex items-center justify-center text-slate-500">
<div className="text-center">
<Plus className="w-12 h-12 mx-auto mb-2 opacity-50" />
<p className="text-sm font-medium">No tracks yet</p>
Expand Down
10 changes: 5 additions & 5 deletions MyApp.Client/components/editor/timeline/timeline-element.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ export function TimelineElement({ element, fps }: TimelineElementProps) {
const getElementColor = () => {
switch (element.type) {
case 'video':
return 'bg-blue-600 dark:bg-blue-700 border-blue-500 dark:border-blue-600';
return 'bg-blue-700 border-blue-600';
case 'audio':
return 'bg-green-600 dark:bg-green-700 border-green-500 dark:border-green-600';
return 'bg-green-700 border-green-600';
case 'image':
return 'bg-purple-600 dark:bg-purple-700 border-purple-500 dark:border-purple-600';
return 'bg-purple-700 border-purple-600';
case 'text':
return 'bg-yellow-600 dark:bg-yellow-700 border-yellow-500 dark:border-yellow-600';
return 'bg-yellow-700 border-yellow-600';
default:
return 'bg-gray-600 dark:bg-gray-700 border-gray-500 dark:border-gray-600';
return 'bg-gray-700 border-gray-600';
}
};

Expand Down
6 changes: 3 additions & 3 deletions MyApp.Client/components/editor/timeline/timeline-ruler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ export function TimelineRuler({ fps, totalFrames }: TimelineRulerProps) {
className="absolute top-0 bottom-0 flex flex-col"
style={{ left: `${frame * pixelsPerFrame}px` }}
>
<div className="w-px h-3 bg-gray-600" />
<span className="text-xs text-gray-500 ml-1">{seconds}s</span>
<div className="w-px h-3 bg-slate-600" />
<span className="text-xs text-slate-500 ml-1">{seconds}s</span>
</div>
);
}
Expand All @@ -44,7 +44,7 @@ export function TimelineRuler({ fps, totalFrames }: TimelineRulerProps) {

return (
<div
className="h-8 bg-white dark:bg-slate-900 border-b border-border relative cursor-pointer overflow-x-auto shadow-sm"
className="h-8 bg-slate-900 border-b border-slate-800 relative cursor-pointer overflow-x-auto shadow-sm"
onClick={handleClick}
style={{ width: '100%' }}
>
Expand Down
20 changes: 10 additions & 10 deletions MyApp.Client/components/editor/timeline/timeline-track.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,32 +27,32 @@ export function TimelineTrack({ track, fps, onDrop, onDragOver }: TimelineTrackP
const getTrackColor = () => {
switch (track.type) {
case 'video':
return 'bg-blue-50 dark:bg-blue-950/20 border-blue-200 dark:border-blue-800/30';
return 'bg-blue-950/20 border-blue-800/30';
case 'audio':
return 'bg-green-50 dark:bg-green-950/20 border-green-200 dark:border-green-800/30';
return 'bg-green-950/20 border-green-800/30';
case 'text':
return 'bg-yellow-50 dark:bg-yellow-950/20 border-yellow-200 dark:border-yellow-800/30';
return 'bg-yellow-950/20 border-yellow-800/30';
default:
return 'bg-purple-50 dark:bg-purple-950/20 border-purple-200 dark:border-purple-800/30';
return 'bg-purple-950/20 border-purple-800/30';
}
};

return (
<div
className={`flex border-b border-border ${getTrackColor()}`}
className={`flex border-b border-slate-800 ${getTrackColor()}`}
style={{ height: `${track.height}px` }}
>
{/* Track Header */}
<div className="w-48 bg-slate-100 dark:bg-slate-900 border-r border-border p-2 flex flex-col justify-between shadow-sm">
<div className="w-48 bg-slate-900 border-r border-slate-800 p-2 flex flex-col justify-between shadow-sm">
<div>
<div className="font-semibold text-sm mb-1 truncate text-foreground">{track.name}</div>
<div className="text-xs text-muted-foreground capitalize font-medium">{track.type}</div>
<div className="font-semibold text-sm mb-1 truncate text-white">{track.name}</div>
<div className="text-xs text-slate-400 capitalize font-medium">{track.type}</div>
</div>
<div className="flex gap-1">
<Button size="sm" variant="ghost" className="h-7 w-7 p-0 hover:bg-accent">
<Button size="sm" variant="ghost" className="h-7 w-7 p-0 text-white hover:bg-slate-800">
{track.isLocked ? <Lock className="w-3.5 h-3.5" /> : <Unlock className="w-3.5 h-3.5" />}
</Button>
<Button size="sm" variant="ghost" className="h-7 w-7 p-0 hover:bg-accent">
<Button size="sm" variant="ghost" className="h-7 w-7 p-0 text-white hover:bg-slate-800">
{track.isMuted ? <VolumeX className="w-3.5 h-3.5" /> : <Volume2 className="w-3.5 h-3.5" />}
</Button>
<Button
Expand Down
Loading
Loading