Some checks are pending
E2E / Playwright e2e (push) Waiting to run
- Added SessionGuard component to manage session expiration and online status. - Updated AuthProvider to streamline session fetching and handling. - Introduced IdentityProvidersSection for managing OAuth, SAML, and LDAP identity providers. - Implemented identity provider guides for easier configuration. - Enhanced mail settings with infinite scroll option for improved user experience. - Updated global styles and layout components for better consistency across the application.
158 lines
4.9 KiB
TypeScript
158 lines
4.9 KiB
TypeScript
"use client"
|
||
|
||
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||
import { useRouter } from "next/navigation"
|
||
import Link from "next/link"
|
||
import { apiClient } from "@/lib/api/client"
|
||
import { Button } from "@/components/ui/button"
|
||
import { ArrowLeft } from "lucide-react"
|
||
import { OnlyOfficeMount } from "@/components/drive/onlyoffice-mount"
|
||
import { OfficeEditorChrome } from "@/components/drive/office-editor-chrome"
|
||
import { useDriveMutations, useDriveShares } from "@/lib/api/hooks/use-drive-queries"
|
||
import { displayFileBaseName } from "@/lib/drive/display-file-name"
|
||
import { resolveRenameName } from "@/lib/drive/drive-default-name"
|
||
import { driveFolderHref } from "@/lib/drive/drive-sidebar-tree"
|
||
import { buildDriveEditHref, resolveDriveEditReturnTo } from "@/lib/drive/drive-url"
|
||
import { useDriveDocumentTitle } from "@/lib/drive/use-drive-document-title"
|
||
import { useDriveUIStore } from "@/lib/stores/drive-ui-store"
|
||
|
||
function fileNameFromPath(filePath: string): string {
|
||
return filePath.split("/").filter(Boolean).pop() ?? filePath
|
||
}
|
||
|
||
function renameTargetPath(filePath: string, newName: string): string {
|
||
const parent = filePath.replace(/\/[^/]+$/, "") || "/"
|
||
const base = parent === "/" ? "" : parent
|
||
return `${base}/${newName}`.replace(/\/+/g, "/") || `/${newName}`
|
||
}
|
||
|
||
export function OfficeEditor({
|
||
filePath,
|
||
returnTo,
|
||
}: {
|
||
filePath: string
|
||
returnTo?: string | null
|
||
}) {
|
||
const router = useRouter()
|
||
const instanceSeq = useRef(0)
|
||
const setSharePath = useDriveUIStore((s) => s.setSharePath)
|
||
const [config, setConfig] = useState<Record<string, unknown> | null>(null)
|
||
const [serverUrl, setServerUrl] = useState("")
|
||
const [editorId, setEditorId] = useState<string | null>(null)
|
||
const [error, setError] = useState<string | null>(null)
|
||
const [displayPath, setDisplayPath] = useState(filePath)
|
||
|
||
useEffect(() => {
|
||
setDisplayPath(filePath)
|
||
}, [filePath])
|
||
|
||
const fileName = fileNameFromPath(displayPath)
|
||
const title = displayFileBaseName(fileName)
|
||
useDriveDocumentTitle(title)
|
||
|
||
const backHref = useMemo(
|
||
() =>
|
||
resolveDriveEditReturnTo(returnTo, displayPath, (folderPath) =>
|
||
driveFolderHref("files", folderPath)
|
||
),
|
||
[returnTo, displayPath]
|
||
)
|
||
|
||
const { data: sharesData } = useDriveShares(displayPath, Boolean(displayPath))
|
||
const { rename } = useDriveMutations()
|
||
|
||
const handleEditorError = useCallback((message: string) => {
|
||
setError(message)
|
||
}, [])
|
||
|
||
useEffect(() => {
|
||
let cancelled = false
|
||
setConfig(null)
|
||
setServerUrl("")
|
||
setEditorId(null)
|
||
setError(null)
|
||
|
||
void (async () => {
|
||
try {
|
||
const res = await apiClient.post<{
|
||
config: Record<string, unknown>
|
||
serverUrl: string
|
||
}>("/office/session", { path: displayPath, mode: "edit" })
|
||
if (cancelled) return
|
||
instanceSeq.current += 1
|
||
setConfig(res.config)
|
||
setServerUrl(res.serverUrl || process.env.NEXT_PUBLIC_ONLYOFFICE_URL || "")
|
||
setEditorId(`ultidrive-editor-${instanceSeq.current}`)
|
||
} catch {
|
||
if (!cancelled) setError("Impossible de charger l’éditeur.")
|
||
}
|
||
})()
|
||
|
||
return () => {
|
||
cancelled = true
|
||
}
|
||
}, [displayPath])
|
||
|
||
const handleRename = useCallback(
|
||
async (input: string) => {
|
||
const newName = resolveRenameName(
|
||
{ name: fileName, type: "file" },
|
||
input
|
||
)
|
||
if (displayFileBaseName(fileName) === input.trim()) return
|
||
await rename.mutateAsync({ path: displayPath, new_name: newName })
|
||
const nextPath = renameTargetPath(displayPath, newName)
|
||
setDisplayPath(nextPath)
|
||
router.replace(buildDriveEditHref(nextPath, returnTo ?? undefined))
|
||
},
|
||
[displayPath, fileName, rename, returnTo, router]
|
||
)
|
||
|
||
const openShareDialog = useCallback(() => {
|
||
setSharePath(displayPath, "file")
|
||
}, [displayPath, setSharePath])
|
||
|
||
if (error) {
|
||
return (
|
||
<div className="flex h-dvh flex-col items-center justify-center gap-4">
|
||
<p className="text-destructive">{error}</p>
|
||
<Button asChild variant="outline">
|
||
<Link href={backHref}>
|
||
<ArrowLeft className="mr-2 h-4 w-4" />
|
||
Retour
|
||
</Link>
|
||
</Button>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
if (!config || !editorId || !serverUrl) {
|
||
return <p className="p-8 text-center text-muted-foreground">Ouverture du document…</p>
|
||
}
|
||
|
||
const docServer = serverUrl.replace(/\/$/, "")
|
||
|
||
return (
|
||
<div className="flex h-dvh flex-col">
|
||
<OfficeEditorChrome
|
||
backHref={backHref}
|
||
backLabel="Drive"
|
||
title={title}
|
||
onRename={handleRename}
|
||
shares={sharesData?.shares ?? []}
|
||
onShareClick={openShareDialog}
|
||
showShare
|
||
showAccount
|
||
/>
|
||
<div className="relative min-h-0 flex-1">
|
||
<OnlyOfficeMount
|
||
editorId={editorId}
|
||
documentServerUrl={docServer}
|
||
config={config}
|
||
onError={handleEditorError}
|
||
/>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|