Some checks are pending
E2E / Playwright e2e (push) Waiting to run
- Introduced turbopack alias for canvas in next.config.mjs. - Updated package.json scripts for development and branding tasks. - Added new dependencies for Tiptap extensions. - Implemented new demo layouts for agenda, contacts, drive, and mail applications. - Enhanced globals.css for improved theming and splash screen animations. - Added OAuth callback handling for drive mounts. - Updated layout components to integrate new demo shells and improve structure.
231 lines
7.5 KiB
TypeScript
231 lines
7.5 KiB
TypeScript
"use client"
|
|
|
|
import { useEffect } from "react"
|
|
import Link from "next/link"
|
|
import { useRouter } from "next/navigation"
|
|
import { Building2, ChevronRight } from "lucide-react"
|
|
import { DriveFolderIcon } from "@/lib/drive/drive-file-icon"
|
|
import {
|
|
DRIVE_ICON_BTN,
|
|
DRIVE_SIDEBAR_CARET_SLOT_CLASS,
|
|
DRIVE_SIDEBAR_ROW_BODY_CLASS,
|
|
DRIVE_SIDEBAR_ROW_CLASS,
|
|
} from "@/lib/drive/drive-chrome-classes"
|
|
import { mailNavRowClass } from "@/lib/mail-chrome-classes"
|
|
import { cn } from "@/lib/utils"
|
|
import { folderPathFromSegments } from "@/lib/drive/drive-url"
|
|
import { displayFileName } from "@/lib/drive/display-file-name"
|
|
import {
|
|
ancestorFolderPaths,
|
|
driveFolderHref,
|
|
orgRootKey,
|
|
selectedFolderPath,
|
|
} from "@/lib/drive/drive-sidebar-tree"
|
|
import { useDriveOrgFolders, useDriveOrgList } from "@/lib/api/hooks/use-drive-queries"
|
|
import type { DriveOrgFolder } from "@/lib/api/types"
|
|
import { useIsMobile } from "@/hooks/use-mobile"
|
|
import { useDriveUIStore } from "@/lib/stores/drive-ui-store"
|
|
|
|
const INDENT_PX = 16
|
|
|
|
function OrgFolderTree({
|
|
folder,
|
|
active,
|
|
pathSegments,
|
|
}: {
|
|
folder: DriveOrgFolder
|
|
active: boolean
|
|
pathSegments: string[]
|
|
}) {
|
|
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 rootKey = orgRootKey(folder.id)
|
|
const isExpanded = expandedPaths.has(rootKey)
|
|
const currentPath = active ? selectedFolderPath("org", pathSegments) : ""
|
|
const isRootSelected = active && pathSegments.length === 0
|
|
const rootHref = driveFolderHref("org", "/", folder.id)
|
|
const list = useDriveOrgList(folder.id, "/", 1, isExpanded)
|
|
const directories = list.data?.files.filter((f) => f.type === "directory") ?? []
|
|
|
|
useEffect(() => {
|
|
if (!active) return
|
|
ensureSidebarPathsExpanded(ancestorFolderPaths(folderPathFromSegments(pathSegments)))
|
|
ensureSidebarPathsExpanded([rootKey])
|
|
}, [active, ensureSidebarPathsExpanded, pathSegments, rootKey])
|
|
|
|
return (
|
|
<div className="min-w-0">
|
|
<div className={cn(DRIVE_SIDEBAR_ROW_CLASS, mailNavRowClass({ isSelected: isRootSelected }))}>
|
|
<button
|
|
type="button"
|
|
aria-label={isExpanded ? "Replier" : "Déplier"}
|
|
className={cn(DRIVE_SIDEBAR_CARET_SLOT_CLASS, "cursor-pointer rounded-md", DRIVE_ICON_BTN)}
|
|
onClick={() => toggleSidebarPath(rootKey)}
|
|
>
|
|
<ChevronRight className={cn("h-3.5 w-3.5 text-muted-foreground transition-transform", isExpanded && "rotate-90")} />
|
|
</button>
|
|
<Link
|
|
href={rootHref}
|
|
className={cn(DRIVE_SIDEBAR_ROW_BODY_CLASS, "cursor-pointer")}
|
|
onClick={(event) => {
|
|
event.preventDefault()
|
|
router.push(rootHref)
|
|
if (isMobile) useDriveUIStore.getState().setSidebarCollapsed(true)
|
|
}}
|
|
>
|
|
<Building2 className="h-4 w-4 shrink-0" />
|
|
<span className="truncate">{folder.mount_point}</span>
|
|
</Link>
|
|
</div>
|
|
{isExpanded
|
|
? directories.map((child) => (
|
|
<OrgFolderNode
|
|
key={child.path}
|
|
orgFolder={folder}
|
|
folderPath={child.path}
|
|
depth={1}
|
|
active={active}
|
|
currentPath={currentPath}
|
|
/>
|
|
))
|
|
: null}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function OrgFolderNode({
|
|
orgFolder,
|
|
folderPath,
|
|
depth,
|
|
active,
|
|
currentPath,
|
|
}: {
|
|
orgFolder: DriveOrgFolder
|
|
folderPath: string
|
|
depth: number
|
|
active: boolean
|
|
currentPath: string
|
|
}) {
|
|
const router = useRouter()
|
|
const isMobile = useIsMobile()
|
|
const expandedPaths = useDriveUIStore((s) => s.expandedSidebarPaths)
|
|
const toggleSidebarPath = useDriveUIStore((s) => s.toggleSidebarPath)
|
|
const isExpanded = expandedPaths.has(folderPath)
|
|
const isSelected = active && currentPath === folderPath
|
|
const href = driveFolderHref("org", folderPath, orgFolder.id)
|
|
const list = useDriveOrgList(orgFolder.id, folderPath, 1, isExpanded)
|
|
const directories = list.data?.files.filter((f) => f.type === "directory") ?? []
|
|
|
|
return (
|
|
<div className="min-w-0">
|
|
<div
|
|
className={cn(DRIVE_SIDEBAR_ROW_CLASS, mailNavRowClass({ isSelected }))}
|
|
style={{ paddingLeft: depth * INDENT_PX }}
|
|
>
|
|
<button
|
|
type="button"
|
|
aria-label={isExpanded ? "Replier" : "Déplier"}
|
|
className={cn(
|
|
DRIVE_SIDEBAR_CARET_SLOT_CLASS,
|
|
"cursor-pointer rounded-md",
|
|
directories.length > 0 ? DRIVE_ICON_BTN : "invisible"
|
|
)}
|
|
onClick={() => toggleSidebarPath(folderPath)}
|
|
>
|
|
<ChevronRight className={cn("h-3.5 w-3.5 text-muted-foreground transition-transform", isExpanded && "rotate-90")} />
|
|
</button>
|
|
<Link
|
|
href={href}
|
|
className={cn(DRIVE_SIDEBAR_ROW_BODY_CLASS, "cursor-pointer")}
|
|
onClick={(event) => {
|
|
event.preventDefault()
|
|
router.push(href)
|
|
if (isMobile) useDriveUIStore.getState().setSidebarCollapsed(true)
|
|
}}
|
|
>
|
|
<DriveFolderIcon file={{ path: folderPath, name: displayFileName(folderPath.split("/").pop() ?? ""), type: "directory", size: 0, mime_type: "", last_modified: "", etag: "", is_favorite: false }} size="sm" />
|
|
<span className="truncate">{displayFileName(folderPath.split("/").pop() ?? folderPath)}</span>
|
|
</Link>
|
|
</div>
|
|
{isExpanded
|
|
? directories.map((child) => (
|
|
<OrgFolderNode
|
|
key={child.path}
|
|
orgFolder={orgFolder}
|
|
folderPath={child.path}
|
|
depth={depth + 1}
|
|
active={active}
|
|
currentPath={currentPath}
|
|
/>
|
|
))
|
|
: null}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export function DriveSidebarOrgFolders({
|
|
active,
|
|
pathSegments,
|
|
rootId,
|
|
}: {
|
|
active: boolean
|
|
pathSegments: string[]
|
|
rootId: string | null
|
|
}) {
|
|
const orgFolders = useDriveOrgFolders()
|
|
const folders = orgFolders.data ?? []
|
|
const expandedPaths = useDriveUIStore((s) => s.expandedSidebarPaths)
|
|
const toggleSidebarPath = useDriveUIStore((s) => s.toggleSidebarPath)
|
|
const sectionKey = "/__org_section__"
|
|
const isSectionExpanded = expandedPaths.has(sectionKey)
|
|
|
|
if (orgFolders.isLoading) {
|
|
return (
|
|
<div className="px-3 py-1.5 text-xs text-muted-foreground">Dossiers d'organisation…</div>
|
|
)
|
|
}
|
|
if (folders.length === 0) {
|
|
return null
|
|
}
|
|
|
|
return (
|
|
<div className="min-w-0 pb-1">
|
|
<button
|
|
type="button"
|
|
className={cn(
|
|
DRIVE_SIDEBAR_ROW_CLASS,
|
|
"w-full cursor-pointer",
|
|
mailNavRowClass({ isSelected: false })
|
|
)}
|
|
onClick={() => toggleSidebarPath(sectionKey)}
|
|
>
|
|
<span className={DRIVE_SIDEBAR_CARET_SLOT_CLASS}>
|
|
<ChevronRight
|
|
className={cn(
|
|
"h-3.5 w-3.5 text-muted-foreground transition-transform",
|
|
isSectionExpanded && "rotate-90"
|
|
)}
|
|
/>
|
|
</span>
|
|
<span className={DRIVE_SIDEBAR_ROW_BODY_CLASS}>
|
|
<Building2 className="h-4 w-4 shrink-0" />
|
|
<span className="truncate">Dossiers d'organisation</span>
|
|
</span>
|
|
</button>
|
|
{isSectionExpanded
|
|
? folders.map((folder) => (
|
|
<OrgFolderTree
|
|
key={folder.id}
|
|
folder={folder}
|
|
active={active && rootId === folder.id}
|
|
pathSegments={rootId === folder.id ? pathSegments : []}
|
|
/>
|
|
))
|
|
: null}
|
|
</div>
|
|
)
|
|
}
|