ultisuite-client/components/drive/drive-file-actions-menu.tsx
R3D347HR4Y 6ec95262af Add OnlyOffice integration and update project configurations
- Updated .env.example to include configuration for OnlyOffice Document Server.
- Modified the workspace configuration to remove the drive-suite path.
- Adjusted TypeScript environment imports for consistency.
- Enhanced Next.js configuration to disable canvas in Webpack.
- Updated package.json to include new dependencies for OnlyOffice and PDF.js.
- Added global styles for OnlyOffice theme integration in the CSS.
- Created new layout and page components for the Drive feature, including public sharing and editing functionalities.
- Updated metadata handling across various layouts to reflect the new app structure.
2026-06-07 15:49:21 +02:00

172 lines
5.6 KiB
TypeScript

"use client"
import { useCallback, useMemo, useState } from "react"
import { toast } from "sonner"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { DriveMoveDialog, type DriveFolderPickerMode } from "@/components/drive/drive-move-dialog"
import { useDriveMutations } from "@/lib/api/hooks/use-drive-queries"
import type { DriveFileInfo } from "@/lib/api/types"
import { displayFileName } from "@/lib/drive/display-file-name"
import { resolveRenameName } from "@/lib/drive/drive-default-name"
import { guardDriveMenuPointer, stopDriveMenuBubble } from "@/lib/drive/drive-menu-guard"
import { useDriveUIStore } from "@/lib/stores/drive-ui-store"
import { DriveNameDialog } from "@/components/drive/drive-name-dialog"
import { DRIVE_MENU_SURFACE_CLASS } from "@/components/drive/drive-file-context-menu"
import { DriveFileMenuActions } from "@/components/drive/drive-file-menu-actions"
import { MoreVertical } from "lucide-react"
import { DRIVE_MENU_BTN } from "@/lib/drive/drive-chrome-classes"
import { cn } from "@/lib/utils"
export function useDriveActionTargets(file: DriveFileInfo, allItems: DriveFileInfo[]) {
const selectedPaths = useDriveUIStore((s) => s.selectedPaths)
return useMemo(() => {
if (selectedPaths.size > 1 && selectedPaths.has(file.path)) {
const picked = allItems.filter((item) => selectedPaths.has(item.path))
if (picked.length > 0) return picked
}
return [file]
}, [allItems, file, selectedPaths])
}
export function DriveFileMenuButton({
file,
allItems,
isTrash,
allowShare = true,
writable = true,
hideFavorite = false,
mutations: mutationsProp,
onDownloadRequest,
onOpen,
onActiveChange,
className,
}: {
file: DriveFileInfo
allItems: DriveFileInfo[]
isTrash?: boolean
allowShare?: boolean
writable?: boolean
hideFavorite?: boolean
mutations?: ReturnType<typeof useDriveMutations>
onDownloadRequest?: () => void
onOpen: () => void
/** Visual highlight only — does not update global selection or bulk bar. */
onActiveChange?: (active: boolean) => void
className?: string
}) {
const [open, setOpen] = useState(false)
const [renameOpen, setRenameOpen] = useState(false)
const [folderPickerMode, setFolderPickerMode] = useState<DriveFolderPickerMode | null>(null)
const setSharePath = useDriveUIStore((s) => s.setSharePath)
const mutationsDefault = useDriveMutations()
const mutations = mutationsProp ?? mutationsDefault
const targets = useDriveActionTargets(file, allItems)
const renameTarget = targets.length === 1 ? targets[0] : null
const handleRename = async (input: string) => {
const target = targets[0]
if (!target) return
const newName = resolveRenameName(target, input)
if (displayFileName(target.name) === newName) return
try {
await mutations.rename.mutateAsync({ path: target.path, new_name: newName })
toast.success("Renommé")
} catch {
toast.error("Impossible de renommer")
throw new Error("rename failed")
}
}
const closeDropdown = useCallback(() => {
setOpen(false)
onActiveChange?.(false)
guardDriveMenuPointer()
}, [onActiveChange])
const openFolderPicker = (mode: DriveFolderPickerMode) => {
closeDropdown()
window.setTimeout(() => setFolderPickerMode(mode), 0)
}
return (
<>
<DriveNameDialog
open={renameOpen}
onOpenChange={setRenameOpen}
title="Renommer"
defaultValue={renameTarget ? displayFileName(renameTarget.name) : ""}
confirmLabel="Renommer"
onConfirm={handleRename}
/>
<DriveMoveDialog
open={folderPickerMode !== null}
onOpenChange={(next) => {
if (!next) setFolderPickerMode(null)
}}
mode={folderPickerMode ?? "move"}
sources={targets}
/>
<DropdownMenu
modal
open={open}
onOpenChange={(next) => {
if (next) {
setOpen(true)
onActiveChange?.(true)
return
}
closeDropdown()
}}
>
<DropdownMenuTrigger asChild>
<button
type="button"
data-drive-menu-btn
aria-label="Actions"
className={cn(DRIVE_MENU_BTN, className)}
onClick={(e) => stopDriveMenuBubble(e)}
onPointerDown={(e) => stopDriveMenuBubble(e)}
>
<MoreVertical className="h-4 w-4" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
data-drive-menu-surface
className={cn(DRIVE_MENU_SURFACE_CLASS, "w-52")}
onCloseAutoFocus={(e) => e.preventDefault()}
onPointerDown={(e) => stopDriveMenuBubble(e)}
onClick={(e) => e.stopPropagation()}
>
<DriveFileMenuActions
variant="dropdown"
targets={targets}
isTrash={isTrash}
allowShare={allowShare}
writable={writable}
hideFavorite={hideFavorite}
onOpen={() => {
closeDropdown()
onOpen()
}}
onClose={closeDropdown}
setSharePath={setSharePath}
mutations={mutations}
onRenameRequest={() => {
closeDropdown()
window.setTimeout(() => setRenameOpen(true), 0)
}}
onMoveRequest={isTrash ? undefined : () => openFolderPicker("move")}
onCopyRequest={isTrash ? undefined : () => openFolderPicker("copy")}
onDownloadRequest={onDownloadRequest}
/>
</DropdownMenuContent>
</DropdownMenu>
</>
)
}