ultisuite-client/components/drive/sidebar-folder-tree.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

283 lines
8.1 KiB
TypeScript

"use client"
import { useEffect } from "react"
import Link from "next/link"
import { useRouter } from "next/navigation"
import type { LucideIcon } from "lucide-react"
import { ChevronRight, HardDrive, Users } from "lucide-react"
import { DRIVE_DROP_TARGET_CLASS } from "@/components/drive/drive-file-context-menu"
import { DriveFolderIcon } from "@/lib/drive/drive-file-icon"
import { DRIVE_ICON_BTN } from "@/lib/drive/drive-chrome-classes"
import { mailNavRowClass } from "@/lib/mail-chrome-classes"
import { cn } from "@/lib/utils"
import type { DriveView } from "@/lib/drive/drive-url"
import { folderPathFromSegments } from "@/lib/drive/drive-url"
import { useDriveList, useDriveSharedWithMe } from "@/lib/api/hooks/use-drive-queries"
import type { DriveFileInfo } from "@/lib/api/types"
import { displayFileName } from "@/lib/drive/display-file-name"
import {
ancestorFolderPaths,
driveFolderHref,
isSharedRootSelected,
normalizeDriveFolderPath,
selectedFolderPath,
} from "@/lib/drive/drive-sidebar-tree"
import { useIsMobile } from "@/hooks/use-mobile"
import { useDriveDropTarget } from "@/lib/hooks/use-drive-drop-target"
import { useDriveUIStore } from "@/lib/stores/drive-ui-store"
const INDENT_PX = 16
function useFolderChildren(folderPath: string, enabled: boolean, sharedRoot: boolean) {
const list = useDriveList(folderPath, 1, "", enabled && !sharedRoot)
const shared = useDriveSharedWithMe(1, "", enabled && sharedRoot)
const active = sharedRoot ? shared : list
const directories =
active.data?.files.filter((file) => file.type === "directory") ?? []
return { directories }
}
function SidebarTreeCaret({
visible,
expanded,
onToggle,
label,
}: {
visible: boolean
expanded: boolean
onToggle: () => void
label: string
}) {
if (!visible) {
return <span className="w-6 shrink-0" aria-hidden="true" />
}
return (
<button
type="button"
aria-label={label}
className={cn(
"flex h-7 w-6 shrink-0 cursor-pointer items-center justify-center rounded-md",
DRIVE_ICON_BTN
)}
onClick={(event) => {
event.preventDefault()
event.stopPropagation()
onToggle()
}}
>
<ChevronRight
className={cn(
"h-3.5 w-3.5 text-muted-foreground transition-transform",
expanded && "rotate-90"
)}
/>
</button>
)
}
function SidebarFolderNode({
folder,
depth,
view,
currentPath,
active,
}: {
folder: DriveFileInfo
depth: number
view: DriveView
currentPath: string
active: boolean
}) {
const router = useRouter()
const isMobile = useIsMobile()
const folderPath = normalizeDriveFolderPath(folder.path)
const expandedPaths = useDriveUIStore((s) => s.expandedSidebarPaths)
const toggleSidebarPath = useDriveUIStore((s) => s.toggleSidebarPath)
const ensureSidebarPathsExpanded = useDriveUIStore((s) => s.ensureSidebarPathsExpanded)
const isExpanded = expandedPaths.has(folderPath)
const isSelected = active && currentPath === folderPath
const { directories } = useFolderChildren(folderPath, true, false)
const hasChildFolders = directories.length > 0
const href = driveFolderHref(view, folderPath)
const label = displayFileName(folder.name)
const { dropProps, canDrop, isOver } = useDriveDropTarget({
folderPath,
disabled: isMobile,
hasChildFolders,
onExpandRequest: () => {
if (!isExpanded) ensureSidebarPathsExpanded([folderPath])
},
})
return (
<div className="min-w-0">
<div
className={cn(
"group flex min-w-0 items-center rounded-lg text-sm",
mailNavRowClass({ isSelected }),
isOver && canDrop && DRIVE_DROP_TARGET_CLASS
)}
style={{ paddingLeft: depth * INDENT_PX }}
{...dropProps}
>
<SidebarTreeCaret
visible={hasChildFolders}
expanded={isExpanded}
label={isExpanded ? "Replier le dossier" : "Déplier le dossier"}
onToggle={() => toggleSidebarPath(folderPath)}
/>
<Link
href={href}
className="flex min-w-0 flex-1 cursor-pointer items-center gap-2 py-1.5 pr-2"
onClick={(event) => {
event.preventDefault()
router.push(href)
if (isMobile) useDriveUIStore.getState().setSidebarCollapsed(true)
}}
>
<DriveFolderIcon file={folder} inSharedView={view === "shared"} size="sm" />
<span className="truncate">{label}</span>
</Link>
</div>
{isExpanded && hasChildFolders
? directories.map((child) => (
<SidebarFolderNode
key={child.path}
folder={child}
depth={depth + 1}
view={view}
currentPath={currentPath}
active={active}
/>
))
: null}
</div>
)
}
function SidebarRootBranch({
view,
rootHref,
rootLabel,
rootIcon: RootIcon,
rootKey,
pathSegments,
active,
}: {
view: DriveView
rootHref: string
rootLabel: string
rootIcon: LucideIcon
rootKey: string
pathSegments: string[]
active: boolean
}) {
const router = useRouter()
const isMobile = useIsMobile()
const expandedPaths = useDriveUIStore((s) => s.expandedSidebarPaths)
const toggleSidebarPath = useDriveUIStore((s) => s.toggleSidebarPath)
const ensureSidebarPathsExpanded = useDriveUIStore((s) => s.ensureSidebarPathsExpanded)
const currentPath = active ? selectedFolderPath(view, pathSegments) : ""
const isRootSelected =
active &&
(view === "shared" ? isSharedRootSelected(view, pathSegments) : currentPath === "/")
const isExpanded = expandedPaths.has(rootKey)
const sharedRoot = view === "shared"
const { directories } = useFolderChildren("/", true, sharedRoot)
const hasChildFolders = directories.length > 0
const { dropProps, canDrop, isOver } = useDriveDropTarget({
folderPath: "/",
disabled: isMobile,
hasChildFolders,
onExpandRequest: () => {
if (!isExpanded) ensureSidebarPathsExpanded([rootKey])
},
})
useEffect(() => {
if (!active) return
ensureSidebarPathsExpanded(ancestorFolderPaths(folderPathFromSegments(pathSegments)))
ensureSidebarPathsExpanded([rootKey])
}, [active, ensureSidebarPathsExpanded, pathSegments, rootKey])
return (
<div className="min-w-0">
<div
className={cn(
"group flex min-w-0 items-center rounded-lg text-sm",
mailNavRowClass({ isSelected: isRootSelected }),
isOver && canDrop && DRIVE_DROP_TARGET_CLASS
)}
{...dropProps}
>
<SidebarTreeCaret
visible={hasChildFolders}
expanded={isExpanded}
label={isExpanded ? "Replier" : "Déplier"}
onToggle={() => toggleSidebarPath(rootKey)}
/>
<Link
href={rootHref}
className="flex min-w-0 flex-1 cursor-pointer items-center gap-2 py-1.5 pr-2"
onClick={(event) => {
event.preventDefault()
router.push(rootHref)
if (isMobile) useDriveUIStore.getState().setSidebarCollapsed(true)
}}
>
<RootIcon className="h-4 w-4 shrink-0" />
<span className="truncate">{rootLabel}</span>
</Link>
</div>
{isExpanded && hasChildFolders
? directories.map((folder) => (
<SidebarFolderNode
key={folder.path}
folder={folder}
depth={1}
view={view}
currentPath={currentPath}
active={active}
/>
))
: null}
</div>
)
}
export function DriveSidebarFolderTree({
view,
pathSegments,
active,
}: {
view: "files" | "shared"
pathSegments: string[]
active: boolean
}) {
if (view === "files") {
return (
<SidebarRootBranch
view="files"
rootHref="/drive"
rootLabel="Mon Drive"
rootIcon={HardDrive}
rootKey="/"
pathSegments={pathSegments}
active={active}
/>
)
}
return (
<SidebarRootBranch
view="shared"
rootHref="/drive/shared"
rootLabel="Partagés avec moi"
rootIcon={Users}
rootKey="/__shared_root__"
pathSegments={pathSegments}
active={active}
/>
)
}