From ea97097b2c09e3fd89753437d4c7c95702ac2c7d Mon Sep 17 00:00:00 2001 From: huanhuanwa <44698191+huanhuanwa@users.noreply.github.com> Date: Tue, 14 May 2024 11:23:31 +0800 Subject: [PATCH] feat(draw): init table render (#864) --- .changeset/tiny-vans-act.md | 5 + packages/draw/src/constants/geometry.ts | 4 +- .../draw/src/engines/basic-shapes/cloud.ts | 9 +- .../draw/src/engines/basic-shapes/diamond.ts | 2 +- .../src/engines/basic-shapes/parallelogram.ts | 2 +- .../src/engines/basic-shapes/round-comment.ts | 2 +- .../src/engines/basic-shapes/trapezoid.ts | 2 +- .../draw/src/engines/basic-shapes/triangle.ts | 2 +- .../draw/src/engines/flowchart/database.ts | 3 +- .../draw/src/engines/flowchart/hard-disk.ts | 2 +- .../src/engines/flowchart/internal-storage.ts | 2 +- .../src/engines/flowchart/manual-input.ts | 2 +- .../draw/src/engines/flowchart/manual-loop.ts | 2 +- .../src/engines/flowchart/note-curly-left.ts | 2 +- .../src/engines/flowchart/note-curly-right.ts | 2 +- .../draw/src/engines/flowchart/off-page.ts | 2 +- packages/draw/src/engines/index.ts | 26 +- packages/draw/src/engines/table/table.ts | 53 +++-- .../generators/geometry-shape.generator.ts | 4 +- .../src/generators/line-active.generator.ts | 4 +- .../draw/src/generators/table.generator.ts | 28 +++ .../draw/src/generators/text.generator.ts | 121 ++++++++++ packages/draw/src/geometry.component.ts | 2 +- packages/draw/src/interfaces/engine.ts | 21 ++ packages/draw/src/interfaces/geometry.ts | 18 +- packages/draw/src/interfaces/index.ts | 10 +- packages/draw/src/interfaces/table.ts | 17 ++ packages/draw/src/plugins/with-table.ts | 59 ++++- packages/draw/src/table.component.ts | 59 ++++- packages/draw/src/utils/common.ts | 24 ++ packages/draw/src/utils/geometry.ts | 18 +- packages/draw/src/utils/hit.ts | 7 +- packages/draw/src/utils/index.ts | 1 + packages/draw/src/utils/line/elbow.ts | 2 +- packages/draw/src/utils/line/line-arrow.ts | 2 +- packages/draw/src/utils/line/line-basic.ts | 3 +- packages/draw/src/utils/line/line-common.ts | 2 +- packages/draw/src/utils/memorize.ts | 2 +- packages/draw/src/utils/style/stroke.ts | 17 +- packages/draw/src/utils/table.ts | 76 ++++++ src/app/editor/editor.component.ts | 5 +- src/app/editor/mock-data.ts | 225 ++++++++++++++++++ 42 files changed, 745 insertions(+), 106 deletions(-) create mode 100644 .changeset/tiny-vans-act.md create mode 100644 packages/draw/src/generators/table.generator.ts create mode 100644 packages/draw/src/generators/text.generator.ts create mode 100644 packages/draw/src/interfaces/engine.ts create mode 100644 packages/draw/src/utils/common.ts create mode 100644 packages/draw/src/utils/table.ts diff --git a/.changeset/tiny-vans-act.md b/.changeset/tiny-vans-act.md new file mode 100644 index 000000000..79aa6340a --- /dev/null +++ b/.changeset/tiny-vans-act.md @@ -0,0 +1,5 @@ +--- +'@plait/draw': minor +--- + +add table plugin diff --git a/packages/draw/src/constants/geometry.ts b/packages/draw/src/constants/geometry.ts index c84ff84ee..c8713a3e0 100644 --- a/packages/draw/src/constants/geometry.ts +++ b/packages/draw/src/constants/geometry.ts @@ -5,14 +5,14 @@ export const ShapeDefaultSpace = { rectangleAndText: 4 }; -export const DefaultGeometryStyle = { +export const DefaultDrawStyle = { strokeWidth: 2, defaultRadius: 4, strokeColor: '#000', fill: 'none' }; -export const DefaultGeometryActiveStyle = { +export const DefaultDrawActiveStyle = { strokeWidth: ACTIVE_STROKE_WIDTH, selectionStrokeWidth: ACTIVE_STROKE_WIDTH }; diff --git a/packages/draw/src/engines/basic-shapes/cloud.ts b/packages/draw/src/engines/basic-shapes/cloud.ts index 7486febc0..a258109db 100644 --- a/packages/draw/src/engines/basic-shapes/cloud.ts +++ b/packages/draw/src/engines/basic-shapes/cloud.ts @@ -1,4 +1,11 @@ -import { PlaitBoard, Point, PointOfRectangle, RectangleClient, drawRectangle, getNearestPointBetweenPointAndSegments, setPathStrokeLinecap } from '@plait/core'; +import { + PlaitBoard, + Point, + PointOfRectangle, + RectangleClient, + getNearestPointBetweenPointAndSegments, + setPathStrokeLinecap +} from '@plait/core'; import { PlaitGeometry, ShapeEngine } from '../../interfaces'; import { Options } from 'roughjs/bin/core'; import { getPolygonEdgeByConnectionPoint } from '../../utils/polygon'; diff --git a/packages/draw/src/engines/basic-shapes/diamond.ts b/packages/draw/src/engines/basic-shapes/diamond.ts index e6c5d14a2..a46f3d3ab 100644 --- a/packages/draw/src/engines/basic-shapes/diamond.ts +++ b/packages/draw/src/engines/basic-shapes/diamond.ts @@ -1,7 +1,7 @@ import { RectangleClient } from '@plait/core'; import { PlaitGeometry, ShapeEngine } from '../../interfaces'; import { createPolygonEngine } from './polygon'; -import { getTextRectangle } from '../../utils/geometry'; +import { getTextRectangle } from '../../utils'; export const DiamondEngine: ShapeEngine = createPolygonEngine({ getPolygonPoints: RectangleClient.getEdgeCenterPoints, diff --git a/packages/draw/src/engines/basic-shapes/parallelogram.ts b/packages/draw/src/engines/basic-shapes/parallelogram.ts index 1be27e009..bb96686a5 100644 --- a/packages/draw/src/engines/basic-shapes/parallelogram.ts +++ b/packages/draw/src/engines/basic-shapes/parallelogram.ts @@ -1,8 +1,8 @@ import { Point, RectangleClient } from '@plait/core'; import { PlaitGeometry, ShapeEngine } from '../../interfaces'; -import { getTextRectangle } from '../../utils/geometry'; import { createPolygonEngine } from './polygon'; import { getCenterPointsOnPolygon } from '../../utils/polygon'; +import { getTextRectangle } from '../../utils'; export const getParallelogramPoints = (rectangle: RectangleClient): Point[] => { return [ diff --git a/packages/draw/src/engines/basic-shapes/round-comment.ts b/packages/draw/src/engines/basic-shapes/round-comment.ts index 849f5b4e0..7ca72778d 100644 --- a/packages/draw/src/engines/basic-shapes/round-comment.ts +++ b/packages/draw/src/engines/basic-shapes/round-comment.ts @@ -10,9 +10,9 @@ import { import { PlaitGeometry, ShapeEngine } from '../../interfaces'; import { Options } from 'roughjs/bin/core'; import { ShapeDefaultSpace } from '../../constants'; -import { getStrokeWidthByElement } from '../../utils'; import { getRoundRectangleRadius } from './round-rectangle'; import { getPolygonEdgeByConnectionPoint } from '../../utils/polygon'; +import { getStrokeWidthByElement } from '../../utils/common'; const heightRatio = 3 / 4; diff --git a/packages/draw/src/engines/basic-shapes/trapezoid.ts b/packages/draw/src/engines/basic-shapes/trapezoid.ts index e22ab3d17..61bd3fb9c 100644 --- a/packages/draw/src/engines/basic-shapes/trapezoid.ts +++ b/packages/draw/src/engines/basic-shapes/trapezoid.ts @@ -2,7 +2,7 @@ import { Point, RectangleClient } from '@plait/core'; import { PlaitGeometry, ShapeEngine } from '../../interfaces'; import { createPolygonEngine } from './polygon'; import { getCenterPointsOnPolygon } from '../../utils/polygon'; -import { getTextRectangle } from '../../utils/geometry'; +import { getTextRectangle } from '../../utils'; export const getTrapezoidPoints = (rectangle: RectangleClient): Point[] => { return [ diff --git a/packages/draw/src/engines/basic-shapes/triangle.ts b/packages/draw/src/engines/basic-shapes/triangle.ts index 2560d327e..981e88526 100644 --- a/packages/draw/src/engines/basic-shapes/triangle.ts +++ b/packages/draw/src/engines/basic-shapes/triangle.ts @@ -1,9 +1,9 @@ import { Point, RectangleClient } from '@plait/core'; import { PlaitGeometry, ShapeEngine } from '../../interfaces'; import { createPolygonEngine } from './polygon'; -import { getStrokeWidthByElement } from '../../utils/style'; import { ShapeDefaultSpace } from '../../constants'; import { getCenterPointsOnPolygon } from '../../utils/polygon'; +import { getStrokeWidthByElement } from '../../utils'; export const getTrianglePoints = (rectangle: RectangleClient): Point[] => { return [ diff --git a/packages/draw/src/engines/flowchart/database.ts b/packages/draw/src/engines/flowchart/database.ts index d7159b1ac..f6e182023 100644 --- a/packages/draw/src/engines/flowchart/database.ts +++ b/packages/draw/src/engines/flowchart/database.ts @@ -3,7 +3,6 @@ import { Point, PointOfRectangle, RectangleClient, - Vector, getEllipseTangentSlope, getNearestPointBetweenPointAndEllipse, getNearestPointBetweenPointAndSegments, @@ -13,9 +12,9 @@ import { } from '@plait/core'; import { PlaitGeometry, ShapeEngine } from '../../interfaces'; import { ShapeDefaultSpace } from '../../constants'; -import { getStrokeWidthByElement } from '../../utils/style/stroke'; import { Options } from 'roughjs/bin/core'; import { RectangleEngine } from '../basic-shapes/rectangle'; +import { getStrokeWidthByElement } from '../../utils'; export const DatabaseEngine: ShapeEngine = { draw(board: PlaitBoard, rectangle: RectangleClient, options: Options) { diff --git a/packages/draw/src/engines/flowchart/hard-disk.ts b/packages/draw/src/engines/flowchart/hard-disk.ts index ffdbd06cf..76c0caa20 100644 --- a/packages/draw/src/engines/flowchart/hard-disk.ts +++ b/packages/draw/src/engines/flowchart/hard-disk.ts @@ -12,9 +12,9 @@ import { } from '@plait/core'; import { PlaitGeometry, ShapeEngine } from '../../interfaces'; import { ShapeDefaultSpace } from '../../constants'; -import { getStrokeWidthByElement } from '../../utils/style/stroke'; import { Options } from 'roughjs/bin/core'; import { RectangleEngine } from '../basic-shapes/rectangle'; +import { getStrokeWidthByElement } from '../../utils'; export const HardDiskEngine: ShapeEngine = { draw(board: PlaitBoard, rectangle: RectangleClient, options: Options) { diff --git a/packages/draw/src/engines/flowchart/internal-storage.ts b/packages/draw/src/engines/flowchart/internal-storage.ts index 915bdd69b..ebbc4eae3 100644 --- a/packages/draw/src/engines/flowchart/internal-storage.ts +++ b/packages/draw/src/engines/flowchart/internal-storage.ts @@ -8,10 +8,10 @@ import { } from '@plait/core'; import { PlaitGeometry, ShapeEngine } from '../../interfaces'; import { ShapeDefaultSpace } from '../../constants'; -import { getStrokeWidthByElement } from '../../utils/style/stroke'; import { Options } from 'roughjs/bin/core'; import { RectangleEngine } from '../basic-shapes/rectangle'; import { getPolygonEdgeByConnectionPoint } from '../../utils/polygon'; +import { getStrokeWidthByElement } from '../../utils'; export const InternalStorageEngine: ShapeEngine = { draw(board: PlaitBoard, rectangle: RectangleClient, options: Options) { diff --git a/packages/draw/src/engines/flowchart/manual-input.ts b/packages/draw/src/engines/flowchart/manual-input.ts index bc769119b..7ab323b93 100644 --- a/packages/draw/src/engines/flowchart/manual-input.ts +++ b/packages/draw/src/engines/flowchart/manual-input.ts @@ -3,7 +3,7 @@ import { PlaitGeometry, ShapeEngine } from '../../interfaces'; import { createPolygonEngine } from '../basic-shapes/polygon'; import { ShapeDefaultSpace } from '../../constants'; import { getCenterPointsOnPolygon } from '../../utils/polygon'; -import { getStrokeWidthByElement } from '../../utils/style/stroke'; +import { getStrokeWidthByElement } from '../../utils'; export const getManualInputPoints = (rectangle: RectangleClient): Point[] => { return [ diff --git a/packages/draw/src/engines/flowchart/manual-loop.ts b/packages/draw/src/engines/flowchart/manual-loop.ts index eb94c66c3..a7ef224bd 100644 --- a/packages/draw/src/engines/flowchart/manual-loop.ts +++ b/packages/draw/src/engines/flowchart/manual-loop.ts @@ -1,8 +1,8 @@ import { Point, RectangleClient } from '@plait/core'; import { PlaitGeometry, ShapeEngine } from '../../interfaces'; -import { getTextRectangle } from '../../utils/geometry'; import { createPolygonEngine } from '../basic-shapes/polygon'; import { getCenterPointsOnPolygon } from '../../utils/polygon'; +import { getTextRectangle } from '../../utils'; export const getManualLoopPoints = (rectangle: RectangleClient): Point[] => { return [ diff --git a/packages/draw/src/engines/flowchart/note-curly-left.ts b/packages/draw/src/engines/flowchart/note-curly-left.ts index 4719ff8e8..699d66340 100644 --- a/packages/draw/src/engines/flowchart/note-curly-left.ts +++ b/packages/draw/src/engines/flowchart/note-curly-left.ts @@ -8,10 +8,10 @@ import { } from '@plait/core'; import { PlaitGeometry, ShapeEngine } from '../../interfaces'; import { ShapeDefaultSpace } from '../../constants'; -import { getStrokeWidthByElement } from '../../utils/style/stroke'; import { Options } from 'roughjs/bin/core'; import { RectangleEngine } from '../basic-shapes/rectangle'; import { getPolygonEdgeByConnectionPoint } from '../../utils/polygon'; +import { getStrokeWidthByElement } from '../../utils'; export const NoteCurlyLeftEngine: ShapeEngine = { draw(board: PlaitBoard, rectangle: RectangleClient, options: Options) { diff --git a/packages/draw/src/engines/flowchart/note-curly-right.ts b/packages/draw/src/engines/flowchart/note-curly-right.ts index 3c341ca30..7861f8f69 100644 --- a/packages/draw/src/engines/flowchart/note-curly-right.ts +++ b/packages/draw/src/engines/flowchart/note-curly-right.ts @@ -8,10 +8,10 @@ import { } from '@plait/core'; import { PlaitGeometry, ShapeEngine } from '../../interfaces'; import { ShapeDefaultSpace } from '../../constants'; -import { getStrokeWidthByElement } from '../../utils/style/stroke'; import { Options } from 'roughjs/bin/core'; import { RectangleEngine } from '../basic-shapes/rectangle'; import { getPolygonEdgeByConnectionPoint } from '../../utils/polygon'; +import { getStrokeWidthByElement } from '../../utils'; export const NoteCurlyRightEngine: ShapeEngine = { draw(board: PlaitBoard, rectangle: RectangleClient, options: Options) { diff --git a/packages/draw/src/engines/flowchart/off-page.ts b/packages/draw/src/engines/flowchart/off-page.ts index da3f8fcf5..256e7498d 100644 --- a/packages/draw/src/engines/flowchart/off-page.ts +++ b/packages/draw/src/engines/flowchart/off-page.ts @@ -2,7 +2,7 @@ import { Point, RectangleClient } from '@plait/core'; import { PlaitGeometry, ShapeEngine } from '../../interfaces'; import { createPolygonEngine } from '../basic-shapes/polygon'; import { ShapeDefaultSpace } from '../../constants'; -import { getStrokeWidthByElement } from '../../utils/style/stroke'; +import { getStrokeWidthByElement } from '../../utils'; export const getOffPagePoints = (rectangle: RectangleClient): Point[] => { return [ diff --git a/packages/draw/src/engines/index.ts b/packages/draw/src/engines/index.ts index f53e11864..c0d0ab16b 100644 --- a/packages/draw/src/engines/index.ts +++ b/packages/draw/src/engines/index.ts @@ -1,4 +1,13 @@ -import { BasicShapes, FlowchartSymbols, GeometryShapes, ShapeEngine, SwimlaneSymbols, TableSymbols } from '../interfaces'; +import { + BasicShapes, + DrawShapes, + EngineExtraData, + FlowchartSymbols, + PlaitGeometry, + ShapeEngine, + SwimlaneSymbols, + TableSymbols +} from '../interfaces'; import { CommentEngine } from './basic-shapes/comment'; import { CrossEngine } from './basic-shapes/cross'; import { DiamondEngine } from './basic-shapes/diamond'; @@ -10,7 +19,6 @@ import { ParallelogramEngine } from './basic-shapes/parallelogram'; import { PentagonEngine } from './basic-shapes/pentagon'; import { PentagonArrowEngine } from './basic-shapes/pentagon-arrow'; import { ProcessArrowEngine } from './basic-shapes/process-arrow'; -import { RectangleEngine } from './basic-shapes/rectangle'; import { RightArrowEngine } from './basic-shapes/right-arrow'; import { RoundCommentEngine } from './basic-shapes/round-comment'; import { RoundRectangleEngine } from './basic-shapes/round-rectangle'; @@ -40,8 +48,10 @@ import { NoteCurlyRightEngine } from './flowchart/note-curly-right'; import { NoteSquareEngine } from './flowchart/note-square'; import { DisplayEngine } from './flowchart/display'; import { TableEngine } from './table/table'; +import { RectangleEngine } from './basic-shapes/rectangle'; +import { PlaitElement } from '@plait/core'; -export const ShapeEngineMap: Record = { +const ShapeEngineMap: Record> = { [BasicShapes.rectangle]: RectangleEngine, [BasicShapes.diamond]: DiamondEngine, [BasicShapes.ellipse]: EllipseEngine, @@ -92,6 +102,12 @@ export const ShapeEngineMap: Record = { [TableSymbols.table]: TableEngine }; -export const getEngine = (shape: GeometryShapes) => { +export const getEngine = < + T extends PlaitElement = PlaitGeometry, + P extends EngineExtraData = EngineExtraData, + K extends EngineExtraData = EngineExtraData +>( + shape: DrawShapes +): ShapeEngine => { return ShapeEngineMap[shape]; -}; +}; \ No newline at end of file diff --git a/packages/draw/src/engines/table/table.ts b/packages/draw/src/engines/table/table.ts index c2d368f5d..39f47e8ac 100644 --- a/packages/draw/src/engines/table/table.ts +++ b/packages/draw/src/engines/table/table.ts @@ -1,20 +1,33 @@ -import { - PlaitBoard, - RectangleClient, - Point, - createG -} from '@plait/core'; +import { PlaitBoard, RectangleClient, Point, createG, drawLine } from '@plait/core'; import { Options } from 'roughjs/bin/core'; +import { PlaitTable, PlaitTableDrawOptions } from '../../interfaces/table'; +import { getCellsWithPoints } from '../../utils/table'; import { ShapeEngine } from '../../interfaces'; +import { ShapeDefaultSpace } from '../../constants'; +import { getStrokeWidthByElement } from '../../utils'; +import { PlaitDrawShapeText } from '../../generators/text.generator'; -export const TableEngine: ShapeEngine = { - draw(board: PlaitBoard, rectangle: RectangleClient, options: Options) { - // TODO: draw table +export const TableEngine: ShapeEngine = { + draw(board: PlaitBoard, rectangle: RectangleClient, roughOptions: Options, options?: PlaitTableDrawOptions) { + const rs = PlaitBoard.getRoughSVG(board); const g = createG(); + 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); + const pointCells = getCellsWithPoints({ ...options?.element } as PlaitTable); + pointCells.forEach(cell => { + const rectangle = RectangleClient.getRectangleByPoints(cell.points!); + const { x, y, width, height } = rectangle; + 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(cellRightBorder, cellBottomBorder); + }); return g; }, isInsidePoint(rectangle: RectangleClient, point: Point) { - return false; + const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]); + return RectangleClient.isHit(rectangle, rangeRectangle); }, getCornerPoints(rectangle: RectangleClient) { return RectangleClient.getCornerPoints(rectangle); @@ -23,9 +36,21 @@ export const TableEngine: ShapeEngine = { return [0, 0]; }, getConnectorPoints(rectangle: RectangleClient) { - return [ - [0, 0], - [0, 0] - ]; + return RectangleClient.getEdgeCenterPoints(rectangle); + }, + getTextRectangle(element: PlaitTable, options?: PlaitDrawShapeText) { + const cells = getCellsWithPoints(element); + const cellIndex = element.cells.findIndex(item => item.id === options!.key); + const cell = cells[cellIndex]; + const cellRectangle = RectangleClient.getRectangleByPoints(cell.points); + const strokeWidth = getStrokeWidthByElement(cell); + const height = cell.textHeight || 0; + const width = cellRectangle.width - ShapeDefaultSpace.rectangleAndText * 2 - strokeWidth * 2; + return { + height, + width: width > 0 ? width : 0, + x: cellRectangle.x + ShapeDefaultSpace.rectangleAndText + strokeWidth, + y: cellRectangle.y + (cellRectangle.height - height) / 2 + }; } }; diff --git a/packages/draw/src/generators/geometry-shape.generator.ts b/packages/draw/src/generators/geometry-shape.generator.ts index aa54d7e2c..cdc3ef002 100644 --- a/packages/draw/src/generators/geometry-shape.generator.ts +++ b/packages/draw/src/generators/geometry-shape.generator.ts @@ -1,7 +1,7 @@ import { BasicShapes, PlaitGeometry } from '../interfaces'; import { Generator } from '@plait/common'; -import { getFillByElement, getLineDashByElement, getStrokeColorByElement, getStrokeWidthByElement } from '../utils/style/stroke'; -import { drawGeometry } from '../utils'; +import { getFillByElement, getLineDashByElement, getStrokeColorByElement } from '../utils/style/stroke'; +import { drawGeometry, getStrokeWidthByElement } from '../utils'; import { RectangleClient } from '@plait/core'; export interface ShapeData {} diff --git a/packages/draw/src/generators/line-active.generator.ts b/packages/draw/src/generators/line-active.generator.ts index 0c61d4dfe..35b797b03 100644 --- a/packages/draw/src/generators/line-active.generator.ts +++ b/packages/draw/src/generators/line-active.generator.ts @@ -2,10 +2,10 @@ import { PlaitBoard, Point, createG, drawRectangle, getSelectedElements } from ' import { LineShape, PlaitLine } from '../interfaces'; import { Generator, PRIMARY_COLOR, drawFillPrimaryHandle, drawPrimaryHandle } from '@plait/common'; import { getMiddlePoints } from '../utils/line/line-basic'; -import { DefaultGeometryActiveStyle } from '../constants'; import { getNextRenderPoints } from '../utils/line/elbow'; import { isUpdatedHandleIndex } from '../utils/line'; import { getHitPointIndex } from '../utils/position/line'; +import { DefaultDrawActiveStyle } from '../constants'; export interface ActiveData { selected: boolean; @@ -73,7 +73,7 @@ export class LineActiveGenerator extends Generator { } const strokeG = drawRectangle(this.board, activeRectangle, { stroke: PRIMARY_COLOR, - strokeWidth: DefaultGeometryActiveStyle.selectionStrokeWidth + strokeWidth: DefaultDrawActiveStyle.selectionStrokeWidth }); strokeG.style.opacity = opacity; activeG.appendChild(strokeG); diff --git a/packages/draw/src/generators/table.generator.ts b/packages/draw/src/generators/table.generator.ts new file mode 100644 index 000000000..791d221ee --- /dev/null +++ b/packages/draw/src/generators/table.generator.ts @@ -0,0 +1,28 @@ +import { TableSymbols } from '../interfaces'; +import { Generator } from '@plait/common'; +import { RectangleClient } from '@plait/core'; +import { PlaitTable } from '../interfaces/table'; +import { getEngine } from '../engines'; + +export interface TableData {} + +export class TableGenerator extends Generator { + canDraw(element: PlaitTable, data: TableData): boolean { + return true; + } + + draw(element: PlaitTable, data: TableData) { + const rectangle = RectangleClient.getRectangleByPoints(element.points); + return getEngine(TableSymbols.table).draw( + this.board, + rectangle, + { + strokeWidth: 1, + stroke: '#333' + }, + { + element: element + } + ); + } +} diff --git a/packages/draw/src/generators/text.generator.ts b/packages/draw/src/generators/text.generator.ts new file mode 100644 index 000000000..9b93d5bef --- /dev/null +++ b/packages/draw/src/generators/text.generator.ts @@ -0,0 +1,121 @@ +import { ELEMENT_TO_TEXT_MANAGES, WithTextOptions, WithTextPluginKey } from '@plait/common'; +import { PlaitBoard, PlaitElement, PlaitOptionsBoard, RectangleClient } from '@plait/core'; +import { TextPlugin, TextManageRef, TextManage, ParagraphElement } from '@plait/text'; +import { getEngine } from '../engines'; +import { DrawShapes, EngineExtraData, PlaitGeometry } from '../interfaces'; +import { getTextRectangle } from '../utils'; +import { ViewContainerRef } from '@angular/core'; + +export interface PlaitDrawShapeText extends EngineExtraData { + key: string; + text: ParagraphElement; + textHeight: number; +} + +export interface TextGeneratorOptions { + onValueChangeHandle: (textChangeRef: TextManageRef, text: PlaitDrawShapeText) => void; +} + +export class TextGenerator { + protected board: PlaitBoard; + + protected element: T; + + protected texts: PlaitDrawShapeText[]; + + protected viewContainerRef: ViewContainerRef; + + protected options: TextGeneratorOptions; + + public textManages!: TextManage[]; + + get shape(): DrawShapes { + return this.element.shape || this.element.type; + } + + constructor( + board: PlaitBoard, + element: T, + texts: PlaitDrawShapeText[], + viewContainerRef: ViewContainerRef, + options: TextGeneratorOptions + ) { + this.board = board; + this.texts = texts; + this.element = element; + this.viewContainerRef = viewContainerRef; + this.options = options; + } + + initialize() { + const textPlugins = ((this.board as PlaitOptionsBoard).getPluginOptions(WithTextPluginKey) || {}).textPlugins; + this.textManages = this.texts.map(text => { + return this.createTextManage(text, textPlugins); + }); + } + + draw(elementG: SVGElement) { + const centerPoint = RectangleClient.getCenterPoint(this.board.getRectangle(this.element)!); + this.texts.forEach((drawShapeText, index) => { + const textManage = this.textManages[index]; + if (drawShapeText.text) { + textManage.draw(drawShapeText.text); + elementG.append(textManage.g); + this.element.angle ?? textManage.updateAngle(centerPoint, this.element.angle); + } + }); + } + + update(element: T, previousDrawShapeTexts: PlaitDrawShapeText[], currentDrawShapeTexts: PlaitDrawShapeText[], elementG: SVGElement) { + this.element = element; + const centerPoint = RectangleClient.getCenterPoint(this.board.getRectangle(this.element)!); + currentDrawShapeTexts.forEach((drawShapeText, index) => { + const textManage = this.textManages[index]; + if (drawShapeText.text) { + textManage.updateText(drawShapeText.text); + textManage.updateRectangle(); + this.element.angle ?? textManage.updateAngle(centerPoint, this.element.angle); + } + }); + } + + private createTextManage(text: PlaitDrawShapeText, textPlugins: TextPlugin[] | undefined) { + const textManage = new TextManage(this.board, this.viewContainerRef, { + getRectangle: () => { + return this.getRectangle(text); + }, + onValueChangeHandle: (textManageRef: TextManageRef) => { + return this.onValueChangeHandle(textManageRef, text); + }, + getMaxWidth: () => { + return this.getMaxWidth(text); + }, + textPlugins + }); + return textManage; + } + + getRectangle(text: PlaitDrawShapeText) { + const getRectangle = getEngine(this.shape).getTextRectangle; + if (getRectangle) { + return getRectangle(this.element, text); + } + return getTextRectangle(this.element); + } + + onValueChangeHandle(textManageRef: TextManageRef, text: PlaitDrawShapeText) { + return this.options.onValueChangeHandle(textManageRef, text); + } + + getMaxWidth(text: PlaitDrawShapeText) { + return this.getRectangle(text).width; + } + + destroy() { + this.textManages.forEach(manage => { + manage.destroy(); + }); + this.textManages = []; + ELEMENT_TO_TEXT_MANAGES.delete(this.element); + } +} diff --git a/packages/draw/src/geometry.component.ts b/packages/draw/src/geometry.component.ts index 241a1af8a..5b81f6958 100644 --- a/packages/draw/src/geometry.component.ts +++ b/packages/draw/src/geometry.component.ts @@ -14,13 +14,13 @@ import { PlaitGeometry } from './interfaces/geometry'; import { GeometryShapeGenerator } from './generators/geometry-shape.generator'; import { TextManage, TextManageRef } from '@plait/text'; import { DrawTransforms } from './transforms'; -import { getTextRectangle } from './utils/geometry'; import { ActiveGenerator, WithTextPluginKey, WithTextOptions, CommonPluginElement, canResize } from '@plait/common'; import { GeometryThreshold } from './constants/geometry'; import { PlaitText } from './interfaces'; import { getEngine } from './engines'; import { LineAutoCompleteGenerator } from './generators/line-auto-complete.generator'; import { memorizeLatestText } from './utils'; +import { getTextRectangle } from './utils/common'; @Component({ selector: 'plait-draw-geometry', diff --git a/packages/draw/src/interfaces/engine.ts b/packages/draw/src/interfaces/engine.ts new file mode 100644 index 000000000..acedb7d85 --- /dev/null +++ b/packages/draw/src/interfaces/engine.ts @@ -0,0 +1,21 @@ +import { RectangleClient, PointOfRectangle, Vector, PlaitBoard, Point, PlaitElement } from '@plait/core'; +import { Options } from 'roughjs/bin/core'; +import { PlaitGeometry } from './geometry'; + +export interface EngineExtraData {} + +export interface ShapeEngine< + T extends PlaitElement = PlaitGeometry, + P extends EngineExtraData = EngineExtraData, + K extends EngineExtraData = EngineExtraData +> { + isInsidePoint: (rectangle: RectangleClient, point: Point) => boolean; + getNearestPoint: (rectangle: RectangleClient, point: Point) => Point; + getNearestCrossingPoint?: (rectangle: RectangleClient, point: Point) => Point; + getConnectorPoints: (rectangle: RectangleClient) => Point[]; + getCornerPoints: (rectangle: RectangleClient) => Point[]; + getEdgeByConnectionPoint?: (rectangle: RectangleClient, point: PointOfRectangle) => [Point, Point] | null; + getTangentVectorByConnectionPoint?: (rectangle: RectangleClient, point: PointOfRectangle) => Vector | null; + draw: (board: PlaitBoard, rectangle: RectangleClient, roughOptions: Options, options?: P) => SVGGElement; + getTextRectangle?: (element: T, options?: K) => RectangleClient; +} diff --git a/packages/draw/src/interfaces/geometry.ts b/packages/draw/src/interfaces/geometry.ts index 6923e9b56..bd497a108 100644 --- a/packages/draw/src/interfaces/geometry.ts +++ b/packages/draw/src/interfaces/geometry.ts @@ -1,5 +1,4 @@ -import { PlaitBoard, PlaitElement, Point, PointOfRectangle, RectangleClient, Vector } from '@plait/core'; -import { Options } from 'roughjs/bin/core'; +import { PlaitElement, Point } from '@plait/core'; import { ParagraphElement } from '@plait/text'; import { StrokeStyle } from './element'; import { PlaitTable } from './table'; @@ -64,7 +63,7 @@ export enum SwimlaneSymbols { swimlaneHorizontal = 'swimlaneHorizontal' } -export type GeometryShapes = BasicShapes | FlowchartSymbols | SwimlaneSymbols | TableSymbols; +export type GeometryShapes = BasicShapes | FlowchartSymbols | SwimlaneSymbols; export interface PlaitGeometry extends PlaitElement { points: [Point, Point]; @@ -97,7 +96,6 @@ export interface PlaitDiamond extends PlaitGeometry { } export interface PlaitSwimlane extends PlaitTable { - type: 'geometry'; shape: SwimlaneSymbols; } @@ -110,15 +108,3 @@ export interface PlaitSwimlaneHorizontal extends PlaitSwimlane { } export const PlaitGeometry = {}; - -export interface ShapeEngine { - isInsidePoint: (rectangle: RectangleClient, point: Point) => boolean; - getNearestPoint: (rectangle: RectangleClient, point: Point) => Point; - getNearestCrossingPoint?: (rectangle: RectangleClient, point: Point) => Point; - getConnectorPoints: (rectangle: RectangleClient) => Point[]; - getCornerPoints: (rectangle: RectangleClient) => Point[]; - getEdgeByConnectionPoint?: (rectangle: RectangleClient, point: PointOfRectangle) => [Point, Point] | null; - getTangentVectorByConnectionPoint?: (rectangle: RectangleClient, point: PointOfRectangle) => Vector | null; - draw: (board: PlaitBoard, rectangle: RectangleClient, roughOptions: Options, options?: T) => SVGGElement; - getTextRectangle?: (element: PlaitGeometry, options?: T) => RectangleClient; -} diff --git a/packages/draw/src/interfaces/index.ts b/packages/draw/src/interfaces/index.ts index a9e33378e..e542defd2 100644 --- a/packages/draw/src/interfaces/index.ts +++ b/packages/draw/src/interfaces/index.ts @@ -1,17 +1,23 @@ -import { BasicShapes, FlowchartSymbols, PlaitGeometry } from './geometry'; +import { ParagraphElement } from '@plait/text'; +import { EngineExtraData } from './engine'; +import { BasicShapes, FlowchartSymbols, GeometryShapes, PlaitGeometry, TableSymbols } from './geometry'; import { PlaitImage } from './image'; import { PlaitLine } from './line'; +import { PlaitTable } from './table'; import { PlaitText } from './text'; export * from './line'; export * from './geometry'; export * from './text'; export * from './element'; +export * from './engine'; -export type PlaitDrawElement = PlaitGeometry | PlaitLine | PlaitImage; +export type PlaitDrawElement = PlaitGeometry | PlaitLine | PlaitImage | PlaitTable; export type PlaitShapeElement = PlaitGeometry | PlaitImage; +export type DrawShapes = GeometryShapes | TableSymbols; + export const PlaitDrawElement = { isGeometry: (value: any): value is PlaitGeometry => { return value.type === 'geometry'; diff --git a/packages/draw/src/interfaces/table.ts b/packages/draw/src/interfaces/table.ts index 87d56d148..c21f5000f 100644 --- a/packages/draw/src/interfaces/table.ts +++ b/packages/draw/src/interfaces/table.ts @@ -1,9 +1,11 @@ import { PlaitElement, Point } from '@plait/core'; import { ParagraphElement } from '@plait/text'; +import { EngineExtraData } from './engine'; export interface PlaitTable extends PlaitElement { id: string; points: Point[]; + type: 'table'; rows: { id: string; height?: number; @@ -23,8 +25,23 @@ export interface PlaitTableCell { colspan?: number; rowspan?: number; text?: PlaitTableCellParagraph; + textHeight?: number; +} + +export interface PlaitTableDrawOptions extends EngineExtraData { + element: PlaitTable; +} + +export interface PlaitTableCellWithPoints extends PlaitTableCell { + points: [Point, Point]; } export interface PlaitTableCellParagraph extends ParagraphElement { writingMode: 'vertical-lr' | 'horizontal-tb'; } + +export const PlaitTableElement = { + isTable: (value: any): value is PlaitTable => { + return value.type === 'table' + } +}; diff --git a/packages/draw/src/plugins/with-table.ts b/packages/draw/src/plugins/with-table.ts index beeacbd49..cf05eb013 100644 --- a/packages/draw/src/plugins/with-table.ts +++ b/packages/draw/src/plugins/with-table.ts @@ -1,17 +1,62 @@ -import { PlaitBoard, PlaitPluginElementContext } from '@plait/core'; -import { PlaitDrawElement, SwimlaneSymbols } from '../interfaces'; import { TableComponent } from '../table.component'; +import { PlaitTableElement } from '../interfaces/table'; +import { + PlaitBoard, + PlaitPluginElementContext, + PlaitElement, + RectangleClient, + Selection, + isPolylineHitRectangle, + getSelectedElements +} from '@plait/core'; export const withTable = (board: PlaitBoard) => { - const { drawElement } = board; + const { drawElement, getRectangle, isRectangleHit, isHit, isMovable, getDeletedFragment } = board; board.drawElement = (context: PlaitPluginElementContext) => { - if ( - PlaitDrawElement.isGeometry(context.element) && - [SwimlaneSymbols.swimlaneHorizontal, SwimlaneSymbols.swimlaneVertical].includes(context.element.shape as SwimlaneSymbols) - ) { + if (PlaitTableElement.isTable(context.element)) { return TableComponent; } return drawElement(context); }; + + board.getDeletedFragment = (data: PlaitElement[]) => { + const elements = getSelectedElements(board); + if (elements.length) { + const tableElements = elements.filter(value => PlaitTableElement.isTable(value)); + data.push(...[...tableElements]); + } + return getDeletedFragment(data); + }; + + board.isHit = (element, point) => { + if (PlaitTableElement.isTable(element)) { + const client = RectangleClient.getRectangleByPoints(element.points); + return RectangleClient.isPointInRectangle(client, point); + } + return isHit(element, point); + }; + + board.getRectangle = (element: PlaitElement) => { + if (PlaitTableElement.isTable(element)) { + return RectangleClient.getRectangleByPoints(element.points); + } + return getRectangle(element); + }; + + board.isMovable = (element: PlaitElement) => { + if (PlaitTableElement.isTable(element)) { + return true; + } + + return isMovable(element); + }; + + board.isRectangleHit = (element: PlaitElement, selection: Selection) => { + if (PlaitTableElement.isTable(element)) { + const rangeRectangle = RectangleClient.getRectangleByPoints([selection.anchor, selection.focus]); + return isPolylineHitRectangle(element.points, rangeRectangle); + } + return isRectangleHit(element, selection); + }; return board; }; diff --git a/packages/draw/src/table.component.ts b/packages/draw/src/table.component.ts index c7737a980..b8e90c2de 100644 --- a/packages/draw/src/table.component.ts +++ b/packages/draw/src/table.component.ts @@ -1,7 +1,10 @@ import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core'; import { PlaitBoard, PlaitPluginElementContext, OnContextChanged, ACTIVE_STROKE_WIDTH, RectangleClient } from '@plait/core'; import { ActiveGenerator, canResize, CommonPluginElement } from '@plait/common'; -import { PlaitTable } from './interfaces/table'; +import { PlaitTable, PlaitTableCell } from './interfaces/table'; +import { PlaitDrawShapeText, TextGenerator } from './generators/text.generator'; +import { TableGenerator } from './generators/table.generator'; +import { TextManageRef } from '@plait/text'; @Component({ selector: 'plait-draw-table', @@ -13,6 +16,10 @@ export class TableComponent extends CommonPluginElement implements OnInit, OnDestroy, OnContextChanged { activeGenerator!: ActiveGenerator; + tableGenerator!: TableGenerator; + + textGenerator!: TextGenerator; + constructor() { super(); } @@ -32,19 +39,65 @@ export class TableComponent extends CommonPluginElement return canResize(this.board, this.element); } }); + this.tableGenerator = new TableGenerator(this.board); + this.initializeTextManage(); } ngOnInit(): void { super.ngOnInit(); this.initializeGenerator(); + this.tableGenerator.processDrawing(this.element, this.getElementG()); + this.textGenerator.draw(this.getElementG()); + } + + getDrawShapeTexts(cells: PlaitTableCell[]): PlaitDrawShapeText[] { + return cells.map(item => { + return { + key: item.id, + text: item.text!, + textHeight: item.textHeight! + }; + }); + } + + initializeTextManage() { + const texts = this.getDrawShapeTexts(this.element.cells); + this.textGenerator = new TextGenerator(this.board, this.element, texts, this.viewContainerRef, { + onValueChangeHandle: (textChangeRef: TextManageRef, text: PlaitDrawShapeText) => {} + }); + this.textGenerator.initialize(); + this.initializeTextManages(this.textGenerator.textManages); + } + + updateText(previousTable: PlaitTable, currentTable: PlaitTable) { + const previousTexts = this.getDrawShapeTexts(previousTable.cells); + const currentTexts = this.getDrawShapeTexts(currentTable.cells); + this.textGenerator.update(this.element, previousTexts, currentTexts, this.getElementG()); } onContextChanged( value: PlaitPluginElementContext, previous: PlaitPluginElementContext - ) {} - + ) { + this.initializeWeakMap(); + if (value.element !== previous.element) { + this.tableGenerator.processDrawing(this.element, this.getElementG()); + this.activeGenerator.processDrawing(this.element, PlaitBoard.getElementActiveHost(this.board), { selected: this.selected }); + const previousTexts = this.getDrawShapeTexts(previous.element.cells); + const currentTexts = this.getDrawShapeTexts(this.element.cells); + this.textGenerator.update(this.element, previousTexts, currentTexts, this.getElementG()); + } else { + const hasSameSelected = value.selected === previous.selected; + const hasSameHandleState = this.activeGenerator.options.hasResizeHandle() === this.activeGenerator.hasResizeHandle; + if (!hasSameSelected || !hasSameHandleState) { + this.activeGenerator.processDrawing(this.element, PlaitBoard.getElementActiveHost(this.board), { selected: this.selected }); + } + } + } ngOnDestroy(): void { super.ngOnDestroy(); + this.activeGenerator.destroy(); + this.tableGenerator.destroy(); + this.textGenerator.destroy(); } } diff --git a/packages/draw/src/utils/common.ts b/packages/draw/src/utils/common.ts new file mode 100644 index 000000000..061e5af94 --- /dev/null +++ b/packages/draw/src/utils/common.ts @@ -0,0 +1,24 @@ +import { PlaitElement, RectangleClient } from '@plait/core'; +import { DefaultDrawStyle, ShapeDefaultSpace } from '../constants'; +import { PlaitDrawElement, PlaitGeometry } from '../interfaces'; + +export const getTextRectangle = (element: T) => { + const elementRectangle = RectangleClient.getRectangleByPoints(element.points!); + const strokeWidth = getStrokeWidthByElement(element); + const height = element.textHeight; + const width = elementRectangle.width - ShapeDefaultSpace.rectangleAndText * 2 - strokeWidth * 2; + return { + height, + width: width > 0 ? width : 0, + x: elementRectangle.x + ShapeDefaultSpace.rectangleAndText + strokeWidth, + y: elementRectangle.y + (elementRectangle.height - height) / 2 + }; +}; + +export const getStrokeWidthByElement = (element: PlaitElement) => { + if (PlaitDrawElement.isText(element)) { + return 0; + } + const strokeWidth = element.strokeWidth || DefaultDrawStyle.strokeWidth; + return strokeWidth; +}; diff --git a/packages/draw/src/utils/geometry.ts b/packages/draw/src/utils/geometry.ts index 16d45d2c9..d33b83b4a 100644 --- a/packages/draw/src/utils/geometry.ts +++ b/packages/draw/src/utils/geometry.ts @@ -32,7 +32,6 @@ import { getFlowchartPointers } from '../constants'; import { ActiveGenerator, PlaitCommonElementRef, RESIZE_HANDLE_DIAMETER, getFirstTextManage } from '@plait/common'; -import { getStrokeWidthByElement } from './style/stroke'; import { Options } from 'roughjs/bin/core'; import { getEngine } from '../engines'; import { getElementShape } from './shape'; @@ -78,19 +77,6 @@ export const createGeometryElement = ( }; }; -export const getTextRectangle = (element: PlaitGeometry) => { - const elementRectangle = RectangleClient.getRectangleByPoints(element.points!); - const strokeWidth = getStrokeWidthByElement(element); - const height = element.textHeight; - const width = elementRectangle.width - ShapeDefaultSpace.rectangleAndText * 2 - strokeWidth * 2; - return { - height, - width: width > 0 ? width : 0, - x: elementRectangle.x + ShapeDefaultSpace.rectangleAndText + strokeWidth, - y: elementRectangle.y + (elementRectangle.height - height) / 2 - }; -}; - export const drawBoundReaction = ( board: PlaitBoard, element: PlaitGeometry, @@ -129,8 +115,8 @@ export const drawBoundReaction = ( return g; }; -export const drawGeometry = (board: PlaitBoard, outerRectangle: RectangleClient, shape: GeometryShapes, options: Options) => { - return getEngine(shape).draw(board, outerRectangle, options); +export const drawGeometry = (board: PlaitBoard, outerRectangle: RectangleClient, shape: GeometryShapes, roughOptions: Options) => { + return getEngine(shape).draw(board, outerRectangle, roughOptions); }; export const getNearestPoint = (element: PlaitShapeElement, point: Point) => { diff --git a/packages/draw/src/utils/hit.ts b/packages/draw/src/utils/hit.ts index 9d0ed0c3a..6d8c821d1 100644 --- a/packages/draw/src/utils/hit.ts +++ b/packages/draw/src/utils/hit.ts @@ -13,13 +13,14 @@ import { } from '@plait/core'; import { PlaitDrawElement, PlaitGeometry, PlaitLine, PlaitShapeElement } from '../interfaces'; import { TRANSPARENT } from '@plait/common'; -import { getNearestPoint, getTextRectangle } from './geometry'; +import { getNearestPoint } from './geometry'; import { getLinePoints } from './line/line-basic'; import { getFillByElement } from './style/stroke'; -import { DefaultGeometryStyle } from '../constants/geometry'; +import { DefaultDrawStyle } from '../constants/geometry'; import { getEngine } from '../engines'; import { getElementShape } from './shape'; import { getHitLineTextIndex } from './position/line'; +import { getTextRectangle } from './common'; export const isTextExceedingBounds = (geometry: PlaitGeometry) => { const client = RectangleClient.getRectangleByPoints(geometry.points); @@ -82,7 +83,7 @@ export const isHitDrawElement = (board: PlaitBoard, element: PlaitElement, point } const engine = getEngine(getElementShape(element)); // when shape equals text, fill is not allowed - if (fill !== DefaultGeometryStyle.fill && fill !== TRANSPARENT && !PlaitDrawElement.isText(element)) { + if (fill !== DefaultDrawStyle.fill && fill !== TRANSPARENT && !PlaitDrawElement.isText(element)) { const isHitInside = engine.isInsidePoint(rectangle!, point); if (isHitInside) { return isHitInside; diff --git a/packages/draw/src/utils/index.ts b/packages/draw/src/utils/index.ts index c500bfd53..841668f93 100644 --- a/packages/draw/src/utils/index.ts +++ b/packages/draw/src/utils/index.ts @@ -4,3 +4,4 @@ export * from './selected'; export * from './style'; export * from './hit'; export * from './memorize'; +export * from './common'; \ No newline at end of file diff --git a/packages/draw/src/utils/line/elbow.ts b/packages/draw/src/utils/line/elbow.ts index ae6b94ee9..0180cbfd4 100644 --- a/packages/draw/src/utils/line/elbow.ts +++ b/packages/draw/src/utils/line/elbow.ts @@ -11,9 +11,9 @@ import { } from '@plait/common'; import { BasicShapes, LineHandleRefPair, PlaitGeometry, PlaitLine } from '../../interfaces'; import { createGeometryElement } from '../geometry'; -import { getStrokeWidthByElement } from '../style/stroke'; import { getElbowLineRouteOptions, getLineHandleRefPair } from './line-common'; import { getMidKeyPoints, getMirrorDataPoints, hasIllegalElbowPoint } from './line-resize'; +import { getStrokeWidthByElement } from '../common'; export const isSelfLoop = (element: PlaitLine) => { return element.source.boundId && element.source.boundId === element.target.boundId; diff --git a/packages/draw/src/utils/line/line-arrow.ts b/packages/draw/src/utils/line/line-arrow.ts index c1ee29bd3..74b17fc2d 100644 --- a/packages/draw/src/utils/line/line-arrow.ts +++ b/packages/draw/src/utils/line/line-arrow.ts @@ -2,7 +2,7 @@ import { Point, arrowPoints, createG, createPath, distanceBetweenPointAndPoint, import { LineMarkerType, PlaitLine } from '../../interfaces'; import { Options } from 'roughjs/bin/core'; import { getExtendPoint, getUnitVectorByPointAndPoint } from '@plait/common'; -import { getStrokeWidthByElement } from '../style'; +import { getStrokeWidthByElement } from '../common'; interface ArrowOptions { marker: LineMarkerType; diff --git a/packages/draw/src/utils/line/line-basic.ts b/packages/draw/src/utils/line/line-basic.ts index a782f028d..7a8428863 100644 --- a/packages/draw/src/utils/line/line-basic.ts +++ b/packages/draw/src/utils/line/line-basic.ts @@ -25,7 +25,7 @@ import { PlaitShapeElement, StrokeStyle } from '../../interfaces'; -import { getLineDashByElement, getStrokeColorByElement, getStrokeWidthByElement } from '../style/stroke'; +import { getLineDashByElement, getStrokeColorByElement } from '../style/stroke'; import { getEngine } from '../../engines'; import { getElementShape } from '../shape'; import { DefaultLineStyle, LINE_TEXT_SPACE } from '../../constants/line'; @@ -37,6 +37,7 @@ import { alignPoints } from './line-resize'; import { getElbowLineRouteOptions, getLineHandleRefPair } from './line-common'; import { getElbowPoints, getNextSourceAndTargetPoints, isUseDefaultOrthogonalRoute } from './elbow'; import { drawLineArrow } from './line-arrow'; +import { getStrokeWidthByElement } from '../common'; export const createLineElement = ( shape: LineShape, diff --git a/packages/draw/src/utils/line/line-common.ts b/packages/draw/src/utils/line/line-common.ts index 2e0bd8d5b..030e80350 100644 --- a/packages/draw/src/utils/line/line-common.ts +++ b/packages/draw/src/utils/line/line-common.ts @@ -20,10 +20,10 @@ import { rotateVector } from '@plait/common'; import { BasicShapes, LineHandleKey, LineHandleRef, LineHandleRefPair, LineMarkerType, PlaitGeometry, PlaitLine } from '../../interfaces'; -import { getStrokeWidthByElement } from '../style/stroke'; import { getEngine } from '../../engines'; import { getElementShape } from '../shape'; import { getSourceAndTargetRectangle } from './elbow'; +import { getStrokeWidthByElement } from '../common'; export const getLineHandleRefPair = (board: PlaitBoard, element: PlaitLine): LineHandleRefPair => { const strokeWidth = getStrokeWidthByElement(element); diff --git a/packages/draw/src/utils/memorize.ts b/packages/draw/src/utils/memorize.ts index 800c62b04..7c072b6d2 100644 --- a/packages/draw/src/utils/memorize.ts +++ b/packages/draw/src/utils/memorize.ts @@ -49,7 +49,7 @@ export const getMemorizedLatestByPointer = (pointer: DrawPointerType) => { return { textProperties, geometryProperties: properties }; }; -export const memorizeLatestText = (element: PlaitDrawElement, operations: BaseOperation[]) => { +export const memorizeLatestText = (element: T, operations: BaseOperation[]) => { const memorizeKey = getMemorizeKey(element); let textMemory = getMemorizedLatest(memorizeKey)?.text || {}; const setNodeOperation = operations.find(operation => operation.type === 'set_node'); diff --git a/packages/draw/src/utils/style/stroke.ts b/packages/draw/src/utils/style/stroke.ts index 6ab57cfa8..4a4ccfd8f 100644 --- a/packages/draw/src/utils/style/stroke.ts +++ b/packages/draw/src/utils/style/stroke.ts @@ -1,15 +1,8 @@ import { PlaitDrawElement, PlaitGeometry, PlaitLine, StrokeStyle } from '../../interfaces'; -import { DefaultGeometryStyle } from '../../constants'; -import { PlaitBoard } from '@plait/core'; +import { DefaultDrawStyle } from '../../constants'; +import { PlaitBoard, PlaitElement } from '@plait/core'; import { getDrawDefaultStrokeColor, getFlowchartDefaultFill } from '../geometry'; - -export const getStrokeWidthByElement = (element: PlaitGeometry | PlaitLine) => { - if (PlaitDrawElement.isText(element)) { - return 0; - } - const strokeWidth = element.strokeWidth || DefaultGeometryStyle.strokeWidth; - return strokeWidth; -}; +import { getStrokeWidthByElement } from '../common'; export const getStrokeColorByElement = (board: PlaitBoard, element: PlaitGeometry | PlaitLine) => { const defaultColor = getDrawDefaultStrokeColor(board.theme.themeColorMode); @@ -20,7 +13,7 @@ export const getStrokeColorByElement = (board: PlaitBoard, element: PlaitGeometr export const getFillByElement = (board: PlaitBoard, element: PlaitGeometry | PlaitLine) => { const defaultFill = PlaitDrawElement.isFlowchart(element) ? getFlowchartDefaultFill(board.theme.themeColorMode) - : DefaultGeometryStyle.fill; + : DefaultDrawStyle.fill; const fill = element.fill || defaultFill; return fill; }; @@ -36,6 +29,6 @@ export const getLineDashByElement = (element: PlaitGeometry | PlaitLine) => { } }; -export const getStrokeStyleByElement = (element: PlaitGeometry | PlaitLine) => { +export const getStrokeStyleByElement = (element: PlaitElement) => { return element.strokeStyle || StrokeStyle.solid; }; diff --git a/packages/draw/src/utils/table.ts b/packages/draw/src/utils/table.ts new file mode 100644 index 000000000..9636ee98c --- /dev/null +++ b/packages/draw/src/utils/table.ts @@ -0,0 +1,76 @@ +import { RectangleClient } from '@plait/core'; +import { PlaitTable, PlaitTableCell, PlaitTableCellWithPoints } from '../interfaces/table'; + +export function getCellsWithPoints(table: PlaitTable): PlaitTableCellWithPoints[] { + 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: PlaitTableCellWithPoints[] = 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 calculateCellsSize(items: { id: string; [key: string]: any }[], tableSize: number, count: number, isWidth: boolean) { + const cellSizes: number[] = []; + 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: PlaitTableCell, sizes: number[], index: number, isWidth: boolean) { + 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; +} diff --git a/src/app/editor/editor.component.ts b/src/app/editor/editor.component.ts index a89e01937..867bb9b32 100644 --- a/src/app/editor/editor.component.ts +++ b/src/app/editor/editor.component.ts @@ -21,7 +21,7 @@ import { setFragment, WritableClipboardOperationType } from '@plait/core'; -import { mockDrawData, mockGroupData, mockMindData, mockRotateData } from './mock-data'; +import { mockDrawData, mockTableData, mockMindData, mockRotateData, mockGroupData } from './mock-data'; import { withMind, PlaitMindBoard, PlaitMind } from '@plait/mind'; import { AbstractResizeState, MindThemeColors } from '@plait/mind'; import { withMindExtend } from '../plugins/with-mind-extend'; @@ -135,6 +135,9 @@ export class BasicEditorComponent implements OnInit { case 'group': this.value = [...mockGroupData]; break; + case 'table': + this.value = [...mockTableData]; + break; case 'rotate': this.value = [...mockRotateData]; break; diff --git a/src/app/editor/mock-data.ts b/src/app/editor/mock-data.ts index d6fd25f23..3aea3a6a2 100644 --- a/src/app/editor/mock-data.ts +++ b/src/app/editor/mock-data.ts @@ -359,6 +359,231 @@ export const mockDrawData: PlaitDrawElement[] = [ } ] as PlaitDrawElement[]; +export const mockTableData: PlaitDrawElement[] = [ + { + id: 'jhETT', + points: [ + [-100, -100], + [500, 300] + ], + type: 'table', + rows: [ + { + id: 'row-1', + height: 30 + }, + { + id: 'row-2', + height: 30 + }, + { + id: 'row-3' + }, + { + id: 'row-4' + } + ], + columns: [ + { + id: 'column-1' + }, + { + id: 'column-2' + }, + { + id: 'column-3' + } + ], + cells: [ + { + id: 'cell-1-1', + rowId: 'row-1', + columnId: 'column-1', + colspan: 3, + textHeight: 20, + text: { + children: [ + { + text: 'merge cell' + } + ], + align: 'center' + } + }, + { + id: 'cell-2-1', + rowId: 'row-2', + textHeight: 20, + columnId: 'column-1', + text: { + children: [ + { + text: 'cell-2-1' + } + ], + align: 'center' + } + }, + { + id: 'cell-2-2', + rowId: 'row-2', + textHeight: 20, + columnId: 'column-2' + }, + { + id: 'cell-2-3', + rowId: 'row-2', + textHeight: 20, + columnId: 'column-3' + }, + { + id: 'cell-3-1', + rowId: 'row-3', + textHeight: 20, + columnId: 'column-1' + }, + { + id: 'cell-3-2', + rowId: 'row-3', + textHeight: 20, + columnId: 'column-2' + }, + { + id: 'cell-3-3', + rowId: 'row-3', + textHeight: 20, + columnId: 'column-3' + }, + { + id: 'cell-4-1', + rowId: 'row-4', + textHeight: 20, + columnId: 'column-1' + }, + { + id: 'cell-4-2', + rowId: 'row-4', + textHeight: 20, + columnId: 'column-2' + }, + { + id: 'cell-4-3', + rowId: 'row-4', + textHeight: 20, + columnId: 'column-3' + } + ] + }, + { + id: 'TTjhE', + points: [ + [600, -100], + [1200, 300] + ], + type: 'table', + rows: [ + { + id: 'row-1' + }, + { + id: 'row-2' + }, + { + id: 'row-3' + }, + { + id: 'row-4' + } + ], + columns: [ + { + id: 'column-1', + width: 30 + }, + { + id: 'column-2', + width: 30 + }, + { + id: 'column-3' + } + ], + cells: [ + { + id: 'cell-1-1', + rowId: 'row-1', + columnId: 'column-1', + textHeight: 20, + rowspan: 4, + text: { + children: [ + { + text: 'merge cell' + } + ], + align: 'center' + } + }, + { + id: 'cell-1-2', + rowId: 'row-1', + textHeight: 20, + columnId: 'column-2' + }, + { + id: 'cell-1-3', + rowId: 'row-1', + textHeight: 20, + columnId: 'column-3' + }, + { + id: 'cell-2-2', + rowId: 'row-2', + textHeight: 20, + columnId: 'column-2' + }, + { + id: 'cell-2-3', + rowId: 'row-2', + textHeight: 20, + columnId: 'column-3' + }, + { + id: 'cell-3-2', + rowId: 'row-3', + textHeight: 20, + columnId: 'column-2' + }, + { + id: 'cell-3-3', + rowId: 'row-3', + textHeight: 20, + columnId: 'column-3', + text: { + children: [ + { + text: 'cell-3-3' + } + ], + align: 'center' + } + }, + { + id: 'cell-4-2', + rowId: 'row-4', + textHeight: 20, + columnId: 'column-2' + }, + { + id: 'cell-4-3', + rowId: 'row-4', + textHeight: 20, + columnId: 'column-3' + } + ] + } +] as PlaitDrawElement[]; + export const mockGroupData: PlaitDrawElement[] = [ { id: 'group1',