ultisuite-client/components/drive/public-share-folder-view.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

275 lines
8.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import { useEffect, useMemo, useRef } from "react"
import { useRouter } from "next/navigation"
import { Upload, FolderPlus } from "lucide-react"
import { toast } from "sonner"
import { FileBrowser } from "@/components/drive/file-browser"
import { DriveFilterBar } from "@/components/drive/drive-filter-bar"
import { DriveViewModeToggle } from "@/components/drive/drive-view-mode-toggle"
import { DriveSortMenu } from "@/components/drive/drive-sort-menu"
import { DriveBulkToolbar } from "@/components/drive/drive-bulk-toolbar"
import { DriveMarqueeSurface } from "@/components/drive/drive-marquee-surface"
import { DriveScrollEndSpacer } from "@/components/drive/drive-scroll-end-spacer"
import { Button } from "@/components/ui/button"
import { usePublicShareMenuMutations, usePublicShareMutations } from "@/lib/api/hooks/use-public-share-mutations"
import type { DriveFileInfo } from "@/lib/api/types"
import { useDriveSettingsStore } from "@/lib/stores/drive-settings-store"
import { useDriveFilteredItems } from "@/lib/hooks/use-drive-filtered-items"
import { useDriveFiltersStore } from "@/lib/stores/drive-filters-store"
import {
sharePermCanCreate,
sharePermCanDelete,
sharePermCanEdit,
sharePermCanUpdate,
} from "@/lib/drive/drive-share-permissions"
import { openPublicShareItem, downloadPublicShareFile } from "@/lib/drive/open-public-share-item"
import { useDriveUIStore } from "@/lib/stores/drive-ui-store"
import {
DRIVE_BROWSER_CARD_CLASS,
DRIVE_CARD_PAD_X,
DRIVE_CARD_SCROLL_PT,
DRIVE_FILTER_CONTENT_GAP,
} from "@/lib/drive/drive-chrome-classes"
import { cn } from "@/lib/utils"
import { nextUntitledName } from "@/lib/drive/drive-default-name"
export function PublicShareFolderView({
token,
folderPath,
files,
permissions,
password,
folderTitle,
}: {
token: string
folderPath: string
files: DriveFileInfo[]
permissions: number
password?: string
folderTitle: string
}) {
const router = useRouter()
const openPreview = useDriveUIStore((s) => s.openPreview)
const selectedPaths = useDriveUIStore((s) => s.selectedPaths)
const clearSelection = useDriveUIStore((s) => s.clearSelection)
const sortField = useDriveSettingsStore((s) => s.sortField)
const sortDir = useDriveSettingsStore((s) => s.sortDir)
const folderPlacement = useDriveSettingsStore((s) => s.folderPlacement)
const filters = useDriveFiltersStore()
const uploadInputRef = useRef<HTMLInputElement>(null)
const canEdit = sharePermCanEdit(permissions)
const canCreate = sharePermCanCreate(permissions)
const canUpdate = sharePermCanUpdate(permissions)
const canDelete = sharePermCanDelete(permissions)
const writable = canUpdate || canDelete
const mutations = usePublicShareMenuMutations(token, password)
const { uploadFile, createFolder } = usePublicShareMutations(token, password)
const filtersSnapshot = useMemo(
() => ({
types: filters.types,
sources: filters.sources,
contactEmail: filters.contactEmail,
contactName: filters.contactName,
datePreset: filters.datePreset,
dateFrom: filters.dateFrom,
dateTo: filters.dateTo,
}),
[
filters.types,
filters.sources,
filters.contactEmail,
filters.contactName,
filters.datePreset,
filters.dateFrom,
filters.dateTo,
]
)
const { filteredItems: filteredFiles } = useDriveFilteredItems(
files,
filtersSnapshot,
{ sortField, sortDir, folderPlacement },
{ scopePath: folderPath }
)
const selectedTargets = useMemo(
() => filteredFiles.filter((f) => selectedPaths.has(f.path)),
[filteredFiles, selectedPaths]
)
const showBulk = selectedTargets.length > 0
useEffect(() => {
clearSelection()
}, [folderPath, clearSelection])
const openItem = (file: DriveFileInfo) => {
openPublicShareItem(file, {
token,
password,
canEdit: sharePermCanEdit(permissions),
router,
openPreview,
contextItems: filteredFiles,
})
}
const handleUpload = async (fileList: FileList | null) => {
if (!fileList?.length || !canCreate) return
const base = folderPath === "/" ? "" : folderPath
try {
for (const file of Array.from(fileList)) {
await uploadFile(`${base}/${file.name}`.replace(/\/+/g, "/"), file)
}
toast.success("Fichier(s) importé(s)")
} catch {
toast.error("Impossible dimporter le fichier")
}
}
const handleNewFolder = async () => {
if (!canCreate) return
const name = nextUntitledName(
filteredFiles.filter((f) => f.type === "directory").map((f) => f.name),
"Dossier"
)
const base = folderPath === "/" ? "" : folderPath
const path = `${base}/${name}`.replace(/\/+/g, "/")
try {
await createFolder.mutateAsync(path)
toast.success("Dossier créé")
} catch {
toast.error("Impossible de créer le dossier")
}
}
const downloadBulk = async (targets: DriveFileInfo[]) => {
for (const file of targets) {
await downloadPublicShareFile(token, file, password)
}
}
return (
<div className={DRIVE_BROWSER_CARD_CLASS}>
<div className={cn("shrink-0", DRIVE_FILTER_CONTENT_GAP)}>
<div
className={cn(
"flex min-h-12 shrink-0 flex-wrap items-center justify-between gap-3 py-2",
DRIVE_CARD_PAD_X
)}
>
<p className="truncate text-sm text-muted-foreground">
<span className="font-medium text-[#3c4043] dark:text-[#e8eaed]">{folderTitle}</span>
{" · "}
{filteredFiles.length} élément{filteredFiles.length > 1 ? "s" : ""}
{canEdit ? " · Éditeur" : " · Lecture seule"}
</p>
<div className="flex flex-wrap items-center gap-2">
{canCreate ? (
<>
<input
ref={uploadInputRef}
type="file"
multiple
className="hidden"
onChange={(e) => void handleUpload(e.target.files)}
/>
<Button
type="button"
variant="outline"
size="sm"
className="gap-2"
onClick={() => uploadInputRef.current?.click()}
>
<Upload className="h-4 w-4" />
Importer
</Button>
<Button
type="button"
variant="outline"
size="sm"
className="gap-2"
onClick={() => void handleNewFolder()}
>
<FolderPlus className="h-4 w-4" />
Dossier
</Button>
</>
) : null}
<DriveSortMenu />
<DriveViewModeToggle />
</div>
</div>
{showBulk ? (
<DriveBulkToolbar
targets={selectedTargets}
allowShare={false}
allowMove={false}
allowCopy={false}
allowQuickLink={false}
allowDelete={canDelete}
mutations={mutations}
onDownloadBulk={downloadBulk}
/>
) : (
<DriveFilterBar showContacts={false} />
)}
</div>
<main
data-drive-browser-main
className="flex min-h-0 flex-1 flex-col overflow-auto"
>
<DriveMarqueeSurface
enabled={filteredFiles.length > 0}
className="min-h-full"
>
{files.length === 0 ? (
<p
className={cn(
DRIVE_CARD_PAD_X,
DRIVE_CARD_SCROLL_PT,
"py-8 text-center text-sm text-muted-foreground"
)}
>
Ce dossier est vide.
{canCreate ? " Importez un fichier ou créez un dossier." : ""}
</p>
) : filteredFiles.length === 0 ? (
<p
className={cn(
DRIVE_CARD_PAD_X,
DRIVE_CARD_SCROLL_PT,
"py-8 text-center text-sm text-muted-foreground"
)}
>
Aucun élément ne correspond aux filtres.
</p>
) : (
<FileBrowser
items={filteredFiles}
view="shared"
allowShare={false}
writable={writable}
hideFavorite
disableDnd
mutations={mutations}
publicShare={{ token, password }}
onOpenItem={openItem}
onDownloadItem={(file) =>
void downloadPublicShareFile(token, file, password).catch(() =>
toast.error("Téléchargement impossible")
)
}
/>
)}
<DriveScrollEndSpacer />
</DriveMarqueeSurface>
</main>
</div>
)
}