From d45dd79fbaa0e2b127376f7b3f9d87fcf5616430 Mon Sep 17 00:00:00 2001 From: kripu77 Date: Sun, 19 Apr 2026 16:09:50 +1000 Subject: [PATCH 1/4] fix(theme): pick text color from fill luminance so dark themes stay readable In dark/starry themes, text on custom (non-default) light fills was being forced to the bright theme ink and rendering invisible. Thread the post-remap effective fill into the text sync and derive readable ink from fill luminance, falling back to theme ink only when the element has no opaque fill. Co-Authored-By: Claude Opus 4.7 (1M context) --- features/board/utils/theme-elements.ts | 100 ++++++++++++++++++++++--- tests/unit/theme-elements.test.ts | 99 ++++++++++++++++++++++++ 2 files changed, 190 insertions(+), 9 deletions(-) diff --git a/features/board/utils/theme-elements.ts b/features/board/utils/theme-elements.ts index 99e3a9f..87232f3 100644 --- a/features/board/utils/theme-elements.ts +++ b/features/board/utils/theme-elements.ts @@ -70,6 +70,77 @@ function normalizeColorToken(color: string): string { return value; } +function parseRgb(color: string): { r: number; g: number; b: number; a: number } | null { + const value = color.trim().toLowerCase(); + + const shortHex = value.match(/^#([0-9a-f])([0-9a-f])([0-9a-f])$/); + if (shortHex) { + const [, r, g, b] = shortHex; + return { + r: parseInt(r + r, 16), + g: parseInt(g + g, 16), + b: parseInt(b + b, 16), + a: 1, + }; + } + + const longHex = value.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/); + if (longHex) { + const [, r, g, b] = longHex; + return { r: parseInt(r, 16), g: parseInt(g, 16), b: parseInt(b, 16), a: 1 }; + } + + const rgbMatch = value.match( + /^rgba?\(\s*(\d{1,3})[\s,]+(\d{1,3})[\s,]+(\d{1,3})(?:[\s,\/]+([01]?(?:\.\d+)?))?\s*\)$/, + ); + if (rgbMatch) { + const [, r, g, b, alpha] = rgbMatch; + return { + r: Number(r), + g: Number(g), + b: Number(b), + a: alpha === undefined ? 1 : Number(alpha), + }; + } + + return null; +} + +function relativeLuminance(channel: number): number { + const c = channel / 255; + return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4); +} + +function isLightFill(fill: string): boolean { + const rgb = parseRgb(fill); + if (!rgb || rgb.a === 0) { + return false; + } + const luminance = + 0.2126 * relativeLuminance(rgb.r) + + 0.7152 * relativeLuminance(rgb.g) + + 0.0722 * relativeLuminance(rgb.b); + return luminance > 0.5; +} + +function hasOpaqueFill(fill: string | undefined): fill is string { + if (typeof fill !== 'string') { + return false; + } + const rgb = parseRgb(fill); + return !!rgb && rgb.a > 0; +} + +function resolveTextInkForFill( + effectiveFill: string | undefined, + theme: BoardThemeMode, +): string { + if (hasOpaqueFill(effectiveFill)) { + return isLightFill(effectiveFill) ? LIGHT_BOARD_INK.text : DARK_BOARD_INK.text; + } + return getBoardInkColors(theme).text; +} + function remapDefaultInk(color: string, theme: BoardThemeMode, kind: 'stroke' | 'text') { const normalized = normalizeColorToken(color); const lightValue = kind === 'stroke' ? LIGHT_BOARD_INK.stroke : LIGHT_BOARD_INK.text; @@ -113,34 +184,42 @@ function isRootMindElement(element: ThemeSyncElement): boolean { return element.type === 'mindmap' && element.isRoot === true; } -function syncSlateTextValue(text: SlateTextValue | undefined, theme: BoardThemeMode) { +function syncSlateTextValue( + text: SlateTextValue | undefined, + theme: BoardThemeMode, + effectiveFill?: string, +) { if (!text || !Array.isArray(text.children)) { return text; } let changed = false; - const targetInk = getBoardInkColors(theme); + const targetTextColor = resolveTextInkForFill(effectiveFill, theme); const nextChildren = text.children.map((child) => { if (!child || typeof child !== 'object' || typeof child.text !== 'string') { return child; } if (!child.color) { - if (targetInk.text === LIGHT_BOARD_INK.text) { + if (targetTextColor === LIGHT_BOARD_INK.text && !hasOpaqueFill(effectiveFill)) { return child; } changed = true; - return { ...child, color: targetInk.text }; + return { ...child, color: targetTextColor }; + } + + const normalized = normalizeColorToken(child.color); + if (normalized !== LIGHT_BOARD_INK.text && normalized !== DARK_BOARD_INK.text) { + return child; } - const nextColor = remapDefaultInk(child.color, theme, 'text'); - if (nextColor === child.color) { + if (normalized === targetTextColor) { return child; } changed = true; - return { ...child, color: nextColor }; + return { ...child, color: targetTextColor }; }); return changed ? { ...text, children: nextChildren } : text; @@ -170,13 +249,16 @@ function syncElementForBoardTheme( changed = true; } + let effectiveFill: string | undefined; if (targetRootFill && (typeof element.fill !== 'string' || isThinkixMindRootFill(element.fill))) { + effectiveFill = targetRootFill; if (element.fill !== targetRootFill) { next.fill = targetRootFill; changed = true; } } else if (typeof element.fill === 'string') { const fill = remapDefaultFill(element.fill, theme); + effectiveFill = fill; if (fill !== element.fill) { next.fill = fill; changed = true; @@ -194,13 +276,13 @@ function syncElementForBoardTheme( changed = true; } - const text = syncSlateTextValue(element.text, theme); + const text = syncSlateTextValue(element.text, theme, effectiveFill); if (text !== element.text) { next.text = text; changed = true; } - const topic = syncSlateTextValue(element.data?.topic, theme); + const topic = syncSlateTextValue(element.data?.topic, theme, effectiveFill); if (element.data && topic !== element.data.topic) { next.data = { ...element.data, topic }; changed = true; diff --git a/tests/unit/theme-elements.test.ts b/tests/unit/theme-elements.test.ts index cb43bbb..05054dd 100644 --- a/tests/unit/theme-elements.test.ts +++ b/tests/unit/theme-elements.test.ts @@ -213,4 +213,103 @@ describe('syncElementsForBoardTheme', () => { expect(nextElements[0].fill).toBe('#17344b'); }); + + it('uses dark text on a custom light fill in dark themes', () => { + const elements = [ + { + id: 'shape-1', + type: 'geometry', + shape: 'rectangle', + fill: '#bbf7d0', + text: { children: [{ text: 'Box' }] }, + }, + ] as unknown as PlaitElement[]; + + const nextElements = syncElementsForBoardTheme(elements, 'dark') as Array<{ + fill?: string; + text?: { children: Array<{ color?: string }> }; + }>; + + expect(nextElements[0].fill).toBe('#bbf7d0'); + expect(nextElements[0].text?.children[0].color).toBe('#000000'); + }); + + it('keeps default-ink text dark on a custom light fill in starry themes', () => { + const elements = [ + { + id: 'shape-1', + type: 'geometry', + shape: 'rectangle', + fill: '#fef3c7', + text: { children: [{ text: 'Box', color: '#000000' }] }, + }, + ] as unknown as PlaitElement[]; + + const nextElements = syncElementsForBoardTheme(elements, 'starry') as Array<{ + fill?: string; + text?: { children: Array<{ color?: string }> }; + }>; + + expect(nextElements[0].fill).toBe('#fef3c7'); + expect(nextElements[0].text?.children[0].color).toBe('#000000'); + }); + + it('uses light text on a custom dark fill in light themes', () => { + const elements = [ + { + id: 'shape-1', + type: 'geometry', + shape: 'rectangle', + fill: '#1e293b', + text: { children: [{ text: 'Box' }] }, + }, + ] as unknown as PlaitElement[]; + + const nextElements = syncElementsForBoardTheme(elements, 'default') as Array<{ + fill?: string; + text?: { children: Array<{ color?: string }> }; + }>; + + expect(nextElements[0].fill).toBe('#1e293b'); + expect(nextElements[0].text?.children[0].color).toBe('#f8fafc'); + }); + + it('preserves a user-customized text color even on custom fills', () => { + const elements = [ + { + id: 'shape-1', + type: 'geometry', + shape: 'rectangle', + fill: '#bbf7d0', + text: { children: [{ text: 'Box', color: '#ff00aa' }] }, + }, + ] as unknown as PlaitElement[]; + + const nextElements = syncElementsForBoardTheme(elements, 'dark') as Array<{ + text?: { children: Array<{ color?: string }> }; + }>; + + expect(nextElements[0].text?.children[0].color).toBe('#ff00aa'); + }); + + it('uses contrast-aware text on a mind topic with a custom fill', () => { + const elements = [ + { + id: 'mind-1', + type: 'mindmap', + fill: '#bbf7d0', + data: { + topic: { children: [{ text: 'Branch' }] }, + }, + }, + ] as unknown as PlaitElement[]; + + const nextElements = syncElementsForBoardTheme(elements, 'dark') as Array<{ + fill?: string; + data?: { topic?: { children: Array<{ color?: string }> } }; + }>; + + expect(nextElements[0].fill).toBe('#bbf7d0'); + expect(nextElements[0].data?.topic?.children[0].color).toBe('#000000'); + }); }); From e6698bd30db19706dde734904427407c3797404e Mon Sep 17 00:00:00 2001 From: kripu77 Date: Sun, 19 Apr 2026 16:09:57 +1000 Subject: [PATCH 2/4] fix(sticky): grow notes to fit content instead of overflowing the 320px cap Long-form sticky notes were being clamped to 320x320, so any content past ~14 wrapped lines spilled outside the box and rendered unreadably. Drop the height cap, widen the width budget to 280px, and derive height directly from the wrapped line count using the chosen width. Co-Authored-By: Claude Opus 4.7 (1M context) --- features/board/utils/sticky-note.ts | 40 +++++++++-------------------- tests/unit/sticky-note.test.ts | 37 ++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 28 deletions(-) diff --git a/features/board/utils/sticky-note.ts b/features/board/utils/sticky-note.ts index 61661ef..7150e0e 100644 --- a/features/board/utils/sticky-note.ts +++ b/features/board/utils/sticky-note.ts @@ -8,11 +8,11 @@ import { type StickyColorName, } from '@/shared/constants'; -const STICKY_TEXT_PADDING = 30; +const STICKY_TEXT_PADDING = 28; const STICKY_LINE_HEIGHT = 22; const STICKY_CHAR_WIDTH = 8; const MIN_STICKY_SIZE = 130; -const MAX_STICKY_SIZE = 320; +const MAX_STICKY_WIDTH = 280; export function isStickyColorName(value: string): value is StickyColorName { return value in STICKY_COLORS; @@ -28,18 +28,16 @@ export function estimateStickySize(text: string): { const lines = text.split(/\r?\n/); const longestLineLength = Math.max(...lines.map((line) => line.length), 1); - const targetWidth = Math.min( - 220, - Math.max( - MIN_STICKY_SIZE, - Math.ceil( - longestLineLength * STICKY_CHAR_WIDTH * 0.6 + STICKY_TEXT_PADDING, - ), - ), + + const desiredContentWidth = Math.ceil(longestLineLength * STICKY_CHAR_WIDTH * 0.6); + const width = Math.min( + MAX_STICKY_WIDTH, + Math.max(MIN_STICKY_SIZE, desiredContentWidth + STICKY_TEXT_PADDING), ); + const charsPerLine = Math.max( 1, - Math.floor((targetWidth - STICKY_TEXT_PADDING) / STICKY_CHAR_WIDTH), + Math.floor((width - STICKY_TEXT_PADDING) / (STICKY_CHAR_WIDTH * 0.6)), ); const wrappedLineCount = Math.max( 1, @@ -48,23 +46,9 @@ export function estimateStickySize(text: string): { 0, ), ); - const width = Math.min( - MAX_STICKY_SIZE, - Math.max( - MIN_STICKY_SIZE, - Math.max( - targetWidth, - Math.min(220, longestLineLength * STICKY_CHAR_WIDTH * 0.75) + - STICKY_TEXT_PADDING, - ), - ), - ); - const height = Math.min( - MAX_STICKY_SIZE, - Math.max( - MIN_STICKY_SIZE, - wrappedLineCount * STICKY_LINE_HEIGHT + STICKY_TEXT_PADDING, - ), + const height = Math.max( + MIN_STICKY_SIZE, + wrappedLineCount * STICKY_LINE_HEIGHT + STICKY_TEXT_PADDING, ); return { width, height }; diff --git a/tests/unit/sticky-note.test.ts b/tests/unit/sticky-note.test.ts index 0e321e7..22ab4f1 100644 --- a/tests/unit/sticky-note.test.ts +++ b/tests/unit/sticky-note.test.ts @@ -179,6 +179,43 @@ describe('with-sticky-note', () => { expect(long.width).toBeGreaterThan(short.width); }); + + it('grows beyond 320px tall when content has many lines', async () => { + const { estimateStickySize } = await import('@/features/board/utils/sticky-note'); + + const longContent = Array.from({ length: 20 }, (_, i) => `Line ${i + 1}`).join('\n'); + const result = estimateStickySize(longContent); + + expect(result.height).toBeGreaterThan(320); + }); + + it('keeps width bounded at a sensible post-it max even for very long single lines', async () => { + const { estimateStickySize } = await import('@/features/board/utils/sticky-note'); + + const veryLong = 'a'.repeat(500); + const result = estimateStickySize(veryLong); + + expect(result.width).toBeLessThanOrEqual(320); + expect(result.width).toBeGreaterThanOrEqual(220); + }); + + it('returns a square minimum size for empty text', async () => { + const { estimateStickySize } = await import('@/features/board/utils/sticky-note'); + + const result = estimateStickySize(''); + + expect(result.width).toBe(result.height); + expect(result.width).toBeGreaterThanOrEqual(130); + }); + + it('produces enough vertical space for every wrapped line', async () => { + const { estimateStickySize } = await import('@/features/board/utils/sticky-note'); + + const lines = Array.from({ length: 30 }, (_, i) => `Item ${i + 1}: detail`); + const result = estimateStickySize(lines.join('\n')); + + expect(result.height).toBeGreaterThanOrEqual(lines.length * 18); + }); }); describe('plugin application', () => { From ba1837fc73345062d6db2972b57fa280e0ac7704 Mon Sep 17 00:00:00 2001 From: kripu77 Date: Sun, 19 Apr 2026 16:10:06 +1000 Subject: [PATCH 3/4] fix(scripts): harden apply-patches.sh so prod builds fail loudly on patch errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The script hard-coded \$PROJECT_ROOT/node_modules/@plait/draw to detect the installed version. In contexts where node_modules sits elsewhere (git worktrees, hoisted monorepo installs, isolated installs), the require() threw, version became "unknown", and the script silently exit 0'd — shipping unpatched code to production with no signal. Resolve via require.resolve so it works regardless of cwd or layout, fail non-zero on every error branch, surface patch's stderr, detect missing 'patch' binary, and verify the marker is present after applying. Co-Authored-By: Claude Opus 4.7 (1M context) --- scripts/apply-patches.sh | 69 ++++++++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 20 deletions(-) diff --git a/scripts/apply-patches.sh b/scripts/apply-patches.sh index c732932..5a098e6 100755 --- a/scripts/apply-patches.sh +++ b/scripts/apply-patches.sh @@ -1,36 +1,65 @@ #!/bin/bash +set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -# Get actual installed version of @plait/draw -DRAW_VERSION=$(node -p "require('$PROJECT_ROOT/node_modules/@plait/draw/package.json').version" 2>/dev/null || echo "unknown") -PATCH_FILE="$PROJECT_ROOT/patches/@plait+draw+$DRAW_VERSION.patch" -TARGET_DIR="$PROJECT_ROOT/node_modules/@plait/draw/fesm2022" -TARGET_FILE="$TARGET_DIR/plait-draw.mjs" +cd "$PROJECT_ROOT" -if [ "$DRAW_VERSION" = "unknown" ]; then - echo "⚠ Could not determine @plait/draw version" - exit 0 +if ! command -v node >/dev/null 2>&1; then + echo "✗ apply-patches: 'node' is required but not on PATH" >&2 + exit 1 +fi + +if ! command -v patch >/dev/null 2>&1; then + echo "✗ apply-patches: 'patch' binary not available — install GNU patch" >&2 + echo " alpine: apk add patch" >&2 + echo " debian/ubuntu: apt-get install patch" >&2 + exit 1 fi +DRAW_PKG_JSON=$(node -p "require.resolve('@plait/draw/package.json')" 2>/dev/null) || { + echo "✗ apply-patches: cannot resolve @plait/draw — is it installed?" >&2 + echo " cwd: $PROJECT_ROOT" >&2 + exit 1 +} + +DRAW_VERSION=$(node -p "require('$DRAW_PKG_JSON').version") +PATCH_FILE="$PROJECT_ROOT/patches/@plait+draw+$DRAW_VERSION.patch" +TARGET_DIR="$(dirname "$DRAW_PKG_JSON")/fesm2022" +TARGET_FILE="$TARGET_DIR/plait-draw.mjs" +MARKER="fillStyle: element.fillStyle" + if [ ! -f "$PATCH_FILE" ]; then - echo "⚠ Patch file not found: $PATCH_FILE (looking for version $DRAW_VERSION)" - exit 0 + echo "✗ apply-patches: patch file not found for @plait/draw v$DRAW_VERSION" >&2 + echo " expected: $PATCH_FILE" >&2 + echo " available patches:" >&2 + ls "$PROJECT_ROOT/patches/" >&2 || true + exit 1 fi if [ ! -f "$TARGET_FILE" ]; then - echo "⚠ Target file not found: $TARGET_FILE" + echo "✗ apply-patches: target file not found: $TARGET_FILE" >&2 + exit 1 +fi + +cd "$TARGET_DIR" + +if patch -p0 --reverse --dry-run --silent < "$PATCH_FILE" >/dev/null 2>&1; then + echo "✓ apply-patches: @plait/draw v$DRAW_VERSION already patched" exit 0 fi -if grep -q "fillStyle: element.fillStyle" "$TARGET_FILE" 2>/dev/null; then - echo "✓ Patch already applied to @plait/draw v$DRAW_VERSION" -else - echo "Applying patch to @plait/draw v$DRAW_VERSION..." - cd "$TARGET_DIR" - patch -p0 < "$PATCH_FILE" --forward 2>/dev/null || { - echo "⚠ Patch may have failed or already been applied" - } - cd - > /dev/null +echo " applying patch to @plait/draw v$DRAW_VERSION..." +if ! patch -p0 --forward < "$PATCH_FILE"; then + echo "✗ apply-patches: patch failed to apply cleanly" >&2 + exit 1 +fi + +if ! grep -q "$MARKER" "$TARGET_FILE"; then + echo "✗ apply-patches: post-apply verification failed — marker not found" >&2 + echo " $TARGET_FILE may be partially patched or unmodified" >&2 + exit 1 fi + +echo "✓ apply-patches: @plait/draw v$DRAW_VERSION patched" From 8048e45af0c6df8e47c66c14bbdbeed685e9d235 Mon Sep 17 00:00:00 2001 From: kripu77 Date: Sun, 19 Apr 2026 16:43:29 +1000 Subject: [PATCH 4/4] fix(scripts): use Bun native patchedDependencies so prod builds work without 'patch' binary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vercel's build image (and many minimal CI containers) ship without the GNU 'patch' binary, so apply-patches.sh — even after the previous hardening — exited non-zero on every prod build with "'patch' binary not available". Whichever way the script behaved, the only outcome on Vercel was a broken deploy. Replace the script with Bun's native patch system: register the patched package via package.json#patchedDependencies, ship the diff at patches/@plait%2Fdraw@0.92.3.patch, and let 'bun install' apply it automatically. No postinstall script, no shell tooling, no 'patch' binary. Verified locally with a fresh install: marker count = 2 in node_modules/@plait/draw/fesm2022/plait-draw.mjs after 'bun install' alone. Also removes a stray patches/patches/ duplicate that was checked in months ago. Co-Authored-By: Claude Opus 4.7 (1M context) --- bun.lock | 28 +- package.json | 9 +- patches/@plait%2Fdraw@0.92.3.patch | 9736 ++++++++++++++++++++++ patches/@plait+draw+0.92.3.patch | 400 - patches/patches/@plait+draw+0.92.3.patch | 400 - scripts/apply-patches.sh | 65 - 6 files changed, 9745 insertions(+), 893 deletions(-) create mode 100644 patches/@plait%2Fdraw@0.92.3.patch delete mode 100644 patches/@plait+draw+0.92.3.patch delete mode 100644 patches/patches/@plait+draw+0.92.3.patch delete mode 100755 scripts/apply-patches.sh diff --git a/bun.lock b/bun.lock index bcfb8cc..2a3afc3 100644 --- a/bun.lock +++ b/bun.lock @@ -84,7 +84,6 @@ "jsdom": "^28.1.0", "msw": "^2.12.10", "nx": "22.4.2", - "patch-package": "^8.0.1", "peggy": "5.1.0", "tailwindcss": "^4", "tw-animate-css": "^1.4.0", @@ -223,6 +222,9 @@ }, }, }, + "patchedDependencies": { + "@plait/draw@0.92.3": "patches/@plait%2Fdraw@0.92.3.patch", + }, "packages": { "@acemir/cssom": ["@acemir/cssom@0.9.31", "", {}, "sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA=="], @@ -1220,7 +1222,7 @@ "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], - "bun-types": ["bun-types@1.3.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-tcpfCCl6XWo6nCVnpcVrxQ+9AYN1iqMIzgrSKYMB/fjLtV2eyAVEg7AxQJuCq/26R6HpKWykQXuSOq/21RYcbg=="], + "bun-types": ["bun-types@1.3.12", "", { "dependencies": { "@types/node": "*" } }, "sha512-HqOLj5PoFajAQciOMRiIZGNoKxDJSr6qigAttOX40vJuSp6DN/CxWp9s3C1Xwm4oH7ybueITwiaOcWXoYVoRkA=="], "call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="], @@ -1252,8 +1254,6 @@ "chevrotain-allstar": ["chevrotain-allstar@0.3.1", "", { "dependencies": { "lodash-es": "^4.17.21" }, "peerDependencies": { "chevrotain": "^11.0.0" } }, "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw=="], - "ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="], - "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], "classcat": ["classcat@5.0.5", "", {}, "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w=="], @@ -1554,8 +1554,6 @@ "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], - "find-yarn-workspace-root": ["find-yarn-workspace-root@2.0.0", "", { "dependencies": { "micromatch": "^4.0.2" } }, "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ=="], - "flat": ["flat@5.0.2", "", { "bin": { "flat": "cli.js" } }, "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ=="], "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], @@ -1574,8 +1572,6 @@ "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], - "fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="], - "fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], @@ -1820,18 +1816,12 @@ "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], - "json-stable-stringify": ["json-stable-stringify@1.3.0", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "isarray": "^2.0.5", "jsonify": "^0.0.1", "object-keys": "^1.1.1" } }, "sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg=="], - "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], "jsonc-parser": ["jsonc-parser@3.2.0", "", {}, "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w=="], - "jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], - - "jsonify": ["jsonify@0.0.1", "", {}, "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg=="], - "jsx-ast-utils": ["jsx-ast-utils@3.3.5", "", { "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", "object.assign": "^4.1.4", "object.values": "^1.1.6" } }, "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ=="], "katex": ["katex@0.16.37", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-TIGjO2cCGYono+uUzgkE7RFF329mLLWGuHUlSr6cwIVj9O8f0VQZ783rsanmJpFUo32vvtj7XT04NGRPh+SZFg=="], @@ -1840,8 +1830,6 @@ "khroma": ["khroma@2.1.0", "", {}, "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw=="], - "klaw-sync": ["klaw-sync@6.0.0", "", { "dependencies": { "graceful-fs": "^4.1.11" } }, "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ=="], - "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], "langium": ["langium@4.2.1", "", { "dependencies": { "chevrotain": "~11.1.1", "chevrotain-allstar": "~0.3.1", "vscode-languageserver": "~9.0.1", "vscode-languageserver-textdocument": "~1.0.11", "vscode-uri": "~3.1.0" } }, "sha512-zu9QWmjpzJcomzdJQAHgDVhLGq5bLosVak1KVa40NzQHXfqr4eAHupvnPOVXEoLkg6Ocefvf/93d//SB7du4YQ=="], @@ -2126,8 +2114,6 @@ "parse5": ["parse5@8.0.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA=="], - "patch-package": ["patch-package@8.0.1", "", { "dependencies": { "@yarnpkg/lockfile": "^1.1.0", "chalk": "^4.1.2", "ci-info": "^3.7.0", "cross-spawn": "^7.0.3", "find-yarn-workspace-root": "^2.0.0", "fs-extra": "^10.0.0", "json-stable-stringify": "^1.0.2", "klaw-sync": "^6.0.0", "minimist": "^1.2.6", "open": "^7.4.2", "semver": "^7.5.3", "slash": "^2.0.0", "tmp": "^0.2.4", "yaml": "^2.2.2" }, "bin": { "patch-package": "index.js" } }, "sha512-VsKRIA8f5uqHQ7NGhwIna6Bx6D9s/1iXlA1hthBVBEbkq+t4kXD0HHt+rJhf/Z+Ci0F/HCB2hvn0qLdLG+Qxlw=="], - "path-data-parser": ["path-data-parser@0.1.0", "", {}, "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w=="], "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], @@ -2326,8 +2312,6 @@ "sirv": ["sirv@3.0.2", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g=="], - "slash": ["slash@2.0.0", "", {}, "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A=="], - "slate": ["slate@0.116.0", "", { "dependencies": { "immer": "^10.0.3", "tiny-warning": "^1.0.3" } }, "sha512-CpI6S0PTFDjFsYKah3geR7X5O/38tjxVlwkYGpfQ583yYkSbt/pyEldTB1CJE3MK4htfn2D/wPkwWtDV06ZqWA=="], "slate-dom": ["slate-dom@0.116.0", "", { "dependencies": { "@juggle/resize-observer": "^3.4.0", "direction": "^1.0.4", "is-hotkey": "^0.2.0", "is-plain-object": "^5.0.0", "lodash": "^4.17.21", "scroll-into-view-if-needed": "^3.1.0", "tiny-invariant": "1.3.1" }, "peerDependencies": { "slate": ">=0.99.0" } }, "sha512-ZyyPdT4zY4d/P/gfqMWBaAWWqV3N2BbAiDqEfOtZwPLcy7vvC02PsVvSZLaGun7DvaM2EIqdN7tTq3REbQkYgA=="], @@ -2504,8 +2488,6 @@ "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], - "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], - "unrs-resolver": ["unrs-resolver@1.11.1", "", { "dependencies": { "napi-postinstall": "^0.3.0" }, "optionalDependencies": { "@unrs/resolver-binding-android-arm-eabi": "1.11.1", "@unrs/resolver-binding-android-arm64": "1.11.1", "@unrs/resolver-binding-darwin-arm64": "1.11.1", "@unrs/resolver-binding-darwin-x64": "1.11.1", "@unrs/resolver-binding-freebsd-x64": "1.11.1", "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", "@unrs/resolver-binding-linux-x64-musl": "1.11.1", "@unrs/resolver-binding-wasm32-wasi": "1.11.1", "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" } }, "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg=="], "until-async": ["until-async@3.0.2", "", {}, "sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw=="], @@ -2886,8 +2868,6 @@ "parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], - "patch-package/open": ["open@7.4.2", "", { "dependencies": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" } }, "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q=="], - "points-on-path/points-on-curve": ["points-on-curve@0.2.0", "", {}, "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A=="], "postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], diff --git a/package.json b/package.json index af9081f..6168af9 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ ], "scripts": { "dev": "next dev", - "prebuild": "bash scripts/apply-patches.sh && bun run build:parser", + "prebuild": "bun run build:parser", "pretest": "bun run build:parser", "build": "bunx next build", "start": "next start", @@ -23,7 +23,6 @@ "test:e2e:debug": "playwright test --debug", "test:e2e:report": "playwright show-report", "test:all": "bun run test:run && bun run test:e2e", - "postinstall": "bash scripts/apply-patches.sh", "build:parser": "peggy -o features/agent/tools/dsl/parser.js features/agent/tools/dsl/thinkix.peggy --format es" }, "dependencies": { @@ -106,12 +105,14 @@ "jsdom": "^28.1.0", "msw": "^2.12.10", "nx": "22.4.2", - "patch-package": "^8.0.1", "peggy": "5.1.0", "tailwindcss": "^4", "tw-animate-css": "^1.4.0", "typescript": "^5", "vitest": "^4.0.18" }, - "nx": {} + "nx": {}, + "patchedDependencies": { + "@plait/draw@0.92.3": "patches/@plait%2Fdraw@0.92.3.patch" + } } \ No newline at end of file diff --git a/patches/@plait%2Fdraw@0.92.3.patch b/patches/@plait%2Fdraw@0.92.3.patch new file mode 100644 index 0000000..1b0e2a3 --- /dev/null +++ b/patches/@plait%2Fdraw@0.92.3.patch @@ -0,0 +1,9736 @@ +diff --git a/fesm2022/plait-draw.mjs b/fesm2022/plait-draw.mjs +index c66478e071aa029f80ab3ca12a09ca5836612d65..1dce2fd6f0556afffd601d31effe595d6fe8f8f1 100644 +--- a/fesm2022/plait-draw.mjs ++++ b/fesm2022/plait-draw.mjs +@@ -1799,7 +1799,7 @@ const CloudEngine = { + .map((command) => `A ${command.rx} ${command.ry} ${command.xAxisRotation} ${command.largeArcFlag} ${command.sweepFlag} ${command.endX} ${command.endY}`) + .join('\n') + + ' Z'; +- const svgElement = rs.path(pathData, { ...options, fillStyle: 'solid' }); ++ const svgElement = rs.path(pathData, options); + setPathStrokeLinecap(svgElement, 'round'); + return svgElement; + }, +@@ -2201,8 +2201,7 @@ const drawBoundReaction = (board, element, roughOptions = { hasMask: true, hasCo + const maskG = drawShape(board, activeRectangle, shape, { + stroke: SELECTION_BORDER_COLOR, + strokeWidth: 0, +- fill: isClosedDrawElement(element) ? SELECTION_FILL_COLOR : DefaultDrawStyle.fill, +- fillStyle: 'solid' ++ fill: isClosedDrawElement(element) ? SELECTION_FILL_COLOR : DefaultDrawStyle.fill + }, drawOptions); + g.appendChild(maskG); + } +@@ -2212,8 +2211,7 @@ const drawBoundReaction = (board, element, roughOptions = { hasMask: true, hasCo + const circleG = drawCircle(PlaitBoard.getRoughSVG(board), point, 8, { + stroke: SELECTION_BORDER_COLOR, + strokeWidth: ACTIVE_STROKE_WIDTH, +- fill: '#FFF', +- fillStyle: 'solid' ++ fill: '#FFF' + }); + g.appendChild(circleG); + }); +@@ -2286,7 +2284,8 @@ class GeometryShapeGenerator extends Generator { + stroke: strokeColor, + strokeWidth, + fill, +- strokeLineDash ++ strokeLineDash, ++ fillStyle: element.fillStyle + }); + } + } +@@ -2496,8 +2495,7 @@ class ArrowLineAutoCompleteGenerator extends Generator { + middlePoints.forEach((point, index) => { + const circle = drawCircle(PlaitBoard.getRoughSVG(this.board), point, LINE_AUTO_COMPLETE_DIAMETER, { + stroke: 'none', +- fill: rgbaToHEX(PRIMARY_COLOR, LINE_AUTO_COMPLETE_OPACITY), +- fillStyle: 'solid' ++ fill: rgbaToHEX(PRIMARY_COLOR, LINE_AUTO_COMPLETE_OPACITY) + }); + circle.classList.add(`line-auto-complete-${index}`); + this.autoCompleteG.appendChild(circle); +@@ -2543,7 +2541,8 @@ class TableGenerator extends Generator { + return getEngine(TableSymbols.table).draw(this.board, rectangle, { + strokeWidth, + stroke: strokeColor, +- strokeLineDash ++ strokeLineDash, ++ fillStyle: element.fillStyle + }, { + element: element + }); +@@ -4085,7 +4084,7 @@ const CommentEngine = { + draw(board, rectangle, options) { + const points = getCommentPoints(rectangle); + const rs = PlaitBoard.getRoughSVG(board); +- const polygon = rs.polygon(points, { ...options, fillStyle: 'solid' }); ++ const polygon = rs.polygon(points, options); + setStrokeLinecap(polygon, 'round'); + return polygon; + }, +@@ -4132,7 +4131,7 @@ function createPolygonEngine(options) { + draw(board, rectangle, options) { + const points = getPoints(rectangle); + const rs = PlaitBoard.getRoughSVG(board); +- const polygon = rs.polygon(points, { ...options, fillStyle: 'solid' }); ++ const polygon = rs.polygon(points, options); + setStrokeLinecap(polygon, 'round'); + return polygon; + }, +@@ -4222,7 +4221,7 @@ function createEllipseEngine(createOptions) { + draw(board, rectangle, options) { + const centerPoint = [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height / 2]; + const rs = PlaitBoard.getRoughSVG(board); +- const shape = rs.ellipse(centerPoint[0], centerPoint[1], rectangle.width, rectangle.height, { ...options, fillStyle: 'solid' }); ++ const shape = rs.ellipse(centerPoint[0], centerPoint[1], rectangle.width, rectangle.height, options); + setStrokeLinecap(shape, 'round'); + return shape; + }, +@@ -4447,7 +4446,7 @@ const RightArrowEngine = createPolygonEngine({ + + const RectangleEngine = { + draw(board, rectangle, options) { +- return drawRectangle(board, rectangle, { ...options, fillStyle: 'solid' }); ++ return drawRectangle(board, rectangle, options); + }, + isInsidePoint(rectangle, point) { + const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]); +@@ -4471,7 +4470,7 @@ const RectangleEngine = { + + const RoundRectangleEngine = { + draw(board, rectangle, options) { +- return drawRoundRectangle(PlaitBoard.getRoughSVG(board), rectangle.x, rectangle.y, rectangle.x + rectangle.width, rectangle.y + rectangle.height, { ...options, fillStyle: 'solid' }, false, getRoundRectangleRadius(rectangle)); ++ return drawRoundRectangle(PlaitBoard.getRoughSVG(board), rectangle.x, rectangle.y, rectangle.x + rectangle.width, rectangle.y + rectangle.height, options, false, getRoundRectangleRadius(rectangle)); + }, + isInsidePoint(rectangle, point) { + return isPointInRoundRectangle(point, rectangle, getRoundRectangleRadius(rectangle)); +@@ -4544,7 +4543,7 @@ const RoundCommentEngine = { + const point9 = [x1 + rectangle.width / 4, y2]; + const point10 = [x1 + rectangle.width / 4, rectangle.y + rectangle.height]; + const point11 = [x1 + rectangle.width / 2, y2]; +- const shape = rs.path(`M${point2[0]} ${point2[1]} A ${radius} ${radius}, 0, 0, 1, ${point3[0]} ${point3[1]} L ${point4[0]} ${point4[1]} A ${radius} ${radius}, 0, 0, 1, ${point5[0]} ${point5[1]} L ${point11[0]} ${point11[1]} ${point10[0]} ${point10[1]} ${point9[0]} ${point9[1]} ${point6[0]} ${point6[1]} A ${radius} ${radius}, 0, 0, 1, ${point7[0]} ${point7[1]} L ${point8[0]} ${point8[1]} A ${radius} ${radius}, 0, 0, 1, ${point1[0]} ${point1[1]} Z`, { ...options, fillStyle: 'solid' }); ++ const shape = rs.path(`M${point2[0]} ${point2[1]} A ${radius} ${radius}, 0, 0, 1, ${point3[0]} ${point3[1]} L ${point4[0]} ${point4[1]} A ${radius} ${radius}, 0, 0, 1, ${point5[0]} ${point5[1]} L ${point11[0]} ${point11[1]} ${point10[0]} ${point10[1]} ${point9[0]} ${point9[1]} ${point6[0]} ${point6[1]} A ${radius} ${radius}, 0, 0, 1, ${point7[0]} ${point7[1]} L ${point8[0]} ${point8[1]} A ${radius} ${radius}, 0, 0, 1, ${point1[0]} ${point1[1]} Z`, options); + setStrokeLinecap(shape, 'round'); + return shape; + }, +@@ -4696,7 +4695,7 @@ const StarEngine = createPolygonEngine({ + + const TerminalEngine = { + draw(board, rectangle, options) { +- return drawRoundRectangle(PlaitBoard.getRoughSVG(board), rectangle.x, rectangle.y, rectangle.x + rectangle.width, rectangle.y + rectangle.height, { ...options, fillStyle: 'solid' }, false, getStartEndRadius(rectangle)); ++ return drawRoundRectangle(PlaitBoard.getRoughSVG(board), rectangle.x, rectangle.y, rectangle.x + rectangle.width, rectangle.y + rectangle.height, options, false, getStartEndRadius(rectangle)); + }, + isInsidePoint(rectangle, point) { + return isPointInRoundRectangle(point, rectangle, getStartEndRadius(rectangle)); +@@ -4865,7 +4864,7 @@ const DelayEngine = { + draw(board, rectangle, options) { + const rs = PlaitBoard.getRoughSVG(board); + const shape = rs.path(`M${rectangle.x} ${rectangle.y} L${rectangle.x + (rectangle.width * 3) / 4} ${rectangle.y} A ${rectangle.width / +- 4} ${rectangle.height / 2}, 0, 0, 1,${rectangle.x + (rectangle.width * 3) / 4} ${rectangle.y + rectangle.height} L${rectangle.x} ${rectangle.y + rectangle.height} Z`, { ...options, fillStyle: 'solid' }); ++ 4} ${rectangle.height / 2}, 0, 0, 1,${rectangle.x + (rectangle.width * 3) / 4} ${rectangle.y + rectangle.height} L${rectangle.x} ${rectangle.y + rectangle.height} Z`, options); + setStrokeLinecap(shape, 'round'); + return shape; + }, +@@ -4906,7 +4905,7 @@ const DelayEngine = { + const StoredDataEngine = { + draw(board, rectangle, options) { + const rs = PlaitBoard.getRoughSVG(board); +- const shape = rs.path(`M${rectangle.x + rectangle.width / 10} ${rectangle.y} L${rectangle.x + rectangle.width} ${rectangle.y} A ${rectangle.width / 10} ${rectangle.height / 2}, 0, 0, 0,${rectangle.x + rectangle.width} ${rectangle.y + rectangle.height} L${rectangle.x + rectangle.width / 10} ${rectangle.y + rectangle.height}A ${rectangle.width / 10} ${rectangle.height / 2}, 0, 0, 1,${rectangle.x + rectangle.width / 10} ${rectangle.y}`, { ...options, fillStyle: 'solid' }); ++ const shape = rs.path(`M${rectangle.x + rectangle.width / 10} ${rectangle.y} L${rectangle.x + rectangle.width} ${rectangle.y} A ${rectangle.width / 10} ${rectangle.height / 2}, 0, 0, 0,${rectangle.x + rectangle.width} ${rectangle.y + rectangle.height} L${rectangle.x + rectangle.width / 10} ${rectangle.y + rectangle.height}A ${rectangle.width / 10} ${rectangle.height / 2}, 0, 0, 1,${rectangle.x + rectangle.width / 10} ${rectangle.y}`, options); + setStrokeLinecap(shape, 'round'); + return shape; + }, +@@ -4979,7 +4978,7 @@ const PredefinedProcessEngine = { + const shape = rs.path(`M${rectangle.x} ${rectangle.y} H${rectangle.x + rectangle.width} V${rectangle.y + rectangle.height} H${rectangle.x} Z M${rectangle.x + rectangle.width * 0.06} ${rectangle.y} L${rectangle.x + rectangle.width * 0.06} ${rectangle.y + + rectangle.height} M${rectangle.x + rectangle.width - rectangle.width * 0.06} ${rectangle.y} L${rectangle.x + + rectangle.width - +- rectangle.width * 0.06} ${rectangle.y + rectangle.height}`, { ...options, fillStyle: 'solid' }); ++ rectangle.width * 0.06} ${rectangle.y + rectangle.height}`, options); + setStrokeLinecap(shape, 'round'); + return shape; + }, +@@ -5057,7 +5056,7 @@ const OrEngine = createEllipseEngine({ + L${rectangle.x + rectangle.width} ${rectangle.y + rectangle.height / 2} + M${rectangle.x + rectangle.width / 2} ${rectangle.y} + L${rectangle.x + rectangle.width / 2} ${rectangle.y + rectangle.height} +- `, { ...options, fillStyle: 'solid' }); ++ `, options); + setStrokeLinecap(shape, 'round'); + return shape; + } +@@ -5078,7 +5077,7 @@ const SummingJunctionEngine = createEllipseEngine({ + L${line1Points[1][0]} ${line1Points[1][1]} + M${line2Points[0][0]} ${line2Points[0][1]} + L${line2Points[1][0]} ${line2Points[1][1]} +- `, { ...options, fillStyle: 'solid' }); ++ `, options); + setStrokeLinecap(shape, 'round'); + return shape; + } +@@ -5094,7 +5093,7 @@ const DocumentEngine = { + (rectangle.height / 9) * 3}, ${rectangle.x + rectangle.width / 2} ${rectangle.y + + rectangle.height - + rectangle.height / 9} T${rectangle.x} ${rectangle.y + rectangle.height - rectangle.height / 9} +- `, { ...options, fillStyle: 'solid' }); ++ `, options); + setStrokeLinecap(shape, 'round'); + return shape; + }, +@@ -5185,7 +5184,7 @@ const MultiDocumentEngine = { + M${rectangle.x + 5} ${rectangle.y + 10} H${rectangle.x + rectangle.width - 10} V${rectangle.y + rectangle.height - rectangle.height / 9} + + M${rectangle.x + 10} ${rectangle.y + 5} H${rectangle.x + rectangle.width - 5} V${rectangle.y + rectangle.height - rectangle.height / 9 - 10 - 3} +- `, { ...options, fillStyle: 'solid' }); ++ `, options); + setStrokeLinecap(shape, 'round'); + return shape; + }, +@@ -5261,7 +5260,7 @@ const DatabaseEngine = { + A${rectangle.width / 2} ${rectangle.height * 0.15}, 0, 0, 0,${rectangle.x} ${rectangle.y + rectangle.height * 0.15} + V${rectangle.y + rectangle.height - rectangle.height * 0.15} + A${rectangle.width / 2} ${rectangle.height * 0.15}, 0, 0, 0, ${rectangle.x + rectangle.width} ${rectangle.y + rectangle.height - rectangle.height * 0.15} +- V${rectangle.y + rectangle.height * 0.15}`, { ...options, fillStyle: 'solid' }); ++ V${rectangle.y + rectangle.height * 0.15}`, options); + setStrokeLinecap(shape, 'round'); + return shape; + }, +@@ -5338,7 +5337,7 @@ const HardDiskEngine = { + H${rectangle.x + rectangle.width * 0.15} + A${rectangle.width * 0.15} ${rectangle.height / 2}, 0, 0, 0, ${rectangle.x + rectangle.width * 0.15} ${rectangle.y + + rectangle.height} +- H${rectangle.x + rectangle.width - rectangle.width * 0.15}`, { ...options, fillStyle: 'solid' }); ++ H${rectangle.x + rectangle.width - rectangle.width * 0.15}`, options); + setStrokeLinecap(shape, 'round'); + return shape; + }, +@@ -5414,7 +5413,7 @@ const InternalStorageEngine = { + const shape = rs.path(`M${rectangle.x} ${rectangle.y} h${rectangle.width} v${rectangle.height} h${-rectangle.width} v${-rectangle.height} + M${rectangle.x} ${rectangle.y + rectangle.height / 10} h${rectangle.width} + M${rectangle.x + rectangle.width / 10} ${rectangle.y} v${rectangle.height} +- `, { ...options, fillStyle: 'solid' }); ++ `, options); + setStrokeLinecap(shape, 'round'); + return shape; + }, +@@ -5482,7 +5481,7 @@ const NoteCurlyLeftEngine = { + ${lowerCurve.controlPoint2[0]} ${lowerCurve.controlPoint2[1]}, + ${lowerCurve.endPoint[0]} ${lowerCurve.endPoint[1]}` + ].join(' '); +- const shape = rs.path(pathData, { ...options, fillStyle: 'solid', fill: 'transparent' }); ++ const shape = rs.path(pathData, { ...options, fill: 'transparent' }); + setStrokeLinecap(shape, 'round'); + return shape; + }, +@@ -5555,7 +5554,7 @@ const NoteCurlyRightEngine = { + ${lowerCurve.controlPoint2[0]} ${lowerCurve.controlPoint2[1]}, + ${lowerCurve.endPoint[0]} ${lowerCurve.endPoint[1]}` + ].join(' '); +- const shape = rs.path(pathData, { ...options, fillStyle: 'solid', fill: 'transparent' }); ++ const shape = rs.path(pathData, { ...options, fill: 'transparent' }); + setStrokeLinecap(shape, 'round'); + return shape; + }, +@@ -5608,7 +5607,7 @@ const NoteSquareEngine = { + const rs = PlaitBoard.getRoughSVG(board); + const shape = rs.path(`M${rectangle.x + rectangle.width * 0.075} ${rectangle.y + rectangle.height} H${rectangle.x} V${rectangle.y} H${rectangle.x + + rectangle.width * 0.075} +- `, { ...options, fillStyle: 'solid', fill: 'transparent' }); ++ `, { ...options, fill: 'transparent' }); + setStrokeLinecap(shape, 'round'); + return shape; + }, +@@ -5655,7 +5654,7 @@ const DisplayEngine = { + H${rectangle.x + rectangle.width * 0.15} + L${rectangle.x} ${rectangle.y + rectangle.height / 2} + Z +- `, { ...options, fillStyle: 'solid' }); ++ `, options); + setStrokeLinecap(shape, 'round'); + return shape; + }, +@@ -5717,7 +5716,7 @@ const TableEngine = { + y: y + ACTIVE_STROKE_WIDTH, + width: width - ACTIVE_STROKE_WIDTH * 2, + height: height - ACTIVE_STROKE_WIDTH * 2 +- }, { fill: cell.fill, fillStyle: 'solid', strokeWidth: 0 }); ++ }, { fill: cell.fill, fillStyle: roughOptions?.fillStyle, strokeWidth: 0 }); + const cellRightBorder = drawLine(rs, [x + width, y], [x + width, y + height], roughOptions); + const cellBottomBorder = drawLine(rs, [x, y + height], [x + width, y + height], roughOptions); + g.append(cellRectangle, cellRightBorder, cellBottomBorder); +@@ -5862,7 +5861,7 @@ const ActorEngine = { + `M${leftLegLine[0][0]} ${leftLegLine[0][1]} L${leftLegLine[1][0]} ${leftLegLine[1][1]}`, + `M${rightLegLine[0][0]} ${rightLegLine[0][1]} L${rightLegLine[1][0]} ${rightLegLine[1][1]}` + ].join(' '); +- const shape = rs.path(pathData, { ...options, fillStyle: 'solid' }); ++ const shape = rs.path(pathData, options); + setStrokeLinecap(shape, 'round'); + return shape; + }, +@@ -5934,7 +5933,7 @@ const ActorEngine = { + const ContainerEngine = { + draw(board, rectangle, options) { + const rs = PlaitBoard.getRoughSVG(board); +- const shape = rs.path(`M${rectangle.x} ${rectangle.y} H${rectangle.x + rectangle.width} V${rectangle.y + rectangle.height} H${rectangle.x} Z M${rectangle.x + 40} ${rectangle.y} L${rectangle.x + 40} ${rectangle.y + rectangle.height} `, { ...options, fillStyle: 'solid' }); ++ const shape = rs.path(`M${rectangle.x} ${rectangle.y} H${rectangle.x + rectangle.width} V${rectangle.y + rectangle.height} H${rectangle.x} Z M${rectangle.x + 40} ${rectangle.y} L${rectangle.x + 40} ${rectangle.y + rectangle.height} `, options); + setStrokeLinecap(shape, 'round'); + return shape; + }, +@@ -6005,7 +6004,7 @@ const PackageEngine = { + `V${points.leftMiddle[1]}`, + `H${points.middlePoint[0]}` + ].join(' '); +- const shape = rs.path(pathData, { ...options, fillStyle: 'solid' }); ++ const shape = rs.path(pathData, options); + setStrokeLinecap(shape, 'round'); + return shape; + }, +@@ -6085,7 +6084,7 @@ const CombinedFragmentEngine = { + H${rectangle.x + rectangle.width / 3 - 8} + L${rectangle.x + rectangle.width / 3} ${rectangle.y + 16} + V${rectangle.y} +- `, { ...options, fillStyle: 'solid' }); ++ `, options); + setStrokeLinecap(shape, 'round'); + return shape; + }, +@@ -6151,7 +6150,7 @@ const DeletionEngine = { + draw(board, rectangle, options) { + const rs = PlaitBoard.getRoughSVG(board); + const lines = getDeletionLines(rectangle); +- const shape = rs.path(lines.map(([from, to]) => `M${from[0]} ${from[1]} L${to[0]} ${to[1]}`).join(' '), { ...options, fillStyle: 'solid', strokeWidth: 4 }); ++ const shape = rs.path(lines.map(([from, to]) => `M${from[0]} ${from[1]} L${to[0]} ${to[1]}`).join(' '), { ...options, strokeWidth: 4 }); + setStrokeLinecap(shape, 'round'); + return shape; + }, +@@ -6192,7 +6191,7 @@ const ActiveClassEngine = { + const shape = rs.path(`M${rectangle.x} ${rectangle.y} H${rectangle.x + rectangle.width} V${rectangle.y + rectangle.height} H${rectangle.x} Z M${rectangle.x + rectangle.width * 0.125} ${rectangle.y} L${rectangle.x + rectangle.width * 0.125} ${rectangle.y + + rectangle.height} M${rectangle.x + rectangle.width - rectangle.width * 0.125} ${rectangle.y} L${rectangle.x + + rectangle.width - +- rectangle.width * 0.125} ${rectangle.y + rectangle.height}`, { ...options, fillStyle: 'solid' }); ++ rectangle.width * 0.125} ${rectangle.y + rectangle.height}`, options); + setStrokeLinecap(shape, 'round'); + return shape; + }, +@@ -6241,7 +6240,7 @@ const NoteEngine = { + Z + M${rectangle.x + rectangle.width - 16} ${rectangle.y} + A16 16, 0,0,1, ${rectangle.x + rectangle.width} ${rectangle.y + 16} +- `, { ...options, fillStyle: 'solid' }); ++ `, options); + setStrokeLinecap(shape, 'round'); + return shape; + }, +@@ -6337,10 +6336,7 @@ const AssemblyEngine = { + // 最后一条线 + `M${line2[0][0]} ${line2[0][1]} H${line2[1][0]}` + ].join(' '); +- const shape = rs.path(pathData, { +- ...options, +- fillStyle: 'solid' +- }); ++ const shape = rs.path(pathData, options); + setStrokeLinecap(shape, 'round'); + return shape; + }, +@@ -6420,7 +6416,6 @@ const RequiredInterfaceEngine = { + ].join(' '); + const shape = rs.path(pathData, { + ...options, +- fillStyle: 'solid', + fill: 'transparent' + }); + setStrokeLinecap(shape, 'round'); +@@ -6511,10 +6506,7 @@ const ProvidedInterfaceEngine = { + `H${line.endX}`, + ...arcCommands.map((command) => `A${command.rx} ${command.ry} ${command.xAxisRotation} ${command.largeArcFlag} ${command.sweepFlag} ${command.endX} ${command.endY}`) + ].join(' '); +- const shape = rs.path(pathData, { +- ...options, +- fillStyle: 'solid' +- }); ++ const shape = rs.path(pathData, options); + setStrokeLinecap(shape, 'round'); + return shape; + }, +@@ -6602,7 +6594,7 @@ const ComponentEngine = { + `M${points.bottomBoxEnd[0]} ${points.bottomBoxEnd[1]}`, + `V${points.mainEnd[1]}` + ].join(' '); +- const shape = rs.path(pathData, { ...options, fillStyle: 'solid' }); ++ const shape = rs.path(pathData, options); + setStrokeLinecap(shape, 'round'); + return shape; + }, +@@ -6685,7 +6677,7 @@ const ComponentBoxEngine = { + V${rectangle.y + rectangle.height} + H${rectangle.x} Z + +- `, { ...options, fillStyle: 'solid' }); ++ `, options); + const componentShape = ComponentEngine.draw(board, componentRectangle, options); + shape.append(componentShape); + setStrokeLinecap(shape, 'round'); +@@ -6730,7 +6722,6 @@ const TemplateEngine = { + const rs = PlaitBoard.getRoughSVG(board); + return drawRoundRectangle(rs, rectangle.x, rectangle.y, rectangle.x + rectangle.width, rectangle.y + rectangle.height, { + ...options, +- fillStyle: 'solid', + dashGap: 10, + strokeLineDash: [10, 10] + }, false, 4); +@@ -8041,8 +8032,7 @@ const withArrowLineBoundReaction = (board) => { + const circleG = drawCircle(PlaitBoard.getRoughSVG(board), ref.connectorPoint || ref.edgePoint, 6, { + stroke: SELECTION_BORDER_COLOR, + strokeWidth: SNAPPING_STROKE_WIDTH, +- fill: SELECTION_BORDER_COLOR, +- fillStyle: 'solid' ++ fill: SELECTION_BORDER_COLOR + }); + boundShapeG.appendChild(circleG); + } +@@ -8310,8 +8300,7 @@ const withArrowLineAutoCompleteReaction = (board) => { + // function 1: dnd + reactionG = drawCircle(PlaitBoard.getRoughSVG(board), hitPoint, LINE_AUTO_COMPLETE_HOVERED_DIAMETER, { + stroke: 'none', +- fill: rgbaToHEX(PRIMARY_COLOR, LINE_AUTO_COMPLETE_HOVERED_OPACITY), +- fillStyle: 'solid' ++ fill: rgbaToHEX(PRIMARY_COLOR, LINE_AUTO_COMPLETE_HOVERED_OPACITY) + }); + PlaitBoard.getActiveHost(board).append(reactionG); + PlaitBoard.getBoardContainer(board).classList.add(CursorClass.crosshair); +diff --git a/node_modules/@plait/draw/fesm2022/plait-draw.mjs.orig b/fesm2022/plait-draw.mjs.orig +new file mode 100644 +index 0000000000000000000000000000000000000000..c66478e071aa029f80ab3ca12a09ca5836612d65 +--- /dev/null ++++ b/fesm2022/plait-draw.mjs.orig +@@ -0,0 +1,9328 @@ ++import { ACTIVE_STROKE_WIDTH, DEFAULT_COLOR, ThemeColorMode, PlaitElement, RectangleClient, getSelectedElements, idCreator, createDebugGenerator, Point, createG, arrowPoints, createPath, distanceBetweenPointAndPoint, drawLinearPath, rotate, catmullRomFitting, PlaitBoard, setStrokeLinecap, findElements, createMask, createRect, getNearestPointBetweenPointAndArc, setPathStrokeLinecap, distanceBetweenPointAndSegments, HIT_DISTANCE_BUFFER, isPointInPolygon, isLineHitRectangle, rotatePointsByAngle, rotateAntiPointsByElement, getEllipseArcCenter, Transforms, clearSelectedElement, addSelectedElement, BoardTransforms, PlaitPointerType, depthFirstRecursion, getIsRecursionFunc, SNAPPING_STROKE_WIDTH, SELECTION_BORDER_COLOR, SELECTION_FILL_COLOR, drawCircle, getI18nValue, SELECTION_RECTANGLE_CLASS_NAME, toActivePointFromViewBoxPoint, toActiveRectangleFromViewBoxRectangle, drawRectangle, isSelectionMoving, rgbaToHEX, getElementById, rotatePointsByElement, PlaitNode, hasValidAngle, toViewBoxPoint, toHostPoint, Direction, rotatePoints, getSelectionAngle, rotatedDataPoints, isAxisChangedByAngle, getRectangleByElements, getRectangleByAngle, getSnapRectangles, getTripleAxis, getMinPointDelta, SNAP_TOLERANCE, drawPointSnapLines, drawSolidLines, getNearestPointBetweenPointAndSegments, getEllipseTangentSlope, getVectorFromPointAndSlope, getNearestPointBetweenPointAndEllipse, isPointInEllipse, isPointInRoundRectangle, drawRoundRectangle, getCrossingPointsBetweenEllipseAndSegment, drawLine, getNearestPointBetweenPointAndDiscreteSegments, getNearestPointBetweenPointAndSegment, Path, getHitElementByPoint, WritableClipboardOperationType, WritableClipboardType, addOrCreateClipboardContext, setAngleForG, toActivePoint, temporaryDisableSelection, toScreenPointFromActivePoint, PRESS_AND_MOVE_BUFFER, CursorClass, isHorizontalDirection, isMainPointer, throttleRAF, getAngleBetweenPoints, normalizeAngle, degreesToRadians, rotateElements, MERGING, ROTATE_HANDLE_CLASS_NAME, isSelectedElement, isDragging } from '@plait/core'; ++import { DEFAULT_FILL, Alignment, WithTextPluginKey, TextManage, getMemorizedLatest, memorizeLatest, removeDuplicatePoints, generateElbowLineRoute, simplifyOrthogonalPoints, getExtendPoint, getUnitVectorByPointAndPoint, Generator, getElementSize, DEFAULT_FONT_FAMILY, getPointByVectorComponent, getStrokeLineDash, StrokeStyle, getPointOnPolyline, buildText, getCrossingPointsBetweenPointAndSegment, isPointOnSegment, getFirstTextEditor, sortElementsByArea, isFilled, getTextEditorsByElement, RESIZE_HANDLE_DIAMETER, drawPrimaryHandle, drawFillPrimaryHandle, PRIMARY_COLOR, measureElement, getFirstTextManage, isSourceAndTargetIntersect, getPoints, DEFAULT_ROUTE_MARGIN, normalizeShapePoints, resetPointsAfterResize, getDirectionByVector, getRectangleResizeHandleRefs, getRotatedResizeCursorClassByAngle, ROTATE_HANDLE_SIZE, ROTATE_HANDLE_DISTANCE_TO_ELEMENT, isCornerHandle, getIndexByResizeHandle, withResize, getSymmetricHandleIndex, getResizeHandlePointByIndex, drawHandle, getDirectionFactorByDirectionComponent, buildClipboardData as buildClipboardData$1, insertClipboardData as insertClipboardData$1, getDirectionByPointOfRectangle, getDirectionFactor, rotateVector, getOppositeDirection, rotateVectorAnti90, getSourceAndTargetOuterRectangle, getNextPoint, CommonElementFlavour, createActiveGenerator, hasResizeHandle, ActiveGenerator, isVirtualKey, isDelete, isSpaceHotkey, isDndMode, isDrawingMode, getElementsText, acceptImageTypes, getElementOfFocusedImage, buildImage, isResizingByCondition, getRatioByPoint, getTextManages, ImageGenerator, getDirectionByIndex, moveXOfPoint, getXDistanceBetweenPoint, ResizeHandle, addRotating, removeRotating, drawRotateHandle } from '@plait/common'; ++import { pointsOnBezierCurves } from 'points-on-curve'; ++import { DEFAULT_FONT_SIZE, AlignEditor } from '@plait/text-plugins'; ++import { Editor, Node } from 'slate'; ++import { isKeyHotkey } from 'is-hotkey'; ++ ++var BasicShapes; ++(function (BasicShapes) { ++ BasicShapes["rectangle"] = "rectangle"; ++ BasicShapes["ellipse"] = "ellipse"; ++ BasicShapes["diamond"] = "diamond"; ++ BasicShapes["roundRectangle"] = "roundRectangle"; ++ BasicShapes["parallelogram"] = "parallelogram"; ++ BasicShapes["text"] = "text"; ++ BasicShapes["triangle"] = "triangle"; ++ BasicShapes["leftArrow"] = "leftArrow"; ++ BasicShapes["trapezoid"] = "trapezoid"; ++ BasicShapes["rightArrow"] = "rightArrow"; ++ BasicShapes["cross"] = "cross"; ++ BasicShapes["star"] = "star"; ++ BasicShapes["pentagon"] = "pentagon"; ++ BasicShapes["hexagon"] = "hexagon"; ++ BasicShapes["octagon"] = "octagon"; ++ BasicShapes["pentagonArrow"] = "pentagonArrow"; ++ BasicShapes["processArrow"] = "processArrow"; ++ BasicShapes["twoWayArrow"] = "twoWayArrow"; ++ BasicShapes["comment"] = "comment"; ++ BasicShapes["roundComment"] = "roundComment"; ++ BasicShapes["cloud"] = "cloud"; ++})(BasicShapes || (BasicShapes = {})); ++var FlowchartSymbols; ++(function (FlowchartSymbols) { ++ FlowchartSymbols["process"] = "process"; ++ FlowchartSymbols["decision"] = "decision"; ++ FlowchartSymbols["data"] = "data"; ++ FlowchartSymbols["connector"] = "connector"; ++ FlowchartSymbols["terminal"] = "terminal"; ++ FlowchartSymbols["manualInput"] = "manualInput"; ++ FlowchartSymbols["preparation"] = "preparation"; ++ FlowchartSymbols["manualLoop"] = "manualLoop"; ++ FlowchartSymbols["merge"] = "merge"; ++ FlowchartSymbols["delay"] = "delay"; ++ FlowchartSymbols["storedData"] = "storedData"; ++ FlowchartSymbols["or"] = "or"; ++ FlowchartSymbols["summingJunction"] = "summingJunction"; ++ FlowchartSymbols["predefinedProcess"] = "predefinedProcess"; ++ FlowchartSymbols["offPage"] = "offPage"; ++ FlowchartSymbols["document"] = "document"; ++ FlowchartSymbols["multiDocument"] = "multiDocument"; ++ FlowchartSymbols["database"] = "database"; ++ FlowchartSymbols["hardDisk"] = "hardDisk"; ++ FlowchartSymbols["internalStorage"] = "internalStorage"; ++ FlowchartSymbols["noteCurlyRight"] = "noteCurlyRight"; ++ FlowchartSymbols["noteCurlyLeft"] = "noteCurlyLeft"; ++ FlowchartSymbols["noteSquare"] = "noteSquare"; ++ FlowchartSymbols["display"] = "display"; ++})(FlowchartSymbols || (FlowchartSymbols = {})); ++var UMLSymbols; ++(function (UMLSymbols) { ++ UMLSymbols["actor"] = "actor"; ++ UMLSymbols["useCase"] = "useCase"; ++ UMLSymbols["container"] = "container"; ++ UMLSymbols["note"] = "note"; ++ UMLSymbols["simpleClass"] = "simpleClass"; ++ UMLSymbols["activityClass"] = "activityClass"; ++ UMLSymbols["branchMerge"] = "branchMerge"; ++ UMLSymbols["port"] = "port"; ++ UMLSymbols["package"] = "package"; ++ UMLSymbols["combinedFragment"] = "combinedFragment"; ++ UMLSymbols["class"] = "class"; ++ UMLSymbols["interface"] = "interface"; ++ UMLSymbols["object"] = "object"; ++ UMLSymbols["component"] = "component"; ++ UMLSymbols["componentBox"] = "componentBox"; ++ UMLSymbols["template"] = "template"; ++ UMLSymbols["activation"] = "activation"; ++ UMLSymbols["deletion"] = "deletion"; ++ UMLSymbols["assembly"] = "assembly"; ++ UMLSymbols["providedInterface"] = "providedInterface"; ++ UMLSymbols["requiredInterface"] = "requiredInterface"; ++})(UMLSymbols || (UMLSymbols = {})); ++var GeometryCommonTextKeys; ++(function (GeometryCommonTextKeys) { ++ GeometryCommonTextKeys["name"] = "name"; ++ GeometryCommonTextKeys["content"] = "content"; ++})(GeometryCommonTextKeys || (GeometryCommonTextKeys = {})); ++const PlaitGeometry = {}; ++ ++var SwimlaneSymbols; ++(function (SwimlaneSymbols) { ++ SwimlaneSymbols["swimlaneVertical"] = "swimlaneVertical"; ++ SwimlaneSymbols["swimlaneHorizontal"] = "swimlaneHorizontal"; ++})(SwimlaneSymbols || (SwimlaneSymbols = {})); ++var SwimlaneDrawSymbols; ++(function (SwimlaneDrawSymbols) { ++ SwimlaneDrawSymbols["swimlaneVertical"] = "swimlaneVertical"; ++ SwimlaneDrawSymbols["swimlaneHorizontal"] = "swimlaneHorizontal"; ++ SwimlaneDrawSymbols["swimlaneVerticalWithHeader"] = "swimlaneVerticalWithHeader"; ++ SwimlaneDrawSymbols["swimlaneHorizontalWithHeader"] = "swimlaneHorizontalWithHeader"; ++})(SwimlaneDrawSymbols || (SwimlaneDrawSymbols = {})); ++ ++var TableSymbols; ++(function (TableSymbols) { ++ TableSymbols["table"] = "table"; ++})(TableSymbols || (TableSymbols = {})); ++const PlaitTableElement = { ++ isTable: (value) => { ++ return value.type === 'table'; ++ }, ++ isVerticalText: (value) => { ++ return value.text?.direction === 'vertical'; ++ } ++}; ++ ++const WithDrawPluginKey = 'plait-draw-plugin-key'; ++var DrawI18nKey; ++(function (DrawI18nKey) { ++ DrawI18nKey["lineText"] = "line-text"; ++ DrawI18nKey["geometryText"] = "geometry-text"; ++})(DrawI18nKey || (DrawI18nKey = {})); ++ ++const ShapeDefaultSpace = { ++ rectangleAndText: 4 ++}; ++const DefaultDrawStyle = { ++ strokeWidth: 2, ++ defaultRadius: 4, ++ strokeColor: '#000', ++ fill: DEFAULT_FILL ++}; ++const DefaultDrawActiveStyle = { ++ strokeWidth: ACTIVE_STROKE_WIDTH, ++ selectionStrokeWidth: ACTIVE_STROKE_WIDTH ++}; ++const DefaultBasicShapeProperty = { ++ width: 100, ++ height: 100, ++ strokeColor: DEFAULT_COLOR, ++ strokeWidth: 2 ++}; ++const DefaultPentagonArrowProperty = { ++ width: 120, ++ height: 50 ++}; ++const DefaultTwoWayArrowProperty = { ++ width: 138, ++ height: 80 ++}; ++const DefaultArrowProperty = { ++ width: 100, ++ height: 80 ++}; ++const DefaultCloudProperty = { ++ width: 120, ++ height: 100 ++}; ++const DefaultTextProperty = { ++ width: 36, ++ height: 20, ++ text: '文本' ++}; ++const GeometryThreshold = { ++ defaultTextMaxWidth: 34 * 14 ++}; ++const DefaultConnectorProperty = { ++ width: 44, ++ height: 44 ++}; ++const DefaultFlowchartProperty = { ++ width: 120, ++ height: 60 ++}; ++const DefaultDataBaseProperty = { ++ width: 70, ++ height: 80 ++}; ++const DefaultInternalStorageProperty = { ++ width: 80, ++ height: 80 ++}; ++const DefaultDecisionProperty = { ++ width: 140, ++ height: 70 ++}; ++const DefaultDataProperty = { ++ width: 124, ++ height: 60 ++}; ++const DefaultDocumentProperty = { ++ width: 120, ++ height: 70 ++}; ++const DefaultNoteProperty = { ++ width: 160, ++ height: 100 ++}; ++const DefaultMultiDocumentProperty = { ++ width: 120, ++ height: 80 ++}; ++const DefaultManualInputProperty = { ++ width: 117, ++ height: 59 ++}; ++const DefaultMergeProperty = { ++ width: 47, ++ height: 33 ++}; ++const DefaultActorProperty = { ++ width: 68, ++ height: 100 ++}; ++const DefaultContainerProperty = { ++ width: 300, ++ height: 240 ++}; ++const DefaultPackageProperty = { ++ width: 210, ++ height: 150, ++ texts: [ ++ { ++ id: GeometryCommonTextKeys.name, ++ text: '包名', ++ align: Alignment.left ++ }, ++ { ++ id: GeometryCommonTextKeys.content, ++ text: '', ++ align: Alignment.left ++ } ++ ] ++}; ++const DefaultActivationProperty = { ++ width: 18, ++ height: 80 ++}; ++const DefaultObjectProperty = { ++ width: 120, ++ height: 60 ++}; ++const DefaultComponentBoxProperty = { ++ width: 200, ++ height: 150 ++}; ++const DefaultDeletionProperty = { ++ width: 40, ++ height: 40 ++}; ++const DefaultPortProperty = { ++ width: 20, ++ height: 20 ++}; ++const DefaultRequiredInterfaceProperty = { ++ width: 70, ++ height: 56 ++}; ++const DefaultAssemblyProperty = { ++ width: 120, ++ height: 56 ++}; ++const DefaultProvidedInterfaceProperty = { ++ width: 70, ++ height: 34 ++}; ++const DefaultCombinedFragmentProperty = { ++ width: 400, ++ height: 280, ++ texts: [ ++ { ++ id: GeometryCommonTextKeys.name, ++ text: 'Opt | Alt | Loop', ++ align: Alignment.left ++ }, ++ { ++ id: GeometryCommonTextKeys.content, ++ text: '[Condition]', ++ align: Alignment.left ++ } ++ ] ++}; ++const DefaultClassProperty = { ++ width: 230, ++ height: 180, ++ texts: [ ++ { text: 'Class', align: Alignment.center }, ++ { ++ text: '+ attribute1:type defaultValue\n+ attribute2:type\n- attribute3:type', ++ align: Alignment.left ++ }, ++ { ++ text: '+ operation1(params):returnType\n- operation2(params)\n- operation3()', ++ align: Alignment.left ++ } ++ ] ++}; ++const DefaultInterfaceProperty = { ++ width: 230, ++ height: 140, ++ texts: [ ++ { text: '<>\nInterface', align: Alignment.center }, ++ { ++ text: '+ operation1(params):returnType\n- operation2(params)\n- operation3()', ++ align: Alignment.left ++ } ++ ] ++}; ++const DefaultBasicShapePropertyMap = { ++ [BasicShapes.pentagonArrow]: DefaultPentagonArrowProperty, ++ [BasicShapes.processArrow]: DefaultPentagonArrowProperty, ++ [BasicShapes.cloud]: DefaultCloudProperty, ++ [BasicShapes.twoWayArrow]: DefaultTwoWayArrowProperty, ++ [BasicShapes.leftArrow]: DefaultArrowProperty, ++ [BasicShapes.rightArrow]: DefaultArrowProperty ++}; ++const DefaultFlowchartPropertyMap = { ++ [FlowchartSymbols.connector]: DefaultConnectorProperty, ++ [FlowchartSymbols.process]: DefaultFlowchartProperty, ++ [FlowchartSymbols.decision]: DefaultDecisionProperty, ++ [FlowchartSymbols.data]: DefaultDataProperty, ++ [FlowchartSymbols.terminal]: DefaultFlowchartProperty, ++ [FlowchartSymbols.manualInput]: DefaultManualInputProperty, ++ [FlowchartSymbols.preparation]: DefaultFlowchartProperty, ++ [FlowchartSymbols.manualLoop]: DefaultFlowchartProperty, ++ [FlowchartSymbols.merge]: DefaultMergeProperty, ++ [FlowchartSymbols.delay]: DefaultFlowchartProperty, ++ [FlowchartSymbols.storedData]: DefaultFlowchartProperty, ++ [FlowchartSymbols.or]: DefaultConnectorProperty, ++ [FlowchartSymbols.summingJunction]: DefaultConnectorProperty, ++ [FlowchartSymbols.predefinedProcess]: DefaultFlowchartProperty, ++ [FlowchartSymbols.offPage]: DefaultFlowchartProperty, ++ [FlowchartSymbols.document]: DefaultDocumentProperty, ++ [FlowchartSymbols.multiDocument]: DefaultMultiDocumentProperty, ++ [FlowchartSymbols.database]: DefaultDataBaseProperty, ++ [FlowchartSymbols.hardDisk]: DefaultFlowchartProperty, ++ [FlowchartSymbols.internalStorage]: DefaultInternalStorageProperty, ++ [FlowchartSymbols.noteCurlyLeft]: DefaultNoteProperty, ++ [FlowchartSymbols.noteCurlyRight]: DefaultNoteProperty, ++ [FlowchartSymbols.noteSquare]: DefaultNoteProperty, ++ [FlowchartSymbols.display]: DefaultFlowchartProperty ++}; ++const DefaultUMLPropertyMap = { ++ [UMLSymbols.actor]: DefaultActorProperty, ++ [UMLSymbols.useCase]: DefaultDocumentProperty, ++ [UMLSymbols.container]: DefaultContainerProperty, ++ [UMLSymbols.note]: DefaultObjectProperty, ++ [UMLSymbols.package]: DefaultPackageProperty, ++ [UMLSymbols.combinedFragment]: DefaultCombinedFragmentProperty, ++ [UMLSymbols.class]: DefaultClassProperty, ++ [UMLSymbols.interface]: DefaultInterfaceProperty, ++ [UMLSymbols.activation]: DefaultActivationProperty, ++ [UMLSymbols.object]: DefaultObjectProperty, ++ [UMLSymbols.deletion]: DefaultDeletionProperty, ++ [UMLSymbols.activityClass]: DefaultObjectProperty, ++ [UMLSymbols.simpleClass]: DefaultObjectProperty, ++ [UMLSymbols.component]: DefaultMultiDocumentProperty, ++ [UMLSymbols.template]: DefaultMultiDocumentProperty, ++ [UMLSymbols.componentBox]: DefaultComponentBoxProperty, ++ [UMLSymbols.port]: DefaultPortProperty, ++ [UMLSymbols.branchMerge]: DefaultDeletionProperty, ++ [UMLSymbols.assembly]: DefaultAssemblyProperty, ++ [UMLSymbols.providedInterface]: DefaultProvidedInterfaceProperty, ++ [UMLSymbols.requiredInterface]: DefaultRequiredInterfaceProperty ++}; ++const MultipleTextGeometryTextKeys = { ++ [UMLSymbols.package]: Object.keys(GeometryCommonTextKeys), ++ [UMLSymbols.combinedFragment]: Object.keys(GeometryCommonTextKeys) ++}; ++const LINE_HIT_GEOMETRY_BUFFER = 4; ++const LINE_SNAPPING_BUFFER = 4; ++const LINE_SNAPPING_CONNECTOR_BUFFER = 4; ++const LINE_ALIGN_TOLERANCE = 4; ++const GEOMETRY_WITHOUT_TEXT = [ ++ FlowchartSymbols.or, ++ FlowchartSymbols.summingJunction, ++ UMLSymbols.activation, ++ UMLSymbols.deletion, ++ UMLSymbols.port, ++ UMLSymbols.branchMerge, ++ UMLSymbols.assembly, ++ UMLSymbols.providedInterface, ++ UMLSymbols.requiredInterface ++]; ++const GEOMETRY_WITH_MULTIPLE_TEXT = [UMLSymbols.package, UMLSymbols.combinedFragment]; ++const GEOMETRY_NOT_CLOSED = [ ++ FlowchartSymbols.noteCurlyLeft, ++ FlowchartSymbols.noteCurlyRight, ++ FlowchartSymbols.noteSquare, ++ UMLSymbols.requiredInterface, ++ UMLSymbols.deletion ++]; ++ ++const getGeometryPointers = () => { ++ return [...Object.keys(BasicShapes), ...Object.keys(FlowchartSymbols), ...Object.keys(UMLSymbols)]; ++}; ++const getSwimlanePointers = () => { ++ return Object.keys(SwimlaneDrawSymbols); ++}; ++const getSwimlaneShapes = () => { ++ return Object.keys(SwimlaneSymbols); ++}; ++const getBasicPointers = () => { ++ return Object.keys(BasicShapes); ++}; ++const getFlowchartPointers = () => { ++ return Object.keys(FlowchartSymbols); ++}; ++const getUMLPointers = () => { ++ return Object.keys(UMLSymbols); ++}; ++const getArrowLinePointers = () => { ++ return Object.keys(ArrowLineShape); ++}; ++const getVectorLinePointers = () => { ++ return Object.keys(VectorLinePointerType); ++}; ++ ++const DEFAULT_IMAGE_WIDTH = 1000; ++ ++const DrawThemeColors = { ++ [ThemeColorMode.default]: { ++ strokeColor: DEFAULT_COLOR, ++ fill: '#FFFFFF' ++ }, ++ [ThemeColorMode.colorful]: { ++ strokeColor: '#06ADBF', ++ fill: '#CDEFF2' ++ }, ++ [ThemeColorMode.soft]: { ++ strokeColor: '#6D89C1', ++ fill: '#DADFEB' ++ }, ++ [ThemeColorMode.retro]: { ++ strokeColor: '#E9C358', ++ fill: '#F6EDCF' ++ }, ++ [ThemeColorMode.dark]: { ++ strokeColor: '#FFFFFF', ++ fill: '#434343' ++ }, ++ [ThemeColorMode.starry]: { ++ strokeColor: '#42ABE5', ++ fill: '#163F5A' ++ } ++}; ++ ++const SWIMLANE_HEADER_SIZE = 42; ++const DefaultSwimlaneVerticalWithHeaderProperty = { ++ width: 580, ++ height: 524 ++}; ++const DefaultSwimlaneHorizontalWithHeaderProperty = { ++ width: 524, ++ height: 580 ++}; ++const DefaultSwimlaneVerticalProperty = { ++ width: 580, ++ height: 524 ++}; ++const DefaultSwimlaneHorizontalProperty = { ++ width: 524, ++ height: 580 ++}; ++const DefaultSwimlanePropertyMap = { ++ [SwimlaneDrawSymbols.swimlaneHorizontal]: DefaultSwimlaneHorizontalProperty, ++ [SwimlaneDrawSymbols.swimlaneVertical]: DefaultSwimlaneVerticalProperty, ++ [SwimlaneDrawSymbols.swimlaneHorizontalWithHeader]: DefaultSwimlaneHorizontalWithHeaderProperty, ++ [SwimlaneDrawSymbols.swimlaneVerticalWithHeader]: DefaultSwimlaneVerticalWithHeaderProperty ++}; ++ ++const MIN_TEXT_WIDTH = 5; ++ ++const DefaultLineStyle = { ++ strokeWidth: 2, ++ strokeColor: '#000' ++}; ++const LINE_TEXT_SPACE = 4; ++const LINE_AUTO_COMPLETE_DIAMETER = 6; ++const LINE_AUTO_COMPLETE_OPACITY = 1; ++const LINE_AUTO_COMPLETE_HOVERED_OPACITY = 1; ++const LINE_AUTO_COMPLETE_HOVERED_DIAMETER = 12; ++const LINE_TEXT = '文本'; ++ ++// TODO: 是否可以完全基于位置定位 TextManager,实现 line 和 多文本 geometry 统一 ++// 一个元素有多个文本时,单纯通过位置无法获取 TextManage,因此这里单独通过 Map 保存关键字 key 和 TextManage 的对应关系 ++// 1. 单文本元素 key 就是元素的 id ++// 2. 表格元素 key 是单元格的 id ++// 3. 符合 isMultipleTextGeometry 的元素,key 是元素 id + text.id (通常不是 id 而是文本位置的常量) ++// 4. arrow-line 和 vector-line 文本不依赖于 text.generator,基于 text 可以直接找到 TextManage ++const KEY_TO_TEXT_MANAGE = new WeakMap(); ++const setTextManage = (board, element, text, textManage) => { ++ const textManages = KEY_TO_TEXT_MANAGE.get(board); ++ return KEY_TO_TEXT_MANAGE.set(board, { ...textManages, [getTextKey(element, text)]: textManage }); ++}; ++const getTextManage = (board, element, text) => { ++ const textManages = KEY_TO_TEXT_MANAGE.get(board); ++ return textManages[getTextKey(element, text)]; ++}; ++const deleteTextManage = (board, key) => { ++ const textManages = KEY_TO_TEXT_MANAGE.get(board); ++ delete textManages[key]; ++ KEY_TO_TEXT_MANAGE.set(board, textManages); ++}; ++class TextGenerator { ++ get shape() { ++ return this.element.shape || this.element.type; ++ } ++ constructor(board, element, texts, options) { ++ this.board = board; ++ this.texts = texts; ++ this.element = element; ++ this.options = options; ++ } ++ initialize() { ++ const textPlugins = (this.board.getPluginOptions(WithTextPluginKey) || {}) ++ .textPlugins; ++ this.textManages = this.texts.map(text => { ++ const textManage = this.createTextManage(text, textPlugins); ++ setTextManage(this.board, this.element, text, textManage); ++ return textManage; ++ }); ++ const ref = PlaitElement.getElementRef(this.element); ++ ref.initializeTextManage(this.textManages); ++ } ++ draw(elementG) { ++ const centerPoint = RectangleClient.getCenterPoint(this.board.getRectangle(this.element)); ++ this.texts.forEach(drawShapeText => { ++ const textManage = getTextManage(this.board, this.element, drawShapeText); ++ if (drawShapeText.text && textManage) { ++ textManage.draw(drawShapeText.text); ++ elementG.append(textManage.g); ++ (this.element.angle || this.element.angle === 0) && textManage.updateAngle(centerPoint, this.element.angle); ++ } ++ }); ++ } ++ update(element, previousDrawShapeTexts, currentDrawShapeTexts, elementG) { ++ this.element = element; ++ const centerPoint = RectangleClient.getCenterPoint(this.board.getRectangle(this.element)); ++ const textPlugins = (this.board.getPluginOptions(WithTextPluginKey) || {}) ++ .textPlugins; ++ const removedTexts = previousDrawShapeTexts.filter(value => { ++ return !currentDrawShapeTexts.find(item => item.id === value.id); ++ }); ++ if (removedTexts.length) { ++ removedTexts.forEach(item => { ++ const textManage = getTextManage(this.board, element, item); ++ const index = this.textManages.findIndex(value => value === textManage); ++ if (index > -1 && item.text) { ++ this.textManages.splice(index, 1); ++ } ++ textManage?.destroy(); ++ deleteTextManage(this.board, item.id); ++ }); ++ } ++ currentDrawShapeTexts.forEach(drawShapeText => { ++ if (drawShapeText.text) { ++ let textManage = getTextManage(this.board, this.element, drawShapeText); ++ if (!textManage) { ++ textManage = this.createTextManage(drawShapeText, textPlugins); ++ setTextManage(this.board, element, drawShapeText, textManage); ++ textManage.draw(drawShapeText.text); ++ elementG.append(textManage.g); ++ this.textManages.push(textManage); ++ } ++ else { ++ textManage.updateText(drawShapeText.text); ++ textManage.updateRectangle(); ++ } ++ (this.element.angle || this.element.angle === 0) && textManage.updateAngle(centerPoint, this.element.angle); ++ } ++ }); ++ } ++ createTextManage(text, textPlugins) { ++ const textManage = new TextManage(this.board, { ++ getRectangle: () => { ++ return this.getRectangle(text); ++ }, ++ onChange: (data) => { ++ return this.options.onChange(this.element, data, text); ++ }, ++ getMaxWidth: () => { ++ return this.getMaxWidth(text); ++ }, ++ getRenderRectangle: () => { ++ return this.options.getRenderRectangle ? this.options.getRenderRectangle(this.element, text) : this.getRectangle(text); ++ }, ++ textPlugins ++ }); ++ return textManage; ++ } ++ getRectangle(text) { ++ const getRectangle = getEngine(this.shape).getTextRectangle; ++ if (getRectangle) { ++ return getRectangle(this.board, this.element, text); ++ } ++ return getTextRectangle$1(this.board, this.element); ++ } ++ getMaxWidth(text) { ++ return this.options.getMaxWidth ? this.options.getMaxWidth() : this.getRectangle(text).width; ++ } ++ destroy() { ++ const ref = PlaitElement.getElementRef(this.element); ++ ref.destroyTextManage(); ++ this.textManages = []; ++ this.texts.forEach(item => { ++ deleteTextManage(this.board, item.id); ++ }); ++ } ++} ++ ++const isSingleSelectTable = (board) => { ++ const selectedElements = getSelectedElements(board); ++ return selectedElements && selectedElements.length === 1 && PlaitDrawElement.isElementByTable(selectedElements[0]); ++}; ++const getSelectedTableElements = (board, elements) => { ++ const selectedElements = elements?.length ? elements : getSelectedElements(board); ++ return selectedElements.filter(value => PlaitDrawElement.isElementByTable(value)); ++}; ++const SELECTED_CELLS = new WeakMap(); ++function getSelectedCells(element) { ++ return SELECTED_CELLS.get(element); ++} ++function setSelectedCells(element, cells) { ++ return SELECTED_CELLS.set(element, cells); ++} ++function clearSelectedCells(element) { ++ return SELECTED_CELLS.delete(element); ++} ++ ++function getCellsWithPoints(board, element) { ++ const table = board?.buildTable(element); ++ if (!table || !table.points || !table.columns || !table.rows) { ++ throw new Error('can not get table cells points'); ++ } ++ const rectangle = RectangleClient.getRectangleByPoints(table.points); ++ const columnsCount = table.columns.length; ++ const rowsCount = table.rows.length; ++ const cellWidths = calculateCellsSize(table.columns, rectangle.width, columnsCount, true); ++ const cellHeights = calculateCellsSize(table.rows, rectangle.height, rowsCount, false); ++ const cells = table.cells.map(cell => { ++ const rowIdx = table.rows.findIndex(row => row.id === cell.rowId); ++ const columnIdx = table.columns.findIndex(column => column.id === cell.columnId); ++ let cellTopLeftX = rectangle.x; ++ for (let i = 0; i < columnIdx; i++) { ++ cellTopLeftX += cellWidths[i]; ++ } ++ let cellTopLeftY = rectangle.y; ++ for (let i = 0; i < rowIdx; i++) { ++ cellTopLeftY += cellHeights[i]; ++ } ++ const cellWidth = calculateCellSize(cell, cellWidths, columnIdx, true); ++ const cellBottomRightX = cellTopLeftX + cellWidth; ++ const cellHeight = calculateCellSize(cell, cellHeights, rowIdx, false); ++ const cellBottomRightY = cellTopLeftY + cellHeight; ++ return { ++ ...cell, ++ points: [ ++ [cellTopLeftX, cellTopLeftY], ++ [cellBottomRightX, cellBottomRightY] ++ ] ++ }; ++ }); ++ return cells; ++} ++function getCellWithPoints(board, table, cellId) { ++ try { ++ const cells = getCellsWithPoints(board, table); ++ const cellIndex = cells && table.cells.findIndex(item => item.id === cellId); ++ return cells[cellIndex]; ++ } ++ catch (error) { ++ throw new Error('can not get table cell points'); ++ } ++} ++function calculateCellsSize(items, tableSize, count, isWidth) { ++ const cellSizes = []; ++ const sizeType = isWidth ? 'width' : 'height'; ++ // The remaining size of the table excluding cells with already set sizes. ++ let totalSizeRemaining = tableSize; ++ items.forEach((item, index) => { ++ if (item[sizeType]) { ++ cellSizes[index] = item[sizeType]; ++ totalSizeRemaining -= item[sizeType]; ++ } ++ }); ++ // Divide the remaining size equally. ++ const remainingItemCount = count - cellSizes.filter(item => !!item).length; ++ const remainingCellSize = remainingItemCount > 0 ? totalSizeRemaining / remainingItemCount : 0; ++ for (let i = 0; i < count; i++) { ++ if (!cellSizes[i]) { ++ cellSizes[i] = remainingCellSize; ++ } ++ } ++ return cellSizes; ++} ++function calculateCellSize(cell, sizes, index, isWidth) { ++ const span = isWidth ? cell.colspan || 1 : cell.rowspan || 1; ++ let size = 0; ++ for (let i = 0; i < span; i++) { ++ const cellIndex = index + i; ++ size += sizes[cellIndex]; ++ } ++ return size; ++} ++function getHitCell(board, element, point) { ++ const table = board.buildTable(element); ++ const cells = getCellsWithPoints(board, table); ++ const rectangle = RectangleClient.getRectangleByPoints([point, point]); ++ const cell = cells.find(item => { ++ const cellRectangle = RectangleClient.getRectangleByPoints(item.points); ++ return RectangleClient.isHit(rectangle, cellRectangle); ++ }); ++ if (cell) { ++ return table.cells.find(item => item.id === cell.id); ++ } ++ return null; ++} ++function editCell(board, cell) { ++ const textManage = getTextManageByCell(board, cell); ++ textManage && textManage.edit(); ++} ++function getTextManageByCell(board, cell) { ++ return getTextManage(board, undefined, cell); ++} ++const updateColumns = (table, columnId, width, offset) => { ++ const columns = table.columns.map(item => (item.id === columnId ? { ...item, width } : item)); ++ const points = [table.points[0], [table.points[1][0] + offset, table.points[1][1]]]; ++ return { columns, points }; ++}; ++const updateRows = (table, rowId, height, offset) => { ++ const rows = table.rows.map(item => (item.id === rowId ? { ...item, height } : item)); ++ const points = [table.points[0], [table.points[1][0], table.points[1][1] + offset]]; ++ return { rows, points }; ++}; ++function updateCellIdsByRowOrColumn(cells, oldId, newId, type) { ++ const id = `${type}Id`; ++ cells.forEach(item => { ++ if (item[id] === oldId) { ++ item[id] = newId; ++ } ++ }); ++} ++function updateRowOrColumnIds(element, type) { ++ element[`${type}s`].forEach(item => { ++ const newId = idCreator(); ++ updateCellIdsByRowOrColumn(element.cells, item.id, newId, type); ++ item.id = newId; ++ }); ++} ++function updateCellIds(cells) { ++ cells.forEach(item => { ++ const newId = idCreator(); ++ item.id = newId; ++ }); ++} ++function isCellIncludeText(cell) { ++ return cell.text; ++} ++function getCellsRectangle(board, element, cells) { ++ const cellsWithPoints = getCellsWithPoints(board, element); ++ const points = cells.map(cell => { ++ const cellWithPoints = cellsWithPoints.find(item => item.id === cell.id); ++ return cellWithPoints.points; ++ }); ++ return RectangleClient.getRectangleByPoints(points); ++} ++const createCell = (rowId, columnId, text = null) => { ++ const cell = { ++ id: idCreator(), ++ rowId, ++ columnId ++ }; ++ if (text !== null) { ++ cell['text'] = { ++ children: [{ text }], ++ align: Alignment.center ++ }; ++ } ++ return cell; ++}; ++const getSelectedTableCellsEditor = (board) => { ++ if (isSingleSelectTable(board)) { ++ const elements = getSelectedTableElements(board); ++ const selectedCells = getSelectedCells(elements[0]); ++ const selectedCellsEditor = selectedCells?.map(cell => { ++ const textManage = getTextManageByCell(board, cell); ++ return textManage?.editor; ++ }); ++ if (selectedCellsEditor?.length) { ++ return selectedCellsEditor; ++ } ++ } ++ return undefined; ++}; ++ ++const SHAPE_MAX_LENGTH = 6; ++const memorizedShape = new WeakMap(); ++const getMemorizeKey = (element) => { ++ let key = ''; ++ switch (true) { ++ case PlaitDrawElement.isText(element): { ++ key = MemorizeKey.text; ++ break; ++ } ++ case PlaitDrawElement.isBasicShape(element): { ++ key = MemorizeKey.basicShape; ++ break; ++ } ++ case PlaitDrawElement.isFlowchart(element): { ++ key = MemorizeKey.flowchart; ++ break; ++ } ++ case PlaitDrawElement.isArrowLine(element): { ++ key = MemorizeKey.arrowLine; ++ break; ++ } ++ case PlaitDrawElement.isUML(element): { ++ key = MemorizeKey.UML; ++ } ++ } ++ return key; ++}; ++const getLineMemorizedLatest = () => { ++ const properties = getMemorizedLatest(MemorizeKey.arrowLine); ++ return { ...properties }; ++}; ++const getMemorizedLatestByPointer = (pointer) => { ++ let memorizeKey = ''; ++ if (PlaitDrawElement.isBasicShape({ shape: pointer })) { ++ memorizeKey = pointer === BasicShapes.text ? MemorizeKey.text : MemorizeKey.basicShape; ++ } ++ else if (PlaitDrawElement.isUML({ shape: pointer })) { ++ memorizeKey = MemorizeKey.UML; ++ } ++ else { ++ memorizeKey = MemorizeKey.flowchart; ++ } ++ const properties = { ...getMemorizedLatest(memorizeKey) }; ++ const textProperties = { ...properties.text }; ++ delete properties.text; ++ return { textProperties, geometryProperties: properties }; ++}; ++const memorizeLatestText = (element, operations) => { ++ const memorizeKey = getMemorizeKey(element); ++ let textMemory = getMemorizedLatest(memorizeKey)?.text || {}; ++ const setNodeOperation = operations.find((operation) => operation.type === 'set_node'); ++ if (setNodeOperation) { ++ const { properties, newProperties } = setNodeOperation; ++ for (const key in newProperties) { ++ const value = newProperties[key]; ++ if (value == null) { ++ delete textMemory[key]; ++ } ++ else { ++ textMemory[key] = value; ++ } ++ } ++ for (const key in properties) { ++ if (!newProperties.hasOwnProperty(key)) { ++ delete textMemory[key]; ++ } ++ } ++ memorizeLatest(memorizeKey, 'text', textMemory); ++ } ++}; ++const memorizeLatestShape = (board, shape) => { ++ const shapes = memorizedShape.has(board) ? memorizedShape.get(board) : []; ++ const shapeIndex = shapes.indexOf(shape); ++ if (shape === BasicShapes.text || shapeIndex === 0) { ++ return; ++ } ++ if (shapeIndex !== -1) { ++ shapes.splice(shapeIndex, 1); ++ } ++ else { ++ if (shapes.length === SHAPE_MAX_LENGTH) { ++ shapes.pop(); ++ } ++ } ++ shapes.unshift(shape); ++ memorizedShape.set(board, shapes); ++}; ++const getMemorizedLatestShape = (board) => { ++ return memorizedShape.get(board); ++}; ++ ++const debugKey$4 = 'debug:plait:line-mirror'; ++const debugGenerator$6 = createDebugGenerator(debugKey$4); ++const alignPoint = (basePoint, movingPoint) => { ++ const newPoint = [...movingPoint]; ++ if (Point.isVertical(newPoint, basePoint, LINE_ALIGN_TOLERANCE)) { ++ newPoint[0] = basePoint[0]; ++ } ++ if (Point.isHorizontal(newPoint, basePoint, LINE_ALIGN_TOLERANCE)) { ++ newPoint[1] = basePoint[1]; ++ } ++ return newPoint; ++}; ++const alignPoints = (basePoints, movingPoint, targetIndex) => { ++ let newMovingPoint = [...movingPoint]; ++ basePoints.forEach((basePoint, index) => { ++ if (index === targetIndex) { ++ return; ++ } ++ newMovingPoint = alignPoint(basePoint, newMovingPoint); ++ }); ++ return newMovingPoint; ++}; ++function getResizedPreviousAndNextPoint(nextRenderPoints, sourcePoint, targetPoint, handleIndex) { ++ const referencePoint = { ++ previous: null, ++ next: null ++ }; ++ const startPoint = nextRenderPoints[handleIndex]; ++ const endPoint = nextRenderPoints[handleIndex + 1]; ++ const isHorizontal = Point.isHorizontal(startPoint, endPoint); ++ const isVertical = Point.isVertical(startPoint, endPoint); ++ const previousPoint = nextRenderPoints[handleIndex - 1] ?? nextRenderPoints[0]; ++ const beforePreviousPoint = nextRenderPoints[handleIndex - 2] ?? sourcePoint; ++ if ((isHorizontal && Point.isHorizontal(beforePreviousPoint, previousPoint)) || ++ (isVertical && Point.isVertical(beforePreviousPoint, previousPoint))) { ++ referencePoint.previous = previousPoint; ++ } ++ const nextPoint = nextRenderPoints[handleIndex + 2] ?? nextRenderPoints[nextRenderPoints.length - 1]; ++ const afterNextPoint = nextRenderPoints[handleIndex + 3] ?? targetPoint; ++ if ((isHorizontal && Point.isHorizontal(nextPoint, afterNextPoint)) || (isVertical && Point.isVertical(nextPoint, afterNextPoint))) { ++ referencePoint.next = nextPoint; ++ } ++ return referencePoint; ++} ++function alignElbowSegment(startKeyPoint, endKeyPoint, resizeState, resizedPreviousAndNextPoint) { ++ let newStartPoint = startKeyPoint; ++ let newEndPoint = endKeyPoint; ++ if (Point.isHorizontal(startKeyPoint, endKeyPoint)) { ++ const offsetY = Point.getOffsetY(resizeState.startPoint, resizeState.endPoint); ++ let pointY = startKeyPoint[1] + offsetY; ++ if (resizedPreviousAndNextPoint.previous && Math.abs(resizedPreviousAndNextPoint.previous[1] - pointY) < LINE_ALIGN_TOLERANCE) { ++ pointY = resizedPreviousAndNextPoint.previous[1]; ++ } ++ else if (resizedPreviousAndNextPoint.next && Math.abs(resizedPreviousAndNextPoint.next[1] - pointY) < LINE_ALIGN_TOLERANCE) { ++ pointY = resizedPreviousAndNextPoint.next[1]; ++ } ++ newStartPoint = [startKeyPoint[0], pointY]; ++ newEndPoint = [endKeyPoint[0], pointY]; ++ } ++ if (Point.isVertical(startKeyPoint, endKeyPoint)) { ++ const offsetX = Point.getOffsetX(resizeState.startPoint, resizeState.endPoint); ++ let pointX = startKeyPoint[0] + offsetX; ++ if (resizedPreviousAndNextPoint.previous && Math.abs(resizedPreviousAndNextPoint.previous[0] - pointX) < LINE_ALIGN_TOLERANCE) { ++ pointX = resizedPreviousAndNextPoint.previous[0]; ++ } ++ else if (resizedPreviousAndNextPoint.next && Math.abs(resizedPreviousAndNextPoint.next[0] - pointX) < LINE_ALIGN_TOLERANCE) { ++ pointX = resizedPreviousAndNextPoint.next[0]; ++ } ++ newStartPoint = [pointX, startKeyPoint[1]]; ++ newEndPoint = [pointX, endKeyPoint[1]]; ++ } ++ return [newStartPoint, newEndPoint]; ++} ++function getIndexAndDeleteCountByKeyPoint(board, element, dataPoints, nextRenderPoints, handleIndex) { ++ let index = null; ++ let deleteCount = null; ++ const startKeyPoint = nextRenderPoints[handleIndex]; ++ const endKeyPoint = nextRenderPoints[handleIndex + 1]; ++ if (!startKeyPoint || !endKeyPoint) { ++ return { ++ index, ++ deleteCount ++ }; ++ } ++ const midDataPoints = dataPoints.slice(1, -1); ++ const startIndex = midDataPoints.findIndex((item) => Point.isEquals(item, startKeyPoint)); ++ const endIndex = midDataPoints.findIndex((item) => Point.isEquals(item, endKeyPoint)); ++ if (Math.max(startIndex, endIndex) > -1) { ++ if (startIndex > -1 && endIndex > -1) { ++ return { ++ index: startIndex, ++ deleteCount: 2 ++ }; ++ } ++ if (startIndex > -1 && endIndex === -1) { ++ const isReplace = startIndex < midDataPoints.length - 1 && ++ Point.isAlign([midDataPoints[startIndex], midDataPoints[startIndex + 1], startKeyPoint, endKeyPoint]); ++ if (isReplace) { ++ return { ++ index: startIndex, ++ deleteCount: 2 ++ }; ++ } ++ return { ++ index: startIndex, ++ deleteCount: 1 ++ }; ++ } ++ if (startIndex === -1 && endIndex > -1) { ++ const isReplace = endIndex > 0 && Point.isAlign([midDataPoints[endIndex], midDataPoints[endIndex - 1], startKeyPoint, endKeyPoint]); ++ if (isReplace) { ++ return { ++ index: endIndex - 1, ++ deleteCount: 2 ++ }; ++ } ++ return { ++ index: endIndex, ++ deleteCount: 1 ++ }; ++ } ++ } ++ else { ++ for (let i = 0; i < midDataPoints.length - 1; i++) { ++ const currentPoint = midDataPoints[i]; ++ const nextPoint = midDataPoints[i + 1]; ++ if (Point.isAlign([currentPoint, nextPoint, startKeyPoint, endKeyPoint])) { ++ index = i; ++ deleteCount = 2; ++ break; ++ } ++ if (Point.isAlign([currentPoint, nextPoint, startKeyPoint])) { ++ index = Math.min(i + 1, midDataPoints.length - 1); ++ deleteCount = 1; ++ break; ++ } ++ if (Point.isAlign([currentPoint, nextPoint, endKeyPoint])) { ++ index = Math.max(i - 1, 0); ++ deleteCount = 1; ++ break; ++ } ++ } ++ } ++ if (index === null) { ++ deleteCount = 0; ++ if (midDataPoints.length > 0) { ++ const handleRefPair = getArrowLineHandleRefPair(board, element); ++ const params = getElbowLineRouteOptions(board, element, handleRefPair); ++ const keyPoints = removeDuplicatePoints(generateElbowLineRoute(params, board)); ++ const nextKeyPoints = simplifyOrthogonalPoints(keyPoints.slice(1, keyPoints.length - 1)); ++ const nextDataPoints = [nextRenderPoints[0], ...midDataPoints, nextRenderPoints[nextRenderPoints.length - 1]]; ++ const mirrorDataPoints = getMirrorDataPoints(board, nextDataPoints, nextKeyPoints, params); ++ for (let i = handleIndex - 1; i >= 0; i--) { ++ const previousIndex = mirrorDataPoints.slice(1, -1).findIndex((item) => Point.isEquals(item, nextRenderPoints[i])); ++ if (previousIndex > -1) { ++ index = previousIndex + 1; ++ break; ++ } ++ } ++ if (index === null) { ++ index = 0; ++ // When renderPoints is a straight line and dataPoints are not on the line, ++ // the default 'deleteCount' is set to midDataPoints.length. ++ if (Point.isAlign(nextRenderPoints)) { ++ deleteCount = midDataPoints.length; ++ } ++ } ++ } ++ else { ++ index = 0; ++ } ++ } ++ return { ++ index, ++ deleteCount ++ }; ++} ++function getMirrorDataPoints(board, nextDataPoints, nextKeyPoints, params) { ++ for (let index = 1; index < nextDataPoints.length - 2; index++) { ++ adjustByCustomPointStartIndex(board, index, nextDataPoints, nextKeyPoints, params); ++ } ++ return nextDataPoints; ++} ++/** ++ * adjust based parallel segment ++ */ ++const adjustByCustomPointStartIndex = (board, customPointStartIndex, nextDataPoints, nextKeyPoints, params) => { ++ const beforePoint = nextDataPoints[customPointStartIndex - 1]; ++ const startPoint = nextDataPoints[customPointStartIndex]; ++ const endPoint = nextDataPoints[customPointStartIndex + 1]; ++ const afterPoint = nextDataPoints[customPointStartIndex + 2]; ++ const beforeSegment = [beforePoint, startPoint]; ++ const afterSegment = [endPoint, afterPoint]; ++ const isStraightWithBefore = Point.isAlign(beforeSegment); ++ const isStraightWithAfter = Point.isAlign(afterSegment); ++ let isAdjustStart = false; ++ let isAdjustEnd = false; ++ if (!isStraightWithBefore || !isStraightWithAfter) { ++ const midKeyPointsWithBefore = getMidKeyPoints(nextKeyPoints, beforeSegment[0], beforeSegment[1]); ++ const midKeyPointsWithAfter = getMidKeyPoints(nextKeyPoints, afterSegment[0], afterSegment[1]); ++ const hasMidKeyPoints = midKeyPointsWithBefore.length > 0 && midKeyPointsWithAfter.length > 0; ++ isAdjustStart = !isStraightWithBefore && !hasMidKeyPoints; ++ isAdjustEnd = !isStraightWithAfter && !hasMidKeyPoints; ++ } ++ if (isAdjustStart || isAdjustEnd) { ++ const parallelSegment = [startPoint, endPoint]; ++ const parallelSegments = findOrthogonalParallelSegments(parallelSegment, nextKeyPoints); ++ const mirrorSegments = findMirrorSegments(board, parallelSegment, parallelSegments, params.sourceRectangle, params.targetRectangle); ++ if (mirrorSegments.length === 1) { ++ const mirrorSegment = mirrorSegments[0]; ++ if (isAdjustStart) { ++ nextDataPoints.splice(customPointStartIndex, 1, mirrorSegment[0]); ++ } ++ if (isAdjustEnd) { ++ nextDataPoints.splice(customPointStartIndex + 1, 1, mirrorSegment[1]); ++ } ++ } ++ else { ++ const isHorizontal = Point.isHorizontal(startPoint, endPoint); ++ const adjustIndex = isHorizontal ? 0 : 1; ++ if (isAdjustStart) { ++ const newStartPoint = [startPoint[0], startPoint[1]]; ++ newStartPoint[adjustIndex] = beforePoint[adjustIndex]; ++ nextDataPoints.splice(customPointStartIndex, 1, newStartPoint); ++ } ++ if (isAdjustEnd) { ++ const newEndPoint = [endPoint[0], endPoint[1]]; ++ newEndPoint[adjustIndex] = afterPoint[adjustIndex]; ++ nextDataPoints.splice(customPointStartIndex + 1, 1, newEndPoint); ++ } ++ } ++ } ++}; ++function isUpdatedHandleIndex(board, element, dataPoints, nextRenderPoints, handleIndex) { ++ const { deleteCount } = getIndexAndDeleteCountByKeyPoint(board, element, dataPoints, nextRenderPoints, handleIndex); ++ if (deleteCount !== null && deleteCount > 1) { ++ return true; ++ } ++ return false; ++} ++function getMidKeyPoints(simplifiedNextKeyPoints, startPoint, endPoint) { ++ let midElbowPoints = []; ++ let startPointIndex = -1; ++ let endPointIndex = -1; ++ for (let i = 0; i < simplifiedNextKeyPoints.length; i++) { ++ if (Point.isAlign([simplifiedNextKeyPoints[i], startPoint])) { ++ startPointIndex = i; ++ } ++ if (startPointIndex > -1 && Point.isAlign([simplifiedNextKeyPoints[i], endPoint])) { ++ endPointIndex = i; ++ break; ++ } ++ } ++ if (startPointIndex > -1 && endPointIndex > -1) { ++ midElbowPoints = simplifiedNextKeyPoints.slice(startPointIndex, endPointIndex + 1); ++ } ++ return midElbowPoints; ++} ++function findOrthogonalParallelSegments(segment, keyPoints) { ++ const isHorizontalSegment = Point.isHorizontal(segment[0], segment[1]); ++ const parallelSegments = []; ++ for (let i = 0; i < keyPoints.length - 1; i++) { ++ const current = keyPoints[i]; ++ const next = keyPoints[i + 1]; ++ const isHorizontal = Point.isHorizontal(current, next, 0.1); ++ if (isHorizontalSegment && isHorizontal) { ++ parallelSegments.push([current, next]); ++ } ++ if (!isHorizontalSegment && !isHorizontal) { ++ parallelSegments.push([current, next]); ++ } ++ } ++ return parallelSegments; ++} ++function findMirrorSegments(board, segment, parallelSegments, sourceRectangle, targetRectangle) { ++ debugGenerator$6.isDebug() && debugGenerator$6.clear(); ++ const mirrorSegments = []; ++ for (let index = 0; index < parallelSegments.length; index++) { ++ const parallelPath = parallelSegments[index]; ++ const startPoint = [segment[0][0], segment[0][1]]; ++ const endPoint = [segment[1][0], segment[1][1]]; ++ const isHorizontal = Point.isHorizontal(startPoint, endPoint); ++ const adjustDataIndex = isHorizontal ? 0 : 1; ++ startPoint[adjustDataIndex] = parallelPath[0][adjustDataIndex]; ++ endPoint[adjustDataIndex] = parallelPath[1][adjustDataIndex]; ++ const fakeRectangle = RectangleClient.getRectangleByPoints([startPoint, endPoint, ...parallelPath]); ++ const isValid = !RectangleClient.isHit(fakeRectangle, sourceRectangle) && !RectangleClient.isHit(fakeRectangle, targetRectangle); ++ if (isValid) { ++ mirrorSegments.push([startPoint, endPoint]); ++ debugGenerator$6.isDebug() && debugGenerator$6.drawPolygon(board, RectangleClient.getCornerPoints(fakeRectangle)); ++ } ++ } ++ return mirrorSegments; ++} ++const hasIllegalElbowPoint = (midDataPoints) => { ++ if (midDataPoints.length === 1) { ++ return true; ++ } ++ return midDataPoints.some((item, index) => { ++ const beforePoint = midDataPoints[index - 1]; ++ const afterPoint = midDataPoints[index + 1]; ++ const beforeSegment = beforePoint && [beforePoint, item]; ++ const afterSegment = afterPoint && [item, afterPoint]; ++ const isStraightWithBefore = beforeSegment && Point.isAlign(beforeSegment); ++ const isStraightWithAfter = afterSegment && Point.isAlign(afterSegment); ++ if (index === 0) { ++ return !isStraightWithAfter; ++ } ++ if (index === midDataPoints.length - 1) { ++ return !isStraightWithBefore; ++ } ++ return !isStraightWithBefore && !isStraightWithAfter; ++ }); ++}; ++ ++const ARROW_LENGTH = 20; ++const drawArrowLineArrow = (element, points, options) => { ++ const arrowG = createG(); ++ if (PlaitArrowLine.isSourceMark(element, ArrowLineMarkerType.none) && PlaitArrowLine.isTargetMark(element, ArrowLineMarkerType.none)) { ++ return null; ++ } ++ const strokeWidth = getStrokeWidthByElement(element); ++ const offset = (strokeWidth * strokeWidth) / 3; ++ if (points.length === 1) { ++ points = [points[0], [points[0][0] + 0.1, points[0][1]]]; ++ } ++ if (!PlaitArrowLine.isSourceMark(element, ArrowLineMarkerType.none)) { ++ const source = getExtendPoint(points[0], points[1], ARROW_LENGTH + offset); ++ const sourceArrow = getArrow(element, { marker: element.source.marker, source, target: points[0], isSource: true }, options); ++ sourceArrow && arrowG.appendChild(sourceArrow); ++ } ++ if (!PlaitArrowLine.isTargetMark(element, ArrowLineMarkerType.none)) { ++ const source = getExtendPoint(points[points.length - 1], points[points.length - 2], ARROW_LENGTH + offset); ++ const arrow = getArrow(element, { marker: element.target.marker, source, target: points[points.length - 1], isSource: false }, options); ++ arrow && arrowG.appendChild(arrow); ++ } ++ return arrowG; ++}; ++const getArrow = (element, arrowOptions, options) => { ++ const { marker, target, source, isSource } = arrowOptions; ++ let targetArrow; ++ switch (marker) { ++ case ArrowLineMarkerType.openTriangle: { ++ targetArrow = drawOpenTriangle(element, source, target, options); ++ break; ++ } ++ case ArrowLineMarkerType.solidTriangle: { ++ targetArrow = drawSolidTriangle(source, target, options); ++ break; ++ } ++ case ArrowLineMarkerType.arrow: { ++ targetArrow = drawArrow(element, source, target, options); ++ break; ++ } ++ case ArrowLineMarkerType.sharpArrow: { ++ targetArrow = drawSharpArrow(source, target, options); ++ break; ++ } ++ case ArrowLineMarkerType.oneSideUp: { ++ targetArrow = drawOneSideArrow(source, target, isSource ? 'down' : 'up', options); ++ break; ++ } ++ case ArrowLineMarkerType.oneSideDown: { ++ targetArrow = drawOneSideArrow(source, target, isSource ? 'up' : 'down', options); ++ break; ++ } ++ case ArrowLineMarkerType.hollowTriangle: { ++ targetArrow = drawHollowTriangleArrow(source, target, options); ++ break; ++ } ++ case ArrowLineMarkerType.singleSlash: { ++ targetArrow = drawSingleSlash(source, target, isSource, options); ++ break; ++ } ++ } ++ return targetArrow; ++}; ++const drawSharpArrow = (source, target, options) => { ++ const startPoint = target; ++ const { pointLeft, pointRight } = arrowPoints(source, target, 20); ++ const g = createG(); ++ const path = createPath(); ++ let polylinePath = `M${pointRight[0]},${pointRight[1]}A25,25,20,0,1,${pointLeft[0]},${pointLeft[1]}L${startPoint[0]},${startPoint[1]}Z`; ++ path.setAttribute('d', polylinePath); ++ path.setAttribute('stroke', `${options?.stroke}`); ++ path.setAttribute('stroke-width', `${options?.strokeWidth}`); ++ path.setAttribute('fill', `${options?.stroke}`); ++ g.appendChild(path); ++ return g; ++}; ++const drawArrow = (element, source, target, options) => { ++ const unitVector = getUnitVectorByPointAndPoint(source, target); ++ const strokeWidth = getStrokeWidthByElement(element); ++ const endPoint = [target[0] + (strokeWidth * unitVector[0]) / 2, target[1] + (strokeWidth * unitVector[1]) / 2]; ++ const distance = distanceBetweenPointAndPoint(...source, ...endPoint); ++ const middlePoint = [ ++ endPoint[0] - (((distance * 3) / 5 + strokeWidth) / 2) * unitVector[0], ++ endPoint[1] - (((distance * 3) / 5 + strokeWidth) / 2) * unitVector[1] ++ ]; ++ const { pointLeft, pointRight } = arrowPoints(source, endPoint, 30); ++ const arrowG = drawLinearPath([pointLeft, endPoint, pointRight, middlePoint], { ...options, fill: options.stroke }, true); ++ const path = arrowG.querySelector('path'); ++ path.setAttribute('stroke-linejoin', 'round'); ++ return arrowG; ++}; ++const drawSolidTriangle = (source, target, options) => { ++ const endPoint = target; ++ const { pointLeft, pointRight } = arrowPoints(source, endPoint, 30); ++ return drawLinearPath([pointLeft, endPoint, pointRight], { ...options, fill: options.stroke }, true); ++}; ++const drawOpenTriangle = (element, source, target, options) => { ++ const unitVector = getUnitVectorByPointAndPoint(source, target); ++ const strokeWidth = getStrokeWidthByElement(element); ++ const endPoint = [target[0] + (strokeWidth * unitVector[0]) / 2, target[1] + (strokeWidth * unitVector[1]) / 2]; ++ const { pointLeft, pointRight } = arrowPoints(source, endPoint, 40); ++ return drawLinearPath([pointLeft, endPoint, pointRight], options); ++}; ++const drawOneSideArrow = (source, target, side, options) => { ++ const { pointLeft, pointRight } = arrowPoints(source, target, 40); ++ return drawLinearPath([side === 'up' ? pointRight : pointLeft, target], options); ++}; ++const drawSingleSlash = (source, target, isSource, options) => { ++ const length = distanceBetweenPointAndPoint(...source, ...target); ++ const middlePoint = getExtendPoint(target, source, length / 2); ++ const angle = isSource ? 120 : 60; ++ const start = rotate(...source, ...middlePoint, (angle * Math.PI) / 180); ++ const end = rotate(...target, ...middlePoint, (angle * Math.PI) / 180); ++ return drawLinearPath([start, end], options); ++}; ++const drawHollowTriangleArrow = (source, target, options) => { ++ const { pointLeft, pointRight } = arrowPoints(source, target, 30); ++ return drawLinearPath([pointLeft, pointRight, target], { ...options, fill: 'white' }, true); ++}; ++ ++class ArrowLineShapeGenerator extends Generator { ++ canDraw(element) { ++ return true; ++ } ++ draw(element) { ++ let lineG; ++ lineG = drawArrowLine(this.board, element); ++ return lineG; ++ } ++} ++ ++const getTextSize = (board, text, maxWidth) => { ++ const textSize = getElementSize(board, text, { fontSize: DEFAULT_FONT_SIZE, fontFamily: DEFAULT_FONT_FAMILY }, maxWidth); ++ const normalizedTextSize = normalizeWidthAndHeight(textSize); ++ return normalizedTextSize; ++}; ++const normalizeWidthAndHeight = (textSize) => { ++ return { ...textSize, width: textSize.width < MIN_TEXT_WIDTH ? MIN_TEXT_WIDTH : textSize.width }; ++}; ++ ++const createArrowLineElement = (shape, points, source, target, texts, options) => { ++ return { ++ id: idCreator(), ++ type: 'arrow-line', ++ shape, ++ source, ++ texts: texts ? texts : [], ++ target, ++ opacity: 1, ++ points, ++ ...options ++ }; ++}; ++const getArrowLinePoints = (board, element) => { ++ switch (element.shape) { ++ case ArrowLineShape.elbow: { ++ return getElbowPoints(board, element); ++ } ++ case ArrowLineShape.curve: { ++ return getCurvePoints(board, element); ++ } ++ default: { ++ const points = PlaitArrowLine.getPoints(board, element); ++ const handleRefPair = getArrowLineHandleRefPair(board, element); ++ points[0] = handleRefPair.source.point; ++ points[points.length - 1] = handleRefPair.target.point; ++ return points; ++ } ++ } ++}; ++const getCurvePoints = (board, element) => { ++ if (element.points.length === 2) { ++ const handleRefPair = getArrowLineHandleRefPair(board, element); ++ const { source, target } = handleRefPair; ++ const sourceBoundElement = handleRefPair.source.boundElement; ++ const targetBoundElement = handleRefPair.target.boundElement; ++ let curvePoints = [source.point]; ++ const sumDistance = distanceBetweenPointAndPoint(...source.point, ...target.point); ++ const offset = 12 + sumDistance / 3; ++ if (sourceBoundElement) { ++ curvePoints.push(getPointByVectorComponent(source.point, source.vector, offset)); ++ } ++ if (targetBoundElement) { ++ curvePoints.push(getPointByVectorComponent(target.point, target.vector, offset)); ++ } ++ const isSingleBound = (sourceBoundElement && !targetBoundElement) || (!sourceBoundElement && targetBoundElement); ++ if (isSingleBound) { ++ curvePoints.push(target.point); ++ const points = Q2C(curvePoints); ++ return pointsOnBezierCurves(points); ++ } ++ if (!sourceBoundElement && !targetBoundElement) { ++ curvePoints.push(getPointByVectorComponent(source.point, source.vector, offset)); ++ curvePoints.push(getPointByVectorComponent(target.point, target.vector, offset)); ++ } ++ curvePoints.push(target.point); ++ return pointsOnBezierCurves(curvePoints); ++ } ++ else { ++ let dataPoints = PlaitArrowLine.getPoints(board, element); ++ dataPoints = removeDuplicatePoints(dataPoints); ++ const points = catmullRomFitting(dataPoints); ++ return pointsOnBezierCurves(points); ++ } ++}; ++const drawArrowLine = (board, element) => { ++ const strokeWidth = getStrokeWidthByElement(element); ++ const strokeColor = getStrokeColorByElement(board, element); ++ const strokeStyle = getStrokeStyleByElement(board, element); ++ const strokeLineDash = getStrokeLineDash(strokeStyle, strokeWidth); ++ const options = { stroke: strokeColor, strokeWidth, strokeLineDash }; ++ const lineG = createG(); ++ let points = getArrowLinePoints(board, element); ++ let line; ++ if (element.shape === ArrowLineShape.curve) { ++ line = PlaitBoard.getRoughSVG(board).curve(points, options); ++ } ++ else { ++ line = drawLinearPath(points, options); ++ } ++ const id = idCreator(); ++ line.setAttribute('mask', `url(#${id})`); ++ if (element.strokeStyle === StrokeStyle.dotted) { ++ setStrokeLinecap(line, 'round'); ++ } ++ lineG.appendChild(line); ++ const { mask, maskTargetFillRect } = drawArrowLineMask(board, element, id); ++ lineG.appendChild(mask); ++ line.appendChild(maskTargetFillRect); ++ const arrow = drawArrowLineArrow(element, points, { stroke: strokeColor, strokeWidth }); ++ arrow && lineG.appendChild(arrow); ++ return lineG; ++}; ++const getHitConnection = (board, point, hitElement) => { ++ const ref = getSnappingRef(board, hitElement, point); ++ const connectionPoint = ref.connectorPoint || ref.edgePoint; ++ return getHitConnectionFromConnectionPoint(connectionPoint, hitElement); ++}; ++const getHitConnectionFromConnectionPoint = (connectionPoint, hitElement) => { ++ let rectangle = RectangleClient.getRectangleByPoints(hitElement.points); ++ return [(connectionPoint[0] - rectangle.x) / rectangle.width, (connectionPoint[1] - rectangle.y) / rectangle.height]; ++}; ++const getHitConnectorPoint = (point, hitElement) => { ++ const rectangle = RectangleClient.getRectangleByPoints(hitElement.points); ++ const shape = getElementShape(hitElement); ++ const connectorPoints = getEngine(shape).getConnectorPoints(rectangle); ++ return connectorPoints.find((connectorPoint) => { ++ return distanceBetweenPointAndPoint(...connectorPoint, ...point) <= LINE_SNAPPING_CONNECTOR_BUFFER; ++ }); ++}; ++const getArrowLineTextRectangle = (board, element, index) => { ++ const text = element.texts[index]; ++ const elbowPoints = getArrowLinePoints(board, element); ++ const point = getPointOnPolyline(elbowPoints, text.position); ++ const textSize = getTextSize(board, text.text); ++ return { ++ x: point[0] - textSize.width / 2, ++ y: point[1] - textSize.height / 2, ++ width: textSize.width, ++ height: textSize.height ++ }; ++}; ++const getArrowLines = (board) => { ++ return findElements(board, { ++ match: (element) => PlaitDrawElement.isArrowLine(element), ++ recursion: (element) => PlaitDrawElement.isDrawElement(element) ++ }); ++}; ++// quadratic Bezier to cubic Bezier ++const Q2C = (points) => { ++ const result = []; ++ const numSegments = points.length / 3; ++ for (let i = 0; i < numSegments; i++) { ++ const start = points[i]; ++ const qControl = points[i + 1]; ++ const end = points[i + 2]; ++ const startDistance = distanceBetweenPointAndPoint(...start, ...qControl); ++ const endDistance = distanceBetweenPointAndPoint(...end, ...qControl); ++ const cControl1 = getExtendPoint(start, qControl, (startDistance * 2) / 3); ++ const cControl2 = getExtendPoint(end, qControl, (endDistance * 2) / 3); ++ result.push(start, cControl1, cControl2, end); ++ } ++ return result; ++}; ++const debugGenerator$5 = createDebugGenerator('debug:plait:arrow-line-resize'); ++const handleArrowLineCreating = (board, lineShape, sourcePoint, movingPoint, sourceElement, lineShapeG, options) => { ++ if (debugGenerator$5.isDebug()) { ++ debugGenerator$5.clear(); ++ debugGenerator$5.drawCircles(board, [sourcePoint], 3, false); ++ } ++ const alignedMovingPoint = alignPoint(sourcePoint, movingPoint); ++ const hitElement = getSnappingShape(board, alignedMovingPoint); ++ const targetConnection = hitElement ? getHitConnection(board, alignedMovingPoint, hitElement) : undefined; ++ const sourceConnection = sourceElement ? getHitConnection(board, sourcePoint, sourceElement) : undefined; ++ const targetBoundId = hitElement ? hitElement.id : undefined; ++ const lineGenerator = new ArrowLineShapeGenerator(board); ++ const memorizedLatest = getLineMemorizedLatest(); ++ let sourceMarker, targetMarker; ++ sourceMarker = memorizedLatest.source; ++ targetMarker = memorizedLatest.target; ++ sourceMarker && delete memorizedLatest.source; ++ targetMarker && delete memorizedLatest.target; ++ const temporaryLineElement = createArrowLineElement(lineShape, [sourcePoint, alignedMovingPoint], { marker: sourceMarker || ArrowLineMarkerType.none, connection: sourceConnection, boundId: sourceElement?.id }, { marker: targetMarker || ArrowLineMarkerType.arrow, connection: targetConnection, boundId: targetBoundId }, [], { ++ strokeWidth: DefaultLineStyle.strokeWidth, ++ ...memorizedLatest, ++ ...options ++ }); ++ const linePoints = getArrowLinePoints(board, temporaryLineElement); ++ const otherPoint = linePoints[0]; ++ temporaryLineElement.points[1] = alignPoint(otherPoint, alignedMovingPoint); ++ lineGenerator.processDrawing(temporaryLineElement, lineShapeG); ++ PlaitBoard.getElementTopHost(board).append(lineShapeG); ++ return temporaryLineElement; ++}; ++function drawArrowLineMask(board, element, id) { ++ const mask = createMask(); ++ mask.setAttribute('id', id); ++ const points = getArrowLinePoints(board, element); ++ let rectangle = RectangleClient.getRectangleByPoints(points); ++ rectangle = RectangleClient.getOutlineRectangle(rectangle, -30); ++ const maskFillRect = createRect(rectangle, { ++ fill: 'white' ++ }); ++ mask.appendChild(maskFillRect); ++ const texts = element.texts; ++ texts.forEach((text, index) => { ++ let textRectangle = getArrowLineTextRectangle(board, element, index); ++ textRectangle = RectangleClient.inflate(textRectangle, LINE_TEXT_SPACE * 2); ++ const rect = createRect(textRectangle, { ++ fill: 'black' ++ }); ++ mask.appendChild(rect); ++ }); ++ // open line ++ const maskTargetFillRect = createRect(rectangle); ++ maskTargetFillRect.setAttribute('opacity', '0'); ++ maskTargetFillRect.setAttribute('fill', 'none'); ++ return { mask, maskTargetFillRect }; ++} ++ ++const getHitArrowLineTextIndex = (board, element, point) => { ++ const texts = element.texts; ++ if (!texts.length) ++ return -1; ++ const points = getArrowLinePoints(board, element); ++ return texts.findIndex((text) => { ++ const center = getPointOnPolyline(points, text.position); ++ const textSize = getTextSize(board, text.text); ++ const rectangle = { ++ x: center[0] - textSize.width / 2, ++ y: center[1] - textSize.height / 2, ++ width: textSize.width, ++ height: textSize.height ++ }; ++ return RectangleClient.isHit(rectangle, RectangleClient.getRectangleByPoints([point, point])); ++ }); ++}; ++ ++const isMultipleTextShape = (shape) => { ++ return GEOMETRY_WITH_MULTIPLE_TEXT.includes(shape); ++}; ++const isMultipleTextGeometry = (geometry) => { ++ return PlaitDrawElement.isGeometry(geometry) && isMultipleTextShape(geometry.shape); ++}; ++const getMultipleTextGeometryTextKeys = (shape) => { ++ return MultipleTextGeometryTextKeys[shape]; ++}; ++const createMultipleTextGeometryElement = (shape, points, options = {}) => { ++ const id = idCreator(); ++ const drawShapeTexts = buildDefaultTextsByShape(shape); ++ return { ++ id, ++ type: 'geometry', ++ shape, ++ angle: 0, ++ opacity: 1, ++ texts: drawShapeTexts, ++ points, ++ ...options ++ }; ++}; ++const buildDefaultTextsByShape = (shape) => { ++ const memorizedLatest = getMemorizedLatestByPointer(shape); ++ const textProperties = { ...memorizedLatest.textProperties }; ++ const alignment = textProperties?.align; ++ delete textProperties?.align; ++ const defaultTexts = getDefaultGeometryProperty(shape)?.texts || []; ++ const textKeys = getMultipleTextGeometryTextKeys(shape); ++ return (textKeys || []).map((textKey) => { ++ const text = defaultTexts?.find((item) => item?.key === textKey); ++ return { ++ id: textKey, ++ text: buildText(text?.text || '', alignment || text?.align || Alignment.center, textProperties) ++ }; ++ }); ++}; ++const getHitMultipleGeometryText = (board, element, point) => { ++ const engine = getEngine(element.shape); ++ const rectangle = RectangleClient.getRectangleByPoints([point, point]); ++ let hitText; ++ if (engine.getTextRectangle) { ++ hitText = element.texts.find((text) => { ++ const textRectangle = engine.getTextRectangle(board, element, { id: text.id }); ++ return RectangleClient.isHit(rectangle, textRectangle); ++ }); ++ } ++ return hitText; ++}; ++ ++class VectorLineShapeGenerator extends Generator { ++ canDraw(element) { ++ return true; ++ } ++ draw(element) { ++ let lineG; ++ lineG = drawVectorLine(this.board, element); ++ return lineG; ++ } ++} ++ ++const getVectorLinePoints = (board, element) => { ++ switch (element.shape) { ++ case VectorLineShape.straight: { ++ return element.points; ++ } ++ case VectorLineShape.curve: { ++ if (element.points.length === 2) { ++ return pointsOnBezierCurves(element.points); ++ } ++ else { ++ let dataPoints = element.points; ++ const points = catmullRomFitting(dataPoints); ++ return pointsOnBezierCurves(points); ++ } ++ } ++ default: ++ return null; ++ } ++}; ++const createVectorLineElement = (shape, points, options) => { ++ return { ++ id: idCreator(), ++ type: 'vector-line', ++ shape, ++ opacity: 1, ++ points, ++ ...options ++ }; ++}; ++const vectorLineCreating = (board, lineShape, points, movingPoint, lineShapeG) => { ++ const lineGenerator = new VectorLineShapeGenerator(board); ++ const memorizedLatest = getLineMemorizedLatest(); ++ const temporaryLineElement = createVectorLineElement(lineShape, [...points, movingPoint], { ++ strokeWidth: DefaultLineStyle.strokeWidth, ++ ...memorizedLatest ++ }); ++ const otherPoint = points[points.length - 1]; ++ temporaryLineElement.points[temporaryLineElement.points.length - 1] = alignPoint(otherPoint, movingPoint); ++ lineGenerator.processDrawing(temporaryLineElement, lineShapeG); ++ PlaitBoard.getElementTopHost(board).append(lineShapeG); ++ return temporaryLineElement; ++}; ++const drawVectorLine = (board, element) => { ++ const strokeWidth = getStrokeWidthByElement(element); ++ const strokeColor = getStrokeColorByElement(board, element); ++ const strokeStyle = getStrokeStyleByElement(board, element); ++ const strokeLineDash = getStrokeLineDash(strokeStyle, strokeWidth); ++ const fill = getFillByElement(board, element); ++ const options = { stroke: strokeColor, strokeWidth, strokeLineDash, fill }; ++ const lineG = createG(); ++ let points = getVectorLinePoints(board, element); ++ const line = drawLinearPath(points, options); ++ const id = idCreator(); ++ line.setAttribute('mask', `url(#${id})`); ++ if (element.strokeStyle === StrokeStyle.dotted) { ++ setStrokeLinecap(line, 'round'); ++ } ++ lineG.appendChild(line); ++ return lineG; ++}; ++ ++const getCenterPointsOnPolygon$1 = (points) => { ++ const centerPoints = []; ++ for (let i = 0; i < points.length; i++) { ++ let j = i == points.length - 1 ? 0 : i + 1; ++ centerPoints.push([(points[i][0] + points[j][0]) / 2, (points[i][1] + points[j][1]) / 2]); ++ } ++ return centerPoints; ++}; ++const getCrossingPointBetweenPointAndPolygon = (corners, point) => { ++ const result = []; ++ for (let index = 1; index <= corners.length; index++) { ++ let start = corners[index - 1]; ++ let end = index === corners.length ? corners[0] : corners[index]; ++ const crossingPoint = getCrossingPointsBetweenPointAndSegment(point, start, end); ++ result.push(...crossingPoint); ++ } ++ return result; ++}; ++const getPolygonEdgeByConnectionPoint = (corners, point) => { ++ for (let index = 1; index <= corners.length; index++) { ++ let start = corners[index - 1]; ++ let end = index === corners.length ? corners[0] : corners[index]; ++ if (isPointOnSegment(point, start, end)) { ++ return [start, end]; ++ } ++ } ++ return null; ++}; ++ ++function generateCloudPath(rectangle) { ++ const divisionWidth = rectangle.width / 7; ++ const divisionHeight = rectangle.height / 3.2; ++ const xRadius = divisionWidth / 8.5; ++ const yRadius = divisionHeight / 20; ++ const startPoint = [rectangle.x + divisionWidth, rectangle.y + divisionHeight]; ++ const arcCommands = [ ++ { ++ rx: xRadius, ++ ry: yRadius * 1.2, ++ xAxisRotation: 0, ++ largeArcFlag: 1, ++ sweepFlag: 1, ++ endX: rectangle.x + divisionWidth * 2, ++ endY: rectangle.y + divisionHeight / 2 ++ }, ++ { ++ rx: xRadius, ++ ry: yRadius, ++ xAxisRotation: 0, ++ largeArcFlag: 1, ++ sweepFlag: 1, ++ endX: rectangle.x + divisionWidth * 4.2, ++ endY: rectangle.y + divisionHeight / 2.2 ++ }, ++ { ++ rx: xRadius, ++ ry: yRadius, ++ xAxisRotation: 0, ++ largeArcFlag: 1, ++ sweepFlag: 1, ++ endX: rectangle.x + divisionWidth * 5.8, ++ endY: rectangle.y + divisionHeight ++ }, ++ { ++ rx: xRadius, ++ ry: yRadius * 1.3, ++ xAxisRotation: 0, ++ largeArcFlag: 1, ++ sweepFlag: 1, ++ endX: rectangle.x + divisionWidth * 6, ++ endY: rectangle.y + divisionHeight * 2.2 ++ }, ++ { ++ rx: xRadius, ++ ry: yRadius * 1.2, ++ xAxisRotation: 0, ++ largeArcFlag: 1, ++ sweepFlag: 1, ++ endX: rectangle.x + divisionWidth * 5, ++ endY: rectangle.y + divisionHeight * 2.8 ++ }, ++ { ++ rx: xRadius, ++ ry: yRadius / 1.2, ++ xAxisRotation: 0, ++ largeArcFlag: 1, ++ sweepFlag: 1, ++ endX: rectangle.x + divisionWidth * 2.8, ++ endY: rectangle.y + divisionHeight * 2.8 ++ }, ++ { ++ rx: xRadius, ++ ry: yRadius, ++ xAxisRotation: 0, ++ largeArcFlag: 1, ++ sweepFlag: 1, ++ endX: rectangle.x + divisionWidth, ++ endY: rectangle.y + divisionHeight * 2.2 ++ }, ++ { ++ rx: xRadius, ++ ry: yRadius * 1.42, ++ xAxisRotation: 0, ++ largeArcFlag: 1, ++ sweepFlag: 1, ++ endX: rectangle.x + divisionWidth, ++ endY: rectangle.y + divisionHeight ++ } ++ ]; ++ return { startPoint, arcCommands }; ++} ++const CloudEngine = { ++ draw(board, rectangle, options) { ++ const rs = PlaitBoard.getRoughSVG(board); ++ const { startPoint, arcCommands } = generateCloudPath(rectangle); ++ const pathData = `M ${startPoint[0]} ${startPoint[1]} ` + ++ arcCommands ++ .map((command) => `A ${command.rx} ${command.ry} ${command.xAxisRotation} ${command.largeArcFlag} ${command.sweepFlag} ${command.endX} ${command.endY}`) ++ .join('\n') + ++ ' Z'; ++ const svgElement = rs.path(pathData, { ...options, fillStyle: 'solid' }); ++ setPathStrokeLinecap(svgElement, 'round'); ++ return svgElement; ++ }, ++ isInsidePoint(rectangle, point) { ++ const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]); ++ return RectangleClient.isHit(rectangle, rangeRectangle); ++ }, ++ getCornerPoints(rectangle) { ++ return RectangleClient.getCornerPoints(rectangle); ++ }, ++ getNearestPoint(rectangle, point) { ++ const { startPoint, arcCommands } = generateCloudPath(rectangle); ++ let minDistance = Infinity; ++ let nearestPoint = point; ++ let currentStart = startPoint; ++ for (const arcCommand of arcCommands) { ++ const arcNearestPoint = getNearestPointBetweenPointAndArc(point, currentStart, arcCommand); ++ const distance = distanceBetweenPointAndPoint(point[0], point[1], arcNearestPoint[0], arcNearestPoint[1]); ++ if (distance < minDistance) { ++ minDistance = distance; ++ nearestPoint = arcNearestPoint; ++ } ++ currentStart = [arcCommand.endX, arcCommand.endY]; ++ } ++ return nearestPoint; ++ }, ++ getEdgeByConnectionPoint(rectangle, pointOfRectangle) { ++ const corners = CloudEngine.getCornerPoints(rectangle); ++ const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle); ++ return getPolygonEdgeByConnectionPoint(corners, point); ++ }, ++ getConnectorPoints(rectangle) { ++ return RectangleClient.getEdgeCenterPoints(rectangle); ++ }, ++ getTextRectangle: (board, element) => { ++ const widthRatio = 1 / 1.5; ++ const rectangle = RectangleClient.getRectangleByPoints(element.points); ++ const textRectangle = getCustomTextRectangle(board, element, widthRatio); ++ return textRectangle; ++ } ++}; ++ ++const isHitArrowLineText = (board, element, point) => { ++ return getHitArrowLineTextIndex(board, element, point) !== -1; ++}; ++const isHitPolyLine = (pathPoints, point) => { ++ const distance = distanceBetweenPointAndSegments(point, pathPoints); ++ return distance <= HIT_DISTANCE_BUFFER; ++}; ++const isHitArrowLine = (board, element, point) => { ++ const points = getArrowLinePoints(board, element); ++ const isHitText = isHitArrowLineText(board, element, point); ++ return isHitText || isHitPolyLine(points, point); ++}; ++const isHitVectorLine = (board, element, point) => { ++ const points = getVectorLinePoints(board, element); ++ if (isClosedPoints(element.points)) { ++ return isPointInPolygon(point, points) || isHitPolyLine(points, point); ++ } ++ else { ++ return isHitPolyLine(points, point); ++ } ++}; ++const isRectangleHitElementText = (board, element, rectangle) => { ++ const engine = getEngine(element.shape); ++ if (isMultipleTextGeometry(element)) { ++ const texts = element.texts; ++ return texts.some((item) => { ++ const textClient = engine.getTextRectangle(board, element, { id: item.id }); ++ return isRectangleHitRotatedPoints(rectangle, RectangleClient.getCornerPoints(textClient), element.angle); ++ }); ++ } ++ else { ++ const textClient = engine.getTextRectangle ? engine.getTextRectangle(board, element) : getTextRectangle$1(board, element); ++ return isRectangleHitRotatedPoints(rectangle, RectangleClient.getCornerPoints(textClient), element.angle); ++ } ++}; ++const isHitElementText = (board, element, point) => { ++ const engine = getEngine(element.shape); ++ if (isMultipleTextGeometry(element)) { ++ const texts = element.texts; ++ return texts.some((item) => { ++ const textClient = engine.getTextRectangle(board, element, { id: item.id }); ++ return RectangleClient.isPointInRectangle(textClient, point); ++ }); ++ } ++ else { ++ const textClient = engine.getTextRectangle ? engine.getTextRectangle(board, element) : getTextRectangle$1(board, element); ++ return RectangleClient.isPointInRectangle(textClient, point); ++ } ++}; ++const isEmptyTextElement = (element) => { ++ if (!isDrawElementIncludeText(element)) { ++ return true; ++ } ++ const editor = getFirstTextEditor(element); ++ return Editor.isEmpty(editor, editor.children[0]); ++}; ++const isRectangleHitDrawElement = (board, element, selection) => { ++ const rangeRectangle = RectangleClient.getRectangleByPoints([selection.anchor, selection.focus]); ++ if (PlaitDrawElement.isGeometry(element)) { ++ const isHitElement = isRectangleHitRotatedElement(board, rangeRectangle, element); ++ if (isHitElement) { ++ return isHitElement; ++ } ++ return !isEmptyTextElement(element) && isRectangleHitElementText(board, element, rangeRectangle); ++ } ++ if (PlaitDrawElement.isImage(element)) { ++ return isRectangleHitRotatedElement(board, rangeRectangle, element); ++ } ++ if (PlaitDrawElement.isArrowLine(element)) { ++ const points = getArrowLinePoints(board, element); ++ return isLineHitRectangle(points, rangeRectangle); ++ } ++ if (PlaitDrawElement.isVectorLine(element)) { ++ const points = getVectorLinePoints(board, element); ++ return isLineHitRectangle(points, rangeRectangle); ++ } ++ return null; ++}; ++const isRectangleHitRotatedElement = (board, rectangle, element) => { ++ const client = RectangleClient.getRectangleByPoints(element.points); ++ return isRectangleHitRotatedPoints(rectangle, RectangleClient.getCornerPoints(client), element.angle); ++}; ++const isRectangleHitRotatedPoints = (rectangle, points, angle) => { ++ let rotatedPoints = rotatePointsByAngle(points, angle) || points; ++ return isLineHitRectangle(rotatedPoints, rectangle); ++}; ++const getHitDrawElement = (board, elements) => { ++ let firstFilledElement = getFirstFilledDrawElement(board, elements); ++ let endIndex = elements.length; ++ if (firstFilledElement) { ++ endIndex = elements.indexOf(firstFilledElement) + 1; ++ } ++ const newElements = elements.slice(0, endIndex); ++ const solidElements = getSolidElements(newElements); ++ if (solidElements) { ++ return solidElements[0]; ++ } ++ const sortElements = sortElementsByArea(board, newElements, 'asc'); ++ return sortElements[0]; ++}; ++const getFirstFilledDrawElement = (board, elements) => { ++ let filledElement = null; ++ for (let i = 0; i < elements.length; i++) { ++ const element = elements[i]; ++ if (isClosedCustomGeometry(board, element) || isClosedDrawElement(element)) { ++ const fill = getFillByElement(board, element); ++ if (isFilled(fill)) { ++ filledElement = element; ++ break; ++ } ++ } ++ } ++ return filledElement; ++}; ++const isFilledDrawElement = (board, element) => { ++ return getFirstFilledDrawElement(board, [element]) !== null; ++}; ++const getSolidElements = (elements) => { ++ const solidElements = elements.filter((item) => PlaitDrawElement.isText(item) || PlaitDrawElement.isLine(item) || PlaitDrawElement.isImage(item)); ++ if (solidElements.length) { ++ return solidElements; ++ } ++ return null; ++}; ++const debugKey$3 = 'debug:plait:hit:shape:edge:sample-points'; ++const debugGenerator$4 = createDebugGenerator(debugKey$3); ++const shapes = [BasicShapes.cloud]; ++const isHitDrawElement = (board, element, point, isStrict = true) => { ++ const rectangle = board.getRectangle(element); ++ point = rotateAntiPointsByElement(board, point, element) || point; ++ if (PlaitDrawElement.isGeometry(element) && rectangle) { ++ if (debugGenerator$4.isDebug() && shapes.includes(element.shape)) { ++ debugGenerator$4.clear(); ++ const { startPoint, arcCommands } = generateCloudPath(rectangle); ++ const points = [startPoint, ...arcCommands.map((arc) => [arc.endX, arc.endY])]; ++ debugGenerator$4.drawCircles(board, points, 5, false); ++ let minDistance = Infinity; ++ let nearestPoint = point; ++ let currentStart = startPoint; ++ for (const arc of arcCommands) { ++ const arcNearestPoint = getNearestPointBetweenPointAndArc(point, currentStart, arc); ++ const distance = distanceBetweenPointAndPoint(point[0], point[1], arcNearestPoint[0], arcNearestPoint[1]); ++ const { center } = getEllipseArcCenter(currentStart, arc); ++ debugGenerator$4.drawCircles(board, [center], 8, false, { fill: 'yellow' }); ++ if (distance < minDistance) { ++ minDistance = distance; ++ nearestPoint = arcNearestPoint; ++ } ++ currentStart = [arc.endX, arc.endY]; ++ } ++ debugGenerator$4.drawCircles(board, [point], 12, false, { fill: 'black', stroke: 'black' }); ++ debugGenerator$4.drawCircles(board, [nearestPoint], 12, false, { fill: 'green', stroke: 'green' }); ++ } ++ if (isHitEdgeOfShape(board, element, point, HIT_DISTANCE_BUFFER)) { ++ return true; ++ } ++ const engine = getEngine(getElementShape(element)); ++ if (PlaitDrawElement.isText(element)) { ++ const textClient = getTextRectangle$1(board, element); ++ return RectangleClient.isPointInRectangle(textClient, point); ++ } ++ if (!!isStrict && isEmptyTextElement(element) && !isFilledDrawElement(board, element)) { ++ return false; ++ } ++ const isHitText = isHitElementText(board, element, point); ++ return isHitText || engine.isInsidePoint(rectangle, point); ++ } ++ if (PlaitDrawElement.isImage(element)) { ++ const client = RectangleClient.getRectangleByPoints(element.points); ++ return RectangleClient.isPointInRectangle(client, point); ++ } ++ if (PlaitDrawElement.isArrowLine(element)) { ++ return isHitArrowLine(board, element, point); ++ } ++ if (PlaitDrawElement.isVectorLine(element)) { ++ return isHitVectorLine(board, element, point); ++ } ++ return null; ++}; ++const isHitEdgeOfShape = (board, element, point, hitDistanceBuffer) => { ++ const nearestPoint = getNearestPoint(element, point); ++ const distance = distanceBetweenPointAndPoint(nearestPoint[0], nearestPoint[1], point[0], point[1]); ++ return distance <= hitDistanceBuffer; ++}; ++const isInsideOfShape = (board, element, point, hitDistanceBuffer) => { ++ const client = RectangleClient.inflate(RectangleClient.getRectangleByPoints(element.points), hitDistanceBuffer); ++ return getEngine(getElementShape(element)).isInsidePoint(client, point); ++}; ++const isHitElementInside = (board, element, point) => { ++ const rectangle = board.getRectangle(element); ++ point = rotateAntiPointsByElement(board, point, element) || point; ++ if (PlaitDrawElement.isGeometry(element) && !PlaitDrawElement.isGeometryByTable(element)) { ++ const engine = getEngine(getElementShape(element)); ++ const isHitInside = engine.isInsidePoint(rectangle, point); ++ if (isHitInside) { ++ return isHitInside; ++ } ++ if (engine.getTextRectangle) { ++ const isHitText = isHitElementText(board, element, point); ++ if (isHitText) { ++ return isHitText; ++ } ++ } ++ } ++ if (PlaitDrawElement.isImage(element)) { ++ const client = RectangleClient.getRectangleByPoints(element.points); ++ return RectangleClient.isPointInRectangle(client, point); ++ } ++ if (PlaitDrawElement.isArrowLine(element)) { ++ return isHitArrowLine(board, element, point); ++ } ++ if (PlaitDrawElement.isVectorLine(element)) { ++ return isHitVectorLine(board, element, point); ++ } ++ return null; ++}; ++ ++const getTextRectangle$1 = (board, element) => { ++ const isAutoSize = PlaitDrawElement.isText(element) ? element.autoSize : false; ++ const elementRectangle = RectangleClient.getRectangleByPoints(element.points); ++ const strokeWidth = getStrokeWidthByElement(element); ++ const width = elementRectangle.width - ShapeDefaultSpace.rectangleAndText * 2 - strokeWidth * 2; ++ const textSize = getTextSize(board, element.text, isAutoSize ? GeometryThreshold.defaultTextMaxWidth : width); ++ if (isAutoSize) { ++ return { ++ height: textSize.height, ++ width: textSize.width, ++ x: elementRectangle.x + ShapeDefaultSpace.rectangleAndText + strokeWidth, ++ y: elementRectangle.y + (elementRectangle.height - textSize.height) / 2 ++ }; ++ } ++ return { ++ height: textSize.height, ++ width: width > 0 ? width : 0, ++ x: elementRectangle.x + ShapeDefaultSpace.rectangleAndText + strokeWidth, ++ y: elementRectangle.y + (elementRectangle.height - textSize.height) / 2 ++ }; ++}; ++const getCustomTextRectangle = (board, element, widthRatio) => { ++ const elementRectangle = RectangleClient.getRectangleByPoints(element.points); ++ const width = widthRatio * elementRectangle.width - ShapeDefaultSpace.rectangleAndText * 2 - getStrokeWidthByElement(element) * 2; ++ const textSize = getTextSize(board, element.text, width); ++ return { ++ height: textSize.height, ++ width: width, ++ x: elementRectangle.x + (elementRectangle.width - width) / 2, ++ y: elementRectangle.y + (elementRectangle.height - textSize.height) / 2 ++ }; ++}; ++const getStrokeWidthByElement = (element) => { ++ if (PlaitDrawElement.isText(element)) { ++ return 0; ++ } ++ const strokeWidth = element.strokeWidth || DefaultDrawStyle.strokeWidth; ++ return strokeWidth; ++}; ++const insertElement = (board, element) => { ++ memorizeLatestShape(board, element.shape); ++ Transforms.insertNode(board, element, [board.children.length]); ++ clearSelectedElement(board); ++ addSelectedElement(board, element); ++ BoardTransforms.updatePointerType(board, PlaitPointerType.selection); ++}; ++const isDrawElementIncludeText = (element) => { ++ if (PlaitDrawElement.isText(element)) { ++ return true; ++ } ++ if (PlaitDrawElement.isImage(element)) { ++ return false; ++ } ++ if (PlaitDrawElement.isGeometry(element)) { ++ return isGeometryIncludeText(element); ++ } ++ if (PlaitDrawElement.isArrowLine(element)) { ++ const editors = getTextEditorsByElement(element); ++ return editors.length > 0; ++ } ++ if (PlaitDrawElement.isElementByTable(element)) { ++ return element.cells.some((cell) => isCellIncludeText(cell)); ++ } ++ return true; ++}; ++const isDrawElementsIncludeText = (elements) => { ++ return elements.some((item) => { ++ return isDrawElementIncludeText(item); ++ }); ++}; ++const isClosedDrawElement = (element) => { ++ if (PlaitDrawElement.isDrawElement(element)) { ++ if (PlaitDrawElement.isText(element) || PlaitDrawElement.isArrowLine(element) || PlaitDrawElement.isImage(element)) { ++ return false; ++ } ++ if (PlaitDrawElement.isVectorLine(element)) { ++ return isClosedPoints(element.points); ++ } ++ if (PlaitDrawElement.isGeometry(element)) { ++ return isGeometryClosed(element); ++ } ++ return true; ++ } ++ return false; ++}; ++const isClosedCustomGeometry = (board, value) => { ++ return PlaitDrawElement.isCustomGeometryElement(board, value) && isClosedPoints(value.points); ++}; ++const getSnappingShape = (board, point) => { ++ let hitElement = getHitShape(board, point); ++ if (hitElement) { ++ const ref = getSnappingRef(board, hitElement, point); ++ if (ref.isHitConnector || ref.isHitEdge) { ++ return hitElement; ++ } ++ } ++ return null; ++}; ++const getSnappingRef = (board, hitElement, point) => { ++ const rotatedPoint = rotateAntiPointsByElement(board, point, hitElement) || point; ++ const connectorPoint = getHitConnectorPoint(rotatedPoint, hitElement); ++ const edgePoint = getNearestPoint(hitElement, rotatedPoint); ++ const isHitEdge = isHitEdgeOfShape(board, hitElement, rotatedPoint, LINE_SNAPPING_BUFFER); ++ return { isHitEdge, isHitConnector: !!connectorPoint, connectorPoint, edgePoint }; ++}; ++const getHitShape = (board, point, offset = LINE_HIT_GEOMETRY_BUFFER) => { ++ let hitShape = null; ++ traverseDrawShapes(board, (element) => { ++ if (hitShape === null && isInsideOfShape(board, element, rotateAntiPointsByElement(board, point, element) || point, offset * 2)) { ++ hitShape = element; ++ } ++ }); ++ return hitShape; ++}; ++const traverseDrawShapes = (board, callback) => { ++ depthFirstRecursion(board, (node) => { ++ if (!PlaitBoard.isBoard(node) && PlaitDrawElement.isShapeElement(node)) { ++ callback(node); ++ } ++ }, getIsRecursionFunc(board), true); ++}; ++const drawShape = (board, outerRectangle, shape, roughOptions, drawOptions) => { ++ return getEngine(shape).draw(board, outerRectangle, roughOptions, drawOptions); ++}; ++const drawBoundReaction = (board, element, roughOptions = { hasMask: true, hasConnector: true }) => { ++ const g = createG(); ++ const rectangle = RectangleClient.getRectangleByPoints(element.points); ++ const activeRectangle = RectangleClient.inflate(rectangle, SNAPPING_STROKE_WIDTH); ++ const shape = getElementShape(element); ++ let drawOptions; ++ if (PlaitDrawElement.isElementByTable(element)) { ++ drawOptions = { element }; ++ } ++ const strokeG = drawShape(board, activeRectangle, shape, { ++ stroke: SELECTION_BORDER_COLOR, ++ strokeWidth: SNAPPING_STROKE_WIDTH ++ }, drawOptions); ++ g.appendChild(strokeG); ++ if (roughOptions.hasMask) { ++ const maskG = drawShape(board, activeRectangle, shape, { ++ stroke: SELECTION_BORDER_COLOR, ++ strokeWidth: 0, ++ fill: isClosedDrawElement(element) ? SELECTION_FILL_COLOR : DefaultDrawStyle.fill, ++ fillStyle: 'solid' ++ }, drawOptions); ++ g.appendChild(maskG); ++ } ++ if (roughOptions.hasConnector) { ++ const connectorPoints = getEngine(shape).getConnectorPoints(rectangle); ++ connectorPoints.forEach((point) => { ++ const circleG = drawCircle(PlaitBoard.getRoughSVG(board), point, 8, { ++ stroke: SELECTION_BORDER_COLOR, ++ strokeWidth: ACTIVE_STROKE_WIDTH, ++ fill: '#FFF', ++ fillStyle: 'solid' ++ }); ++ g.appendChild(circleG); ++ }); ++ } ++ return g; ++}; ++const getTextKey = (element, text) => { ++ if (element && isMultipleTextGeometry(element)) { ++ return `${element.id}-${text.id}`; ++ } ++ else { ++ return text.id; ++ } ++}; ++const getGeometryAlign = (board, element) => { ++ if (isMultipleTextGeometry(element)) { ++ const drawShapeText = element.texts.find((item) => item.id.includes(GeometryCommonTextKeys.content)); ++ return drawShapeText?.text.align || Alignment.center; ++ } ++ if (isSingleTextGeometry(element)) { ++ return element.text?.align || Alignment.center; ++ } ++ if (PlaitDrawElement.isElementByTable(element)) { ++ const firstTextCell = element.cells.find((item) => item.text); ++ return firstTextCell?.text?.align || Alignment.center; ++ } ++ return Alignment.center; ++}; ++const isClosedPoints = (points) => { ++ const startPoint = points[0]; ++ const endPoint = points[points.length - 1]; ++ return startPoint[0] === endPoint[0] && startPoint[1] === endPoint[1]; ++}; ++const getDefaultGeometryText = (board) => { ++ return getI18nValue(board, DrawI18nKey.geometryText, DefaultTextProperty.text); ++}; ++ ++const getStrokeColorByElement = (board, element) => { ++ const defaultColor = getDrawDefaultStrokeColor(board.theme.themeColorMode); ++ const strokeColor = element.strokeColor || defaultColor; ++ return strokeColor; ++}; ++const getFillByElement = (board, element) => { ++ const defaultFill = PlaitDrawElement.isFlowchart(element) && isClosedDrawElement(element) ++ ? getFlowchartDefaultFill(board.theme.themeColorMode) ++ : DefaultDrawStyle.fill; ++ const fill = element.fill || defaultFill; ++ return fill; ++}; ++const getStrokeStyleByElement = (board, element) => { ++ return element.strokeStyle || StrokeStyle.solid; ++}; ++ ++class GeometryShapeGenerator extends Generator { ++ canDraw(element, data) { ++ return true; ++ } ++ draw(element, data) { ++ const rectangle = RectangleClient.getRectangleByPoints(element.points); ++ const shape = element.shape; ++ if (shape === BasicShapes.text) { ++ return; ++ } ++ const fill = getFillByElement(this.board, element); ++ const strokeWidth = getStrokeWidthByElement(element); ++ const strokeColor = getStrokeColorByElement(this.board, element); ++ const strokeStyle = getStrokeStyleByElement(this.board, element); ++ const strokeLineDash = getStrokeLineDash(strokeStyle, strokeWidth); ++ return drawGeometry(this.board, RectangleClient.inflate(rectangle, -strokeWidth), shape, { ++ stroke: strokeColor, ++ strokeWidth, ++ fill, ++ strokeLineDash ++ }); ++ } ++} ++ ++function getMiddlePoints(board, element) { ++ const result = []; ++ const shape = element.shape; ++ const hideBuffer = 10; ++ if (shape === ArrowLineShape.straight) { ++ const points = PlaitDrawElement.isArrowLine(element) ++ ? PlaitArrowLine.getPoints(board, element) ++ : element.points; ++ for (let i = 0; i < points.length - 1; i++) { ++ const distance = distanceBetweenPointAndPoint(...points[i], ...points[i + 1]); ++ if (distance < hideBuffer) ++ continue; ++ result.push([(points[i][0] + points[i + 1][0]) / 2, (points[i][1] + points[i + 1][1]) / 2]); ++ } ++ } ++ if (shape === ArrowLineShape.curve) { ++ const points = PlaitDrawElement.isArrowLine(element) ++ ? PlaitArrowLine.getPoints(board, element) ++ : element.points; ++ const pointsOnBezier = PlaitDrawElement.isArrowLine(element) ++ ? getCurvePoints(board, element) ++ : getVectorLinePoints(board, element); ++ if (points.length === 2) { ++ const start = 0; ++ const endIndex = pointsOnBezier.length - 1; ++ const middleIndex = Math.round((start + endIndex) / 2); ++ result.push(pointsOnBezier[middleIndex]); ++ } ++ else { ++ for (let i = 0; i < points.length - 1; i++) { ++ const startIndex = pointsOnBezier.findIndex(point => point[0] === points[i][0] && point[1] === points[i][1]); ++ const endIndex = pointsOnBezier.findIndex(point => point[0] === points[i + 1][0] && point[1] === points[i + 1][1]); ++ const middleIndex = Math.round((startIndex + endIndex) / 2); ++ const distance = distanceBetweenPointAndPoint(...points[i], ...points[i + 1]); ++ if (distance < hideBuffer) ++ continue; ++ result.push(pointsOnBezier[middleIndex]); ++ } ++ } ++ } ++ if (shape === ArrowLineShape.elbow) { ++ const renderPoints = getElbowPoints(board, element); ++ const options = getElbowLineRouteOptions(board, element); ++ if (!isUseDefaultOrthogonalRoute(element, options)) { ++ const [nextSourcePoint, nextTargetPoint] = getNextSourceAndTargetPoints(board, element); ++ for (let i = 0; i < renderPoints.length - 1; i++) { ++ if ((i == 0 && Point.isEquals(renderPoints[i + 1], nextSourcePoint)) || ++ (i === renderPoints.length - 2 && Point.isEquals(renderPoints[renderPoints.length - 2], nextTargetPoint))) { ++ continue; ++ } ++ const [currentX, currentY] = renderPoints[i]; ++ const [nextX, nextY] = renderPoints[i + 1]; ++ const middlePoint = [(currentX + nextX) / 2, (currentY + nextY) / 2]; ++ result.push(middlePoint); ++ } ++ } ++ } ++ return result; ++} ++ ++var LineResizeHandle; ++(function (LineResizeHandle) { ++ LineResizeHandle["source"] = "source"; ++ LineResizeHandle["target"] = "target"; ++ LineResizeHandle["addHandle"] = "addHandle"; ++})(LineResizeHandle || (LineResizeHandle = {})); ++const getHitLineResizeHandleRef = (board, element, point) => { ++ let dataPoints = PlaitDrawElement.isArrowLine(element) ? PlaitArrowLine.getPoints(board, element) : element.points; ++ const index = getHitPointIndex(dataPoints, point); ++ if (index !== -1) { ++ const handleIndex = index; ++ if (index === 0) { ++ return { handle: LineResizeHandle.source, handleIndex }; ++ } ++ if (index === dataPoints.length - 1) { ++ return { handle: LineResizeHandle.target, handleIndex }; ++ } ++ // elbow line, data points only verify source connection point and target connection point ++ if (element.shape !== ArrowLineShape.elbow) { ++ return { handleIndex }; ++ } ++ } ++ const middlePoints = getMiddlePoints(board, element); ++ const indexOfMiddlePoints = getHitPointIndex(middlePoints, point); ++ if (indexOfMiddlePoints !== -1) { ++ return { ++ handle: LineResizeHandle.addHandle, ++ handleIndex: indexOfMiddlePoints ++ }; ++ } ++ return undefined; ++}; ++function getHitPointIndex(points, movingPoint) { ++ const rectangles = points.map(point => { ++ return { ++ x: point[0] - RESIZE_HANDLE_DIAMETER / 2, ++ y: point[1] - RESIZE_HANDLE_DIAMETER / 2, ++ width: RESIZE_HANDLE_DIAMETER, ++ height: RESIZE_HANDLE_DIAMETER ++ }; ++ }); ++ const rectangle = rectangles.find(rectangle => { ++ return RectangleClient.isHit(RectangleClient.getRectangleByPoints([movingPoint, movingPoint]), rectangle); ++ }); ++ return rectangle ? rectangles.indexOf(rectangle) : -1; ++} ++ ++class LineActiveGenerator extends Generator { ++ constructor(board, options = { active: true }) { ++ super(board, options); ++ this.board = board; ++ this.onlySelectedCurrentLine = false; ++ } ++ canDraw(element, data) { ++ if (data.selected) { ++ return true; ++ } ++ else { ++ return false; ++ } ++ } ++ draw(element, data) { ++ const activeG = createG(); ++ const selectedElements = getSelectedElements(this.board); ++ this.onlySelectedCurrentLine = selectedElements.length === 1; ++ if (this.onlySelectedCurrentLine) { ++ activeG.classList.add(SELECTION_RECTANGLE_CLASS_NAME); ++ const points = PlaitDrawElement.isArrowLine(element) ? PlaitArrowLine.getPoints(this.board, element) : element.points; ++ let updatePoints = [...points]; ++ let elbowNextRenderPoints = []; ++ if (element.shape === ArrowLineShape.elbow) { ++ updatePoints = points.slice(0, 1).concat(points.slice(-1)); ++ elbowNextRenderPoints = getNextRenderPoints(this.board, element, data.linePoints); ++ } ++ const activePoints = updatePoints.map((point) => toActivePointFromViewBoxPoint(this.board, point)); ++ activePoints.forEach((point) => { ++ const updateHandle = drawPrimaryHandle(this.board, point); ++ activeG.appendChild(updateHandle); ++ }); ++ const middlePoints = getMiddlePoints(this.board, element); ++ const activeMiddlePoints = middlePoints.map((point) => toActivePointFromViewBoxPoint(this.board, point)); ++ if (!PlaitBoard.hasBeenTextEditing(this.board)) { ++ for (let i = 0; i < activeMiddlePoints.length; i++) { ++ const point = activeMiddlePoints[i]; ++ if (element.shape === ArrowLineShape.elbow && elbowNextRenderPoints.length) { ++ const handleIndex = getHitPointIndex(activeMiddlePoints, point); ++ const isUpdateHandleIndex = isUpdatedHandleIndex(this.board, element, [...points], elbowNextRenderPoints, handleIndex); ++ if (isUpdateHandleIndex) { ++ const updateHandle = drawPrimaryHandle(this.board, point); ++ activeG.appendChild(updateHandle); ++ continue; ++ } ++ } ++ const circle = drawFillPrimaryHandle(this.board, point); ++ activeG.appendChild(circle); ++ } ++ } ++ } ++ else { ++ const rectangle = this.board.getRectangle(element); ++ if (rectangle) { ++ const activeRectangle = toActiveRectangleFromViewBoxRectangle(this.board, rectangle); ++ let opacity = '0.5'; ++ if (activeRectangle.height === 0 || activeRectangle.width === 0) { ++ opacity = '0.8'; ++ } ++ const strokeG = drawRectangle(this.board, activeRectangle, { ++ stroke: PRIMARY_COLOR, ++ strokeWidth: DefaultDrawActiveStyle.selectionStrokeWidth ++ }); ++ strokeG.style.opacity = opacity; ++ activeG.appendChild(strokeG); ++ } ++ } ++ return activeG; ++ } ++ needUpdate() { ++ const selectedElements = getSelectedElements(this.board); ++ const onlySelectedCurrentLine = selectedElements.length === 1; ++ return onlySelectedCurrentLine !== this.onlySelectedCurrentLine; ++ } ++} ++ ++class ArrowLineAutoCompleteGenerator extends Generator { ++ static { this.key = 'line-auto-complete-generator'; } ++ constructor(board) { ++ super(board, { active: true }); ++ this.board = board; ++ this.hoverElement = null; ++ } ++ canDraw(element, data) { ++ const selectedElements = getSelectedElements(this.board); ++ if (data.selected && selectedElements.length === 1 && !isSelectionMoving(this.board)) { ++ return true; ++ } ++ else { ++ return false; ++ } ++ } ++ draw(element, data) { ++ this.autoCompleteG = createG(); ++ const middlePoints = getAutoCompletePoints(this.board, element, true); ++ middlePoints.forEach((point, index) => { ++ const circle = drawCircle(PlaitBoard.getRoughSVG(this.board), point, LINE_AUTO_COMPLETE_DIAMETER, { ++ stroke: 'none', ++ fill: rgbaToHEX(PRIMARY_COLOR, LINE_AUTO_COMPLETE_OPACITY), ++ fillStyle: 'solid' ++ }); ++ circle.classList.add(`line-auto-complete-${index}`); ++ this.autoCompleteG.appendChild(circle); ++ }); ++ return this.autoCompleteG; ++ } ++ removeAutoCompleteG(index) { ++ this.hoverElement = this.autoCompleteG.querySelector(`.line-auto-complete-${index}`); ++ this.hoverElement.style.visibility = 'hidden'; ++ } ++ recoverAutoCompleteG() { ++ if (this.hoverElement) { ++ this.hoverElement.style.visibility = 'visible'; ++ this.hoverElement = null; ++ } ++ } ++} ++ ++class SingleTextGenerator extends TextGenerator { ++ get textManage() { ++ return this.textManages[0]; ++ } ++ constructor(board, element, text, options) { ++ super(board, element, [{ id: element.id, text: text }], options); ++ } ++ update(element, previousText, currentText, elementG) { ++ if (!isMultipleTextGeometry(element)) { ++ super.update(element, [{ text: previousText, id: element.id }], [{ text: currentText, id: element.id }], elementG); ++ } ++ } ++} ++ ++class TableGenerator extends Generator { ++ canDraw(element, data) { ++ return true; ++ } ++ draw(element, data) { ++ const rectangle = RectangleClient.getRectangleByPoints(element.points); ++ const strokeWidth = getStrokeWidthByElement(element); ++ const strokeColor = getStrokeColorByElement(this.board, element); ++ const strokeStyle = getStrokeStyleByElement(this.board, element); ++ const strokeLineDash = getStrokeLineDash(strokeStyle, strokeWidth); ++ return getEngine(TableSymbols.table).draw(this.board, rectangle, { ++ strokeWidth, ++ stroke: strokeColor, ++ strokeLineDash ++ }, { ++ element: element ++ }); ++ } ++} ++ ++const getElementShape = (value) => { ++ if (PlaitDrawElement.isImage(value)) { ++ return BasicShapes.rectangle; ++ } ++ if (PlaitDrawElement.isTable(value)) { ++ return TableSymbols.table; ++ } ++ return value.shape; ++}; ++const getGeometryGeneratorByShape = (board, shape) => { ++ if (PlaitDrawElement.isUMLClassOrInterface({ shape: shape })) { ++ return new TableGenerator(board); ++ } ++ else { ++ return new GeometryShapeGenerator(board); ++ } ++}; ++ ++const createUMLClassOrInterfaceGeometryElement = (board, shape, points) => { ++ const memorizedLatest = getMemorizedLatestByPointer(shape); ++ const element = { ++ id: idCreator(), ++ type: 'geometry', ++ angle: 0, ++ opacity: 1, ++ points, ++ strokeWidth: DefaultBasicShapeProperty.strokeWidth, ++ ...memorizedLatest.geometryProperties ++ }; ++ let rows; ++ let columns; ++ if (shape === UMLSymbols.class) { ++ rows = [ ++ { ++ id: idCreator(), ++ height: 30 ++ }, ++ { ++ id: idCreator() ++ }, ++ { ++ id: idCreator() ++ } ++ ]; ++ columns = [ ++ { ++ id: idCreator() ++ } ++ ]; ++ } ++ else { ++ rows = [ ++ { ++ id: idCreator(), ++ height: 50 ++ }, ++ { ++ id: idCreator() ++ } ++ ]; ++ columns = [ ++ { ++ id: idCreator() ++ } ++ ]; ++ } ++ return { ++ ...element, ++ shape, ++ rows, ++ columns, ++ cells: buildTableCellsForGeometry(board, rows, columns, shape) ++ }; ++}; ++const buildTableCellsForGeometry = (board, rows, columns, shape) => { ++ const cellCount = rows.length * columns.length; ++ const defaultTexts = getDefaultGeometryProperty(shape)?.texts || []; ++ return new Array(cellCount).fill('').map((item, index) => { ++ const rowIndex = Math.floor(index / columns.length); ++ const columnIndex = index % columns.length; ++ return { ++ id: idCreator(), ++ rowId: rows[rowIndex].id, ++ columnId: columns[columnIndex].id, ++ text: { ++ children: [ ++ { ++ text: defaultTexts[index].text ++ } ++ ], ++ align: defaultTexts[index].align ++ } ++ }; ++ }); ++}; ++ ++const createGeometryElement = (shape, points, text, options = {}, textProperties = {}) => { ++ if (GEOMETRY_WITHOUT_TEXT.includes(shape)) { ++ return createGeometryElementWithoutText(shape, points, options); ++ } ++ else { ++ return createGeometryElementWithText(shape, points, text, options, textProperties); ++ } ++}; ++const createGeometryElementWithText = (shape, points, text, options = {}, textProperties = {}) => { ++ let textOptions = {}; ++ let alignment = Alignment.center; ++ if (shape === BasicShapes.text) { ++ textOptions = { autoSize: true }; ++ alignment = undefined; ++ } ++ textProperties = { ...textProperties }; ++ textProperties?.align && (alignment = textProperties?.align); ++ delete textProperties?.align; ++ return { ++ id: idCreator(), ++ type: 'geometry', ++ shape, ++ angle: 0, ++ opacity: 1, ++ text: buildText(text, alignment, textProperties), ++ points, ++ ...textOptions, ++ ...options ++ }; ++}; ++const createGeometryElementWithoutText = (shape, points, options = {}) => { ++ return { ++ id: idCreator(), ++ type: 'geometry', ++ shape, ++ angle: 0, ++ opacity: 1, ++ points, ++ ...options ++ }; ++}; ++const drawGeometry = (board, outerRectangle, shape, roughOptions) => { ++ return getEngine(shape).draw(board, outerRectangle, roughOptions); ++}; ++const getNearestPoint = (element, point) => { ++ const rectangle = RectangleClient.getRectangleByPoints(element.points); ++ const shape = getElementShape(element); ++ return getEngine(shape).getNearestPoint(rectangle, point); ++}; ++const getCenterPointsOnPolygon = (points) => { ++ const centerPoint = []; ++ for (let i = 0; i < points.length; i++) { ++ let j = i == points.length - 1 ? 0 : i + 1; ++ centerPoint.push([(points[i][0] + points[j][0]) / 2, (points[i][1] + points[j][1]) / 2]); ++ } ++ return centerPoint; ++}; ++const getDefaultFlowchartProperty = (symbol) => { ++ return DefaultFlowchartPropertyMap[symbol]; ++}; ++const getDefaultBasicShapeProperty = (shape) => { ++ return DefaultBasicShapePropertyMap[shape] || DefaultBasicShapeProperty; ++}; ++const getDefaultUMLProperty = (shape) => { ++ return DefaultUMLPropertyMap[shape]; ++}; ++const getAutoCompletePoints = (board, element, isToActive = false) => { ++ const AutoCompleteMargin = (12 + RESIZE_HANDLE_DIAMETER / 2) * 2; ++ const rectangle = RectangleClient.getRectangleByPoints(element.points); ++ const activeRectangle = toActiveRectangleFromViewBoxRectangle(board, rectangle); ++ const targetRectangle = isToActive ? activeRectangle : rectangle; ++ return RectangleClient.getEdgeCenterPoints(RectangleClient.inflate(targetRectangle, AutoCompleteMargin)); ++}; ++const getHitIndexOfAutoCompletePoint = (movingPoint, points) => { ++ return points.findIndex((point) => { ++ const movingRectangle = RectangleClient.getRectangleByPoints([movingPoint]); ++ let rectangle = RectangleClient.getRectangleByPoints([point]); ++ rectangle = RectangleClient.inflate(rectangle, RESIZE_HANDLE_DIAMETER); ++ return RectangleClient.isHit(movingRectangle, rectangle); ++ }); ++}; ++const getDrawDefaultStrokeColor = (theme) => { ++ return DrawThemeColors[theme].strokeColor; ++}; ++const getFlowchartDefaultFill = (theme) => { ++ return DrawThemeColors[theme].fill; ++}; ++const getTextShapeProperty = (board, text, fontSize) => { ++ fontSize = fontSize ? Number(fontSize) : DEFAULT_FONT_SIZE; ++ const textSize = measureElement(board, buildText(text), { fontSize, fontFamily: DEFAULT_FONT_FAMILY }); ++ return { ++ width: textSize.width + ShapeDefaultSpace.rectangleAndText * 2, ++ height: textSize.height ++ }; ++}; ++const getDefaultGeometryPoints = (pointer, centerPoint) => { ++ const property = getDefaultGeometryProperty(pointer); ++ return RectangleClient.getPoints(RectangleClient.getRectangleByCenterPoint(centerPoint, property.width, property.height)); ++}; ++const getDefaultGeometryProperty = (pointer) => { ++ const isFlowChart = getFlowchartPointers().includes(pointer); ++ const isUML = getUMLPointers().includes(pointer); ++ if (isFlowChart) { ++ return getDefaultFlowchartProperty(pointer); ++ } ++ else if (isUML) { ++ return getDefaultUMLProperty(pointer); ++ } ++ else { ++ return getDefaultBasicShapeProperty(pointer); ++ } ++}; ++const getDefaultTextPoints = (board, centerPoint, fontSize) => { ++ const property = getTextShapeProperty(board, DefaultTextProperty.text, fontSize); ++ return RectangleClient.getPoints(RectangleClient.getRectangleByCenterPoint(centerPoint, property.width, property.height)); ++}; ++const createTextElement = (board, points, text) => { ++ const memorizedLatest = getMemorizedLatestByPointer(BasicShapes.text); ++ return createGeometryElement(BasicShapes.text, points, text, memorizedLatest.geometryProperties, { ++ ...memorizedLatest.textProperties ++ }); ++}; ++const createDefaultGeometry = (board, points, shape) => { ++ const memorizedLatest = getMemorizedLatestByPointer(shape); ++ if (PlaitDrawElement.isUMLClassOrInterface({ shape })) { ++ return createUMLClassOrInterfaceGeometryElement(board, shape, points); ++ } ++ if (isMultipleTextShape(shape)) { ++ return createMultipleTextGeometryElement(shape, points, { ++ strokeWidth: DefaultBasicShapeProperty.strokeWidth, ++ ...memorizedLatest.geometryProperties ++ }); ++ } ++ else { ++ return createGeometryElement(shape, points, '', { ++ strokeWidth: DefaultBasicShapeProperty.strokeWidth, ++ ...memorizedLatest.geometryProperties ++ }, { ...memorizedLatest.textProperties }); ++ } ++}; ++const editText = (board, element, text) => { ++ const textManage = text ? getTextManage(board, element, text) : getFirstTextManage(element); ++ if (textManage) { ++ textManage.edit(() => { }); ++ } ++}; ++const isGeometryIncludeText = (element) => { ++ return isSingleTextGeometry(element) || isMultipleTextGeometry(element); ++}; ++const isSingleTextShape = (shape) => { ++ return !GEOMETRY_WITHOUT_TEXT.includes(shape) && !isMultipleTextShape(shape); ++}; ++const isSingleTextGeometry = (element) => { ++ return PlaitDrawElement.isGeometry(element) && isSingleTextShape(element.shape); ++}; ++const isGeometryClosed = (element) => { ++ return !GEOMETRY_NOT_CLOSED.includes(element.shape); ++}; ++ ++const isSelfLoop = (element) => { ++ return element.source.boundId && element.source.boundId === element.target.boundId; ++}; ++const isUseDefaultOrthogonalRoute = (element, options) => { ++ return isSourceAndTargetIntersect(options) && !isSelfLoop(element); ++}; ++const getElbowPoints = (board, element) => { ++ const handleRefPair = getArrowLineHandleRefPair(board, element); ++ const params = getElbowLineRouteOptions(board, element, handleRefPair); ++ // console.log(params, 'params'); ++ if (isUseDefaultOrthogonalRoute(element, params)) { ++ return simplifyOrthogonalPoints(getPoints(handleRefPair.source.point, handleRefPair.source.direction, handleRefPair.target.point, handleRefPair.target.direction, DEFAULT_ROUTE_MARGIN)); ++ } ++ const keyPoints = removeDuplicatePoints(generateElbowLineRoute(params, board)); ++ const nextKeyPoints = keyPoints.slice(1, keyPoints.length - 1); ++ if (element.points.length === 2) { ++ return simplifyOrthogonalPoints(keyPoints); ++ } ++ else { ++ const simplifiedNextKeyPoints = simplifyOrthogonalPoints(nextKeyPoints); ++ const dataPoints = removeDuplicatePoints(PlaitArrowLine.getPoints(board, element)); ++ const midDataPoints = dataPoints.slice(1, -1); ++ if (hasIllegalElbowPoint(midDataPoints)) { ++ return simplifyOrthogonalPoints(keyPoints); ++ } ++ const nextDataPoints = [simplifiedNextKeyPoints[0], ...midDataPoints, simplifiedNextKeyPoints[simplifiedNextKeyPoints.length - 1]]; ++ const mirrorDataPoints = getMirrorDataPoints(board, nextDataPoints, simplifiedNextKeyPoints, params); ++ // console.log(mirrorDataPoints, 'mirrorDataPoints'); ++ const renderPoints = [keyPoints[0]]; ++ for (let index = 0; index < mirrorDataPoints.length - 1; index++) { ++ let currentPoint = mirrorDataPoints[index]; ++ let nextPoint = mirrorDataPoints[index + 1]; ++ const isStraight = Point.isAlign([currentPoint, nextPoint]); ++ if (!isStraight) { ++ const midKeyPoints = getMidKeyPoints(simplifiedNextKeyPoints, currentPoint, nextPoint); ++ if (midKeyPoints.length) { ++ renderPoints.push(currentPoint); ++ renderPoints.push(...midKeyPoints); ++ } ++ else { ++ renderPoints.push(currentPoint); ++ console.log('unknown data points'); ++ } ++ } ++ else { ++ renderPoints.push(currentPoint); ++ } ++ } ++ renderPoints.push(keyPoints[keyPoints.length - 2], keyPoints[keyPoints.length - 1]); ++ // Remove the middle point to avoid the situation where the starting and ending positions are drawn back, such as when sourcePoint is between nextSourcePoint and the first key point. ++ // Issue ++ // keyPoint2 ++ // | ++ // | ++ // nextPoint---sourcePoint---keyPoint1 ++ // The correct rendering should be (nextPoint should be filtered out): ++ // keyPoint2 ++ // | ++ // | ++ // sourcePoint---keyPoint1 ++ const ret = simplifyOrthogonalPoints(renderPoints); ++ return ret; ++ } ++}; ++const getNextSourceAndTargetPoints = (board, element) => { ++ const options = getElbowLineRouteOptions(board, element); ++ return [options.nextSourcePoint, options.nextTargetPoint]; ++}; ++const getSourceAndTargetRectangle = (board, element, handleRefPair) => { ++ let sourceElement = element.source.boundId ? getElementById(board, element.source.boundId) : undefined; ++ let targetElement = element.target.boundId ? getElementById(board, element.target.boundId) : undefined; ++ if (!sourceElement) { ++ const source = handleRefPair.source; ++ sourceElement = createFakeElement(source.point, source.vector); ++ } ++ if (!targetElement) { ++ const target = handleRefPair.target; ++ targetElement = createFakeElement(target.point, target.vector); ++ } ++ let sourceRectangle = RectangleClient.getRectangleByPoints(sourceElement.points); ++ const rotatedSourceCornerPoints = rotatePointsByElement(RectangleClient.getCornerPoints(sourceRectangle), sourceElement) || ++ RectangleClient.getCornerPoints(sourceRectangle); ++ sourceRectangle = RectangleClient.getRectangleByPoints(rotatedSourceCornerPoints); ++ sourceRectangle = RectangleClient.inflate(sourceRectangle, getStrokeWidthByElement(sourceElement) * 2); ++ let targetRectangle = RectangleClient.getRectangleByPoints(targetElement.points); ++ const rotatedTargetCornerPoints = rotatePointsByElement(RectangleClient.getCornerPoints(targetRectangle), targetElement) || ++ RectangleClient.getCornerPoints(targetRectangle); ++ targetRectangle = RectangleClient.getRectangleByPoints(rotatedTargetCornerPoints); ++ targetRectangle = RectangleClient.inflate(targetRectangle, getStrokeWidthByElement(targetElement) * 2); ++ return { ++ sourceRectangle, ++ targetRectangle ++ }; ++}; ++const createFakeElement = (startPoint, vector) => { ++ const point = getPointByVectorComponent(startPoint, vector, -25); ++ const points = RectangleClient.getPoints(RectangleClient.getRectangleByCenterPoint(point, 50, 50)); ++ return createGeometryElement(BasicShapes.rectangle, points, ''); ++}; ++function getNextRenderPoints(board, element, renderPoints) { ++ let newRenderKeyPoints = renderPoints ?? getElbowPoints(board, element); ++ const [nextSourcePoint, nextTargetPoint] = getNextSourceAndTargetPoints(board, element); ++ newRenderKeyPoints.splice(0, 1, nextSourcePoint); ++ newRenderKeyPoints.splice(-1, 1, nextTargetPoint); ++ return removeDuplicatePoints(newRenderKeyPoints); ++} ++ ++const getSelectedDrawElements = (board, elements) => { ++ const selectedElements = elements?.length ? elements : getSelectedElements(board); ++ return selectedElements.filter(value => PlaitDrawElement.isDrawElement(value)); ++}; ++const getSelectedGeometryElements = (board) => { ++ const selectedElements = getSelectedElements(board).filter(value => PlaitDrawElement.isGeometry(value)); ++ return selectedElements; ++}; ++const getSelectedCustomGeometryElements = (board) => { ++ const selectedElements = getSelectedElements(board).filter(value => PlaitDrawElement.isCustomGeometryElement(board, value)); ++ return selectedElements; ++}; ++const getSelectedArrowLineElements = (board) => { ++ const selectedElements = getSelectedElements(board).filter(value => PlaitDrawElement.isArrowLine(value)); ++ return selectedElements; ++}; ++const getSelectedVectorLineElements = (board) => { ++ const selectedElements = getSelectedElements(board).filter(value => PlaitDrawElement.isVectorLine(value)); ++ return selectedElements; ++}; ++const getSelectedImageElements = (board) => { ++ const selectedElements = getSelectedElements(board).filter(value => PlaitDrawElement.isImage(value)); ++ return selectedElements; ++}; ++const isSingleSelectSwimlane = (board) => { ++ const selectedElements = getSelectedElements(board); ++ return selectedElements && selectedElements.length === 1 && PlaitDrawElement.isSwimlane(selectedElements[0]); ++}; ++const isSingleSelectLine = (board) => { ++ const selectedElements = getSelectedElements(board); ++ return selectedElements && selectedElements.length === 1 && PlaitDrawElement.isLine(selectedElements[0]); ++}; ++const getSelectedSwimlane = (board) => { ++ const selectedElements = getSelectedElements(board); ++ return selectedElements.find(item => PlaitDrawElement.isSwimlane(item)); ++}; ++ ++const insertGeometry = (board, points, shape) => { ++ const newElement = createDefaultGeometry(board, points, shape); ++ insertElement(board, newElement); ++ return newElement; ++}; ++const insertText = (board, point, text) => { ++ const memorizedLatest = getMemorizedLatestByPointer(BasicShapes.text); ++ const property = getTextShapeProperty(board, text, memorizedLatest.textProperties['font-size']); ++ const points = [point, [point[0] + property.width, point[1] + property.height]]; ++ const newElement = createTextElement(board, points, text); ++ insertElement(board, newElement); ++}; ++const resizeGeometry = (board, points, path) => { ++ const normalizePoints = normalizeShapePoints(points); ++ const element = PlaitNode.get(board, path); ++ const newProperties = { points: normalizePoints, text: { ...element.text } }; ++ if (PlaitDrawElement.isText(element) && element.autoSize) { ++ newProperties.autoSize = false; ++ } ++ Transforms.setNode(board, newProperties, path); ++}; ++const switchGeometryShape = (board, shape) => { ++ const selectedElements = getSelectedElements(board); ++ const refs = []; ++ selectedElements.forEach((item) => { ++ if (PlaitDrawElement.isGeometry(item) && !PlaitDrawElement.isText(item)) { ++ const path = PlaitBoard.findPath(board, item); ++ Transforms.setNode(board, { shape }, path); ++ collectArrowLineUpdatedRefsByGeometry(board, { ...item, shape }, refs); ++ } ++ }); ++ if (refs.length) { ++ refs.forEach((ref) => { ++ DrawTransforms.resizeArrowLine(board, ref.property, ref.path); ++ }); ++ } ++}; ++ ++const normalizePoints = (board, element, width, height) => { ++ let points = element.points; ++ let autoSize = PlaitDrawElement.isText(element) ? element.autoSize : false; ++ const defaultSpace = ShapeDefaultSpace.rectangleAndText; ++ if (autoSize) { ++ const newWidth = width < MIN_TEXT_WIDTH ? MIN_TEXT_WIDTH : width; ++ const editor = getFirstTextEditor(element); ++ if (AlignEditor.isActive(editor, Alignment.right)) { ++ points = [ ++ [points[1][0] - (newWidth + defaultSpace * 2), points[0][1]], ++ [points[1][0], points[0][1] + height] ++ ]; ++ } ++ else if (AlignEditor.isActive(editor, Alignment.center)) { ++ const oldWidth = Math.abs(points[0][0] - points[1][0]); ++ const offset = (newWidth - oldWidth) / 2; ++ points = [ ++ [points[0][0] - offset - defaultSpace, points[0][1]], ++ [points[1][0] + offset + defaultSpace, points[0][1] + height] ++ ]; ++ } ++ else { ++ points = [points[0], [points[0][0] + newWidth + defaultSpace * 2, points[0][1] + height]]; ++ } ++ if (hasValidAngle(element)) { ++ points = resetPointsAfterResize(RectangleClient.getRectangleByPoints(element.points), RectangleClient.getRectangleByPoints(points), RectangleClient.getCenterPoint(RectangleClient.getRectangleByPoints(element.points)), RectangleClient.getCenterPoint(RectangleClient.getRectangleByPoints(points)), element.angle); ++ } ++ } ++ return { points }; ++}; ++const setText = (board, element, text, width, height) => { ++ const newElement = { ++ text, ++ ...normalizePoints(board, element, width, height) ++ }; ++ const path = board.children.findIndex((child) => child === element); ++ Transforms.setNode(board, newElement, [path]); ++}; ++const setTextSize = (board, element, width, height) => { ++ const isAutoSize = PlaitDrawElement.isText(element) ? element.autoSize : false; ++ if (isAutoSize) { ++ const newElement = { ++ ...normalizePoints(board, element, width, height) ++ }; ++ const path = board.children.findIndex((child) => child === element); ++ Transforms.setNode(board, newElement, [path]); ++ } ++}; ++ ++const insertImage = (board, imageItem, startPoint) => { ++ const { width, height, url } = imageItem; ++ const viewportWidth = PlaitBoard.getBoardContainer(board).clientWidth; ++ const viewportHeight = PlaitBoard.getBoardContainer(board).clientHeight; ++ const point = toViewBoxPoint(board, toHostPoint(board, viewportWidth / 2, viewportHeight / 2)); ++ const points = startPoint ++ ? [startPoint, [startPoint[0] + width, startPoint[1] + height]] ++ : [ ++ [point[0] - width / 2, point[1] - height / 2], ++ [point[0] + width / 2, point[1] + height / 2] ++ ]; ++ const imageElement = { ++ id: idCreator(), ++ type: 'image', ++ points, ++ url ++ }; ++ Transforms.insertNode(board, imageElement, [board.children.length]); ++ Transforms.addSelectionWithTemporaryElements(board, [imageElement]); ++}; ++const createImage = (startPoint, imageItem) => { ++ const { width, height, url } = imageItem; ++ const points = [startPoint, [startPoint[0] + width, startPoint[1] + height]]; ++ const imageElement = { ++ id: idCreator(), ++ type: 'image', ++ points, ++ url ++ }; ++ return imageElement; ++}; ++ ++const resizeArrowLine = (board, options, path) => { ++ Transforms.setNode(board, options, path); ++}; ++const setArrowLineTexts = (board, element, texts) => { ++ const path = PlaitBoard.findPath(board, element); ++ Transforms.setNode(board, { texts }, path); ++}; ++const removeArrowLineText = (board, element, index) => { ++ const path = PlaitBoard.findPath(board, element); ++ const texts = element.texts?.length ? [...element.texts] : []; ++ const newTexts = [...texts]; ++ newTexts.splice(index, 1); ++ Transforms.setNode(board, { texts: newTexts }, path); ++}; ++const setArrowLineMark = (board, handleKey, marker) => { ++ memorizeLatest(MemorizeKey.arrowLine, handleKey, marker); ++ const selectedElements = getSelectedArrowLineElements(board); ++ selectedElements.forEach((element) => { ++ const path = PlaitBoard.findPath(board, element); ++ let handle = handleKey === ArrowLineHandleKey.source ? element.source : element.target; ++ handle = { ...handle, marker }; ++ Transforms.setNode(board, { [handleKey]: handle }, path); ++ }); ++}; ++const setArrowLineShape = (board, newProperties) => { ++ const elements = getSelectedArrowLineElements(board); ++ elements.map(element => { ++ const _properties = { ...newProperties }; ++ if (element.shape === newProperties.shape) { ++ return; ++ } ++ const path = PlaitBoard.findPath(board, element); ++ Transforms.setNode(board, _properties, path); ++ }); ++}; ++const connectArrowLineToDraw = (board, lineElement, handle, geometryElement) => { ++ const linePoints = PlaitArrowLine.getPoints(board, lineElement); ++ const point = handle === ArrowLineHandleKey.source ? linePoints[0] : linePoints[linePoints.length - 1]; ++ const connection = getHitConnection(board, point, geometryElement); ++ if (connection) { ++ let source = lineElement.source; ++ let target = lineElement.target; ++ if (handle === ArrowLineHandleKey.source) { ++ source = { ++ ...source, ++ boundId: geometryElement.id, ++ connection ++ }; ++ } ++ else { ++ target = { ++ ...target, ++ boundId: geometryElement.id, ++ connection ++ }; ++ } ++ const path = PlaitBoard.findPath(board, lineElement); ++ resizeArrowLine(board, { source, target }, path); ++ } ++}; ++ ++function buildSwimlaneTable(element) { ++ const swimlaneElement = { ...element }; ++ if (PlaitDrawElement.isHorizontalSwimlane(element)) { ++ swimlaneElement.cells = element.cells.map((item, index) => { ++ if (index === 0 && element.header) { ++ item = { ++ ...element.cells[0], ++ rowspan: element.rows.length ++ }; ++ } ++ if (item.text && !item.text.direction) { ++ item = { ++ ...item, ++ text: { ++ ...item.text, ++ direction: 'vertical' ++ } ++ }; ++ } ++ return item; ++ }); ++ return swimlaneElement; ++ } ++ if (element.header) { ++ swimlaneElement.cells = [ ++ { ++ ...element.cells[0], ++ colspan: element.columns.length ++ }, ++ ...element.cells.slice(1, element.cells.length) ++ ]; ++ } ++ return swimlaneElement; ++} ++const getDefaultSwimlanePoints = (pointer, centerPoint) => { ++ const property = DefaultSwimlanePropertyMap[pointer]; ++ return RectangleClient.getPoints(RectangleClient.getRectangleByCenterPoint(centerPoint, property.width, property.height)); ++}; ++const createDefaultSwimlane = (shape, points) => { ++ const header = isSwimlaneWithHeader(shape); ++ const dataShape = adjustSwimlaneShape(shape); ++ const width = points[1][0] - points[0][0]; ++ const height = points[1][1] - points[0][1]; ++ const rows = createDefaultRowsOrColumns(dataShape, 'row', header, height); ++ const columns = createDefaultRowsOrColumns(dataShape, 'column', header, width); ++ const swimlane = { ++ id: idCreator(), ++ type: 'swimlane', ++ shape: dataShape, ++ points, ++ rows, ++ columns, ++ header, ++ cells: createDefaultCells(dataShape, rows, columns, header) ++ }; ++ return swimlane; ++}; ++const createDefaultRowsOrColumns = (shape, type, header, size) => { ++ const createItems = (count) => new Array(count).fill('').map(() => ({ id: idCreator() })); ++ let data = createItems(3); ++ if ((type === 'row' && shape === SwimlaneSymbols.swimlaneVertical) || ++ (type === 'column' && shape === SwimlaneSymbols.swimlaneHorizontal)) { ++ data = header ? data : createItems(2); ++ const dimension = type === 'row' ? 'height' : 'width'; ++ let defaultSize = SWIMLANE_HEADER_SIZE; ++ if (size < SWIMLANE_HEADER_SIZE * data.length) { ++ defaultSize = Math.min((size / data.length / SWIMLANE_HEADER_SIZE) * SWIMLANE_HEADER_SIZE, SWIMLANE_HEADER_SIZE); ++ } ++ data = data.map((item, index) => { ++ if (index === 0 || (index === 1 && header)) { ++ return { ++ ...item, ++ [dimension]: defaultSize ++ }; ++ } ++ return item; ++ }); ++ } ++ return data; ++}; ++const createDefaultCells = (shape, rows, columns, header) => { ++ let headerCell = []; ++ let startIndex = 0; ++ if (header) { ++ headerCell = [createCell(rows[0].id, columns[0].id, 'New Swimlane')]; ++ startIndex = 1; ++ } ++ const cells = new Array(6).fill('').map((_, index) => { ++ if (index < 3) { ++ const rowId = shape === SwimlaneSymbols.swimlaneVertical ? rows[startIndex].id : rows[index].id; ++ const columnId = shape === SwimlaneSymbols.swimlaneVertical ? columns[index].id : columns[startIndex].id; ++ return createCell(rowId, columnId, header ? 'Lane' : 'New Swimlane'); ++ } ++ const rowId = shape === SwimlaneSymbols.swimlaneVertical ? rows[startIndex + 1].id : rows[index - 3].id; ++ const columnId = shape === SwimlaneSymbols.swimlaneVertical ? columns[index - 3].id : columns[startIndex + 1].id; ++ return createCell(rowId, columnId); ++ }); ++ return [...headerCell, ...cells]; ++}; ++const getSwimlaneCount = (swimlane) => { ++ if (PlaitDrawElement.isHorizontalSwimlane(swimlane)) { ++ return swimlane.rows.length; ++ } ++ if (PlaitDrawElement.isVerticalSwimlane(swimlane)) { ++ return swimlane.columns.length; ++ } ++ return 0; ++}; ++const isSwimlaneWithHeader = (shape) => { ++ return [SwimlaneDrawSymbols.swimlaneHorizontalWithHeader, SwimlaneDrawSymbols.swimlaneVerticalWithHeader].includes(shape); ++}; ++const isSwimlaneShape = (shape) => { ++ return getSwimlaneShapes().includes(shape); ++}; ++const adjustSwimlaneShape = (shape) => { ++ return [SwimlaneDrawSymbols.swimlaneHorizontalWithHeader, SwimlaneDrawSymbols.swimlaneHorizontal].includes(shape) ++ ? SwimlaneSymbols.swimlaneHorizontal ++ : SwimlaneSymbols.swimlaneVertical; ++}; ++const isSwimlanePointers = (board, pointer = board.pointer) => { ++ return getSwimlanePointers().includes(pointer); ++}; ++ ++const updateSwimlaneCount = (board, swimlane, count) => { ++ if (count > 0 && PlaitDrawElement.isSwimlane(swimlane)) { ++ const currentCount = getSwimlaneCount(swimlane); ++ if (PlaitDrawElement.isHorizontalSwimlane(swimlane)) { ++ if (count > currentCount) { ++ addSwimlaneRow(board, swimlane, swimlane.rows.length, count - currentCount); ++ } ++ else { ++ const deleteIndex = swimlane.rows.length - (currentCount - count); ++ removeSwimlaneRow(board, swimlane, deleteIndex, currentCount - count); ++ } ++ } ++ if (PlaitDrawElement.isVerticalSwimlane(swimlane)) { ++ if (count > currentCount) { ++ addSwimlaneColumn(board, swimlane, swimlane.columns.length, count - currentCount); ++ } ++ else { ++ const deleteIndex = swimlane.columns.length - (currentCount - count); ++ removeSwimlaneColumn(board, swimlane, deleteIndex, currentCount - count); ++ } ++ } ++ } ++}; ++const addSwimlaneRow = (board, swimlane, index, count = 1) => { ++ if (PlaitDrawElement.isHorizontalSwimlane(swimlane)) { ++ const newRows = [...swimlane.rows]; ++ const addRows = []; ++ for (let i = 0; i < count; i++) { ++ addRows.push({ id: idCreator() }); ++ } ++ newRows.splice(index, 0, ...addRows); ++ const newCells = [...swimlane.cells]; ++ addRows.forEach(item => { ++ newCells.push(...createNewSwimlaneCells(swimlane, item.id, 'column')); ++ }); ++ const lastCellPoints = getCellWithPoints(board, swimlane, swimlane.cells[swimlane.cells.length - 1].id).points; ++ const lastRowHeight = RectangleClient.getRectangleByPoints(lastCellPoints).height; ++ const newPoints = [swimlane.points[0], [swimlane.points[1][0], swimlane.points[1][1] + lastRowHeight * count]]; ++ updateSwimlane(board, swimlane, swimlane.columns, newRows, newCells, newPoints); ++ } ++}; ++const addSwimlaneColumn = (board, swimlane, index, count = 1) => { ++ if (PlaitDrawElement.isVerticalSwimlane(swimlane)) { ++ const newColumns = [...swimlane.columns]; ++ const addColumns = []; ++ for (let i = 0; i < count; i++) { ++ addColumns.push({ id: idCreator() }); ++ } ++ newColumns.splice(index, 0, ...addColumns); ++ const newCells = [...swimlane.cells]; ++ addColumns.forEach(item => { ++ newCells.push(...createNewSwimlaneCells(swimlane, item.id, 'row')); ++ }); ++ const lastCellPoints = getCellWithPoints(board, swimlane, swimlane.cells[swimlane.cells.length - 1].id).points; ++ const lastColumnWidth = RectangleClient.getRectangleByPoints(lastCellPoints).width; ++ const newPoints = [swimlane.points[0], [swimlane.points[1][0] + lastColumnWidth * count, swimlane.points[1][1]]]; ++ updateSwimlane(board, swimlane, newColumns, swimlane.rows, newCells, newPoints); ++ } ++}; ++const removeSwimlaneRow = (board, swimlane, index, count = 1) => { ++ if (PlaitDrawElement.isHorizontalSwimlane(swimlane)) { ++ if (count > swimlane.rows.length) { ++ return; ++ } ++ const newRows = [...swimlane.rows]; ++ newRows.splice(index, count); ++ if (newRows.length === 0) { ++ const path = PlaitBoard.findPath(board, swimlane); ++ Transforms.removeNode(board, path); ++ } ++ else { ++ let newCells = [...swimlane.cells]; ++ const removeRows = []; ++ for (let i = index; i < count + index; i++) { ++ const removeRow = swimlane.rows[i]; ++ removeRows.push(removeRow); ++ newCells = newCells.filter(item => item.rowId !== removeRow.id); ++ } ++ let removeRowHeight = 0; ++ removeRows.forEach(row => { ++ if (!row.height) { ++ const rowCell = swimlane.cells.find(item => item.rowId === row.id); ++ const cellPoints = getCellWithPoints(board, swimlane, rowCell.id).points; ++ removeRowHeight += RectangleClient.getRectangleByPoints(cellPoints).height; ++ } ++ else { ++ removeRowHeight += row.height; ++ } ++ }); ++ const newPoints = [swimlane.points[0], [swimlane.points[1][0], swimlane.points[1][1] - removeRowHeight]]; ++ updateSwimlane(board, swimlane, swimlane.columns, newRows, newCells, newPoints); ++ } ++ } ++}; ++const removeSwimlaneColumn = (board, swimlane, index, count = 1) => { ++ if (PlaitDrawElement.isVerticalSwimlane(swimlane)) { ++ if (count > swimlane.columns.length) { ++ return; ++ } ++ const newColumns = [...swimlane.columns]; ++ newColumns.splice(index, count); ++ if (newColumns.length === 0) { ++ const path = PlaitBoard.findPath(board, swimlane); ++ Transforms.removeNode(board, path); ++ } ++ else { ++ let newCells = [...swimlane.cells]; ++ const removeColumns = []; ++ for (let i = index; i < count + index; i++) { ++ const removeColumn = swimlane.columns[i]; ++ removeColumns.push(removeColumn); ++ newCells = newCells.filter(item => item.columnId !== removeColumn.id); ++ } ++ let removeColumnWidth = 0; ++ removeColumns.forEach(column => { ++ if (!column.width) { ++ const rowCell = swimlane.cells.find(item => item.columnId === column.id); ++ const cellPoints = getCellWithPoints(board, swimlane, rowCell.id).points; ++ removeColumnWidth += RectangleClient.getRectangleByPoints(cellPoints).width; ++ } ++ else { ++ removeColumnWidth += column.width; ++ } ++ }); ++ const newPoints = [swimlane.points[0], [swimlane.points[1][0] - removeColumnWidth, swimlane.points[1][1]]]; ++ updateSwimlane(board, swimlane, newColumns, swimlane.rows, newCells, newPoints); ++ } ++ } ++}; ++const createNewSwimlaneCells = (swimlane, newId, type) => { ++ const cells = swimlane[`${type}s`].map(item => ({ ++ id: idCreator(), ++ rowId: type === 'row' ? item.id : newId, ++ columnId: type === 'row' ? newId : item.id ++ })); ++ if (swimlane.header) { ++ cells.shift(); ++ } ++ cells[0] = { ++ ...cells[0], ++ text: { ++ children: [{ text: swimlane.header ? 'Lane' : 'New Swimlane' }], ++ align: Alignment.center, ++ direction: type === 'row' ? undefined : 'vertical' ++ } ++ }; ++ return cells; ++}; ++const updateSwimlane = (board, swimlane, newColumns, newRows, newCells, newPoints) => { ++ const path = PlaitBoard.findPath(board, swimlane); ++ Transforms.setNode(board, { ++ columns: newColumns, ++ rows: newRows, ++ cells: newCells, ++ points: newPoints ++ }, path); ++}; ++ ++const setDrawTexts = (board, element, text) => { ++ const newTexts = element.texts?.map(item => { ++ if (item.id === text.id) { ++ return { ...item, ...text }; ++ } ++ return item; ++ }); ++ const newElement = { ++ texts: newTexts ++ }; ++ const path = board.children.findIndex(child => child === element); ++ Transforms.setNode(board, newElement, [path]); ++}; ++ ++const setTableText = (board, path, cellId, text, textHeight) => { ++ const table = PlaitNode.get(board, path); ++ const cell = getCellWithPoints(board, table, cellId); ++ const cellIndex = table.cells.findIndex((item) => item.id === cell.id); ++ let rows = [...table.rows]; ++ let columns = [...table.columns]; ++ let cells = [...table.cells]; ++ let points = [...table.points]; ++ const { width: cellWidth, height: cellHeight } = RectangleClient.getRectangleByPoints(cell.points); ++ const defaultSpace = ShapeDefaultSpace.rectangleAndText; ++ if (PlaitTableElement.isVerticalText(cell)) { ++ const columnIdx = table.columns.findIndex((column) => column.id === cell.columnId); ++ if (textHeight > cellWidth) { ++ const newColumnWidth = textHeight + defaultSpace * 2; ++ const offset = newColumnWidth - cellWidth; ++ const result = updateColumns(table, table.columns[columnIdx].id, newColumnWidth, offset); ++ points = result.points; ++ columns = result.columns; ++ } ++ } ++ else { ++ const rowIdx = table.rows.findIndex((row) => row.id === cell.rowId); ++ const tableRow = table.rows[rowIdx]; ++ const compareHeight = tableRow.height ?? Math.max(cellHeight, 0); ++ if (textHeight > compareHeight) { ++ const newRowHeight = textHeight + defaultSpace * 2; ++ const offset = newRowHeight - compareHeight; ++ const result = updateRows(table, table.rows[rowIdx].id, newRowHeight, offset); ++ points = result.points; ++ rows = result.rows; ++ } ++ } ++ cells[cellIndex] = { ++ ...cells[cellIndex], ++ text ++ }; ++ Transforms.setNode(board, { rows, columns, cells, points }, path); ++}; ++ ++const setTableFill = (board, element, fill, path) => { ++ const selectedCells = getSelectedCells(element); ++ let newCells = element.cells; ++ if (selectedCells?.length) { ++ newCells = element.cells.map(cell => { ++ if (selectedCells.map(item => item.id).includes(cell.id)) { ++ return getNewCell(cell, fill); ++ } ++ return cell; ++ }); ++ } ++ else { ++ newCells = element.cells.map(cell => { ++ if (cell.text) { ++ return getNewCell(cell, fill); ++ } ++ return cell; ++ }); ++ } ++ Transforms.setNode(board, { cells: newCells }, path); ++}; ++const getNewCell = (cell, fill) => { ++ const newCell = { ++ ...cell ++ }; ++ if (fill) { ++ newCell.fill = fill; ++ } ++ else { ++ delete newCell.fill; ++ } ++ return newCell; ++}; ++ ++const setVectorLineShape = (board, newProperties) => { ++ const elements = getSelectedVectorLineElements(board); ++ elements.map(element => { ++ if (element.shape === newProperties.shape) { ++ return; ++ } ++ const path = PlaitBoard.findPath(board, element); ++ Transforms.setNode(board, { ...newProperties }, path); ++ }); ++}; ++ ++const insertDrawByVector = (board, point, shape, vector) => { ++ const swimlanePointers = getSwimlanePointers(); ++ const isSwimlanePointer = swimlanePointers.includes(shape); ++ let shapeProperty = DefaultFlowchartPropertyMap[shape] || ++ DefaultBasicShapePropertyMap[shape] || ++ DefaultUMLPropertyMap[shape] || ++ DefaultBasicShapeProperty; ++ if (isSwimlanePointer) { ++ shapeProperty = DefaultSwimlanePropertyMap[shape]; ++ } ++ const direction = getDirectionByVector(vector); ++ if (direction) { ++ let offset = 0; ++ if ([Direction.left, Direction.right].includes(direction)) { ++ offset = -shapeProperty.width / 2; ++ } ++ else { ++ offset = -shapeProperty.height / 2; ++ } ++ const vectorPoint = getPointByVectorComponent(point, vector, offset); ++ const points = RectangleClient.getPoints(RectangleClient.getRectangleByCenterPoint(vectorPoint, shapeProperty.width, shapeProperty.height)); ++ if (isSwimlanePointer) { ++ const swimlane = createDefaultSwimlane(shape, points); ++ insertElement(board, swimlane); ++ return swimlane; ++ } ++ return insertGeometry(board, points, shape); ++ } ++ return null; ++}; ++ ++const DrawTransforms = { ++ setText, ++ setDrawTexts, ++ insertGeometry, ++ resizeGeometry, ++ insertText, ++ setTextSize, ++ resizeArrowLine, ++ setArrowLineTexts, ++ removeArrowLineText, ++ setArrowLineMark, ++ setArrowLineShape, ++ setVectorLineShape, ++ insertImage, ++ connectArrowLineToDraw, ++ switchGeometryShape, ++ setTableText, ++ addSwimlaneRow, ++ addSwimlaneColumn, ++ removeSwimlaneRow, ++ removeSwimlaneColumn, ++ updateSwimlaneCount, ++ setTableFill, ++ insertDrawByVector ++}; ++ ++const getHitRectangleResizeHandleRef = (board, rectangle, point, angle = 0) => { ++ const centerPoint = RectangleClient.getCenterPoint(rectangle); ++ const resizeHandleRefs = getRectangleResizeHandleRefs(rectangle, RESIZE_HANDLE_DIAMETER); ++ if (angle) { ++ const rotatedPoint = rotatePoints([point], centerPoint, -angle)[0]; ++ let result = resizeHandleRefs.find((resizeHandleRef) => { ++ return RectangleClient.isHit(RectangleClient.getRectangleByPoints([rotatedPoint, rotatedPoint]), resizeHandleRef.rectangle); ++ }); ++ if (result) { ++ result.cursorClass = getRotatedResizeCursorClassByAngle(result.cursorClass, angle); ++ } ++ return result; ++ } ++ else { ++ return resizeHandleRefs.find((resizeHandleRef) => { ++ return RectangleClient.isHit(RectangleClient.getRectangleByPoints([point, point]), resizeHandleRef.rectangle); ++ }); ++ } ++}; ++const getRotateHandleRectangle = (rectangle) => { ++ return { ++ x: rectangle.x - ROTATE_HANDLE_DISTANCE_TO_ELEMENT - ROTATE_HANDLE_SIZE, ++ y: rectangle.y + rectangle.height + ROTATE_HANDLE_DISTANCE_TO_ELEMENT, ++ width: ROTATE_HANDLE_SIZE, ++ height: ROTATE_HANDLE_SIZE ++ }; ++}; ++ ++const debugKey$2 = 'debug:plait:resize-for-rotation'; ++const debugGenerator$3 = createDebugGenerator(debugKey$2); ++function withDrawResize(board) { ++ const { afterChange, drawSelectionRectangle } = board; ++ let snapG; ++ let handleG; ++ let needCustomActiveRectangle = false; ++ let resizeActivePoints = null; ++ const canResize = () => { ++ const elements = getSelectedElements(board); ++ return (elements.length >= 1 && ++ elements.every((el) => (PlaitDrawElement.isDrawElement(el) || (PlaitDrawElement.isCustomGeometryElement(board, el) && el.points.length > 1)) && ++ !isSingleSelectLine(board) && ++ !isSingleSelectSwimlane(board))); ++ }; ++ const options = { ++ key: 'draw-elements', ++ canResize, ++ hitTest: (point) => { ++ const elements = getSelectedElements(board); ++ const boundingRectangle = getRectangleByElements(board, elements, false); ++ const angle = getSelectionAngle(elements); ++ const handleRef = getHitRectangleResizeHandleRef(board, boundingRectangle, point, angle); ++ if (handleRef) { ++ return { ++ element: [...elements], ++ rectangle: boundingRectangle, ++ handle: handleRef.handle, ++ cursorClass: handleRef.cursorClass ++ }; ++ } ++ return null; ++ }, ++ onResize: (resizeRef, resizeState) => { ++ snapG?.remove(); ++ debugGenerator$3.isDebug() && debugGenerator$3.clear(); ++ const isFromCorner = isCornerHandle(board, resizeRef.handle); ++ const isAspectRatio = resizeState.isShift || (resizeRef.element.length === 1 && PlaitDrawElement.isImage(resizeRef.element[0])); ++ const centerPoint = RectangleClient.getCenterPoint(resizeRef.rectangle); ++ const handleIndex = getIndexByResizeHandle(resizeRef.handle); ++ const { originPoint, handlePoint } = getResizeOriginPointAndHandlePoint(board, handleIndex, resizeRef.rectangle); ++ const angle = getSelectionAngle(resizeRef.element); ++ let bulkRotationRef; ++ if (angle) { ++ bulkRotationRef = { ++ angle: angle, ++ offsetX: 0, ++ offsetY: 0, ++ newCenterPoint: [0, 0] ++ }; ++ const [rotatedStartPoint, rotateEndPoint] = rotatePoints([resizeState.startPoint, resizeState.endPoint], centerPoint, -bulkRotationRef.angle); ++ resizeState.startPoint = rotatedStartPoint; ++ resizeState.endPoint = rotateEndPoint; ++ } ++ const resizeSnapRefOptions = getSnapResizingRefOptions(board, resizeRef, resizeState, { ++ originPoint, ++ handlePoint ++ }, isAspectRatio, isFromCorner); ++ const resizeSnapRef = getSnapResizingRef(board, resizeRef.element, resizeSnapRefOptions); ++ resizeActivePoints = resizeSnapRef.activePoints; ++ snapG = resizeSnapRef.snapG; ++ PlaitBoard.getElementTopHost(board).append(snapG); ++ if (bulkRotationRef) { ++ const boundingBoxCornerPoints = RectangleClient.getPoints(resizeRef.rectangle); ++ const resizedBoundingBoxCornerPoints = boundingBoxCornerPoints.map((p) => { ++ return movePointByZoomAndOriginPoint(p, originPoint, resizeSnapRef.xZoom, resizeSnapRef.yZoom); ++ }); ++ const newBoundingBox = RectangleClient.getRectangleByPoints(resizedBoundingBoxCornerPoints); ++ debugGenerator$3.isDebug() && debugGenerator$3.drawRectangle(board, newBoundingBox, { stroke: 'blue' }); ++ const newBoundingBoxCenter = RectangleClient.getCenterPoint(newBoundingBox); ++ const adjustedNewBoundingBoxPoints = resetPointsAfterResize(RectangleClient.getRectangleByPoints(boundingBoxCornerPoints), RectangleClient.getRectangleByPoints(resizedBoundingBoxCornerPoints), centerPoint, newBoundingBoxCenter, bulkRotationRef.angle); ++ const newCenter = RectangleClient.getCenterPoint(RectangleClient.getRectangleByPoints(adjustedNewBoundingBoxPoints)); ++ bulkRotationRef = Object.assign(bulkRotationRef, { ++ offsetX: newCenter[0] - newBoundingBoxCenter[0], ++ offsetY: newCenter[1] - newBoundingBoxCenter[1], ++ newCenterPoint: newCenter ++ }); ++ debugGenerator$3.isDebug() && debugGenerator$3.drawRectangle(board, adjustedNewBoundingBoxPoints); ++ } ++ resizeRef.element.forEach((target) => { ++ const path = PlaitBoard.findPath(board, target); ++ let points; ++ if (bulkRotationRef) { ++ const reversedPoints = rotatedDataPoints(target.points, centerPoint, -bulkRotationRef.angle); ++ points = reversedPoints.map((p) => { ++ return movePointByZoomAndOriginPoint(p, originPoint, resizeSnapRef.xZoom, resizeSnapRef.yZoom); ++ }); ++ const adjustTargetPoints = points.map((p) => [ ++ p[0] + bulkRotationRef.offsetX, ++ p[1] + bulkRotationRef.offsetY ++ ]); ++ points = rotatedDataPoints(adjustTargetPoints, bulkRotationRef.newCenterPoint, bulkRotationRef.angle); ++ } ++ else { ++ if (hasValidAngle(target)) { ++ needCustomActiveRectangle = true; ++ } ++ if (hasValidAngle(target) && isAxisChangedByAngle(target.angle)) { ++ points = getResizePointsByOtherwiseAxis(board, target.points, originPoint, resizeSnapRef.xZoom, resizeSnapRef.yZoom); ++ } ++ else { ++ points = target.points.map((p) => { ++ return movePointByZoomAndOriginPoint(p, originPoint, resizeSnapRef.xZoom, resizeSnapRef.yZoom); ++ }); ++ } ++ } ++ if (PlaitDrawElement.isGeometry(target)) { ++ DrawTransforms.resizeGeometry(board, points, path); ++ } ++ else if (PlaitDrawElement.isLine(target) || ++ PlaitDrawElement.isCustomGeometryElement(board, target) || ++ PlaitDrawElement.isVectorLine(target)) { ++ Transforms.setNode(board, { points }, path); ++ } ++ else if (PlaitDrawElement.isImage(target)) { ++ if (isAspectRatio) { ++ Transforms.setNode(board, { points }, path); ++ } ++ else { ++ // The image element does not follow the resize, but moves based on the center point. ++ const targetRectangle = RectangleClient.getRectangleByPoints(target.points); ++ const centerPoint = RectangleClient.getCenterPoint(targetRectangle); ++ const newCenterPoint = movePointByZoomAndOriginPoint(centerPoint, originPoint, resizeSnapRef.xZoom, resizeSnapRef.yZoom); ++ const newTargetRectangle = RectangleClient.getRectangleByCenterPoint(newCenterPoint, targetRectangle.width, targetRectangle.height); ++ Transforms.setNode(board, { points: RectangleClient.getPoints(newTargetRectangle) }, path); ++ } ++ } ++ }); ++ }, ++ afterResize: (resizeRef) => { ++ snapG?.remove(); ++ snapG = null; ++ if (needCustomActiveRectangle) { ++ needCustomActiveRectangle = false; ++ resizeActivePoints = null; ++ const selectedElements = getSelectedElements(board); ++ Transforms.addSelectionWithTemporaryElements(board, selectedElements); ++ } ++ } ++ }; ++ withResize(board, options); ++ board.afterChange = () => { ++ afterChange(); ++ if (handleG) { ++ handleG.remove(); ++ handleG = null; ++ } ++ const selectedElements = getSelectedElements(board); ++ if (canResize() && !isSelectionMoving(board) && selectedElements.length > 1) { ++ handleG = generatorResizeHandles(board, resizeActivePoints, needCustomActiveRectangle); ++ PlaitBoard.getActiveHost(board).append(handleG); ++ } ++ }; ++ board.drawSelectionRectangle = () => { ++ if (needCustomActiveRectangle) { ++ const rectangle = RectangleClient.getRectangleByPoints(resizeActivePoints); ++ const activeRectangle = toActiveRectangleFromViewBoxRectangle(board, rectangle); ++ return drawRectangle(board, RectangleClient.inflate(activeRectangle, ACTIVE_STROKE_WIDTH), { ++ stroke: SELECTION_BORDER_COLOR, ++ strokeWidth: ACTIVE_STROKE_WIDTH ++ }); ++ } ++ return drawSelectionRectangle(); ++ }; ++ return board; ++} ++const getResizeOriginPointAndHandlePoint = (board, handleIndex, rectangle) => { ++ const symmetricHandleIndex = getSymmetricHandleIndex(board, handleIndex); ++ const originPoint = getResizeHandlePointByIndex(rectangle, symmetricHandleIndex); ++ const handlePoint = getResizeHandlePointByIndex(rectangle, handleIndex); ++ return { ++ originPoint, ++ handlePoint ++ }; ++}; ++const getResizeZoom = (resizeStartAndEnd, resizeOriginPoint, resizeHandlePoint, isFromCorner, isAspectRatio) => { ++ const [startPoint, endPoint] = resizeStartAndEnd; ++ let xZoom = 0; ++ let yZoom = 0; ++ if (isFromCorner) { ++ if (isAspectRatio) { ++ let normalizedOffsetX = Point.getOffsetX(startPoint, endPoint); ++ xZoom = normalizedOffsetX / (resizeHandlePoint[0] - resizeOriginPoint[0]); ++ yZoom = xZoom; ++ } ++ else { ++ let normalizedOffsetX = Point.getOffsetX(startPoint, endPoint); ++ let normalizedOffsetY = Point.getOffsetY(startPoint, endPoint); ++ xZoom = normalizedOffsetX / (resizeHandlePoint[0] - resizeOriginPoint[0]); ++ yZoom = normalizedOffsetY / (resizeHandlePoint[1] - resizeOriginPoint[1]); ++ } ++ } ++ else { ++ const isHorizontal = Point.isHorizontal(resizeOriginPoint, resizeHandlePoint, 0.1) || false; ++ let normalizedOffset = isHorizontal ? Point.getOffsetX(startPoint, endPoint) : Point.getOffsetY(startPoint, endPoint); ++ let benchmarkOffset = isHorizontal ? resizeHandlePoint[0] - resizeOriginPoint[0] : resizeHandlePoint[1] - resizeOriginPoint[1]; ++ const zoom = normalizedOffset / benchmarkOffset; ++ if (isAspectRatio) { ++ xZoom = zoom; ++ yZoom = zoom; ++ } ++ else { ++ if (isHorizontal) { ++ xZoom = zoom; ++ } ++ else { ++ yZoom = zoom; ++ } ++ } ++ } ++ return { xZoom, yZoom }; ++}; ++const movePointByZoomAndOriginPoint = (p, resizeOriginPoint, xZoom, yZoom) => { ++ const offsetX = (p[0] - resizeOriginPoint[0]) * xZoom; ++ const offsetY = (p[1] - resizeOriginPoint[1]) * yZoom; ++ return [p[0] + offsetX, p[1] + offsetY]; ++}; ++/** ++ * 1. Rotate 90° ++ * 2. Scale based on the rotated points ++ * 3. Reverse rotate the scaled points by 90° ++ */ ++const getResizePointsByOtherwiseAxis = (board, points, resizeOriginPoint, xZoom, yZoom) => { ++ const currentRectangle = RectangleClient.getRectangleByPoints(points); ++ debugGenerator$3.isDebug() && debugGenerator$3.drawRectangle(board, currentRectangle, { stroke: 'black' }); ++ let resultPoints = points; ++ resultPoints = rotatePoints(resultPoints, RectangleClient.getCenterPoint(currentRectangle), (1 / 2) * Math.PI); ++ debugGenerator$3.isDebug() && debugGenerator$3.drawRectangle(board, resultPoints, { stroke: 'blue' }); ++ resultPoints = resultPoints.map((p) => { ++ return movePointByZoomAndOriginPoint(p, resizeOriginPoint, xZoom, yZoom); ++ }); ++ debugGenerator$3.isDebug() && debugGenerator$3.drawRectangle(board, resultPoints); ++ const newRectangle = RectangleClient.getRectangleByPoints(resultPoints); ++ return rotatePoints(resultPoints, RectangleClient.getCenterPoint(newRectangle), -(1 / 2) * Math.PI); ++}; ++const generatorResizeHandles = (board, resizeActivePoints, needCustomActiveRectangle) => { ++ const handleG = createG(); ++ const elements = getSelectedElements(board); ++ const boundingRectangle = needCustomActiveRectangle ++ ? RectangleClient.getRectangleByPoints(resizeActivePoints) ++ : getRectangleByElements(board, elements, false); ++ const boundingActiveRectangle = toActiveRectangleFromViewBoxRectangle(board, boundingRectangle); ++ let corners = RectangleClient.getCornerPoints(boundingActiveRectangle); ++ const angle = getSelectionAngle(elements); ++ if (angle) { ++ const centerPoint = RectangleClient.getCenterPoint(boundingActiveRectangle); ++ corners = rotatePoints(corners, centerPoint, angle); ++ } ++ corners.forEach((corner) => { ++ const g = drawHandle(board, corner); ++ handleG.append(g); ++ }); ++ return handleG; ++}; ++ ++const debugKey$1 = 'debug:plait:point-for-geometry'; ++const debugGenerator$2 = createDebugGenerator(debugKey$1); ++const EQUAL_SPACING = 10; ++function getSnapResizingRefOptions(board, resizeRef, resizeState, resizeOriginPointAndHandlePoint, isAspectRatio, isFromCorner) { ++ const { originPoint, handlePoint } = resizeOriginPointAndHandlePoint; ++ const resizePoints = [resizeState.startPoint, resizeState.endPoint]; ++ const { xZoom, yZoom } = getResizeZoom(resizePoints, originPoint, handlePoint, isFromCorner, isAspectRatio); ++ let activeElements; ++ let resizeOriginPoint = []; ++ if (Array.isArray(resizeRef.element)) { ++ activeElements = resizeRef.element; ++ const rectangle = getRectangleByElements(board, resizeRef.element, false); ++ resizeOriginPoint = RectangleClient.getPoints(rectangle); ++ } ++ else { ++ activeElements = [resizeRef.element]; ++ resizeOriginPoint = resizeRef.element.points; ++ } ++ const points = resizeOriginPoint.map(p => { ++ return movePointByZoomAndOriginPoint(p, originPoint, xZoom, yZoom); ++ }); ++ const rectangle = RectangleClient.getRectangleByPoints(points); ++ const activeRectangle = getRectangleByAngle(rectangle, getSelectionAngle(activeElements)); ++ const resizeHandlePoint = movePointByZoomAndOriginPoint(handlePoint, originPoint, xZoom, yZoom); ++ const [x, y] = getUnitVectorByPointAndPoint(originPoint, resizeHandlePoint); ++ return { ++ resizePoints, ++ resizeOriginPoint, ++ activeRectangle, ++ originPoint, ++ handlePoint, ++ directionFactors: [getDirectionFactorByDirectionComponent(x), getDirectionFactorByDirectionComponent(y)], ++ isAspectRatio, ++ isFromCorner ++ }; ++} ++function getSnapResizingRef(board, activeElements, resizeSnapOptions) { ++ const snapG = createG(); ++ const snapRectangles = getSnapRectangles(board, activeElements); ++ let snapLineDelta = getIsometricLineDelta(snapRectangles, resizeSnapOptions); ++ if (snapLineDelta.deltaX === 0 && snapLineDelta.deltaY === 0) { ++ snapLineDelta = getSnapPointDelta(snapRectangles, resizeSnapOptions); ++ } ++ const angle = getSelectionAngle(activeElements); ++ const activePointAndZoom = getActivePointAndZoom(snapLineDelta, resizeSnapOptions, angle); ++ const isometricLinesG = drawIsometricSnapLines(board, activePointAndZoom.activePoints, snapRectangles, resizeSnapOptions, angle); ++ const pointLinesG = drawResizingPointSnapLines(board, activePointAndZoom.activePoints, snapRectangles, resizeSnapOptions, angle); ++ snapG.append(isometricLinesG, pointLinesG); ++ return { ...activePointAndZoom, ...snapLineDelta, snapG }; ++} ++function getSnapPointDelta(snapRectangles, resizeSnapOptions) { ++ let pointLineDelta = { ++ deltaX: 0, ++ deltaY: 0 ++ }; ++ const { isAspectRatio, activeRectangle, directionFactors } = resizeSnapOptions; ++ const drawHorizontal = directionFactors[0] !== 0 || isAspectRatio; ++ const drawVertical = directionFactors[1] !== 0 || isAspectRatio; ++ if (drawHorizontal) { ++ const xSnapAxis = getTripleAxis(activeRectangle, true); ++ const pointX = directionFactors[0] === -1 ? xSnapAxis[0] : xSnapAxis[2]; ++ const deltaX = getMinPointDelta(snapRectangles, pointX, true); ++ if (Math.abs(deltaX) < SNAP_TOLERANCE) { ++ pointLineDelta.deltaX = deltaX; ++ if (pointLineDelta.deltaX !== 0 && isAspectRatio) { ++ pointLineDelta.deltaY = pointLineDelta.deltaX / (activeRectangle.width / activeRectangle.height); ++ return pointLineDelta; ++ } ++ } ++ } ++ if (drawVertical) { ++ const ySnapAxis = getTripleAxis(activeRectangle, false); ++ const pointY = directionFactors[1] === -1 ? ySnapAxis[0] : ySnapAxis[2]; ++ const deltaY = getMinPointDelta(snapRectangles, pointY, false); ++ if (Math.abs(deltaY) < SNAP_TOLERANCE) { ++ pointLineDelta.deltaY = deltaY; ++ if (pointLineDelta.deltaY !== 0 && isAspectRatio) { ++ pointLineDelta.deltaX = pointLineDelta.deltaY * (activeRectangle.width / activeRectangle.height); ++ return pointLineDelta; ++ } ++ } ++ } ++ return pointLineDelta; ++} ++function getActivePointAndZoom(resizeSnapDelta, resizeSnapOptions, angle) { ++ const { deltaX, deltaY } = resizeSnapDelta; ++ const { resizePoints, isCreate } = resizeSnapOptions; ++ const newResizePoints = [resizePoints[0], [resizePoints[1][0] + deltaX, resizePoints[1][1] + deltaY]]; ++ let activePoints = newResizePoints; ++ let xZoom = 0; ++ let yZoom = 0; ++ if (!isCreate) { ++ const { originPoint, handlePoint, isFromCorner, isAspectRatio, resizeOriginPoint } = resizeSnapOptions; ++ const resizeZoom = getResizeZoom(newResizePoints, originPoint, handlePoint, isFromCorner, isAspectRatio); ++ xZoom = resizeZoom.xZoom; ++ yZoom = resizeZoom.yZoom; ++ activePoints = resizeOriginPoint.map(p => { ++ return movePointByZoomAndOriginPoint(p, originPoint, xZoom, yZoom); ++ }); ++ if (angle) { ++ activePoints = resetPointsAfterResize(RectangleClient.getRectangleByPoints(resizeOriginPoint), RectangleClient.getRectangleByPoints(activePoints), RectangleClient.getCenterPoint(RectangleClient.getRectangleByPoints(resizeOriginPoint)), RectangleClient.getCenterPoint(RectangleClient.getRectangleByPoints(activePoints)), angle); ++ } ++ } ++ return { ++ xZoom, ++ yZoom, ++ activePoints ++ }; ++} ++function getIsometricLineDelta(snapRectangles, resizeSnapOptions) { ++ let isometricLineDelta = { ++ deltaX: 0, ++ deltaY: 0 ++ }; ++ const { isAspectRatio, activeRectangle } = resizeSnapOptions; ++ const widthSnapRectangle = snapRectangles.find(item => Math.abs(item.width - activeRectangle.width) < SNAP_TOLERANCE); ++ if (widthSnapRectangle) { ++ const deltaWidth = widthSnapRectangle.width - activeRectangle.width; ++ isometricLineDelta.deltaX = deltaWidth * resizeSnapOptions.directionFactors[0]; ++ if (isAspectRatio) { ++ const deltaHeight = deltaWidth / (activeRectangle.width / activeRectangle.height); ++ isometricLineDelta.deltaY = deltaHeight * resizeSnapOptions.directionFactors[1]; ++ return isometricLineDelta; ++ } ++ } ++ const heightSnapRectangle = snapRectangles.find(item => Math.abs(item.height - activeRectangle.height) < SNAP_TOLERANCE); ++ if (heightSnapRectangle) { ++ const deltaHeight = heightSnapRectangle.height - activeRectangle.height; ++ isometricLineDelta.deltaY = deltaHeight * resizeSnapOptions.directionFactors[1]; ++ if (isAspectRatio) { ++ const deltaWidth = deltaHeight * (activeRectangle.width / activeRectangle.height); ++ isometricLineDelta.deltaX = deltaWidth * resizeSnapOptions.directionFactors[0]; ++ return isometricLineDelta; ++ } ++ } ++ return isometricLineDelta; ++} ++function getIsometricLinePoints(rectangle, isHorizontal) { ++ return isHorizontal ++ ? [ ++ [rectangle.x, rectangle.y - EQUAL_SPACING], ++ [rectangle.x + rectangle.width, rectangle.y - EQUAL_SPACING] ++ ] ++ : [ ++ [rectangle.x - EQUAL_SPACING, rectangle.y], ++ [rectangle.x - EQUAL_SPACING, rectangle.y + rectangle.height] ++ ]; ++} ++function drawResizingPointSnapLines(board, activePoints, snapRectangles, resizeSnapOptions, angle) { ++ debugGenerator$2.isDebug() && debugGenerator$2.clear(); ++ const rectangle = RectangleClient.getRectangleByPoints(activePoints); ++ const activeRectangle = getRectangleByAngle(rectangle, angle); ++ const { isAspectRatio, directionFactors } = resizeSnapOptions; ++ const drawHorizontal = directionFactors[0] !== 0 || isAspectRatio; ++ const drawVertical = directionFactors[1] !== 0 || isAspectRatio; ++ return drawPointSnapLines(board, activeRectangle, snapRectangles, drawHorizontal, drawVertical); ++} ++function drawIsometricSnapLines(board, activePoints, snapRectangles, resizeSnapOptions, angle) { ++ let widthIsometricPoints = []; ++ let heightIsometricPoints = []; ++ const drawHorizontalLine = resizeSnapOptions.directionFactors[0] !== 0 || resizeSnapOptions.isAspectRatio; ++ const drawVerticalLine = resizeSnapOptions.directionFactors[1] !== 0 || resizeSnapOptions.isAspectRatio; ++ const rectangle = RectangleClient.getRectangleByPoints(activePoints); ++ const activeRectangle = getRectangleByAngle(rectangle, angle); ++ for (let snapRectangle of snapRectangles) { ++ if (activeRectangle.width === snapRectangle.width && drawHorizontalLine) { ++ widthIsometricPoints.push(getIsometricLinePoints(snapRectangle, true)); ++ } ++ if (activeRectangle.height === snapRectangle.height && drawVerticalLine) { ++ heightIsometricPoints.push(getIsometricLinePoints(snapRectangle, false)); ++ } ++ } ++ if (widthIsometricPoints.length && drawHorizontalLine) { ++ widthIsometricPoints.push(getIsometricLinePoints(activeRectangle, true)); ++ } ++ if (heightIsometricPoints.length && drawVerticalLine) { ++ heightIsometricPoints.push(getIsometricLinePoints(activeRectangle, false)); ++ } ++ const isometricLines = [...widthIsometricPoints, ...heightIsometricPoints]; ++ return drawSolidLines(board, isometricLines); ++} ++ ++const buildClipboardData = (board, elements, startPoint) => { ++ return buildClipboardData$1(board, elements, startPoint, (element) => { ++ if (PlaitDrawElement.isArrowLine(element)) { ++ let source = { ...element.source }; ++ let target = { ...element.target }; ++ let points = [...element.points]; ++ if (element.source.boundId) { ++ points[0] = getConnectionPoint(getElementById(board, element.source.boundId), element.source.connection); ++ if (!getElementById(board, element.source.boundId, elements)) { ++ delete source.boundId; ++ delete source.connection; ++ } ++ } ++ if (element.target.boundId) { ++ points[points.length - 1] = getConnectionPoint(getElementById(board, element.target.boundId), element.target.connection); ++ if (!getElementById(board, element.target.boundId, elements)) { ++ delete target.boundId; ++ delete target.connection; ++ } ++ } ++ points = points.map(point => [point[0] - startPoint[0], point[1] - startPoint[1]]); ++ return { ...element, points, source, target }; ++ } ++ return undefined; ++ }); ++}; ++const insertClipboardData = (board, elements, startPoint) => { ++ insertClipboardData$1(board, elements, startPoint, (element, idsMap) => { ++ if (PlaitDrawElement.isArrowLine(element)) { ++ if (element.source.boundId) { ++ const boundElement = elements.find(item => [element.source.boundId, idsMap[element.source.boundId]].includes(item.id)); ++ if (boundElement) { ++ element.source.boundId = idsMap[element.source.boundId]; ++ } ++ } ++ if (element.target.boundId) { ++ const boundElement = elements.find(item => [element.target.boundId, idsMap[element.target.boundId]].includes(item.id)); ++ if (boundElement) { ++ element.target.boundId = idsMap[element.target.boundId]; ++ } ++ } ++ } ++ if (PlaitDrawElement.isElementByTable(element)) { ++ updateRowOrColumnIds(element, 'row'); ++ updateRowOrColumnIds(element, 'column'); ++ updateCellIds(element.cells); ++ } ++ }); ++}; ++ ++const heightRatio$1 = 3 / 4; ++const CommentEngine = { ++ draw(board, rectangle, options) { ++ const points = getCommentPoints(rectangle); ++ const rs = PlaitBoard.getRoughSVG(board); ++ const polygon = rs.polygon(points, { ...options, fillStyle: 'solid' }); ++ setStrokeLinecap(polygon, 'round'); ++ return polygon; ++ }, ++ isInsidePoint(rectangle, point) { ++ const parallelogramPoints = getCommentPoints(rectangle); ++ return isPointInPolygon(point, parallelogramPoints); ++ }, ++ getCornerPoints(rectangle) { ++ return getCommentPoints(rectangle); ++ }, ++ getNearestPoint(rectangle, point) { ++ return getNearestPointBetweenPointAndSegments(point, getCommentPoints(rectangle)); ++ }, ++ getEdgeByConnectionPoint(rectangle, pointOfRectangle) { ++ const corners = getCommentPoints(rectangle); ++ const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle); ++ return getPolygonEdgeByConnectionPoint(corners, point); ++ }, ++ getConnectorPoints(rectangle) { ++ return RectangleClient.getEdgeCenterPoints(rectangle); ++ }, ++ getTextRectangle: (board, element) => { ++ const elementRectangle = RectangleClient.getRectangleByPoints(element.points); ++ const textRectangle = getTextRectangle$1(board, element); ++ textRectangle.y = elementRectangle.y + (elementRectangle.height * heightRatio$1 - textRectangle.height) / 2; ++ return textRectangle; ++ } ++}; ++const getCommentPoints = (rectangle) => { ++ return [ ++ [rectangle.x, rectangle.y], ++ [rectangle.x + rectangle.width, rectangle.y], ++ [rectangle.x + rectangle.width, rectangle.y + rectangle.height * heightRatio$1], ++ [rectangle.x + (rectangle.width * 3) / 5, rectangle.y + rectangle.height * heightRatio$1], ++ [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height], ++ [rectangle.x + (rectangle.width * 2) / 5, rectangle.y + rectangle.height * heightRatio$1], ++ [rectangle.x, rectangle.y + rectangle.height * heightRatio$1] ++ ]; ++}; ++ ++function createPolygonEngine(options) { ++ const getPoints = options.getPolygonPoints; ++ const engine = { ++ draw(board, rectangle, options) { ++ const points = getPoints(rectangle); ++ const rs = PlaitBoard.getRoughSVG(board); ++ const polygon = rs.polygon(points, { ...options, fillStyle: 'solid' }); ++ setStrokeLinecap(polygon, 'round'); ++ return polygon; ++ }, ++ isInsidePoint(rectangle, point) { ++ const points = getPoints(rectangle); ++ return isPointInPolygon(point, points); ++ }, ++ getCornerPoints(rectangle) { ++ return getPoints(rectangle); ++ }, ++ getNearestPoint(rectangle, point) { ++ return getNearestPointBetweenPointAndSegments(point, getPoints(rectangle)); ++ }, ++ getNearestCrossingPoint(rectangle, point) { ++ const corners = getPoints(rectangle); ++ const crossingPoints = getCrossingPointBetweenPointAndPolygon(corners, point); ++ let nearestPoint = crossingPoints[0]; ++ let nearestDistance = distanceBetweenPointAndPoint(point[0], point[1], nearestPoint[0], nearestPoint[1]); ++ crossingPoints ++ .filter((v, index) => index > 0) ++ .forEach(crossingPoint => { ++ let distance = distanceBetweenPointAndPoint(point[0], point[1], crossingPoint[0], crossingPoint[1]); ++ if (distance < nearestDistance) { ++ nearestDistance = distance; ++ nearestPoint = crossingPoint; ++ } ++ }); ++ return nearestPoint; ++ }, ++ getEdgeByConnectionPoint(rectangle, pointOfRectangle) { ++ const corners = getPoints(rectangle); ++ const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle); ++ return getPolygonEdgeByConnectionPoint(corners, point); ++ }, ++ getConnectorPoints(rectangle) { ++ if (options.getConnectorPoints) { ++ return options.getConnectorPoints(rectangle); ++ } ++ return getPoints(rectangle); ++ } ++ }; ++ if (options.getTextRectangle) { ++ engine.getTextRectangle = options.getTextRectangle; ++ } ++ return engine; ++} ++ ++const getCrossPoints = (rectangle) => { ++ return [ ++ [rectangle.x + rectangle.width / 4, rectangle.y], ++ [rectangle.x + (rectangle.width * 3) / 4, rectangle.y], ++ [rectangle.x + (rectangle.width * 3) / 4, rectangle.y + rectangle.height / 4], ++ [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 4], ++ [rectangle.x + rectangle.width, rectangle.y + (rectangle.height * 3) / 4], ++ [rectangle.x + (rectangle.width * 3) / 4, rectangle.y + (rectangle.height * 3) / 4], ++ [rectangle.x + (rectangle.width * 3) / 4, rectangle.y + rectangle.height], ++ [rectangle.x + rectangle.width / 4, rectangle.y + rectangle.height], ++ [rectangle.x + rectangle.width / 4, rectangle.y + (rectangle.height * 3) / 4], ++ [rectangle.x, rectangle.y + (rectangle.height * 3) / 4], ++ [rectangle.x, rectangle.y + rectangle.height / 4], ++ [rectangle.x + rectangle.width / 4, rectangle.y + rectangle.height / 4] ++ ]; ++}; ++const CrossEngine = createPolygonEngine({ ++ getPolygonPoints: getCrossPoints, ++ getConnectorPoints(rectangle) { ++ return RectangleClient.getEdgeCenterPoints(rectangle); ++ }, ++ getTextRectangle: (board, element) => { ++ const widthRatio = 1 / 2; ++ return getCustomTextRectangle(board, element, widthRatio); ++ } ++}); ++ ++const DiamondEngine = createPolygonEngine({ ++ getPolygonPoints: RectangleClient.getEdgeCenterPoints, ++ getConnectorPoints(rectangle) { ++ return RectangleClient.getEdgeCenterPoints(rectangle); ++ }, ++ getTextRectangle: (board, element) => { ++ return getCustomTextRectangle(board, element, 1 / 2); ++ } ++}); ++ ++function createEllipseEngine(createOptions) { ++ const engine = { ++ draw(board, rectangle, options) { ++ const centerPoint = [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height / 2]; ++ const rs = PlaitBoard.getRoughSVG(board); ++ const shape = rs.ellipse(centerPoint[0], centerPoint[1], rectangle.width, rectangle.height, { ...options, fillStyle: 'solid' }); ++ setStrokeLinecap(shape, 'round'); ++ return shape; ++ }, ++ isInsidePoint(rectangle, point) { ++ const centerPoint = [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height / 2]; ++ return isPointInEllipse(point, centerPoint, rectangle.width / 2, rectangle.height / 2); ++ }, ++ getCornerPoints(rectangle) { ++ return RectangleClient.getEdgeCenterPoints(rectangle); ++ }, ++ getNearestPoint(rectangle, point) { ++ const centerPoint = [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height / 2]; ++ return getNearestPointBetweenPointAndEllipse(point, centerPoint, rectangle.width / 2, rectangle.height / 2); ++ }, ++ getTangentVectorByConnectionPoint(rectangle, pointOfRectangle) { ++ const connectionPoint = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle); ++ const centerPoint = [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height / 2]; ++ const point = [connectionPoint[0] - centerPoint[0], -(connectionPoint[1] - centerPoint[1])]; ++ const a = rectangle.width / 2; ++ const b = rectangle.height / 2; ++ const slope = getEllipseTangentSlope(point[0], point[1], a, b); ++ const vector = getVectorFromPointAndSlope(point[0], point[1], slope); ++ return vector; ++ }, ++ getConnectorPoints(rectangle) { ++ return RectangleClient.getEdgeCenterPoints(rectangle); ++ }, ++ getTextRectangle: (board, element) => { ++ return getCustomTextRectangle(board, element, 3 / 4); ++ } ++ }; ++ if (createOptions?.draw) { ++ engine.draw = createOptions.draw; ++ } ++ if (createOptions?.getTextRectangle) { ++ engine.getTextRectangle = createOptions.getTextRectangle; ++ } ++ return engine; ++} ++const EllipseEngine = createEllipseEngine(); ++ ++const getHexagonPoints = (rectangle) => { ++ return [ ++ [rectangle.x + rectangle.width / 4, rectangle.y], ++ [rectangle.x + (rectangle.width * 3) / 4, rectangle.y], ++ [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2], ++ [rectangle.x + (rectangle.width * 3) / 4, rectangle.y + rectangle.height], ++ [rectangle.x + rectangle.width / 4, rectangle.y + rectangle.height], ++ [rectangle.x, rectangle.y + rectangle.height / 2] ++ ]; ++}; ++const HexagonEngine = createPolygonEngine({ ++ getPolygonPoints: getHexagonPoints, ++ getConnectorPoints(rectangle) { ++ return RectangleClient.getEdgeCenterPoints(rectangle); ++ }, ++ getTextRectangle: (board, element) => { ++ return getCustomTextRectangle(board, element, 3 / 4); ++ } ++}); ++ ++const getLeftArrowPoints = (rectangle) => { ++ return [ ++ [rectangle.x, rectangle.y + rectangle.height / 2], ++ [rectangle.x + rectangle.width * 0.32, rectangle.y], ++ [rectangle.x + rectangle.width * 0.32, rectangle.y + rectangle.height * 0.2], ++ [rectangle.x + rectangle.width, rectangle.y + rectangle.height * 0.2], ++ [rectangle.x + rectangle.width, rectangle.y + rectangle.height * 0.8], ++ [rectangle.x + rectangle.width * 0.32, rectangle.y + rectangle.height * 0.8], ++ [rectangle.x + rectangle.width * 0.32, rectangle.y + rectangle.height] ++ ]; ++}; ++const LeftArrowEngine = createPolygonEngine({ ++ getPolygonPoints: getLeftArrowPoints, ++ getConnectorPoints: (rectangle) => { ++ return [ ++ [rectangle.x, rectangle.y + rectangle.height / 2], ++ [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2] ++ ]; ++ }, ++ getTextRectangle: (board, element) => { ++ const elementRectangle = RectangleClient.getRectangleByPoints(element.points); ++ const customTextRectangle = getCustomTextRectangle(board, element, 1 - 0.32); ++ customTextRectangle.x = elementRectangle.x + elementRectangle.width * 0.32 + ShapeDefaultSpace.rectangleAndText; ++ return customTextRectangle; ++ } ++}); ++ ++const getOctagonPoints = (rectangle) => { ++ return [ ++ [rectangle.x + (rectangle.width * 3) / 10, rectangle.y], ++ [rectangle.x + (rectangle.width * 7) / 10, rectangle.y], ++ [rectangle.x + rectangle.width, rectangle.y + (rectangle.height * 3) / 10], ++ [rectangle.x + rectangle.width, rectangle.y + (rectangle.height * 7) / 10], ++ [rectangle.x + (rectangle.width * 7) / 10, rectangle.y + rectangle.height], ++ [rectangle.x + (rectangle.width * 3) / 10, rectangle.y + rectangle.height], ++ [rectangle.x, rectangle.y + (rectangle.height * 7) / 10], ++ [rectangle.x, rectangle.y + (rectangle.height * 3) / 10] ++ ]; ++}; ++const OctagonEngine = createPolygonEngine({ ++ getPolygonPoints: getOctagonPoints, ++ getConnectorPoints(rectangle) { ++ return RectangleClient.getEdgeCenterPoints(rectangle); ++ }, ++ getTextRectangle: (board, element) => { ++ return getCustomTextRectangle(board, element, 3 / 4); ++ } ++}); ++ ++const getParallelogramPoints = (rectangle) => { ++ return [ ++ [rectangle.x + rectangle.width / 4, rectangle.y], ++ [rectangle.x + rectangle.width, rectangle.y], ++ [rectangle.x + (rectangle.width * 3) / 4, rectangle.y + rectangle.height], ++ [rectangle.x, rectangle.y + rectangle.height] ++ ]; ++}; ++const ParallelogramEngine = createPolygonEngine({ ++ getPolygonPoints: getParallelogramPoints, ++ getConnectorPoints: (rectangle) => { ++ const cornerPoints = getParallelogramPoints(rectangle); ++ return getCenterPointsOnPolygon$1(cornerPoints); ++ }, ++ getTextRectangle: (board, element) => { ++ return getCustomTextRectangle(board, element, 1 / 2); ++ } ++}); ++ ++const getPentagonPoints = (rectangle) => { ++ return [ ++ [rectangle.x + rectangle.width / 2, rectangle.y], ++ [rectangle.x + rectangle.width, rectangle.y + (rectangle.height * 2) / 5], ++ [rectangle.x + (rectangle.width * 4) / 5, rectangle.y + rectangle.height], ++ [rectangle.x + rectangle.width / 5, rectangle.y + rectangle.height], ++ [rectangle.x, rectangle.y + (rectangle.height * 2) / 5] ++ ]; ++}; ++const PentagonEngine = createPolygonEngine({ ++ getPolygonPoints: getPentagonPoints, ++ getTextRectangle: (board, element) => { ++ const elementRectangle = RectangleClient.getRectangleByPoints(element.points); ++ const customTextRectangle = getCustomTextRectangle(board, element, 3 / 5); ++ const startY = elementRectangle.y + elementRectangle.height / 5; ++ const endY = elementRectangle.y + elementRectangle.height; ++ customTextRectangle.y = startY + (endY - startY - customTextRectangle.height) / 2; ++ return customTextRectangle; ++ } ++}); ++ ++const getPentagonArrowPoints = (rectangle) => { ++ const wider = rectangle.width > rectangle.height / 2; ++ return [ ++ [rectangle.x, rectangle.y], ++ [rectangle.x + (wider ? rectangle.width - rectangle.height / 2 : 0), rectangle.y], ++ [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2], ++ [rectangle.x + (wider ? rectangle.width - rectangle.height / 2 : 0), rectangle.y + rectangle.height], ++ [rectangle.x, rectangle.y + rectangle.height] ++ ]; ++}; ++const PentagonArrowEngine = createPolygonEngine({ ++ getPolygonPoints: getPentagonArrowPoints, ++ getConnectorPoints(rectangle) { ++ return RectangleClient.getEdgeCenterPoints(rectangle); ++ }, ++ getTextRectangle: (board, element) => { ++ const elementRectangle = RectangleClient.getRectangleByPoints(element.points); ++ const customTextRectangle = getCustomTextRectangle(board, element, 3 / 4); ++ customTextRectangle.x = elementRectangle.x + ShapeDefaultSpace.rectangleAndText; ++ return customTextRectangle; ++ } ++}); ++ ++const getProcessArrowPoints = (rectangle) => { ++ const wider = rectangle.width > rectangle.height / 2; ++ return [ ++ [rectangle.x, rectangle.y], ++ [rectangle.x + (wider ? rectangle.width - rectangle.height / 2 : 0), rectangle.y], ++ [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2], ++ [rectangle.x + (wider ? rectangle.width - rectangle.height / 2 : 0), rectangle.y + rectangle.height], ++ [rectangle.x, rectangle.y + rectangle.height], ++ [rectangle.x + (wider ? rectangle.height / 2 : rectangle.width), rectangle.y + rectangle.height / 2] ++ ]; ++}; ++const ProcessArrowEngine = createPolygonEngine({ ++ getPolygonPoints: getProcessArrowPoints, ++ getTextRectangle: (board, element) => { ++ const elementRectangle = RectangleClient.getRectangleByPoints(element.points); ++ const wider = elementRectangle.width > elementRectangle.height + 20; ++ const widthRatio = wider ? (elementRectangle.width - elementRectangle.height) / elementRectangle.width : 3 / 4; ++ const customTextRectangle = getCustomTextRectangle(board, element, widthRatio); ++ return customTextRectangle; ++ } ++}); ++ ++const getRightArrowPoints = (rectangle) => { ++ return [ ++ [rectangle.x, rectangle.y + rectangle.height * 0.2], ++ [rectangle.x + rectangle.width * 0.68, rectangle.y + rectangle.height * 0.2], ++ [rectangle.x + rectangle.width * 0.68, rectangle.y], ++ [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2], ++ [rectangle.x + rectangle.width * 0.68, rectangle.y + rectangle.height], ++ [rectangle.x + rectangle.width * 0.68, rectangle.y + rectangle.height * 0.8], ++ [rectangle.x, rectangle.y + rectangle.height * 0.8] ++ ]; ++}; ++const RightArrowEngine = createPolygonEngine({ ++ getPolygonPoints: getRightArrowPoints, ++ getConnectorPoints: (rectangle) => { ++ return [ ++ [rectangle.x, rectangle.y + rectangle.height / 2], ++ [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2] ++ ]; ++ }, ++ getTextRectangle: (board, element) => { ++ const elementRectangle = RectangleClient.getRectangleByPoints(element.points); ++ const customTextRectangle = getCustomTextRectangle(board, element, 1 - 0.32); ++ customTextRectangle.x = elementRectangle.x + ShapeDefaultSpace.rectangleAndText; ++ return customTextRectangle; ++ } ++}); ++ ++const RectangleEngine = { ++ draw(board, rectangle, options) { ++ return drawRectangle(board, rectangle, { ...options, fillStyle: 'solid' }); ++ }, ++ isInsidePoint(rectangle, point) { ++ const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]); ++ return RectangleClient.isHit(rectangle, rangeRectangle); ++ }, ++ getCornerPoints(rectangle) { ++ return RectangleClient.getCornerPoints(rectangle); ++ }, ++ getNearestPoint(rectangle, point) { ++ return getNearestPointBetweenPointAndSegments(point, RectangleEngine.getCornerPoints(rectangle)); ++ }, ++ getEdgeByConnectionPoint(rectangle, pointOfRectangle) { ++ const corners = RectangleEngine.getCornerPoints(rectangle); ++ const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle); ++ return getPolygonEdgeByConnectionPoint(corners, point); ++ }, ++ getConnectorPoints(rectangle) { ++ return RectangleClient.getEdgeCenterPoints(rectangle); ++ } ++}; ++ ++const RoundRectangleEngine = { ++ draw(board, rectangle, options) { ++ return drawRoundRectangle(PlaitBoard.getRoughSVG(board), rectangle.x, rectangle.y, rectangle.x + rectangle.width, rectangle.y + rectangle.height, { ...options, fillStyle: 'solid' }, false, getRoundRectangleRadius(rectangle)); ++ }, ++ isInsidePoint(rectangle, point) { ++ return isPointInRoundRectangle(point, rectangle, getRoundRectangleRadius(rectangle)); ++ }, ++ getCornerPoints(rectangle) { ++ return RectangleClient.getCornerPoints(rectangle); ++ }, ++ getNearestPoint(rectangle, point) { ++ return getNearestPointBetweenPointAndRoundRectangle$1(point, rectangle, getRoundRectangleRadius(rectangle)); ++ }, ++ getEdgeByConnectionPoint(rectangle, pointOfRectangle) { ++ const corners = RectangleEngine.getCornerPoints(rectangle); ++ const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle); ++ return getPolygonEdgeByConnectionPoint(corners, point); ++ }, ++ getConnectorPoints(rectangle) { ++ return RectangleClient.getEdgeCenterPoints(rectangle); ++ } ++}; ++const getRoundRectangleRadius = (rectangle) => { ++ return Math.min(rectangle.width * 0.1, rectangle.height * 0.1); ++}; ++function getNearestPointBetweenPointAndRoundRectangle$1(point, rectangle, radius) { ++ const { x: rectX, y: rectY, width, height } = rectangle; ++ const cornerPoints = RectangleClient.getCornerPoints(rectangle); ++ let result = getNearestPointBetweenPointAndSegments(point, cornerPoints); ++ let circleCenter = null; ++ const inLeftTop = point[0] >= rectX && point[0] <= rectX + radius && point[1] >= rectY && point[1] <= rectY + radius; ++ if (inLeftTop) { ++ circleCenter = [rectX + radius, rectY + radius]; ++ } ++ const inLeftBottom = point[0] >= rectX && point[0] <= rectX + radius && point[1] >= rectY + height && point[1] <= rectY + height - radius; ++ if (inLeftBottom) { ++ circleCenter = [rectX + radius, rectY + height - radius]; ++ } ++ const inRightTop = point[0] >= rectX + width - radius && point[0] <= rectX + width && point[1] >= rectY && point[1] <= rectY + radius; ++ if (inRightTop) { ++ circleCenter = [rectX + width - radius, rectY + radius]; ++ } ++ const inRightBottom = point[0] >= rectX + width - radius && ++ point[0] <= rectX + width && ++ point[1] >= rectY + height - radius && ++ point[1] <= rectY + height; ++ if (inRightBottom) { ++ circleCenter = [rectX + width - radius, rectY + height - radius]; ++ } ++ if (circleCenter) { ++ result = getNearestPointBetweenPointAndEllipse(point, circleCenter, radius, radius); ++ } ++ return result; ++} ++ ++const heightRatio = 3 / 4; ++const RoundCommentEngine = { ++ draw(board, rectangle, options) { ++ const rs = PlaitBoard.getRoughSVG(board); ++ const x1 = rectangle.x; ++ const y1 = rectangle.y; ++ const x2 = rectangle.x + rectangle.width; ++ const y2 = rectangle.y + rectangle.height * heightRatio; ++ const radius = getRoundRectangleRadius(rectangle); ++ const point1 = [x1 + radius, y1]; ++ const point2 = [x2 - radius, y1]; ++ const point3 = [x2, y1 + radius]; ++ const point4 = [x2, y2 - radius]; ++ const point5 = [x2 - radius, y2]; ++ const point6 = [x1 + radius, y2]; ++ const point7 = [x1, y2 - radius]; ++ const point8 = [x1, y1 + radius]; ++ const point9 = [x1 + rectangle.width / 4, y2]; ++ const point10 = [x1 + rectangle.width / 4, rectangle.y + rectangle.height]; ++ const point11 = [x1 + rectangle.width / 2, y2]; ++ const shape = rs.path(`M${point2[0]} ${point2[1]} A ${radius} ${radius}, 0, 0, 1, ${point3[0]} ${point3[1]} L ${point4[0]} ${point4[1]} A ${radius} ${radius}, 0, 0, 1, ${point5[0]} ${point5[1]} L ${point11[0]} ${point11[1]} ${point10[0]} ${point10[1]} ${point9[0]} ${point9[1]} ${point6[0]} ${point6[1]} A ${radius} ${radius}, 0, 0, 1, ${point7[0]} ${point7[1]} L ${point8[0]} ${point8[1]} A ${radius} ${radius}, 0, 0, 1, ${point1[0]} ${point1[1]} Z`, { ...options, fillStyle: 'solid' }); ++ setStrokeLinecap(shape, 'round'); ++ return shape; ++ }, ++ isInsidePoint(rectangle, point) { ++ const points = [ ++ [rectangle.x + rectangle.width / 4, rectangle.y + (rectangle.height * 3) / 4], ++ [rectangle.x + rectangle.width / 4, rectangle.y + rectangle.height], ++ [rectangle.x + rectangle.width / 2, rectangle.y + (rectangle.height * 3) / 4] ++ ]; ++ rectangle.height = (rectangle.height * 3) / 4; ++ return isPointInPolygon(point, points) || isPointInRoundRectangle(point, rectangle, getRoundRectangleRadius(rectangle)); ++ }, ++ getCornerPoints(rectangle) { ++ return getRoundCommentPoints(rectangle); ++ }, ++ getNearestPoint(rectangle, point) { ++ return getNearestPointBetweenPointAndSegments(point, getRoundCommentPoints(rectangle)); ++ }, ++ getEdgeByConnectionPoint(rectangle, pointOfRectangle) { ++ const corners = getRoundCommentPoints(rectangle); ++ const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle); ++ return getPolygonEdgeByConnectionPoint(corners, point); ++ }, ++ getConnectorPoints(rectangle) { ++ return [ ++ [rectangle.x + rectangle.width / 2, rectangle.y], ++ [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2], ++ [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height * heightRatio], ++ [rectangle.x, rectangle.y + rectangle.height / 2] ++ ]; ++ }, ++ getTextRectangle: (board, element) => { ++ const elementRectangle = RectangleClient.getRectangleByPoints(element.points); ++ const textRectangle = getTextRectangle$1(board, element); ++ textRectangle.y = elementRectangle.y + (elementRectangle.height * heightRatio - textRectangle.height) / 2; ++ return textRectangle; ++ } ++}; ++const getRoundCommentPoints = (rectangle) => { ++ return [ ++ [rectangle.x, rectangle.y], ++ [rectangle.x + rectangle.width, rectangle.y], ++ [rectangle.x + rectangle.width, rectangle.y + rectangle.height * heightRatio], ++ [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height * heightRatio], ++ [rectangle.x + rectangle.width / 4, rectangle.y + rectangle.height], ++ [rectangle.x + rectangle.width / 4, rectangle.y + rectangle.height * heightRatio], ++ [rectangle.x, rectangle.y + rectangle.height * heightRatio] ++ ]; ++}; ++ ++const getTrapezoidPoints = (rectangle) => { ++ return [ ++ [rectangle.x + rectangle.width * 0.15, rectangle.y], ++ [rectangle.x + rectangle.width * 0.85, rectangle.y], ++ [rectangle.x + rectangle.width, rectangle.y + rectangle.height], ++ [rectangle.x, rectangle.y + rectangle.height] ++ ]; ++}; ++const TrapezoidEngine = createPolygonEngine({ ++ getPolygonPoints: getTrapezoidPoints, ++ getConnectorPoints(rectangle) { ++ const points = getTrapezoidPoints(rectangle); ++ return getCenterPointsOnPolygon$1(points); ++ }, ++ getTextRectangle(board, element) { ++ return getCustomTextRectangle(board, element, 3 / 4); ++ } ++}); ++ ++const getTrianglePoints = (rectangle) => { ++ return [ ++ [rectangle.x + rectangle.width / 2, rectangle.y], ++ [rectangle.x + rectangle.width, rectangle.y + rectangle.height], ++ [rectangle.x, rectangle.y + rectangle.height] ++ ]; ++}; ++const TriangleEngine = createPolygonEngine({ ++ getPolygonPoints: getTrianglePoints, ++ getConnectorPoints(rectangle) { ++ const cornerPoints = getTrianglePoints(rectangle); ++ const lineCenterPoints = getCenterPointsOnPolygon$1(cornerPoints); ++ return [...lineCenterPoints, ...cornerPoints]; ++ }, ++ getTextRectangle: (board, element) => { ++ const customTextRectangle = getCustomTextRectangle(board, element, 1 / 2); ++ const elementRectangle = RectangleClient.getRectangleByPoints(element.points); ++ customTextRectangle.y = ++ elementRectangle.y + ++ (elementRectangle.height * 2.5) / 5 + ++ (elementRectangle.height - (elementRectangle.height * 2.5) / 5 - customTextRectangle.height) / 2; ++ return customTextRectangle; ++ } ++}); ++ ++const getTwoWayArrowPoints = (rectangle) => { ++ return [ ++ [rectangle.x, rectangle.y + rectangle.height / 2], ++ [rectangle.x + (rectangle.width * 8) / 25, rectangle.y], ++ [rectangle.x + (rectangle.width * 8) / 25, rectangle.y + rectangle.height / 5], ++ [rectangle.x + (rectangle.width * 17) / 25, rectangle.y + rectangle.height / 5], ++ [rectangle.x + (rectangle.width * 17) / 25, rectangle.y], ++ [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2], ++ [rectangle.x + (rectangle.width * 17) / 25, rectangle.y + rectangle.height], ++ [rectangle.x + (rectangle.width * 17) / 25, rectangle.y + (rectangle.height * 4) / 5], ++ [rectangle.x + (rectangle.width * 8) / 25, rectangle.y + (rectangle.height * 4) / 5], ++ [rectangle.x + (rectangle.width * 8) / 25, rectangle.y + rectangle.height] ++ ]; ++}; ++const TwoWayArrowEngine = createPolygonEngine({ ++ getPolygonPoints: getTwoWayArrowPoints, ++ getConnectorPoints: (rectangle) => { ++ return [ ++ [rectangle.x, rectangle.y + rectangle.height / 2], ++ [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2] ++ ]; ++ } ++}); ++ ++const getStarPoints = (rectangle) => { ++ return [ ++ [rectangle.x + rectangle.width / 2, rectangle.y + (rectangle.height * 75) / 91], ++ [rectangle.x + (rectangle.width * 18.61) / 96, rectangle.y + rectangle.height], ++ [rectangle.x + (rectangle.width * 24.2235871) / 96, rectangle.y + (rectangle.height * 57.7254249) / 91], ++ [rectangle.x, rectangle.y + (rectangle.height * 34.5491503) / 91], ++ [rectangle.x + (rectangle.width * 33.3053687) / 96, rectangle.y + (rectangle.height * 29.7745751) / 91], ++ [rectangle.x + rectangle.width / 2, rectangle.y], ++ [rectangle.x + (rectangle.width * 62.6946313) / 96, rectangle.y + (rectangle.height * 29.7745751) / 91], ++ [rectangle.x + rectangle.width, rectangle.y + (rectangle.height * 34.5491503) / 91], ++ [rectangle.x + (rectangle.width * 71.7764129) / 96, rectangle.y + (rectangle.height * 57.7254249) / 91], ++ [rectangle.x + (rectangle.width * 77.3892626) / 96, rectangle.y + rectangle.height] ++ ]; ++}; ++const StarEngine = createPolygonEngine({ ++ getPolygonPoints: getStarPoints, ++ getConnectorPoints: (rectangle) => { ++ const points = getStarPoints(rectangle); ++ return [points[1], points[3], points[5], points[7], points[9]]; ++ }, ++ getTextRectangle: (board, element) => { ++ const elementRectangle = RectangleClient.getRectangleByPoints(element.points); ++ const customTextRectangle = getCustomTextRectangle(board, element, 1 / 2); ++ customTextRectangle.y = ++ elementRectangle.y + ++ elementRectangle.height / 5 + ++ (elementRectangle.height - elementRectangle.height / 5 - customTextRectangle.height) / 2; ++ return customTextRectangle; ++ } ++}); ++ ++const TerminalEngine = { ++ draw(board, rectangle, options) { ++ return drawRoundRectangle(PlaitBoard.getRoughSVG(board), rectangle.x, rectangle.y, rectangle.x + rectangle.width, rectangle.y + rectangle.height, { ...options, fillStyle: 'solid' }, false, getStartEndRadius(rectangle)); ++ }, ++ isInsidePoint(rectangle, point) { ++ return isPointInRoundRectangle(point, rectangle, getStartEndRadius(rectangle)); ++ }, ++ getCornerPoints(rectangle) { ++ return RectangleClient.getCornerPoints(rectangle); ++ }, ++ getNearestPoint(rectangle, point) { ++ return getNearestPointBetweenPointAndRoundRectangle(point, rectangle, getStartEndRadius(rectangle)); ++ }, ++ getTangentVectorByConnectionPoint(rectangle, pointOfRectangle) { ++ const connectionPoint = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle); ++ const radius = getStartEndRadius(rectangle); ++ const center = getBoundCenterOfRoundRectangle(rectangle, radius, connectionPoint); ++ if (center) { ++ const point = [connectionPoint[0] - center[0], -(connectionPoint[1] - center[1])]; ++ const a = radius; ++ const b = radius; ++ const slope = getEllipseTangentSlope(point[0], point[1], a, b); ++ return getVectorFromPointAndSlope(point[0], point[1], slope); ++ } ++ return null; ++ }, ++ getConnectorPoints(rectangle) { ++ return RectangleClient.getEdgeCenterPoints(rectangle); ++ } ++}; ++const getStartEndRadius = (rectangle) => { ++ return Math.min(rectangle.width / 2, rectangle.height / 2); ++}; ++function getNearestPointBetweenPointAndRoundRectangle(point, rectangle, radius) { ++ let result = null; ++ let boundCenter = getBoundCenterOfRoundRectangle(rectangle, radius, point); ++ if (boundCenter) { ++ result = getNearestPointBetweenPointAndEllipse(point, boundCenter, radius, radius); ++ } ++ else { ++ const cornerPoints = RectangleClient.getCornerPoints(rectangle); ++ result = getNearestPointBetweenPointAndSegments(point, cornerPoints); ++ } ++ return result; ++} ++function getBoundCenterOfRoundRectangle(rectangle, radius, point) { ++ const { x, y, width, height } = rectangle; ++ let center = null; ++ const inLeftTop = point[0] >= x && point[0] <= x + radius && point[1] >= y && point[1] <= y + radius; ++ if (inLeftTop) { ++ center = [x + radius, y + radius]; ++ } ++ const inLeftBottom = point[0] >= x && point[0] <= x + radius && point[1] >= y + height - radius && point[1] <= y + height; ++ if (inLeftBottom) { ++ center = [x + radius, y + height - radius]; ++ } ++ const inRightTop = point[0] >= x + width - radius && point[0] <= x + width && point[1] >= y && point[1] <= y + radius; ++ if (inRightTop) { ++ center = [x + width - radius, y + radius]; ++ } ++ const inRightBottom = point[0] >= x + width - radius && point[0] <= x + width && point[1] >= y + height - radius && point[1] <= y + height; ++ if (inRightBottom) { ++ center = [x + width - radius, y + height - radius]; ++ } ++ return center; ++} ++ ++const getManualInputPoints = (rectangle) => { ++ return [ ++ [rectangle.x, rectangle.y + rectangle.height / 4], ++ [rectangle.x + rectangle.width, rectangle.y], ++ [rectangle.x + rectangle.width, rectangle.y + rectangle.height], ++ [rectangle.x, rectangle.y + rectangle.height] ++ ]; ++}; ++const ManualInputEngine = createPolygonEngine({ ++ getPolygonPoints: getManualInputPoints, ++ getConnectorPoints: (rectangle) => { ++ const cornerPoints = getManualInputPoints(rectangle); ++ return getCenterPointsOnPolygon$1(cornerPoints); ++ }, ++ getTextRectangle: (board, element) => { ++ const elementRectangle = RectangleClient.getRectangleByPoints(element.points); ++ const strokeWidth = getStrokeWidthByElement(element); ++ const width = elementRectangle.width - ShapeDefaultSpace.rectangleAndText * 2 - strokeWidth * 2; ++ const text = element.text; ++ const textSize = getTextSize(board, text, width); ++ return { ++ height: textSize.height, ++ width: width > 0 ? width : 0, ++ x: elementRectangle.x + ShapeDefaultSpace.rectangleAndText + strokeWidth, ++ y: elementRectangle.y + elementRectangle.height / 4 + ((elementRectangle.height * 3) / 4 - textSize.height) / 2 ++ }; ++ } ++}); ++ ++const getPreparationPoints = (rectangle) => { ++ return [ ++ [rectangle.x, rectangle.y + rectangle.height / 2], ++ [rectangle.x + rectangle.width / 6, rectangle.y], ++ [rectangle.x + (rectangle.width * 5) / 6, rectangle.y], ++ [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2], ++ [rectangle.x + (rectangle.width * 5) / 6, rectangle.y + rectangle.height], ++ [rectangle.x + rectangle.width / 6, rectangle.y + rectangle.height] ++ ]; ++}; ++const PreparationEngine = createPolygonEngine({ ++ getPolygonPoints: getPreparationPoints, ++ getConnectorPoints: (rectangle) => { ++ return RectangleClient.getEdgeCenterPoints(rectangle); ++ }, ++ getTextRectangle(board, element) { ++ const rectangle = getCustomTextRectangle(board, element, 2 / 3); ++ return rectangle; ++ } ++}); ++ ++const getManualLoopPoints = (rectangle) => { ++ return [ ++ [rectangle.x, rectangle.y], ++ [rectangle.x + rectangle.width, rectangle.y], ++ [rectangle.x + (rectangle.width * 7) / 8, rectangle.y + rectangle.height], ++ [rectangle.x + rectangle.width / 8, rectangle.y + rectangle.height] ++ ]; ++}; ++const ManualLoopEngine = createPolygonEngine({ ++ getPolygonPoints: getManualLoopPoints, ++ getConnectorPoints: (rectangle) => { ++ const cornerPoints = getManualLoopPoints(rectangle); ++ return getCenterPointsOnPolygon$1(cornerPoints); ++ }, ++ getTextRectangle: (board, element) => { ++ const rectangle = getCustomTextRectangle(board, element, 3 / 4); ++ return rectangle; ++ } ++}); ++ ++const getMergePoints = (rectangle) => { ++ return [ ++ [rectangle.x, rectangle.y], ++ [rectangle.x + rectangle.width, rectangle.y], ++ [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height] ++ ]; ++}; ++const MergeEngine = createPolygonEngine({ ++ getPolygonPoints: getMergePoints, ++ getConnectorPoints: (rectangle) => { ++ const cornerPoints = getMergePoints(rectangle); ++ const lineCenterPoints = getCenterPointsOnPolygon$1(cornerPoints); ++ return [...lineCenterPoints, ...cornerPoints]; ++ }, ++ getTextRectangle: (board, element) => { ++ const elementRectangle = RectangleClient.getRectangleByPoints(element.points); ++ const strokeWidth = getStrokeWidthByElement(element); ++ const originWidth = elementRectangle.width - ShapeDefaultSpace.rectangleAndText * 2 - strokeWidth * 2; ++ const width = (originWidth * 2) / 3; ++ const text = element.text; ++ const textSize = getTextSize(board, text, width); ++ return { ++ height: textSize.height, ++ width: width > 0 ? width : 0, ++ x: elementRectangle.x + ShapeDefaultSpace.rectangleAndText + strokeWidth + originWidth / 6, ++ y: elementRectangle.y + ((elementRectangle.height * 2) / 3 - textSize.height) / 2 ++ }; ++ } ++}); ++ ++const DelayEngine = { ++ draw(board, rectangle, options) { ++ const rs = PlaitBoard.getRoughSVG(board); ++ const shape = rs.path(`M${rectangle.x} ${rectangle.y} L${rectangle.x + (rectangle.width * 3) / 4} ${rectangle.y} A ${rectangle.width / ++ 4} ${rectangle.height / 2}, 0, 0, 1,${rectangle.x + (rectangle.width * 3) / 4} ${rectangle.y + rectangle.height} L${rectangle.x} ${rectangle.y + rectangle.height} Z`, { ...options, fillStyle: 'solid' }); ++ setStrokeLinecap(shape, 'round'); ++ return shape; ++ }, ++ isInsidePoint(rectangle, point) { ++ //split shape to rectangle and a half ellipse ++ const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]); ++ const isInRectangle = RectangleClient.isHit({ ++ ...rectangle, ++ width: (rectangle.width * 3) / 4 ++ }, rangeRectangle); ++ const isInEllipse = isPointInEllipse(point, [rectangle.x + (rectangle.width * 3) / 4, rectangle.y + rectangle.height / 2], rectangle.width / 4, rectangle.height / 2); ++ return isInRectangle || isInEllipse; ++ }, ++ getCornerPoints(rectangle) { ++ return RectangleClient.getCornerPoints(rectangle); ++ }, ++ getNearestPoint(rectangle, point) { ++ const nearestPoint = getNearestPointBetweenPointAndSegments(point, RectangleEngine.getCornerPoints(rectangle)); ++ if (nearestPoint[0] > rectangle.x + (rectangle.width * 3) / 4) { ++ return getNearestPointBetweenPointAndEllipse(point, [rectangle.x + (rectangle.width * 3) / 4, rectangle.y + rectangle.height / 2], rectangle.width / 4, rectangle.height / 2); ++ } ++ return nearestPoint; ++ }, ++ getTangentVectorByConnectionPoint(rectangle, pointOfRectangle) { ++ const connectionPoint = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle); ++ const centerPoint = [rectangle.x + (rectangle.width * 3) / 4, rectangle.y + rectangle.height / 2]; ++ const point = [connectionPoint[0] - centerPoint[0], -(connectionPoint[1] - centerPoint[1])]; ++ const a = rectangle.width / 4; ++ const b = rectangle.height / 2; ++ const slope = getEllipseTangentSlope(point[0], point[1], a, b); ++ return getVectorFromPointAndSlope(point[0], point[1], slope); ++ }, ++ getConnectorPoints(rectangle) { ++ return RectangleClient.getEdgeCenterPoints(rectangle); ++ } ++}; ++ ++const StoredDataEngine = { ++ draw(board, rectangle, options) { ++ const rs = PlaitBoard.getRoughSVG(board); ++ const shape = rs.path(`M${rectangle.x + rectangle.width / 10} ${rectangle.y} L${rectangle.x + rectangle.width} ${rectangle.y} A ${rectangle.width / 10} ${rectangle.height / 2}, 0, 0, 0,${rectangle.x + rectangle.width} ${rectangle.y + rectangle.height} L${rectangle.x + rectangle.width / 10} ${rectangle.y + rectangle.height}A ${rectangle.width / 10} ${rectangle.height / 2}, 0, 0, 1,${rectangle.x + rectangle.width / 10} ${rectangle.y}`, { ...options, fillStyle: 'solid' }); ++ setStrokeLinecap(shape, 'round'); ++ return shape; ++ }, ++ isInsidePoint(rectangle, point) { ++ //split shape to rectangle and a half ellipse ++ const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]); ++ const isInRectangle = RectangleClient.isHit({ ++ ...rectangle, ++ x: rectangle.x + rectangle.width / 10, ++ width: (rectangle.width * 9) / 10 ++ }, rangeRectangle); ++ const isInFrontEllipse = isPointInEllipse(point, [rectangle.x + rectangle.width / 10, rectangle.y + rectangle.height / 2], rectangle.width / 10, rectangle.height / 2); ++ const notInBackEllipse = !isPointInEllipse(point, [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2], rectangle.width / 10, rectangle.height / 2); ++ return (isInRectangle && notInBackEllipse) || isInFrontEllipse; ++ }, ++ getCornerPoints(rectangle) { ++ return RectangleClient.getCornerPoints(rectangle); ++ }, ++ getNearestPoint(rectangle, point) { ++ const nearestPoint = getNearestPointBetweenPointAndSegments(point, RectangleEngine.getCornerPoints(rectangle)); ++ if (nearestPoint[0] < rectangle.x + rectangle.width / 10) { ++ const centerPoint = [rectangle.x + rectangle.width / 10, rectangle.y + rectangle.height / 2]; ++ const nearestPoint = getNearestPointBetweenPointAndEllipse(point, centerPoint, rectangle.width / 10, rectangle.height / 2); ++ if (nearestPoint[0] > centerPoint[0]) { ++ nearestPoint[0] = centerPoint[0] * 2 - nearestPoint[0]; ++ } ++ return nearestPoint; ++ } ++ if (nearestPoint[0] > rectangle.x + (rectangle.width * 9) / 10) { ++ const centerPoint = [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2]; ++ const nearestPoint = getNearestPointBetweenPointAndEllipse(point, centerPoint, rectangle.width / 10, rectangle.height / 2); ++ if (nearestPoint[0] > centerPoint[0]) { ++ nearestPoint[0] = centerPoint[0] * 2 - nearestPoint[0]; ++ } ++ return nearestPoint; ++ } ++ return nearestPoint; ++ }, ++ getTangentVectorByConnectionPoint(rectangle, pointOfRectangle) { ++ const connectionPoint = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle); ++ let centerPoint = [rectangle.x + rectangle.width / 10, rectangle.y + rectangle.height / 2]; ++ let a = rectangle.width / 10; ++ let b = rectangle.height / 2; ++ const isBackEllipse = connectionPoint[0] > rectangle.x + (rectangle.width * 9) / 10 && connectionPoint[1] > rectangle.y; ++ if (isBackEllipse) { ++ centerPoint = [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2]; ++ } ++ const point = [connectionPoint[0] - centerPoint[0], -(connectionPoint[1] - centerPoint[1])]; ++ const slope = getEllipseTangentSlope(point[0], point[1], a, b); ++ const vector = getVectorFromPointAndSlope(point[0], point[1], slope); ++ return isBackEllipse ? vector.map((num) => -num) : vector; ++ }, ++ getConnectorPoints(rectangle) { ++ return [ ++ [rectangle.x + rectangle.width / 2, rectangle.y], ++ [rectangle.x + (rectangle.width * 9) / 10, rectangle.y + rectangle.height / 2], ++ [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height], ++ [rectangle.x, rectangle.y + rectangle.height / 2] ++ ]; ++ }, ++ getTextRectangle(board, element) { ++ const widthRatio = 3 / 4; ++ return getCustomTextRectangle(board, element, widthRatio); ++ } ++}; ++ ++const PredefinedProcessEngine = { ++ draw(board, rectangle, options) { ++ const rs = PlaitBoard.getRoughSVG(board); ++ const shape = rs.path(`M${rectangle.x} ${rectangle.y} H${rectangle.x + rectangle.width} V${rectangle.y + rectangle.height} H${rectangle.x} Z M${rectangle.x + rectangle.width * 0.06} ${rectangle.y} L${rectangle.x + rectangle.width * 0.06} ${rectangle.y + ++ rectangle.height} M${rectangle.x + rectangle.width - rectangle.width * 0.06} ${rectangle.y} L${rectangle.x + ++ rectangle.width - ++ rectangle.width * 0.06} ${rectangle.y + rectangle.height}`, { ...options, fillStyle: 'solid' }); ++ setStrokeLinecap(shape, 'round'); ++ return shape; ++ }, ++ isInsidePoint(rectangle, point) { ++ const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]); ++ return RectangleClient.isHit(rectangle, rangeRectangle); ++ }, ++ getCornerPoints(rectangle) { ++ return RectangleClient.getCornerPoints(rectangle); ++ }, ++ getNearestPoint(rectangle, point) { ++ return getNearestPointBetweenPointAndSegments(point, RectangleEngine.getCornerPoints(rectangle)); ++ }, ++ getEdgeByConnectionPoint(rectangle, pointOfRectangle) { ++ const corners = RectangleEngine.getCornerPoints(rectangle); ++ const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle); ++ return getPolygonEdgeByConnectionPoint(corners, point); ++ }, ++ getConnectorPoints(rectangle) { ++ return RectangleClient.getEdgeCenterPoints(rectangle); ++ }, ++ getTextRectangle: (board, element) => { ++ const elementRectangle = RectangleClient.getRectangleByPoints(element.points); ++ const strokeWidth = getStrokeWidthByElement(element); ++ const width = elementRectangle.width - ShapeDefaultSpace.rectangleAndText * 2 - strokeWidth * 2 - elementRectangle.width * 0.06 * 2; ++ const text = element.text; ++ const textSize = getTextSize(board, text, width); ++ return { ++ height: textSize.height, ++ width: width > 0 ? width : 0, ++ x: elementRectangle.x + ShapeDefaultSpace.rectangleAndText + strokeWidth + elementRectangle.width * 0.06, ++ y: elementRectangle.y + (elementRectangle.height - textSize.height) / 2 ++ }; ++ } ++}; ++ ++const getOffPagePoints = (rectangle) => { ++ return [ ++ [rectangle.x, rectangle.y], ++ [rectangle.x + rectangle.width, rectangle.y], ++ [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2], ++ [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height], ++ [rectangle.x, rectangle.y + rectangle.height / 2] ++ ]; ++}; ++const OffPageEngine = createPolygonEngine({ ++ getPolygonPoints: getOffPagePoints, ++ getConnectorPoints: (rectangle) => { ++ return RectangleClient.getEdgeCenterPoints(rectangle); ++ }, ++ getTextRectangle: (board, element) => { ++ const elementRectangle = RectangleClient.getRectangleByPoints(element.points); ++ const strokeWidth = getStrokeWidthByElement(element); ++ const width = elementRectangle.width - ShapeDefaultSpace.rectangleAndText * 2 - strokeWidth * 2; ++ const text = element.text; ++ const textSize = getTextSize(board, text, width); ++ return { ++ width: width > 0 ? width : 0, ++ height: textSize.height, ++ x: elementRectangle.x + ShapeDefaultSpace.rectangleAndText + strokeWidth, ++ y: elementRectangle.y + (elementRectangle.height - elementRectangle.height / 2 - textSize.height) / 2 ++ }; ++ } ++}); ++ ++const OrEngine = createEllipseEngine({ ++ draw(board, rectangle, options) { ++ const rs = PlaitBoard.getRoughSVG(board); ++ const rx = rectangle.width / 2; ++ const ry = rectangle.height / 2; ++ const startPoint = [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2]; ++ const shape = rs.path(`M${startPoint[0]} ${startPoint[1]} ++ A${rx},${ry} 0 1,1 ${startPoint[0]} ${startPoint[1] - 0.01} ++ M${rectangle.x} ${rectangle.y + rectangle.height / 2} ++ L${rectangle.x + rectangle.width} ${rectangle.y + rectangle.height / 2} ++ M${rectangle.x + rectangle.width / 2} ${rectangle.y} ++ L${rectangle.x + rectangle.width / 2} ${rectangle.y + rectangle.height} ++ `, { ...options, fillStyle: 'solid' }); ++ setStrokeLinecap(shape, 'round'); ++ return shape; ++ } ++}); ++ ++const SummingJunctionEngine = createEllipseEngine({ ++ draw(board, rectangle, options) { ++ const rs = PlaitBoard.getRoughSVG(board); ++ const rx = rectangle.width / 2; ++ const ry = rectangle.height / 2; ++ const startPoint = [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2]; ++ const centerPoint = [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height / 2]; ++ const line1Points = getCrossingPointsBetweenEllipseAndSegment([rectangle.x, rectangle.y], [rectangle.x + rectangle.width, rectangle.y + rectangle.height], centerPoint[0], centerPoint[1], rx, ry); ++ const line2Points = getCrossingPointsBetweenEllipseAndSegment([rectangle.x + rectangle.width, rectangle.y], [rectangle.x, rectangle.y + rectangle.height], centerPoint[0], centerPoint[1], rx, ry); ++ const shape = rs.path(`M${startPoint[0]} ${startPoint[1]} ++ A${rx},${ry} 0 1,1 ${startPoint[0]} ${startPoint[1] - 0.01} ++ M${line1Points[0][0]} ${line1Points[0][1]} ++ L${line1Points[1][0]} ${line1Points[1][1]} ++ M${line2Points[0][0]} ${line2Points[0][1]} ++ L${line2Points[1][0]} ${line2Points[1][1]} ++ `, { ...options, fillStyle: 'solid' }); ++ setStrokeLinecap(shape, 'round'); ++ return shape; ++ } ++}); ++ ++const DocumentEngine = { ++ draw(board, rectangle, options) { ++ const rs = PlaitBoard.getRoughSVG(board); ++ const shape = rs.path(`M${rectangle.x} ${rectangle.y + rectangle.height - rectangle.height / 9} V${rectangle.y} H${rectangle.x + ++ rectangle.width} V${rectangle.y + rectangle.height - rectangle.height / 9} ++ Q${rectangle.x + rectangle.width - rectangle.width / 4} ${rectangle.y + ++ rectangle.height - ++ (rectangle.height / 9) * 3}, ${rectangle.x + rectangle.width / 2} ${rectangle.y + ++ rectangle.height - ++ rectangle.height / 9} T${rectangle.x} ${rectangle.y + rectangle.height - rectangle.height / 9} ++ `, { ...options, fillStyle: 'solid' }); ++ setStrokeLinecap(shape, 'round'); ++ return shape; ++ }, ++ isInsidePoint(rectangle, point) { ++ const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]); ++ return RectangleClient.isHit(rectangle, rangeRectangle); ++ }, ++ getCornerPoints(rectangle) { ++ return RectangleClient.getCornerPoints(rectangle); ++ }, ++ getNearestPoint(rectangle, point) { ++ let nearestPoint = getNearestPointBetweenPointAndSegments(point, RectangleEngine.getCornerPoints(rectangle)); ++ let curvePoints = catmullRomFitting([ ++ [rectangle.x, rectangle.y + rectangle.height - rectangle.height / 9], ++ [rectangle.x + rectangle.width / 4, rectangle.y + rectangle.height], ++ [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height - rectangle.height / 9], ++ [rectangle.x + (rectangle.width / 4) * 3, rectangle.y + rectangle.height - (rectangle.height / 9) * 2], ++ [rectangle.x + rectangle.width, rectangle.y + rectangle.height - rectangle.height / 9] ++ ]); ++ curvePoints = pointsOnBezierCurves(curvePoints); ++ if (nearestPoint[1] > rectangle.y + rectangle.height - rectangle.height / 9) { ++ if (nearestPoint[0] === rectangle.x + rectangle.width / 2) { ++ nearestPoint[1] = rectangle.y + rectangle.height - rectangle.height / 9; ++ return nearestPoint; ++ } ++ nearestPoint = getNearestPointBetweenPointAndSegments(point, curvePoints, false); ++ } ++ return nearestPoint; ++ }, ++ getConnectorPoints(rectangle) { ++ return [ ++ [rectangle.x + rectangle.width / 2, rectangle.y], ++ [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2], ++ [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height - rectangle.height / 9], ++ [rectangle.x, rectangle.y + rectangle.height / 2] ++ ]; ++ }, ++ getTangentVectorByConnectionPoint(rectangle, pointOfRectangle) { ++ const connectionPoint = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle); ++ if (connectionPoint[0] > rectangle.x && connectionPoint[0] < rectangle.x + rectangle.width / 4) { ++ return getUnitVectorByPointAndPoint([rectangle.x + rectangle.width / 4, rectangle.y + rectangle.height], connectionPoint); ++ } ++ if (connectionPoint[0] > rectangle.x + rectangle.width / 4 && connectionPoint[0] < rectangle.x + (rectangle.width / 4) * 3) { ++ return getUnitVectorByPointAndPoint([rectangle.x + (rectangle.width / 4) * 3, rectangle.y + rectangle.height - rectangle.height / 9], [rectangle.x + rectangle.width / 4, rectangle.y + rectangle.height]); ++ } ++ if (connectionPoint[0] > rectangle.x + (rectangle.width / 4) * 3) { ++ return getUnitVectorByPointAndPoint([rectangle.x + rectangle.width, rectangle.y + rectangle.height - rectangle.height / 9], connectionPoint); ++ } ++ return getUnitVectorByPointAndPoint(connectionPoint, [rectangle.x + rectangle.width / 4, rectangle.y + rectangle.height]); ++ }, ++ getTextRectangle: (board, element) => { ++ return getCustomTextRectangle(board, element, 0.88); ++ } ++}; ++ ++const getMultiDocumentPoints = (rectangle) => { ++ const linePoints = [ ++ [rectangle.x, rectangle.y + 10], ++ [rectangle.x + 5, rectangle.y + 10], ++ [rectangle.x + 5, rectangle.y + 5], ++ [rectangle.x + 10, rectangle.y + 5], ++ [rectangle.x + 10, rectangle.y], ++ [rectangle.x + rectangle.width, rectangle.y], ++ [rectangle.x + rectangle.width, rectangle.y + rectangle.height - rectangle.height / 9 - 10 - 3], ++ [rectangle.x + rectangle.width - 5, rectangle.y + rectangle.height - rectangle.height / 9 - 10 - 3 - 4], ++ [rectangle.x + rectangle.width - 5, rectangle.y + rectangle.height - rectangle.height / 9 - 5 - 3], ++ [rectangle.x + rectangle.width - 10, rectangle.y + rectangle.height - rectangle.height / 9 - 5 - 3 - 4], ++ [rectangle.x + rectangle.width - 10, rectangle.y + rectangle.height - rectangle.height / 9] ++ ]; ++ let curvePoints = catmullRomFitting([ ++ [rectangle.x + rectangle.width - 10, rectangle.y + rectangle.height - rectangle.height / 9], ++ [rectangle.x + rectangle.width - 10 - (rectangle.width - 10) / 4, rectangle.y + rectangle.height - (rectangle.height / 9) * 2], ++ [rectangle.x + (rectangle.width - 10) / 2, rectangle.y + rectangle.height - rectangle.height / 9], ++ [rectangle.x + (rectangle.width - 10) / 4, rectangle.y + rectangle.height], ++ [rectangle.x, rectangle.y + rectangle.height - rectangle.height / 9] ++ ]); ++ curvePoints = pointsOnBezierCurves(curvePoints); ++ return [...linePoints, ...curvePoints]; ++}; ++const MultiDocumentEngine = { ++ draw(board, rectangle, options) { ++ const rs = PlaitBoard.getRoughSVG(board); ++ const shape = rs.path(`M${rectangle.x} ${rectangle.y + rectangle.height - rectangle.height / 9} V${rectangle.y + 10} H${rectangle.x + 5} V${rectangle.y + 5} H${rectangle.x + 10} V${rectangle.y} H${rectangle.x + rectangle.width} V${rectangle.y + rectangle.height - rectangle.height / 9 - 10 - 3} L${rectangle.x + rectangle.width - 5} ${rectangle.y + rectangle.height - rectangle.height / 9 - 10 - 3 - 4} V${rectangle.y + rectangle.height - rectangle.height / 9 - 5 - 3} ++ L${rectangle.x + rectangle.width - 10} ${rectangle.y + rectangle.height - rectangle.height / 9 - 5 - 3 - 4} V${rectangle.y + rectangle.height - rectangle.height / 9} ++ ++ Q${rectangle.x + rectangle.width - 10 - (rectangle.width - 10) / 4} ${rectangle.y + rectangle.height - (rectangle.height / 9) * 3}, ${rectangle.x + (rectangle.width - 10) / 2} ${rectangle.y + rectangle.height - rectangle.height / 9} T${rectangle.x} ${rectangle.y + rectangle.height - rectangle.height / 9} ++ ++ M${rectangle.x + 5} ${rectangle.y + 10} H${rectangle.x + rectangle.width - 10} V${rectangle.y + rectangle.height - rectangle.height / 9} ++ ++ M${rectangle.x + 10} ${rectangle.y + 5} H${rectangle.x + rectangle.width - 5} V${rectangle.y + rectangle.height - rectangle.height / 9 - 10 - 3} ++ `, { ...options, fillStyle: 'solid' }); ++ setStrokeLinecap(shape, 'round'); ++ return shape; ++ }, ++ isInsidePoint(rectangle, point) { ++ const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]); ++ return RectangleClient.isHit(rectangle, rangeRectangle); ++ }, ++ getCornerPoints(rectangle) { ++ return RectangleClient.getCornerPoints(rectangle); ++ }, ++ getNearestPoint(rectangle, point) { ++ return getNearestPointBetweenPointAndSegments(point, getMultiDocumentPoints(rectangle), false); ++ }, ++ getConnectorPoints(rectangle) { ++ let curvePoints = catmullRomFitting([ ++ [rectangle.x, rectangle.y + rectangle.height - rectangle.height / 9], ++ [rectangle.x + (rectangle.width - 10) / 4, rectangle.y + rectangle.height], ++ [rectangle.x + (rectangle.width - 10) / 2, rectangle.y + rectangle.height - rectangle.height / 9], ++ [rectangle.x + ((rectangle.width - 10) / 4) * 3, rectangle.y + rectangle.height - (rectangle.height / 9) * 2], ++ [rectangle.x + rectangle.width - 10, rectangle.y + rectangle.height - rectangle.height / 9] ++ ]); ++ curvePoints = pointsOnBezierCurves(curvePoints); ++ const crossingPoint = getNearestPointBetweenPointAndSegments([rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height], curvePoints); ++ return [ ++ [rectangle.x + rectangle.width / 2, rectangle.y], ++ [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2], ++ [crossingPoint[0], crossingPoint[1]], ++ [rectangle.x, rectangle.y + rectangle.height / 2] ++ ]; ++ }, ++ getNearestCrossingPoint(rectangle, point) { ++ const crossingPoints = getCrossingPointBetweenPointAndPolygon(getMultiDocumentPoints(rectangle), point); ++ let nearestPoint = crossingPoints[0]; ++ let nearestDistance = distanceBetweenPointAndPoint(point[0], point[1], nearestPoint[0], nearestPoint[1]); ++ crossingPoints ++ .filter((v, index) => index > 0) ++ .forEach((crossingPoint) => { ++ let distance = distanceBetweenPointAndPoint(point[0], point[1], crossingPoint[0], crossingPoint[1]); ++ if (distance < nearestDistance) { ++ nearestDistance = distance; ++ nearestPoint = crossingPoint; ++ } ++ }); ++ return nearestPoint; ++ }, ++ getTangentVectorByConnectionPoint(rectangle, pointOfRectangle) { ++ const connectionPoint = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle); ++ if (connectionPoint[0] > rectangle.x && connectionPoint[0] < rectangle.x + (rectangle.width - 10) / 4) { ++ return getUnitVectorByPointAndPoint([rectangle.x + (rectangle.width - 10) / 4, rectangle.y + rectangle.height], connectionPoint); ++ } ++ if (connectionPoint[0] > rectangle.x + (rectangle.width - 10) / 4 && ++ connectionPoint[0] < rectangle.x + ((rectangle.width - 10) / 4) * 3) { ++ return getUnitVectorByPointAndPoint([rectangle.x + ((rectangle.width - 10) / 4) * 3, rectangle.y + rectangle.height - rectangle.height / 9], [rectangle.x + (rectangle.width - 10) / 4, rectangle.y + rectangle.height]); ++ } ++ if (connectionPoint[0] > rectangle.x + ((rectangle.width - 10) / 4) * 3 && ++ connectionPoint[0] < rectangle.x + rectangle.width - 10) { ++ return getUnitVectorByPointAndPoint([rectangle.x + (rectangle.width - 10), rectangle.y + rectangle.height - rectangle.height / 9], connectionPoint); ++ } ++ const direction = getDirectionByPointOfRectangle(pointOfRectangle) || Direction.bottom; ++ const factor = getDirectionFactor(direction); ++ return [factor.x, factor.y]; ++ }, ++ getTextRectangle: (board, element) => { ++ return getCustomTextRectangle(board, element, 0.88); ++ } ++}; ++ ++const DatabaseEngine = { ++ draw(board, rectangle, options) { ++ const rs = PlaitBoard.getRoughSVG(board); ++ const shape = rs.path(`M${rectangle.x} ${rectangle.y + rectangle.height * 0.15} ++ A${rectangle.width / 2} ${rectangle.height * 0.15}, 0, 0, 0,${rectangle.x + rectangle.width} ${rectangle.y + rectangle.height * 0.15} ++ A${rectangle.width / 2} ${rectangle.height * 0.15}, 0, 0, 0,${rectangle.x} ${rectangle.y + rectangle.height * 0.15} ++ V${rectangle.y + rectangle.height - rectangle.height * 0.15} ++ A${rectangle.width / 2} ${rectangle.height * 0.15}, 0, 0, 0, ${rectangle.x + rectangle.width} ${rectangle.y + rectangle.height - rectangle.height * 0.15} ++ V${rectangle.y + rectangle.height * 0.15}`, { ...options, fillStyle: 'solid' }); ++ setStrokeLinecap(shape, 'round'); ++ return shape; ++ }, ++ isInsidePoint(rectangle, point) { ++ const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]); ++ const isInRectangle = RectangleClient.isHit({ ++ ...rectangle, ++ y: rectangle.y + rectangle.height * 0.15, ++ height: rectangle.height - rectangle.height * 0.3 ++ }, rangeRectangle); ++ const isInTopEllipse = isPointInEllipse(point, [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height * 0.15], rectangle.width / 2, rectangle.height * 0.15); ++ const isInBottomEllipse = isPointInEllipse(point, [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height - rectangle.height * 0.15], rectangle.width / 2, rectangle.height * 0.15); ++ return isInRectangle || isInTopEllipse || isInBottomEllipse; ++ }, ++ getCornerPoints(rectangle) { ++ return RectangleClient.getCornerPoints(rectangle); ++ }, ++ getNearestPoint(rectangle, point) { ++ const nearestPoint = getNearestPointBetweenPointAndSegments(point, RectangleEngine.getCornerPoints(rectangle)); ++ if (nearestPoint[1] < rectangle.y + rectangle.height * 0.15) { ++ const centerPoint = [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height * 0.15]; ++ const nearestPoint = getNearestPointBetweenPointAndEllipse(point, centerPoint, rectangle.width / 2, rectangle.height * 0.15); ++ if (nearestPoint[1] > centerPoint[1]) { ++ nearestPoint[1] = centerPoint[1] * 2 - nearestPoint[1]; ++ } ++ return nearestPoint; ++ } ++ if (nearestPoint[1] > rectangle.y + rectangle.height - rectangle.height * 0.15) { ++ const centerPoint = [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height - rectangle.height * 0.15]; ++ const nearestPoint = getNearestPointBetweenPointAndEllipse(point, centerPoint, rectangle.width / 2, rectangle.height * 0.15); ++ if (nearestPoint[1] < centerPoint[1]) { ++ nearestPoint[1] = centerPoint[0] * 2 - nearestPoint[1]; ++ } ++ return nearestPoint; ++ } ++ return nearestPoint; ++ }, ++ getConnectorPoints(rectangle) { ++ return RectangleClient.getEdgeCenterPoints(rectangle); ++ }, ++ getTangentVectorByConnectionPoint(rectangle, pointOfRectangle) { ++ const connectionPoint = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle); ++ let centerPoint = [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height - rectangle.height * 0.15]; ++ let a = rectangle.width / 2; ++ let b = rectangle.height * 0.15; ++ const isInTopEllipse = connectionPoint[1] < rectangle.y + rectangle.height * 0.15 && connectionPoint[0] > rectangle.x; ++ if (isInTopEllipse) { ++ centerPoint = [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height * 0.15]; ++ } ++ const point = [connectionPoint[0] - centerPoint[0], -(connectionPoint[1] - centerPoint[1])]; ++ const slope = getEllipseTangentSlope(point[0], point[1], a, b); ++ const vector = getVectorFromPointAndSlope(point[0], point[1], slope); ++ return vector; ++ }, ++ getTextRectangle: (board, element) => { ++ const elementRectangle = RectangleClient.getRectangleByPoints(element.points); ++ const textRectangle = getCustomTextRectangle(board, element, 1); ++ textRectangle.y += getStrokeWidthByElement(element); ++ const startY = elementRectangle.y + elementRectangle.height * 0.45; ++ const endY = elementRectangle.y + elementRectangle.height - elementRectangle.height * 0.3; ++ textRectangle.y = startY + (endY - startY - textRectangle.height) / 2; ++ return textRectangle; ++ } ++}; ++ ++const HardDiskEngine = { ++ draw(board, rectangle, options) { ++ const rs = PlaitBoard.getRoughSVG(board); ++ const shape = rs.path(`M${rectangle.x + rectangle.width - rectangle.width * 0.15} ${rectangle.y} ++ A${rectangle.width * 0.15} ${rectangle.height / 2}, 0, 0, 0,${rectangle.x + ++ rectangle.width - ++ rectangle.width * 0.15} ${rectangle.y + rectangle.height} ++ A${rectangle.width * 0.15} ${rectangle.height / 2}, 0, 0, 0,${rectangle.x + rectangle.width - rectangle.width * 0.15} ${rectangle.y} ++ H${rectangle.x + rectangle.width * 0.15} ++ A${rectangle.width * 0.15} ${rectangle.height / 2}, 0, 0, 0, ${rectangle.x + rectangle.width * 0.15} ${rectangle.y + ++ rectangle.height} ++ H${rectangle.x + rectangle.width - rectangle.width * 0.15}`, { ...options, fillStyle: 'solid' }); ++ setStrokeLinecap(shape, 'round'); ++ return shape; ++ }, ++ isInsidePoint(rectangle, point) { ++ const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]); ++ const isInRectangle = RectangleClient.isHit({ ++ ...rectangle, ++ x: rectangle.x + rectangle.width * 0.15, ++ width: rectangle.width - rectangle.width * 0.3 ++ }, rangeRectangle); ++ const isInLeftEllipse = isPointInEllipse(point, [rectangle.x + rectangle.width * 0.15, rectangle.y + rectangle.height / 2], rectangle.width * 0.15, rectangle.height / 2); ++ const isInRightEllipse = isPointInEllipse(point, [rectangle.x + rectangle.width - rectangle.width * 0.15, rectangle.y + rectangle.height / 2], rectangle.width * 0.15, rectangle.height / 2); ++ return isInRectangle || isInLeftEllipse || isInRightEllipse; ++ }, ++ getCornerPoints(rectangle) { ++ return RectangleClient.getCornerPoints(rectangle); ++ }, ++ getNearestPoint(rectangle, point) { ++ const nearestPoint = getNearestPointBetweenPointAndSegments(point, RectangleEngine.getCornerPoints(rectangle)); ++ if (nearestPoint[0] < rectangle.x + rectangle.width * 0.15) { ++ const centerPoint = [rectangle.x + rectangle.width * 0.15, rectangle.y + rectangle.height / 2]; ++ const nearestPoint = getNearestPointBetweenPointAndEllipse(point, centerPoint, rectangle.width * 0.15, rectangle.height / 2); ++ if (nearestPoint[0] > centerPoint[0]) { ++ nearestPoint[0] = centerPoint[0] * 2 - nearestPoint[0]; ++ } ++ return nearestPoint; ++ } ++ if (nearestPoint[0] > rectangle.x + rectangle.width - rectangle.width * 0.15) { ++ const centerPoint = [rectangle.x + rectangle.width - rectangle.width * 0.15, rectangle.y + rectangle.height / 2]; ++ const nearestPoint = getNearestPointBetweenPointAndEllipse(point, centerPoint, rectangle.width * 0.15, rectangle.height / 2); ++ if (nearestPoint[0] < centerPoint[0]) { ++ nearestPoint[0] = centerPoint[0] * 2 - nearestPoint[0]; ++ } ++ return nearestPoint; ++ } ++ return nearestPoint; ++ }, ++ getConnectorPoints(rectangle) { ++ return RectangleClient.getEdgeCenterPoints(rectangle); ++ }, ++ getTangentVectorByConnectionPoint(rectangle, pointOfRectangle) { ++ const connectionPoint = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle); ++ let centerPoint = [rectangle.x + rectangle.width * 0.15, rectangle.y + rectangle.height / 2]; ++ let a = rectangle.width * 0.15; ++ let b = rectangle.height / 2; ++ const isInRightEllipse = connectionPoint[0] > rectangle.x + rectangle.width - rectangle.width * 0.15 && connectionPoint[1] > rectangle.y; ++ if (isInRightEllipse) { ++ centerPoint = [rectangle.x + rectangle.width - rectangle.width * 0.15, rectangle.y + rectangle.height / 2]; ++ } ++ const point = [connectionPoint[0] - centerPoint[0], -(connectionPoint[1] - centerPoint[1])]; ++ const slope = getEllipseTangentSlope(point[0], point[1], a, b); ++ const vector = getVectorFromPointAndSlope(point[0], point[1], slope); ++ return vector; ++ }, ++ getTextRectangle: (board, element) => { ++ const elementRectangle = RectangleClient.getRectangleByPoints(element.points); ++ const strokeWidth = getStrokeWidthByElement(element); ++ const width = elementRectangle.width - elementRectangle.width * 0.45 - ShapeDefaultSpace.rectangleAndText * 2 - strokeWidth * 2; ++ const text = element.text; ++ const textSize = getTextSize(board, text, width); ++ return { ++ height: textSize.height, ++ width: width > 0 ? width : 0, ++ x: elementRectangle.x + elementRectangle.width * 0.15 + ShapeDefaultSpace.rectangleAndText + strokeWidth, ++ y: elementRectangle.y + (elementRectangle.height - textSize.height) / 2 ++ }; ++ } ++}; ++ ++const InternalStorageEngine = { ++ draw(board, rectangle, options) { ++ const rs = PlaitBoard.getRoughSVG(board); ++ const shape = rs.path(`M${rectangle.x} ${rectangle.y} h${rectangle.width} v${rectangle.height} h${-rectangle.width} v${-rectangle.height} ++ M${rectangle.x} ${rectangle.y + rectangle.height / 10} h${rectangle.width} ++ M${rectangle.x + rectangle.width / 10} ${rectangle.y} v${rectangle.height} ++ `, { ...options, fillStyle: 'solid' }); ++ setStrokeLinecap(shape, 'round'); ++ return shape; ++ }, ++ isInsidePoint(rectangle, point) { ++ const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]); ++ return RectangleClient.isHit(rectangle, rangeRectangle); ++ }, ++ getCornerPoints(rectangle) { ++ return RectangleClient.getCornerPoints(rectangle); ++ }, ++ getNearestPoint(rectangle, point) { ++ return getNearestPointBetweenPointAndSegments(point, RectangleEngine.getCornerPoints(rectangle)); ++ }, ++ getEdgeByConnectionPoint(rectangle, pointOfRectangle) { ++ const corners = RectangleEngine.getCornerPoints(rectangle); ++ const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle); ++ return getPolygonEdgeByConnectionPoint(corners, point); ++ }, ++ getConnectorPoints(rectangle) { ++ return RectangleClient.getEdgeCenterPoints(rectangle); ++ }, ++ getTextRectangle: (board, element) => { ++ const elementRectangle = RectangleClient.getRectangleByPoints(element.points); ++ const strokeWidth = getStrokeWidthByElement(element); ++ const width = elementRectangle.width - elementRectangle.width * 0.1 - ShapeDefaultSpace.rectangleAndText * 2 - strokeWidth * 2; ++ const text = element.text; ++ const textSize = getTextSize(board, text, width); ++ return { ++ height: textSize.height, ++ width: width > 0 ? width : 0, ++ x: elementRectangle.x + elementRectangle.width * 0.1 + ShapeDefaultSpace.rectangleAndText + strokeWidth, ++ y: elementRectangle.y + elementRectangle.height * 0.1 + (elementRectangle.height - elementRectangle.height * 0.1 - textSize.height) / 2 ++ }; ++ } ++}; ++ ++function generateNoteCurlyLeftPath(rectangle) { ++ const curlyWidth = rectangle.width * 0.09; ++ const rightX = rectangle.x + rectangle.width; ++ const centerY = rectangle.y + rectangle.height / 2; ++ return { ++ startPoint: [rightX, rectangle.y], ++ upperCurve: { ++ controlPoint1: [rightX - curlyWidth, rectangle.y], ++ controlPoint2: [rightX, centerY], ++ endPoint: [rightX - curlyWidth, centerY] ++ }, ++ lowerCurve: { ++ controlPoint1: [rightX, centerY], ++ controlPoint2: [rightX - curlyWidth, rectangle.y + rectangle.height], ++ endPoint: [rightX, rectangle.y + rectangle.height] ++ } ++ }; ++} ++const NoteCurlyLeftEngine = { ++ draw(board, rectangle, options) { ++ const rs = PlaitBoard.getRoughSVG(board); ++ const { startPoint, upperCurve, lowerCurve } = generateNoteCurlyLeftPath(rectangle); ++ const pathData = [ ++ `M${startPoint[0]} ${startPoint[1]}`, ++ `C${upperCurve.controlPoint1[0]} ${upperCurve.controlPoint1[1]}, ++ ${upperCurve.controlPoint2[0]} ${upperCurve.controlPoint2[1]}, ++ ${upperCurve.endPoint[0]} ${upperCurve.endPoint[1]}`, ++ `C${lowerCurve.controlPoint1[0]} ${lowerCurve.controlPoint1[1]}, ++ ${lowerCurve.controlPoint2[0]} ${lowerCurve.controlPoint2[1]}, ++ ${lowerCurve.endPoint[0]} ${lowerCurve.endPoint[1]}` ++ ].join(' '); ++ const shape = rs.path(pathData, { ...options, fillStyle: 'solid', fill: 'transparent' }); ++ setStrokeLinecap(shape, 'round'); ++ return shape; ++ }, ++ getNearestPoint(rectangle, point) { ++ const { startPoint, upperCurve, lowerCurve } = generateNoteCurlyLeftPath(rectangle); ++ const upperBezierPoints = pointsOnBezierCurves([startPoint, upperCurve.controlPoint1, upperCurve.controlPoint2, upperCurve.endPoint], 0.001); ++ const lowerBezierPoints = pointsOnBezierCurves([upperCurve.endPoint, lowerCurve.controlPoint1, lowerCurve.controlPoint2, lowerCurve.endPoint], 0.001); ++ const allPoints = [...upperBezierPoints, ...lowerBezierPoints]; ++ let minDistance = Infinity; ++ let nearestPoint = point; ++ for (const curvePoint of allPoints) { ++ const distance = distanceBetweenPointAndPoint(point[0], point[1], curvePoint[0], curvePoint[1]); ++ if (distance < minDistance) { ++ minDistance = distance; ++ nearestPoint = [...curvePoint]; ++ } ++ } ++ return nearestPoint; ++ }, ++ isInsidePoint(rectangle, point) { ++ const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]); ++ return RectangleClient.isHit(rectangle, rangeRectangle); ++ }, ++ getCornerPoints(rectangle) { ++ return RectangleClient.getCornerPoints(rectangle); ++ }, ++ getEdgeByConnectionPoint(rectangle, pointOfRectangle) { ++ const corners = RectangleEngine.getCornerPoints(rectangle); ++ const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle); ++ return getPolygonEdgeByConnectionPoint(corners, point); ++ }, ++ getConnectorPoints(rectangle) { ++ return RectangleClient.getEdgeCenterPoints(rectangle); ++ }, ++ getTextRectangle: (board, element) => { ++ const elementRectangle = RectangleClient.getRectangleByPoints(element.points); ++ const textRectangle = getCustomTextRectangle(board, element, 0.9); ++ textRectangle.x = elementRectangle.x + getStrokeWidthByElement(element) + ShapeDefaultSpace.rectangleAndText; ++ return textRectangle; ++ } ++}; ++ ++function generateNoteCurlyRightPath(rectangle) { ++ const curlyWidth = rectangle.width * 0.09; ++ const centerY = rectangle.y + rectangle.height / 2; ++ return { ++ startPoint: [rectangle.x, rectangle.y], ++ upperCurve: { ++ controlPoint1: [rectangle.x + curlyWidth, rectangle.y], ++ controlPoint2: [rectangle.x, centerY], ++ endPoint: [rectangle.x + curlyWidth, centerY] ++ }, ++ lowerCurve: { ++ controlPoint1: [rectangle.x, centerY], ++ controlPoint2: [rectangle.x + curlyWidth, rectangle.y + rectangle.height], ++ endPoint: [rectangle.x, rectangle.y + rectangle.height] ++ } ++ }; ++} ++const NoteCurlyRightEngine = { ++ draw(board, rectangle, options) { ++ const rs = PlaitBoard.getRoughSVG(board); ++ const { startPoint, upperCurve, lowerCurve } = generateNoteCurlyRightPath(rectangle); ++ const pathData = [ ++ `M${startPoint[0]} ${startPoint[1]}`, ++ `C${upperCurve.controlPoint1[0]} ${upperCurve.controlPoint1[1]}, ++ ${upperCurve.controlPoint2[0]} ${upperCurve.controlPoint2[1]}, ++ ${upperCurve.endPoint[0]} ${upperCurve.endPoint[1]}`, ++ `C${lowerCurve.controlPoint1[0]} ${lowerCurve.controlPoint1[1]}, ++ ${lowerCurve.controlPoint2[0]} ${lowerCurve.controlPoint2[1]}, ++ ${lowerCurve.endPoint[0]} ${lowerCurve.endPoint[1]}` ++ ].join(' '); ++ const shape = rs.path(pathData, { ...options, fillStyle: 'solid', fill: 'transparent' }); ++ setStrokeLinecap(shape, 'round'); ++ return shape; ++ }, ++ isInsidePoint(rectangle, point) { ++ const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]); ++ return RectangleClient.isHit(rectangle, rangeRectangle); ++ }, ++ getCornerPoints(rectangle) { ++ return RectangleClient.getCornerPoints(rectangle); ++ }, ++ getNearestPoint(rectangle, point) { ++ const { startPoint, upperCurve, lowerCurve } = generateNoteCurlyRightPath(rectangle); ++ // 生成上部贝塞尔曲线的点 ++ const upperBezierPoints = pointsOnBezierCurves([startPoint, upperCurve.controlPoint1, upperCurve.controlPoint2, upperCurve.endPoint], 0.001); ++ // 生成下部贝塞尔曲线的点 ++ const lowerBezierPoints = pointsOnBezierCurves([upperCurve.endPoint, lowerCurve.controlPoint1, lowerCurve.controlPoint2, lowerCurve.endPoint], 0.001); ++ // 合并所有点 ++ const allPoints = [...upperBezierPoints, ...lowerBezierPoints]; ++ // 找到最近的点 ++ let minDistance = Infinity; ++ let nearestPoint = [...point]; ++ for (const curvePoint of allPoints) { ++ const distance = distanceBetweenPointAndPoint(point[0], point[1], curvePoint[0], curvePoint[1]); ++ if (distance < minDistance) { ++ minDistance = distance; ++ nearestPoint = [...curvePoint]; ++ } ++ } ++ return nearestPoint; ++ }, ++ getEdgeByConnectionPoint(rectangle, pointOfRectangle) { ++ const corners = RectangleEngine.getCornerPoints(rectangle); ++ const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle); ++ return getPolygonEdgeByConnectionPoint(corners, point); ++ }, ++ getConnectorPoints(rectangle) { ++ return RectangleClient.getEdgeCenterPoints(rectangle); ++ }, ++ getTextRectangle: (board, element) => { ++ const elementRectangle = RectangleClient.getRectangleByPoints(element.points); ++ const textRectangle = getCustomTextRectangle(board, element, 0.9); ++ textRectangle.x = ++ elementRectangle.x + getStrokeWidthByElement(element) + ShapeDefaultSpace.rectangleAndText + elementRectangle.width * 0.1; ++ return textRectangle; ++ } ++}; ++ ++const NoteSquareEngine = { ++ draw(board, rectangle, options) { ++ const rs = PlaitBoard.getRoughSVG(board); ++ const shape = rs.path(`M${rectangle.x + rectangle.width * 0.075} ${rectangle.y + rectangle.height} H${rectangle.x} V${rectangle.y} H${rectangle.x + ++ rectangle.width * 0.075} ++ `, { ...options, fillStyle: 'solid', fill: 'transparent' }); ++ setStrokeLinecap(shape, 'round'); ++ return shape; ++ }, ++ isInsidePoint(rectangle, point) { ++ const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]); ++ return RectangleClient.isHit(rectangle, rangeRectangle); ++ }, ++ getCornerPoints(rectangle) { ++ return RectangleClient.getCornerPoints(rectangle); ++ }, ++ getNearestPoint(rectangle, point) { ++ return getNearestPointBetweenPointAndSegments(point, RectangleEngine.getCornerPoints(rectangle)); ++ }, ++ getEdgeByConnectionPoint(rectangle, pointOfRectangle) { ++ const corners = RectangleEngine.getCornerPoints(rectangle); ++ const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle); ++ return getPolygonEdgeByConnectionPoint(corners, point); ++ }, ++ getConnectorPoints(rectangle) { ++ return RectangleClient.getEdgeCenterPoints(rectangle); ++ }, ++ getTextRectangle: (board, element) => { ++ return getCustomTextRectangle(board, element, 0.88); ++ } ++}; ++ ++const getDisplayPoints = (rectangle) => { ++ return [ ++ [rectangle.x, rectangle.y + rectangle.height / 2], ++ [rectangle.x + rectangle.width * 0.15, rectangle.y], ++ [rectangle.x + rectangle.width, rectangle.y], ++ [rectangle.x + rectangle.width, rectangle.y + rectangle.height], ++ [rectangle.x + rectangle.width * 0.15, rectangle.y + rectangle.height] ++ ]; ++}; ++const DisplayEngine = { ++ draw(board, rectangle, options) { ++ const rs = PlaitBoard.getRoughSVG(board); ++ const shape = rs.path(`M${rectangle.x + rectangle.width * 0.15} ${rectangle.y} ++ H${rectangle.x + rectangle.width - rectangle.width * 0.1} ++ A ${rectangle.width * 0.1} ${rectangle.height / 2}, 0, 0, 1,${rectangle.x + ++ rectangle.width - ++ rectangle.width * 0.1} ${rectangle.y + rectangle.height} ++ H${rectangle.x + rectangle.width * 0.15} ++ L${rectangle.x} ${rectangle.y + rectangle.height / 2} ++ Z ++ `, { ...options, fillStyle: 'solid' }); ++ setStrokeLinecap(shape, 'round'); ++ return shape; ++ }, ++ isInsidePoint(rectangle, point) { ++ const polygonPoints = [ ++ [rectangle.x, rectangle.y + rectangle.height / 2], ++ [rectangle.x + rectangle.width * 0.15, rectangle.y], ++ [rectangle.x + rectangle.width - rectangle.width * 0.1, rectangle.y], ++ [rectangle.x + rectangle.width - rectangle.width * 0.1, rectangle.y + rectangle.height], ++ [rectangle.x + rectangle.width * 0.15, rectangle.y + rectangle.height] ++ ]; ++ const isInPolygon = isPointInPolygon(point, polygonPoints); ++ const isInEllipse = isPointInEllipse(point, [rectangle.x + rectangle.width - rectangle.width * 0.1, rectangle.y + rectangle.height / 2], rectangle.width * 0.1, rectangle.height / 2); ++ return isInPolygon || isInEllipse; ++ }, ++ getCornerPoints(rectangle) { ++ return RectangleClient.getCornerPoints(rectangle); ++ }, ++ getNearestPoint(rectangle, point) { ++ const nearestPoint = getNearestPointBetweenPointAndSegments(point, getDisplayPoints(rectangle)); ++ if (nearestPoint[0] > rectangle.x + rectangle.width - rectangle.width * 0.1) { ++ return getNearestPointBetweenPointAndEllipse(point, [rectangle.x + rectangle.width - rectangle.width * 0.1, rectangle.y + rectangle.height / 2], rectangle.width * 0.1, rectangle.height / 2); ++ } ++ return nearestPoint; ++ }, ++ getTangentVectorByConnectionPoint(rectangle, pointOfRectangle) { ++ const connectionPoint = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle); ++ const centerPoint = [rectangle.x + rectangle.width - rectangle.width * 0.1, rectangle.y + rectangle.height / 2]; ++ const point = [connectionPoint[0] - centerPoint[0], -(connectionPoint[1] - centerPoint[1])]; ++ const a = rectangle.width * 0.1; ++ const b = rectangle.height / 2; ++ const slope = getEllipseTangentSlope(point[0], point[1], a, b); ++ return getVectorFromPointAndSlope(point[0], point[1], slope); ++ }, ++ getConnectorPoints(rectangle) { ++ return RectangleClient.getEdgeCenterPoints(rectangle); ++ }, ++ getTextRectangle: (board, element) => { ++ return getCustomTextRectangle(board, element, 0.75); ++ } ++}; ++ ++const TableEngine = { ++ draw(board, rectangle, roughOptions, options) { ++ const g = createG(); ++ try { ++ const pointCells = getCellsWithPoints(board, { ...options?.element }); ++ if (pointCells) { ++ const rs = PlaitBoard.getRoughSVG(board); ++ const { x, y, width, height } = rectangle; ++ const tableTopBorder = drawLine(rs, [x, y], [x + width, y], roughOptions); ++ const tableLeftBorder = drawLine(rs, [x, y], [x, y + height], roughOptions); ++ g.append(tableTopBorder, tableLeftBorder); ++ pointCells.forEach((cell) => { ++ const rectangle = RectangleClient.getRectangleByPoints(cell.points); ++ const { x, y, width, height } = rectangle; ++ const cellRectangle = drawRectangle(board, { ++ x: x + ACTIVE_STROKE_WIDTH, ++ y: y + ACTIVE_STROKE_WIDTH, ++ width: width - ACTIVE_STROKE_WIDTH * 2, ++ height: height - ACTIVE_STROKE_WIDTH * 2 ++ }, { fill: cell.fill, fillStyle: 'solid', strokeWidth: 0 }); ++ const cellRightBorder = drawLine(rs, [x + width, y], [x + width, y + height], roughOptions); ++ const cellBottomBorder = drawLine(rs, [x, y + height], [x + width, y + height], roughOptions); ++ g.append(cellRectangle, cellRightBorder, cellBottomBorder); ++ }); ++ setStrokeLinecap(g, 'round'); ++ } ++ } ++ catch (error) { ++ console.error(error); ++ } ++ return g; ++ }, ++ isInsidePoint(rectangle, point) { ++ const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]); ++ return RectangleClient.isHit(rectangle, rangeRectangle); ++ }, ++ getCornerPoints(rectangle) { ++ return RectangleClient.getCornerPoints(rectangle); ++ }, ++ getNearestPoint(rectangle, point) { ++ return getNearestPointBetweenPointAndRoundRectangle$1(point, rectangle, getRoundRectangleRadius(rectangle)); ++ }, ++ getConnectorPoints(rectangle) { ++ return RectangleClient.getEdgeCenterPoints(rectangle); ++ }, ++ getTextRectangle(board, element, options) { ++ try { ++ if (options && options.id) { ++ const cell = getCellWithPoints(board, element, options.id); ++ if (cell) { ++ if (PlaitTableElement.isVerticalText(cell)) { ++ return getVerticalTextRectangle(board, cell); ++ } ++ else { ++ return getHorizontalTextRectangle(board, cell); ++ } ++ } ++ } ++ } ++ catch (error) { ++ console.error(error); ++ } ++ return { ++ x: 0, ++ y: 0, ++ width: 0, ++ height: 0 ++ }; ++ } ++}; ++function getVerticalTextRectangle(board, cell) { ++ const cellRectangle = RectangleClient.getRectangleByPoints(cell.points); ++ const strokeWidth = getStrokeWidthByElement(cell); ++ const width = cellRectangle.height - ShapeDefaultSpace.rectangleAndText * 2 - strokeWidth * 2; ++ return getTextRectangle(board, cell, width, cellRectangle); ++} ++function getHorizontalTextRectangle(board, cell) { ++ const cellRectangle = RectangleClient.getRectangleByPoints(cell.points); ++ const strokeWidth = getStrokeWidthByElement(cell); ++ const width = cellRectangle.width - ShapeDefaultSpace.rectangleAndText * 2 - strokeWidth * 2; ++ return getTextRectangle(board, cell, width, cellRectangle); ++} ++function getTextRectangle(board, cell, width, cellRectangle) { ++ const text = cell.text; ++ if (text) { ++ const textSize = getTextSize(board, text, width); ++ return { ++ width: width > 0 ? width : 0, ++ height: textSize.height, ++ x: cellRectangle.x - width / 2 + cellRectangle.width / 2, ++ y: cellRectangle.y + (cellRectangle.height - textSize.height) / 2 ++ }; ++ } ++ else { ++ return { ++ width: 0, ++ height: 0, ++ x: cellRectangle.x, ++ y: cellRectangle.y ++ }; ++ } ++} ++const getCellTextHeight = (board, cell, isVertical = false) => { ++ if (cell.text) { ++ const cellRectangle = RectangleClient.getRectangleByPoints(cell.points); ++ const strokeWidth = getStrokeWidthByElement(cell); ++ let width = cellRectangle.width - ShapeDefaultSpace.rectangleAndText * 2 - strokeWidth * 2; ++ if (isVertical) { ++ width = cellRectangle.height - ShapeDefaultSpace.rectangleAndText * 2 - strokeWidth * 2; ++ } ++ return getTextSize(board, cell.text, width).height; ++ } ++ return 0; ++}; ++ ++function generateActorPath(rectangle) { ++ const centerX = rectangle.x + rectangle.width / 2; ++ const headRadius = { width: rectangle.width / 3 / 2, height: rectangle.height / 4 / 2 }; ++ const centerY = rectangle.y + rectangle.height / 4 / 2; ++ return { ++ headArcCommand: { ++ rx: headRadius.width, ++ ry: headRadius.height, ++ xAxisRotation: 0, ++ largeArcFlag: 0, ++ sweepFlag: 1, ++ endX: centerX, ++ endY: rectangle.y ++ }, ++ bodyLine: [ ++ [centerX, rectangle.y + rectangle.height / 4], ++ [centerX, rectangle.y + (rectangle.height / 4) * 3] ++ ], ++ armsLine: [ ++ [rectangle.x, rectangle.y + rectangle.height / 2], ++ [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2] ++ ], ++ leftLegLine: [ ++ [centerX, rectangle.y + (rectangle.height / 4) * 3], ++ [rectangle.x + rectangle.width / 12, rectangle.y + rectangle.height] ++ ], ++ rightLegLine: [ ++ [centerX, rectangle.y + (rectangle.height / 4) * 3], ++ [rectangle.x + (rectangle.width / 12) * 11, rectangle.y + rectangle.height] ++ ] ++ }; ++} ++const ActorEngine = { ++ draw(board, rectangle, options) { ++ const rs = PlaitBoard.getRoughSVG(board); ++ const { headArcCommand, bodyLine, armsLine, leftLegLine, rightLegLine } = generateActorPath(rectangle); ++ const pathData = [ ++ // 头部(从中间开始画) ++ `M${bodyLine[0][0]} ${bodyLine[0][1]}`, ++ `A${headArcCommand.rx} ${headArcCommand.ry} ${headArcCommand.xAxisRotation} ${headArcCommand.largeArcFlag} ${headArcCommand.sweepFlag} ${headArcCommand.endX} ${headArcCommand.endY}`, ++ `A${headArcCommand.rx} ${headArcCommand.ry} ${headArcCommand.xAxisRotation} ${headArcCommand.largeArcFlag} ${headArcCommand.sweepFlag} ${bodyLine[0][0]} ${bodyLine[0][1]}`, ++ // 身体 ++ `V${bodyLine[1][1]}`, ++ // 手臂 ++ `M${armsLine[0][0]} ${armsLine[0][1]} H${armsLine[1][0]}`, ++ // 腿 ++ `M${leftLegLine[0][0]} ${leftLegLine[0][1]} L${leftLegLine[1][0]} ${leftLegLine[1][1]}`, ++ `M${rightLegLine[0][0]} ${rightLegLine[0][1]} L${rightLegLine[1][0]} ${rightLegLine[1][1]}` ++ ].join(' '); ++ const shape = rs.path(pathData, { ...options, fillStyle: 'solid' }); ++ setStrokeLinecap(shape, 'round'); ++ return shape; ++ }, ++ getNearestPoint(rectangle, point) { ++ const { headArcCommand, bodyLine, armsLine, leftLegLine, rightLegLine } = generateActorPath(rectangle); ++ // 检查头部椭圆 ++ const headCenter = [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height / 4 / 2]; ++ const nearestPointForHead = getNearestPointBetweenPointAndEllipse(point, headCenter, headArcCommand.rx, headArcCommand.ry); ++ const distanceForHead = distanceBetweenPointAndPoint(...point, ...nearestPointForHead); ++ // 检查所有线段 ++ const allSegments = [bodyLine, armsLine, leftLegLine, rightLegLine]; ++ const nearestPointForLines = getNearestPointBetweenPointAndDiscreteSegments(point, allSegments); ++ const distanceForLines = distanceBetweenPointAndPoint(...point, ...nearestPointForLines); ++ return distanceForHead < distanceForLines ? nearestPointForHead : nearestPointForLines; ++ }, ++ isInsidePoint(rectangle, point) { ++ const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]); ++ return RectangleClient.isHit(rectangle, rangeRectangle); ++ }, ++ getCornerPoints(rectangle) { ++ return RectangleClient.getCornerPoints(rectangle); ++ }, ++ getConnectorPoints(rectangle) { ++ return RectangleClient.getEdgeCenterPoints(rectangle); ++ }, ++ getTangentVectorByConnectionPoint(rectangle, pointOfRectangle) { ++ const connectionPoint = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle); ++ if (connectionPoint[1] >= rectangle.y && connectionPoint[1] <= rectangle.y + rectangle.height / 4) { ++ const centerPoint = [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height / 4 / 2]; ++ const point = [connectionPoint[0] - centerPoint[0], -(connectionPoint[1] - centerPoint[1])]; ++ const a = rectangle.width / 2; ++ const b = rectangle.height / 2; ++ const slope = getEllipseTangentSlope(point[0], point[1], a, b); ++ const vector = getVectorFromPointAndSlope(point[0], point[1], slope); ++ return vector; ++ } ++ if (connectionPoint[1] >= rectangle.y + rectangle.height / 4 && connectionPoint[1] < rectangle.y + (rectangle.height / 4) * 3) { ++ if (connectionPoint[0] < rectangle.x + rectangle.width / 2) { ++ return rotateVector(getUnitVectorByPointAndPoint([rectangle.x, rectangle.y + rectangle.height / 2], connectionPoint), -(Math.PI / 2)); ++ } ++ else { ++ return rotateVector(getUnitVectorByPointAndPoint([rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2], connectionPoint), -(Math.PI / 2)); ++ } ++ } ++ if (connectionPoint[1] >= rectangle.y + (rectangle.height / 4) * 3) { ++ if (connectionPoint[0] < rectangle.x + rectangle.width / 2) { ++ return getUnitVectorByPointAndPoint(connectionPoint, [rectangle.x + rectangle.width / 12, rectangle.y + rectangle.height]); ++ } ++ else { ++ return getUnitVectorByPointAndPoint([rectangle.x + (rectangle.width / 12) * 11, rectangle.y + rectangle.height], connectionPoint); ++ } ++ } ++ return getUnitVectorByPointAndPoint(connectionPoint, [rectangle.x + rectangle.width / 4, rectangle.y + rectangle.height]); ++ }, ++ getTextRectangle: (board, element) => { ++ const elementRectangle = RectangleClient.getRectangleByPoints(element.points); ++ const width = elementRectangle.width + 40; ++ const text = element.text; ++ const textSize = getTextSize(board, text, width); ++ return { ++ height: textSize.height, ++ width: width > 0 ? width : 0, ++ x: elementRectangle.x - 20, ++ y: elementRectangle.y + elementRectangle.height + 4 ++ }; ++ } ++}; ++ ++const ContainerEngine = { ++ draw(board, rectangle, options) { ++ const rs = PlaitBoard.getRoughSVG(board); ++ const shape = rs.path(`M${rectangle.x} ${rectangle.y} H${rectangle.x + rectangle.width} V${rectangle.y + rectangle.height} H${rectangle.x} Z M${rectangle.x + 40} ${rectangle.y} L${rectangle.x + 40} ${rectangle.y + rectangle.height} `, { ...options, fillStyle: 'solid' }); ++ setStrokeLinecap(shape, 'round'); ++ return shape; ++ }, ++ isInsidePoint(rectangle, point) { ++ const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]); ++ return RectangleClient.isHit(rectangle, rangeRectangle); ++ }, ++ getCornerPoints(rectangle) { ++ return RectangleClient.getCornerPoints(rectangle); ++ }, ++ getNearestPoint(rectangle, point) { ++ return getNearestPointBetweenPointAndSegments(point, RectangleEngine.getCornerPoints(rectangle)); ++ }, ++ getEdgeByConnectionPoint(rectangle, pointOfRectangle) { ++ const corners = RectangleEngine.getCornerPoints(rectangle); ++ const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle); ++ return getPolygonEdgeByConnectionPoint(corners, point); ++ }, ++ getConnectorPoints(rectangle) { ++ return RectangleClient.getEdgeCenterPoints(rectangle); ++ }, ++ getTextRectangle(board, element) { ++ const elementRectangle = RectangleClient.getRectangleByPoints(element.points); ++ const strokeWidth = getStrokeWidthByElement(element); ++ const width = 40 - ShapeDefaultSpace.rectangleAndText * 2 - strokeWidth * 2; ++ const text = element.text; ++ const textSize = getTextSize(board, text, width); ++ return { ++ height: textSize.height, ++ width: width > 0 ? width : 0, ++ x: elementRectangle.x + ShapeDefaultSpace.rectangleAndText + strokeWidth, ++ y: elementRectangle.y + (elementRectangle.height - textSize.height) / 2 ++ }; ++ } ++}; ++ ++function generatePackagePath(rectangle) { ++ const headerHeight = 25; ++ const topWidth = rectangle.width * 0.7; ++ const cornerX = rectangle.x + rectangle.width * 0.8; ++ return { ++ headerHeight, ++ points: { ++ leftTop: [rectangle.x, rectangle.y + headerHeight], ++ topStart: [rectangle.x, rectangle.y], ++ topEnd: [rectangle.x + topWidth, rectangle.y], ++ cornerPoint: [cornerX, rectangle.y + headerHeight], ++ rightTop: [rectangle.x + rectangle.width, rectangle.y + headerHeight], ++ rightBottom: [rectangle.x + rectangle.width, rectangle.y + rectangle.height], ++ leftBottom: [rectangle.x, rectangle.y + rectangle.height], ++ leftMiddle: [rectangle.x, rectangle.y + headerHeight], ++ middlePoint: [cornerX, rectangle.y + headerHeight] ++ } ++ }; ++} ++const PackageEngine = { ++ draw(board, rectangle, options) { ++ const rs = PlaitBoard.getRoughSVG(board); ++ const { points } = generatePackagePath(rectangle); ++ const pathData = [ ++ `M${points.leftTop[0]} ${points.leftTop[1]}`, ++ `V${points.topStart[1]}`, ++ `H${points.topEnd[0]}`, ++ `L${points.cornerPoint[0]} ${points.cornerPoint[1]}`, ++ `H${points.rightTop[0]}`, ++ `V${points.rightBottom[1]}`, ++ `H${points.leftBottom[0]}`, ++ `V${points.leftMiddle[1]}`, ++ `H${points.middlePoint[0]}` ++ ].join(' '); ++ const shape = rs.path(pathData, { ...options, fillStyle: 'solid' }); ++ setStrokeLinecap(shape, 'round'); ++ return shape; ++ }, ++ isInsidePoint(rectangle, point) { ++ const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]); ++ return RectangleClient.isHit(rectangle, rangeRectangle); ++ }, ++ getCornerPoints(rectangle) { ++ return RectangleClient.getCornerPoints(rectangle); ++ }, ++ getNearestPoint(rectangle, point) { ++ const { points } = generatePackagePath(rectangle); ++ const segments = [ ++ // 左边竖线 ++ [points.topStart, points.leftTop], ++ [points.leftTop, points.leftBottom], ++ // 底边 ++ [points.leftBottom, points.rightBottom], ++ // 右边竖线 ++ [points.rightBottom, points.rightTop], ++ // 顶部折线 ++ [points.topStart, points.topEnd], ++ [points.topEnd, points.cornerPoint], ++ [points.cornerPoint, points.rightTop], ++ // 中间横线 ++ [points.leftMiddle, points.middlePoint] ++ ]; ++ return getNearestPointBetweenPointAndDiscreteSegments(point, segments); ++ }, ++ getConnectorPoints(rectangle) { ++ return RectangleClient.getEdgeCenterPoints(rectangle); ++ }, ++ getTangentVectorByConnectionPoint(rectangle, pointOfRectangle) { ++ const connectionPoint = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle); ++ if (connectionPoint[0] > rectangle.x + rectangle.width * 0.7 && connectionPoint[1] < rectangle.y + 25) { ++ return getUnitVectorByPointAndPoint([rectangle.x + rectangle.width * 0.7, rectangle.y], connectionPoint); ++ } ++ return getUnitVectorByPointAndPoint([rectangle.x + rectangle.width * 0.8, rectangle.y + 25], connectionPoint); ++ }, ++ getTextRectangle(board, element, options) { ++ const elementRectangle = RectangleClient.getRectangleByPoints(element.points); ++ const strokeWidth = getStrokeWidthByElement(element); ++ const textInfo = element.texts?.find((item) => item.id === options?.id); ++ if (options?.id === GeometryCommonTextKeys.name && textInfo) { ++ const width = elementRectangle.width * 0.7 - ShapeDefaultSpace.rectangleAndText - strokeWidth; ++ const textSize = getTextSize(board, textInfo.text, width); ++ return { ++ height: textSize.height, ++ width: width > 0 ? width : 0, ++ x: elementRectangle.x + ShapeDefaultSpace.rectangleAndText + strokeWidth, ++ y: elementRectangle.y + (25 - textSize.height) / 2 ++ }; ++ } ++ if (options?.id === GeometryCommonTextKeys.content && textInfo) { ++ const width = elementRectangle.width - ShapeDefaultSpace.rectangleAndText * 2 - strokeWidth * 2; ++ const textSize = getTextSize(board, textInfo.text, width); ++ return { ++ height: textSize.height, ++ width: width > 0 ? width : 0, ++ x: elementRectangle.x + ShapeDefaultSpace.rectangleAndText + strokeWidth, ++ y: elementRectangle.y + 25 + (elementRectangle.height - 25 - textSize.height) / 2 ++ }; ++ } ++ return elementRectangle; ++ } ++}; ++ ++const CombinedFragmentEngine = { ++ draw(board, rectangle, options) { ++ const rs = PlaitBoard.getRoughSVG(board); ++ const shape = rs.path(`M${rectangle.x} ${rectangle.y + 25} ++ V${rectangle.y} ++ H${rectangle.x + rectangle.width} ++ V${rectangle.y + rectangle.height} ++ H${rectangle.x} ++ V${rectangle.y + 25} ++ H${rectangle.x + rectangle.width / 3 - 8} ++ L${rectangle.x + rectangle.width / 3} ${rectangle.y + 16} ++ V${rectangle.y} ++ `, { ...options, fillStyle: 'solid' }); ++ setStrokeLinecap(shape, 'round'); ++ return shape; ++ }, ++ isInsidePoint(rectangle, point) { ++ const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]); ++ return RectangleClient.isHit(rectangle, rangeRectangle); ++ }, ++ getCornerPoints(rectangle) { ++ return RectangleClient.getCornerPoints(rectangle); ++ }, ++ getNearestPoint(rectangle, point) { ++ return getNearestPointBetweenPointAndSegments(point, RectangleEngine.getCornerPoints(rectangle)); ++ }, ++ getEdgeByConnectionPoint(rectangle, pointOfRectangle) { ++ const corners = RectangleEngine.getCornerPoints(rectangle); ++ const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle); ++ return getPolygonEdgeByConnectionPoint(corners, point); ++ }, ++ getConnectorPoints(rectangle) { ++ return RectangleClient.getEdgeCenterPoints(rectangle); ++ }, ++ getTextRectangle(board, element, options) { ++ const elementRectangle = RectangleClient.getRectangleByPoints(element.points); ++ const strokeWidth = getStrokeWidthByElement(element); ++ const textInfo = element.texts?.find((item) => item.id === options?.id); ++ if (options?.id === GeometryCommonTextKeys.name && textInfo) { ++ const width = elementRectangle.width / 3 - 8 - ShapeDefaultSpace.rectangleAndText - strokeWidth; ++ const textSize = getTextSize(board, textInfo.text, width); ++ return { ++ height: textSize.height, ++ width: width > 0 ? width : 0, ++ x: elementRectangle.x + ShapeDefaultSpace.rectangleAndText + strokeWidth, ++ y: elementRectangle.y + (25 - textSize.height) / 2 ++ }; ++ } ++ if (options?.id === GeometryCommonTextKeys.content && textInfo) { ++ const width = elementRectangle.width - ShapeDefaultSpace.rectangleAndText * 2 - strokeWidth * 2; ++ const textSize = getTextSize(board, textInfo.text, width); ++ return { ++ height: textSize.height, ++ width: width > 0 ? width : 0, ++ x: elementRectangle.x + ShapeDefaultSpace.rectangleAndText + strokeWidth, ++ y: elementRectangle.y + 25 + ShapeDefaultSpace.rectangleAndText + strokeWidth ++ }; ++ } ++ return elementRectangle; ++ } ++}; ++ ++function getDeletionLines(rectangle) { ++ return [ ++ [ ++ [rectangle.x, rectangle.y], ++ [rectangle.x + rectangle.width, rectangle.y + rectangle.height] ++ ], ++ [ ++ [rectangle.x + rectangle.width, rectangle.y], ++ [rectangle.x, rectangle.y + rectangle.height] ++ ] ++ ]; ++} ++const DeletionEngine = { ++ draw(board, rectangle, options) { ++ const rs = PlaitBoard.getRoughSVG(board); ++ const lines = getDeletionLines(rectangle); ++ const shape = rs.path(lines.map(([from, to]) => `M${from[0]} ${from[1]} L${to[0]} ${to[1]}`).join(' '), { ...options, fillStyle: 'solid', strokeWidth: 4 }); ++ setStrokeLinecap(shape, 'round'); ++ return shape; ++ }, ++ isInsidePoint(rectangle, point) { ++ const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]); ++ return RectangleClient.isHit(rectangle, rangeRectangle); ++ }, ++ getCornerPoints(rectangle) { ++ return RectangleClient.getCornerPoints(rectangle); ++ }, ++ getNearestPoint(rectangle, point) { ++ const lines = getDeletionLines(rectangle); ++ let minDistance = Infinity; ++ let nearestPoint = point; ++ lines.forEach(line => { ++ const currentPoint = getNearestPointBetweenPointAndSegment(point, line); ++ const distance = distanceBetweenPointAndPoint(point[0], point[1], currentPoint[0], currentPoint[1]); ++ if (distance < minDistance) { ++ minDistance = distance; ++ nearestPoint = currentPoint; ++ } ++ }); ++ return nearestPoint; ++ }, ++ getEdgeByConnectionPoint(rectangle, pointOfRectangle) { ++ const corners = RectangleEngine.getCornerPoints(rectangle); ++ const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle); ++ return getPolygonEdgeByConnectionPoint(corners, point); ++ }, ++ getConnectorPoints(rectangle) { ++ return RectangleClient.getEdgeCenterPoints(rectangle); ++ } ++}; ++ ++const ActiveClassEngine = { ++ draw(board, rectangle, options) { ++ const rs = PlaitBoard.getRoughSVG(board); ++ const shape = rs.path(`M${rectangle.x} ${rectangle.y} H${rectangle.x + rectangle.width} V${rectangle.y + rectangle.height} H${rectangle.x} Z M${rectangle.x + rectangle.width * 0.125} ${rectangle.y} L${rectangle.x + rectangle.width * 0.125} ${rectangle.y + ++ rectangle.height} M${rectangle.x + rectangle.width - rectangle.width * 0.125} ${rectangle.y} L${rectangle.x + ++ rectangle.width - ++ rectangle.width * 0.125} ${rectangle.y + rectangle.height}`, { ...options, fillStyle: 'solid' }); ++ setStrokeLinecap(shape, 'round'); ++ return shape; ++ }, ++ isInsidePoint(rectangle, point) { ++ const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]); ++ return RectangleClient.isHit(rectangle, rangeRectangle); ++ }, ++ getCornerPoints(rectangle) { ++ return RectangleClient.getCornerPoints(rectangle); ++ }, ++ getNearestPoint(rectangle, point) { ++ return getNearestPointBetweenPointAndSegments(point, RectangleEngine.getCornerPoints(rectangle)); ++ }, ++ getEdgeByConnectionPoint(rectangle, pointOfRectangle) { ++ const corners = RectangleEngine.getCornerPoints(rectangle); ++ const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle); ++ return getPolygonEdgeByConnectionPoint(corners, point); ++ }, ++ getConnectorPoints(rectangle) { ++ return RectangleClient.getEdgeCenterPoints(rectangle); ++ }, ++ getTextRectangle: (board, element) => { ++ const elementRectangle = RectangleClient.getRectangleByPoints(element.points); ++ const strokeWidth = getStrokeWidthByElement(element); ++ const width = elementRectangle.width - ShapeDefaultSpace.rectangleAndText * 2 - strokeWidth * 2 - elementRectangle.width * 0.125 * 2; ++ const text = element.text; ++ const textSize = getTextSize(board, text, width); ++ return { ++ height: textSize.height, ++ width: width > 0 ? width : 0, ++ x: elementRectangle.x + ShapeDefaultSpace.rectangleAndText + strokeWidth + elementRectangle.width * 0.125, ++ y: elementRectangle.y + (elementRectangle.height - textSize.height) / 2 ++ }; ++ } ++}; ++ ++const NoteEngine = { ++ draw(board, rectangle, options) { ++ const rs = PlaitBoard.getRoughSVG(board); ++ const shape = rs.path(`M${rectangle.x} ${rectangle.y} ++ h${rectangle.width - 16} ++ v16 ++ h16 ++ v${rectangle.height - 16} ++ h${-rectangle.width} ++ Z ++ M${rectangle.x + rectangle.width - 16} ${rectangle.y} ++ A16 16, 0,0,1, ${rectangle.x + rectangle.width} ${rectangle.y + 16} ++ `, { ...options, fillStyle: 'solid' }); ++ setStrokeLinecap(shape, 'round'); ++ return shape; ++ }, ++ isInsidePoint(rectangle, point) { ++ const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]); ++ return RectangleClient.isHit(rectangle, rangeRectangle); ++ }, ++ getCornerPoints(rectangle) { ++ return RectangleClient.getCornerPoints(rectangle); ++ }, ++ getNearestPoint(rectangle, point) { ++ const nearestPoint = getNearestPointBetweenPointAndSegments(point, RectangleEngine.getCornerPoints(rectangle)); ++ if (nearestPoint[0] > rectangle.x + rectangle.width - 16 && nearestPoint[1] < rectangle.y + 16) { ++ return getNearestPointBetweenPointAndEllipse(point, [rectangle.x + rectangle.width - 16, rectangle.y + 16], 16, 16); ++ } ++ return nearestPoint; ++ }, ++ getTangentVectorByConnectionPoint(rectangle, pointOfRectangle) { ++ const connectionPoint = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle); ++ const centerPoint = [rectangle.x + (rectangle.width * 3) / 4, rectangle.y + rectangle.height / 2]; ++ const point = [connectionPoint[0] - centerPoint[0], -(connectionPoint[1] - centerPoint[1])]; ++ const slope = getEllipseTangentSlope(point[0], point[1], 16, 16); ++ return getVectorFromPointAndSlope(point[0], point[1], slope); ++ }, ++ getConnectorPoints(rectangle) { ++ return RectangleClient.getEdgeCenterPoints(rectangle); ++ }, ++ getTextRectangle: (board, element) => { ++ const elementRectangle = RectangleClient.getRectangleByPoints(element.points); ++ const strokeWidth = getStrokeWidthByElement(element); ++ const width = elementRectangle.width - ShapeDefaultSpace.rectangleAndText * 2 - strokeWidth - 15; ++ const text = element.text; ++ const textSize = getTextSize(board, text, width); ++ return { ++ height: textSize.height, ++ width: width > 0 ? width : 0, ++ x: elementRectangle.x + ShapeDefaultSpace.rectangleAndText + strokeWidth, ++ y: elementRectangle.y + (elementRectangle.height - textSize.height) / 2 ++ }; ++ } ++}; ++ ++function generateAssemblyPath(rectangle) { ++ const centerY = rectangle.y + rectangle.height / 2; ++ const firstLineEndX = rectangle.x + rectangle.width * 0.3; ++ const circleWidth = rectangle.width * 0.13; ++ const circleHeight = rectangle.height * 0.285; ++ const verticalX = firstLineEndX + circleWidth; ++ const verticalRadius = rectangle.width * 0.233; ++ return { ++ startPoint: [rectangle.x, centerY], ++ line1: [ ++ [rectangle.x, centerY], ++ [firstLineEndX, centerY] ++ ], ++ circleArcCommand: { ++ rx: circleWidth, ++ ry: circleHeight, ++ xAxisRotation: 0, ++ largeArcFlag: 1, ++ sweepFlag: 1, ++ endX: firstLineEndX, ++ endY: centerY ++ }, ++ verticalArcCommand: { ++ rx: verticalRadius, ++ ry: rectangle.height / 2, ++ xAxisRotation: 0, ++ largeArcFlag: 0, ++ sweepFlag: 1, ++ endX: verticalX, ++ endY: rectangle.y + rectangle.height ++ }, ++ line2: [ ++ [verticalX + verticalRadius, centerY], ++ [rectangle.x + rectangle.width, centerY] ++ ] ++ }; ++} ++const AssemblyEngine = { ++ draw(board, rectangle, options) { ++ const rs = PlaitBoard.getRoughSVG(board); ++ const { startPoint, line1, circleArcCommand, verticalArcCommand, line2 } = generateAssemblyPath(rectangle); ++ const pathData = [ ++ `M${startPoint[0]} ${startPoint[1]}`, ++ `H${line1[1][0]}`, ++ // 画完整的圆形:先画一个半圆,再画另一个半圆 ++ `A${circleArcCommand.rx} ${circleArcCommand.ry} ${circleArcCommand.xAxisRotation} ${circleArcCommand.largeArcFlag} ${circleArcCommand.sweepFlag} ${line1[1][0] + circleArcCommand.rx * 2} ${circleArcCommand.endY}`, ++ `A${circleArcCommand.rx} ${circleArcCommand.ry} ${circleArcCommand.xAxisRotation} ${circleArcCommand.largeArcFlag} ${circleArcCommand.sweepFlag} ${circleArcCommand.endX} ${circleArcCommand.endY}`, ++ // 垂直椭圆 ++ `M${verticalArcCommand.endX} ${rectangle.y}`, ++ `A${verticalArcCommand.rx} ${verticalArcCommand.ry} ${verticalArcCommand.xAxisRotation} ${verticalArcCommand.largeArcFlag} ${verticalArcCommand.sweepFlag} ${verticalArcCommand.endX} ${verticalArcCommand.endY}`, ++ // 最后一条线 ++ `M${line2[0][0]} ${line2[0][1]} H${line2[1][0]}` ++ ].join(' '); ++ const shape = rs.path(pathData, { ++ ...options, ++ fillStyle: 'solid' ++ }); ++ setStrokeLinecap(shape, 'round'); ++ return shape; ++ }, ++ isInsidePoint(rectangle, point) { ++ const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]); ++ return RectangleClient.isHit(rectangle, rangeRectangle); ++ }, ++ getCornerPoints(rectangle) { ++ return RectangleClient.getCornerPoints(rectangle); ++ }, ++ getConnectorPoints(rectangle) { ++ return RectangleClient.getEdgeCenterPoints(rectangle); ++ }, ++ getNearestPoint(rectangle, point) { ++ const { line1, line2, circleArcCommand, verticalArcCommand } = generateAssemblyPath(rectangle); ++ // 检查直线段 ++ const nearestPointForLines = getNearestPointBetweenPointAndSegments(point, [...line1, ...line2]); ++ const distanceForLines = distanceBetweenPointAndPoint(...point, ...nearestPointForLines); ++ // 检查中间圆形 ++ const circleCenter = [line1[1][0] + circleArcCommand.rx, line1[1][1]]; ++ const nearestPointForCircle = getNearestPointBetweenPointAndEllipse(point, circleCenter, circleArcCommand.rx, circleArcCommand.ry); ++ const distanceForCircle = distanceBetweenPointAndPoint(...point, ...nearestPointForCircle); ++ // 检查垂直椭圆(使用 getNearestPointBetweenPointAndArc 处理半圆弧) ++ const arcStartPoint = [verticalArcCommand.endX, rectangle.y]; ++ const nearestPointForEllipse = getNearestPointBetweenPointAndArc(point, arcStartPoint, verticalArcCommand); ++ const distanceForEllipse = distanceBetweenPointAndPoint(...point, ...nearestPointForEllipse); ++ // 返回最近的点 ++ const minDistance = Math.min(distanceForLines, distanceForCircle, distanceForEllipse); ++ if (minDistance === distanceForLines) ++ return nearestPointForLines; ++ if (minDistance === distanceForCircle) ++ return nearestPointForCircle; ++ return nearestPointForEllipse; ++ }, ++ getTangentVectorByConnectionPoint(rectangle, pointOfRectangle) { ++ const connectionPoint = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle); ++ if (connectionPoint[0] > rectangle.x + rectangle.width * 0.43 && connectionPoint[1] < rectangle.y + rectangle.height / 2) { ++ return rotateVector(getUnitVectorByPointAndPoint(connectionPoint, [rectangle.x, rectangle.y + rectangle.height / 2]), -Math.PI); ++ } ++ if (connectionPoint[0] > rectangle.x + rectangle.width * 0.43 && connectionPoint[1] > rectangle.y + rectangle.height / 2) { ++ return getUnitVectorByPointAndPoint(connectionPoint, [rectangle.x, rectangle.y + rectangle.height / 2]); ++ } ++ return getUnitVectorByPointAndPoint(connectionPoint, [rectangle.x, rectangle.y + rectangle.height / 2]); ++ } ++}; ++ ++function generateRequiredInterfacePath(rectangle) { ++ const arcWidth = rectangle.width * 0.39; ++ const arcHeight = rectangle.height / 2; ++ return { ++ startPoint: [rectangle.x, rectangle.y], ++ leftArcCommand: { ++ rx: arcWidth, ++ ry: arcHeight, ++ xAxisRotation: 0, ++ largeArcFlag: 0, ++ sweepFlag: 1, ++ endX: rectangle.x, ++ endY: rectangle.y + rectangle.height ++ }, ++ line: { ++ startX: rectangle.x + rectangle.width * 0.41, ++ startY: rectangle.y + rectangle.height / 2, ++ endX: rectangle.x + rectangle.width, ++ endY: rectangle.y + rectangle.height / 2 ++ } ++ }; ++} ++const RequiredInterfaceEngine = { ++ draw(board, rectangle, options) { ++ const rs = PlaitBoard.getRoughSVG(board); ++ const { startPoint, leftArcCommand, line } = generateRequiredInterfacePath(rectangle); ++ const pathData = [ ++ `M${startPoint[0]} ${startPoint[1]}`, ++ `A${leftArcCommand.rx} ${leftArcCommand.ry} ${leftArcCommand.xAxisRotation} ${leftArcCommand.largeArcFlag} ${leftArcCommand.sweepFlag} ${leftArcCommand.endX} ${leftArcCommand.endY}`, ++ `M${line.startX} ${line.startY} H${line.endX}` ++ ].join(' '); ++ const shape = rs.path(pathData, { ++ ...options, ++ fillStyle: 'solid', ++ fill: 'transparent' ++ }); ++ setStrokeLinecap(shape, 'round'); ++ return shape; ++ }, ++ isInsidePoint(rectangle, point) { ++ const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]); ++ return RectangleClient.isHit(rectangle, rangeRectangle); ++ }, ++ getCornerPoints(rectangle) { ++ return RectangleClient.getCornerPoints(rectangle); ++ }, ++ getNearestPoint(rectangle, point) { ++ const { startPoint, leftArcCommand, line } = generateRequiredInterfacePath(rectangle); ++ let minDistance = Infinity; ++ let nearestPoint = point; ++ // 检查圆弧段 ++ const arcNearestPoint = getNearestPointBetweenPointAndArc(point, startPoint, leftArcCommand); ++ const arcDistance = distanceBetweenPointAndPoint(point[0], point[1], arcNearestPoint[0], arcNearestPoint[1]); ++ if (arcDistance < minDistance) { ++ minDistance = arcDistance; ++ nearestPoint = arcNearestPoint; ++ } ++ // 检查直线段 ++ const lineStart = [line.startX, line.startY]; ++ const lineEnd = [line.endX, line.endY]; ++ const lineNearestPoint = getNearestPointBetweenPointAndSegment(point, [lineStart, lineEnd]); ++ const lineDistance = distanceBetweenPointAndPoint(point[0], point[1], lineNearestPoint[0], lineNearestPoint[1]); ++ if (lineDistance < minDistance) { ++ minDistance = lineDistance; ++ nearestPoint = lineNearestPoint; ++ } ++ return nearestPoint; ++ }, ++ getEdgeByConnectionPoint(rectangle, pointOfRectangle) { ++ const corners = RectangleEngine.getCornerPoints(rectangle); ++ const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle); ++ return getPolygonEdgeByConnectionPoint(corners, point); ++ }, ++ getConnectorPoints(rectangle) { ++ return RectangleClient.getEdgeCenterPoints(rectangle); ++ } ++}; ++ ++const percentage = 0.54; ++function generateProvidedInterfacePath(rectangle) { ++ const centerY = rectangle.y + rectangle.height / 2; ++ const rx = (rectangle.width * (1 - percentage)) / 2; ++ const ry = rectangle.height / 2; ++ const startPoint = [rectangle.x, centerY]; ++ const lineEndX = rectangle.x + rectangle.width * percentage; ++ return { ++ startPoint, ++ line: { ++ startX: startPoint[0], ++ startY: centerY, ++ endX: lineEndX, ++ endY: centerY ++ }, ++ arcCommands: [ ++ { ++ rx, ++ ry, ++ xAxisRotation: 0, ++ largeArcFlag: 1, ++ sweepFlag: 1, ++ endX: rectangle.x + rectangle.width, ++ endY: centerY ++ }, ++ { ++ rx, ++ ry, ++ xAxisRotation: 0, ++ largeArcFlag: 1, ++ sweepFlag: 1, ++ endX: lineEndX, ++ endY: centerY ++ } ++ ] ++ }; ++} ++const ProvidedInterfaceEngine = { ++ draw(board, rectangle, options) { ++ const rs = PlaitBoard.getRoughSVG(board); ++ const { startPoint, line, arcCommands } = generateProvidedInterfacePath(rectangle); ++ const pathData = [ ++ `M${startPoint[0]} ${startPoint[1]}`, ++ `H${line.endX}`, ++ ...arcCommands.map((command) => `A${command.rx} ${command.ry} ${command.xAxisRotation} ${command.largeArcFlag} ${command.sweepFlag} ${command.endX} ${command.endY}`) ++ ].join(' '); ++ const shape = rs.path(pathData, { ++ ...options, ++ fillStyle: 'solid' ++ }); ++ setStrokeLinecap(shape, 'round'); ++ return shape; ++ }, ++ isInsidePoint(rectangle, point) { ++ const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]); ++ return RectangleClient.isHit(rectangle, rangeRectangle); ++ }, ++ getCornerPoints(rectangle) { ++ return RectangleClient.getCornerPoints(rectangle); ++ }, ++ getConnectorPoints(rectangle) { ++ return RectangleClient.getEdgeCenterPoints(rectangle); ++ }, ++ getNearestPoint(rectangle, point) { ++ const { startPoint, line, arcCommands } = generateProvidedInterfacePath(rectangle); ++ // 检查直线段 ++ const lineStart = [line.startX, line.startY]; ++ const lineEnd = [line.endX, line.endY]; ++ const nearestPointForLine = getNearestPointBetweenPointAndSegments(point, [lineStart, lineEnd]); ++ const distanceForLine = distanceBetweenPointAndPoint(...point, ...nearestPointForLine); ++ // 检查圆弧段 ++ const arcCenter = [rectangle.x + (3 * rectangle.width) / 4, line.startY]; ++ const nearestPointForEllipse = getNearestPointBetweenPointAndEllipse(point, arcCenter, arcCommands[0].rx, arcCommands[0].ry); ++ const distanceForEllipse = distanceBetweenPointAndPoint(...point, ...nearestPointForEllipse); ++ return distanceForLine < distanceForEllipse ? nearestPointForLine : nearestPointForEllipse; ++ }, ++ getTangentVectorByConnectionPoint(rectangle, pointOfRectangle) { ++ const connectionPoint = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle); ++ const centerPoint = [rectangle.x + (rectangle.width * 3) / 4, rectangle.y + rectangle.height / 2]; ++ if (connectionPoint[0] > rectangle.x + rectangle.width * 0.54) { ++ const point = [connectionPoint[0] - centerPoint[0], -(connectionPoint[1] - centerPoint[1])]; ++ const rx = (rectangle.width * 0.46) / 2; ++ const ry = rectangle.height / 2; ++ const slope = getEllipseTangentSlope(point[0], point[1], rx, ry); ++ return getVectorFromPointAndSlope(point[0], point[1], slope); ++ } ++ return getUnitVectorByPointAndPoint(connectionPoint, [rectangle.x, rectangle.y + rectangle.height / 2]); ++ } ++}; ++ ++function generateComponentPath(rectangle) { ++ const mainLineX = rectangle.x + 12; ++ const boxWidth = rectangle.width > 70 ? 24 : rectangle.width * 0.2; ++ const boxHeight = rectangle.height - 28 - rectangle.height * 0.35 > 1 ? 14 : rectangle.height * 0.175; ++ const topBoxY = rectangle.y + rectangle.height * 0.175; ++ const bottomBoxY = rectangle.y + rectangle.height - rectangle.height * 0.175 - boxHeight; ++ return { ++ boxSize: { ++ width: boxWidth, ++ height: boxHeight ++ }, ++ points: { ++ mainStart: [mainLineX, rectangle.y], ++ topBoxStart: [mainLineX, topBoxY], ++ topBoxEnd: [mainLineX, topBoxY + boxHeight], ++ bottomBoxStart: [mainLineX, bottomBoxY], ++ bottomBoxEnd: [mainLineX, bottomBoxY + boxHeight], ++ mainEnd: [mainLineX, rectangle.y + rectangle.height], ++ rightTop: [rectangle.x + rectangle.width, rectangle.y], ++ rightBottom: [rectangle.x + rectangle.width, rectangle.y + rectangle.height] ++ } ++ }; ++} ++const ComponentEngine = { ++ draw(board, rectangle, options) { ++ const rs = PlaitBoard.getRoughSVG(board); ++ const { boxSize, points } = generateComponentPath(rectangle); ++ const pathData = [ ++ // 主矩形轮廓 ++ `M${points.mainStart[0]} ${points.mainStart[1]}`, ++ `H${points.rightTop[0]}`, ++ `V${points.rightBottom[1]}`, ++ `H${points.mainEnd[0]}`, ++ // 上方小矩形 ++ `M${points.topBoxStart[0]} ${points.topBoxStart[1]}`, ++ `h${boxSize.width / 2} v${boxSize.height} h${-boxSize.width} v${-boxSize.height} h${boxSize.width / 2}`, ++ // 下方小矩形 ++ `M${points.bottomBoxStart[0]} ${points.bottomBoxStart[1]}`, ++ `h${boxSize.width / 2} v${boxSize.height} h${-boxSize.width} v${-boxSize.height} h${boxSize.width / 2}`, ++ // 连接线 ++ `M${points.mainStart[0]} ${points.mainStart[1]}`, ++ `V${points.topBoxStart[1]}`, ++ `M${points.topBoxEnd[0]} ${points.topBoxEnd[1]}`, ++ `V${points.bottomBoxStart[1]}`, ++ `M${points.bottomBoxEnd[0]} ${points.bottomBoxEnd[1]}`, ++ `V${points.mainEnd[1]}` ++ ].join(' '); ++ const shape = rs.path(pathData, { ...options, fillStyle: 'solid' }); ++ setStrokeLinecap(shape, 'round'); ++ return shape; ++ }, ++ getNearestPoint(rectangle, point) { ++ const { boxSize, points } = generateComponentPath(rectangle); ++ const segments = [ ++ // 主矩形轮廓 ++ [points.mainStart, [points.rightTop[0], points.mainStart[1]]], ++ [[points.rightTop[0], points.mainStart[1]], points.rightBottom], ++ [points.rightBottom, [points.mainEnd[0], points.rightBottom[1]]], ++ [[points.mainEnd[0], points.rightBottom[1]], points.mainStart], ++ // 上方小矩形 ++ [points.topBoxStart, [points.topBoxStart[0] + boxSize.width / 2, points.topBoxStart[1]]], ++ [[points.topBoxStart[0] + boxSize.width / 2, points.topBoxStart[1]], [points.topBoxStart[0] + boxSize.width / 2, points.topBoxEnd[1]]], ++ [[points.topBoxStart[0] + boxSize.width / 2, points.topBoxEnd[1]], [points.topBoxStart[0] - boxSize.width / 2, points.topBoxEnd[1]]], ++ [[points.topBoxStart[0] - boxSize.width / 2, points.topBoxEnd[1]], [points.topBoxStart[0] - boxSize.width / 2, points.topBoxStart[1]]], ++ [[points.topBoxStart[0] - boxSize.width / 2, points.topBoxStart[1]], points.topBoxStart], ++ // 下方小矩形 ++ [points.bottomBoxStart, [points.bottomBoxStart[0] + boxSize.width / 2, points.bottomBoxStart[1]]], ++ [[points.bottomBoxStart[0] + boxSize.width / 2, points.bottomBoxStart[1]], [points.bottomBoxStart[0] + boxSize.width / 2, points.bottomBoxEnd[1]]], ++ [[points.bottomBoxStart[0] + boxSize.width / 2, points.bottomBoxEnd[1]], [points.bottomBoxStart[0] - boxSize.width / 2, points.bottomBoxEnd[1]]], ++ [[points.bottomBoxStart[0] - boxSize.width / 2, points.bottomBoxEnd[1]], [points.bottomBoxStart[0] - boxSize.width / 2, points.bottomBoxStart[1]]], ++ [[points.bottomBoxStart[0] - boxSize.width / 2, points.bottomBoxStart[1]], points.bottomBoxStart], ++ // 连接线 ++ [points.mainStart, points.topBoxStart], ++ [points.topBoxEnd, points.bottomBoxStart], ++ [points.bottomBoxEnd, points.mainEnd] ++ ]; ++ return getNearestPointBetweenPointAndDiscreteSegments(point, segments); ++ }, ++ isInsidePoint(rectangle, point) { ++ const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]); ++ return RectangleClient.isHit(rectangle, rangeRectangle); ++ }, ++ getCornerPoints(rectangle) { ++ return RectangleClient.getCornerPoints(rectangle); ++ }, ++ getTangentVectorByConnectionPoint(rectangle, pointOfRectangle) { ++ const { points } = generateComponentPath(rectangle); ++ const connectionPoint = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle); ++ return getUnitVectorByPointAndPoint(points.mainStart, connectionPoint); ++ }, ++ getConnectorPoints(rectangle) { ++ const { points } = generateComponentPath(rectangle); ++ return [ ++ [rectangle.x + rectangle.width / 2, rectangle.y], ++ [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2], ++ [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height], ++ [points.mainStart[0], rectangle.y + rectangle.height / 2] ++ ]; ++ }, ++ getTextRectangle(board, element) { ++ const elementRectangle = RectangleClient.getRectangleByPoints(element.points); ++ const strokeWidth = getStrokeWidthByElement(element); ++ const width = elementRectangle.width - 24 - ShapeDefaultSpace.rectangleAndText * 2 - strokeWidth * 2; ++ const text = element.text; ++ const textSize = getTextSize(board, text, width); ++ return { ++ height: textSize.height, ++ width: width > 0 ? width : 0, ++ x: elementRectangle.x + 24 + ShapeDefaultSpace.rectangleAndText + strokeWidth, ++ y: elementRectangle.y + (elementRectangle.height - textSize.height) / 2 ++ }; ++ } ++}; ++ ++const ComponentBoxEngine = { ++ draw(board, rectangle, options) { ++ const rs = PlaitBoard.getRoughSVG(board); ++ const componentWidth = rectangle.width - 45 * 2 - 18 > 1 ? 45 : rectangle.width * 0.25; ++ const componentHeight = rectangle.height - 30 - 8 * 2 > 1 ? 30 : rectangle.height * 0.2; ++ const componentRectangle = { ++ x: rectangle.x + rectangle.width - 18 - componentWidth, ++ y: rectangle.y + 8, ++ width: componentWidth, ++ height: componentHeight ++ }; ++ const shape = rs.path(`M${rectangle.x} ${rectangle.y} ++ H${rectangle.x + rectangle.width} ++ V${rectangle.y + rectangle.height} ++ H${rectangle.x} Z ++ ++ `, { ...options, fillStyle: 'solid' }); ++ const componentShape = ComponentEngine.draw(board, componentRectangle, options); ++ shape.append(componentShape); ++ setStrokeLinecap(shape, 'round'); ++ return shape; ++ }, ++ isInsidePoint(rectangle, point) { ++ const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]); ++ return RectangleClient.isHit(rectangle, rangeRectangle); ++ }, ++ getCornerPoints(rectangle) { ++ return RectangleClient.getCornerPoints(rectangle); ++ }, ++ getNearestPoint(rectangle, point) { ++ return getNearestPointBetweenPointAndSegments(point, RectangleEngine.getCornerPoints(rectangle)); ++ }, ++ getEdgeByConnectionPoint(rectangle, pointOfRectangle) { ++ const corners = RectangleEngine.getCornerPoints(rectangle); ++ const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle); ++ return getPolygonEdgeByConnectionPoint(corners, point); ++ }, ++ getConnectorPoints(rectangle) { ++ return RectangleClient.getEdgeCenterPoints(rectangle); ++ }, ++ getTextRectangle(board, element) { ++ const elementRectangle = RectangleClient.getRectangleByPoints(element.points); ++ const strokeWidth = getStrokeWidthByElement(element); ++ const componentWidth = elementRectangle.width - 45 * 2 - 18 > 1 ? 45 : elementRectangle.width * 0.25; ++ const width = elementRectangle.width - 18 - componentWidth - ShapeDefaultSpace.rectangleAndText - strokeWidth * 2; ++ const text = element.text; ++ const textSize = getTextSize(board, text, width); ++ return { ++ height: textSize.height, ++ width: width > 0 ? width : 0, ++ x: elementRectangle.x + ShapeDefaultSpace.rectangleAndText + strokeWidth, ++ y: elementRectangle.y + (elementRectangle.height - textSize.height) / 2 ++ }; ++ } ++}; ++ ++const TemplateEngine = { ++ draw(board, rectangle, options) { ++ const rs = PlaitBoard.getRoughSVG(board); ++ return drawRoundRectangle(rs, rectangle.x, rectangle.y, rectangle.x + rectangle.width, rectangle.y + rectangle.height, { ++ ...options, ++ fillStyle: 'solid', ++ dashGap: 10, ++ strokeLineDash: [10, 10] ++ }, false, 4); ++ }, ++ isInsidePoint(rectangle, point) { ++ const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]); ++ return RectangleClient.isHit(rectangle, rangeRectangle); ++ }, ++ getCornerPoints(rectangle) { ++ return RectangleClient.getCornerPoints(rectangle); ++ }, ++ getNearestPoint(rectangle, point) { ++ return getNearestPointBetweenPointAndSegments(point, RectangleEngine.getCornerPoints(rectangle)); ++ }, ++ getEdgeByConnectionPoint(rectangle, pointOfRectangle) { ++ const corners = RectangleEngine.getCornerPoints(rectangle); ++ const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle); ++ return getPolygonEdgeByConnectionPoint(corners, point); ++ }, ++ getConnectorPoints(rectangle) { ++ return RectangleClient.getEdgeCenterPoints(rectangle); ++ }, ++ getTextRectangle(board, element) { ++ const elementRectangle = RectangleClient.getRectangleByPoints(element.points); ++ const strokeWidth = getStrokeWidthByElement(element); ++ const width = elementRectangle.width - ShapeDefaultSpace.rectangleAndText * 2 - strokeWidth * 2; ++ const text = element.text; ++ const textSize = getTextSize(board, text, width); ++ return { ++ height: textSize.height, ++ width: width > 0 ? width : 0, ++ x: elementRectangle.x + ShapeDefaultSpace.rectangleAndText + strokeWidth, ++ y: elementRectangle.y + (elementRectangle.height - textSize.height) / 2 ++ }; ++ } ++}; ++ ++const ShapeEngineMap = { ++ [BasicShapes.rectangle]: RectangleEngine, ++ [BasicShapes.diamond]: DiamondEngine, ++ [BasicShapes.ellipse]: EllipseEngine, ++ [BasicShapes.parallelogram]: ParallelogramEngine, ++ [BasicShapes.roundRectangle]: RoundRectangleEngine, ++ [BasicShapes.text]: RectangleEngine, ++ [BasicShapes.triangle]: TriangleEngine, ++ [BasicShapes.leftArrow]: LeftArrowEngine, ++ [BasicShapes.trapezoid]: TrapezoidEngine, ++ [BasicShapes.rightArrow]: RightArrowEngine, ++ [BasicShapes.cross]: CrossEngine, ++ [BasicShapes.star]: StarEngine, ++ [BasicShapes.pentagon]: PentagonEngine, ++ [BasicShapes.hexagon]: HexagonEngine, ++ [BasicShapes.octagon]: OctagonEngine, ++ [BasicShapes.pentagonArrow]: PentagonArrowEngine, ++ [BasicShapes.processArrow]: ProcessArrowEngine, ++ [BasicShapes.twoWayArrow]: TwoWayArrowEngine, ++ [BasicShapes.comment]: CommentEngine, ++ [BasicShapes.roundComment]: RoundCommentEngine, ++ [BasicShapes.cloud]: CloudEngine, ++ [FlowchartSymbols.process]: RectangleEngine, ++ [FlowchartSymbols.decision]: DiamondEngine, ++ [FlowchartSymbols.connector]: EllipseEngine, ++ [FlowchartSymbols.data]: ParallelogramEngine, ++ [FlowchartSymbols.terminal]: TerminalEngine, ++ [FlowchartSymbols.database]: DatabaseEngine, ++ [FlowchartSymbols.hardDisk]: HardDiskEngine, ++ [FlowchartSymbols.internalStorage]: InternalStorageEngine, ++ [FlowchartSymbols.manualInput]: ManualInputEngine, ++ [FlowchartSymbols.preparation]: PreparationEngine, ++ [FlowchartSymbols.manualLoop]: ManualLoopEngine, ++ [FlowchartSymbols.merge]: MergeEngine, ++ [FlowchartSymbols.delay]: DelayEngine, ++ [FlowchartSymbols.storedData]: StoredDataEngine, ++ [FlowchartSymbols.or]: OrEngine, ++ [FlowchartSymbols.summingJunction]: SummingJunctionEngine, ++ [FlowchartSymbols.predefinedProcess]: PredefinedProcessEngine, ++ [FlowchartSymbols.offPage]: OffPageEngine, ++ [FlowchartSymbols.document]: DocumentEngine, ++ [FlowchartSymbols.multiDocument]: MultiDocumentEngine, ++ [FlowchartSymbols.noteCurlyLeft]: NoteCurlyLeftEngine, ++ [FlowchartSymbols.noteCurlyRight]: NoteCurlyRightEngine, ++ [FlowchartSymbols.noteSquare]: NoteSquareEngine, ++ [FlowchartSymbols.display]: DisplayEngine, ++ [SwimlaneSymbols.swimlaneHorizontal]: TableEngine, ++ [SwimlaneSymbols.swimlaneVertical]: TableEngine, ++ [TableSymbols.table]: TableEngine, ++ [UMLSymbols.actor]: ActorEngine, ++ [UMLSymbols.useCase]: EllipseEngine, ++ [UMLSymbols.container]: ContainerEngine, ++ [UMLSymbols.note]: NoteEngine, ++ [UMLSymbols.package]: PackageEngine, ++ [UMLSymbols.combinedFragment]: CombinedFragmentEngine, ++ [UMLSymbols.class]: TableEngine, ++ [UMLSymbols.interface]: TableEngine, ++ [UMLSymbols.activation]: RectangleEngine, ++ [UMLSymbols.object]: RectangleEngine, ++ [UMLSymbols.deletion]: DeletionEngine, ++ [UMLSymbols.activityClass]: ActiveClassEngine, ++ [UMLSymbols.simpleClass]: RectangleEngine, ++ [UMLSymbols.component]: ComponentEngine, ++ [UMLSymbols.componentBox]: ComponentBoxEngine, ++ [UMLSymbols.template]: TemplateEngine, ++ [UMLSymbols.port]: RectangleEngine, ++ [UMLSymbols.branchMerge]: DiamondEngine, ++ [UMLSymbols.assembly]: AssemblyEngine, ++ [UMLSymbols.requiredInterface]: RequiredInterfaceEngine, ++ [UMLSymbols.providedInterface]: ProvidedInterfaceEngine ++}; ++const getEngine = (shape) => { ++ return ShapeEngineMap[shape]; ++}; ++ ++const getArrowLineHandleRefPair = (board, element) => { ++ const strokeWidth = getStrokeWidthByElement(element); ++ const sourceBoundElement = element.source.boundId ? getElementById(board, element.source.boundId) : undefined; ++ const targetBoundElement = element.target.boundId ? getElementById(board, element.target.boundId) : undefined; ++ let sourcePoint = sourceBoundElement ? getConnectionPoint(sourceBoundElement, element.source.connection) : element.points[0]; ++ let targetPoint = targetBoundElement ++ ? getConnectionPoint(targetBoundElement, element.target.connection) ++ : element.points[element.points.length - 1]; ++ let sourceDirection = getDirectionByVector([targetPoint[0] - sourcePoint[0], targetPoint[1] - sourcePoint[1]]) || Direction.right; ++ let targetDirection = getOppositeDirection(sourceDirection); ++ const sourceFactor = getDirectionFactor(sourceDirection); ++ const targetFactor = getDirectionFactor(targetDirection); ++ const sourceHandleRef = { ++ key: ArrowLineHandleKey.source, ++ point: sourcePoint, ++ direction: sourceDirection, ++ vector: [sourceFactor.x, sourceFactor.y] ++ }; ++ const targetHandleRef = { ++ key: ArrowLineHandleKey.target, ++ point: targetPoint, ++ direction: targetDirection, ++ vector: [targetFactor.x, targetFactor.y] ++ }; ++ if (sourceBoundElement) { ++ const connectionOffset = PlaitArrowLine.isSourceMarkOrTargetMark(element, ArrowLineMarkerType.none, ArrowLineHandleKey.source) ++ ? 0 ++ : strokeWidth; ++ const sourceVector = getVectorByConnection(sourceBoundElement, element.source.connection); ++ sourceHandleRef.vector = sourceVector; ++ sourceHandleRef.boundElement = sourceBoundElement; ++ if (hasValidAngle(sourceBoundElement)) { ++ const direction = getDirectionByVector(rotateVector(sourceVector, sourceBoundElement.angle)); ++ sourceDirection = direction ? direction : sourceDirection; ++ } ++ else { ++ const direction = getDirectionByVector(sourceVector); ++ sourceDirection = direction ? direction : sourceDirection; ++ } ++ sourceHandleRef.direction = sourceDirection; ++ sourcePoint = getConnectionPoint(sourceBoundElement, element.source.connection, sourceDirection, connectionOffset); ++ sourceHandleRef.point = rotatePointsByElement(sourcePoint, sourceBoundElement) || sourcePoint; ++ } ++ if (targetBoundElement) { ++ const connectionOffset = PlaitArrowLine.isSourceMarkOrTargetMark(element, ArrowLineMarkerType.none, ArrowLineHandleKey.target) ++ ? 0 ++ : strokeWidth; ++ const targetVector = getVectorByConnection(targetBoundElement, element.target.connection); ++ targetHandleRef.vector = targetVector; ++ targetHandleRef.boundElement = targetBoundElement; ++ if (hasValidAngle(targetBoundElement)) { ++ const direction = getDirectionByVector(rotateVector(targetVector, targetBoundElement.angle)); ++ targetDirection = direction ? direction : targetDirection; ++ } ++ else { ++ const direction = getDirectionByVector(targetVector); ++ targetDirection = direction ? direction : targetDirection; ++ } ++ targetHandleRef.direction = targetDirection; ++ targetPoint = getConnectionPoint(targetBoundElement, element.target.connection, targetDirection, connectionOffset); ++ targetHandleRef.point = rotatePointsByElement(targetPoint, targetBoundElement) || targetPoint; ++ } ++ return { source: sourceHandleRef, target: targetHandleRef }; ++}; ++const getConnectionPoint = (geometry, connection, direction, delta) => { ++ const rectangle = RectangleClient.getRectangleByPoints(geometry.points); ++ if (direction && delta) { ++ const directionFactor = getDirectionFactor(direction); ++ const point = RectangleClient.getConnectionPoint(rectangle, connection); ++ return [point[0] + directionFactor.x * delta, point[1] + directionFactor.y * delta]; ++ } ++ else { ++ return RectangleClient.getConnectionPoint(rectangle, connection); ++ } ++}; ++const getVectorByConnection = (boundElement, connection) => { ++ const rectangle = RectangleClient.getRectangleByPoints(boundElement.points); ++ const shape = getElementShape(boundElement); ++ const engine = getEngine(shape); ++ let vector = [0, 0]; ++ const direction = getDirectionByPointOfRectangle(connection); ++ if (direction && boundElement.shape !== BasicShapes.ellipse) { ++ const factor = getDirectionFactor(direction); ++ return [factor.x, factor.y]; ++ } ++ if (engine.getEdgeByConnectionPoint) { ++ const edge = engine.getEdgeByConnectionPoint(rectangle, connection); ++ if (edge) { ++ const lineVector = [edge[1][0] - edge[0][0], edge[1][1] - edge[0][1]]; ++ return rotateVectorAnti90(lineVector); ++ } ++ } ++ if (engine.getTangentVectorByConnectionPoint) { ++ const lineVector = engine.getTangentVectorByConnectionPoint(rectangle, connection); ++ if (lineVector) { ++ return rotateVectorAnti90(lineVector); ++ } ++ } ++ return vector; ++}; ++const getElbowLineRouteOptions = (board, element, handleRefPair) => { ++ handleRefPair = handleRefPair ?? getArrowLineHandleRefPair(board, element); ++ const { sourceRectangle, targetRectangle } = getSourceAndTargetRectangle(board, element, handleRefPair); ++ const { sourceOuterRectangle, targetOuterRectangle } = getSourceAndTargetOuterRectangle(sourceRectangle, targetRectangle); ++ const sourcePoint = handleRefPair.source.point; ++ const targetPoint = handleRefPair.target.point; ++ const nextSourcePoint = getNextPoint(sourcePoint, sourceOuterRectangle, handleRefPair.source.direction); ++ const nextTargetPoint = getNextPoint(targetPoint, targetOuterRectangle, handleRefPair.target.direction); ++ return { ++ sourcePoint, ++ nextSourcePoint, ++ sourceRectangle, ++ sourceOuterRectangle, ++ targetPoint, ++ nextTargetPoint, ++ targetRectangle, ++ targetOuterRectangle ++ }; ++}; ++const collectArrowLineUpdatedRefsByGeometry = (board, element, refs) => { ++ const lines = findElements(board, { ++ match: (element) => { ++ if (PlaitDrawElement.isArrowLine(element)) { ++ return element.source.boundId === element.id || element.target.boundId === element.id; ++ } ++ return false; ++ }, ++ recursion: element => true ++ }); ++ if (lines.length) { ++ lines.forEach(line => { ++ const isSourceBound = line.source.boundId === element.id; ++ const handle = isSourceBound ? 'source' : 'target'; ++ const object = { ...line[handle] }; ++ const linePoints = getArrowLinePoints(board, line); ++ const point = isSourceBound ? linePoints[0] : linePoints[linePoints.length - 1]; ++ object.connection = getHitConnection(board, point, element); ++ const path = PlaitBoard.findPath(board, line); ++ const index = refs.findIndex(obj => Path.equals(obj.path, path)); ++ if (index === -1) { ++ refs.push({ ++ property: { ++ [handle]: object ++ }, ++ path ++ }); ++ } ++ else { ++ refs[index].property = { ...refs[index].property, [handle]: object }; ++ } ++ }); ++ } ++}; ++ ++var ArrowLineMarkerType; ++(function (ArrowLineMarkerType) { ++ ArrowLineMarkerType["arrow"] = "arrow"; ++ ArrowLineMarkerType["none"] = "none"; ++ ArrowLineMarkerType["openTriangle"] = "open-triangle"; ++ ArrowLineMarkerType["solidTriangle"] = "solid-triangle"; ++ ArrowLineMarkerType["sharpArrow"] = "sharp-arrow"; ++ ArrowLineMarkerType["oneSideUp"] = "one-side-up"; ++ ArrowLineMarkerType["oneSideDown"] = "one-side-down"; ++ ArrowLineMarkerType["hollowTriangle"] = "hollow-triangle"; ++ ArrowLineMarkerType["singleSlash"] = "single-slash"; ++})(ArrowLineMarkerType || (ArrowLineMarkerType = {})); ++var ArrowLineShape; ++(function (ArrowLineShape) { ++ ArrowLineShape["straight"] = "straight"; ++ ArrowLineShape["curve"] = "curve"; ++ ArrowLineShape["elbow"] = "elbow"; ++})(ArrowLineShape || (ArrowLineShape = {})); ++var ArrowLineHandleKey; ++(function (ArrowLineHandleKey) { ++ ArrowLineHandleKey["source"] = "source"; ++ ArrowLineHandleKey["target"] = "target"; ++})(ArrowLineHandleKey || (ArrowLineHandleKey = {})); ++const PlaitArrowLine = { ++ isSourceMarkOrTargetMark(line, markType, handleKey) { ++ if (handleKey === ArrowLineHandleKey.source) { ++ return line.source.marker === markType; ++ } ++ else { ++ return line.target.marker === markType; ++ } ++ }, ++ isSourceMark(line, markType) { ++ return PlaitArrowLine.isSourceMarkOrTargetMark(line, markType, ArrowLineHandleKey.source); ++ }, ++ isTargetMark(line, markType) { ++ return PlaitArrowLine.isSourceMarkOrTargetMark(line, markType, ArrowLineHandleKey.target); ++ }, ++ isBoundElementOfSource(line, element) { ++ return line.source.boundId === element.id; ++ }, ++ isBoundElementOfTarget(line, element) { ++ return line.target.boundId === element.id; ++ }, ++ getPoints(board, line) { ++ let sourcePoint; ++ if (line.source.boundId) { ++ const sourceElement = getElementById(board, line.source.boundId); ++ const sourceConnectionPoint = getConnectionPoint(sourceElement, line.source.connection); ++ sourcePoint = rotatePointsByElement(sourceConnectionPoint, sourceElement) || sourceConnectionPoint; ++ } ++ else { ++ sourcePoint = line.points[0]; ++ } ++ let targetPoint; ++ if (line.target.boundId) { ++ const targetElement = getElementById(board, line.target.boundId); ++ const targetConnectionPoint = getConnectionPoint(targetElement, line.target.connection); ++ targetPoint = rotatePointsByElement(targetConnectionPoint, targetElement) || targetConnectionPoint; ++ } ++ else { ++ targetPoint = line.points[line.points.length - 1]; ++ } ++ const restPoints = line.points.length > 2 ? line.points.slice(1, line.points.length - 1) : []; ++ return [sourcePoint, ...restPoints, targetPoint]; ++ } ++}; ++ ++var MemorizeKey; ++(function (MemorizeKey) { ++ MemorizeKey["basicShape"] = "basicShape"; ++ MemorizeKey["flowchart"] = "flowchart"; ++ MemorizeKey["text"] = "text"; ++ MemorizeKey["arrowLine"] = "arrow-line"; ++ MemorizeKey["UML"] = "UML"; ++})(MemorizeKey || (MemorizeKey = {})); ++ ++var VectorLinePointerType; ++(function (VectorLinePointerType) { ++ VectorLinePointerType["vectorLine"] = "vectorLine"; ++})(VectorLinePointerType || (VectorLinePointerType = {})); ++var VectorLineShape; ++(function (VectorLineShape) { ++ VectorLineShape["straight"] = "straight"; ++ VectorLineShape["curve"] = "curve"; ++})(VectorLineShape || (VectorLineShape = {})); ++ ++const PlaitDrawElement = { ++ isGeometry: (value) => { ++ return value.type === 'geometry'; ++ }, ++ isArrowLine: (value) => { ++ return value.type === 'arrow-line' || value.type === 'line'; ++ }, ++ isVectorLine: (value) => { ++ return value.type === 'vector-line'; ++ }, ++ isLine: (value) => { ++ return PlaitDrawElement.isArrowLine(value) || PlaitDrawElement.isVectorLine(value); ++ }, ++ isText: (value) => { ++ return value.type === 'geometry' && value.shape === BasicShapes.text; ++ }, ++ isImage: (value) => { ++ return value.type === 'image'; ++ }, ++ isTable: (value) => { ++ return PlaitTableElement.isTable(value); ++ }, ++ isDrawElement: (value) => { ++ if (PlaitDrawElement.isGeometry(value) || ++ PlaitDrawElement.isLine(value) || ++ PlaitDrawElement.isImage(value) || ++ PlaitDrawElement.isTable(value) || ++ PlaitDrawElement.isSwimlane(value)) { ++ return true; ++ } ++ else { ++ return false; ++ } ++ }, ++ isCustomGeometryElement: (board, value) => { ++ const options = board.getPluginOptions(WithDrawPluginKey); ++ const customGeometryTypes = options?.customGeometryTypes || []; ++ if (customGeometryTypes.includes(value.type)) { ++ return true; ++ } ++ else { ++ return false; ++ } ++ }, ++ isShapeElement: (value) => { ++ return (PlaitDrawElement.isImage(value) || ++ PlaitDrawElement.isGeometry(value) || ++ PlaitDrawElement.isTable(value) || ++ PlaitDrawElement.isSwimlane(value)); ++ }, ++ isBasicShape: (value) => { ++ return Object.keys(BasicShapes).includes(value.shape); ++ }, ++ isFlowchart: (value) => { ++ return Object.keys(FlowchartSymbols).includes(value.shape); ++ }, ++ isUML: (value) => { ++ return Object.keys(UMLSymbols).includes(value.shape); ++ }, ++ isSwimlane: (value) => { ++ return value.type === 'swimlane'; ++ }, ++ isVerticalSwimlane: (value) => { ++ return PlaitDrawElement.isSwimlane(value) && value.shape === SwimlaneSymbols.swimlaneVertical; ++ }, ++ isHorizontalSwimlane: (value) => { ++ return PlaitDrawElement.isSwimlane(value) && value.shape === SwimlaneSymbols.swimlaneHorizontal; ++ }, ++ isUMLClassOrInterface: (value) => { ++ return Object.keys(UMLSymbols).includes(value.shape) && [UMLSymbols.class, UMLSymbols.interface].includes(value.shape); ++ }, ++ isGeometryByTable: (value) => { ++ return PlaitDrawElement.isUMLClassOrInterface(value); ++ }, ++ isElementByTable: (value) => { ++ return PlaitDrawElement.isTable(value) || PlaitDrawElement.isSwimlane(value) || PlaitDrawElement.isGeometryByTable(value); ++ } ++}; ++ ++class GeometryComponent extends CommonElementFlavour { ++ constructor() { ++ super(); ++ } ++ initializeGenerator() { ++ this.activeGenerator = createActiveGenerator(this.board, { ++ getStrokeWidth: () => { ++ const selectedElements = getSelectedElements(this.board); ++ if (selectedElements.length === 1 && !isSelectionMoving(this.board)) { ++ return ACTIVE_STROKE_WIDTH; ++ } ++ else { ++ return ACTIVE_STROKE_WIDTH; ++ } ++ }, ++ getStrokeOpacity: () => { ++ const selectedElements = getSelectedElements(this.board); ++ if (selectedElements.length === 1 && !isSelectionMoving(this.board)) { ++ return 1; ++ } ++ else { ++ return 0.5; ++ } ++ }, ++ getRectangle: (element) => { ++ return RectangleClient.getRectangleByPoints(element.points); ++ }, ++ hasResizeHandle: () => { ++ return hasResizeHandle(this.board, this.element); ++ } ++ }); ++ this.lineAutoCompleteGenerator = new ArrowLineAutoCompleteGenerator(this.board); ++ this.shapeGenerator = new GeometryShapeGenerator(this.board); ++ if (isGeometryIncludeText(this.element)) { ++ this.initializeTextManage(); ++ } ++ this.getRef().addGenerator(ArrowLineAutoCompleteGenerator.key, this.lineAutoCompleteGenerator); ++ this.getRef().addGenerator(ActiveGenerator.key, this.activeGenerator); ++ this.getRef().updateActiveSection = () => { ++ this.activeGenerator.processDrawing(this.element, PlaitBoard.getActiveHost(this.board), { ++ selected: this.selected ++ }); ++ this.lineAutoCompleteGenerator.processDrawing(this.element, PlaitBoard.getActiveHost(this.board), { ++ selected: this.selected ++ }); ++ }; ++ } ++ initialize() { ++ super.initialize(); ++ this.initializeGenerator(); ++ this.shapeGenerator.processDrawing(this.element, this.getElementG()); ++ this.activeGenerator.processDrawing(this.element, PlaitBoard.getActiveHost(this.board), { ++ selected: this.selected ++ }); ++ this.lineAutoCompleteGenerator.processDrawing(this.element, PlaitBoard.getElementTopHost(this.board), { ++ selected: this.selected ++ }); ++ this.textGenerator && this.textGenerator.draw(this.getElementG()); ++ } ++ onContextChanged(value, previous) { ++ if (value.element !== previous.element || value.hasThemeChanged) { ++ this.shapeGenerator.processDrawing(this.element, this.getElementG()); ++ this.activeGenerator.processDrawing(this.element, PlaitBoard.getActiveHost(this.board), { selected: this.selected }); ++ this.lineAutoCompleteGenerator.processDrawing(this.element, PlaitBoard.getActiveHost(this.board), { ++ selected: this.selected ++ }); ++ this.textGenerator && this.updateText(previous.element, value.element); ++ } ++ else { ++ const hasSameSelected = value.selected === previous.selected; ++ if (!hasSameSelected || value.selected) { ++ this.activeGenerator.processDrawing(this.element, PlaitBoard.getActiveHost(this.board), { ++ selected: this.selected ++ }); ++ this.lineAutoCompleteGenerator.processDrawing(this.element, PlaitBoard.getActiveHost(this.board), { ++ selected: this.selected ++ }); ++ } ++ } ++ } ++ updateText(previousElement, currentElement) { ++ if (isMultipleTextGeometry(this.element)) { ++ this.textGenerator.update(this.element, previousElement.texts, currentElement.texts, this.getElementG()); ++ } ++ else { ++ this.textGenerator.update(this.element, previousElement.text, currentElement.text, this.getElementG()); ++ } ++ } ++ initializeTextManage() { ++ const onTextChange = (element, textManageChangeData, text) => { ++ if (textManageChangeData.newText) { ++ if (isMultipleTextGeometry(element)) { ++ DrawTransforms.setDrawTexts(this.board, element, { ++ id: text.id, ++ text: textManageChangeData.newText ++ }); ++ } ++ else { ++ DrawTransforms.setText(this.board, element, textManageChangeData.newText, textManageChangeData.width, textManageChangeData.height); ++ } ++ } ++ else { ++ DrawTransforms.setTextSize(this.board, element, textManageChangeData.width, textManageChangeData.height); ++ } ++ textManageChangeData.operations && memorizeLatestText(element, textManageChangeData.operations); ++ }; ++ if (isMultipleTextGeometry(this.element)) { ++ this.textGenerator = new TextGenerator(this.board, this.element, this.element.texts, { ++ onChange: onTextChange ++ }); ++ } ++ else { ++ this.textGenerator = new SingleTextGenerator(this.board, this.element, this.element.text, { ++ onChange: onTextChange, ++ getMaxWidth: () => { ++ let width = getTextRectangle$1(this.board, this.element).width; ++ const getRectangle = getEngine(this.element.shape).getTextRectangle; ++ if (getRectangle) { ++ width = getRectangle(this.board, this.element).width; ++ } ++ return PlaitDrawElement.isText(this.element) && this.element.autoSize ? GeometryThreshold.defaultTextMaxWidth : width; ++ } ++ }); ++ } ++ this.textGenerator.initialize(); ++ } ++ destroy() { ++ super.destroy(); ++ this.activeGenerator.destroy(); ++ this.lineAutoCompleteGenerator.destroy(); ++ this.textGenerator?.destroy(); ++ } ++} ++ ++const debugKey = 'debug:plait:line-turning'; ++const debugGenerator$1 = createDebugGenerator(debugKey); ++class ArrowLineComponent extends CommonElementFlavour { ++ constructor() { ++ super(); ++ this.boundedElements = {}; ++ } ++ initializeGenerator() { ++ this.shapeGenerator = new ArrowLineShapeGenerator(this.board); ++ this.activeGenerator = new LineActiveGenerator(this.board); ++ this.initializeTextManages(); ++ } ++ initialize() { ++ this.initializeGenerator(); ++ this.shapeGenerator.processDrawing(this.element, this.getElementG()); ++ const linePoints = getArrowLinePoints(this.board, this.element); ++ this.activeGenerator.processDrawing(this.element, PlaitBoard.getActiveHost(this.board), { ++ selected: this.selected, ++ linePoints ++ }); ++ super.initialize(); ++ this.boundedElements = this.getBoundedElements(); ++ this.drawText(); ++ this.getRef().updateActiveSection = () => { ++ const linePoints = getArrowLinePoints(this.board, this.element); ++ this.activeGenerator.processDrawing(this.element, PlaitBoard.getActiveHost(this.board), { ++ selected: this.selected, ++ linePoints ++ }); ++ }; ++ debugGenerator$1.isDebug() && debugGenerator$1.drawCircles(this.board, this.element.points.slice(1, -1), 4, true); ++ } ++ getBoundedElements() { ++ const boundedElements = {}; ++ if (this.element.source.boundId) { ++ const boundElement = getElementById(this.board, this.element.source.boundId); ++ if (boundElement) { ++ boundedElements.source = boundElement; ++ } ++ } ++ if (this.element.target.boundId) { ++ const boundElement = getElementById(this.board, this.element.target.boundId); ++ if (boundElement) { ++ boundedElements.target = boundElement; ++ } ++ } ++ return boundedElements; ++ } ++ onContextChanged(value, previous) { ++ const boundedElements = this.getBoundedElements(); ++ const isBoundedElementsChanged = boundedElements.source !== this.boundedElements.source || boundedElements.target !== this.boundedElements.target; ++ this.boundedElements = boundedElements; ++ const linePoints = getArrowLinePoints(this.board, this.element); ++ if (value.element !== previous.element || value.hasThemeChanged) { ++ this.shapeGenerator.processDrawing(this.element, this.getElementG()); ++ this.activeGenerator.processDrawing(this.element, PlaitBoard.getActiveHost(this.board), { ++ selected: this.selected, ++ linePoints ++ }); ++ this.updateText(previous.element.texts, value.element.texts); ++ this.updateTextRectangle(); ++ } ++ else { ++ const needUpdate = value.selected !== previous.selected || this.activeGenerator.needUpdate(); ++ if (needUpdate || value.selected) { ++ this.activeGenerator.processDrawing(this.element, PlaitBoard.getActiveHost(this.board), { ++ selected: this.selected, ++ linePoints ++ }); ++ } ++ } ++ if (isBoundedElementsChanged) { ++ this.shapeGenerator.processDrawing(this.element, this.getElementG()); ++ this.activeGenerator.processDrawing(this.element, PlaitBoard.getActiveHost(this.board), { ++ selected: this.selected, ++ linePoints ++ }); ++ this.updateTextRectangle(); ++ return; ++ } ++ } ++ initializeTextManages() { ++ if (this.element.texts?.length) { ++ const textManages = []; ++ this.element.texts.forEach((text, index) => { ++ const manage = this.createTextManage(text, index); ++ textManages.push(manage); ++ }); ++ this.getRef().initializeTextManage(textManages); ++ } ++ } ++ drawText() { ++ if (this.element.texts?.length) { ++ this.getRef() ++ .getTextManages() ++ .forEach((manage, index) => { ++ manage.draw(this.element.texts[index].text); ++ this.getElementG().append(manage.g); ++ }); ++ } ++ } ++ createTextManage(text, index) { ++ return new TextManage(this.board, { ++ getRectangle: () => { ++ return getArrowLineTextRectangle(this.board, this.element, index); ++ }, ++ onChange: (textManageChangeData) => { ++ const path = PlaitBoard.findPath(this.board, this.element); ++ const node = PlaitNode.get(this.board, path); ++ const texts = [...node.texts]; ++ // const newWidth = textManageChangeData.width < MIN_TEXT_WIDTH ? MIN_TEXT_WIDTH : textManageChangeData.width; ++ texts.splice(index, 1, { ++ text: textManageChangeData.newText ? textManageChangeData.newText : this.element.texts[index].text, ++ position: this.element.texts[index].position ++ }); ++ DrawTransforms.setArrowLineTexts(this.board, this.element, texts); ++ textManageChangeData.operations && memorizeLatestText(this.element, textManageChangeData.operations); ++ }, ++ getMaxWidth: () => GeometryThreshold.defaultTextMaxWidth, ++ textPlugins: [] ++ }); ++ } ++ updateText(previousTexts, currentTexts) { ++ if (previousTexts === currentTexts) ++ return; ++ const previousTextsLength = previousTexts.length; ++ const currentTextsLength = currentTexts.length; ++ const textManages = this.getRef().getTextManages(); ++ if (currentTextsLength === previousTextsLength) { ++ for (let i = 0; i < previousTextsLength; i++) { ++ if (previousTexts[i].text !== currentTexts[i].text) { ++ textManages[i].updateText(currentTexts[i].text); ++ } ++ } ++ } ++ else { ++ this.getRef().destroyTextManage(); ++ this.initializeTextManages(); ++ this.drawText(); ++ } ++ } ++ updateTextRectangle() { ++ const textManages = this.getRef().getTextManages(); ++ textManages.forEach((manage) => { ++ manage.updateRectangle(); ++ }); ++ } ++ destroy() { ++ super.destroy(); ++ this.activeGenerator.destroy(); ++ this.getRef().destroyTextManage(); ++ } ++} ++ ++class VectorLineComponent extends CommonElementFlavour { ++ constructor() { ++ super(); ++ } ++ initializeGenerator() { ++ this.shapeGenerator = new VectorLineShapeGenerator(this.board); ++ this.activeGenerator = new LineActiveGenerator(this.board); ++ this.getRef().updateActiveSection = () => { ++ const linePoints = getVectorLinePoints(this.board, this.element); ++ this.activeGenerator.processDrawing(this.element, PlaitBoard.getActiveHost(this.board), { ++ selected: this.selected, ++ linePoints ++ }); ++ }; ++ } ++ initialize() { ++ this.initializeGenerator(); ++ this.shapeGenerator.processDrawing(this.element, this.getElementG()); ++ const linePoints = getVectorLinePoints(this.board, this.element); ++ this.activeGenerator.processDrawing(this.element, PlaitBoard.getActiveHost(this.board), { ++ selected: this.selected, ++ linePoints ++ }); ++ super.initialize(); ++ } ++ onContextChanged(value, previous) { ++ const linePoints = getVectorLinePoints(this.board, this.element); ++ if (value.element !== previous.element || value.hasThemeChanged) { ++ this.shapeGenerator.processDrawing(this.element, this.getElementG()); ++ this.activeGenerator.processDrawing(this.element, PlaitBoard.getActiveHost(this.board), { ++ selected: this.selected, ++ linePoints ++ }); ++ } ++ else { ++ const needUpdate = value.selected !== previous.selected || this.activeGenerator.needUpdate() || value.selected; ++ if (needUpdate) { ++ this.activeGenerator.processDrawing(this.element, PlaitBoard.getActiveHost(this.board), { ++ selected: this.selected, ++ linePoints ++ }); ++ } ++ } ++ } ++ destroy() { ++ super.destroy(); ++ this.activeGenerator.destroy(); ++ } ++} ++ ++const withDrawHotkey = (board) => { ++ const { keyDown, dblClick } = board; ++ board.keyDown = (event) => { ++ const selectedElements = getSelectedElements(board); ++ const isSingleSelection = selectedElements.length === 1; ++ const targetElement = selectedElements[0]; ++ if (!PlaitBoard.isReadonly(board) && ++ !isVirtualKey(event) && ++ !isDelete(event) && ++ !isSpaceHotkey(event) && ++ isSingleSelection && ++ PlaitDrawElement.isGeometry(targetElement)) { ++ event.preventDefault(); ++ editText(board, targetElement); ++ return; ++ } ++ keyDown(event); ++ }; ++ board.dblClick = (event) => { ++ event.preventDefault(); ++ if (!PlaitBoard.isReadonly(board)) { ++ const point = toViewBoxPoint(board, toHostPoint(board, event.x, event.y)); ++ const hitElement = getHitElementByPoint(board, point, undefined, false); ++ if (hitElement && PlaitDrawElement.isGeometry(hitElement) && isDrawElementIncludeText(hitElement)) { ++ if (isMultipleTextGeometry(hitElement)) { ++ const hitText = getHitMultipleGeometryText(board, hitElement, point) || ++ hitElement.texts.find((item) => item.id.includes(GeometryCommonTextKeys.content)) || ++ hitElement.texts[0]; ++ editText(board, hitElement, hitText); ++ } ++ else { ++ editText(board, hitElement); ++ } ++ return; ++ } ++ } ++ dblClick(event); ++ }; ++ return board; ++}; ++ ++const isGeometryDndMode = (board) => { ++ const geometryPointers = getGeometryPointers(); ++ const isGeometryPointer = PlaitBoard.isInPointer(board, geometryPointers); ++ const dndMode = isGeometryPointer && isDndMode(board); ++ return dndMode; ++}; ++const isGeometryDrawingMode = (board) => { ++ const geometryPointers = getGeometryPointers(); ++ const isGeometryPointer = PlaitBoard.isInPointer(board, geometryPointers); ++ const drawingMode = isGeometryPointer && isDrawingMode(board); ++ return drawingMode; ++}; ++const withGeometryCreateByDrag = (board) => { ++ const { pointerMove, globalPointerUp, pointerUp } = board; ++ let geometryShapeG = null; ++ let temporaryElement = null; ++ let fakeCreateTextRef = null; ++ board.pointerMove = (event) => { ++ geometryShapeG?.remove(); ++ geometryShapeG = createG(); ++ const geometryPointers = getGeometryPointers(); ++ const isGeometryPointer = PlaitBoard.isInPointer(board, geometryPointers); ++ const dragMode = isGeometryPointer && isDndMode(board); ++ const movingPoint = toViewBoxPoint(board, toHostPoint(board, event.x, event.y)); ++ const pointer = PlaitBoard.getPointer(board); ++ const geometryGenerator = getGeometryGeneratorByShape(board, pointer); ++ if (dragMode) { ++ const memorizedLatest = getMemorizedLatestByPointer(pointer); ++ if (pointer === BasicShapes.text) { ++ const property = getTextShapeProperty(board, getDefaultGeometryText(board), memorizedLatest.textProperties['font-size']); ++ const points = RectangleClient.getPoints(RectangleClient.getRectangleByCenterPoint(movingPoint, property.width, property.height)); ++ temporaryElement = createTextElement(board, points, getDefaultGeometryText(board)); ++ if (!fakeCreateTextRef) { ++ const textManage = new TextManage(board, { ++ getRectangle: () => { ++ return getTextRectangle$1(board, temporaryElement); ++ } ++ }); ++ textManage.draw(temporaryElement.text); ++ fakeCreateTextRef = { ++ g: createG(), ++ textManage ++ }; ++ PlaitBoard.getHost(board).append(fakeCreateTextRef.g); ++ fakeCreateTextRef.g.append(textManage.g); ++ } ++ else { ++ fakeCreateTextRef.textManage.updateRectangle(); ++ fakeCreateTextRef.g.append(fakeCreateTextRef.textManage.g); ++ } ++ } ++ else { ++ const points = getDefaultGeometryPoints(pointer, movingPoint); ++ temporaryElement = createDefaultGeometry(board, points, pointer); ++ geometryGenerator.processDrawing(temporaryElement, geometryShapeG); ++ PlaitBoard.getElementTopHost(board).append(geometryShapeG); ++ } ++ } ++ pointerMove(event); ++ }; ++ board.pointerUp = (event) => { ++ if (isGeometryDndMode(board) && temporaryElement) { ++ return; ++ } ++ pointerUp(event); ++ }; ++ board.globalPointerUp = (event) => { ++ if (isGeometryDndMode(board) && temporaryElement) { ++ insertElement(board, temporaryElement); ++ fakeCreateTextRef?.textManage.destroy(); ++ fakeCreateTextRef?.g.remove(); ++ fakeCreateTextRef = null; ++ } ++ temporaryElement = null; ++ geometryShapeG?.remove(); ++ geometryShapeG = null; ++ globalPointerUp(event); ++ }; ++ return board; ++}; ++const withGeometryCreateByDrawing = (board) => { ++ const { pointerDown, pointerMove, pointerUp, keyDown, keyUp, touchMove } = board; ++ let start = null; ++ let geometryShapeG = null; ++ let temporaryElement = null; ++ let isShift = false; ++ let snapG; ++ board.keyDown = (event) => { ++ isShift = isKeyHotkey('shift', event); ++ keyDown(event); ++ }; ++ board.keyUp = (event) => { ++ isShift = false; ++ keyUp(event); ++ }; ++ board.pointerDown = (event) => { ++ if (!PlaitBoard.isReadonly(board) && isGeometryDrawingMode(board)) { ++ const point = toViewBoxPoint(board, toHostPoint(board, event.x, event.y)); ++ start = point; ++ const pointer = PlaitBoard.getPointer(board); ++ if (pointer === BasicShapes.text) { ++ const memorizedLatest = getMemorizedLatestByPointer(pointer); ++ const property = getTextShapeProperty(board, getDefaultGeometryText(board), memorizedLatest.textProperties['font-size']); ++ const points = RectangleClient.getPoints(RectangleClient.getRectangleByCenterPoint(point, property.width, property.height)); ++ const textElement = createTextElement(board, points, getDefaultGeometryText(board)); ++ insertElement(board, textElement); ++ start = null; ++ } ++ } ++ pointerDown(event); ++ }; ++ board.touchMove = (event) => { ++ if (start && isGeometryDrawingMode(board)) { ++ event.preventDefault(); ++ } ++ touchMove(event); ++ }; ++ board.pointerMove = (event) => { ++ geometryShapeG?.remove(); ++ geometryShapeG = createG(); ++ const movingPoint = toViewBoxPoint(board, toHostPoint(board, event.x, event.y)); ++ const pointer = PlaitBoard.getPointer(board); ++ const geometryGenerator = getGeometryGeneratorByShape(board, pointer); ++ snapG?.remove(); ++ if (start && isGeometryDrawingMode(board)) { ++ let points = normalizeShapePoints([start, movingPoint], isShift); ++ const activeRectangle = RectangleClient.getRectangleByPoints(points); ++ const [x, y] = getUnitVectorByPointAndPoint(start, movingPoint); ++ const resizeSnapRef = getSnapResizingRef(board, [], { ++ resizePoints: points, ++ activeRectangle, ++ directionFactors: [getDirectionFactorByDirectionComponent(x), getDirectionFactorByDirectionComponent(y)], ++ isAspectRatio: isShift, ++ isFromCorner: true, ++ isCreate: true ++ }); ++ snapG = resizeSnapRef.snapG; ++ PlaitBoard.getElementTopHost(board).append(snapG); ++ points = normalizeShapePoints(resizeSnapRef.activePoints, isShift); ++ temporaryElement = createDefaultGeometry(board, points, pointer); ++ geometryGenerator.processDrawing(temporaryElement, geometryShapeG); ++ PlaitBoard.getElementTopHost(board).append(geometryShapeG); ++ } ++ pointerMove(event); ++ }; ++ board.pointerUp = (event) => { ++ if (isGeometryDrawingMode(board) && start) { ++ const targetPoint = toViewBoxPoint(board, toHostPoint(board, event.x, event.y)); ++ const { width, height } = RectangleClient.getRectangleByPoints([start, targetPoint]); ++ if (Math.hypot(width, height) < 8) { ++ const pointer = PlaitBoard.getPointer(board); ++ if (pointer !== BasicShapes.text) { ++ const points = getDefaultGeometryPoints(pointer, targetPoint); ++ temporaryElement = createDefaultGeometry(board, points, pointer); ++ } ++ } ++ if (temporaryElement) { ++ insertElement(board, temporaryElement); ++ } ++ snapG?.remove(); ++ geometryShapeG?.remove(); ++ geometryShapeG = null; ++ start = null; ++ temporaryElement = null; ++ return; ++ } ++ pointerUp(event); ++ }; ++ return board; ++}; ++ ++const withDrawFragment = (baseBoard) => { ++ const board = baseBoard; ++ const { getDeletedFragment, buildFragment, insertFragment } = board; ++ board.getDeletedFragment = (data) => { ++ const drawElements = getSelectedDrawElements(board); ++ if (drawElements.length) { ++ const geometryElements = drawElements.filter(value => PlaitDrawElement.isGeometry(value)); ++ const arrowLineElements = drawElements.filter(value => PlaitDrawElement.isArrowLine(value)); ++ const vectorLineElements = drawElements.filter(value => PlaitDrawElement.isVectorLine(value)); ++ const imageElements = drawElements.filter(value => PlaitDrawElement.isImage(value)); ++ const tableElements = drawElements.filter(value => PlaitDrawElement.isTable(value)); ++ const swimlaneElements = drawElements.filter(value => PlaitDrawElement.isSwimlane(value)); ++ const boundLineElements = [ ++ ...getBoundedArrowLineElements(board, geometryElements), ++ ...getBoundedArrowLineElements(board, imageElements), ++ ...getBoundedArrowLineElements(board, tableElements), ++ ...getBoundedArrowLineElements(board, swimlaneElements) ++ ].filter(line => !arrowLineElements.includes(line)); ++ data.push(...[ ++ ...geometryElements, ++ ...arrowLineElements, ++ ...vectorLineElements, ++ ...imageElements, ++ ...tableElements, ++ ...swimlaneElements, ++ ...boundLineElements.filter(line => !arrowLineElements.includes(line)) ++ ]); ++ } ++ return getDeletedFragment(data); ++ }; ++ board.buildFragment = (clipboardContext, rectangle, operationType, originData) => { ++ const targetDrawElements = getSelectedDrawElements(board, originData); ++ let boundLineElements = []; ++ if (targetDrawElements.length) { ++ if (operationType === WritableClipboardOperationType.cut) { ++ const geometryElements = targetDrawElements.filter(value => PlaitDrawElement.isGeometry(value)); ++ const lineElements = targetDrawElements.filter(value => PlaitDrawElement.isArrowLine(value)); ++ boundLineElements = getBoundedArrowLineElements(board, geometryElements).filter(line => !lineElements.includes(line)); ++ } ++ const selectedElements = [...targetDrawElements, ...boundLineElements]; ++ const elements = buildClipboardData(board, selectedElements, rectangle ? [rectangle.x, rectangle.y] : [0, 0]); ++ const text = getElementsText(selectedElements); ++ const addition = { ++ text, ++ type: WritableClipboardType.elements, ++ elements: elements ++ }; ++ clipboardContext = addOrCreateClipboardContext(clipboardContext, addition); ++ } ++ return buildFragment(clipboardContext, rectangle, operationType, originData); ++ }; ++ board.insertFragment = (clipboardData, targetPoint, operationType) => { ++ const selectedElements = getSelectedElements(board); ++ if (clipboardData?.files?.length) { ++ const acceptImageArray = acceptImageTypes.map(type => 'image/' + type); ++ const canInsertionImage = !getElementOfFocusedImage(board) && !(selectedElements.length === 1 && board.isImageBindingAllowed(selectedElements[0])); ++ if (acceptImageArray.includes(clipboardData.files[0].type) && canInsertionImage) { ++ const imageFile = clipboardData.files[0]; ++ buildImage(board, imageFile, DEFAULT_IMAGE_WIDTH, imageItem => { ++ DrawTransforms.insertImage(board, imageItem, targetPoint); ++ }); ++ return; ++ } ++ } ++ if (clipboardData?.elements?.length) { ++ const drawElements = clipboardData.elements?.filter(value => PlaitDrawElement.isDrawElement(value)); ++ if (clipboardData.elements && clipboardData.elements.length > 0 && drawElements.length > 0) { ++ insertClipboardData(board, drawElements, targetPoint); ++ } ++ } ++ if (clipboardData?.text) { ++ if (!clipboardData.elements || clipboardData.elements.length === 0) { ++ // (* ̄︶ ̄) ++ const insertAsChildren = selectedElements.length === 1 && selectedElements[0].children; ++ const insertAsFreeText = !insertAsChildren; ++ if (insertAsFreeText) { ++ DrawTransforms.insertText(board, targetPoint, clipboardData.text); ++ return; ++ } ++ } ++ } ++ insertFragment(clipboardData, targetPoint, operationType); ++ }; ++ return board; ++}; ++const getBoundedArrowLineElements = (board, plaitShapes) => { ++ const lines = getArrowLines(board); ++ return lines.filter(line => plaitShapes.find(shape => PlaitArrowLine.isBoundElementOfSource(line, shape) || PlaitArrowLine.isBoundElementOfTarget(line, shape))); ++}; ++ ++const withArrowLineCreateByDraw = (board) => { ++ const { pointerDown, pointerMove, globalPointerUp, touchStart } = board; ++ let start = null; ++ let sourceElement; ++ let lineShapeG = null; ++ let temporaryElement = null; ++ board.touchStart = (event) => { ++ const linePointers = getArrowLinePointers(); ++ const isLinePointer = PlaitBoard.isInPointer(board, linePointers); ++ if (!PlaitBoard.isReadonly(board) && isLinePointer && isDrawingMode(board)) { ++ return event.preventDefault(); ++ } ++ touchStart(event); ++ }; ++ board.pointerDown = (event) => { ++ const linePointers = getArrowLinePointers(); ++ const isLinePointer = PlaitBoard.isInPointer(board, linePointers); ++ if (!PlaitBoard.isReadonly(board) && isLinePointer && isDrawingMode(board)) { ++ const point = toViewBoxPoint(board, toHostPoint(board, event.x, event.y)); ++ start = point; ++ const hitElement = getSnappingShape(board, point); ++ if (hitElement) { ++ sourceElement = hitElement; ++ const ref = getSnappingRef(board, hitElement, point); ++ start = ref.connectorPoint || ref.edgePoint; ++ } ++ } ++ pointerDown(event); ++ }; ++ board.pointerMove = (event) => { ++ lineShapeG?.remove(); ++ lineShapeG = createG(); ++ let movingPoint = toViewBoxPoint(board, toHostPoint(board, event.x, event.y)); ++ if (start) { ++ const lineShape = PlaitBoard.getPointer(board); ++ temporaryElement = handleArrowLineCreating(board, lineShape, start, movingPoint, sourceElement, lineShapeG); ++ } ++ pointerMove(event); ++ }; ++ board.globalPointerUp = (event) => { ++ if (temporaryElement) { ++ Transforms.insertNode(board, temporaryElement, [board.children.length]); ++ clearSelectedElement(board); ++ addSelectedElement(board, temporaryElement); ++ BoardTransforms.updatePointerType(board, PlaitPointerType.selection); ++ } ++ lineShapeG?.remove(); ++ lineShapeG = null; ++ sourceElement = null; ++ start = null; ++ temporaryElement = null; ++ globalPointerUp(event); ++ }; ++ return board; ++}; ++ ++const debugGenerator = createDebugGenerator('debug:plait:arrow-line-resize'); ++const withArrowLineResize = (board) => { ++ let elbowLineIndex; ++ let elbowLineDeleteCount; ++ let elbowSourcePoint; ++ let elbowTargetPoint; ++ let elbowNextRenderPoints; ++ const options = { ++ key: 'draw-line', ++ canResize: () => { ++ return true; ++ }, ++ hitTest: (point) => { ++ const selectedLineElements = getSelectedArrowLineElements(board); ++ if (selectedLineElements.length > 0) { ++ let result = null; ++ selectedLineElements.forEach((value) => { ++ const handleRef = getHitLineResizeHandleRef(board, value, point); ++ if (handleRef) { ++ result = { ++ element: value, ++ handle: handleRef.handle, ++ handleIndex: handleRef.handleIndex ++ }; ++ } ++ }); ++ return result; ++ } ++ return null; ++ }, ++ beforeResize: (resizeRef) => { ++ if (resizeRef.element.shape === ArrowLineShape.elbow && ++ resizeRef.handle !== LineResizeHandle.source && ++ resizeRef.handle !== LineResizeHandle.target) { ++ const params = getElbowLineRouteOptions(board, resizeRef.element); ++ if (isUseDefaultOrthogonalRoute(resizeRef.element, params)) { ++ return; ++ } ++ const points = [...resizeRef.element.points]; ++ const handleIndex = resizeRef.handleIndex; ++ const pointsOnElbow = getElbowPoints(board, resizeRef.element); ++ elbowSourcePoint = pointsOnElbow[0]; ++ elbowTargetPoint = pointsOnElbow[pointsOnElbow.length - 1]; ++ elbowNextRenderPoints = getNextRenderPoints(board, resizeRef.element, pointsOnElbow); ++ const value = getIndexAndDeleteCountByKeyPoint(board, resizeRef.element, [...points], elbowNextRenderPoints, handleIndex); ++ elbowLineIndex = value.index; ++ elbowLineDeleteCount = value.deleteCount; ++ } ++ }, ++ onResize: (resizeRef, resizeState) => { ++ const drawPoints = getArrowLinePoints(board, resizeRef.element); ++ let points = [...resizeRef.element.points]; ++ points[0] = drawPoints[0]; ++ points[points.length - 1] = drawPoints[drawPoints.length - 1]; ++ let source = { ...resizeRef.element.source }; ++ let target = { ...resizeRef.element.target }; ++ let handleIndex = resizeRef.handleIndex; ++ const hitElement = getSnappingShape(board, resizeState.endPoint); ++ if (resizeRef.handle === LineResizeHandle.source || resizeRef.handle === LineResizeHandle.target) { ++ const handleObject = resizeRef.handle === LineResizeHandle.source ? source : target; ++ if (debugGenerator.isDebug()) { ++ debugGenerator.clear(); ++ debugGenerator.drawCircles(board, points, 3, false); ++ debugGenerator.drawCircles(board, [resizeState.endPoint], 4, false, { fill: 'yellow' }); ++ } ++ points[handleIndex] = resizeState.endPoint; ++ points[handleIndex] = alignPoints(points, points[handleIndex], handleIndex); ++ if (debugGenerator.isDebug()) { ++ debugGenerator.drawCircles(board, [points[handleIndex]], 2, false, { fill: 'green' }); ++ } ++ if (hitElement) { ++ handleObject.connection = getHitConnection(board, points[handleIndex], hitElement); ++ handleObject.boundId = hitElement.id; ++ } ++ else { ++ handleObject.connection = undefined; ++ handleObject.boundId = undefined; ++ } ++ } ++ else { ++ if (resizeRef.element.shape === ArrowLineShape.elbow) { ++ if (elbowNextRenderPoints && elbowSourcePoint && elbowTargetPoint) { ++ const resizedPreviousAndNextPoint = getResizedPreviousAndNextPoint(elbowNextRenderPoints, elbowSourcePoint, elbowTargetPoint, handleIndex); ++ const startKeyPoint = elbowNextRenderPoints[handleIndex]; ++ const endKeyPoint = elbowNextRenderPoints[handleIndex + 1]; ++ const [newStartPoint, newEndPoint] = alignElbowSegment(startKeyPoint, endKeyPoint, resizeState, resizedPreviousAndNextPoint); ++ let midDataPoints = [...points].slice(1, points.length - 1); ++ if (elbowLineIndex !== null && elbowLineDeleteCount !== null) { ++ if (hasIllegalElbowPoint(midDataPoints)) { ++ midDataPoints = [newStartPoint, newEndPoint]; ++ } ++ else { ++ midDataPoints.splice(elbowLineIndex, elbowLineDeleteCount, newStartPoint, newEndPoint); ++ } ++ points = [elbowSourcePoint, ...midDataPoints, elbowTargetPoint]; ++ } ++ } ++ } ++ else { ++ if (resizeRef.handle === LineResizeHandle.addHandle) { ++ points.splice(handleIndex + 1, 0, resizeState.endPoint); ++ } ++ else { ++ points[handleIndex] = resizeState.endPoint; ++ } ++ } ++ if (resizeRef.element.shape !== ArrowLineShape.elbow || ++ (resizeRef.element.shape === ArrowLineShape.elbow && points.length === 2)) { ++ points[handleIndex] = alignPoints(points, points[handleIndex], handleIndex); ++ } ++ } ++ DrawTransforms.resizeArrowLine(board, { points, source, target }, resizeRef.path); ++ }, ++ afterResize: (resizeRef) => { ++ if (resizeRef.element.shape === ArrowLineShape.elbow) { ++ const element = PlaitNode.get(board, resizeRef.path); ++ let points = element && [...element.points]; ++ if (points.length > 2 && elbowNextRenderPoints && elbowSourcePoint && elbowTargetPoint) { ++ const nextSourcePoint = elbowNextRenderPoints[0]; ++ const nextTargetPoint = elbowNextRenderPoints[elbowNextRenderPoints.length - 1]; ++ points.splice(0, 1, nextSourcePoint); ++ points.splice(-1, 1, nextTargetPoint); ++ points = simplifyOrthogonalPoints(points); ++ if (Point.isEquals(points[0], nextSourcePoint)) { ++ points.splice(0, 1); ++ } ++ if (Point.isEquals(points[points.length - 1], nextTargetPoint)) { ++ points.pop(); ++ } ++ if (points.length === 1) { ++ points = []; ++ } ++ points = [elbowSourcePoint, ...points, elbowTargetPoint]; ++ DrawTransforms.resizeArrowLine(board, { points }, resizeRef.path); ++ } ++ } ++ elbowLineIndex = null; ++ elbowLineDeleteCount = null; ++ elbowSourcePoint = null; ++ elbowTargetPoint = null; ++ elbowNextRenderPoints = null; ++ } ++ }; ++ withResize(board, options); ++ return board; ++}; ++ ++/* ++Purpose: Visual feedback for snapping/binding arrow-line endpoints to shapes. ++- Active when using arrow-line pointers or resizing source/target handles. ++- Detects target shape under cursor and whether edge/connector snapping applies. ++- Draws bound outline/mask and a connector marker at the snap point. ++- Applies rotation to reaction graphics for angled (rotated) shapes. ++Lifecycle: pointerMove → detect hit/snapping → render reaction → pointerUp cleanup. ++*/ ++const withArrowLineBoundReaction = (board) => { ++ const { pointerMove, pointerUp } = board; ++ let boundShapeG = null; ++ board.pointerMove = (event) => { ++ boundShapeG?.remove(); ++ if (PlaitBoard.isReadonly(board)) { ++ pointerMove(event); ++ return; ++ } ++ const linePointers = Object.keys(ArrowLineShape); ++ const isLinePointer = PlaitBoard.isInPointer(board, linePointers); ++ const movingPoint = toViewBoxPoint(board, toHostPoint(board, event.x, event.y)); ++ const isLineResizing = isResizingByCondition(board, (resizeRef) => { ++ const { element, handle } = resizeRef; ++ const isSourceOrTarget = handle === LineResizeHandle.target || handle === LineResizeHandle.source; ++ return PlaitDrawElement.isArrowLine(element) && isSourceOrTarget; ++ }); ++ if (isLinePointer || isLineResizing) { ++ const hitElement = getHitShape(board, movingPoint); ++ if (hitElement) { ++ const ref = getSnappingRef(board, hitElement, movingPoint); ++ const isSnapping = ref.isHitEdge || ref.isHitConnector; ++ boundShapeG = drawBoundReaction(board, hitElement, { hasMask: isSnapping, hasConnector: true }); ++ if (isSnapping) { ++ const circleG = drawCircle(PlaitBoard.getRoughSVG(board), ref.connectorPoint || ref.edgePoint, 6, { ++ stroke: SELECTION_BORDER_COLOR, ++ strokeWidth: SNAPPING_STROKE_WIDTH, ++ fill: SELECTION_BORDER_COLOR, ++ fillStyle: 'solid' ++ }); ++ boundShapeG.appendChild(circleG); ++ } ++ if (hasValidAngle(hitElement)) { ++ setAngleForG(boundShapeG, RectangleClient.getCenterPointByPoints(hitElement.points), hitElement.angle); ++ } ++ PlaitBoard.getElementTopHost(board).append(boundShapeG); ++ } ++ } ++ pointerMove(event); ++ }; ++ board.pointerUp = (event) => { ++ boundShapeG?.remove(); ++ boundShapeG = null; ++ pointerUp(event); ++ }; ++ return board; ++}; ++ ++const getDefaultLineText = (board) => { ++ return getI18nValue(board, DrawI18nKey.lineText, LINE_TEXT); ++}; ++const withArrowLineText = (board) => { ++ const { dblClick } = board; ++ board.dblClick = (event) => { ++ if (!PlaitBoard.isReadonly(board)) { ++ const clickPoint = toViewBoxPoint(board, toHostPoint(board, event.x, event.y)); ++ const hitTarget = getHitElementByPoint(board, clickPoint); ++ if (hitTarget && PlaitDrawElement.isArrowLine(hitTarget)) { ++ const hitTargetPath = hitTarget && PlaitBoard.findPath(board, hitTarget); ++ const points = getArrowLinePoints(board, hitTarget); ++ const point = getNearestPointBetweenPointAndSegments(clickPoint, points); ++ const texts = hitTarget.texts?.length ? [...hitTarget.texts] : []; ++ const textIndex = getHitArrowLineTextIndex(board, hitTarget, clickPoint); ++ const isHitText = isHitArrowLineText(board, hitTarget, clickPoint); ++ if (isHitText) { ++ editHandle(board, hitTarget, textIndex); ++ } ++ else { ++ const defaultLineText = getDefaultLineText(board); ++ const textMemory = getMemorizedLatest('arrow-line')?.text || {}; ++ const textElement = buildText(defaultLineText, undefined, textMemory); ++ const ratio = getRatioByPoint(points, point); ++ texts.push({ ++ text: textElement, ++ position: ratio ++ }); ++ DrawTransforms.setArrowLineTexts(board, hitTarget, texts); ++ setTimeout(() => { ++ if (hitTargetPath) { ++ const newHitTarget = PlaitNode.get(board, hitTargetPath); ++ const textManages = getTextManages(newHitTarget); ++ editHandle(board, newHitTarget, textManages.length - 1, true); ++ } ++ }); ++ } ++ return; ++ } ++ } ++ dblClick(event); ++ }; ++ return board; ++}; ++function editHandle(board, element, manageIndex, isFirstEdit = false) { ++ const textManages = getTextManages(element); ++ const textManage = textManages[manageIndex]; ++ textManage.edit(() => { ++ const text = Node.string(textManage.getText()); ++ const defaultLineText = getDefaultLineText(board); ++ const shouldRemove = !text || (isFirstEdit && text === defaultLineText); ++ if (shouldRemove) { ++ DrawTransforms.removeArrowLineText(board, element, manageIndex); ++ } ++ }); ++} ++ ++class ImageComponent extends CommonElementFlavour { ++ constructor() { ++ super(); ++ } ++ initializeGenerator() { ++ this.imageGenerator = new ImageGenerator(this.board, { ++ getRectangle: (element) => { ++ return { ++ x: element.points[0][0], ++ y: element.points[0][1], ++ width: element.points[1][0] - element.points[0][0], ++ height: element.points[1][1] - element.points[0][1] ++ }; ++ }, ++ getImageItem: (element) => { ++ return { ++ url: element.url, ++ width: element.points[1][0] - element.points[0][0], ++ height: element.points[1][1] - element.points[0][1] ++ }; ++ } ++ }); ++ this.lineAutoCompleteGenerator = new ArrowLineAutoCompleteGenerator(this.board); ++ this.getRef().addGenerator(ArrowLineAutoCompleteGenerator.key, this.lineAutoCompleteGenerator); ++ this.getRef().updateActiveSection = () => { ++ this.imageGenerator.setFocus(this.element, this.selected); ++ this.lineAutoCompleteGenerator.processDrawing(this.element, PlaitBoard.getActiveHost(this.board), { ++ selected: this.selected ++ }); ++ }; ++ } ++ initialize() { ++ super.initialize(); ++ this.initializeGenerator(); ++ this.imageGenerator.processDrawing(this.element, this.getElementG()); ++ this.lineAutoCompleteGenerator.processDrawing(this.element, PlaitBoard.getActiveHost(this.board), { ++ selected: this.selected ++ }); ++ } ++ onContextChanged(value, previous) { ++ if (value.element !== previous.element) { ++ this.imageGenerator.updateImage(this.getElementG(), previous.element, value.element); ++ this.imageGenerator.setFocus(this.element, this.selected); ++ this.lineAutoCompleteGenerator.processDrawing(this.element, PlaitBoard.getActiveHost(this.board), { ++ selected: this.selected ++ }); ++ } ++ else { ++ const hasSameSelected = value.selected === previous.selected; ++ if (!hasSameSelected || value.selected) { ++ this.imageGenerator.setFocus(this.element, this.selected); ++ this.lineAutoCompleteGenerator.processDrawing(this.element, PlaitBoard.getActiveHost(this.board), { ++ selected: this.selected ++ }); ++ } ++ } ++ } ++ destroy() { ++ super.destroy(); ++ this.imageGenerator.destroy(); ++ this.lineAutoCompleteGenerator.destroy(); ++ } ++} ++ ++const WithArrowLineAutoCompletePluginKey = 'plait-arrow-line-auto-complete-plugin-key'; ++const BOARD_TO_PRE_COMMIT = new WeakMap(); ++/* ++Purpose: Create arrow line via auto-complete drag from a shape edge. ++- On pointerDown at an auto-complete point, set elbow mode and capture source. ++- On drag (beyond buffer), choose nearest crossing on the source edge if available. ++- Rotate source point; build temporary elbow arrow while moving. ++- On release, insert the arrow and select; otherwise commit preview pair if present. ++Lifecycle: pointerDown → pointerMove (create temp) → pointerUp insert/commit → reset. ++*/ ++const withArrowLineAutoComplete = (board) => { ++ const { pointerDown, pointerMove, globalPointerUp, touchMove } = board; ++ let autoCompletePoint = null; ++ let lineShapeG = null; ++ let sourceElement; ++ let temporaryElement; ++ board.pointerDown = (event) => { ++ const selectedElements = getSelectedDrawElements(board); ++ const targetElement = selectedElements.length === 1 && selectedElements[0]; ++ const activePoint = toActivePoint(board, event.x, event.y); ++ if (!PlaitBoard.isReadonly(board) && targetElement && PlaitDrawElement.isShapeElement(targetElement)) { ++ const points = getAutoCompletePoints(board, targetElement, true); ++ const index = getHitIndexOfAutoCompletePoint(rotateAntiPointsByElement(board, activePoint, targetElement, true) || activePoint, points); ++ const hitPoint = points[index]; ++ if (hitPoint) { ++ temporaryDisableSelection(board); ++ const screenPoint = toScreenPointFromActivePoint(board, hitPoint); ++ autoCompletePoint = toViewBoxPoint(board, toHostPoint(board, screenPoint[0], screenPoint[1])); ++ sourceElement = targetElement; ++ BoardTransforms.updatePointerType(board, ArrowLineShape.elbow); ++ } ++ } ++ pointerDown(event); ++ }; ++ board.touchMove = (event) => { ++ if (autoCompletePoint && sourceElement) { ++ return event.preventDefault(); ++ } ++ touchMove(event); ++ }; ++ board.pointerMove = (event) => { ++ lineShapeG?.remove(); ++ lineShapeG = createG(); ++ let movingPoint = toViewBoxPoint(board, toHostPoint(board, event.x, event.y)); ++ if (autoCompletePoint && sourceElement) { ++ const distance = distanceBetweenPointAndPoint(...(rotateAntiPointsByElement(board, movingPoint, sourceElement) || movingPoint), ...autoCompletePoint); ++ if (distance > PRESS_AND_MOVE_BUFFER * 2) { ++ const rectangle = RectangleClient.getRectangleByPoints(sourceElement.points); ++ const shape = getElementShape(sourceElement); ++ const engine = getEngine(shape); ++ let sourcePoint = autoCompletePoint; ++ if (engine.getNearestCrossingPoint) { ++ const crossingPoint = engine.getNearestCrossingPoint(rectangle, autoCompletePoint); ++ sourcePoint = crossingPoint; ++ } ++ // source point must be click point ++ const rotatedSourcePoint = rotatePointsByElement(sourcePoint, sourceElement) || sourcePoint; ++ temporaryElement = handleArrowLineCreating(board, ArrowLineShape.elbow, rotatedSourcePoint, movingPoint, sourceElement, lineShapeG); ++ Transforms.addSelectionWithTemporaryElements(board, []); ++ } ++ } ++ pointerMove(event); ++ }; ++ board.globalPointerUp = (event) => { ++ if (temporaryElement) { ++ Transforms.insertNode(board, temporaryElement, [board.children.length]); ++ clearSelectedElement(board); ++ addSelectedElement(board, temporaryElement); ++ const afterComplete = board.getPluginOptions(WithArrowLineAutoCompletePluginKey)?.afterComplete; ++ afterComplete && afterComplete(temporaryElement); ++ } ++ else { ++ const preCommitRef = BOARD_TO_PRE_COMMIT.get(board); ++ if (preCommitRef) { ++ Transforms.insertNode(board, preCommitRef.temporaryArrowLineElement, [board.children.length]); ++ insertElement(board, preCommitRef.temporaryShapeElement); ++ BOARD_TO_PRE_COMMIT.delete(board); ++ } ++ } ++ if (autoCompletePoint) { ++ BoardTransforms.updatePointerType(board, PlaitPointerType.selection); ++ autoCompletePoint = null; ++ } ++ lineShapeG?.remove(); ++ lineShapeG = null; ++ sourceElement = null; ++ temporaryElement = null; ++ globalPointerUp(event); ++ }; ++ return board; ++}; ++ ++const PREVIEW_ARROW_LINE_DISTANCE = 100; ++/* ++Purpose: Hover-driven auto-complete preview for arrow lines. ++- When hovering a selected shape’s auto-complete point, show a hint circle. ++- Offset a temporary shape and build an elbow arrow from the hit edge. ++- Rotate points by element angle; compute target connection; bind target id. ++- Store temporary arrow/shape for pre-commit; commit on click via auto-complete plugin. ++Lifecycle: pointerMove (hit/preview) → pointerLeave/globalPointerUp cleanup. ++*/ ++const withArrowLineAutoCompleteReaction = (board) => { ++ const { pointerMove, pointerLeave, globalPointerUp } = board; ++ let reactionG = null; ++ let temporaryArrowLineElement = null; ++ let temporaryShapeElement = null; ++ let temporaryArrowLineG = null; ++ let temporaryShapeG = null; ++ board.pointerMove = (event) => { ++ reactionG?.remove(); ++ PlaitBoard.getBoardContainer(board).classList.remove(CursorClass.crosshair); ++ const selectedElements = getSelectedDrawElements(board); ++ temporaryArrowLineG?.remove(); ++ temporaryShapeG?.remove(); ++ const originElement = selectedElements.length === 1 && selectedElements[0]; ++ const activePoint = toActivePoint(board, event.x, event.y); ++ if (!PlaitBoard.isReadonly(board) && !isSelectionMoving(board) && originElement && PlaitDrawElement.isShapeElement(originElement)) { ++ const points = getAutoCompletePoints(board, originElement, true); ++ const hitIndex = getHitIndexOfAutoCompletePoint(rotateAntiPointsByElement(board, activePoint, originElement, true) || activePoint, points); ++ const hitPoint = points[hitIndex]; ++ const ref = PlaitElement.getElementRef(originElement); ++ const lineAutoCompleteGenerator = ref.getGenerator(ArrowLineAutoCompleteGenerator.key); ++ lineAutoCompleteGenerator.recoverAutoCompleteG(); ++ if (hitPoint) { ++ // function 1: dnd ++ reactionG = drawCircle(PlaitBoard.getRoughSVG(board), hitPoint, LINE_AUTO_COMPLETE_HOVERED_DIAMETER, { ++ stroke: 'none', ++ fill: rgbaToHEX(PRIMARY_COLOR, LINE_AUTO_COMPLETE_HOVERED_OPACITY), ++ fillStyle: 'solid' ++ }); ++ PlaitBoard.getActiveHost(board).append(reactionG); ++ PlaitBoard.getBoardContainer(board).classList.add(CursorClass.crosshair); ++ if (hasValidAngle(originElement)) { ++ const rectangle = board.getRectangle(originElement); ++ const activeRectangle = toActiveRectangleFromViewBoxRectangle(board, rectangle); ++ setAngleForG(reactionG, RectangleClient.getCenterPoint(activeRectangle), originElement.angle); ++ } ++ // function 2: hover to preview and click to commit ++ if (PlaitDrawElement.isGeometry(originElement) && !PlaitDrawElement.isText(originElement)) { ++ const originRect = RectangleClient.getRectangleByPoints(originElement.points); ++ let arrowLineStartPoint = RectangleClient.getEdgeCenterPoints(originRect)[hitIndex]; ++ const arrowLineDirection = getDirectionByIndex(hitIndex); ++ let arrowLineEndPoint = moveXOfPoint(arrowLineStartPoint, PREVIEW_ARROW_LINE_DISTANCE, arrowLineDirection); ++ const geometryGenerator = getGeometryGeneratorByShape(board, originElement.shape); ++ const temporaryShapePoints = originElement.points.map((point) => moveXOfPoint(point, PREVIEW_ARROW_LINE_DISTANCE + ++ getXDistanceBetweenPoint(originElement.points[0], originElement.points[1], isHorizontalDirection(arrowLineDirection)), arrowLineDirection)); ++ temporaryArrowLineG = createG(); ++ temporaryShapeG = createG(); ++ temporaryArrowLineG.style.opacity = '0.6'; ++ temporaryShapeG.style.opacity = '0.6'; ++ temporaryShapeElement = createDefaultGeometry(board, temporaryShapePoints, originElement.shape); ++ temporaryShapeElement.angle = originElement.angle; ++ temporaryShapeElement.fill = originElement.fill; ++ temporaryShapeElement.strokeColor = originElement.strokeColor; ++ temporaryShapeElement.strokeStyle = originElement.strokeStyle; ++ temporaryShapeElement.strokeWidth = originElement.strokeWidth; ++ temporaryShapeElement.groupId = originElement.groupId; ++ const rotatedArrowLineStartPoint = rotatePointsByElement(arrowLineStartPoint, originElement) || arrowLineStartPoint; ++ const rotatedArrowLineEndPoint = rotatePointsByElement(arrowLineEndPoint, temporaryShapeElement) || arrowLineEndPoint; ++ temporaryArrowLineElement = handleArrowLineCreating(board, ArrowLineShape.elbow, rotatedArrowLineStartPoint, rotatedArrowLineEndPoint, originElement, temporaryArrowLineG); ++ BOARD_TO_PRE_COMMIT.set(board, { temporaryArrowLineElement, temporaryShapeElement }); ++ const connectionInfo = getHitConnection(board, rotatedArrowLineEndPoint, temporaryShapeElement); ++ temporaryArrowLineElement.target.boundId = temporaryShapeElement.id; ++ temporaryArrowLineElement.target.connection = connectionInfo; ++ geometryGenerator.processDrawing(temporaryShapeElement, temporaryShapeG); ++ PlaitBoard.getElementTopHost(board).append(temporaryShapeG); ++ } ++ return; ++ } ++ } ++ BOARD_TO_PRE_COMMIT.delete(board); ++ pointerMove(event); ++ }; ++ board.pointerLeave = (pointer) => { ++ clearRef(); ++ pointerLeave(pointer); ++ }; ++ const clearRef = () => { ++ if (reactionG) { ++ reactionG?.remove(); ++ PlaitBoard.getBoardContainer(board).classList.remove(CursorClass.crosshair); ++ temporaryArrowLineG?.remove(); ++ temporaryShapeG?.remove(); ++ } ++ if (BOARD_TO_PRE_COMMIT.get(board)) { ++ BOARD_TO_PRE_COMMIT.delete(board); ++ } ++ }; ++ board.globalPointerUp = (event) => { ++ globalPointerUp(event); ++ clearRef(); ++ }; ++ return board; ++}; ++ ++const withArrowLineTextMove = (board) => { ++ let textIndex = 0; ++ const movableBuffer = 100; ++ const options = { ++ key: 'line-text', ++ canResize: () => { ++ return true; ++ }, ++ hitTest: (point) => { ++ let result = null; ++ const line = getHitElementByPoint(board, point, (element) => { ++ return PlaitDrawElement.isArrowLine(element); ++ }); ++ if (line) { ++ const index = getHitArrowLineTextIndex(board, line, point); ++ const textManages = getTextManages(line); ++ const textManage = textManages[index]; ++ if (index !== -1 && !textManage.isEditing) { ++ textIndex = index; ++ return { element: line, handle: ResizeHandle.e }; ++ } ++ } ++ return result; ++ }, ++ onResize: (resizeRef, resizeState) => { ++ const element = resizeRef.element; ++ if (element) { ++ const movingPoint = resizeState.endPoint; ++ const points = getArrowLinePoints(board, element); ++ const distance = distanceBetweenPointAndSegments(movingPoint, points); ++ if (distance <= movableBuffer) { ++ const point = getNearestPointBetweenPointAndSegments(movingPoint, points, false); ++ const position = getRatioByPoint(points, point); ++ const texts = [...element.texts]; ++ texts[textIndex] = { ++ ...texts[textIndex], ++ position ++ }; ++ DrawTransforms.setArrowLineTexts(board, element, texts); ++ } ++ } ++ } ++ }; ++ withResize(board, options); ++ return board; ++}; ++ ++const withDrawRotate = (board) => { ++ const { pointerDown, pointerMove, globalPointerUp, afterChange, drawSelectionRectangle } = board; ++ let rotateRef = null; ++ let rotateHandleG; ++ let needCustomActiveRectangle = false; ++ const canRotate = () => { ++ const elements = getSelectedElements(board); ++ return (elements.length > 0 && ++ elements.every((el) => (PlaitDrawElement.isDrawElement(el) && !PlaitDrawElement.isArrowLine(el)) || ++ PlaitDrawElement.isCustomGeometryElement(board, el))); ++ }; ++ board.pointerDown = (event) => { ++ if (!canRotate() || PlaitBoard.isReadonly(board) || PlaitBoard.hasBeenTextEditing(board) || !isMainPointer(event)) { ++ pointerDown(event); ++ return; ++ } ++ const activePoint = toActivePoint(board, event.x, event.y); ++ const elements = getSelectedElements(board); ++ const rectangle = getRectangleByElements(board, elements, false); ++ const activeRectangle = toActiveRectangleFromViewBoxRectangle(board, rectangle); ++ const handleRectangle = getRotateHandleRectangle(activeRectangle); ++ const angle = getSelectionAngle(elements); ++ const rotatedPoint = angle ? rotatePoints(activePoint, RectangleClient.getCenterPoint(activeRectangle), -angle) : activePoint; ++ if (handleRectangle && RectangleClient.isHit(RectangleClient.getRectangleByPoints([rotatedPoint, rotatedPoint]), handleRectangle)) { ++ rotateRef = { ++ elements: [...elements], ++ startPoint: activePoint ++ }; ++ } ++ pointerDown(event); ++ }; ++ board.pointerMove = (event) => { ++ if (rotateRef) { ++ event.preventDefault(); ++ const isShift = !!event.shiftKey; ++ addRotating(board, rotateRef); ++ const endPoint = toActivePoint(board, event.x, event.y); ++ const rectangle = getRectangleByElements(board, rotateRef.elements, false); ++ const activeRectangle = toActiveRectangleFromViewBoxRectangle(board, rectangle); ++ const selectionCenterPoint = RectangleClient.getCenterPoint(activeRectangle); ++ if (!getSelectionAngle(rotateRef.elements) && rotateRef.elements.length > 1) { ++ needCustomActiveRectangle = true; ++ } ++ throttleRAF(board, 'with-common-rotate', () => { ++ if (rotateRef && rotateRef.startPoint) { ++ let angle = getAngleBetweenPoints(rotateRef.startPoint, endPoint, selectionCenterPoint); ++ const selectionAngle = getSelectionAngle(rotateRef.elements); ++ angle = normalizeAngle(selectionAngle + angle); ++ if (isShift) { ++ angle += Math.PI / 12 / 2; ++ angle -= angle % (Math.PI / 12); ++ } ++ let remainder = angle % (Math.PI / 2); ++ if (Math.PI / 2 - remainder <= degreesToRadians(5)) { ++ const snapAngle = Math.PI / 2 - remainder; ++ angle += snapAngle; ++ } ++ if (remainder <= degreesToRadians(5)) { ++ const snapAngle = -remainder; ++ angle += snapAngle; ++ } ++ rotateRef.angle = normalizeAngle(angle - selectionAngle) || 0; ++ rotateElements(board, rotateRef.elements, rotateRef.angle); ++ MERGING.set(board, true); ++ PlaitBoard.getBoardContainer(board).classList.add('element-rotating'); ++ } ++ }); ++ return; ++ } ++ pointerMove(event); ++ }; ++ board.globalPointerUp = (event) => { ++ globalPointerUp(event); ++ if (needCustomActiveRectangle) { ++ needCustomActiveRectangle = false; ++ const selectedElements = getSelectedElements(board); ++ Transforms.addSelectionWithTemporaryElements(board, selectedElements); ++ } ++ PlaitBoard.getBoardContainer(board).classList.remove('element-rotating'); ++ removeRotating(board); ++ rotateRef = null; ++ MERGING.set(board, false); ++ }; ++ board.afterChange = () => { ++ afterChange(); ++ if (rotateHandleG) { ++ rotateHandleG.remove(); ++ rotateHandleG = null; ++ } ++ if (canRotate() && !isSelectionMoving(board)) { ++ if (needCustomActiveRectangle && rotateRef) { ++ const boundingRectangle = getRectangleByElements(board, rotateRef.elements, false); ++ const boundingActiveRectangle = toActiveRectangleFromViewBoxRectangle(board, boundingRectangle); ++ rotateHandleG = drawRotateHandle(board, boundingActiveRectangle); ++ rotateHandleG.classList.add(ROTATE_HANDLE_CLASS_NAME); ++ if (rotateRef.angle) { ++ setAngleForG(rotateHandleG, RectangleClient.getCenterPoint(boundingActiveRectangle), rotateRef.angle); ++ } ++ } ++ else { ++ const elements = getSelectedElements(board); ++ const boundingRectangle = getRectangleByElements(board, elements, false); ++ const boundingActiveRectangle = toActiveRectangleFromViewBoxRectangle(board, boundingRectangle); ++ rotateHandleG = drawRotateHandle(board, boundingActiveRectangle); ++ rotateHandleG.classList.add(ROTATE_HANDLE_CLASS_NAME); ++ setAngleForG(rotateHandleG, RectangleClient.getCenterPoint(boundingActiveRectangle), getSelectionAngle(elements)); ++ } ++ PlaitBoard.getActiveHost(board).append(rotateHandleG); ++ } ++ }; ++ board.drawSelectionRectangle = () => { ++ if (needCustomActiveRectangle && rotateRef) { ++ const rectangle = getRectangleByElements(board, rotateRef.elements, false); ++ const activeRectangle = toActiveRectangleFromViewBoxRectangle(board, rectangle); ++ const rectangleG = drawRectangle(board, RectangleClient.inflate(activeRectangle, ACTIVE_STROKE_WIDTH), { ++ stroke: SELECTION_BORDER_COLOR, ++ strokeWidth: ACTIVE_STROKE_WIDTH ++ }); ++ rectangleG.classList.add(SELECTION_RECTANGLE_CLASS_NAME); ++ if (rotateRef.angle) { ++ setAngleForG(rectangleG, RectangleClient.getCenterPoint(activeRectangle), rotateRef.angle); ++ } ++ return rectangleG; ++ } ++ return drawSelectionRectangle(); ++ }; ++ return board; ++}; ++ ++class TableComponent extends CommonElementFlavour { ++ constructor() { ++ super(); ++ } ++ initializeGenerator() { ++ this.activeGenerator = createActiveGenerator(this.board, { ++ getStrokeWidth: () => { ++ return ACTIVE_STROKE_WIDTH; ++ }, ++ getStrokeOpacity: () => { ++ return 1; ++ }, ++ getRectangle: (value) => { ++ const cells = getSelectedCells(value); ++ if (cells?.length) { ++ return getCellsRectangle(this.board, this.element, cells); ++ } ++ return RectangleClient.getRectangleByPoints(value.points); ++ }, ++ hasResizeHandle: () => { ++ const cells = getSelectedCells(this.element); ++ if (cells?.length) { ++ return false; ++ } ++ return hasResizeHandle(this.board, this.element); ++ } ++ }); ++ this.tableGenerator = new TableGenerator(this.board); ++ this.initializeTextManage(); ++ this.lineAutoCompleteGenerator = new ArrowLineAutoCompleteGenerator(this.board); ++ this.getRef().addGenerator(ArrowLineAutoCompleteGenerator.key, this.lineAutoCompleteGenerator); ++ this.getRef().updateActiveSection = () => { ++ this.activeGenerator.processDrawing(this.element, PlaitBoard.getActiveHost(this.board), { ++ selected: this.selected ++ }); ++ this.lineAutoCompleteGenerator.processDrawing(this.element, PlaitBoard.getActiveHost(this.board), { ++ selected: this.selected ++ }); ++ }; ++ } ++ initialize() { ++ super.initialize(); ++ this.initializeGenerator(); ++ this.draw(); ++ } ++ draw() { ++ this.tableGenerator.processDrawing(this.element, this.getElementG()); ++ this.textGenerator.draw(this.getElementG()); ++ this.rotateVerticalText(); ++ this.lineAutoCompleteGenerator.processDrawing(this.element, PlaitBoard.getActiveHost(this.board), { ++ selected: this.selected ++ }); ++ } ++ rotateVerticalText() { ++ const table = this.board.buildTable(this.element); ++ table.cells.forEach((item) => { ++ if (PlaitTableElement.isVerticalText(item)) { ++ const textManage = getTextManageByCell(this.board, item); ++ if (textManage) { ++ const engine = getEngine(TableSymbols.table); ++ const rectangle = engine.getTextRectangle(this.board, this.element, { id: item.id, board: this.board }); ++ textManage.g.classList.add('vertical-cell-text'); ++ setAngleForG(textManage.g, RectangleClient.getCenterPoint(rectangle), degreesToRadians(-90)); ++ } ++ } ++ }); ++ } ++ getDrawShapeTexts(cells) { ++ return cells ++ .filter((item) => isCellIncludeText(item)) ++ .map((item) => { ++ return { ++ id: item.id, ++ text: item.text, ++ board: this.board ++ }; ++ }); ++ } ++ initializeTextManage() { ++ const texts = this.getDrawShapeTexts(this.element.cells); ++ this.textGenerator = new TextGenerator(this.board, this.element, texts, { ++ onChange: (value, data, text) => { ++ const path = PlaitBoard.findPath(this.board, value); ++ if (data.newText) { ++ DrawTransforms.setTableText(this.board, path, text.id, data.newText, data.height); ++ } ++ data.operations && memorizeLatestText(value, data.operations); ++ }, ++ getRenderRectangle: (value, text) => { ++ const cell = getCellWithPoints(this.board, value, text.id); ++ if (PlaitTableElement.isVerticalText(cell)) { ++ const cellRectangle = RectangleClient.getRectangleByPoints(cell.points); ++ const strokeWidth = getStrokeWidthByElement(cell); ++ const width = cellRectangle.height - ShapeDefaultSpace.rectangleAndText * 2 - strokeWidth * 2; ++ const height = getCellTextHeight(this.board, cell, true); ++ return { ++ width: height, ++ height: width > 0 ? width : 0, ++ x: cellRectangle.x + ShapeDefaultSpace.rectangleAndText + strokeWidth, ++ y: cellRectangle.y + (cellRectangle.height - width) / 2 ++ }; ++ } ++ else { ++ return getHorizontalTextRectangle(this.board, cell); ++ } ++ } ++ }); ++ this.textGenerator.initialize(); ++ } ++ onContextChanged(value, previous) { ++ if (value.element !== previous.element || value.hasThemeChanged) { ++ const previousSelectedCells = getSelectedCells(previous.element); ++ if (previousSelectedCells?.length) { ++ clearSelectedCells(previous.element); ++ setSelectedCells(value.element, previousSelectedCells); ++ } ++ this.tableGenerator.processDrawing(value.element, this.getElementG()); ++ this.activeGenerator.processDrawing(value.element, PlaitBoard.getActiveHost(this.board), { selected: this.selected }); ++ const previousTexts = this.getDrawShapeTexts(previous.element.cells); ++ const currentTexts = this.getDrawShapeTexts(value.element.cells); ++ this.textGenerator.update(value.element, previousTexts, currentTexts, this.getElementG()); ++ this.rotateVerticalText(); ++ } ++ else { ++ const hasSameSelected = value.selected === previous.selected; ++ const currentSelectedCells = getSelectedCells(value.element); ++ if (!hasSameSelected || currentSelectedCells?.length || value.selected) { ++ this.activeGenerator.processDrawing(value.element, PlaitBoard.getActiveHost(this.board), { ++ selected: this.selected ++ }); ++ } ++ if (!this.selected) { ++ clearSelectedCells(value.element); ++ } ++ } ++ this.lineAutoCompleteGenerator.processDrawing(this.element, PlaitBoard.getActiveHost(this.board), { ++ selected: this.selected ++ }); ++ } ++ destroy() { ++ super.destroy(); ++ this.activeGenerator.destroy(); ++ this.tableGenerator.destroy(); ++ this.textGenerator.destroy(); ++ this.lineAutoCompleteGenerator.destroy(); ++ } ++} ++ ++const MIN_CELL_SIZE = 20; ++function withTableResize(board) { ++ let snapG; ++ const options = { ++ key: 'draw-table', ++ canResize: () => { ++ const selectedElements = getSelectedElements(board); ++ return isSingleSelectTable(board) && !hasValidAngle(selectedElements[0]); ++ }, ++ hitTest: (point) => { ++ const selectedElements = getSelectedElements(board); ++ const hitElement = selectedElements[0]; ++ // debugGenerator.clear(); ++ if (hitElement && PlaitDrawElement.isElementByTable(hitElement)) { ++ let rectangle = board.getRectangle(hitElement); ++ // debugGenerator.drawRectangle(board, rectangle); ++ // debugGenerator.drawCircles(board, [point], 5); ++ let handleRef = getHitRectangleResizeHandleRef(board, rectangle, point, hitElement.angle); ++ if (handleRef) { ++ const selectElement = isSelectedElement(board, hitElement); ++ if ((selectElement && isSingleSelectTable(board)) || (!selectElement && !isCornerHandle(board, handleRef.handle))) { ++ return { ++ element: hitElement, ++ handle: handleRef.handle, ++ cursorClass: handleRef.cursorClass, ++ rectangle ++ }; ++ } ++ } ++ const cells = getCellsWithPoints(board, hitElement); ++ for (let i = 0; i < cells.length; i++) { ++ rectangle = RectangleClient.getRectangleByPoints(cells[i].points); ++ handleRef = getHitRectangleResizeHandleRef(board, rectangle, point, 0); ++ if (handleRef && !isCornerHandle(board, handleRef.handle)) { ++ return { ++ element: hitElement, ++ handle: handleRef.handle, ++ cursorClass: handleRef.cursorClass, ++ rectangle, ++ options: { ++ cell: cells[i] ++ } ++ }; ++ } ++ } ++ } ++ return null; ++ }, ++ onResize: (resizeRef, resizeState) => { ++ snapG?.remove(); ++ const path = PlaitBoard.findPath(board, resizeRef.element); ++ if (resizeRef.options?.cell && resizeRef.rectangle) { ++ const handleIndex = getIndexByResizeHandle(resizeRef.handle); ++ const { originPoint, handlePoint } = getResizeOriginPointAndHandlePoint(board, handleIndex, resizeRef.rectangle); ++ const resizePoints = [resizeState.startPoint, resizeState.endPoint]; ++ const { xZoom, yZoom } = getResizeZoom(resizePoints, originPoint, handlePoint, false, false); ++ const originPoints = resizeRef.options?.cell.points; ++ const targetPoints = originPoints.map((p) => { ++ return movePointByZoomAndOriginPoint(p, originPoint, xZoom, yZoom); ++ }); ++ const offsetX = targetPoints[1][0] - originPoints[1][0]; ++ const offsetY = targetPoints[1][1] - originPoints[1][1]; ++ const width = targetPoints[1][0] - targetPoints[0][0]; ++ const height = targetPoints[1][1] - targetPoints[0][1]; ++ if (offsetX !== 0 && width >= MIN_CELL_SIZE) { ++ const { columns, points } = updateColumns(resizeRef.element, resizeRef.options?.cell.columnId, width, offsetX); ++ Transforms.setNode(board, { columns, points }, path); ++ } ++ else if (offsetY !== 0 && height >= MIN_CELL_SIZE) { ++ const { rows, points } = updateRows(resizeRef.element, resizeRef.options?.cell.rowId, height, offsetY); ++ Transforms.setNode(board, { rows, points }, path); ++ } ++ } ++ else { ++ const isFromCorner = isCornerHandle(board, resizeRef.handle); ++ const isAspectRatio = resizeState.isShift; ++ const handleIndex = getIndexByResizeHandle(resizeRef.handle); ++ const { originPoint, handlePoint } = getResizeOriginPointAndHandlePoint(board, handleIndex, resizeRef.rectangle); ++ const resizeSnapRefOptions = getSnapResizingRefOptions(board, resizeRef, resizeState, { ++ originPoint, ++ handlePoint ++ }, isAspectRatio, isFromCorner); ++ const resizeSnapRef = getSnapResizingRef(board, [resizeRef.element], resizeSnapRefOptions); ++ snapG = resizeSnapRef.snapG; ++ PlaitBoard.getElementTopHost(board).append(snapG); ++ const points = resizeSnapRef.activePoints; ++ const originPoints = resizeRef.element.points; ++ const originRect = RectangleClient.getRectangleByPoints(originPoints); ++ const targetRect = RectangleClient.getRectangleByPoints(points); ++ const offsetWidth = targetRect.width - originRect.width; ++ const offsetHeight = targetRect.height - originRect.height; ++ let columns = [...resizeRef.element.columns]; ++ let rows = [...resizeRef.element.rows]; ++ if (offsetWidth !== 0) { ++ columns = columns.map((item) => { ++ if (item.width) { ++ return { ++ ...item, ++ width: item.width + offsetWidth * (item.width / originRect.width) ++ }; ++ } ++ return item; ++ }); ++ } ++ if (offsetHeight !== 0) { ++ rows = rows.map((item) => { ++ if (item.height) { ++ return { ++ ...item, ++ height: item.height + offsetHeight * (item.height / originRect.height) ++ }; ++ } ++ return item; ++ }); ++ } ++ Transforms.setNode(board, { points: normalizeShapePoints(points), columns, rows }, path); ++ } ++ }, ++ afterResize: (resizeRef) => { ++ snapG?.remove(); ++ snapG = null; ++ } ++ }; ++ withResize(board, options); ++ return board; ++} ++ ++const withTable = (board) => { ++ const tableBoard = board; ++ const { drawElement, getRectangle, isRectangleHit, isHit, isMovable, dblClick, keyDown, pointerUp } = tableBoard; ++ tableBoard.drawElement = (context) => { ++ if (PlaitDrawElement.isElementByTable(context.element)) { ++ return TableComponent; ++ } ++ return drawElement(context); ++ }; ++ tableBoard.isHit = (element, point, isStrict) => { ++ if (PlaitDrawElement.isElementByTable(element)) { ++ const client = RectangleClient.getRectangleByPoints(element.points); ++ const nearestPoint = TableEngine.getNearestPoint(client, point); ++ const distance = distanceBetweenPointAndPoint(nearestPoint[0], nearestPoint[1], point[0], point[1]); ++ return distance <= HIT_DISTANCE_BUFFER || RectangleClient.isPointInRectangle(client, point); ++ } ++ return isHit(element, point, isStrict); ++ }; ++ tableBoard.getRectangle = (element) => { ++ if (PlaitDrawElement.isElementByTable(element)) { ++ return RectangleClient.getRectangleByPoints(element.points); ++ } ++ return getRectangle(element); ++ }; ++ tableBoard.isMovable = (element) => { ++ if (PlaitDrawElement.isElementByTable(element)) { ++ return true; ++ } ++ return isMovable(element); ++ }; ++ tableBoard.isRectangleHit = (element, selection) => { ++ if (PlaitDrawElement.isElementByTable(element)) { ++ const rangeRectangle = RectangleClient.getRectangleByPoints([selection.anchor, selection.focus]); ++ const client = RectangleClient.getRectangleByPoints(element.points); ++ return isLineHitRectangle(RectangleClient.getCornerPoints(client), rangeRectangle); ++ } ++ return isRectangleHit(element, selection); ++ }; ++ tableBoard.keyDown = (event) => { ++ const selectedElements = getSelectedElements(board); ++ const isSingleSelection = selectedElements.length === 1; ++ const targetElement = selectedElements[0]; ++ if (!PlaitBoard.isReadonly(board) && ++ !PlaitBoard.hasBeenTextEditing(tableBoard) && ++ !isVirtualKey(event) && ++ !isDelete(event) && ++ !isSpaceHotkey(event) && ++ isSingleSelection) { ++ event.preventDefault(); ++ if (PlaitDrawElement.isElementByTable(targetElement)) { ++ const cells = getSelectedCells(targetElement); ++ let cell = targetElement.cells.find((item) => item.text); ++ if (cells?.length) { ++ cell = cells.find((item) => item.text); ++ } ++ if (cell) { ++ editCell(board, cell); ++ return; ++ } ++ } ++ } ++ keyDown(event); ++ }; ++ tableBoard.dblClick = (event) => { ++ event.preventDefault(); ++ if (!PlaitBoard.isReadonly(board)) { ++ const point = toViewBoxPoint(board, toHostPoint(board, event.x, event.y)); ++ const hitElement = getHitElementByPoint(board, point); ++ if (hitElement && PlaitDrawElement.isElementByTable(hitElement)) { ++ const hitCell = getHitCell(tableBoard, hitElement, point); ++ if (hitCell && hitCell.text) { ++ editCell(board, hitCell); ++ return; ++ } ++ } ++ } ++ dblClick(event); ++ }; ++ tableBoard.pointerUp = (event) => { ++ const isSetSelectionPointer = PlaitBoard.isPointer(tableBoard, PlaitPointerType.selection) || PlaitBoard.isPointer(tableBoard, PlaitPointerType.hand); ++ const isSkip = !isMainPointer(event) || isDragging(tableBoard) || !isSetSelectionPointer; ++ if (isSkip) { ++ pointerUp(event); ++ return; ++ } ++ if (isSingleSelectTable(tableBoard)) { ++ const point = toViewBoxPoint(tableBoard, toHostPoint(tableBoard, event.x, event.y)); ++ const element = getSelectedTableElements(tableBoard)[0]; ++ const hitCell = getHitCell(tableBoard, element, point); ++ if (hitCell && hitCell.text) { ++ setSelectedCells(element, [hitCell]); ++ } ++ } ++ pointerUp(event); ++ }; ++ tableBoard.buildTable = (element) => { ++ return element; ++ }; ++ return withTableResize(tableBoard); ++}; ++ ++const isSwimlaneDndMode = (board) => { ++ const isSwimlanePointer = isSwimlanePointers(board); ++ const dndMode = isSwimlanePointer && isDndMode(board); ++ return dndMode; ++}; ++const isSwimlaneDrawingMode = (board) => { ++ const isSwimlanePointer = isSwimlanePointers(board); ++ const drawingMode = isSwimlanePointer && isDrawingMode(board); ++ return drawingMode; ++}; ++const withSwimlaneCreateByDrag = (board) => { ++ const { pointerMove, globalPointerUp, pointerUp } = board; ++ let swimlaneG = null; ++ let temporaryElement = null; ++ board.pointerMove = (event) => { ++ swimlaneG?.remove(); ++ swimlaneG = createG(); ++ const tableGenerator = new TableGenerator(board); ++ const pointer = PlaitBoard.getPointer(board); ++ const dragMode = isSwimlaneDndMode(board); ++ const movingPoint = toViewBoxPoint(board, toHostPoint(board, event.x, event.y)); ++ if (dragMode) { ++ const points = getDefaultSwimlanePoints(pointer, movingPoint); ++ temporaryElement = createDefaultSwimlane(pointer, points); ++ tableGenerator.processDrawing(temporaryElement, swimlaneG); ++ PlaitBoard.getElementTopHost(board).append(swimlaneG); ++ } ++ pointerMove(event); ++ }; ++ board.pointerUp = (event) => { ++ if (isSwimlaneDndMode(board) && temporaryElement) { ++ return; ++ } ++ pointerUp(event); ++ }; ++ board.globalPointerUp = (event) => { ++ if (isSwimlaneDndMode(board) && temporaryElement) { ++ insertElement(board, temporaryElement); ++ } ++ temporaryElement = null; ++ swimlaneG?.remove(); ++ swimlaneG = null; ++ globalPointerUp(event); ++ }; ++ return board; ++}; ++const withSwimlaneCreateByDrawing = (board) => { ++ const { pointerDown, pointerMove, pointerUp, keyDown, keyUp } = board; ++ let start = null; ++ let swimlaneG = null; ++ let temporaryElement = null; ++ let isShift = false; ++ let snapG; ++ board.keyDown = (event) => { ++ isShift = isKeyHotkey('shift', event); ++ keyDown(event); ++ }; ++ board.keyUp = (event) => { ++ isShift = false; ++ keyUp(event); ++ }; ++ board.pointerDown = (event) => { ++ if (!PlaitBoard.isReadonly(board) && isSwimlaneDrawingMode(board)) { ++ const point = toViewBoxPoint(board, toHostPoint(board, event.x, event.y)); ++ start = point; ++ } ++ pointerDown(event); ++ }; ++ board.pointerMove = (event) => { ++ swimlaneG?.remove(); ++ swimlaneG = createG(); ++ const tableGenerator = new TableGenerator(board); ++ const movingPoint = toViewBoxPoint(board, toHostPoint(board, event.x, event.y)); ++ const pointer = PlaitBoard.getPointer(board); ++ snapG?.remove(); ++ if (start && isSwimlaneDrawingMode(board)) { ++ let points = normalizeShapePoints([start, movingPoint], isShift); ++ const activeRectangle = RectangleClient.getRectangleByPoints(points); ++ const [x, y] = getUnitVectorByPointAndPoint(start, movingPoint); ++ const resizeSnapRef = getSnapResizingRef(board, [], { ++ resizePoints: points, ++ activeRectangle, ++ directionFactors: [getDirectionFactorByDirectionComponent(x), getDirectionFactorByDirectionComponent(y)], ++ isAspectRatio: isShift, ++ isFromCorner: true, ++ isCreate: true ++ }); ++ snapG = resizeSnapRef.snapG; ++ PlaitBoard.getElementTopHost(board).append(snapG); ++ points = normalizeShapePoints(resizeSnapRef.activePoints, isShift); ++ temporaryElement = createDefaultSwimlane(pointer, points); ++ tableGenerator.processDrawing(temporaryElement, swimlaneG); ++ PlaitBoard.getElementTopHost(board).append(swimlaneG); ++ } ++ pointerMove(event); ++ }; ++ board.pointerUp = (event) => { ++ if (isSwimlaneDrawingMode(board) && start) { ++ const targetPoint = toViewBoxPoint(board, toHostPoint(board, event.x, event.y)); ++ const { width, height } = RectangleClient.getRectangleByPoints([start, targetPoint]); ++ if (Math.hypot(width, height) < 8) { ++ const pointer = PlaitBoard.getPointer(board); ++ const points = getDefaultSwimlanePoints(pointer, targetPoint); ++ temporaryElement = createDefaultSwimlane(pointer, points); ++ } ++ if (temporaryElement) { ++ insertElement(board, temporaryElement); ++ } ++ snapG?.remove(); ++ swimlaneG?.remove(); ++ swimlaneG = null; ++ start = null; ++ temporaryElement = null; ++ return; ++ } ++ pointerUp(event); ++ }; ++ return board; ++}; ++ ++const withSwimlane = (board) => { ++ const { drawElement, buildTable, pointerUp } = board; ++ board.drawElement = (context) => { ++ if (PlaitDrawElement.isSwimlane(context.element)) { ++ return TableComponent; ++ } ++ return drawElement(context); ++ }; ++ board.buildTable = (element) => { ++ if (PlaitDrawElement.isSwimlane(element)) { ++ return buildSwimlaneTable(element); ++ } ++ return buildTable(element); ++ }; ++ return withSwimlaneCreateByDrawing(withSwimlaneCreateByDrag(board)); ++}; ++ ++const withVectorLineCreateByDraw = (board) => { ++ const { pointerDown, pointerMove, dblClick, globalKeyDown } = board; ++ let lineShapeG = null; ++ let temporaryElement = null; ++ let drawPoints = []; ++ const vectorLineComplete = () => { ++ if (temporaryElement) { ++ Transforms.insertNode(board, temporaryElement, [board.children.length]); ++ } ++ PlaitBoard.getBoardContainer(board).classList.remove(`vector-line-closed`); ++ lineShapeG?.remove(); ++ lineShapeG = null; ++ temporaryElement = null; ++ drawPoints = []; ++ }; ++ board.pointerDown = (event) => { ++ const penPointers = getVectorLinePointers(); ++ const isVectorLinePointer = PlaitBoard.isInPointer(board, penPointers); ++ if (!PlaitBoard.isReadonly(board) && isVectorLinePointer && isDrawingMode(board)) { ++ let point = toViewBoxPoint(board, toHostPoint(board, event.x, event.y)); ++ if (drawPoints.length > 1) { ++ const isClosed = distanceBetweenPointAndPoint(...point, ...drawPoints[0]) <= LINE_HIT_GEOMETRY_BUFFER; ++ if (isClosed) { ++ drawPoints.push(drawPoints[0]); ++ vectorLineComplete(); ++ return; ++ } ++ } ++ drawPoints.push(point); ++ return; ++ } ++ pointerDown(event); ++ }; ++ board.pointerMove = (event) => { ++ lineShapeG?.remove(); ++ lineShapeG = createG(); ++ let movingPoint = toViewBoxPoint(board, toHostPoint(board, event.x, event.y)); ++ const pointer = PlaitBoard.getPointer(board); ++ if (pointer === VectorLinePointerType.vectorLine) { ++ if (drawPoints.length > 0) { ++ const distance = distanceBetweenPointAndPoint(...movingPoint, ...drawPoints[0]); ++ if (distance <= LINE_HIT_GEOMETRY_BUFFER) { ++ movingPoint = drawPoints[0]; ++ PlaitBoard.getBoardContainer(board).classList.add(`vector-line-closed`); ++ } ++ else { ++ PlaitBoard.getBoardContainer(board).classList.remove(`vector-line-closed`); ++ } ++ temporaryElement = vectorLineCreating(board, VectorLineShape.straight, drawPoints, movingPoint, lineShapeG); ++ } ++ } ++ pointerMove(event); ++ }; ++ board.dblClick = (event) => { ++ if (!PlaitBoard.isReadonly(board)) { ++ if (temporaryElement) { ++ vectorLineComplete(); ++ BoardTransforms.updatePointerType(board, PlaitPointerType.selection); ++ return; ++ } ++ } ++ dblClick(event); ++ }; ++ board.globalKeyDown = (event) => { ++ if (!PlaitBoard.isReadonly(board)) { ++ const isEsc = isKeyHotkey('esc', event); ++ const isV = isKeyHotkey('v', event); ++ if ((isEsc || isV) && temporaryElement) { ++ vectorLineComplete(); ++ if (isV) { ++ BoardTransforms.updatePointerType(board, PlaitPointerType.selection); ++ } ++ } ++ } ++ globalKeyDown(event); ++ }; ++ return board; ++}; ++ ++const withVectorLineResize = (board) => { ++ const options = { ++ key: 'draw-vector-line', ++ canResize: () => { ++ return true; ++ }, ++ hitTest: (point) => { ++ const selectedVectorLineElements = getSelectedVectorLineElements(board); ++ if (selectedVectorLineElements.length > 0) { ++ let result = null; ++ selectedVectorLineElements.forEach(value => { ++ const handleRef = getHitLineResizeHandleRef(board, value, point); ++ if (handleRef) { ++ result = { ++ element: value, ++ handle: handleRef.handle, ++ handleIndex: handleRef.handleIndex ++ }; ++ } ++ }); ++ return result; ++ } ++ return null; ++ }, ++ onResize: (resizeRef, resizeState) => { ++ let points = [...resizeRef.element.points]; ++ let handleIndex = resizeRef.handleIndex; ++ if (resizeRef.handle === LineResizeHandle.source || resizeRef.handle === LineResizeHandle.target) { ++ points[handleIndex] = resizeState.endPoint; ++ if (isClosedPoints(resizeRef.element.points)) { ++ points[points.length - 1] = resizeState.endPoint; ++ } ++ else { ++ const targetPoint = resizeRef.handle === LineResizeHandle.source ? points[points.length - 1] : points[0]; ++ const distance = distanceBetweenPointAndPoint(...resizeState.endPoint, ...targetPoint); ++ if (distance <= LINE_HIT_GEOMETRY_BUFFER) { ++ points[handleIndex] = targetPoint; ++ } ++ } ++ } ++ else { ++ if (resizeRef.handle === LineResizeHandle.addHandle) { ++ points.splice(handleIndex + 1, 0, resizeState.endPoint); ++ } ++ else { ++ points[handleIndex] = resizeState.endPoint; ++ } ++ } ++ Transforms.setNode(board, { points }, resizeRef.path); ++ } ++ }; ++ withResize(board, options); ++ return board; ++}; ++ ++const withDraw = (board) => { ++ const { drawElement, getRectangle, isRectangleHit, isHit, isInsidePoint, isMovable, isAlign, getRelatedFragment, getOneHitElement } = board; ++ board.drawElement = (context) => { ++ if (PlaitDrawElement.isGeometry(context.element)) { ++ if (PlaitDrawElement.isUML(context.element)) { ++ return GeometryComponent; ++ } ++ return GeometryComponent; ++ } ++ else if (PlaitDrawElement.isArrowLine(context.element)) { ++ return ArrowLineComponent; ++ } ++ else if (PlaitDrawElement.isVectorLine(context.element)) { ++ return VectorLineComponent; ++ } ++ else if (PlaitDrawElement.isImage(context.element)) { ++ return ImageComponent; ++ } ++ return drawElement(context); ++ }; ++ board.getRectangle = (element) => { ++ if (PlaitDrawElement.isGeometry(element)) { ++ return RectangleClient.getRectangleByPoints(element.points); ++ } ++ if (PlaitDrawElement.isArrowLine(element)) { ++ const points = getArrowLinePoints(board, element); ++ const lineTextRectangles = element.texts.map((text, index) => { ++ const rectangle = getArrowLineTextRectangle(board, element, index); ++ return rectangle; ++ }); ++ const linePointsRectangle = RectangleClient.getRectangleByPoints(points); ++ return RectangleClient.getBoundingRectangle([linePointsRectangle, ...lineTextRectangles]); ++ } ++ if (PlaitDrawElement.isVectorLine(element)) { ++ const points = getVectorLinePoints(board, element); ++ const linePointsRectangle = RectangleClient.getRectangleByPoints(points); ++ return RectangleClient.getBoundingRectangle([linePointsRectangle]); ++ } ++ if (PlaitDrawElement.isImage(element)) { ++ return RectangleClient.getRectangleByPoints(element.points); ++ } ++ return getRectangle(element); ++ }; ++ board.isRectangleHit = (element, selection) => { ++ const result = isRectangleHitDrawElement(board, element, selection); ++ if (result !== null) { ++ return result; ++ } ++ return isRectangleHit(element, selection); ++ }; ++ board.isHit = (element, point, isStrict) => { ++ const result = isHitDrawElement(board, element, point, isStrict); ++ if (result !== null) { ++ return result; ++ } ++ return isHit(element, point, isStrict); ++ }; ++ board.getOneHitElement = (elements) => { ++ const isAllDrawElements = elements.every((item) => PlaitDrawElement.isDrawElement(item)); ++ if (isAllDrawElements) { ++ return getHitDrawElement(board, elements); ++ } ++ return getOneHitElement(elements); ++ }; ++ board.isInsidePoint = (element, point) => { ++ const result = isHitElementInside(board, element, point); ++ if (result !== null) { ++ return result; ++ } ++ return isInsidePoint(element, point); ++ }; ++ board.isMovable = (element) => { ++ if (PlaitDrawElement.isGeometry(element)) { ++ return true; ++ } ++ if (PlaitDrawElement.isImage(element)) { ++ return true; ++ } ++ if (PlaitDrawElement.isVectorLine(element)) { ++ return true; ++ } ++ if (PlaitDrawElement.isArrowLine(element)) { ++ const selectedElements = getSelectedElements(board); ++ const isSelected = (boundId) => { ++ return !!selectedElements.find((value) => value.id === boundId); ++ }; ++ if (!element.source.boundId && !element.target.boundId) { ++ return true; ++ } ++ if (element.source.boundId && isSelected(element.source.boundId) && selectedElements.includes(element)) { ++ return true; ++ } ++ if (element.target.boundId && isSelected(element.target.boundId) && selectedElements.includes(element)) { ++ return true; ++ } ++ return false; ++ } ++ return isMovable(element); ++ }; ++ board.isAlign = (element) => { ++ if (PlaitDrawElement.isGeometry(element) || PlaitDrawElement.isImage(element)) { ++ return true; ++ } ++ return isAlign(element); ++ }; ++ board.getRelatedFragment = (elements, originData) => { ++ const selectedElements = originData?.length ? originData : getSelectedElements(board); ++ const lineElements = board.children.filter((element) => PlaitDrawElement.isArrowLine(element)); ++ const activeLines = lineElements.filter((line) => { ++ const source = selectedElements.find((element) => element.id === line.source.boundId); ++ const target = selectedElements.find((element) => element.id === line.target.boundId); ++ const isSelected = selectedElements.includes(line); ++ return source && target && !isSelected; ++ }); ++ return getRelatedFragment([...elements, ...activeLines], originData); ++ }; ++ return withSwimlane(withTable(withDrawResize(withVectorLineCreateByDraw(withArrowLineAutoCompleteReaction(withArrowLineBoundReaction(withVectorLineResize(withArrowLineResize(withArrowLineTextMove(withArrowLineText(withDrawRotate(withArrowLineCreateByDraw(withArrowLineAutoComplete(withGeometryCreateByDrag(withGeometryCreateByDrawing(withDrawFragment(withDrawHotkey(board))))))))))))))))); ++}; ++ ++/** ++ * Generated bundle index. Do not edit. ++ */ ++ ++export { ArrowLineAutoCompleteGenerator, ArrowLineComponent, ArrowLineHandleKey, ArrowLineMarkerType, ArrowLineShape, BOARD_TO_PRE_COMMIT, BasicShapes, DEFAULT_IMAGE_WIDTH, DefaultActivationProperty, DefaultActorProperty, DefaultArrowProperty, DefaultAssemblyProperty, DefaultBasicShapeProperty, DefaultBasicShapePropertyMap, DefaultClassProperty, DefaultCloudProperty, DefaultCombinedFragmentProperty, DefaultComponentBoxProperty, DefaultConnectorProperty, DefaultContainerProperty, DefaultDataBaseProperty, DefaultDataProperty, DefaultDecisionProperty, DefaultDeletionProperty, DefaultDocumentProperty, DefaultDrawActiveStyle, DefaultDrawStyle, DefaultFlowchartProperty, DefaultFlowchartPropertyMap, DefaultInterfaceProperty, DefaultInternalStorageProperty, DefaultLineStyle, DefaultManualInputProperty, DefaultMergeProperty, DefaultMultiDocumentProperty, DefaultNoteProperty, DefaultObjectProperty, DefaultPackageProperty, DefaultPentagonArrowProperty, DefaultPortProperty, DefaultProvidedInterfaceProperty, DefaultRequiredInterfaceProperty, DefaultSwimlaneHorizontalProperty, DefaultSwimlaneHorizontalWithHeaderProperty, DefaultSwimlanePropertyMap, DefaultSwimlaneVerticalProperty, DefaultSwimlaneVerticalWithHeaderProperty, DefaultTextProperty, DefaultTwoWayArrowProperty, DefaultUMLPropertyMap, DrawI18nKey, DrawThemeColors, DrawTransforms, FlowchartSymbols, GEOMETRY_NOT_CLOSED, GEOMETRY_WITHOUT_TEXT, GEOMETRY_WITH_MULTIPLE_TEXT, GeometryCommonTextKeys, GeometryComponent, GeometryShapeGenerator, GeometryThreshold, KEY_TO_TEXT_MANAGE, LINE_ALIGN_TOLERANCE, LINE_AUTO_COMPLETE_DIAMETER, LINE_AUTO_COMPLETE_HOVERED_DIAMETER, LINE_AUTO_COMPLETE_HOVERED_OPACITY, LINE_AUTO_COMPLETE_OPACITY, LINE_HIT_GEOMETRY_BUFFER, LINE_SNAPPING_BUFFER, LINE_SNAPPING_CONNECTOR_BUFFER, LINE_TEXT, LINE_TEXT_SPACE, LineActiveGenerator, MIN_TEXT_WIDTH, MemorizeKey, MultipleTextGeometryTextKeys, PlaitArrowLine, PlaitDrawElement, PlaitGeometry, PlaitTableElement, Q2C, SELECTED_CELLS, SWIMLANE_HEADER_SIZE, ShapeDefaultSpace, SingleTextGenerator, SwimlaneDrawSymbols, SwimlaneSymbols, TableGenerator, TableSymbols, TextGenerator, UMLSymbols, VectorLineComponent, VectorLinePointerType, VectorLineShape, WithArrowLineAutoCompletePluginKey, WithDrawPluginKey, adjustSwimlaneShape, alignElbowSegment, alignPoint, alignPoints, buildClipboardData, buildDefaultTextsByShape, buildSwimlaneTable, clearSelectedCells, collectArrowLineUpdatedRefsByGeometry, createArrowLineElement, createCell, createDefaultCells, createDefaultGeometry, createDefaultRowsOrColumns, createDefaultSwimlane, createGeometryElement, createGeometryElementWithText, createGeometryElementWithoutText, createMultipleTextGeometryElement, createTextElement, createUMLClassOrInterfaceGeometryElement, createVectorLineElement, debugGenerator$2 as debugGenerator, deleteTextManage, drawArrowLine, drawArrowLineArrow, drawBoundReaction, drawGeometry, drawShape, drawVectorLine, editCell, editText, getArrowLineHandleRefPair, getArrowLinePointers, getArrowLinePoints, getArrowLineTextRectangle, getArrowLines, getAutoCompletePoints, getBasicPointers, getCellWithPoints, getCellsRectangle, getCellsWithPoints, getCenterPointsOnPolygon, getConnectionPoint, getCurvePoints, getCustomTextRectangle, getDefaultBasicShapeProperty, getDefaultFlowchartProperty, getDefaultGeometryPoints, getDefaultGeometryProperty, getDefaultGeometryText, getDefaultSwimlanePoints, getDefaultTextPoints, getDefaultUMLProperty, getDrawDefaultStrokeColor, getElbowLineRouteOptions, getElbowPoints, getFillByElement, getFirstFilledDrawElement, getFlowchartDefaultFill, getFlowchartPointers, getGeometryAlign, getGeometryPointers, getHitCell, getHitConnection, getHitConnectionFromConnectionPoint, getHitConnectorPoint, getHitDrawElement, getHitIndexOfAutoCompletePoint, getHitMultipleGeometryText, getHitShape, getIndexAndDeleteCountByKeyPoint, getLineMemorizedLatest, getMemorizeKey, getMemorizedLatestByPointer, getMemorizedLatestShape, getMidKeyPoints, getMiddlePoints, getMirrorDataPoints, getMultipleTextGeometryTextKeys, getNearestPoint, getNextRenderPoints, getNextSourceAndTargetPoints, getResizedPreviousAndNextPoint, getSelectedArrowLineElements, getSelectedCells, getSelectedCustomGeometryElements, getSelectedDrawElements, getSelectedGeometryElements, getSelectedImageElements, getSelectedSwimlane, getSelectedTableCellsEditor, getSelectedTableElements, getSelectedVectorLineElements, getSnapResizingRef, getSnapResizingRefOptions, getSnappingRef, getSnappingShape, getSolidElements, getSourceAndTargetRectangle, getStrokeColorByElement, getStrokeStyleByElement, getStrokeWidthByElement, getSwimlaneCount, getSwimlanePointers, getSwimlaneShapes, getTextKey, getTextManage, getTextManageByCell, getTextRectangle$1 as getTextRectangle, getTextShapeProperty, getUMLPointers, getVectorByConnection, getVectorLinePointers, getVectorLinePoints, handleArrowLineCreating, hasIllegalElbowPoint, insertClipboardData, insertElement, isCellIncludeText, isClosedCustomGeometry, isClosedDrawElement, isClosedPoints, isDrawElementIncludeText, isDrawElementsIncludeText, isEmptyTextElement, isFilledDrawElement, isGeometryClosed, isGeometryIncludeText, isHitArrowLine, isHitArrowLineText, isHitDrawElement, isHitEdgeOfShape, isHitElementInside, isHitElementText, isHitPolyLine, isHitVectorLine, isInsideOfShape, isMultipleTextGeometry, isMultipleTextShape, isRectangleHitDrawElement, isRectangleHitElementText, isRectangleHitRotatedElement, isRectangleHitRotatedPoints, isSelfLoop, isSingleSelectLine, isSingleSelectSwimlane, isSingleSelectTable, isSingleTextGeometry, isSingleTextShape, isSwimlanePointers, isSwimlaneShape, isSwimlaneWithHeader, isUpdatedHandleIndex, isUseDefaultOrthogonalRoute, memorizeLatestShape, memorizeLatestText, setSelectedCells, setTextManage, traverseDrawShapes, updateCellIds, updateCellIdsByRowOrColumn, updateColumns, updateRowOrColumnIds, updateRows, vectorLineCreating, withArrowLineAutoComplete, withDraw }; ++//# sourceMappingURL=plait-draw.mjs.map diff --git a/patches/@plait+draw+0.92.3.patch b/patches/@plait+draw+0.92.3.patch deleted file mode 100644 index 77780e2..0000000 --- a/patches/@plait+draw+0.92.3.patch +++ /dev/null @@ -1,400 +0,0 @@ ---- plait-draw.mjs.backup 2026-02-21 17:17:17 -+++ plait-draw.mjs 2026-02-21 17:26:05 -@@ -1783,7 +1783,7 @@ - .map((command) => `A ${command.rx} ${command.ry} ${command.xAxisRotation} ${command.largeArcFlag} ${command.sweepFlag} ${command.endX} ${command.endY}`) - .join('\n') + - ' Z'; -- const svgElement = rs.path(pathData, { ...options, fillStyle: 'solid' }); -+ const svgElement = rs.path(pathData, options); - setPathStrokeLinecap(svgElement, 'round'); - return svgElement; - }, -@@ -2185,8 +2185,7 @@ - const maskG = drawShape(board, activeRectangle, shape, { - stroke: SELECTION_BORDER_COLOR, - strokeWidth: 0, -- fill: isClosedDrawElement(element) ? SELECTION_FILL_COLOR : DefaultDrawStyle.fill, -- fillStyle: 'solid' -+ fill: isClosedDrawElement(element) ? SELECTION_FILL_COLOR : DefaultDrawStyle.fill - }, drawOptions); - g.appendChild(maskG); - } -@@ -2196,8 +2195,7 @@ - const circleG = drawCircle(PlaitBoard.getRoughSVG(board), point, 8, { - stroke: SELECTION_BORDER_COLOR, - strokeWidth: ACTIVE_STROKE_WIDTH, -- fill: '#FFF', -- fillStyle: 'solid' -+ fill: '#FFF' - }); - g.appendChild(circleG); - }); -@@ -2270,7 +2268,8 @@ - stroke: strokeColor, - strokeWidth, - fill, -- strokeLineDash -+ strokeLineDash, -+ fillStyle: element.fillStyle - }); - } - } -@@ -2480,8 +2479,7 @@ - middlePoints.forEach((point, index) => { - const circle = drawCircle(PlaitBoard.getRoughSVG(this.board), point, LINE_AUTO_COMPLETE_DIAMETER, { - stroke: 'none', -- fill: rgbaToHEX(PRIMARY_COLOR, LINE_AUTO_COMPLETE_OPACITY), -- fillStyle: 'solid' -+ fill: rgbaToHEX(PRIMARY_COLOR, LINE_AUTO_COMPLETE_OPACITY) - }); - circle.classList.add(`line-auto-complete-${index}`); - this.autoCompleteG.appendChild(circle); -@@ -2527,7 +2525,8 @@ - return getEngine(TableSymbols.table).draw(this.board, rectangle, { - strokeWidth, - stroke: strokeColor, -- strokeLineDash -+ strokeLineDash, -+ fillStyle: element.fillStyle - }, { - element: element - }); -@@ -4069,7 +4068,7 @@ - draw(board, rectangle, options) { - const points = getCommentPoints(rectangle); - const rs = PlaitBoard.getRoughSVG(board); -- const polygon = rs.polygon(points, { ...options, fillStyle: 'solid' }); -+ const polygon = rs.polygon(points, options); - setStrokeLinecap(polygon, 'round'); - return polygon; - }, -@@ -4116,7 +4115,7 @@ - draw(board, rectangle, options) { - const points = getPoints(rectangle); - const rs = PlaitBoard.getRoughSVG(board); -- const polygon = rs.polygon(points, { ...options, fillStyle: 'solid' }); -+ const polygon = rs.polygon(points, options); - setStrokeLinecap(polygon, 'round'); - return polygon; - }, -@@ -4206,7 +4205,7 @@ - draw(board, rectangle, options) { - const centerPoint = [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height / 2]; - const rs = PlaitBoard.getRoughSVG(board); -- const shape = rs.ellipse(centerPoint[0], centerPoint[1], rectangle.width, rectangle.height, { ...options, fillStyle: 'solid' }); -+ const shape = rs.ellipse(centerPoint[0], centerPoint[1], rectangle.width, rectangle.height, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -4431,7 +4430,7 @@ - - const RectangleEngine = { - draw(board, rectangle, options) { -- return drawRectangle(board, rectangle, { ...options, fillStyle: 'solid' }); -+ return drawRectangle(board, rectangle, options); - }, - isInsidePoint(rectangle, point) { - const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]); -@@ -4455,7 +4454,7 @@ - - const RoundRectangleEngine = { - draw(board, rectangle, options) { -- return drawRoundRectangle(PlaitBoard.getRoughSVG(board), rectangle.x, rectangle.y, rectangle.x + rectangle.width, rectangle.y + rectangle.height, { ...options, fillStyle: 'solid' }, false, getRoundRectangleRadius(rectangle)); -+ return drawRoundRectangle(PlaitBoard.getRoughSVG(board), rectangle.x, rectangle.y, rectangle.x + rectangle.width, rectangle.y + rectangle.height, options, false, getRoundRectangleRadius(rectangle)); - }, - isInsidePoint(rectangle, point) { - return isPointInRoundRectangle(point, rectangle, getRoundRectangleRadius(rectangle)); -@@ -4528,7 +4527,7 @@ - const point9 = [x1 + rectangle.width / 4, y2]; - const point10 = [x1 + rectangle.width / 4, rectangle.y + rectangle.height]; - const point11 = [x1 + rectangle.width / 2, y2]; -- const shape = rs.path(`M${point2[0]} ${point2[1]} A ${radius} ${radius}, 0, 0, 1, ${point3[0]} ${point3[1]} L ${point4[0]} ${point4[1]} A ${radius} ${radius}, 0, 0, 1, ${point5[0]} ${point5[1]} L ${point11[0]} ${point11[1]} ${point10[0]} ${point10[1]} ${point9[0]} ${point9[1]} ${point6[0]} ${point6[1]} A ${radius} ${radius}, 0, 0, 1, ${point7[0]} ${point7[1]} L ${point8[0]} ${point8[1]} A ${radius} ${radius}, 0, 0, 1, ${point1[0]} ${point1[1]} Z`, { ...options, fillStyle: 'solid' }); -+ const shape = rs.path(`M${point2[0]} ${point2[1]} A ${radius} ${radius}, 0, 0, 1, ${point3[0]} ${point3[1]} L ${point4[0]} ${point4[1]} A ${radius} ${radius}, 0, 0, 1, ${point5[0]} ${point5[1]} L ${point11[0]} ${point11[1]} ${point10[0]} ${point10[1]} ${point9[0]} ${point9[1]} ${point6[0]} ${point6[1]} A ${radius} ${radius}, 0, 0, 1, ${point7[0]} ${point7[1]} L ${point8[0]} ${point8[1]} A ${radius} ${radius}, 0, 0, 1, ${point1[0]} ${point1[1]} Z`, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -4680,7 +4679,7 @@ - - const TerminalEngine = { - draw(board, rectangle, options) { -- return drawRoundRectangle(PlaitBoard.getRoughSVG(board), rectangle.x, rectangle.y, rectangle.x + rectangle.width, rectangle.y + rectangle.height, { ...options, fillStyle: 'solid' }, false, getStartEndRadius(rectangle)); -+ return drawRoundRectangle(PlaitBoard.getRoughSVG(board), rectangle.x, rectangle.y, rectangle.x + rectangle.width, rectangle.y + rectangle.height, options, false, getStartEndRadius(rectangle)); - }, - isInsidePoint(rectangle, point) { - return isPointInRoundRectangle(point, rectangle, getStartEndRadius(rectangle)); -@@ -4849,7 +4848,7 @@ - draw(board, rectangle, options) { - const rs = PlaitBoard.getRoughSVG(board); - const shape = rs.path(`M${rectangle.x} ${rectangle.y} L${rectangle.x + (rectangle.width * 3) / 4} ${rectangle.y} A ${rectangle.width / -- 4} ${rectangle.height / 2}, 0, 0, 1,${rectangle.x + (rectangle.width * 3) / 4} ${rectangle.y + rectangle.height} L${rectangle.x} ${rectangle.y + rectangle.height} Z`, { ...options, fillStyle: 'solid' }); -+ 4} ${rectangle.height / 2}, 0, 0, 1,${rectangle.x + (rectangle.width * 3) / 4} ${rectangle.y + rectangle.height} L${rectangle.x} ${rectangle.y + rectangle.height} Z`, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -4890,7 +4889,7 @@ - const StoredDataEngine = { - draw(board, rectangle, options) { - const rs = PlaitBoard.getRoughSVG(board); -- const shape = rs.path(`M${rectangle.x + rectangle.width / 10} ${rectangle.y} L${rectangle.x + rectangle.width} ${rectangle.y} A ${rectangle.width / 10} ${rectangle.height / 2}, 0, 0, 0,${rectangle.x + rectangle.width} ${rectangle.y + rectangle.height} L${rectangle.x + rectangle.width / 10} ${rectangle.y + rectangle.height}A ${rectangle.width / 10} ${rectangle.height / 2}, 0, 0, 1,${rectangle.x + rectangle.width / 10} ${rectangle.y}`, { ...options, fillStyle: 'solid' }); -+ const shape = rs.path(`M${rectangle.x + rectangle.width / 10} ${rectangle.y} L${rectangle.x + rectangle.width} ${rectangle.y} A ${rectangle.width / 10} ${rectangle.height / 2}, 0, 0, 0,${rectangle.x + rectangle.width} ${rectangle.y + rectangle.height} L${rectangle.x + rectangle.width / 10} ${rectangle.y + rectangle.height}A ${rectangle.width / 10} ${rectangle.height / 2}, 0, 0, 1,${rectangle.x + rectangle.width / 10} ${rectangle.y}`, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -4963,7 +4962,7 @@ - const shape = rs.path(`M${rectangle.x} ${rectangle.y} H${rectangle.x + rectangle.width} V${rectangle.y + rectangle.height} H${rectangle.x} Z M${rectangle.x + rectangle.width * 0.06} ${rectangle.y} L${rectangle.x + rectangle.width * 0.06} ${rectangle.y + - rectangle.height} M${rectangle.x + rectangle.width - rectangle.width * 0.06} ${rectangle.y} L${rectangle.x + - rectangle.width - -- rectangle.width * 0.06} ${rectangle.y + rectangle.height}`, { ...options, fillStyle: 'solid' }); -+ rectangle.width * 0.06} ${rectangle.y + rectangle.height}`, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -5041,7 +5040,7 @@ - L${rectangle.x + rectangle.width} ${rectangle.y + rectangle.height / 2} - M${rectangle.x + rectangle.width / 2} ${rectangle.y} - L${rectangle.x + rectangle.width / 2} ${rectangle.y + rectangle.height} -- `, { ...options, fillStyle: 'solid' }); -+ `, options); - setStrokeLinecap(shape, 'round'); - return shape; - } -@@ -5062,7 +5061,7 @@ - L${line1Points[1][0]} ${line1Points[1][1]} - M${line2Points[0][0]} ${line2Points[0][1]} - L${line2Points[1][0]} ${line2Points[1][1]} -- `, { ...options, fillStyle: 'solid' }); -+ `, options); - setStrokeLinecap(shape, 'round'); - return shape; - } -@@ -5078,7 +5077,7 @@ - (rectangle.height / 9) * 3}, ${rectangle.x + rectangle.width / 2} ${rectangle.y + - rectangle.height - - rectangle.height / 9} T${rectangle.x} ${rectangle.y + rectangle.height - rectangle.height / 9} -- `, { ...options, fillStyle: 'solid' }); -+ `, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -5169,7 +5168,7 @@ - M${rectangle.x + 5} ${rectangle.y + 10} H${rectangle.x + rectangle.width - 10} V${rectangle.y + rectangle.height - rectangle.height / 9} - - M${rectangle.x + 10} ${rectangle.y + 5} H${rectangle.x + rectangle.width - 5} V${rectangle.y + rectangle.height - rectangle.height / 9 - 10 - 3} -- `, { ...options, fillStyle: 'solid' }); -+ `, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -5245,7 +5244,7 @@ - A${rectangle.width / 2} ${rectangle.height * 0.15}, 0, 0, 0,${rectangle.x} ${rectangle.y + rectangle.height * 0.15} - V${rectangle.y + rectangle.height - rectangle.height * 0.15} - A${rectangle.width / 2} ${rectangle.height * 0.15}, 0, 0, 0, ${rectangle.x + rectangle.width} ${rectangle.y + rectangle.height - rectangle.height * 0.15} -- V${rectangle.y + rectangle.height * 0.15}`, { ...options, fillStyle: 'solid' }); -+ V${rectangle.y + rectangle.height * 0.15}`, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -5322,7 +5321,7 @@ - H${rectangle.x + rectangle.width * 0.15} - A${rectangle.width * 0.15} ${rectangle.height / 2}, 0, 0, 0, ${rectangle.x + rectangle.width * 0.15} ${rectangle.y + - rectangle.height} -- H${rectangle.x + rectangle.width - rectangle.width * 0.15}`, { ...options, fillStyle: 'solid' }); -+ H${rectangle.x + rectangle.width - rectangle.width * 0.15}`, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -5398,7 +5397,7 @@ - const shape = rs.path(`M${rectangle.x} ${rectangle.y} h${rectangle.width} v${rectangle.height} h${-rectangle.width} v${-rectangle.height} - M${rectangle.x} ${rectangle.y + rectangle.height / 10} h${rectangle.width} - M${rectangle.x + rectangle.width / 10} ${rectangle.y} v${rectangle.height} -- `, { ...options, fillStyle: 'solid' }); -+ `, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -5466,7 +5465,7 @@ - ${lowerCurve.controlPoint2[0]} ${lowerCurve.controlPoint2[1]}, - ${lowerCurve.endPoint[0]} ${lowerCurve.endPoint[1]}` - ].join(' '); -- const shape = rs.path(pathData, { ...options, fillStyle: 'solid', fill: 'transparent' }); -+ const shape = rs.path(pathData, { ...options, fill: 'transparent' }); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -5539,7 +5538,7 @@ - ${lowerCurve.controlPoint2[0]} ${lowerCurve.controlPoint2[1]}, - ${lowerCurve.endPoint[0]} ${lowerCurve.endPoint[1]}` - ].join(' '); -- const shape = rs.path(pathData, { ...options, fillStyle: 'solid', fill: 'transparent' }); -+ const shape = rs.path(pathData, { ...options, fill: 'transparent' }); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -5592,7 +5591,7 @@ - const rs = PlaitBoard.getRoughSVG(board); - const shape = rs.path(`M${rectangle.x + rectangle.width * 0.075} ${rectangle.y + rectangle.height} H${rectangle.x} V${rectangle.y} H${rectangle.x + - rectangle.width * 0.075} -- `, { ...options, fillStyle: 'solid', fill: 'transparent' }); -+ `, { ...options, fill: 'transparent' }); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -5639,7 +5638,7 @@ - H${rectangle.x + rectangle.width * 0.15} - L${rectangle.x} ${rectangle.y + rectangle.height / 2} - Z -- `, { ...options, fillStyle: 'solid' }); -+ `, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -5701,7 +5700,7 @@ - y: y + ACTIVE_STROKE_WIDTH, - width: width - ACTIVE_STROKE_WIDTH * 2, - height: height - ACTIVE_STROKE_WIDTH * 2 -- }, { fill: cell.fill, fillStyle: 'solid', strokeWidth: 0 }); -+ }, { fill: cell.fill, fillStyle: roughOptions?.fillStyle, strokeWidth: 0 }); - const cellRightBorder = drawLine(rs, [x + width, y], [x + width, y + height], roughOptions); - const cellBottomBorder = drawLine(rs, [x, y + height], [x + width, y + height], roughOptions); - g.append(cellRectangle, cellRightBorder, cellBottomBorder); -@@ -5846,7 +5845,7 @@ - `M${leftLegLine[0][0]} ${leftLegLine[0][1]} L${leftLegLine[1][0]} ${leftLegLine[1][1]}`, - `M${rightLegLine[0][0]} ${rightLegLine[0][1]} L${rightLegLine[1][0]} ${rightLegLine[1][1]}` - ].join(' '); -- const shape = rs.path(pathData, { ...options, fillStyle: 'solid' }); -+ const shape = rs.path(pathData, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -5918,7 +5917,7 @@ - const ContainerEngine = { - draw(board, rectangle, options) { - const rs = PlaitBoard.getRoughSVG(board); -- const shape = rs.path(`M${rectangle.x} ${rectangle.y} H${rectangle.x + rectangle.width} V${rectangle.y + rectangle.height} H${rectangle.x} Z M${rectangle.x + 40} ${rectangle.y} L${rectangle.x + 40} ${rectangle.y + rectangle.height} `, { ...options, fillStyle: 'solid' }); -+ const shape = rs.path(`M${rectangle.x} ${rectangle.y} H${rectangle.x + rectangle.width} V${rectangle.y + rectangle.height} H${rectangle.x} Z M${rectangle.x + 40} ${rectangle.y} L${rectangle.x + 40} ${rectangle.y + rectangle.height} `, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -5989,7 +5988,7 @@ - `V${points.leftMiddle[1]}`, - `H${points.middlePoint[0]}` - ].join(' '); -- const shape = rs.path(pathData, { ...options, fillStyle: 'solid' }); -+ const shape = rs.path(pathData, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -6069,7 +6068,7 @@ - H${rectangle.x + rectangle.width / 3 - 8} - L${rectangle.x + rectangle.width / 3} ${rectangle.y + 16} - V${rectangle.y} -- `, { ...options, fillStyle: 'solid' }); -+ `, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -6135,7 +6134,7 @@ - draw(board, rectangle, options) { - const rs = PlaitBoard.getRoughSVG(board); - const lines = getDeletionLines(rectangle); -- const shape = rs.path(lines.map(([from, to]) => `M${from[0]} ${from[1]} L${to[0]} ${to[1]}`).join(' '), { ...options, fillStyle: 'solid', strokeWidth: 4 }); -+ const shape = rs.path(lines.map(([from, to]) => `M${from[0]} ${from[1]} L${to[0]} ${to[1]}`).join(' '), { ...options, strokeWidth: 4 }); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -6176,7 +6175,7 @@ - const shape = rs.path(`M${rectangle.x} ${rectangle.y} H${rectangle.x + rectangle.width} V${rectangle.y + rectangle.height} H${rectangle.x} Z M${rectangle.x + rectangle.width * 0.125} ${rectangle.y} L${rectangle.x + rectangle.width * 0.125} ${rectangle.y + - rectangle.height} M${rectangle.x + rectangle.width - rectangle.width * 0.125} ${rectangle.y} L${rectangle.x + - rectangle.width - -- rectangle.width * 0.125} ${rectangle.y + rectangle.height}`, { ...options, fillStyle: 'solid' }); -+ rectangle.width * 0.125} ${rectangle.y + rectangle.height}`, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -6225,7 +6224,7 @@ - Z - M${rectangle.x + rectangle.width - 16} ${rectangle.y} - A16 16, 0,0,1, ${rectangle.x + rectangle.width} ${rectangle.y + 16} -- `, { ...options, fillStyle: 'solid' }); -+ `, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -6321,10 +6320,7 @@ - // 最后一条线 - `M${line2[0][0]} ${line2[0][1]} H${line2[1][0]}` - ].join(' '); -- const shape = rs.path(pathData, { -- ...options, -- fillStyle: 'solid' -- }); -+ const shape = rs.path(pathData, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -6404,7 +6400,6 @@ - ].join(' '); - const shape = rs.path(pathData, { - ...options, -- fillStyle: 'solid', - fill: 'transparent' - }); - setStrokeLinecap(shape, 'round'); -@@ -6495,10 +6490,7 @@ - `H${line.endX}`, - ...arcCommands.map((command) => `A${command.rx} ${command.ry} ${command.xAxisRotation} ${command.largeArcFlag} ${command.sweepFlag} ${command.endX} ${command.endY}`) - ].join(' '); -- const shape = rs.path(pathData, { -- ...options, -- fillStyle: 'solid' -- }); -+ const shape = rs.path(pathData, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -6586,7 +6578,7 @@ - `M${points.bottomBoxEnd[0]} ${points.bottomBoxEnd[1]}`, - `V${points.mainEnd[1]}` - ].join(' '); -- const shape = rs.path(pathData, { ...options, fillStyle: 'solid' }); -+ const shape = rs.path(pathData, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -6669,7 +6661,7 @@ - V${rectangle.y + rectangle.height} - H${rectangle.x} Z - -- `, { ...options, fillStyle: 'solid' }); -+ `, options); - const componentShape = ComponentEngine.draw(board, componentRectangle, options); - shape.append(componentShape); - setStrokeLinecap(shape, 'round'); -@@ -6714,7 +6706,6 @@ - const rs = PlaitBoard.getRoughSVG(board); - return drawRoundRectangle(rs, rectangle.x, rectangle.y, rectangle.x + rectangle.width, rectangle.y + rectangle.height, { - ...options, -- fillStyle: 'solid', - dashGap: 10, - strokeLineDash: [10, 10] - }, false, 4); -@@ -8014,8 +8005,7 @@ - const circleG = drawCircle(PlaitBoard.getRoughSVG(board), ref.connectorPoint || ref.edgePoint, 6, { - stroke: SELECTION_BORDER_COLOR, - strokeWidth: SNAPPING_STROKE_WIDTH, -- fill: SELECTION_BORDER_COLOR, -- fillStyle: 'solid' -+ fill: SELECTION_BORDER_COLOR - }); - boundShapeG.appendChild(circleG); - } -@@ -8266,8 +8256,7 @@ - // function 1: dnd - reactionG = drawCircle(PlaitBoard.getRoughSVG(board), hitPoint, LINE_AUTO_COMPLETE_HOVERED_DIAMETER, { - stroke: 'none', -- fill: rgbaToHEX(PRIMARY_COLOR, LINE_AUTO_COMPLETE_HOVERED_OPACITY), -- fillStyle: 'solid' -+ fill: rgbaToHEX(PRIMARY_COLOR, LINE_AUTO_COMPLETE_HOVERED_OPACITY) - }); - PlaitBoard.getActiveHost(board).append(reactionG); - PlaitBoard.getBoardContainer(board).classList.add(CursorClass.crosshair); diff --git a/patches/patches/@plait+draw+0.92.3.patch b/patches/patches/@plait+draw+0.92.3.patch deleted file mode 100644 index dc10e6b..0000000 --- a/patches/patches/@plait+draw+0.92.3.patch +++ /dev/null @@ -1,400 +0,0 @@ ---- plait-draw.mjs.backup 2026-02-21 17:17:17 -+++ plait-draw.mjs 2026-02-21 17:26:05 -@@ -1783,7 +1783,7 @@ - .map((command) => `A ${command.rx} ${command.ry} ${command.xAxisRotation} ${command.largeArcFlag} ${command.sweepFlag} ${command.endX} ${command.endY}`) - .join('\n') + - ' Z'; -- const svgElement = rs.path(pathData, { ...options, fillStyle: 'solid' }); -+ const svgElement = rs.path(pathData, options); - setPathStrokeLinecap(svgElement, 'round'); - return svgElement; - }, -@@ -2185,8 +2185,7 @@ - const maskG = drawShape(board, activeRectangle, shape, { - stroke: SELECTION_BORDER_COLOR, - strokeWidth: 0, -- fill: isClosedDrawElement(element) ? SELECTION_FILL_COLOR : DefaultDrawStyle.fill, -- fillStyle: 'solid' -+ fill: isClosedDrawElement(element) ? SELECTION_FILL_COLOR : DefaultDrawStyle.fill - }, drawOptions); - g.appendChild(maskG); - } -@@ -2196,8 +2195,7 @@ - const circleG = drawCircle(PlaitBoard.getRoughSVG(board), point, 8, { - stroke: SELECTION_BORDER_COLOR, - strokeWidth: ACTIVE_STROKE_WIDTH, -- fill: '#FFF', -- fillStyle: 'solid' -+ fill: '#FFF' - }); - g.appendChild(circleG); - }); -@@ -2270,7 +2268,8 @@ - stroke: strokeColor, - strokeWidth, - fill, -- strokeLineDash -+ strokeLineDash, -+ fillStyle: element.fillStyle - }); - } - } -@@ -2480,8 +2479,7 @@ - middlePoints.forEach((point, index) => { - const circle = drawCircle(PlaitBoard.getRoughSVG(this.board), point, LINE_AUTO_COMPLETE_DIAMETER, { - stroke: 'none', -- fill: rgbaToHEX(PRIMARY_COLOR, LINE_AUTO_COMPLETE_OPACITY), -- fillStyle: 'solid' -+ fill: rgbaToHEX(PRIMARY_COLOR, LINE_AUTO_COMPLETE_OPACITY) - }); - circle.classList.add(`line-auto-complete-${index}`); - this.autoCompleteG.appendChild(circle); -@@ -2527,7 +2525,8 @@ - return getEngine(TableSymbols.table).draw(this.board, rectangle, { - strokeWidth, - stroke: strokeColor, -- strokeLineDash -+ strokeLineDash, -+ fillStyle: element.fillStyle - }, { - element: element - }); -@@ -4069,7 +4068,7 @@ - draw(board, rectangle, options) { - const points = getCommentPoints(rectangle); - const rs = PlaitBoard.getRoughSVG(board); -- const polygon = rs.polygon(points, { ...options, fillStyle: 'solid' }); -+ const polygon = rs.polygon(points, options); - setStrokeLinecap(polygon, 'round'); - return polygon; - }, -@@ -4116,7 +4115,7 @@ - draw(board, rectangle, options) { - const points = getPoints(rectangle); - const rs = PlaitBoard.getRoughSVG(board); -- const polygon = rs.polygon(points, { ...options, fillStyle: 'solid' }); -+ const polygon = rs.polygon(points, options); - setStrokeLinecap(polygon, 'round'); - return polygon; - }, -@@ -4206,7 +4205,7 @@ - draw(board, rectangle, options) { - const centerPoint = [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height / 2]; - const rs = PlaitBoard.getRoughSVG(board); -- const shape = rs.ellipse(centerPoint[0], centerPoint[1], rectangle.width, rectangle.height, { ...options, fillStyle: 'solid' }); -+ const shape = rs.ellipse(centerPoint[0], centerPoint[1], rectangle.width, rectangle.height, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -4431,7 +4430,7 @@ - - const RectangleEngine = { - draw(board, rectangle, options) { -- return drawRectangle(board, rectangle, { ...options, fillStyle: 'solid' }); -+ return drawRectangle(board, rectangle, options); - }, - isInsidePoint(rectangle, point) { - const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]); -@@ -4455,7 +4454,7 @@ - - const RoundRectangleEngine = { - draw(board, rectangle, options) { -- return drawRoundRectangle(PlaitBoard.getRoughSVG(board), rectangle.x, rectangle.y, rectangle.x + rectangle.width, rectangle.y + rectangle.height, { ...options, fillStyle: 'solid' }, false, getRoundRectangleRadius(rectangle)); -+ return drawRoundRectangle(PlaitBoard.getRoughSVG(board), rectangle.x, rectangle.y, rectangle.x + rectangle.width, rectangle.y + rectangle.height, options, false, getRoundRectangleRadius(rectangle)); - }, - isInsidePoint(rectangle, point) { - return isPointInRoundRectangle(point, rectangle, getRoundRectangleRadius(rectangle)); -@@ -4528,7 +4527,7 @@ - const point9 = [x1 + rectangle.width / 4, y2]; - const point10 = [x1 + rectangle.width / 4, rectangle.y + rectangle.height]; - const point11 = [x1 + rectangle.width / 2, y2]; -- const shape = rs.path(`M${point2[0]} ${point2[1]} A ${radius} ${radius}, 0, 0, 1, ${point3[0]} ${point3[1]} L ${point4[0]} ${point4[1]} A ${radius} ${radius}, 0, 0, 1, ${point5[0]} ${point5[1]} L ${point11[0]} ${point11[1]} ${point10[0]} ${point10[1]} ${point9[0]} ${point9[1]} ${point6[0]} ${point6[1]} A ${radius} ${radius}, 0, 0, 1, ${point7[0]} ${point7[1]} L ${point8[0]} ${point8[1]} A ${radius} ${radius}, 0, 0, 1, ${point1[0]} ${point1[1]} Z`, { ...options, fillStyle: 'solid' }); -+ const shape = rs.path(`M${point2[0]} ${point2[1]} A ${radius} ${radius}, 0, 0, 1, ${point3[0]} ${point3[1]} L ${point4[0]} ${point4[1]} A ${radius} ${radius}, 0, 0, 1, ${point5[0]} ${point5[1]} L ${point11[0]} ${point11[1]} ${point10[0]} ${point10[1]} ${point9[0]} ${point9[1]} ${point6[0]} ${point6[1]} A ${radius} ${radius}, 0, 0, 1, ${point7[0]} ${point7[1]} L ${point8[0]} ${point8[1]} A ${radius} ${radius}, 0, 0, 1, ${point1[0]} ${point1[1]} Z`, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -4680,7 +4679,7 @@ - - const TerminalEngine = { - draw(board, rectangle, options) { -- return drawRoundRectangle(PlaitBoard.getRoughSVG(board), rectangle.x, rectangle.y, rectangle.x + rectangle.width, rectangle.y + rectangle.height, { ...options, fillStyle: 'solid' }, false, getStartEndRadius(rectangle)); -+ return drawRoundRectangle(PlaitBoard.getRoughSVG(board), rectangle.x, rectangle.y, rectangle.x + rectangle.width, rectangle.y + rectangle.height, options, false, getStartEndRadius(rectangle)); - }, - isInsidePoint(rectangle, point) { - return isPointInRoundRectangle(point, rectangle, getStartEndRadius(rectangle)); -@@ -4849,7 +4848,7 @@ - draw(board, rectangle, options) { - const rs = PlaitBoard.getRoughSVG(board); - const shape = rs.path(`M${rectangle.x} ${rectangle.y} L${rectangle.x + (rectangle.width * 3) / 4} ${rectangle.y} A ${rectangle.width / -- 4} ${rectangle.height / 2}, 0, 0, 1,${rectangle.x + (rectangle.width * 3) / 4} ${rectangle.y + rectangle.height} L${rectangle.x} ${rectangle.y + rectangle.height} Z`, { ...options, fillStyle: 'solid' }); -+ 4} ${rectangle.height / 2}, 0, 0, 1,${rectangle.x + (rectangle.width * 3) / 4} ${rectangle.y + rectangle.height} L${rectangle.x} ${rectangle.y + rectangle.height} Z`, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -4890,7 +4889,7 @@ - const StoredDataEngine = { - draw(board, rectangle, options) { - const rs = PlaitBoard.getRoughSVG(board); -- const shape = rs.path(`M${rectangle.x + rectangle.width / 10} ${rectangle.y} L${rectangle.x + rectangle.width} ${rectangle.y} A ${rectangle.width / 10} ${rectangle.height / 2}, 0, 0, 0,${rectangle.x + rectangle.width} ${rectangle.y + rectangle.height} L${rectangle.x + rectangle.width / 10} ${rectangle.y + rectangle.height}A ${rectangle.width / 10} ${rectangle.height / 2}, 0, 0, 1,${rectangle.x + rectangle.width / 10} ${rectangle.y}`, { ...options, fillStyle: 'solid' }); -+ const shape = rs.path(`M${rectangle.x + rectangle.width / 10} ${rectangle.y} L${rectangle.x + rectangle.width} ${rectangle.y} A ${rectangle.width / 10} ${rectangle.height / 2}, 0, 0, 0,${rectangle.x + rectangle.width} ${rectangle.y + rectangle.height} L${rectangle.x + rectangle.width / 10} ${rectangle.y + rectangle.height}A ${rectangle.width / 10} ${rectangle.height / 2}, 0, 0, 1,${rectangle.x + rectangle.width / 10} ${rectangle.y}`, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -4963,7 +4962,7 @@ - const shape = rs.path(`M${rectangle.x} ${rectangle.y} H${rectangle.x + rectangle.width} V${rectangle.y + rectangle.height} H${rectangle.x} Z M${rectangle.x + rectangle.width * 0.06} ${rectangle.y} L${rectangle.x + rectangle.width * 0.06} ${rectangle.y + - rectangle.height} M${rectangle.x + rectangle.width - rectangle.width * 0.06} ${rectangle.y} L${rectangle.x + - rectangle.width - -- rectangle.width * 0.06} ${rectangle.y + rectangle.height}`, { ...options, fillStyle: 'solid' }); -+ rectangle.width * 0.06} ${rectangle.y + rectangle.height}`, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -5041,7 +5040,7 @@ - L${rectangle.x + rectangle.width} ${rectangle.y + rectangle.height / 2} - M${rectangle.x + rectangle.width / 2} ${rectangle.y} - L${rectangle.x + rectangle.width / 2} ${rectangle.y + rectangle.height} -- `, { ...options, fillStyle: 'solid' }); -+ `, options); - setStrokeLinecap(shape, 'round'); - return shape; - } -@@ -5062,7 +5061,7 @@ - L${line1Points[1][0]} ${line1Points[1][1]} - M${line2Points[0][0]} ${line2Points[0][1]} - L${line2Points[1][0]} ${line2Points[1][1]} -- `, { ...options, fillStyle: 'solid' }); -+ `, options); - setStrokeLinecap(shape, 'round'); - return shape; - } -@@ -5078,7 +5077,7 @@ - (rectangle.height / 9) * 3}, ${rectangle.x + rectangle.width / 2} ${rectangle.y + - rectangle.height - - rectangle.height / 9} T${rectangle.x} ${rectangle.y + rectangle.height - rectangle.height / 9} -- `, { ...options, fillStyle: 'solid' }); -+ `, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -5169,7 +5168,7 @@ - M${rectangle.x + 5} ${rectangle.y + 10} H${rectangle.x + rectangle.width - 10} V${rectangle.y + rectangle.height - rectangle.height / 9} - - M${rectangle.x + 10} ${rectangle.y + 5} H${rectangle.x + rectangle.width - 5} V${rectangle.y + rectangle.height - rectangle.height / 9 - 10 - 3} -- `, { ...options, fillStyle: 'solid' }); -+ `, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -5245,7 +5244,7 @@ - A${rectangle.width / 2} ${rectangle.height * 0.15}, 0, 0, 0,${rectangle.x} ${rectangle.y + rectangle.height * 0.15} - V${rectangle.y + rectangle.height - rectangle.height * 0.15} - A${rectangle.width / 2} ${rectangle.height * 0.15}, 0, 0, 0, ${rectangle.x + rectangle.width} ${rectangle.y + rectangle.height - rectangle.height * 0.15} -- V${rectangle.y + rectangle.height * 0.15}`, { ...options, fillStyle: 'solid' }); -+ V${rectangle.y + rectangle.height * 0.15}`, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -5322,7 +5321,7 @@ - H${rectangle.x + rectangle.width * 0.15} - A${rectangle.width * 0.15} ${rectangle.height / 2}, 0, 0, 0, ${rectangle.x + rectangle.width * 0.15} ${rectangle.y + - rectangle.height} -- H${rectangle.x + rectangle.width - rectangle.width * 0.15}`, { ...options, fillStyle: 'solid' }); -+ H${rectangle.x + rectangle.width - rectangle.width * 0.15}`, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -5398,7 +5397,7 @@ - const shape = rs.path(`M${rectangle.x} ${rectangle.y} h${rectangle.width} v${rectangle.height} h${-rectangle.width} v${-rectangle.height} - M${rectangle.x} ${rectangle.y + rectangle.height / 10} h${rectangle.width} - M${rectangle.x + rectangle.width / 10} ${rectangle.y} v${rectangle.height} -- `, { ...options, fillStyle: 'solid' }); -+ `, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -5466,7 +5465,7 @@ - ${lowerCurve.controlPoint2[0]} ${lowerCurve.controlPoint2[1]}, - ${lowerCurve.endPoint[0]} ${lowerCurve.endPoint[1]}` - ].join(' '); -- const shape = rs.path(pathData, { ...options, fillStyle: 'solid', fill: 'transparent' }); -+ const shape = rs.path(pathData, { ...options, fill: 'transparent' }); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -5539,7 +5538,7 @@ - ${lowerCurve.controlPoint2[0]} ${lowerCurve.controlPoint2[1]}, - ${lowerCurve.endPoint[0]} ${lowerCurve.endPoint[1]}` - ].join(' '); -- const shape = rs.path(pathData, { ...options, fillStyle: 'solid', fill: 'transparent' }); -+ const shape = rs.path(pathData, { ...options, fill: 'transparent' }); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -5592,7 +5591,7 @@ - const rs = PlaitBoard.getRoughSVG(board); - const shape = rs.path(`M${rectangle.x + rectangle.width * 0.075} ${rectangle.y + rectangle.height} H${rectangle.x} V${rectangle.y} H${rectangle.x + - rectangle.width * 0.075} -- `, { ...options, fillStyle: 'solid', fill: 'transparent' }); -+ `, { ...options, fill: 'transparent' }); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -5639,7 +5638,7 @@ - H${rectangle.x + rectangle.width * 0.15} - L${rectangle.x} ${rectangle.y + rectangle.height / 2} - Z -- `, { ...options, fillStyle: 'solid' }); -+ `, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -5701,7 +5700,7 @@ - y: y + ACTIVE_STROKE_WIDTH, - width: width - ACTIVE_STROKE_WIDTH * 2, - height: height - ACTIVE_STROKE_WIDTH * 2 -- }, { fill: cell.fill, fillStyle: 'solid', strokeWidth: 0 }); -+ }, { fill: cell.fill, fillStyle: roughOptions?.fillStyle, strokeWidth: 0 }); - const cellRightBorder = drawLine(rs, [x + width, y], [x + width, y + height], roughOptions); - const cellBottomBorder = drawLine(rs, [x, y + height], [x + width, y + height], roughOptions); - g.append(cellRectangle, cellRightBorder, cellBottomBorder); -@@ -5846,7 +5845,7 @@ - `M${leftLegLine[0][0]} ${leftLegLine[0][1]} L${leftLegLine[1][0]} ${leftLegLine[1][1]}`, - `M${rightLegLine[0][0]} ${rightLegLine[0][1]} L${rightLegLine[1][0]} ${rightLegLine[1][1]}` - ].join(' '); -- const shape = rs.path(pathData, { ...options, fillStyle: 'solid' }); -+ const shape = rs.path(pathData, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -5918,7 +5917,7 @@ - const ContainerEngine = { - draw(board, rectangle, options) { - const rs = PlaitBoard.getRoughSVG(board); -- const shape = rs.path(`M${rectangle.x} ${rectangle.y} H${rectangle.x + rectangle.width} V${rectangle.y + rectangle.height} H${rectangle.x} Z M${rectangle.x + 40} ${rectangle.y} L${rectangle.x + 40} ${rectangle.y + rectangle.height} `, { ...options, fillStyle: 'solid' }); -+ const shape = rs.path(`M${rectangle.x} ${rectangle.y} H${rectangle.x + rectangle.width} V${rectangle.y + rectangle.height} H${rectangle.x} Z M${rectangle.x + 40} ${rectangle.y} L${rectangle.x + 40} ${rectangle.y + rectangle.height} `, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -5989,7 +5988,7 @@ - `V${points.leftMiddle[1]}`, - `H${points.middlePoint[0]}` - ].join(' '); -- const shape = rs.path(pathData, { ...options, fillStyle: 'solid' }); -+ const shape = rs.path(pathData, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -6069,7 +6068,7 @@ - H${rectangle.x + rectangle.width / 3 - 8} - L${rectangle.x + rectangle.width / 3} ${rectangle.y + 16} - V${rectangle.y} -- `, { ...options, fillStyle: 'solid' }); -+ `, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -6135,7 +6134,7 @@ - draw(board, rectangle, options) { - const rs = PlaitBoard.getRoughSVG(board); - const lines = getDeletionLines(rectangle); -- const shape = rs.path(lines.map(([from, to]) => `M${from[0]} ${from[1]} L${to[0]} ${to[1]}`).join(' '), { ...options, fillStyle: 'solid', strokeWidth: 4 }); -+ const shape = rs.path(lines.map(([from, to]) => `M${from[0]} ${from[1]} L${to[0]} ${to[1]}`).join(' '), { ...options, strokeWidth: 4 }); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -6176,7 +6175,7 @@ - const shape = rs.path(`M${rectangle.x} ${rectangle.y} H${rectangle.x + rectangle.width} V${rectangle.y + rectangle.height} H${rectangle.x} Z M${rectangle.x + rectangle.width * 0.125} ${rectangle.y} L${rectangle.x + rectangle.width * 0.125} ${rectangle.y + - rectangle.height} M${rectangle.x + rectangle.width - rectangle.width * 0.125} ${rectangle.y} L${rectangle.x + - rectangle.width - -- rectangle.width * 0.125} ${rectangle.y + rectangle.height}`, { ...options, fillStyle: 'solid' }); -+ rectangle.width * 0.125} ${rectangle.y + rectangle.height}`, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -6225,7 +6224,7 @@ - Z - M${rectangle.x + rectangle.width - 16} ${rectangle.y} - A16 16, 0,0,1, ${rectangle.x + rectangle.width} ${rectangle.y + 16} -- `, { ...options, fillStyle: 'solid' }); -+ `, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -6321,10 +6320,7 @@ - // 最后一条线 - `M${line2[0][0]} ${line2[0][1]} H${line2[1][0]}` - ].join(' '); -- const shape = rs.path(pathData, { -- ...options, -- fillStyle: 'solid' -- }); -+ const shape = rs.path(pathData, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -6404,7 +6400,6 @@ - ].join(' '); - const shape = rs.path(pathData, { - ...options, -- fillStyle: 'solid', - fill: 'transparent' - }); - setStrokeLinecap(shape, 'round'); -@@ -6495,10 +6490,7 @@ - `H${line.endX}`, - ...arcCommands.map((command) => `A${command.rx} ${command.ry} ${command.xAxisRotation} ${command.largeArcFlag} ${command.sweepFlag} ${command.endX} ${command.endY}`) - ].join(' '); -- const shape = rs.path(pathData, { -- ...options, -- fillStyle: 'solid' -- }); -+ const shape = rs.path(pathData, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -6586,7 +6578,7 @@ - `M${points.bottomBoxEnd[0]} ${points.bottomBoxEnd[1]}`, - `V${points.mainEnd[1]}` - ].join(' '); -- const shape = rs.path(pathData, { ...options, fillStyle: 'solid' }); -+ const shape = rs.path(pathData, options); - setStrokeLinecap(shape, 'round'); - return shape; - }, -@@ -6669,7 +6661,7 @@ - V${rectangle.y + rectangle.height} - H${rectangle.x} Z - -- `, { ...options, fillStyle: 'solid' }); -+ `, options); - const componentShape = ComponentEngine.draw(board, componentRectangle, options); - shape.append(componentShape); - setStrokeLinecap(shape, 'round'); -@@ -6714,7 +6706,6 @@ - const rs = PlaitBoard.getRoughSVG(board); - return drawRoundRectangle(rs, rectangle.x, rectangle.y, rectangle.x + rectangle.width, rectangle.y + rectangle.height, { - ...options, -- fillStyle: 'solid', - dashGap: 10, - strokeLineDash: [10, 10] - }, false, 4); -@@ -8014,8 +8005,7 @@ - const circleG = drawCircle(PlaitBoard.getRoughSVG(board), ref.connectorPoint || ref.edgePoint, 6, { - stroke: SELECTION_BORDER_COLOR, - strokeWidth: SNAPPING_STROKE_WIDTH, -- fill: SELECTION_BORDER_COLOR, -- fillStyle: 'solid' -+ fill: SELECTION_BORDER_COLOR - }); - boundShapeG.appendChild(circleG); - } -@@ -8266,8 +8256,7 @@ - // function 1: dnd - reactionG = drawCircle(PlaitBoard.getRoughSVG(board), hitPoint, LINE_AUTO_COMPLETE_HOVERED_DIAMETER, { - stroke: 'none', -- fill: rgbaToHEX(PRIMARY_COLOR, LINE_AUTO_COMPLETE_HOVERED_OPACITY), -- fillStyle: 'solid' -+ fill: rgbaToHEX(PRIMARY_COLOR, LINE_AUTO_COMPLETE_HOVERED_OPACITY) - }); - PlaitBoard.getActiveHost(board).append(reactionG); - PlaitBoard.getBoardContainer(board).classList.add(CursorClass.crosshair); \ No newline at end of file diff --git a/scripts/apply-patches.sh b/scripts/apply-patches.sh deleted file mode 100755 index 5a098e6..0000000 --- a/scripts/apply-patches.sh +++ /dev/null @@ -1,65 +0,0 @@ -#!/bin/bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" - -cd "$PROJECT_ROOT" - -if ! command -v node >/dev/null 2>&1; then - echo "✗ apply-patches: 'node' is required but not on PATH" >&2 - exit 1 -fi - -if ! command -v patch >/dev/null 2>&1; then - echo "✗ apply-patches: 'patch' binary not available — install GNU patch" >&2 - echo " alpine: apk add patch" >&2 - echo " debian/ubuntu: apt-get install patch" >&2 - exit 1 -fi - -DRAW_PKG_JSON=$(node -p "require.resolve('@plait/draw/package.json')" 2>/dev/null) || { - echo "✗ apply-patches: cannot resolve @plait/draw — is it installed?" >&2 - echo " cwd: $PROJECT_ROOT" >&2 - exit 1 -} - -DRAW_VERSION=$(node -p "require('$DRAW_PKG_JSON').version") -PATCH_FILE="$PROJECT_ROOT/patches/@plait+draw+$DRAW_VERSION.patch" -TARGET_DIR="$(dirname "$DRAW_PKG_JSON")/fesm2022" -TARGET_FILE="$TARGET_DIR/plait-draw.mjs" -MARKER="fillStyle: element.fillStyle" - -if [ ! -f "$PATCH_FILE" ]; then - echo "✗ apply-patches: patch file not found for @plait/draw v$DRAW_VERSION" >&2 - echo " expected: $PATCH_FILE" >&2 - echo " available patches:" >&2 - ls "$PROJECT_ROOT/patches/" >&2 || true - exit 1 -fi - -if [ ! -f "$TARGET_FILE" ]; then - echo "✗ apply-patches: target file not found: $TARGET_FILE" >&2 - exit 1 -fi - -cd "$TARGET_DIR" - -if patch -p0 --reverse --dry-run --silent < "$PATCH_FILE" >/dev/null 2>&1; then - echo "✓ apply-patches: @plait/draw v$DRAW_VERSION already patched" - exit 0 -fi - -echo " applying patch to @plait/draw v$DRAW_VERSION..." -if ! patch -p0 --forward < "$PATCH_FILE"; then - echo "✗ apply-patches: patch failed to apply cleanly" >&2 - exit 1 -fi - -if ! grep -q "$MARKER" "$TARGET_FILE"; then - echo "✗ apply-patches: post-apply verification failed — marker not found" >&2 - echo " $TARGET_FILE may be partially patched or unmodified" >&2 - exit 1 -fi - -echo "✓ apply-patches: @plait/draw v$DRAW_VERSION patched"