Skip to content
Closed
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
115 changes: 92 additions & 23 deletions apps/web/src/components/editor/media-panel/views/media.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
Music,
Search,
Video,
ZoomIn,
ZoomOut,
} from "lucide-react";
import { useEffect, useRef, useState, useMemo } from "react";
import { toast } from "sonner";
Expand All @@ -34,6 +36,7 @@ import { DraggableMediaItem } from "@/components/ui/draggable-item";
import { useProjectStore } from "@/stores/project-store";
import { useTimelineStore } from "@/stores/timeline-store";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Slider } from "@/components/ui/slider";
import {
Tooltip,
TooltipContent,
Expand Down Expand Up @@ -76,6 +79,12 @@ export function MediaView() {
const [progress, setProgress] = useState(0);
const [searchQuery, setSearchQuery] = useState("");
const [mediaFilter, setMediaFilter] = useState("all");
// Get media size from localStorage or default to 3
const [mediaSize, setMediaSize] = useState(() => {
const stored = localStorage.getItem("mediaSize");
const parsed = parseInt(stored ?? "", 10);
return !isNaN(parsed) && parsed >= 1 && parsed <= 5 ? parsed : 3;
});
const [sortBy, setSortBy] = useState<"name" | "type" | "duration" | "size">(
"name"
);
Expand Down Expand Up @@ -109,6 +118,23 @@ export function MediaView() {
}
};

const handleMediaSizeChange = (size: number) => {
setMediaSize(size);
localStorage.setItem("mediaSize", size.toString());
};

const handleSizeIncrease = () => {
handleMediaSizeChange(Math.min(5, mediaSize + 1));
};

const handleSizeDecrease = () => {
handleMediaSizeChange(Math.max(1, mediaSize - 1));
};

const handleSizeSliderChange = (values: number[]) => {
handleMediaSizeChange(values[0]);
};

const { isDragOver, dragProps } = useDragDrop({
// When files are dropped, process them
onDrop: processFiles,
Expand Down Expand Up @@ -145,7 +171,7 @@ export function MediaView() {
const [filteredMediaItems, setFilteredMediaItems] = useState(mediaItems);

useEffect(() => {
let filtered = mediaItems.filter((item) => {
const filtered = mediaItems.filter((item) => {
if (mediaFilter && mediaFilter !== "all" && item.type !== mediaFilter) {
return false;
}
Expand Down Expand Up @@ -306,6 +332,32 @@ export function MediaView() {
<span>Upload</span>
</Button>
<div className="flex items-center gap-0">
{mediaViewMode === "grid" && (
<div className="flex items-center gap-1">
<Button
variant="text"
size="icon"
onClick={handleSizeDecrease}
>
<ZoomOut className="h-4 w-4" />
</Button>
<Slider
className="w-16"
value={[mediaSize]}
onValueChange={handleSizeSliderChange}
min={1}
max={5}
step={1}
/>
<Button
variant="text"
size="icon"
onClick={handleSizeIncrease}
>
<ZoomIn className="h-4 w-4" />
</Button>
</div>
)}
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
Expand Down Expand Up @@ -446,6 +498,7 @@ export function MediaView() {
/>
) : mediaViewMode === "grid" ? (
<GridView
mediaSize={mediaSize}
filteredMediaItems={filteredMediaItems}
renderPreview={renderPreview}
handleRemove={handleRemove}
Expand All @@ -469,16 +522,29 @@ function GridView({
filteredMediaItems,
renderPreview,
handleRemove,
mediaSize,
}: {
filteredMediaItems: MediaItem[];
renderPreview: (item: MediaItem) => React.ReactNode;
handleRemove: (e: React.MouseEvent, id: string) => Promise<void>;
mediaSize: number;
}) {
// Map size levels (1-5) to appropriate grid item widths
const getGridItemWidth = (size: number) => {
const sizeMap = {
1: 90,
2: 120,
3: 160,
4: 190,
5: 220,
};
return sizeMap[size as keyof typeof sizeMap] || 120;
};
return (
<div
className="grid gap-2"
className="grid gap-x-5 gap-y-3"
style={{
gridTemplateColumns: "repeat(auto-fill, 160px)",
gridTemplateColumns: `repeat(auto-fill, ${getGridItemWidth(mediaSize)}px)`,
}}
>
{filteredMediaItems.map((item) => (
Expand All @@ -488,6 +554,7 @@ function GridView({
onRemove={handleRemove}
>
<DraggableMediaItem
size={mediaSize}
name={item.name}
preview={renderPreview(item)}
dragData={{
Expand Down Expand Up @@ -522,26 +589,28 @@ function ListView({
return (
<div className="space-y-1">
{filteredMediaItems.map((item) => (
<MediaItemWithContextMenu
key={item.id}
item={item}
onRemove={handleRemove}
>
<DraggableMediaItem
name={item.name}
preview={renderPreview(item)}
dragData={{
id: item.id,
type: item.type,
name: item.name,
}}
showPlusOnDrag={false}
onAddToTimeline={(currentTime) =>
useTimelineStore.getState().addMediaAtTime(item, currentTime)
}
variant="compact"
/>
</MediaItemWithContextMenu>
<div>
<MediaItemWithContextMenu
key={item.id}
item={item}
onRemove={handleRemove}
>
<DraggableMediaItem
name={item.name}
preview={renderPreview(item)}
dragData={{
id: item.id,
type: item.type,
name: item.name,
}}
showPlusOnDrag={false}
onAddToTimeline={(currentTime) =>
useTimelineStore.getState().addMediaAtTime(item, currentTime)
}
variant="compact"
/>
</MediaItemWithContextMenu>
</div>
))}
</div>
);
Expand Down
32 changes: 26 additions & 6 deletions apps/web/src/components/ui/draggable-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ export interface DraggableMediaItemProps {
showPlusOnDrag?: boolean;
showLabel?: boolean;
rounded?: boolean;
/**
* media item size
*/
size?: number;
variant?: "card" | "compact";
}

Expand All @@ -38,6 +42,7 @@ export function DraggableMediaItem({
showPlusOnDrag = true,
showLabel = true,
rounded = true,
size,
variant = "card",
}: DraggableMediaItemProps) {
const [isDragging, setIsDragging] = useState(false);
Expand Down Expand Up @@ -88,10 +93,26 @@ export function DraggableMediaItem({
setIsDragging(false);
};

const getMediaTitle = (
name: string
): [fileName: string, extension: string] => {
const lastDotIndex = name.lastIndexOf(".");
if (lastDotIndex > 0 && name.length - lastDotIndex <= 5) {
// Split at the last dot if extension is 4 chars or less
return [name.substring(0, lastDotIndex), name.substring(lastDotIndex)];
}
// No extension or very long extension, return full name
return [name, ""];
};

const [fileName, extension] = getMediaTitle(name);
return (
<>
{variant === "card" ? (
<div ref={dragRef} className="relative group w-28 h-28">
<div
ref={dragRef}
className={cn("relative group", size ? "size-full" : "size-28")}
>
<div
className={`flex flex-col gap-1 p-0 h-auto w-full relative cursor-default ${className}`}
>
Expand All @@ -116,13 +137,12 @@ export function DraggableMediaItem({
</AspectRatio>
{showLabel && (
<span
className="text-[0.7rem] text-muted-foreground truncate w-full text-left"
className="text-[0.7rem] text-muted-foreground w-full text-left flex"
aria-label={name}
title={name}
>
{name.length > 8
? `${name.slice(0, 16)}...${name.slice(-3)}`
: name}
<span className="truncate max-w-full">{fileName}</span>
{extension && extension}
</span>
)}
</div>
Expand All @@ -139,7 +159,7 @@ export function DraggableMediaItem({
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
>
<div className="w-6 h-6 flex-shrink-0 rounded overflow-hidden">
<div className="flex-shrink-0 rounded overflow-hidden size-6">
{preview}
</div>
<span className="text-sm truncate flex-1 w-full">{name}</span>
Expand Down