ultisuite-client/lib/drive/docs-graphic-layout.ts
R3D347HR4Y 2a7c153748
Some checks are pending
E2E / Playwright e2e (push) Waiting to run
wrap page
2026-06-10 12:48:27 +02:00

152 lines
4.5 KiB
TypeScript

import type { CSSProperties } from "react"
import {
type DocsGraphicAttrs,
parseGraphicAttrs,
} from "./docs-graphic-types.ts"
export type DocsGraphicLayoutStyle = {
wrapper: CSSProperties
inner: CSSProperties
content: CSSProperties
behindText: boolean
inFrontText: boolean
}
export function computeGraphicLayoutStyle(
raw: Record<string, unknown> | DocsGraphicAttrs
): DocsGraphicLayoutStyle {
const attrs = "graphicType" in raw && typeof raw.graphicType === "string"
? (raw as DocsGraphicAttrs)
: parseGraphicAttrs(raw)
const width = attrs.width
const height = attrs.height
const rotation = attrs.rotationDeg
const transformParts = rotation ? [`rotate(${rotation}deg)`] : []
const behindText = attrs.wrap === "behind"
const inFrontText = attrs.wrap === "in-front"
const isAbsolute = attrs.placement === "absolute" || behindText || inFrontText
const wrapper: CSSProperties = {
width: isAbsolute ? undefined : width,
height: isAbsolute ? undefined : height,
maxWidth: "100%",
}
const inner: CSSProperties = {
width,
height,
transform: transformParts.length ? transformParts.join(" ") : undefined,
transformOrigin: "center center",
position: isAbsolute ? "absolute" : "relative",
left: isAbsolute ? attrs.x : undefined,
top: isAbsolute ? attrs.y : undefined,
zIndex: behindText ? 0 : inFrontText ? 20 : attrs.zIndex || undefined,
pointerEvents: behindText ? "none" : "auto",
opacity: attrs.opacity < 1 ? attrs.opacity : undefined,
boxShadow: attrs.shadow || undefined,
}
if (attrs.placement === "inline" || attrs.wrap === "inline") {
inner.display = "inline-block"
inner.verticalAlign = "baseline"
} else if (attrs.wrap === "top-bottom") {
inner.display = "block"
inner.clear = "both"
inner.marginBlock = "8px"
if (attrs.floatSide === "center") {
inner.marginInline = "auto"
}
} else if (attrs.wrap === "square" || attrs.wrap === "tight" || attrs.wrap === "through") {
inner.display = "block"
inner.float = attrs.floatSide === "right" ? "right" : "left"
inner.marginInlineStart = attrs.floatSide === "right" ? "12px" : undefined
inner.marginInlineEnd = attrs.floatSide === "right" ? undefined : "12px"
inner.marginBlock = "4px"
if (attrs.wrap === "tight") {
inner.shapeOutside = "margin-box"
inner.marginInlineStart = attrs.floatSide === "right" ? "6px" : undefined
inner.marginInlineEnd = attrs.floatSide === "right" ? undefined : "6px"
}
if (attrs.wrap === "through") {
inner.opacity = 0.85
inner.mixBlendMode = "multiply"
}
} else if (attrs.placement === "block") {
inner.display = "block"
inner.marginBlock = "8px"
if (attrs.floatSide === "center") {
inner.marginInline = "auto"
}
}
const content: CSSProperties = {
width: "100%",
height: "100%",
overflow: "hidden",
}
return { wrapper, inner, content, behindText, inFrontText }
}
export const RESIZE_HANDLES = [
"nw",
"n",
"ne",
"e",
"se",
"s",
"sw",
"w",
] as const
export type ResizeHandle = (typeof RESIZE_HANDLES)[number]
export function resizeWithHandle(
handle: ResizeHandle,
startWidth: number,
startHeight: number,
dx: number,
dy: number,
minSize = 24,
lockAspect = false
): { width: number; height: number; xOffset: number; yOffset: number } {
let width = startWidth
let height = startHeight
let xOffset = 0
let yOffset = 0
if (lockAspect) {
const aspect = startWidth / Math.max(startHeight, 1)
if (Math.abs(dx) >= Math.abs(dy)) {
width = startWidth + (handle.includes("w") ? -dx : handle.includes("e") ? dx : dx)
height = width / aspect
} else {
height = startHeight + (handle.includes("n") ? -dy : handle.includes("s") ? dy : dy)
width = height * aspect
}
if (handle.includes("w")) xOffset = startWidth - width
if (handle.includes("n")) yOffset = startHeight - height
} else {
if (handle.includes("e")) width = startWidth + dx
if (handle.includes("w")) {
width = startWidth - dx
xOffset = dx
}
if (handle.includes("s")) height = startHeight + dy
if (handle.includes("n")) {
height = startHeight - dy
yOffset = dy
}
}
width = Math.max(minSize, width)
height = Math.max(minSize, height)
if (handle.includes("w") && width === minSize) xOffset = startWidth - minSize
if (handle.includes("n") && height === minSize) yOffset = startHeight - minSize
return { width: Math.round(width), height: Math.round(height), xOffset, yOffset }
}