223 lines
7.0 KiB
TypeScript
223 lines
7.0 KiB
TypeScript
"use client"
|
|
|
|
import { useRef } from "react"
|
|
import type { Editor } from "@tiptap/react"
|
|
import { Icon } from "@iconify/react"
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuSub,
|
|
DropdownMenuSubContent,
|
|
DropdownMenuSubTrigger,
|
|
DropdownMenuTrigger,
|
|
} from "@/components/ui/dropdown-menu"
|
|
import { Button } from "@/components/ui/button"
|
|
import { buildInsertGraphicAttrs } from "@/lib/drive/extensions/docs-graphic"
|
|
import {
|
|
DOCS_GRAPHIC_PLACEMENT_LABELS,
|
|
DOCS_GRAPHIC_WRAP_LABELS,
|
|
type DocsGraphicFloatSide,
|
|
type DocsGraphicPlacement,
|
|
type DocsGraphicWrap,
|
|
parseGraphicAttrs,
|
|
} from "@/lib/drive/docs-graphic-types"
|
|
|
|
function readSelectedGraphicAttrs(editor: Editor) {
|
|
if (editor.isActive("docsGraphic")) {
|
|
return parseGraphicAttrs(editor.getAttributes("docsGraphic") as Record<string, unknown>)
|
|
}
|
|
if (editor.isActive("docsInlineGraphic")) {
|
|
return parseGraphicAttrs(editor.getAttributes("docsInlineGraphic") as Record<string, unknown>)
|
|
}
|
|
return null
|
|
}
|
|
|
|
export function DocsGraphicInsertMenu({
|
|
editor,
|
|
disabled,
|
|
}: {
|
|
editor: Editor | null
|
|
disabled?: boolean
|
|
}) {
|
|
const imageInputRef = useRef<HTMLInputElement>(null)
|
|
|
|
if (!editor) return null
|
|
|
|
const insertImage = (file: File, options?: { wrap?: DocsGraphicWrap; placement?: DocsGraphicPlacement }) => {
|
|
const reader = new FileReader()
|
|
reader.onload = () => {
|
|
const src = reader.result as string
|
|
editor
|
|
.chain()
|
|
.focus()
|
|
.insertDocsGraphic(
|
|
buildInsertGraphicAttrs("image", {
|
|
src,
|
|
wrap: options?.wrap ?? "square",
|
|
placement: options?.placement ?? "block",
|
|
width: 280,
|
|
height: 180,
|
|
})
|
|
)
|
|
.run()
|
|
}
|
|
reader.readAsDataURL(file)
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button
|
|
type="button"
|
|
variant="ghost"
|
|
size="icon"
|
|
className="docs-toolbar-btn size-7 shrink-0"
|
|
disabled={disabled}
|
|
aria-label="Insérer un élément graphique"
|
|
>
|
|
<Icon icon="material-symbols:image-outline" className="size-4" />
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="start" className="min-w-56">
|
|
<DropdownMenuItem onClick={() => imageInputRef.current?.click()}>
|
|
Image…
|
|
</DropdownMenuItem>
|
|
<DropdownMenuSub>
|
|
<DropdownMenuSubTrigger>Forme</DropdownMenuSubTrigger>
|
|
<DropdownMenuSubContent>
|
|
{(["rect", "ellipse", "line", "arrow"] as const).map((shapeType) => (
|
|
<DropdownMenuItem
|
|
key={shapeType}
|
|
onClick={() =>
|
|
editor
|
|
.chain()
|
|
.focus()
|
|
.insertDocsGraphic(buildInsertGraphicAttrs("shape", { shapeType }))
|
|
.run()
|
|
}
|
|
>
|
|
{shapeType === "rect"
|
|
? "Rectangle"
|
|
: shapeType === "ellipse"
|
|
? "Ellipse"
|
|
: shapeType === "line"
|
|
? "Ligne"
|
|
: "Flèche"}
|
|
</DropdownMenuItem>
|
|
))}
|
|
</DropdownMenuSubContent>
|
|
</DropdownMenuSub>
|
|
<DropdownMenuItem
|
|
onClick={() =>
|
|
editor
|
|
.chain()
|
|
.focus()
|
|
.insertDocsGraphic(buildInsertGraphicAttrs("gradient"))
|
|
.run()
|
|
}
|
|
>
|
|
Dégradé
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
<input
|
|
ref={imageInputRef}
|
|
type="file"
|
|
accept="image/*"
|
|
className="hidden"
|
|
onChange={(event) => {
|
|
const file = event.target.files?.[0]
|
|
if (file) insertImage(file)
|
|
event.target.value = ""
|
|
}}
|
|
/>
|
|
</>
|
|
)
|
|
}
|
|
|
|
export function DocsGraphicLayoutMenu({
|
|
editor,
|
|
disabled,
|
|
}: {
|
|
editor: Editor | null
|
|
disabled?: boolean
|
|
}) {
|
|
if (!editor) return null
|
|
const attrs = readSelectedGraphicAttrs(editor)
|
|
if (!attrs) return null
|
|
|
|
const applyWrap = (wrap: DocsGraphicWrap) => {
|
|
editor.chain().focus().setDocsGraphicWrap(wrap).run()
|
|
}
|
|
const applyPlacement = (placement: DocsGraphicPlacement) => {
|
|
editor.chain().focus().setDocsGraphicPlacement(placement).run()
|
|
}
|
|
const applyFloatSide = (floatSide: DocsGraphicFloatSide) => {
|
|
editor.chain().focus().setDocsGraphicFloatSide(floatSide).run()
|
|
}
|
|
|
|
return (
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button
|
|
type="button"
|
|
variant="ghost"
|
|
size="icon"
|
|
className="docs-toolbar-btn docs-toolbar-btn--active size-7 shrink-0"
|
|
disabled={disabled}
|
|
aria-label="Disposition de l'élément graphique"
|
|
>
|
|
<Icon icon="material-symbols:layers-outline" className="size-4" />
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="start" className="min-w-64">
|
|
<DropdownMenuSub>
|
|
<DropdownMenuSubTrigger>Habillage texte</DropdownMenuSubTrigger>
|
|
<DropdownMenuSubContent>
|
|
{(Object.keys(DOCS_GRAPHIC_WRAP_LABELS) as DocsGraphicWrap[]).map((wrap) => (
|
|
<DropdownMenuItem key={wrap} onClick={() => applyWrap(wrap)}>
|
|
{DOCS_GRAPHIC_WRAP_LABELS[wrap]}
|
|
{attrs.wrap === wrap ? " ✓" : ""}
|
|
</DropdownMenuItem>
|
|
))}
|
|
</DropdownMenuSubContent>
|
|
</DropdownMenuSub>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuSub>
|
|
<DropdownMenuSubTrigger>Placement</DropdownMenuSubTrigger>
|
|
<DropdownMenuSubContent>
|
|
{(Object.keys(DOCS_GRAPHIC_PLACEMENT_LABELS) as DocsGraphicPlacement[]).map(
|
|
(placement) => (
|
|
<DropdownMenuItem key={placement} onClick={() => applyPlacement(placement)}>
|
|
{DOCS_GRAPHIC_PLACEMENT_LABELS[placement]}
|
|
{attrs.placement === placement ? " ✓" : ""}
|
|
</DropdownMenuItem>
|
|
)
|
|
)}
|
|
</DropdownMenuSubContent>
|
|
</DropdownMenuSub>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuSub>
|
|
<DropdownMenuSubTrigger>Côté du flottement</DropdownMenuSubTrigger>
|
|
<DropdownMenuSubContent>
|
|
{(["left", "right", "center"] as const).map((side) => (
|
|
<DropdownMenuItem key={side} onClick={() => applyFloatSide(side)}>
|
|
{side === "left" ? "Gauche" : side === "right" ? "Droite" : "Centre"}
|
|
{attrs.floatSide === side ? " ✓" : ""}
|
|
</DropdownMenuItem>
|
|
))}
|
|
</DropdownMenuSubContent>
|
|
</DropdownMenuSub>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
)
|
|
}
|
|
|
|
export function readGraphicToolbarActive(editor: Editor | null): boolean {
|
|
if (!editor) return false
|
|
return editor.isActive("docsGraphic") || editor.isActive("docsInlineGraphic")
|
|
}
|