diff options
Diffstat (limited to 'apps/web/src/components')
| -rw-r--r-- | apps/web/src/components/editor/media-panel/views/media.tsx | 52 | ||||
| -rw-r--r-- | apps/web/src/components/editor/timeline-track.tsx | 137 | ||||
| -rw-r--r-- | apps/web/src/components/footer.tsx | 264 |
3 files changed, 232 insertions, 221 deletions
diff --git a/apps/web/src/components/editor/media-panel/views/media.tsx b/apps/web/src/components/editor/media-panel/views/media.tsx index 48e54961..5438737f 100644 --- a/apps/web/src/components/editor/media-panel/views/media.tsx +++ b/apps/web/src/components/editor/media-panel/views/media.tsx @@ -207,9 +207,8 @@ export function MediaView() { <DragOverlay isVisible={isDragOver} />
<div className="p-3 pb-2">
- {/* Button to add/upload media */}
+ {/* Search and filter controls */}
<div className="flex gap-2">
- {/* Search and filter controls */}
<Select value={mediaFilter} onValueChange={setMediaFilter}>
<SelectTrigger className="w-[80px] h-full text-xs">
<SelectValue />
@@ -228,29 +227,6 @@ export function MediaView() { value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
-
- {/* Add media button */}
- <Button
- variant="outline"
- size="sm"
- onClick={handleFileSelect}
- disabled={isProcessing}
- className="flex-none bg-transparent min-w-[30px] whitespace-nowrap overflow-hidden px-2 justify-center items-center"
- >
- {isProcessing ? (
- <>
- <Upload className="h-4 w-4 animate-spin" />
- <span className="hidden md:inline ml-2">{progress}%</span>
- </>
- ) : (
- <>
- <Plus className="h-4 w-4" />
- <span className="hidden sm:inline ml-2" aria-label="Add file">
- Add
- </span>
- </>
- )}
- </Button>
</div>
</div>
@@ -265,8 +241,32 @@ export function MediaView() { No media in project
</p>
<p className="text-xs text-muted-foreground/70 mt-1">
- Drag files here or use the button above
+ Drag files here or use the button below
</p>
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={handleFileSelect}
+ disabled={isProcessing}
+ className="flex-none bg-transparent min-w-[30px] whitespace-nowrap overflow-hidden px-2 justify-center items-center mt-2"
+ >
+ {isProcessing ? (
+ <>
+ <Upload className="h-4 w-4 animate-spin" />
+ <span className="hidden md:inline ml-2">{progress}%</span>
+ </>
+ ) : (
+ <>
+ <Plus className="h-4 w-4" />
+ <span
+ className="hidden sm:inline ml-2"
+ aria-label="Add file"
+ >
+ Add
+ </span>
+ </>
+ )}
+ </Button>
</div>
) : (
<div
diff --git a/apps/web/src/components/editor/timeline-track.tsx b/apps/web/src/components/editor/timeline-track.tsx index c3a16b9a..033b79a6 100644 --- a/apps/web/src/components/editor/timeline-track.tsx +++ b/apps/web/src/components/editor/timeline-track.tsx @@ -33,6 +33,7 @@ export function TimelineTrackContent({ const { mediaItems } = useMediaStore(); const { tracks, + addTrack, moveElementToTrack, updateElementStartTime, addElementToTrack, @@ -495,6 +496,16 @@ export function TimelineTrackContent({ e.preventDefault(); e.stopPropagation(); + // Debug logging + console.log( + JSON.stringify({ + message: "Drop event started in timeline track", + dataTransferTypes: Array.from(e.dataTransfer.types), + trackId: track.id, + trackType: track.type, + }) + ); + // Reset all drag states dragCounterRef.current = 0; setIsDropping(false); @@ -727,68 +738,28 @@ export function TimelineTrackContent({ // Handle position-aware track creation for media elements if (!isCompatible || dropPosition !== "on") { - const needsNewTrack = !isCompatible || dropPosition !== "on"; - - if (needsNewTrack) { - if (isVideoOrImage) { - // For video/image, check if we need a main track or additional media track - const mainTrack = getMainTrack(tracks); - - if (!mainTrack) { - // No main track exists, create it - const updatedTracks = ensureMainTrack(tracks); - const newMainTrack = getMainTrack(updatedTracks); - if (newMainTrack && newMainTrack.elements.length === 0) { - targetTrackId = newMainTrack.id; - targetTrack = newMainTrack; - } else { - // Main track was created but somehow has elements, create new media track - const mainTrackIndex = updatedTracks.findIndex( - (t) => t.id === newMainTrack?.id - ); - targetTrackId = insertTrackAt("media", mainTrackIndex); - const updatedTracksAfterInsert = - useTimelineStore.getState().tracks; - const newTargetTrack = updatedTracksAfterInsert.find( - (t) => t.id === targetTrackId - ); - if (!newTargetTrack) return; - targetTrack = newTargetTrack; - } - } else if ( - mainTrack.elements.length === 0 && - dropPosition === "on" - ) { - // Main track exists and is empty, use it - targetTrackId = mainTrack.id; - targetTrack = mainTrack; - } else { - // Create new media track above main track - const mainTrackIndex = tracks.findIndex( - (t) => t.id === mainTrack.id - ); - let insertIndex: number; + if (isVideoOrImage) { + // For video/image, check if we need a main track or additional media track + const mainTrack = getMainTrack(tracks); - if (dropPosition === "above") { - insertIndex = currentTrackIndex; - } else if (dropPosition === "below") { - insertIndex = currentTrackIndex + 1; - } else { - // Insert above main track - insertIndex = mainTrackIndex; - } - - targetTrackId = insertTrackAt("media", insertIndex); - const updatedTracks = useTimelineStore.getState().tracks; - const newTargetTrack = updatedTracks.find( - (t) => t.id === targetTrackId - ); - if (!newTargetTrack) return; - targetTrack = newTargetTrack; - } - } else if (isAudio) { - // Audio tracks go at the bottom - const mainTrack = getMainTrack(tracks); + if (!mainTrack) { + // No main track exists, create it + targetTrackId = addTrack("media"); + const updatedTracks = useTimelineStore.getState().tracks; + const newTargetTrack = updatedTracks.find( + (t) => t.id === targetTrackId + ); + if (!newTargetTrack) return; + targetTrack = newTargetTrack; + } else if ( + mainTrack.elements.length === 0 && + dropPosition === "on" + ) { + // Main track exists and is empty, use it + targetTrackId = mainTrack.id; + targetTrack = mainTrack; + } else { + // Create new media track let insertIndex: number; if (dropPosition === "above") { @@ -796,18 +767,14 @@ export function TimelineTrackContent({ } else if (dropPosition === "below") { insertIndex = currentTrackIndex + 1; } else { - // Insert after main track (bottom area) - if (mainTrack) { - const mainTrackIndex = tracks.findIndex( - (t) => t.id === mainTrack.id - ); - insertIndex = mainTrackIndex + 1; - } else { - insertIndex = tracks.length; // Bottom of timeline - } + // Insert above main track + const mainTrackIndex = tracks.findIndex( + (t) => t.id === mainTrack.id + ); + insertIndex = mainTrackIndex; } - targetTrackId = insertTrackAt("audio", insertIndex); + targetTrackId = insertTrackAt("media", insertIndex); const updatedTracks = useTimelineStore.getState().tracks; const newTargetTrack = updatedTracks.find( (t) => t.id === targetTrackId @@ -815,6 +782,34 @@ export function TimelineTrackContent({ if (!newTargetTrack) return; targetTrack = newTargetTrack; } + } else if (isAudio) { + // Audio tracks go at the bottom + const mainTrack = getMainTrack(tracks); + let insertIndex: number; + + if (dropPosition === "above") { + insertIndex = currentTrackIndex; + } else if (dropPosition === "below") { + insertIndex = currentTrackIndex + 1; + } else { + // Insert after main track (bottom area) + if (mainTrack) { + const mainTrackIndex = tracks.findIndex( + (t) => t.id === mainTrack.id + ); + insertIndex = mainTrackIndex + 1; + } else { + insertIndex = tracks.length; // Bottom of timeline + } + } + + targetTrackId = insertTrackAt("audio", insertIndex); + const updatedTracks = useTimelineStore.getState().tracks; + const newTargetTrack = updatedTracks.find( + (t) => t.id === targetTrackId + ); + if (!newTargetTrack) return; + targetTrack = newTargetTrack; } } diff --git a/apps/web/src/components/footer.tsx b/apps/web/src/components/footer.tsx index 10f0e119..81505d4a 100644 --- a/apps/web/src/components/footer.tsx +++ b/apps/web/src/components/footer.tsx @@ -1,124 +1,140 @@ -"use client";
-
-import { motion } from "motion/react";
-import Link from "next/link";
-import { useEffect, useState } from "react";
-import { RiGithubLine, RiTwitterXLine } from "react-icons/ri";
-import { getStars } from "@/lib/fetch-github-stars";
-import Image from "next/image";
-
-export function Footer() {
- const [star, setStar] = useState<string>();
-
- useEffect(() => {
- const fetchStars = async () => {
- try {
- const data = await getStars();
- setStar(data);
- } catch (err) {
- console.error("Failed to fetch GitHub stars", err);
- }
- };
-
- fetchStars();
- }, []);
-
- return (
- <motion.footer
- className="bg-background border-t"
- initial={{ opacity: 0 }}
- animate={{ opacity: 1 }}
- transition={{ delay: 0.8, duration: 0.8 }}
- >
- <div className="max-w-5xl mx-auto px-8 py-10">
- <div className="grid grid-cols-1 md:grid-cols-2 gap-12 mb-8">
- {/* Brand Section */}
- <div className="md:col-span-1 max-w-sm">
- <div className="flex items-center gap-2 mb-4">
- <Image src="/logo.svg" alt="OpenCut" width={24} height={24} />
- <span className="font-bold text-lg">OpenCut</span>
- </div>
- <p className="text-sm text-muted-foreground mb-5">
- The open source video editor that gets the job done. Simple,
- powerful, and works on any platform.
- </p>
- <div className="flex gap-3">
- <Link
- href="https://github.com/OpenCut-app/OpenCut"
- className="text-muted-foreground hover:text-foreground transition-colors"
- target="_blank"
- rel="noopener noreferrer"
- >
- <RiGithubLine className="h-5 w-5" />
- </Link>
- <Link
- href="https://x.com/OpenCutApp"
- className="text-muted-foreground hover:text-foreground transition-colors"
- target="_blank"
- rel="noopener noreferrer"
- >
- <RiTwitterXLine className="h-5 w-5" />
- </Link>
- </div>
- </div>
-
- <div className="flex gap-12 justify-end items-start py-2">
- <div>
- <h3 className="font-semibold text-foreground mb-4">Resources</h3>
- <ul className="space-y-2 text-sm">
- <li>
- <Link
- href="/privacy"
- className="text-muted-foreground hover:text-foreground transition-colors"
- >
- Privacy policy
- </Link>
- </li>
- <li>
- <Link
- href="/terms"
- className="text-muted-foreground hover:text-foreground transition-colors"
- >
- Terms of use
- </Link>
- </li>
- </ul>
- </div>
-
- {/* Company Links */}
- <div>
- <h3 className="font-semibold text-foreground mb-4">Company</h3>
- <ul className="space-y-2 text-sm">
- <li>
- <Link
- href="/contributors"
- className="text-muted-foreground hover:text-foreground transition-colors"
- >
- Contributors
- </Link>
- </li>
- <li>
- <Link
- href="https://github.com/OpenCut-app/OpenCut/blob/main/README.md"
- className="text-muted-foreground hover:text-foreground transition-colors"
- target="_blank"
- rel="noopener noreferrer"
- >
- About
- </Link>
- </li>
- </ul>
- </div>
- </div>
- </div>
-
- {/* Bottom Section */}
- <div className="pt-2 flex flex-col md:flex-row justify-between items-center gap-4">
- <div className="flex items-center gap-4 text-sm text-muted-foreground">
- <span>© 2025 OpenCut, All Rights Reserved</span>
- </div>
- </div>
- </div>
- </motion.footer>
- );
-}
+"use client"; + +import { motion } from "motion/react"; +import Link from "next/link"; +import { useEffect, useState } from "react"; +import { RiDiscordFill, RiGithubLine, RiTwitterXLine } from "react-icons/ri"; +import { getStars } from "@/lib/fetch-github-stars"; +import Image from "next/image"; + +export function Footer() { + const [star, setStar] = useState<string>(); + + useEffect(() => { + const fetchStars = async () => { + try { + const data = await getStars(); + setStar(data); + } catch (err) { + console.error("Failed to fetch GitHub stars", err); + } + }; + + fetchStars(); + }, []); + + return ( + <motion.footer + className="bg-background border-t" + initial={{ opacity: 0 }} + animate={{ opacity: 1 }} + transition={{ delay: 0.8, duration: 0.8 }} + > + <div className="max-w-5xl mx-auto px-8 py-10"> + <div className="grid grid-cols-1 md:grid-cols-2 gap-12 mb-8"> + {/* Brand Section */} + <div className="md:col-span-1 max-w-sm"> + <div className="flex items-center gap-2 mb-4"> + <Image src="/logo.svg" alt="OpenCut" width={24} height={24} /> + <span className="font-bold text-lg">OpenCut</span> + </div> + <p className="text-sm text-muted-foreground mb-5"> + The open source video editor that gets the job done. Simple, + powerful, and works on any platform. + </p> + <div className="flex gap-3"> + <Link + href="https://github.com/OpenCut-app/OpenCut" + className="text-muted-foreground hover:text-foreground transition-colors" + target="_blank" + rel="noopener noreferrer" + > + <RiGithubLine className="h-5 w-5" /> + </Link> + <Link + href="https://x.com/OpenCutApp" + className="text-muted-foreground hover:text-foreground transition-colors" + target="_blank" + rel="noopener noreferrer" + > + <RiTwitterXLine className="h-5 w-5" /> + </Link> + <Link + href="https://discord.com/invite/Mu3acKZvCp" + className="text-muted-foreground hover:text-foreground transition-colors" + target="_blank" + rel="noopener noreferrer" + > + <RiDiscordFill className="h-5 w-5" /> + </Link> + </div> + </div> + + <div className="flex gap-12 justify-end items-start py-2"> + <div> + <h3 className="font-semibold text-foreground mb-4">Resources</h3> + <ul className="space-y-2 text-sm"> + <li> + <Link + href="/roadmap" + className="text-muted-foreground hover:text-foreground transition-colors" + > + Roadmap + </Link> + </li> + <li> + <Link + href="/privacy" + className="text-muted-foreground hover:text-foreground transition-colors" + > + Privacy policy + </Link> + </li> + <li> + <Link + href="/terms" + className="text-muted-foreground hover:text-foreground transition-colors" + > + Terms of use + </Link> + </li> + </ul> + </div> + + {/* Company Links */} + <div> + <h3 className="font-semibold text-foreground mb-4">Company</h3> + <ul className="space-y-2 text-sm"> + <li> + <Link + href="/contributors" + className="text-muted-foreground hover:text-foreground transition-colors" + > + Contributors + </Link> + </li> + <li> + <Link + href="https://github.com/OpenCut-app/OpenCut/blob/main/README.md" + className="text-muted-foreground hover:text-foreground transition-colors" + target="_blank" + rel="noopener noreferrer" + > + About + </Link> + </li> + </ul> + </div> + </div> + </div> + + {/* Bottom Section */} + <div className="pt-2 flex flex-col md:flex-row justify-between items-center gap-4"> + <div className="flex items-center gap-4 text-sm text-muted-foreground"> + <span>© 2025 OpenCut, All Rights Reserved</span> + </div> + </div> + </div> + </motion.footer> + ); +} |
