This commit is contained in:
Maze Winther
2026-03-02 17:33:01 +01:00
+128 -56
View File
@@ -46,6 +46,13 @@ import {
} from "@hugeicons/core-free-icons"; } from "@hugeicons/core-free-icons";
import { OcVideoIcon } from "@opencut/ui/icons"; import { OcVideoIcon } from "@opencut/ui/icons";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
ContextMenuSeparator,
ContextMenuTrigger,
} from "@/components/ui/context-menu";
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuCheckboxItem, DropdownMenuCheckboxItem,
@@ -534,10 +541,25 @@ function ProjectItem({
const isSelected = selectedProjectIdSet.has(project.id); const isSelected = selectedProjectIdSet.has(project.id);
const selectedProjectCount = selectedProjectIds.length; const selectedProjectCount = selectedProjectIds.length;
const [isDropdownOpen, setIsDropdownOpen] = useState(false); const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const [isRenameDialogOpen, setIsRenameDialogOpen] = useState(false);
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
const [isInfoDialogOpen, setIsInfoDialogOpen] = useState(false);
const editor = useEditor();
const durationLabel = formatProjectDuration({ duration: project.duration }); const durationLabel = formatProjectDuration({ duration: project.duration });
const isMultiSelect = selectedProjectCount > 1; const isMultiSelect = selectedProjectCount > 1;
const isGridView = viewMode === "grid"; const isGridView = viewMode === "grid";
const handleRename = () => setIsRenameDialogOpen(true);
const handleDuplicate = async () => {
await duplicateProjects({ editor, ids: [project.id] });
};
const handleDeleteClick = () => setIsDeleteDialogOpen(true);
const handleInfoClick = () => setIsInfoDialogOpen(true);
const handleDeleteConfirm = async () => {
await deleteProjects({ editor, ids: [project.id] });
setIsDeleteDialogOpen(false);
};
const handleCheckboxChange = ({ const handleCheckboxChange = ({
checked, checked,
shiftKey, shiftKey,
@@ -622,8 +644,7 @@ function ProjectItem({
const listContent = ( const listContent = (
<div <div
className={`flex items-center gap-4 py-2 px-4 border-b border-border/50 ${ className={`flex items-center gap-4 py-2 px-4 border-b border-border/50 ${isSelected ? "bg-primary/5" : ""
isSelected ? "bg-primary/5" : ""
}`} }`}
> >
<Checkbox <Checkbox
@@ -635,7 +656,7 @@ function ProjectItem({
shiftKey: event.shiftKey, shiftKey: event.shiftKey,
}); });
}} }}
onCheckedChange={() => {}} onCheckedChange={() => { }}
className="size-5 shrink-0" className="size-5 shrink-0"
/> />
@@ -647,27 +668,27 @@ function ProjectItem({
<ProjectMenu <ProjectMenu
isOpen={isDropdownOpen} isOpen={isDropdownOpen}
onOpenChange={setIsDropdownOpen} onOpenChange={setIsDropdownOpen}
project={project}
variant="list" variant="list"
onRenameClick={handleRename}
onDuplicateClick={handleDuplicate}
onDeleteClick={handleDeleteClick}
onInfoClick={handleInfoClick}
/> />
)} )}
</div> </div>
); );
const cardContent = isGridView ? gridContent : listContent;
if (!isGridView) {
return <div className="group relative">{listContent}</div>;
}
return ( return (
<>
<ContextMenu>
<ContextMenuTrigger asChild>
<div className="group relative"> <div className="group relative">
{isGridView ? (
<>
<Link href={`/editor/${project.id}`} className="block"> <Link href={`/editor/${project.id}`} className="block">
{cardContent} {gridContent}
</Link> </Link>
{isGridView && (
<>
<Checkbox <Checkbox
checked={isSelected} checked={isSelected}
onMouseDown={(event) => event.preventDefault()} onMouseDown={(event) => event.preventDefault()}
@@ -689,31 +710,112 @@ function ProjectItem({
<ProjectMenu <ProjectMenu
isOpen={isDropdownOpen} isOpen={isDropdownOpen}
onOpenChange={setIsDropdownOpen} onOpenChange={setIsDropdownOpen}
project={project} onRenameClick={handleRename}
onDuplicateClick={handleDuplicate}
onDeleteClick={handleDeleteClick}
onInfoClick={handleInfoClick}
/> />
)} )}
</> </>
) : (
listContent
)} )}
</div> </div>
</ContextMenuTrigger>
<ProjectContextMenuContent
onRenameClick={handleRename}
onDuplicateClick={handleDuplicate}
onDeleteClick={handleDeleteClick}
onInfoClick={handleInfoClick}
/>
</ContextMenu>
<RenameProjectDialog
isOpen={isRenameDialogOpen}
onOpenChange={setIsRenameDialogOpen}
projectName={project.name}
onConfirm={async (newName) => {
await renameProject({ editor, id: project.id, name: newName });
setIsRenameDialogOpen(false);
}}
/>
<DeleteProjectDialog
isOpen={isDeleteDialogOpen}
onOpenChange={setIsDeleteDialogOpen}
projectNames={[project.name]}
onConfirm={handleDeleteConfirm}
/>
<ProjectInfoDialog
isOpen={isInfoDialogOpen}
onOpenChange={setIsInfoDialogOpen}
project={project}
/>
</>
);
}
function ProjectContextMenuContent({
onRenameClick,
onDuplicateClick,
onDeleteClick,
onInfoClick,
}: {
onRenameClick: () => void;
onDuplicateClick: () => void;
onDeleteClick: () => void;
onInfoClick: () => void;
}) {
return (
<ContextMenuContent>
<ContextMenuItem
icon={<HugeiconsIcon icon={Edit03Icon} />}
onClick={onRenameClick}
>
Rename
</ContextMenuItem>
<ContextMenuItem
icon={<HugeiconsIcon icon={Copy01Icon} />}
onClick={onDuplicateClick}
>
Duplicate
</ContextMenuItem>
<ContextMenuItem
icon={<HugeiconsIcon icon={InformationCircleIcon} />}
onClick={onInfoClick}
>
Info
</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem
variant="destructive"
icon={<HugeiconsIcon icon={Delete02Icon} />}
onClick={onDeleteClick}
>
Delete
</ContextMenuItem>
</ContextMenuContent>
); );
} }
function ProjectMenu({ function ProjectMenu({
isOpen, isOpen,
onOpenChange, onOpenChange,
project,
variant = "grid", variant = "grid",
onRenameClick,
onDuplicateClick,
onDeleteClick,
onInfoClick,
}: { }: {
isOpen: boolean; isOpen: boolean;
onOpenChange: (open: boolean) => void; onOpenChange: (open: boolean) => void;
project: TProjectMetadata;
variant?: "grid" | "list"; variant?: "grid" | "list";
onRenameClick: () => void;
onDuplicateClick: () => void;
onDeleteClick: () => void;
onInfoClick: () => void;
}) { }) {
const editor = useEditor();
const [isRenameDialogOpen, setIsRenameDialogOpen] = useState(false);
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
const [isInfoDialogOpen, setIsInfoDialogOpen] = useState(false);
const handleMenuClick = ({ const handleMenuClick = ({
event, event,
}: { }: {
@@ -736,34 +838,28 @@ function ProjectMenu({
}; };
const handleRename = () => { const handleRename = () => {
setIsRenameDialogOpen(true); onRenameClick();
onOpenChange(false); onOpenChange(false);
}; };
const handleDuplicate = async () => { const handleDuplicate = () => {
await duplicateProjects({ editor, ids: [project.id] }); onDuplicateClick();
onOpenChange(false); onOpenChange(false);
}; };
const handleDeleteClick = () => { const handleDeleteClick = () => {
setIsDeleteDialogOpen(true); onDeleteClick();
onOpenChange(false); onOpenChange(false);
}; };
const handleDeleteConfirm = async () => {
await deleteProjects({ editor, ids: [project.id] });
setIsDeleteDialogOpen(false);
};
const handleInfoClick = () => { const handleInfoClick = () => {
setIsInfoDialogOpen(true); onInfoClick();
onOpenChange(false); onOpenChange(false);
}; };
const isGrid = variant === "grid"; const isGrid = variant === "grid";
return ( return (
<>
<DropdownMenu open={isOpen} onOpenChange={onOpenChange}> <DropdownMenu open={isOpen} onOpenChange={onOpenChange}>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button <Button
@@ -813,30 +909,6 @@ function ProjectMenu({
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
<RenameProjectDialog
isOpen={isRenameDialogOpen}
onOpenChange={setIsRenameDialogOpen}
projectName={project.name}
onConfirm={async (newName) => {
await renameProject({ editor, id: project.id, name: newName });
setIsRenameDialogOpen(false);
}}
/>
<DeleteProjectDialog
isOpen={isDeleteDialogOpen}
onOpenChange={setIsDeleteDialogOpen}
projectNames={[project.name]}
onConfirm={handleDeleteConfirm}
/>
<ProjectInfoDialog
isOpen={isInfoDialogOpen}
onOpenChange={setIsInfoDialogOpen}
project={project}
/>
</>
); );
} }