ultisuite-client/lib/drive/use-docs-insert-menu.tsx
R3D347HR4Y 303b2b1074
Some checks are pending
E2E / Playwright e2e (push) Waiting to run
wow
2026-06-11 01:22:40 +02:00

200 lines
6.3 KiB
TypeScript

"use client"
import { useCallback, useMemo, useRef, useState, type ReactNode } from "react"
import type { Editor } from "@tiptap/react"
import type { DocsInsertMenuActions } from "@/components/drive/richtext/docs-insert-menu"
import { DocsDriveDrawPickerDialog } from "@/components/drive/richtext/docs-drive-draw-picker-dialog"
import { DocsDriveImagePickerDialog } from "@/components/drive/richtext/docs-drive-image-picker-dialog"
import { DocsPageNumbersDialog } from "@/components/drive/richtext/docs-header-footer-dialogs"
import { openDocsGraphicDrawModal } from "@/lib/drive/docs-graphic-draw-bridge"
import { importExcalidrawFromDriveFile } from "@/lib/drive/docs-graphic-draw-import"
import { openDocsLinkPopover } from "@/lib/drive/docs-link-bridge"
import { startDocsRegionEdit } from "@/lib/drive/docs-page-elements-bridge"
import type { DocPageSetup } from "@/lib/drive/doc-page-setup"
import {
buildImageInsertGraphicAttrs,
buildInsertGraphicAttrs,
} from "@/lib/drive/extensions/docs-graphic"
import type { DriveFileInfo } from "@/lib/api/types"
export function useDocsInsertMenu({
editor,
disabled,
pageSetup,
onPageSetupPatch,
}: {
editor: Editor | null
disabled?: boolean
pageSetup?: DocPageSetup | null
onPageSetupPatch?: (patch: Partial<DocPageSetup>) => void
}) {
const imageInputRef = useRef<HTMLInputElement>(null)
const [driveImagePickerOpen, setDriveImagePickerOpen] = useState(false)
const [driveDrawPickerOpen, setDriveDrawPickerOpen] = useState(false)
const [pageNumbersOpen, setPageNumbersOpen] = useState(false)
const insertImageSrc = useCallback(
(src: string, options?: { alt?: string }) => {
if (!editor) return
void buildImageInsertGraphicAttrs({
src,
alt: options?.alt ?? "",
wrap: "square",
placement: "block",
}).then((attrs) => {
editor.chain().focus().insertDocsGraphic(attrs).run()
})
},
[editor]
)
const insertImageFile = useCallback(
(file: File) => {
const reader = new FileReader()
reader.onload = () => {
const src = reader.result as string
insertImageSrc(src, { alt: file.name })
}
reader.readAsDataURL(file)
},
[insertImageSrc]
)
const applyPageNumbers = useCallback(
(placement: "header" | "footer") => {
onPageSetupPatch?.({
pageNumbers: {
enabled: true,
placement,
align: "right",
startAt: pageSetup?.pageNumbers?.startAt ?? 1,
showOnFirstPage: pageSetup?.pageNumbers?.showOnFirstPage ?? true,
},
})
startDocsRegionEdit(placement)
},
[onPageSetupPatch, pageSetup?.pageNumbers]
)
const actions = useMemo<DocsInsertMenuActions>(
() => ({
onInsertImageFromComputer: () => imageInputRef.current?.click(),
onInsertImageFromDrive: () => setDriveImagePickerOpen(true),
onInsertNewDraw: () => {
if (!editor) return
editor
.chain()
.focus()
.insertDocsGraphic(
buildInsertGraphicAttrs("draw", { width: 320, height: 240, drawDriveFileId: null })
)
.run()
openDocsGraphicDrawModal()
},
onInsertDrawFromDrive: () => setDriveDrawPickerOpen(true),
onInsertLink: () => openDocsLinkPopover(),
onInsertHorizontalRule: () => {
editor?.chain().focus().insertContent({ type: "horizontalRule" }).run()
},
onInsertTable: (rows, cols) => {
editor
?.chain()
.focus()
.insertTable({ rows, cols, withHeaderRow: false })
.run()
},
onInsertHeader: () => startDocsRegionEdit("header"),
onInsertFooter: () => startDocsRegionEdit("footer"),
onInsertWatermark: () => {
const text = window.prompt("Texte du filigrane", pageSetup?.pageBackground?.watermark?.text ?? "")
if (text == null || !text.trim()) return
onPageSetupPatch?.({
pageBackground: {
...pageSetup?.pageBackground,
watermark: {
kind: "text",
text: text.trim(),
color: pageSetup?.pageBackground?.watermark?.color ?? "#b4b4b4",
opacity: pageSetup?.pageBackground?.watermark?.opacity ?? 0.35,
rotationDeg: pageSetup?.pageBackground?.watermark?.rotationDeg ?? -35,
},
},
})
},
onInsertPageNumbersHeader: () => applyPageNumbers("header"),
onInsertPageNumbersFooter: () => applyPageNumbers("footer"),
onOpenPageNumbersOptions: () => setPageNumbersOpen(true),
}),
[applyPageNumbers, editor, onPageSetupPatch, pageSetup?.pageBackground]
)
const handlePickDriveImage = useCallback(
async (src: string, file: DriveFileInfo) => {
insertImageSrc(src, { alt: file.name })
},
[insertImageSrc]
)
const handlePickDriveDraw = useCallback(
async (file: DriveFileInfo) => {
if (!editor) return
const imported = await importExcalidrawFromDriveFile(file)
editor
.chain()
.focus()
.insertDocsGraphic(
buildInsertGraphicAttrs("draw", {
drawScene: imported.drawScene,
src: imported.src,
width: imported.width,
height: imported.height,
drawDriveFileId: imported.drawDriveFileId,
alt: imported.alt,
lockAspectRatio: true,
})
)
.run()
},
[editor]
)
const dialogs: ReactNode = (
<>
<DocsDriveImagePickerDialog
open={driveImagePickerOpen}
onOpenChange={setDriveImagePickerOpen}
onPickImage={handlePickDriveImage}
/>
<DocsDriveDrawPickerDialog
open={driveDrawPickerOpen}
onOpenChange={setDriveDrawPickerOpen}
onPickDraw={handlePickDriveDraw}
/>
<DocsPageNumbersDialog
open={pageNumbersOpen}
onOpenChange={setPageNumbersOpen}
settings={pageSetup?.pageNumbers}
onApply={(pageNumbers) => onPageSetupPatch?.({ pageNumbers })}
/>
<input
ref={imageInputRef}
type="file"
accept="image/*"
className="hidden"
onChange={(event) => {
const file = event.target.files?.[0]
if (file) insertImageFile(file)
event.target.value = ""
}}
/>
</>
)
return {
actions,
dialogs,
disabled,
pageElementsEnabled: Boolean(onPageSetupPatch),
}
}