diff options
Diffstat (limited to 'apps/web/src/app/projects/page.tsx')
| -rw-r--r-- | apps/web/src/app/projects/page.tsx | 352 |
1 files changed, 212 insertions, 140 deletions
diff --git a/apps/web/src/app/projects/page.tsx b/apps/web/src/app/projects/page.tsx index b855cefc..a86cf775 100644 --- a/apps/web/src/app/projects/page.tsx +++ b/apps/web/src/app/projects/page.tsx @@ -47,6 +47,13 @@ import { import { OcVideoIcon } from "@opencut/ui/icons"; import { Label } from "@/components/ui/label"; import { + ContextMenu, + ContextMenuContent, + ContextMenuItem, + ContextMenuSeparator, + ContextMenuTrigger, +} from "@/components/ui/context-menu"; +import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, @@ -534,10 +541,25 @@ function ProjectItem({ const isSelected = selectedProjectIdSet.has(project.id); const selectedProjectCount = selectedProjectIds.length; 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 isMultiSelect = selectedProjectCount > 1; 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 = ({ checked, shiftKey, @@ -622,9 +644,8 @@ function ProjectItem({ const listContent = ( <div - className={`flex items-center gap-4 py-2 px-4 border-b border-border/50 ${ - isSelected ? "bg-primary/5" : "" - }`} + className={`flex items-center gap-4 py-2 px-4 border-b border-border/50 ${isSelected ? "bg-primary/5" : "" + }`} > <Checkbox checked={isSelected} @@ -635,7 +656,7 @@ function ProjectItem({ shiftKey: event.shiftKey, }); }} - onCheckedChange={() => {}} + onCheckedChange={() => { }} className="size-5 shrink-0" /> @@ -643,77 +664,158 @@ function ProjectItem({ {listRowContent} </Link> - {!isMultiSelect && ( - <ProjectMenu - isOpen={isDropdownOpen} - onOpenChange={setIsDropdownOpen} - project={project} - variant="list" - /> - )} + {!isMultiSelect && ( + <ProjectMenu + isOpen={isDropdownOpen} + onOpenChange={setIsDropdownOpen} + variant="list" + onRenameClick={handleRename} + onDuplicateClick={handleDuplicate} + onDeleteClick={handleDeleteClick} + onInfoClick={handleInfoClick} + /> + )} </div> ); - const cardContent = isGridView ? gridContent : listContent; + return ( + <> + <ContextMenu> + <ContextMenuTrigger asChild> + <div className="group relative"> + {isGridView ? ( + <> + <Link href={`/editor/${project.id}`} className="block"> + {gridContent} + </Link> - if (!isGridView) { - return <div className="group relative">{listContent}</div>; - } + <Checkbox + checked={isSelected} + onMouseDown={(event) => event.preventDefault()} + onClick={(event) => { + handleCheckboxChange({ + checked: !isSelected, + shiftKey: event.shiftKey, + }); + }} + onCheckedChange={() => {}} + className={`absolute z-10 size-5 top-3 left-3 ${ + isSelected || isDropdownOpen + ? "opacity-100" + : "opacity-0 group-hover:opacity-100" + }`} + /> - return ( - <div className="group relative"> - <Link href={`/editor/${project.id}`} className="block"> - {cardContent} - </Link> + {!isMultiSelect && ( + <ProjectMenu + isOpen={isDropdownOpen} + onOpenChange={setIsDropdownOpen} + onRenameClick={handleRename} + onDuplicateClick={handleDuplicate} + onDeleteClick={handleDeleteClick} + onInfoClick={handleInfoClick} + /> + )} + </> + ) : ( + listContent + )} + </div> + </ContextMenuTrigger> + <ProjectContextMenuContent + onRenameClick={handleRename} + onDuplicateClick={handleDuplicate} + onDeleteClick={handleDeleteClick} + onInfoClick={handleInfoClick} + /> + </ContextMenu> - {isGridView && ( - <> - <Checkbox - checked={isSelected} - onMouseDown={(event) => event.preventDefault()} - onClick={(event) => { - handleCheckboxChange({ - checked: !isSelected, - shiftKey: event.shiftKey, - }); - }} - onCheckedChange={() => {}} - className={`absolute z-10 size-5 top-3 left-3 ${ - isSelected || isDropdownOpen - ? "opacity-100" - : "opacity-0 group-hover:opacity-100" - }`} - /> + <RenameProjectDialog + isOpen={isRenameDialogOpen} + onOpenChange={setIsRenameDialogOpen} + projectName={project.name} + onConfirm={async (newName) => { + await renameProject({ editor, id: project.id, name: newName }); + setIsRenameDialogOpen(false); + }} + /> - {!isMultiSelect && ( - <ProjectMenu - isOpen={isDropdownOpen} - onOpenChange={setIsDropdownOpen} - project={project} - /> - )} - </> - )} - </div> + <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({ isOpen, onOpenChange, - project, variant = "grid", + onRenameClick, + onDuplicateClick, + onDeleteClick, + onInfoClick, }: { isOpen: boolean; onOpenChange: (open: boolean) => void; - project: TProjectMetadata; 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 = ({ event, }: { @@ -736,107 +838,77 @@ function ProjectMenu({ }; const handleRename = () => { - setIsRenameDialogOpen(true); + onRenameClick(); onOpenChange(false); }; - const handleDuplicate = async () => { - await duplicateProjects({ editor, ids: [project.id] }); + const handleDuplicate = () => { + onDuplicateClick(); onOpenChange(false); }; const handleDeleteClick = () => { - setIsDeleteDialogOpen(true); + onDeleteClick(); onOpenChange(false); }; - const handleDeleteConfirm = async () => { - await deleteProjects({ editor, ids: [project.id] }); - setIsDeleteDialogOpen(false); - }; - const handleInfoClick = () => { - setIsInfoDialogOpen(true); + onInfoClick(); onOpenChange(false); }; const isGrid = variant === "grid"; return ( - <> - <DropdownMenu open={isOpen} onOpenChange={onOpenChange}> - <DropdownMenuTrigger asChild> - <Button - variant="background" - className={ - isGrid - ? `absolute z-10 top-3 right-3 ${isOpen ? "opacity-100" : "opacity-0 group-hover:opacity-100"}` - : "!bg-transparent !shadow-none" - } - size="icon" - aria-label="Project menu" - onClick={(event) => - handleMenuClick({ - event: event as unknown as MouseEvent<HTMLButtonElement>, - }) - } - onMouseDown={(event) => event.stopPropagation()} - onKeyDown={(event) => - handleMenuKeyDown({ - event: event as unknown as KeyboardEvent<HTMLButtonElement>, - }) - } - > - <HugeiconsIcon - icon={MoreHorizontalIcon} - className="text-foreground" - aria-hidden="true" - /> - </Button> - </DropdownMenuTrigger> - <DropdownMenuContent className="w-48" align="end"> - <DropdownMenuItem onClick={handleRename}> - <HugeiconsIcon icon={Edit03Icon} /> - Rename - </DropdownMenuItem> - <DropdownMenuItem onClick={handleDuplicate}> - <HugeiconsIcon icon={Copy01Icon} /> - Duplicate - </DropdownMenuItem> - <DropdownMenuItem onClick={handleInfoClick}> - <HugeiconsIcon icon={InformationCircleIcon} /> - Info - </DropdownMenuItem> - <DropdownMenuItem variant="destructive" onClick={handleDeleteClick}> - <HugeiconsIcon icon={Delete02Icon} /> - Delete - </DropdownMenuItem> - </DropdownMenuContent> - </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} - /> - </> + <DropdownMenu open={isOpen} onOpenChange={onOpenChange}> + <DropdownMenuTrigger asChild> + <Button + variant="background" + className={ + isGrid + ? `absolute z-10 top-3 right-3 ${isOpen ? "opacity-100" : "opacity-0 group-hover:opacity-100"}` + : "!bg-transparent !shadow-none" + } + size="icon" + aria-label="Project menu" + onClick={(event) => + handleMenuClick({ + event: event as unknown as MouseEvent<HTMLButtonElement>, + }) + } + onMouseDown={(event) => event.stopPropagation()} + onKeyDown={(event) => + handleMenuKeyDown({ + event: event as unknown as KeyboardEvent<HTMLButtonElement>, + }) + } + > + <HugeiconsIcon + icon={MoreHorizontalIcon} + className="text-foreground" + aria-hidden="true" + /> + </Button> + </DropdownMenuTrigger> + <DropdownMenuContent className="w-48" align="end"> + <DropdownMenuItem onClick={handleRename}> + <HugeiconsIcon icon={Edit03Icon} /> + Rename + </DropdownMenuItem> + <DropdownMenuItem onClick={handleDuplicate}> + <HugeiconsIcon icon={Copy01Icon} /> + Duplicate + </DropdownMenuItem> + <DropdownMenuItem onClick={handleInfoClick}> + <HugeiconsIcon icon={InformationCircleIcon} /> + Info + </DropdownMenuItem> + <DropdownMenuItem variant="destructive" onClick={handleDeleteClick}> + <HugeiconsIcon icon={Delete02Icon} /> + Delete + </DropdownMenuItem> + </DropdownMenuContent> + </DropdownMenu> ); } |
