jvs-draw 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +66 -0
  2. package/dist/App.vue.d.ts +2 -0
  3. package/dist/components/BoardName.vue.d.ts +5 -0
  4. package/dist/components/ContextMenu.vue.d.ts +18 -0
  5. package/dist/components/ExcalidrawCanvas.vue.d.ts +13 -0
  6. package/dist/components/FloatingPopovers.vue.d.ts +2 -0
  7. package/dist/components/FloatingPropertiesBar.vue.d.ts +7 -0
  8. package/dist/components/Footer.vue.d.ts +2 -0
  9. package/dist/components/HelpDialog.vue.d.ts +2 -0
  10. package/dist/components/PropertiesPanel.vue.d.ts +11 -0
  11. package/dist/components/Toolbar.vue.d.ts +2 -0
  12. package/dist/components/panelCom/alignStyle.vue.d.ts +2 -0
  13. package/dist/components/panelCom/arrowStyle.vue.d.ts +2 -0
  14. package/dist/components/panelCom/borderStyle.vue.d.ts +2 -0
  15. package/dist/components/panelCom/colorStyle.vue.d.ts +11 -0
  16. package/dist/components/panelCom/fillStyle.vue.d.ts +2 -0
  17. package/dist/components/panelCom/lineStyle.vue.d.ts +2 -0
  18. package/dist/components/panelCom/textStyle.vue.d.ts +2 -0
  19. package/dist/core/element.d.ts +25 -0
  20. package/dist/core/renderer.d.ts +7 -0
  21. package/dist/elbow/algorithms/a-star.d.ts +10 -0
  22. package/dist/elbow/algorithms/data-structures/graph.d.ts +14 -0
  23. package/dist/elbow/algorithms/data-structures/index.d.ts +2 -0
  24. package/dist/elbow/algorithms/data-structures/priority-queue.d.ts +16 -0
  25. package/dist/elbow/algorithms/index.d.ts +2 -0
  26. package/dist/elbow/constants/default.d.ts +10 -0
  27. package/dist/elbow/constants/index.d.ts +4 -0
  28. package/dist/elbow/constants/media.d.ts +6 -0
  29. package/dist/elbow/constants/property.d.ts +5 -0
  30. package/dist/elbow/constants/resize.d.ts +10 -0
  31. package/dist/elbow/core.d.ts +37 -0
  32. package/dist/elbow/utils/elbow-line-route.d.ts +38 -0
  33. package/dist/elbow/utils/index.d.ts +4 -0
  34. package/dist/elbow/utils/line-path.d.ts +7 -0
  35. package/dist/elbow/utils/math.d.ts +3 -0
  36. package/dist/elbow/utils/vector.d.ts +6 -0
  37. package/dist/index.d.ts +3 -0
  38. package/dist/jvs-draw.css +1 -0
  39. package/dist/jvs-draw.es.js +6810 -0
  40. package/dist/jvs-draw.umd.js +6 -0
  41. package/dist/jvs-ui-public/icon/icon.ico +0 -0
  42. package/dist/jvs-ui-public/icon/logo.ico +0 -0
  43. package/dist/jvs-ui-public/icon-fonts/iconfont.css +627 -0
  44. package/dist/jvs-ui-public/icon-fonts/iconfont.js +1 -0
  45. package/dist/jvs-ui-public/icon-fonts/iconfont.json +1080 -0
  46. package/dist/jvs-ui-public/icon-fonts/iconfont.ttf +0 -0
  47. package/dist/jvs-ui-public/icon-fonts/iconfont.woff +0 -0
  48. package/dist/jvs-ui-public/icon-fonts/iconfont.woff2 +0 -0
  49. package/dist/jvs-ui-public/public-fonts/iconfont.css +265 -0
  50. package/dist/jvs-ui-public/public-fonts/iconfont.js +1 -0
  51. package/dist/jvs-ui-public/public-fonts/iconfont.json +443 -0
  52. package/dist/jvs-ui-public/public-fonts/iconfont.svg +143 -0
  53. package/dist/jvs-ui-public/public-fonts/iconfont.ttf +0 -0
  54. package/dist/jvs-ui-public/public-fonts/iconfont.woff +0 -0
  55. package/dist/jvs-ui-public/public-fonts/iconfont.woff2 +0 -0
  56. package/dist/main.d.ts +0 -0
  57. package/dist/store/index.d.ts +1518 -0
  58. package/dist/types/element.d.ts +128 -0
  59. package/dist/utils/calcDomHeight.d.ts +6 -0
  60. package/dist/utils/image.d.ts +10 -0
  61. package/dist/utils/isPointOnElement.d.ts +1 -0
  62. package/dist/utils/math.d.ts +71 -0
  63. package/dist/utils/routing.d.ts +3 -0
  64. package/dist/utils/utils.d.ts +2 -0
  65. package/dist/vite.svg +1 -0
  66. package/package.json +51 -0
package/README.md ADDED
@@ -0,0 +1,66 @@
1
+ # JVS-Draw (Excalidraw Reconstruction)
2
+
3
+ 本项目是对 [Excalidraw](https://github.com/excalidraw/excalidraw) 的重构,采用 **Vite + Vue 3 + TypeScript** 技术栈。
4
+
5
+ ## 技术栈
6
+
7
+ - **核心框架**: Vue 3 (Composition API)
8
+ - **状态管理**: Pinia
9
+ - **绘图引擎**: RoughJS
10
+ - **构建工具**: Vite
11
+ - **图标库**: Remix Icon
12
+ - **样式**: CSS/SCSS
13
+
14
+ ## 项目结构
15
+
16
+ ```text
17
+ jvs-draw/
18
+ ├── src/
19
+ │ ├── components/ # UI 组件 (Canvas, Toolbar, Properties 等)
20
+ │ ├── core/ # 核心逻辑 (Renderer, Scene 管理)
21
+ │ ├── store/ # 状态管理 (Pinia Store)
22
+ │ ├── types/ # 类型定义
23
+ │ ├── utils/ # 工具函数 (Math, Helper)
24
+ │ └── App.vue # 主应用组件
25
+ ├── public/ # 静态资源
26
+ └── package.json # 依赖配置
27
+ ```
28
+
29
+ ## 已实现功能
30
+ - [x] Vue 3 + Vite 环境搭建
31
+ - [x] 核心渲染引擎 (Rectangle, Ellipse, Diamond, Frame, Image)
32
+ - [x] 基础 Toolbar UI (Hand, Selection, Shapes, Arrow, Line, Freedraw, Text, Image, Frame, Eraser)
33
+ - [x] 响应式画布与手势支持 (Zoom, Pan)
34
+ - [x] 元素的移动、缩放和旋转
35
+ - [x] 线条与箭头 (支持自动吸附与动态绑定)
36
+ - [x] 画框工具 (Frame) - 支持自动吸附与容器跟随
37
+ - [x] 文本编辑 (双击编辑、自动换行、动态调整)
38
+ - [x] 图片插入与渲染
39
+ - [x] 右侧属性面板 (自定义颜色、边框样式、粗细、透明度、图层、对齐)
40
+ - [x] 画布操作 (清除、锁定)
41
+ - [x] 快捷键支持
42
+
43
+ ## 环境要求
44
+
45
+ - **Node.js**: v20.0.0 或更高版本 (推荐使用 v20.19.6)
46
+ - **包管理器**: npm (或其他均可)
47
+
48
+ ## 运行项目
49
+
50
+ 1. 安装依赖:
51
+ ```bash
52
+ npm install
53
+ ```
54
+
55
+ 2. 启动开发服务器:
56
+ ```bash
57
+ npm run dev
58
+ ```
59
+
60
+ ## 后续计划
61
+
62
+ - [ ] 实现连线和箭头逻辑
63
+ - [ ] 实现自由绘制 (Freehand)
64
+ - [ ] 实现文字编辑功能
65
+ - [ ] 实现元素的移动、缩放和选择
66
+ - [ ] 完善右侧属性面板 (颜色、边框、透明度等)
@@ -0,0 +1,2 @@
1
+ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, HTMLDivElement>;
2
+ export default _default;
@@ -0,0 +1,5 @@
1
+ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {
2
+ firstPopoverRef: unknown;
3
+ inputRef: unknown;
4
+ }, HTMLDivElement>;
5
+ export default _default;
@@ -0,0 +1,18 @@
1
+ type __VLS_Props = {
2
+ visible: boolean;
3
+ x: number;
4
+ y: number;
5
+ isElementSelected: boolean;
6
+ isElementLocked?: boolean;
7
+ showGrid: boolean;
8
+ };
9
+ declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
10
+ close: (...args: any[]) => void;
11
+ action: (...args: any[]) => void;
12
+ }, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
13
+ onClose?: ((...args: any[]) => any) | undefined;
14
+ onAction?: ((...args: any[]) => any) | undefined;
15
+ }>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {
16
+ menuRef: HTMLDivElement;
17
+ }, any>;
18
+ export default _default;
@@ -0,0 +1,13 @@
1
+ import { ExcalidrawElement } from '../types/element';
2
+ type __VLS_Props = {
3
+ elements: ExcalidrawElement[];
4
+ zoom: number;
5
+ scrollX: number;
6
+ scrollY: number;
7
+ };
8
+ declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {
9
+ container: HTMLDivElement;
10
+ canvas: HTMLCanvasElement;
11
+ textInput: HTMLTextAreaElement;
12
+ }, HTMLDivElement>;
13
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, HTMLDivElement>;
2
+ export default _default;
@@ -0,0 +1,7 @@
1
+ type __VLS_Props = {
2
+ zoom: number;
3
+ scrollX: number;
4
+ scrollY: number;
5
+ };
6
+ declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
7
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, HTMLDivElement>;
2
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
2
+ export default _default;
@@ -0,0 +1,11 @@
1
+ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {
2
+ bgColorPickerRef: unknown;
3
+ bgColorRef: HTMLDivElement;
4
+ strokeColorPickerRef: unknown;
5
+ strokeColorRef: HTMLDivElement;
6
+ fontColorPickerRef: unknown;
7
+ fontColorRef: HTMLDivElement;
8
+ fontBgColorPickerRef: unknown;
9
+ fontBgColorRef: HTMLDivElement;
10
+ }, any>;
11
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, HTMLDivElement>;
2
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, HTMLDivElement>;
2
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, HTMLDivElement>;
2
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, HTMLDivElement>;
2
+ export default _default;
@@ -0,0 +1,11 @@
1
+ type __VLS_PublicProps = {
2
+ modelValue?: any;
3
+ };
4
+ declare const _default: import('vue').DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
5
+ colorChange: (...args: any[]) => void;
6
+ "update:modelValue": (value: any) => void;
7
+ }, string, import('vue').PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
8
+ onColorChange?: ((...args: any[]) => any) | undefined;
9
+ "onUpdate:modelValue"?: ((value: any) => any) | undefined;
10
+ }>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, HTMLDivElement>;
11
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, HTMLDivElement>;
2
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, HTMLDivElement>;
2
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, HTMLDivElement>;
2
+ export default _default;
@@ -0,0 +1,25 @@
1
+ import { ExcalidrawElement } from '../types/element';
2
+ export interface ElementOptions {
3
+ strokeColor?: string;
4
+ backgroundColor?: string;
5
+ fillStyle?: string;
6
+ strokeWidth?: number;
7
+ strokeStyle?: string;
8
+ roughness?: number;
9
+ opacity?: number;
10
+ strokeOpacity?: number;
11
+ backgroundOpacity?: number;
12
+ roundness?: number;
13
+ fontSize?: number;
14
+ fontFamily?: string;
15
+ fontColor?: string;
16
+ fontBgColor?: string;
17
+ textAlign?: "left" | "center" | "right";
18
+ verticalAlign?: "top" | "middle" | "bottom";
19
+ isBold?: boolean;
20
+ isItalic?: boolean;
21
+ isUnderline?: boolean;
22
+ isStrikethrough?: boolean;
23
+ arrowType?: "sharp" | "round" | "elbow";
24
+ }
25
+ export declare const newElement: (type: ExcalidrawElement["type"], x: number, y: number, options?: ElementOptions) => ExcalidrawElement;
@@ -0,0 +1,7 @@
1
+ import { ExcalidrawElement } from '../types/element';
2
+ export declare const renderScene: (canvas: HTMLCanvasElement, elements: readonly ExcalidrawElement[], scrollX: number, scrollY: number, zoom: number, selectedElementIds?: Record<string, boolean>, editingElementId?: string | null, erasingElementIds?: Record<string, boolean>, highlightedFrameId?: string | null, highlightedElementIds?: Record<string, boolean>, gridType?: string, hoveredLockIconId?: string | null, viewBackgroundColor?: string) => void;
3
+ export declare const renderLaserTrails: (canvas: HTMLCanvasElement, trails: {
4
+ x: number;
5
+ y: number;
6
+ time: number;
7
+ }[][], zoom: number, scrollX: number, scrollY: number) => void;
@@ -0,0 +1,10 @@
1
+ import { Point } from '../core';
2
+ import { PointGraph, PointNode } from './data-structures/graph';
3
+ export declare class AStar {
4
+ private graph;
5
+ cameFrom: Map<PointNode, PointNode>;
6
+ constructor(graph: PointGraph);
7
+ heuristic(a: Point, b: Point): number;
8
+ search(start: Point, end: Point, previousStart: Point): void;
9
+ getRoute(start: Point, end: Point): Point[];
10
+ }
@@ -0,0 +1,14 @@
1
+ import { Point } from '../../core';
2
+ export declare class PointNode {
3
+ data: Point;
4
+ distance: number;
5
+ adjacentNodes: PointNode[];
6
+ constructor(data: Point);
7
+ }
8
+ export declare class PointGraph {
9
+ private index;
10
+ add(p: Point): void;
11
+ connect(a: Point, b: Point): void;
12
+ has(p: Point): boolean;
13
+ get(p: Point): PointNode | null;
14
+ }
@@ -0,0 +1,2 @@
1
+ export * from './graph';
2
+ export * from './priority-queue';
@@ -0,0 +1,16 @@
1
+ import { PointNode } from './graph';
2
+ export declare class PriorityQueue {
3
+ list: {
4
+ node: PointNode;
5
+ priority: number;
6
+ }[];
7
+ constructor();
8
+ enqueue(item: {
9
+ node: PointNode;
10
+ priority: number;
11
+ }): void;
12
+ dequeue(): {
13
+ node: PointNode;
14
+ priority: number;
15
+ } | undefined;
16
+ }
@@ -0,0 +1,2 @@
1
+ export * from './a-star';
2
+ export * from './data-structures';
@@ -0,0 +1,10 @@
1
+ export declare const BASE = 4;
2
+ export declare const PRIMARY_COLOR = "#6698FF";
3
+ export declare const RESIZE_HANDLE_DIAMETER = 9;
4
+ export declare const WithTextPluginKey = "plait-text-plugin-key";
5
+ export declare const DEFAULT_ROUTE_MARGIN = 30;
6
+ export declare const TRANSPARENT = "transparent";
7
+ export declare const ROTATE_HANDLE_DISTANCE_TO_ELEMENT = 4;
8
+ export declare const ROTATE_HANDLE_SIZE = 18;
9
+ export declare const DEFAULT_FONT_FAMILY = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'PingFang SC', 'Noto Sans', 'Noto Sans CJK SC', 'Microsoft Yahei', 'Hiragino Sans GB', Arial, sans-serif";
10
+ export declare const DEFAULT_FILL = "none";
@@ -0,0 +1,4 @@
1
+ export * from './default';
2
+ export * from './media';
3
+ export * from './resize';
4
+ export * from './property';
@@ -0,0 +1,6 @@
1
+ export declare enum MediaKeys {
2
+ image = "image"
3
+ }
4
+ export declare const PICTURE_ACCEPTED_UPLOAD_SIZE = 20;
5
+ export declare const acceptImageTypes: string[];
6
+ export declare const WithCommonPluginKey = "plait-common-plugin-key";
@@ -0,0 +1,5 @@
1
+ export declare enum StrokeStyle {
2
+ solid = "solid",
3
+ dashed = "dashed",
4
+ dotted = "dotted"
5
+ }
@@ -0,0 +1,10 @@
1
+ export declare enum ResizeHandle {
2
+ nw = "0",
3
+ n = "4",
4
+ ne = "1",
5
+ e = "5",
6
+ se = "2",
7
+ s = "6",
8
+ sw = "3",
9
+ w = "7"
10
+ }
@@ -0,0 +1,37 @@
1
+ export type Point = [number, number];
2
+ export type Vector = [number, number];
3
+ export declare enum Direction {
4
+ top = "top",
5
+ bottom = "bottom",
6
+ left = "left",
7
+ right = "right"
8
+ }
9
+ export interface PlaitBoard {
10
+ }
11
+ export interface DebugGenerator {
12
+ clear(): void;
13
+ drawRectangle(board: PlaitBoard, rect: RectangleClient): void;
14
+ drawLine(board: PlaitBoard, points: Point[]): void;
15
+ }
16
+ export declare const createDebugGenerator: (name: string) => DebugGenerator;
17
+ export declare const distanceBetweenPointAndPoint: (x1: number, y1: number, x2: number, y2: number) => number;
18
+ export declare class RectangleClient {
19
+ x: number;
20
+ y: number;
21
+ width: number;
22
+ height: number;
23
+ constructor(x: number, y: number, width: number, height: number);
24
+ static getRectangleByPoints(points: Point[]): RectangleClient;
25
+ static getCornerPointsByPoints(points: Point[]): Point[];
26
+ static isHit(rect1: RectangleClient, rect2: RectangleClient): boolean;
27
+ static isHitX(rect1: RectangleClient, rect2: RectangleClient): boolean;
28
+ static isHitY(rect1: RectangleClient, rect2: RectangleClient): boolean;
29
+ static isPointInRectangle(rect: RectangleClient, point: Point): boolean;
30
+ static getGapCenter(rect1: RectangleClient, rect2: RectangleClient, isHorizontal: boolean): number;
31
+ static expand(rect: RectangleClient, left: number, top: number, right: number, bottom: number): RectangleClient;
32
+ }
33
+ export declare namespace Point {
34
+ function isEquals(p1: Point, p2: Point): boolean;
35
+ function isOverHorizontal(points: Point[]): boolean;
36
+ function isOverVertical(points: Point[]): boolean;
37
+ }
@@ -0,0 +1,38 @@
1
+ import { Direction, PlaitBoard, Point, RectangleClient } from '../core';
2
+ import { PointGraph } from '../algorithms';
3
+ export interface ElbowLineRouteOptions {
4
+ sourcePoint: Point;
5
+ nextSourcePoint: Point;
6
+ sourceRectangle: RectangleClient;
7
+ sourceOuterRectangle: RectangleClient;
8
+ targetPoint: Point;
9
+ nextTargetPoint: Point;
10
+ targetOuterRectangle: RectangleClient;
11
+ targetRectangle: RectangleClient;
12
+ }
13
+ export interface RouteAdjustOptions {
14
+ centerX?: number;
15
+ centerY?: number;
16
+ sourceRectangle: RectangleClient;
17
+ targetRectangle: RectangleClient;
18
+ }
19
+ export interface AdjustOptions {
20
+ parallelPaths: [Point, Point][];
21
+ pointOfHit: Point;
22
+ sourceRectangle: RectangleClient;
23
+ targetRectangle: RectangleClient;
24
+ }
25
+ export declare const generateElbowLineRoute: (options: ElbowLineRouteOptions, board?: PlaitBoard) => Point[];
26
+ export declare const routeAdjust: (path: Point[], options: RouteAdjustOptions, board?: PlaitBoard) => Point[];
27
+ export declare const getGraphPoints: (options: ElbowLineRouteOptions) => Point[];
28
+ export declare const createGraph: (points: Point[]) => PointGraph;
29
+ export declare const reduceRouteMargin: (sourceRectangle: RectangleClient, targetRectangle: RectangleClient) => {
30
+ sourceOffset: number[];
31
+ targetOffset: number[];
32
+ };
33
+ export declare const getNextPoint: (point: Point, outerRectangle: RectangleClient, direction: Direction) => Point;
34
+ export declare const getSourceAndTargetOuterRectangle: (sourceRectangle: RectangleClient, targetRectangle: RectangleClient) => {
35
+ sourceOuterRectangle: RectangleClient;
36
+ targetOuterRectangle: RectangleClient;
37
+ };
38
+ export declare const isSourceAndTargetIntersect: (options: ElbowLineRouteOptions) => boolean;
@@ -0,0 +1,4 @@
1
+ export * from './line-path';
2
+ export * from './elbow-line-route';
3
+ export * from './vector';
4
+ export * from './math';
@@ -0,0 +1,7 @@
1
+ import { Point } from '../core';
2
+ export declare function getPointOnPolyline(points: Point[], ratio: number): any[] | undefined;
3
+ export declare function calculatePolylineLength(points: Point[]): number;
4
+ export declare function getRatioByPoint(points: Point[], point: Point): number;
5
+ export declare const removeDuplicatePoints: (points: Point[]) => Point[];
6
+ export declare function simplifyOrthogonalPoints(points: Point[]): Point[];
7
+ export declare const getExtendPoint: (source: Point, target: Point, extendDistance: number) => Point;
@@ -0,0 +1,3 @@
1
+ import { Point } from '../core';
2
+ export declare function isPointOnSegment(point: Point, startPoint: Point, endPoint: Point): boolean;
3
+ export declare const getCrossingPointsBetweenPointAndSegment: (point: Point, startPoint: Point, endPoint: Point) => Point[];
@@ -0,0 +1,6 @@
1
+ import { Point, Vector } from '../core';
2
+ export declare function getUnitVectorByPointAndPoint(point1: Point, point2: Point): Point;
3
+ export declare function getPointByVectorComponent(point: Point, vector: Vector, component: number): Point;
4
+ export declare function getPointByVectorDirectionComponent(point: Point, unitVector: Vector, directionComponent: number, isHorizontal: boolean): Point;
5
+ export declare function rotateVectorAnti90(vector: Vector): Vector;
6
+ export declare function rotateVector(vector: Vector, angle: number): Vector;
@@ -0,0 +1,3 @@
1
+ import { default as JvsDraw } from './App.vue';
2
+ export { JvsDraw };
3
+ export default JvsDraw;
@@ -0,0 +1 @@
1
+ .toolbar[data-v-b6ea5f5c]{position:absolute;top:50%;left:24px;transform:translateY(-50%);display:flex;flex-direction:column;background:var(--color-bg-panel);padding:8px 4px;box-sizing:border-box;border-radius:var(--radius-md);box-shadow:0 0 15px #363b4c1a;z-index:100;align-items:center;width:56px}.tool-group[data-v-b6ea5f5c]{display:flex;flex-direction:column;gap:8px}.tool-group .tool-item[data-v-b6ea5f5c]{height:36px;width:36px;cursor:pointer;display:flex;align-items:center;justify-content:center;border-radius:4px;position:relative}.tool-group .tool-item[data-v-b6ea5f5c]:hover{background:#eeeff0}.tool-group .tool-item .svg-icon[data-v-b6ea5f5c]{width:20px;height:20px}.tool-group .active[data-v-b6ea5f5c]{background:#1e6fff1f;color:var(--color-primary)}.tool-group .active[data-v-b6ea5f5c]:hover{background:#1e6fff1f}.tool-group .trand-line[data-v-b6ea5f5c]{height:1px;background:#e4e7eb;width:36px;cursor:default}.tool-group .trand-line[data-v-b6ea5f5c]:hover{background:#e4e7eb}.separator[data-v-b6ea5f5c]{width:20px;height:1px;background:var(--color-border);margin:8px 0}button[data-v-b6ea5f5c]{display:flex;align-items:center;justify-content:center;width:36px;height:36px;border-radius:var(--radius-sm);color:var(--color-text);position:relative;outline:none}button i[data-v-b6ea5f5c]{font-size:16px;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.shortcut[data-v-b6ea5f5c]{position:absolute;bottom:2px;right:2px;font-size:8px;font-weight:500;opacity:.6}button.destructive[data-v-b6ea5f5c]:hover{background:#fff0f0;color:#ff4d4f}.sub-tools-container[data-v-b6ea5f5c]{display:grid;grid-template-columns:repeat(5,1fr);gap:6px}.sub-tools-container .sub-tool-item[data-v-b6ea5f5c]{display:flex;justify-content:space-between;align-items:center;border-radius:4px;justify-content:center;font-size:13px;color:var(--color-text);transition:all .2s;cursor:pointer;width:36px;height:36px}.sub-tools-container .sub-tool-item .svg-icon[data-v-b6ea5f5c]{width:24px;height:24px;min-width:24px}.sub-tools-container .sub-tool-item[data-v-b6ea5f5c]:hover{background:var(--color-bg-hover, #f0f2f5)}.sub-tools-container .sub-tool-item.active[data-v-b6ea5f5c]{background:#1e6fff1f;color:var(--color-primary, #1e6fff)}.sub-tools-container .sub-tool-item .sub-tool-label[data-v-b6ea5f5c]{font-weight:500}.sub-tools-container .sub-tool-item .sub-tool-shortcut[data-v-b6ea5f5c]{font-size:12px;color:var(--color-text-secondary, #8c909e)}.fill-style-box[data-v-9356d2e1]{height:32px;min-height:32px;background:#f5f6f7;border-radius:4px;display:grid;padding:3px 8px;box-sizing:border-box;grid-template-columns:repeat(3,1fr);grid-column-gap:8px}.fill-style-box .fill-style-item[data-v-9356d2e1]{display:flex;align-items:center;justify-content:center;cursor:pointer;border-radius:4px;height:26px}.fill-style-box .fill-style-item .svg-icon[data-v-9356d2e1]{min-width:24px;width:24px;height:16px}.fill-style-box .active[data-v-9356d2e1]{background:#fff}.fill-style-box[data-v-dcf857c8]{height:32px;min-height:32px;background:#f5f6f7;border-radius:4px;display:grid;padding:3px 8px;box-sizing:border-box;grid-template-columns:repeat(3,1fr);grid-column-gap:8px}.fill-style-box .fill-style-item[data-v-dcf857c8]{display:flex;align-items:center;justify-content:center;cursor:pointer;border-radius:4px;height:26px}.fill-style-box .fill-style-item .svg-icon[data-v-dcf857c8]{min-width:24px;width:24px;height:16px}.fill-style-box .active[data-v-dcf857c8]{background:#fff}.fill-style-box[data-v-794ba895]{height:32px;min-height:32px;background:#f5f6f7;border-radius:4px;display:grid;padding:3px 8px;box-sizing:border-box;grid-template-columns:repeat(3,1fr);grid-column-gap:8px}.fill-style-box .fill-style-item[data-v-794ba895]{display:flex;align-items:center;justify-content:center;cursor:pointer;border-radius:4px;height:26px}.fill-style-box .fill-style-item .svg-icon[data-v-794ba895]{min-width:24px;width:24px;height:16px}.fill-style-box .active[data-v-794ba895]{background:#fff}.fill-style-box[data-v-36600f47]{height:32px;min-height:32px;background:#f5f6f7;border-radius:4px;display:grid;padding:3px 8px;box-sizing:border-box;grid-template-columns:repeat(4,1fr);grid-column-gap:8px}.fill-style-box .fill-style-item[data-v-36600f47]{display:flex;align-items:center;justify-content:center;cursor:pointer;border-radius:4px;height:26px}.fill-style-box .fill-style-item .svg-icon[data-v-36600f47]{min-width:16px;width:16px;height:16px}.fill-style-box .active[data-v-36600f47]{background:#fff}.fill-style-box[data-v-b408686c]{height:32px;min-height:32px;background:#f5f6f7;border-radius:4px;display:grid;padding:3px 8px;box-sizing:border-box;grid-template-columns:repeat(6,1fr);grid-column-gap:8px}.fill-style-box .fill-style-item[data-v-b408686c]{display:flex;align-items:center;justify-content:center;cursor:pointer;border-radius:4px;height:26px}.fill-style-box .fill-style-item .svg-icon[data-v-b408686c]{min-width:16px;width:16px;height:16px}.fill-style-box .active[data-v-b408686c]{background:#fff}.no-vertical-align[data-v-b408686c]{grid-template-columns:repeat(3,1fr)}.fill-style-box[data-v-b56fafad]{height:32px;min-height:32px;background:#f5f6f7;border-radius:4px;display:grid;padding:3px 8px;box-sizing:border-box;grid-template-columns:repeat(2,1fr);grid-column-gap:8px}.fill-style-box .fill-style-item[data-v-b56fafad]{display:flex;align-items:center;justify-content:center;cursor:pointer;border-radius:4px;height:26px}.fill-style-box .fill-style-item .svg-icon[data-v-b56fafad]{min-width:24px;width:24px;height:16px}.fill-style-box .active[data-v-b56fafad]{background:#fff}.properties-panel[data-v-00889af3]{position:absolute;top:0;right:0;bottom:0;width:320px;box-sizing:border-box;background:#fff;border-left:1px solid var(--color-border);box-shadow:var(--shadow-lg);z-index:90;display:flex;flex-direction:column;gap:16px;overflow-y:auto;border-radius:0}.properties-panel .el-slider[data-v-00889af3]{--el-slider-button-size: 16px;width:100%}.properties-panel .el-slider[data-v-00889af3] .el-input__wrapper,.properties-panel .el-slider[data-v-00889af3] .el-select__wrapper,.properties-panel .el-slider[data-v-00889af3] .el-textarea__inner{background:#f5f6f7;box-shadow:none}.properties-panel .el-slider[data-v-00889af3] .el-slider__button{border-width:1px}.properties-panel .el-slider[data-v-00889af3] .el-slider__runway.show-input{margin-right:20px}.properties-panel .el-slider[data-v-00889af3] .el-input__wrapper{padding-left:8px;padding-right:8px}.properties-panel .el-slider[data-v-00889af3] .el-input__wrapper .el-input__inner{text-align:left}.properties-panel .el-slider[data-v-00889af3] .el-slider__input{width:56px}.header[data-v-00889af3]{display:flex;justify-content:space-between;align-items:center;padding:16px 16px 0}.header div[data-v-00889af3]{font-size:14px;color:#363b4c;font-weight:600}.header svg[data-v-00889af3]{width:14px;height:14px;fill:#363b4c;cursor:pointer}.content[data-v-00889af3]{display:flex;flex-direction:column;gap:16px;padding-top:2px}.shape-panel-box[data-v-00889af3]{display:flex;gap:8px;align-items:center;padding:0 16px;box-sizing:border-box}.shape-panel-row[data-v-00889af3]{display:grid;grid-template-columns:repeat(2,1fr);gap:8px}.shape-panel-row[data-v-00889af3] .el-input-number__decrease,.shape-panel-row[data-v-00889af3] .el-input-number__increase{display:none!important}.shape-panel-row .input-box[data-v-00889af3]{width:120px;display:flex;align-items:center;font-weight:400;font-size:12px;color:#6f7588;gap:8px;background:#f5f6f7;border-radius:4px;padding:0 8px;box-sizing:border-box;position:relative}.shape-panel-row .input-box .unit[data-v-00889af3]{font-size:16px;position:absolute;left:32px;top:50%;transform:translateY(-60%)}.shape-panel-row .input-box .icon[data-v-00889af3]{width:16px;font-size:14px;display:flex;align-items:center;justify-content:center}.shape-panel-row .input-box .svg-icon[data-v-00889af3]{width:16px;height:16px;min-width:16px}.shape-panel-row .input-box .el-input-number[data-v-00889af3]{width:100%}.shape-panel-row .input-box .el-input-number[data-v-00889af3] .el-input__wrapper{padding:0!important;box-shadow:none!important;background:transparent!important}.shape-panel-row .input-box .el-input-number[data-v-00889af3] .el-input__wrapper .el-input__inner{text-align:left}.shape-panel-row .btn-box[data-v-00889af3]{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;cursor:pointer}.shape-panel-row .btn-box .btn-item[data-v-00889af3]{display:flex;align-items:center;justify-content:center;cursor:pointer;width:100%;background:#f5f6f7;border-radius:4px}.shape-panel-row .btn-box .btn-item .svg-icon[data-v-00889af3]{width:16px;height:16px;min-width:16px}.shape-panel-row .input-box[data-v-00889af3]:has(.is-focus){box-shadow:0 0 0 1px #1e6fff}.not-bind-width-height .input-box[data-v-00889af3]{width:100%}.bind-box[data-v-00889af3]{width:32px;height:32px;cursor:pointer;background:#f5f6f7;border-radius:4px;display:flex;align-items:center;justify-content:center}.bind-box .svg-icon[data-v-00889af3]{width:16px;height:16px;min-width:16px}.bind-box.active[data-v-00889af3]{background-color:#1e6fff!important}.roundness-box[data-v-00889af3]{display:flex;align-items:center;gap:8px;padding:0 16px}.roundness-box .btn-box[data-v-00889af3]{cursor:pointer;width:32px;min-width:32px;height:32px;background:#f5f6f7;border-radius:4px;display:flex;align-items:center;justify-content:center}.roundness-box .btn-box .svg-icon[data-v-00889af3]{width:16px;height:16px;min-width:16px}.item-label[data-v-00889af3]{font-weight:400;font-size:14px;color:#6f7588;word-break:keep-all}.divider[data-v-00889af3]{width:100%;height:1px;background:#eeeff0}.type-item-title[data-v-00889af3]{padding:0 16px}.type-item-title .title[data-v-00889af3]{font-weight:700;font-size:14px}.style-item[data-v-00889af3]{display:flex;gap:8px;padding:0 16px}.style-item .title[data-v-00889af3]{width:80px;min-width:80px;font-weight:400;font-size:14px;color:#6f7588;height:32px;line-height:32px}.style-item .style-boxs[data-v-00889af3]{width:100%;gap:8px;display:grid}.style-item .style-boxs .style-boxs-item[data-v-00889af3]{display:grid;grid-template-columns:repeat(2,1fr);gap:8px}.style-item .style-boxs .style-boxs-item[data-v-00889af3] .el-select__wrapper{box-shadow:none!important;background:#f5f6f7!important;padding:0 8px}.style-item .style-boxs .style-boxs-item[data-v-00889af3] .el-select__wrapper:hover{background:#f5f6f7}.style-item .style-boxs .style-boxs-item[data-v-00889af3] .is-focused{box-shadow:0 0 0 1px #1e6fff!important}.style-item .style-boxs .style-boxs-item[data-v-00889af3] .el-input__inner{color:#666;font-size:14px;text-align:center}.style-item .style-boxs .style-boxs-item[data-v-00889af3] .el-input__wrapper{background:#f5f6f7;box-shadow:none}.style-item .style-boxs .style-boxs-item[data-v-00889af3] .el-input__wrapper .el-input__inner{text-align:left!important}.style-item .style-boxs .style-boxs-item[data-v-00889af3] .el-input__wrapper.is-focus{box-shadow:0 0 0 1px #1e6fff!important}.style-item .input-value-box[data-v-00889af3]{background:#f5f6f7;border-radius:4px;width:100%;height:32px;display:flex;align-items:center;padding:0 8px}.style-item .input-value-box .svg-icon[data-v-00889af3]{width:20px;height:20px;border-radius:4px}.style-item .input-value-box .color-box[data-v-00889af3]{width:19px;height:19px;border-radius:4px;border:1px solid #eeeff0}.style-item .input-value-box .color-text[data-v-00889af3]{height:20px;display:flex;align-items:center;margin-left:8px}.context-menu[data-v-22c72497]{position:fixed;z-index:1000;background:#fff;border-radius:4px;box-shadow:0 2px 10px #0003;padding:4px 0;min-width:200px;font-family:sans-serif;font-size:14px;color:#333}.context-menu ul[data-v-22c72497]{list-style:none;margin:0;padding:0}.context-menu li[data-v-22c72497]{padding:8px 16px;cursor:pointer;display:flex;justify-content:space-between;align-items:center}.context-menu li[data-v-22c72497]:hover{background-color:#f0f0f0}.divider[data-v-22c72497]{height:1px;background-color:#e0e0e0;margin:8px 0}.delete[data-v-22c72497]{color:red}.shortcut[data-v-22c72497]{font-size:12px;color:#999;margin-left:16px}.excalidraw-container[data-v-b4198a57]{width:100%;height:100%;overflow:hidden;background-color:#fff;background-image:linear-gradient(rgba(0,0,0,.03) 1px,transparent 1px),linear-gradient(90px,rgba(0,0,0,.03) 1px,transparent 1px);background-size:20px 20px}canvas[data-v-b4198a57]{display:block}.text-editor[data-v-b4198a57]{background:transparent;border:none;padding:4px;box-sizing:border-box;margin:0;resize:none;outline:none;overflow:hidden;z-index:50;font-family:Virgil,sans-serif;white-space:pre-wrap;word-break:break-all;overflow-wrap:break-word;width:100%;text-align:center;vertical-align:center}.excalidraw-textContainer[data-v-b4198a57]{display:flex;align-items:center;position:absolute;background:transparent!important;cursor:text}.color-box[data-v-9b04b94a]{display:grid;grid-template-columns:repeat(8,1fr);grid-gap:6px}.color-box .color-item[data-v-9b04b94a]{width:20px;height:20px;border-radius:4px;cursor:pointer;border:1px solid #EEEFF0;position:relative}.color-box .active[data-v-9b04b94a]:after{content:"";position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:calc(100% + 2px);height:calc(100% + 2px);border-radius:6px;border:2px solid #B7D1FF}.color-box .trans-icon[data-v-9b04b94a]{width:22px;height:22px;position:relative;cursor:pointer}.color-box .trans-icon .svg-icon[data-v-9b04b94a]{width:22px;height:22px}.color-box .color-item-add[data-v-9b04b94a]{width:20px;height:20px;border-radius:4px;cursor:pointer;border:1px solid transparent}.color-box .color-item-add .svg-icon[data-v-9b04b94a]{width:20px;height:20px}.board-name-container[data-v-772e3fac]{position:absolute;top:24px;left:24px;height:44px;z-index:101;background:#ffffffe6;border-radius:4px;display:flex;align-items:center;box-shadow:0 2px 8px #363b4c26;padding:0 16px}.board-name-input[data-v-772e3fac]{height:44px;font-size:16px;color:#363b4c;border:none;padding:0 12px;font-family:inherit;outline:none;min-width:200px}.board-name-input[data-v-772e3fac] .el-input__wrapper{box-shadow:none;background-color:transparent!important}.board-name-input[data-v-772e3fac]:focus{box-shadow:0 2px 8px #363b4c40}.action-item[data-v-772e3fac]{width:32px;height:32px;min-width:32px;border-radius:4px;cursor:pointer;display:flex;align-items:center;justify-content:center}.action-item[data-v-772e3fac]:hover{background:#f5f6f7}.svg-icon[data-v-772e3fac]{width:20px;height:20px;cursor:pointer}.board-settings-popover,.preferences-popover{padding:8px 0!important}.menu-list[data-v-772e3fac]{display:flex;flex-direction:column}.menu-item[data-v-772e3fac]{height:36px;display:flex;align-items:center;justify-content:space-between;margin:0 8px;padding:0 8px;cursor:pointer;color:#363b4c;font-size:14px;transition:background .2s;border-radius:4px}.menu-item[data-v-772e3fac]:hover{background:#f5f6f7}.menu-item-content[data-v-772e3fac]{display:flex;align-items:center;gap:8px}.menu-icon[data-v-772e3fac]{font-size:16px;width:16px;height:16px;color:#8c909e}.menu-arrow[data-v-772e3fac]{font-size:16px;color:#8c909e}.preferences-list[data-v-772e3fac]{display:flex;flex-direction:column;gap:8px}.preferences-list .line[data-v-772e3fac]{width:100%;height:1px;background:#eeeff0}.preferences-list .grid-item[data-v-772e3fac]{display:flex;align-items:center;padding:8px 16px;gap:8px;cursor:pointer;border-radius:4px}.preferences-list .grid-item .svg-icon[data-v-772e3fac]{width:16px;height:16px}.preferences-list .grid-item[data-v-772e3fac]:hover{background:#f5f6f7}.preferences-list .active[data-v-772e3fac]{background:#d2e2ff!important;color:#1e6fff}.preference-item[data-v-772e3fac]{display:flex;align-items:center;justify-content:space-between;padding:8px 16px}.preference-info[data-v-772e3fac]{display:flex;flex-direction:column;gap:4px;width:100%}.preference-title[data-v-772e3fac]{font-size:14px;color:#363b4c;display:flex;align-items:center;justify-content:space-between}.preference-desc[data-v-772e3fac]{font-size:12px;color:#8c909e}.board-settings-popover{transform:translate(-10px)!important}.preferences-popover{transform:translate(6px)}.sub-preferences-popover{padding:0!important;transform:translate(6px)}.sub-preferences-popover .preferences-list{padding:16px!important;gap:8px}.sub-preferences-popover .preferences-list .title{font-weight:400;font-size:14px;color:#6f7588}.sub-preferences-popover .grid-list{padding:8px!important}.footer-controls[data-v-06dd6954]{position:absolute;bottom:24px;right:24px;display:flex;gap:12px;z-index:100;transition:right .3s ease}.zoom-controls[data-v-06dd6954],.history-controls[data-v-06dd6954],.help-controls[data-v-06dd6954]{display:flex;align-items:center;padding:4px;box-sizing:border-box;border-radius:var(--radius-md);box-shadow:var(--shadow-md);height:44px;gap:4px;border:0px}.tool-item[data-v-06dd6954]{cursor:pointer;width:28px;height:28px;display:flex;align-items:center;justify-content:center;border-radius:var(--radius-sm)}.tool-item[data-v-06dd6954]:hover{background:#f5f6f7}.tool-item .svg-icon[data-v-06dd6954]{width:24px;height:24px}.disabled[data-v-06dd6954]{cursor:not-allowed}.disabled[data-v-06dd6954]:hover{background-color:transparent}.separator[data-v-06dd6954]{width:1px;height:20px;background:var(--color-border);margin:0 4px}button[data-v-06dd6954]{width:28px;height:28px;display:flex;align-items:center;justify-content:center;border-radius:var(--radius-sm);color:var(--color-text);font-size:16px;border:none;background:transparent;cursor:pointer}button[data-v-06dd6954]:hover:not(:disabled){background:#0000000d}button[data-v-06dd6954]:disabled{opacity:.3;cursor:not-allowed}.active[data-v-06dd6954]{background:#1e6fff1f!important;color:var(--color-primary)}button.destructive[data-v-06dd6954]:hover{background:#fff0f0;color:#ff4d4f}span[data-v-06dd6954]{font-size:12px;font-weight:500;color:var(--color-text);min-width:40px;text-align:center;-webkit-user-select:none;user-select:none}.modal-overlay[data-v-0caac542]{position:fixed;top:0;left:0;width:100vw;height:100vh;background:#0006;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);z-index:1000;display:flex;align-items:center;justify-content:center}.help-dialog[data-v-0caac542]{width:800px;max-width:90vw;max-height:85vh;background:#fff;border-radius:var(--radius-lg);box-shadow:var(--shadow-xl);display:flex;flex-direction:column;overflow:hidden}.header[data-v-0caac542]{padding:16px 24px;border-bottom:1px solid rgba(0,0,0,.1);display:flex;justify-content:space-between;align-items:center}.header h2[data-v-0caac542]{margin:0;font-size:18px;font-weight:600}.close-btn[data-v-0caac542]{background:none;border:none;font-size:24px;cursor:pointer;color:var(--color-text-muted);width:32px;height:32px;display:flex;align-items:center;justify-content:center;border-radius:var(--radius-sm)}.close-btn[data-v-0caac542]:hover{background:#0000000d;color:var(--color-text)}.content[data-v-0caac542]{padding:24px;overflow-y:auto;display:grid;grid-template-columns:1fr 1fr;gap:40px}.column[data-v-0caac542]{display:flex;flex-direction:column;gap:24px}h3[data-v-0caac542]{margin:0;font-size:14px;font-weight:600;color:var(--color-text-muted);text-transform:uppercase}.shortcut-list[data-v-0caac542]{display:flex;flex-direction:column;gap:8px}.item[data-v-0caac542]{display:flex;justify-content:space-between;align-items:center;font-size:14px;color:var(--color-text);padding:4px 0}.keys[data-v-0caac542]{display:flex;gap:4px;align-items:center}kbd[data-v-0caac542]{background:#f8f9fa;border:1px solid #dee2e6;border-radius:4px;padding:2px 6px;font-family:monospace;font-size:12px;box-shadow:0 1px #0000001a;min-width:20px;text-align:center}.popovers-container[data-v-1fe4c384]{display:flex;align-items:center;gap:4px}.settings-btn[data-v-1fe4c384]{background:transparent;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#666;width:32px;height:32px;transition:all .2s;border-radius:4px}.settings-btn .svg-icon[data-v-1fe4c384]{width:20px;height:20px;border-radius:4px}.settings-btn .color-box[data-v-1fe4c384]{width:19px;height:19px;border-radius:4px;border:1px solid #eeeff0}.settings-btn .border-box[data-v-1fe4c384]{width:16px;height:16px;border-radius:4px;border:2px solid #eeeff0}.settings-btn[data-v-1fe4c384]:hover{background:#f5f6f7;color:var(--color-primary, #4b9fff)}.popover-content[data-v-1fe4c384]{display:flex;flex-direction:column;gap:8px}.popover-content .title[data-v-1fe4c384]{font-weight:400;font-size:14px;color:#6f7588;display:flex;align-items:center;justify-content:space-between}.popover-content .el-slider[data-v-1fe4c384]{--el-slider-button-size: 16px;width:100%}.popover-content .el-slider[data-v-1fe4c384] .el-input__wrapper,.popover-content .el-slider[data-v-1fe4c384] .el-select__wrapper,.popover-content .el-slider[data-v-1fe4c384] .el-textarea__inner{background:#f5f6f7;box-shadow:none}.popover-content .el-slider[data-v-1fe4c384] .el-slider__button{border-width:1px}.popover-content .el-slider[data-v-1fe4c384] .el-slider__runway.show-input{margin-right:20px}.popover-content .el-slider[data-v-1fe4c384] .el-input__wrapper{padding-left:8px;padding-right:8px}.popover-content .el-slider[data-v-1fe4c384] .el-input__wrapper .el-input__inner{text-align:left}.popover-content .el-slider[data-v-1fe4c384] .el-slider__input{width:56px}.separator[data-v-1fe4c384]{height:12px}.separator-horizontal[data-v-1fe4c384]{width:100%;height:1px;background-color:var(--color-border, #e0e0e0);margin:4px 0}.popover-title[data-v-1fe4c384]{font-size:12px;font-weight:500;color:var(--color-text, #333)}.font-size-select[data-v-1fe4c384]{display:flex;align-items:center;margin:0 4px}.font-size-select[data-v-1fe4c384] .el-select__wrapper{box-shadow:none!important;background:#f5f6f7!important;padding:0 8px}.font-size-select[data-v-1fe4c384] .el-select__wrapper:hover{background:#f5f6f7}.font-size-select[data-v-1fe4c384] .is-focused{box-shadow:0 0 0 1px #1e6fff!important}.font-size-select[data-v-1fe4c384] .el-input__inner{color:#666;font-size:14px;text-align:center}.button-group[data-v-1fe4c384]{display:flex;gap:4px}.button-group button[data-v-1fe4c384]{flex:1;height:32px;display:flex;align-items:center;justify-content:center;border-radius:4px;background:#0000000a;color:var(--color-text, #333);border:1px solid transparent;cursor:pointer;transition:all .2s}.button-group button[data-v-1fe4c384]:hover{background:#f5f6f7}.button-group button.active[data-v-1fe4c384]{background:#f5f6f7;color:var(--color-primary, #4b9fff)}.button-group button i[data-v-1fe4c384]{font-size:18px}.divider[data-v-1fe4c384]{width:1px;height:20px;background:#e4e7eb}.sub-tools-container[data-v-1fe4c384]{display:grid;grid-template-columns:repeat(5,1fr);gap:6px}.sub-tools-container .sub-tool-item[data-v-1fe4c384]{display:flex;justify-content:space-between;align-items:center;border-radius:4px;justify-content:center;font-size:13px;color:var(--color-text);transition:all .2s;cursor:pointer;width:36px;height:36px}.sub-tools-container .sub-tool-item .svg-icon[data-v-1fe4c384]{width:24px;height:24px;min-width:24px}.sub-tools-container .sub-tool-item[data-v-1fe4c384]:hover{background:var(--color-bg-hover, #f0f2f5)}.sub-tools-container .sub-tool-item.active[data-v-1fe4c384]{background:#1e6fff1f;color:var(--color-primary, #1e6fff)}.sub-tools-container .sub-tool-item .sub-tool-label[data-v-1fe4c384]{font-weight:500}.sub-tools-container .sub-tool-item .sub-tool-shortcut[data-v-1fe4c384]{font-size:12px;color:var(--color-text-secondary, #8c909e)}.custom-toolbar-popover{border-radius:4px!important;padding:16px!important;box-shadow:0 4px 20px #00000026!important;border:1px solid rgba(0,0,0,.1)!important}.floating-toolbar[data-v-206d2632]{position:absolute;height:40px;background:#ffffffe6;border-radius:8px;box-shadow:0 4px 12px #00000026;display:flex;align-items:center;padding:0 8px;gap:8px;z-index:100;border:1px solid var(--color-border);-webkit-user-select:none;user-select:none}.drag-handle[data-v-206d2632]{cursor:grab;display:flex;align-items:center;justify-content:center;color:#666;width:24px;height:24px;border-radius:4px}.drag-handle .svg-icon[data-v-206d2632]{width:16px;height:16px}.drag-handle[data-v-206d2632]:active{cursor:grabbing}.divider[data-v-206d2632]{width:1px;height:20px;background:#e4e7eb}.settings-btn[data-v-206d2632]{background:transparent;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#666;width:32px;height:32px;border-radius:4px;transition:all .2s}.settings-btn .svg-icon[data-v-206d2632]{width:20px;height:20px}.settings-btn[data-v-206d2632]:hover{background:#0000000d;color:var(--color-primary)}.settings-btn i[data-v-206d2632]{font-size:18px}body{margin:0;padding:0;overflow:hidden;font-family:Inter,sans-serif}.app-container{width:100vw;height:100vh;position:relative}