- 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.
165 lines
5.9 KiB
TypeScript
165 lines
5.9 KiB
TypeScript
"use client"
|
|
|
|
import { useMemo, useState } from "react"
|
|
import { ChevronRight, Folder, Plus, X } from "lucide-react"
|
|
import { Button } from "@/components/ui/button"
|
|
import { Checkbox } from "@/components/ui/checkbox"
|
|
import { Label } from "@/components/ui/label"
|
|
import { useDriveList } from "@/lib/api/hooks/use-drive-queries"
|
|
import type { ApiTokenDriveScope } from "@/lib/api/types"
|
|
import { displayFileName } from "@/lib/drive/display-file-name"
|
|
import { normalizeDriveFolderPath } from "@/lib/drive/drive-sidebar-tree"
|
|
import { cn } from "@/lib/utils"
|
|
|
|
export function ApiTokenDriveScopeEditor({
|
|
scope,
|
|
onChange,
|
|
enabled,
|
|
className,
|
|
}: {
|
|
scope: ApiTokenDriveScope
|
|
onChange: (scope: ApiTokenDriveScope) => void
|
|
enabled: boolean
|
|
className?: string
|
|
}) {
|
|
const [browsePath, setBrowsePath] = useState("/")
|
|
const list = useDriveList(browsePath, 1, "", enabled && !scope.all_folders)
|
|
|
|
const folders = useMemo(
|
|
() => (list.data?.files ?? []).filter((f) => f.type === "directory"),
|
|
[list.data?.files]
|
|
)
|
|
|
|
const crumbs = useMemo(() => {
|
|
const normalized = normalizeDriveFolderPath(browsePath)
|
|
if (normalized === "/") return [{ path: "/", label: "Mon Drive" }]
|
|
const parts = normalized.slice(1).split("/")
|
|
const out: { path: string; label: string }[] = [{ path: "/", label: "Mon Drive" }]
|
|
for (let i = 0; i < parts.length; i++) {
|
|
const path = "/" + parts.slice(0, i + 1).join("/")
|
|
out.push({ path, label: displayFileName(parts[i]!) })
|
|
}
|
|
return out
|
|
}, [browsePath])
|
|
|
|
if (!enabled) return null
|
|
|
|
function addFolder(path: string) {
|
|
const normalized = normalizeDriveFolderPath(path)
|
|
if (scope.folder_paths.includes(normalized)) return
|
|
onChange({
|
|
all_folders: false,
|
|
folder_paths: [...scope.folder_paths, normalized],
|
|
})
|
|
}
|
|
|
|
function removeFolder(path: string) {
|
|
onChange({
|
|
all_folders: false,
|
|
folder_paths: scope.folder_paths.filter((p) => p !== path),
|
|
})
|
|
}
|
|
|
|
return (
|
|
<fieldset className={cn("space-y-3 rounded-md border border-border p-3", className)}>
|
|
<legend className="px-1 text-sm font-medium text-foreground">
|
|
Périmètre Drive — dossiers
|
|
</legend>
|
|
<label className="flex items-start gap-2">
|
|
<Checkbox
|
|
checked={scope.all_folders}
|
|
onCheckedChange={(checked) =>
|
|
onChange({
|
|
all_folders: checked === true,
|
|
folder_paths: checked === true ? [] : scope.folder_paths,
|
|
})
|
|
}
|
|
className="mt-0.5"
|
|
/>
|
|
<span className="text-sm">
|
|
Tout le Drive
|
|
<span className="mt-0.5 block text-xs text-muted-foreground">
|
|
Accès à l'intégralité de l'arborescence.
|
|
</span>
|
|
</span>
|
|
</label>
|
|
|
|
{!scope.all_folders && (
|
|
<div className="space-y-3">
|
|
{scope.folder_paths.length > 0 && (
|
|
<div className="space-y-1.5">
|
|
<Label className="text-xs text-muted-foreground">Dossiers autorisés</Label>
|
|
<ul className="space-y-1">
|
|
{scope.folder_paths.map((path) => (
|
|
<li
|
|
key={path}
|
|
className="flex items-center justify-between gap-2 rounded-md border border-border px-2.5 py-1.5 text-sm"
|
|
>
|
|
<span className="truncate font-mono text-xs">{path}</span>
|
|
<Button
|
|
type="button"
|
|
variant="ghost"
|
|
size="icon"
|
|
className="size-7 shrink-0"
|
|
onClick={() => removeFolder(path)}
|
|
aria-label={`Retirer ${path}`}
|
|
>
|
|
<X className="size-3.5" />
|
|
</Button>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
)}
|
|
|
|
<div className="space-y-2 rounded-md border border-dashed border-border p-2.5">
|
|
<Label className="text-xs text-muted-foreground">Ajouter un dossier</Label>
|
|
<div className="flex flex-wrap items-center gap-1 text-xs text-muted-foreground">
|
|
{crumbs.map((crumb, index) => (
|
|
<span key={crumb.path} className="inline-flex items-center gap-1">
|
|
{index > 0 && <ChevronRight className="size-3" />}
|
|
<button
|
|
type="button"
|
|
className="rounded px-1 hover:bg-muted hover:text-foreground"
|
|
onClick={() => setBrowsePath(crumb.path)}
|
|
>
|
|
{crumb.label}
|
|
</button>
|
|
</span>
|
|
))}
|
|
</div>
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
size="sm"
|
|
className="w-full justify-start"
|
|
onClick={() => addFolder(browsePath)}
|
|
>
|
|
<Plus className="mr-1.5 size-3.5" />
|
|
Autoriser « {crumbs[crumbs.length - 1]?.label ?? "Mon Drive"} »
|
|
</Button>
|
|
{list.isLoading ? (
|
|
<p className="text-xs text-muted-foreground">Chargement…</p>
|
|
) : folders.length > 0 ? (
|
|
<ul className="max-h-36 space-y-1 overflow-y-auto">
|
|
{folders.map((folder) => (
|
|
<li key={folder.path}>
|
|
<button
|
|
type="button"
|
|
className="flex w-full items-center gap-2 rounded px-2 py-1.5 text-left text-sm hover:bg-muted/50"
|
|
onClick={() => setBrowsePath(folder.path)}
|
|
>
|
|
<Folder className="size-4 shrink-0 text-muted-foreground" />
|
|
<span className="truncate">{displayFileName(folder.name)}</span>
|
|
</button>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</fieldset>
|
|
)
|
|
}
|