mirror of
https://github.com/OpenCut-app/OpenCut.git
synced 2026-07-13 21:52:53 +02:00
refactor: type-safety & separate concerns
This commit is contained in:
@@ -6,6 +6,7 @@ import { useState, useRef, useEffect } from "react";
|
||||
import { extractTimelineAudio } from "@/lib/mediabunny-utils";
|
||||
import { encryptWithRandomKey, arrayBufferToBase64 } from "@/lib/zk-encryption";
|
||||
import { useTimelineStore } from "@/stores/timeline-store";
|
||||
import { DEFAULT_TEXT_ELEMENT } from "@/constants/text-constants";
|
||||
import { Loader2, Shield, Trash2, Upload } from "lucide-react";
|
||||
import {
|
||||
Dialog,
|
||||
@@ -162,24 +163,13 @@ export function Captions() {
|
||||
// Add all caption elements to the same track
|
||||
shortCaptions.forEach((caption, index) => {
|
||||
addElementToTrack(captionTrackId, {
|
||||
type: "text",
|
||||
...DEFAULT_TEXT_ELEMENT,
|
||||
name: `Caption ${index + 1}`,
|
||||
content: caption.text,
|
||||
duration: caption.duration,
|
||||
startTime: caption.startTime,
|
||||
trimStart: 0,
|
||||
trimEnd: 0,
|
||||
fontSize: 65,
|
||||
fontFamily: "Arial",
|
||||
color: "#ffffff",
|
||||
textAlign: "center",
|
||||
fontWeight: "bold",
|
||||
fontStyle: "normal",
|
||||
textDecoration: "none",
|
||||
x: 0,
|
||||
y: 0,
|
||||
rotation: 0,
|
||||
opacity: 1,
|
||||
fontSize: 65, // Larger for captions
|
||||
fontWeight: "bold", // Bold for captions
|
||||
} as TextElement);
|
||||
});
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
import { useDragDrop } from "@/hooks/use-drag-drop";
|
||||
import { processMediaFiles } from "@/lib/media-processing";
|
||||
import { useMediaStore, type MediaItem } from "@/stores/media-store";
|
||||
import { useMediaStore } from "@/stores/media-store";
|
||||
import { MediaFile } from "@/types/media";
|
||||
import {
|
||||
ArrowDown01,
|
||||
CloudUpload,
|
||||
@@ -13,7 +14,7 @@ import {
|
||||
Music,
|
||||
Video,
|
||||
} from "lucide-react";
|
||||
import { useRef, useState, useMemo, useEffect } from "react";
|
||||
import { useRef, useState, useMemo } from "react";
|
||||
import { useHighlightScroll } from "@/hooks/use-highlight-scroll";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -47,7 +48,7 @@ function MediaItemWithContextMenu({
|
||||
children,
|
||||
onRemove,
|
||||
}: {
|
||||
item: MediaItem;
|
||||
item: MediaFile;
|
||||
children: React.ReactNode;
|
||||
onRemove: (e: React.MouseEvent, id: string) => Promise<void>;
|
||||
}) {
|
||||
@@ -68,14 +69,12 @@ function MediaItemWithContextMenu({
|
||||
}
|
||||
|
||||
export function MediaView() {
|
||||
const { mediaItems, addMediaItem, removeMediaItem } = useMediaStore();
|
||||
const { mediaFiles, addMediaFile, removeMediaFile } = useMediaStore();
|
||||
const { activeProject } = useProjectStore();
|
||||
const { mediaViewMode, setMediaViewMode } = usePanelStore();
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const [isProcessing, setIsProcessing] = useState(false);
|
||||
const [progress, setProgress] = useState(0);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [mediaFilter, setMediaFilter] = useState("all");
|
||||
const [sortBy, setSortBy] = useState<"name" | "type" | "duration" | "size">(
|
||||
"name"
|
||||
);
|
||||
@@ -96,16 +95,13 @@ export function MediaView() {
|
||||
setIsProcessing(true);
|
||||
setProgress(0);
|
||||
try {
|
||||
// Process files (extract metadata, generate thumbnails, etc.)
|
||||
const processedItems = await processMediaFiles(files, (p) =>
|
||||
setProgress(p)
|
||||
);
|
||||
// Add each processed media item to the store
|
||||
for (const item of processedItems) {
|
||||
await addMediaItem(activeProject.id, item);
|
||||
await addMediaFile(activeProject.id, item);
|
||||
}
|
||||
} catch (error) {
|
||||
// Show error toast if processing fails
|
||||
console.error("Error processing files:", error);
|
||||
toast.error("Failed to process files");
|
||||
} finally {
|
||||
@@ -122,13 +118,11 @@ export function MediaView() {
|
||||
const handleFileSelect = () => fileInputRef.current?.click(); // Open file picker
|
||||
|
||||
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
// When files are selected via file picker, process them
|
||||
if (e.target.files) processFiles(e.target.files);
|
||||
e.target.value = ""; // Reset input
|
||||
};
|
||||
|
||||
const handleRemove = async (e: React.MouseEvent, id: string) => {
|
||||
// Remove a media item from the store
|
||||
e.stopPropagation();
|
||||
|
||||
if (!activeProject) {
|
||||
@@ -136,8 +130,7 @@ export function MediaView() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Media store now handles cascade deletion automatically
|
||||
await removeMediaItem(activeProject.id, id);
|
||||
await removeMediaFile(activeProject.id, id);
|
||||
};
|
||||
|
||||
const formatDuration = (duration: number) => {
|
||||
@@ -147,28 +140,15 @@ export function MediaView() {
|
||||
return `${min}:${sec.toString().padStart(2, "0")}`;
|
||||
};
|
||||
|
||||
const [filteredMediaItems, setFilteredMediaItems] = useState(mediaItems);
|
||||
|
||||
useEffect(() => {
|
||||
let filtered = mediaItems.filter((item) => {
|
||||
const filteredMediaItems = useMemo(() => {
|
||||
let filtered = mediaFiles.filter((item) => {
|
||||
if (item.ephemeral) return false;
|
||||
if (mediaFilter && mediaFilter !== "all" && item.type !== mediaFilter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
searchQuery &&
|
||||
!item.name.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
filtered.sort((a, b) => {
|
||||
let valueA: any;
|
||||
let valueB: any;
|
||||
let valueA: string | number;
|
||||
let valueB: string | number;
|
||||
|
||||
switch (sortBy) {
|
||||
case "name":
|
||||
@@ -196,8 +176,8 @@ export function MediaView() {
|
||||
return 0;
|
||||
});
|
||||
|
||||
setFilteredMediaItems(filtered);
|
||||
}, [mediaItems, mediaFilter, searchQuery, sortBy, sortOrder]);
|
||||
return filtered;
|
||||
}, [mediaFiles, sortBy, sortOrder]);
|
||||
|
||||
const previewComponents = useMemo(() => {
|
||||
const previews = new Map<string, React.ReactNode>();
|
||||
@@ -276,7 +256,7 @@ export function MediaView() {
|
||||
return previews;
|
||||
}, [filteredMediaItems]);
|
||||
|
||||
const renderPreview = (item: MediaItem) => previewComponents.get(item.id);
|
||||
const renderPreview = (item: MediaFile) => previewComponents.get(item.id);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -481,12 +461,14 @@ function GridView({
|
||||
highlightedId,
|
||||
registerElement,
|
||||
}: {
|
||||
filteredMediaItems: MediaItem[];
|
||||
renderPreview: (item: MediaItem) => React.ReactNode;
|
||||
filteredMediaItems: MediaFile[];
|
||||
renderPreview: (item: MediaFile) => React.ReactNode;
|
||||
handleRemove: (e: React.MouseEvent, id: string) => Promise<void>;
|
||||
highlightedId: string | null;
|
||||
registerElement: (id: string, element: HTMLElement | null) => void;
|
||||
}) {
|
||||
const { addElementAtTime } = useTimelineStore();
|
||||
|
||||
return (
|
||||
<div
|
||||
className="grid gap-2"
|
||||
@@ -507,7 +489,7 @@ function GridView({
|
||||
}}
|
||||
showPlusOnDrag={false}
|
||||
onAddToTimeline={(currentTime) =>
|
||||
useTimelineStore.getState().addMediaAtTime(item, currentTime)
|
||||
addElementAtTime(item, currentTime)
|
||||
}
|
||||
rounded={false}
|
||||
variant="card"
|
||||
@@ -527,12 +509,14 @@ function ListView({
|
||||
highlightedId,
|
||||
registerElement,
|
||||
}: {
|
||||
filteredMediaItems: MediaItem[];
|
||||
renderPreview: (item: MediaItem) => React.ReactNode;
|
||||
filteredMediaItems: MediaFile[];
|
||||
renderPreview: (item: MediaFile) => React.ReactNode;
|
||||
handleRemove: (e: React.MouseEvent, id: string) => Promise<void>;
|
||||
highlightedId: string | null;
|
||||
registerElement: (id: string, element: HTMLElement | null) => void;
|
||||
}) {
|
||||
const { addElementAtTime } = useTimelineStore();
|
||||
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
{filteredMediaItems.map((item) => (
|
||||
@@ -548,7 +532,7 @@ function ListView({
|
||||
}}
|
||||
showPlusOnDrag={false}
|
||||
onAddToTimeline={(currentTime) =>
|
||||
useTimelineStore.getState().addMediaAtTime(item, currentTime)
|
||||
addElementAtTime(item, currentTime)
|
||||
}
|
||||
variant="compact"
|
||||
isHighlighted={highlightedId === item.id}
|
||||
|
||||
@@ -35,10 +35,10 @@ import {
|
||||
ICONIFY_HOSTS,
|
||||
POPULAR_COLLECTIONS,
|
||||
} from "@/lib/iconify-api";
|
||||
import { cn, generateUUID } from "@/lib/utils";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants";
|
||||
import Image from "next/image";
|
||||
import type { MediaItem } from "@/stores/media-store";
|
||||
import type { MediaFile } from "@/types/media";
|
||||
import { DraggableMediaItem } from "@/components/ui/draggable-item";
|
||||
import { InputWithBack } from "@/components/ui/input-with-back";
|
||||
import { StickerCategory } from "@/stores/stickers-store";
|
||||
@@ -186,9 +186,9 @@ function EmptyView({ message }: { message: string }) {
|
||||
|
||||
function StickersContentView({ category }: { category: StickerCategory }) {
|
||||
const { activeProject } = useProjectStore();
|
||||
const { addMediaAtTime } = useTimelineStore();
|
||||
const { addElementAtTime } = useTimelineStore();
|
||||
const { currentTime } = usePlaybackStore();
|
||||
const { addMediaItem } = useMediaStore();
|
||||
const { addMediaFile } = useMediaStore();
|
||||
const {
|
||||
searchQuery,
|
||||
selectedCollection,
|
||||
@@ -287,7 +287,7 @@ function StickersContentView({ category }: { category: StickerCategory }) {
|
||||
throw new Error("Failed to download sticker");
|
||||
}
|
||||
|
||||
const mediaItem: Omit<MediaItem, "id"> = {
|
||||
const mediaItem: Omit<MediaFile, "id"> = {
|
||||
name: iconName.replace(":", "-"),
|
||||
type: "image",
|
||||
file,
|
||||
@@ -298,16 +298,16 @@ function StickersContentView({ category }: { category: StickerCategory }) {
|
||||
ephemeral: false,
|
||||
};
|
||||
|
||||
await addMediaItem(activeProject.id, mediaItem);
|
||||
await addMediaFile(activeProject.id, mediaItem);
|
||||
|
||||
const added = useMediaStore
|
||||
.getState()
|
||||
.mediaItems.find(
|
||||
.mediaFiles.find(
|
||||
(m) => m.url === mediaItem.url && m.name === mediaItem.name
|
||||
);
|
||||
if (!added) throw new Error("Sticker not in media store");
|
||||
|
||||
addMediaAtTime(added, currentTime);
|
||||
addElementAtTime(added, currentTime);
|
||||
|
||||
toast.success(`Added "${iconName}" to timeline`);
|
||||
} catch (error) {
|
||||
@@ -640,8 +640,8 @@ function StickerItem({
|
||||
name={displayName}
|
||||
preview={preview}
|
||||
dragData={{
|
||||
type: "sticker",
|
||||
iconName: iconName,
|
||||
id: "sticker-placeholder",
|
||||
type: "image",
|
||||
name: displayName,
|
||||
}}
|
||||
onAddToTimeline={() => onAdd(iconName)}
|
||||
|
||||
@@ -1,31 +1,7 @@
|
||||
import { DraggableMediaItem } from "@/components/ui/draggable-item";
|
||||
import { BaseView } from "./base-view";
|
||||
import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants";
|
||||
import { useTimelineStore } from "@/stores/timeline-store";
|
||||
import { type TextElement } from "@/types/timeline";
|
||||
|
||||
const textData: TextElement = {
|
||||
id: "default-text",
|
||||
type: "text",
|
||||
name: "Default text",
|
||||
content: "Default text",
|
||||
fontSize: 48,
|
||||
fontFamily: "Arial",
|
||||
color: "#ffffff",
|
||||
backgroundColor: "transparent",
|
||||
textAlign: "center" as const,
|
||||
fontWeight: "normal" as const,
|
||||
fontStyle: "normal" as const,
|
||||
textDecoration: "none" as const,
|
||||
x: 0,
|
||||
y: 0,
|
||||
rotation: 0,
|
||||
opacity: 1,
|
||||
duration: TIMELINE_CONSTANTS.DEFAULT_TEXT_DURATION,
|
||||
startTime: 0,
|
||||
trimStart: 0,
|
||||
trimEnd: 0,
|
||||
};
|
||||
import { DEFAULT_TEXT_ELEMENT } from "@/constants/text-constants";
|
||||
|
||||
export function TextView() {
|
||||
return (
|
||||
@@ -38,14 +14,20 @@ export function TextView() {
|
||||
</div>
|
||||
}
|
||||
dragData={{
|
||||
id: textData.id,
|
||||
type: textData.type,
|
||||
name: textData.name,
|
||||
content: textData.content,
|
||||
id: "temp-text-id",
|
||||
type: DEFAULT_TEXT_ELEMENT.type,
|
||||
name: DEFAULT_TEXT_ELEMENT.name,
|
||||
content: DEFAULT_TEXT_ELEMENT.content,
|
||||
}}
|
||||
aspectRatio={1}
|
||||
onAddToTimeline={(currentTime) =>
|
||||
useTimelineStore.getState().addTextAtTime(textData, currentTime)
|
||||
useTimelineStore.getState().addElementAtTime(
|
||||
{
|
||||
...DEFAULT_TEXT_ELEMENT,
|
||||
id: "temp-text-id",
|
||||
},
|
||||
currentTime
|
||||
)
|
||||
}
|
||||
showLabel={false}
|
||||
/>
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
import { useTimelineStore } from "@/stores/timeline-store";
|
||||
import { TimelineElement, TimelineTrack } from "@/types/timeline";
|
||||
import { useMediaStore, type MediaItem } from "@/stores/media-store";
|
||||
import { useMediaStore } from "@/stores/media-store";
|
||||
import { MediaFile } from "@/types/media";
|
||||
import { usePlaybackStore } from "@/stores/playback-store";
|
||||
import { useEditorStore } from "@/stores/editor-store";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Play, Pause, Expand, SkipBack, SkipForward } from "lucide-react";
|
||||
import { useState, useRef, useEffect, useCallback } from "react";
|
||||
import { VideoSampleSink } from "mediabunny";
|
||||
import { renderTimelineFrame } from "@/lib/timeline-renderer";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { formatTimeCode } from "@/lib/time";
|
||||
@@ -33,12 +33,12 @@ import { PLATFORM_LAYOUTS, type PlatformLayout } from "@/stores/editor-store";
|
||||
interface ActiveElement {
|
||||
element: TimelineElement;
|
||||
track: TimelineTrack;
|
||||
mediaItem: MediaItem | null;
|
||||
mediaItem: MediaFile | null;
|
||||
}
|
||||
|
||||
export function PreviewPanel() {
|
||||
const { tracks, getTotalDuration, updateTextElement } = useTimelineStore();
|
||||
const { mediaItems } = useMediaStore();
|
||||
const { mediaFiles } = useMediaStore();
|
||||
const { currentTime, toggle, setCurrentTime } = usePlaybackStore();
|
||||
const { isPlaying, volume, muted } = usePlaybackStore();
|
||||
const { activeProject } = useProjectStore();
|
||||
@@ -264,7 +264,7 @@ export function PreviewPanel() {
|
||||
mediaItem =
|
||||
element.mediaId === "test"
|
||||
? null
|
||||
: mediaItems.find((item) => item.id === element.mediaId) ||
|
||||
: mediaFiles.find((item) => item.id === element.mediaId) ||
|
||||
null;
|
||||
}
|
||||
activeElements.push({ element, track, mediaItem });
|
||||
@@ -334,7 +334,7 @@ export function PreviewPanel() {
|
||||
const gain = audioGainRef.current!;
|
||||
|
||||
const tracksSnapshot = useTimelineStore.getState().tracks;
|
||||
const mediaList = useMediaStore.getState().mediaItems;
|
||||
const mediaList = mediaFiles;
|
||||
const idToMedia = new Map(mediaList.map((m) => [m.id, m] as const));
|
||||
const playbackNow = usePlaybackStore.getState().currentTime;
|
||||
|
||||
@@ -447,7 +447,7 @@ export function PreviewPanel() {
|
||||
}
|
||||
playingSourcesRef.current.clear();
|
||||
};
|
||||
}, [isPlaying, volume, muted, mediaItems]);
|
||||
}, [isPlaying, volume, muted, mediaFiles]);
|
||||
|
||||
// Canvas: draw current frame for visible elements using offscreen compositing
|
||||
useEffect(() => {
|
||||
@@ -533,13 +533,11 @@ export function PreviewPanel() {
|
||||
canvasWidth: displayWidth,
|
||||
canvasHeight: displayHeight,
|
||||
tracks,
|
||||
mediaItems,
|
||||
mediaFiles,
|
||||
backgroundColor:
|
||||
activeProject?.backgroundType === "blur"
|
||||
? "transparent"
|
||||
: activeProject?.backgroundColor || "#000000",
|
||||
useCache: false,
|
||||
fps: activeProject?.fps || DEFAULT_FPS,
|
||||
});
|
||||
|
||||
// Blit offscreen to visible canvas
|
||||
|
||||
@@ -10,7 +10,7 @@ import { SquareSlashIcon } from "lucide-react";
|
||||
|
||||
export function PropertiesPanel() {
|
||||
const { selectedElements, tracks } = useTimelineStore();
|
||||
const { mediaItems } = useMediaStore();
|
||||
const { mediaFiles } = useMediaStore();
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -28,11 +28,11 @@ export function PropertiesPanel() {
|
||||
);
|
||||
}
|
||||
if (element?.type === "media") {
|
||||
const mediaItem = mediaItems.find(
|
||||
(item) => item.id === element.mediaId
|
||||
const mediaFile = mediaFiles.find(
|
||||
(file) => file.id === element.mediaId
|
||||
);
|
||||
|
||||
if (mediaItem?.type === "audio") {
|
||||
if (mediaFile?.type === "audio") {
|
||||
return <AudioProperties key={elementId} element={element} />;
|
||||
}
|
||||
|
||||
|
||||
@@ -82,13 +82,11 @@ export function Timeline() {
|
||||
toggleTrackMute,
|
||||
dragState,
|
||||
} = useTimelineStore();
|
||||
const { mediaItems, addMediaItem } = useMediaStore();
|
||||
const { mediaFiles, addMediaFile } = useMediaStore();
|
||||
const { activeProject } = useProjectStore();
|
||||
const { currentTime, duration, seek, setDuration, isPlaying, toggle } =
|
||||
usePlaybackStore();
|
||||
const { currentTime, duration, seek, setDuration } = usePlaybackStore();
|
||||
const [isDragOver, setIsDragOver] = useState(false);
|
||||
const [isProcessing, setIsProcessing] = useState(false);
|
||||
const [progress, setProgress] = useState(0);
|
||||
const { addElementToNewTrack } = useTimelineStore();
|
||||
const dragCounterRef = useRef(0);
|
||||
const timelineRef = useRef<HTMLDivElement>(null);
|
||||
const rulerRef = useRef<HTMLDivElement>(null);
|
||||
@@ -456,10 +454,10 @@ export function Timeline() {
|
||||
|
||||
if (dragData.type === "text") {
|
||||
// Always create new text track to avoid overlaps
|
||||
useTimelineStore.getState().addTextToNewTrack(dragData);
|
||||
addElementToNewTrack(dragData);
|
||||
} else {
|
||||
// Handle media items
|
||||
const mediaItem = mediaItems.find(
|
||||
const mediaItem = mediaFiles.find(
|
||||
(item: any) => item.id === dragData.id
|
||||
);
|
||||
if (!mediaItem) {
|
||||
@@ -467,7 +465,7 @@ export function Timeline() {
|
||||
return;
|
||||
}
|
||||
|
||||
useTimelineStore.getState().addMediaToNewTrack(mediaItem);
|
||||
addElementToNewTrack(mediaItem);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error parsing dropped item data:", error);
|
||||
@@ -480,17 +478,12 @@ export function Timeline() {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsProcessing(true);
|
||||
setProgress(0);
|
||||
try {
|
||||
const processedItems = await processMediaFiles(
|
||||
e.dataTransfer.files,
|
||||
(p) => setProgress(p)
|
||||
);
|
||||
const processedItems = await processMediaFiles(e.dataTransfer.files);
|
||||
for (const processedItem of processedItems) {
|
||||
await addMediaItem(activeProject.id, processedItem);
|
||||
const currentMediaItems = useMediaStore.getState().mediaItems;
|
||||
const addedItem = currentMediaItems.find(
|
||||
await addMediaFile(activeProject.id, processedItem);
|
||||
const currentMediaFiles = mediaFiles;
|
||||
const addedItem = currentMediaFiles.find(
|
||||
(item) =>
|
||||
item.name === processedItem.name && item.url === processedItem.url
|
||||
);
|
||||
@@ -516,9 +509,6 @@ export function Timeline() {
|
||||
// Show error if file processing fails
|
||||
console.error("Error processing external files:", error);
|
||||
toast.error("Failed to process dropped files");
|
||||
} finally {
|
||||
setIsProcessing(false);
|
||||
setProgress(0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -40,10 +40,8 @@ export function TimelineElement({
|
||||
onElementMouseDown,
|
||||
onElementClick,
|
||||
}: TimelineElementProps) {
|
||||
const { mediaItems } = useMediaStore();
|
||||
const { mediaFiles } = useMediaStore();
|
||||
const {
|
||||
updateElementTrim,
|
||||
updateElementDuration,
|
||||
removeElementFromTrack,
|
||||
removeElementFromTrackWithRipple,
|
||||
dragState,
|
||||
@@ -58,7 +56,7 @@ export function TimelineElement({
|
||||
|
||||
const mediaItem =
|
||||
element.type === "media"
|
||||
? mediaItems.find((item) => item.id === element.mediaId)
|
||||
? mediaFiles.find((file) => file.id === element.mediaId)
|
||||
: null;
|
||||
const hasAudio = mediaItem?.type === "audio" || mediaItem?.type === "video";
|
||||
|
||||
@@ -67,8 +65,6 @@ export function TimelineElement({
|
||||
element,
|
||||
track,
|
||||
zoomLevel,
|
||||
onUpdateTrim: updateElementTrim,
|
||||
onUpdateDuration: updateElementDuration,
|
||||
});
|
||||
|
||||
const { requestRevealMedia } = useMediaPanelStore.getState();
|
||||
@@ -190,7 +186,7 @@ export function TimelineElement({
|
||||
}
|
||||
|
||||
// Render media element ->
|
||||
const mediaItem = mediaItems.find((item) => item.id === element.mediaId);
|
||||
const mediaItem = mediaFiles.find((file) => file.id === element.mediaId);
|
||||
if (!mediaItem) {
|
||||
return (
|
||||
<span className="text-xs text-foreground/80 truncate">
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
canElementGoOnTrack,
|
||||
} from "@/types/timeline";
|
||||
import { usePlaybackStore } from "@/stores/playback-store";
|
||||
import { DEFAULT_TEXT_ELEMENT } from "@/constants/text-constants";
|
||||
import type {
|
||||
TimelineElement as TimelineElementType,
|
||||
DragData,
|
||||
@@ -33,7 +34,7 @@ export function TimelineTrackContent({
|
||||
zoomLevel: number;
|
||||
onSnapPointChange?: (snapPoint: SnapPoint | null) => void;
|
||||
}) {
|
||||
const { mediaItems } = useMediaStore();
|
||||
const { mediaFiles } = useMediaStore();
|
||||
const {
|
||||
tracks,
|
||||
addTrack,
|
||||
@@ -537,7 +538,7 @@ export function TimelineTrackContent({
|
||||
});
|
||||
} else {
|
||||
// Media elements
|
||||
const mediaItem = mediaItems.find(
|
||||
const mediaItem = mediaFiles.find(
|
||||
(item) => item.id === dragData.id
|
||||
);
|
||||
if (mediaItem) {
|
||||
@@ -890,29 +891,14 @@ export function TimelineTrackContent({
|
||||
}
|
||||
|
||||
addElementToTrack(targetTrackId, {
|
||||
type: "text",
|
||||
name: dragData.name || "Text",
|
||||
content: dragData.content || "Default Text",
|
||||
duration: TIMELINE_CONSTANTS.DEFAULT_TEXT_DURATION,
|
||||
...DEFAULT_TEXT_ELEMENT,
|
||||
name: dragData.name || DEFAULT_TEXT_ELEMENT.name,
|
||||
content: dragData.content || DEFAULT_TEXT_ELEMENT.content,
|
||||
startTime: textSnappedTime,
|
||||
trimStart: 0,
|
||||
trimEnd: 0,
|
||||
fontSize: 48,
|
||||
fontFamily: "Arial",
|
||||
color: "#ffffff",
|
||||
backgroundColor: "transparent",
|
||||
textAlign: "center",
|
||||
fontWeight: "normal",
|
||||
fontStyle: "normal",
|
||||
textDecoration: "none",
|
||||
x: 0,
|
||||
y: 0,
|
||||
rotation: 0,
|
||||
opacity: 1,
|
||||
});
|
||||
} else {
|
||||
// Handle media items
|
||||
const mediaItem = mediaItems.find((item) => item.id === dragData.id);
|
||||
const mediaItem = mediaFiles.find((item) => item.id === dragData.id);
|
||||
|
||||
if (!mediaItem) {
|
||||
toast.error("Media item not found");
|
||||
@@ -1054,7 +1040,7 @@ export function TimelineTrackContent({
|
||||
} else if (hasFiles) {
|
||||
// External file drops
|
||||
const { activeProject } = useProjectStore.getState();
|
||||
const { addMediaItem } = useMediaStore.getState();
|
||||
const { addMediaFile } = useMediaStore.getState();
|
||||
const { addElementToTrack } = useTimelineStore.getState();
|
||||
|
||||
if (!activeProject) {
|
||||
@@ -1066,9 +1052,9 @@ export function TimelineTrackContent({
|
||||
processMediaFiles(e.dataTransfer.files)
|
||||
.then(async (processedItems) => {
|
||||
for (const processedItem of processedItems) {
|
||||
await addMediaItem(activeProject.id, processedItem);
|
||||
const currentMediaItems = useMediaStore.getState().mediaItems;
|
||||
const addedItem = currentMediaItems.find(
|
||||
await addMediaFile(activeProject.id, processedItem);
|
||||
const currentMediaFiles = mediaFiles;
|
||||
const addedItem = currentMediaFiles.find(
|
||||
(item) =>
|
||||
item.name === processedItem.name &&
|
||||
item.url === processedItem.url
|
||||
|
||||
Reference in New Issue
Block a user