diff --git a/.changeset/strong-kiwis-smell.md b/.changeset/strong-kiwis-smell.md new file mode 100644 index 000000000..7dfc6d2f4 --- /dev/null +++ b/.changeset/strong-kiwis-smell.md @@ -0,0 +1,7 @@ +--- +'@plait/draw': minor +--- + +init swimlane + +support add/remove swimlane row/column \ No newline at end of file diff --git a/packages/draw/src/constants/draw.ts b/packages/draw/src/constants/draw.ts new file mode 100644 index 000000000..ddd08d86d --- /dev/null +++ b/packages/draw/src/constants/draw.ts @@ -0,0 +1 @@ +export const DEFAULT_TEXT_HEIGHT = 20; diff --git a/packages/draw/src/constants/index.ts b/packages/draw/src/constants/index.ts index 2033f5553..8b3e315be 100644 --- a/packages/draw/src/constants/index.ts +++ b/packages/draw/src/constants/index.ts @@ -2,3 +2,4 @@ export * from './geometry'; export * from './pointer'; export * from './image'; export * from './theme'; +export * from './draw'; \ 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 c622fbdf3..00ff8e8dd 100644 --- a/packages/draw/src/engines/table/table.ts +++ b/packages/draw/src/engines/table/table.ts @@ -15,7 +15,7 @@ export const TableEngine: ShapeEngine { const rectangle = RectangleClient.getRectangleByPoints(cell.points!); const { x, y, width, height } = rectangle; @@ -39,7 +39,7 @@ export const TableEngine: ShapeEngine { - canDraw(element: PlaitTable, data: TableData): boolean { +export class TableGenerator extends Generator { + canDraw(element: T, data: TableData): boolean { return true; } - draw(element: PlaitTable, data: TableData) { - const rectangle = RectangleClient.getRectangleByPoints(element.points); + draw(element: T, data: TableData) { + const rectangle = RectangleClient.getRectangleByPoints(element.points!); return getEngine(TableSymbols.table).draw( this.board, rectangle, diff --git a/packages/draw/src/generators/text.generator.ts b/packages/draw/src/generators/text.generator.ts index 008a24dc1..035fa2fd1 100644 --- a/packages/draw/src/generators/text.generator.ts +++ b/packages/draw/src/generators/text.generator.ts @@ -10,10 +10,11 @@ export interface PlaitDrawShapeText extends EngineExtraData { key: string; text: ParagraphElement; textHeight: number; + board?: PlaitBoard; } export interface TextGeneratorOptions { - onValueChangeHandle: (textChangeRef: TextManageRef, text: PlaitDrawShapeText) => void; + onValueChangeHandle: (element: T, textChangeRef: TextManageRef, text: PlaitDrawShapeText) => void; getRenderRectangle?: (element: T, text: PlaitDrawShapeText) => RectangleClient; getMaxWidth?: () => number; } @@ -97,11 +98,34 @@ export class TextGenerator { this.element = element; ELEMENT_TO_TEXT_MANAGES.set(this.element, this.textManages); const centerPoint = RectangleClient.getCenterPoint(this.board.getRectangle(this.element)!); + const textPlugins = ((this.board as PlaitOptionsBoard).getPluginOptions(WithTextPluginKey) || {}).textPlugins; + const removedTexts = previousDrawShapeTexts.filter(value => { + return !currentDrawShapeTexts.find(item => item.key === value.key); + }); + if (removedTexts.length) { + removedTexts.forEach(item => { + const textManage = getTextManage(item.key); + const index = this.textManages.findIndex(value => value === textManage); + if (index > -1 && item.text && item.textHeight) { + this.textManages.splice(index, 1); + } + textManage?.destroy(); + deleteTextManage(item.key); + }); + } currentDrawShapeTexts.forEach(drawShapeText => { - const textManage = getTextManage(this.getTextKey(drawShapeText)); - if (drawShapeText.text && textManage) { - textManage.updateText(drawShapeText.text); - textManage.updateRectangle(); + if (drawShapeText.text) { + let textManage = getTextManage(this.getTextKey(drawShapeText)); + if (!textManage) { + textManage = this.createTextManage(drawShapeText, textPlugins); + setTextManage(drawShapeText.key, textManage); + textManage.draw(drawShapeText.text); + elementG.append(textManage.g); + this.textManages.push(textManage); + } else { + textManage.updateText(drawShapeText.text); + textManage.updateRectangle(); + } (this.element.angle || this.element.angle === 0) && textManage.updateAngle(centerPoint, this.element.angle); } }); @@ -135,7 +159,7 @@ export class TextGenerator { } onValueChangeHandle(textManageRef: TextManageRef, text: PlaitDrawShapeText) { - return this.options.onValueChangeHandle(textManageRef, text); + return this.options.onValueChangeHandle(this.element, textManageRef, text); } getMaxWidth(text: PlaitDrawShapeText) { @@ -148,6 +172,8 @@ export class TextGenerator { }); this.textManages = []; ELEMENT_TO_TEXT_MANAGES.delete(this.element); - KEY_TO_TEXT_MANAGE.clear(); + this.texts.forEach(item => { + deleteTextManage(item.key); + }); } } diff --git a/packages/draw/src/geometry.component.ts b/packages/draw/src/geometry.component.ts index e394ed5af..6f62c0dc9 100644 --- a/packages/draw/src/geometry.component.ts +++ b/packages/draw/src/geometry.component.ts @@ -123,23 +123,23 @@ export class GeometryComponent extends CommonElementFlavour { + const onTextValueChangeHandle = (element: PlaitCommonGeometry, textManageRef: TextManageRef, text: PlaitDrawShapeText) => { const height = textManageRef.height / this.board.viewport.zoom; const width = textManageRef.width / this.board.viewport.zoom; if (textManageRef.newValue) { - if (isMultipleTextGeometry(this.element)) { - DrawTransforms.setDrawShapeText(this.board, this.element, { + if (isMultipleTextGeometry(element)) { + DrawTransforms.setDrawShapeText(this.board, element, { key: text.key, text: textManageRef.newValue, textHeight: height }); } else { - DrawTransforms.setText(this.board, this.element as PlaitGeometry, textManageRef.newValue, width, height); + DrawTransforms.setText(this.board, element as PlaitGeometry, textManageRef.newValue, width, height); } } else { - DrawTransforms.setTextSize(this.board, this.element as PlaitGeometry, width, height); + DrawTransforms.setTextSize(this.board, element as PlaitGeometry, width, height); } - textManageRef.operations && memorizeLatestText(this.element, textManageRef.operations); + textManageRef.operations && memorizeLatestText(element, textManageRef.operations); }; if (isMultipleTextGeometry(this.element)) { diff --git a/packages/draw/src/interfaces/geometry.ts b/packages/draw/src/interfaces/geometry.ts index 03caa62c6..ad427cfba 100644 --- a/packages/draw/src/interfaces/geometry.ts +++ b/packages/draw/src/interfaces/geometry.ts @@ -79,6 +79,8 @@ export enum MultipleTextGeometryCommonTextKeys { export type GeometryShapes = BasicShapes | FlowchartSymbols | SwimlaneSymbols | UMLSymbols; +export type SwimlaneDirection = 'horizontal' | 'vertical'; + export interface PlaitCommonGeometry extends PlaitElement { points: [Point, Point]; type: 'geometry'; diff --git a/packages/draw/src/interfaces/index.ts b/packages/draw/src/interfaces/index.ts index 0fbced3de..42523d75c 100644 --- a/packages/draw/src/interfaces/index.ts +++ b/packages/draw/src/interfaces/index.ts @@ -1,6 +1,4 @@ -import { ParagraphElement } from '@plait/text'; -import { EngineExtraData } from './engine'; -import { BasicShapes, FlowchartSymbols, GeometryShapes, PlaitGeometry, TableSymbols, UMLSymbols } from './geometry'; +import { BasicShapes, FlowchartSymbols, GeometryShapes, PlaitGeometry, SwimlaneSymbols, TableSymbols, UMLSymbols } from './geometry'; import { PlaitImage } from './image'; import { PlaitLine } from './line'; import { PlaitTable } from './table'; @@ -50,6 +48,15 @@ export const PlaitDrawElement = { isUML: (value: any) => { return Object.keys(UMLSymbols).includes(value.shape); }, + isSwimlane: (value: any) => { + return Object.keys(SwimlaneSymbols).includes(value.shape); + }, + isVerticalSwimlane: (value: any) => { + return Object.keys(SwimlaneSymbols).includes(value.shape) && value.shape === SwimlaneSymbols.swimlaneVertical; + }, + isHorizontalSwimlane: (value: any) => { + return Object.keys(SwimlaneSymbols).includes(value.shape) && value.shape === SwimlaneSymbols.swimlaneHorizontal; + }, isUMLClassOrInterface: (value: any) => { return false; } diff --git a/packages/draw/src/plugins/with-draw.ts b/packages/draw/src/plugins/with-draw.ts index 837db2827..323d55c97 100644 --- a/packages/draw/src/plugins/with-draw.ts +++ b/packages/draw/src/plugins/with-draw.ts @@ -19,6 +19,7 @@ import { isHitDrawElement, isHitElementInside, isRectangleHitDrawElement } from import { getLinePoints, getLineTextRectangle } from '../utils/line/line-basic'; import { withDrawRotate } from './with-draw-rotate'; import { withTable } from './with-table'; +import { withSwimlane } from './with-swimlane'; export const withDraw = (board: PlaitBoard) => { const { drawElement, getRectangle, isRectangleHit, isHit, isInsidePoint, isMovable, isAlign, getRelatedFragment } = board; @@ -125,19 +126,21 @@ export const withDraw = (board: PlaitBoard) => { return getRelatedFragment([...elements, ...activeLines], originData); }; - return withTable( - withDrawResize( - withLineAutoCompleteReaction( - withLineBoundReaction( - withLineResize( - withLineTextMove( - withLineText( - withGeometryResize( - withDrawRotate( - withLineCreateByDraw( - withLineAutoComplete( - withGeometryCreateByDrag( - withGeometryCreateByDrawing(withDrawFragment(withDrawHotkey(board))) + return withSwimlane( + withTable( + withDrawResize( + withLineAutoCompleteReaction( + withLineBoundReaction( + withLineResize( + withLineTextMove( + withLineText( + withGeometryResize( + withDrawRotate( + withLineCreateByDraw( + withLineAutoComplete( + withGeometryCreateByDrag( + withGeometryCreateByDrawing(withDrawFragment(withDrawHotkey(board))) + ) ) ) ) diff --git a/packages/draw/src/plugins/with-swimlane.ts b/packages/draw/src/plugins/with-swimlane.ts new file mode 100644 index 000000000..99a6031d0 --- /dev/null +++ b/packages/draw/src/plugins/with-swimlane.ts @@ -0,0 +1,26 @@ +import { PlaitPluginElementContext } from '@plait/core'; +import { PlaitDrawElement, PlaitSwimlane } from '../interfaces'; +import { buildSwimlaneTable } from '../utils/swimlane'; +import { PlaitTableBoard } from './with-table'; +import { PlaitTable } from '../interfaces/table'; +import { TableComponent } from '../table.component'; + +export const withSwimlane = (board: PlaitTableBoard) => { + const { drawElement, buildTable } = board; + + board.drawElement = (context: PlaitPluginElementContext) => { + if (PlaitDrawElement.isSwimlane(context.element)) { + return TableComponent; + } + return drawElement(context); + }; + + board.buildTable = (element: PlaitTable) => { + if (PlaitDrawElement.isSwimlane(element)) { + return buildSwimlaneTable(element as PlaitSwimlane); + } + return buildTable(element); + }; + + return board; +}; diff --git a/packages/draw/src/plugins/with-table.ts b/packages/draw/src/plugins/with-table.ts index cf7ff55ed..da85e98a7 100644 --- a/packages/draw/src/plugins/with-table.ts +++ b/packages/draw/src/plugins/with-table.ts @@ -1,5 +1,5 @@ import { TableComponent } from '../table.component'; -import { PlaitTableElement } from '../interfaces/table'; +import { PlaitTable, PlaitTableElement } from '../interfaces/table'; import { PlaitBoard, PlaitPluginElementContext, @@ -14,17 +14,23 @@ import { } from '@plait/core'; import { editCell, getHitCell } from '../utils/table'; +export interface PlaitTableBoard extends PlaitBoard { + buildTable: (element: PlaitTable) => PlaitTable; +} + export const withTable = (board: PlaitBoard) => { - const { drawElement, getRectangle, isRectangleHit, isHit, isMovable, getDeletedFragment, dblClick } = board; - - board.drawElement = (context: PlaitPluginElementContext) => { + const tableBoard = board as PlaitTableBoard; + + const { drawElement, getRectangle, isRectangleHit, isHit, isMovable, getDeletedFragment, dblClick } = tableBoard; + + tableBoard.drawElement = (context: PlaitPluginElementContext) => { if (PlaitTableElement.isTable(context.element)) { return TableComponent; } return drawElement(context); }; - board.getDeletedFragment = (data: PlaitElement[]) => { + tableBoard.getDeletedFragment = (data: PlaitElement[]) => { const elements = getSelectedElements(board); if (elements.length) { const tableElements = elements.filter(value => PlaitTableElement.isTable(value)); @@ -33,7 +39,7 @@ export const withTable = (board: PlaitBoard) => { return getDeletedFragment(data); }; - board.isHit = (element, point) => { + tableBoard.isHit = (element, point) => { if (PlaitTableElement.isTable(element)) { const client = RectangleClient.getRectangleByPoints(element.points); return RectangleClient.isPointInRectangle(client, point); @@ -41,14 +47,14 @@ export const withTable = (board: PlaitBoard) => { return isHit(element, point); }; - board.getRectangle = (element: PlaitElement) => { + tableBoard.getRectangle = (element: PlaitElement) => { if (PlaitTableElement.isTable(element)) { return RectangleClient.getRectangleByPoints(element.points); } return getRectangle(element); }; - board.isMovable = (element: PlaitElement) => { + tableBoard.isMovable = (element: PlaitElement) => { if (PlaitTableElement.isTable(element)) { return true; } @@ -56,7 +62,7 @@ export const withTable = (board: PlaitBoard) => { return isMovable(element); }; - board.isRectangleHit = (element: PlaitElement, selection: Selection) => { + tableBoard.isRectangleHit = (element: PlaitElement, selection: Selection) => { if (PlaitTableElement.isTable(element)) { const rangeRectangle = RectangleClient.getRectangleByPoints([selection.anchor, selection.focus]); return isPolylineHitRectangle(element.points, rangeRectangle); @@ -64,13 +70,13 @@ export const withTable = (board: PlaitBoard) => { return isRectangleHit(element, selection); }; - board.dblClick = (event: MouseEvent) => { + tableBoard.dblClick = (event: MouseEvent) => { event.preventDefault(); if (!PlaitBoard.isReadonly(board)) { const point = toViewBoxPoint(board, toHostPoint(board, event.x, event.y)); const hitElement = getHitElementByPoint(board, point); if (hitElement && PlaitTableElement.isTable(hitElement)) { - const hitCell = getHitCell(hitElement, point); + const hitCell = getHitCell(tableBoard, hitElement, point); if (hitCell && hitCell.text && hitCell.textHeight) { editCell(hitCell); } @@ -78,6 +84,10 @@ export const withTable = (board: PlaitBoard) => { } dblClick(event); }; - - return board; + + tableBoard.buildTable = (element: PlaitTable) => { + return element; + }; + + return tableBoard; }; diff --git a/packages/draw/src/table.component.ts b/packages/draw/src/table.component.ts index 6e4aaaf9e..18d3dc1e4 100644 --- a/packages/draw/src/table.component.ts +++ b/packages/draw/src/table.component.ts @@ -13,46 +13,50 @@ import { getTextManage, PlaitDrawShapeText, TextGenerator } from './generators/t import { TableGenerator } from './generators/table.generator'; import { TextManageRef } from '@plait/text'; import { DrawTransforms } from './transforms'; -import { getCellsWithPoints, getCellWithPoints } from './utils/table'; +import { getCellWithPoints } from './utils/table'; import { getStrokeWidthByElement, memorizeLatestText } from './utils'; import { getEngine } from './engines'; import { TableSymbols } from './interfaces'; import { getHorizontalTextRectangle } from './engines/table/table'; import { ShapeDefaultSpace } from './constants'; -export class TableComponent extends CommonElementFlavour implements OnContextChanged { - activeGenerator!: ActiveGenerator; +export class TableComponent extends CommonElementFlavour implements OnContextChanged { + activeGenerator!: ActiveGenerator; - tableGenerator!: TableGenerator; + tableGenerator!: TableGenerator; - textGenerator!: TextGenerator; + textGenerator!: TextGenerator; constructor() { super(); } initializeGenerator() { - this.activeGenerator = new ActiveGenerator(this.board, { + this.activeGenerator = new ActiveGenerator(this.board, { getStrokeWidth: () => { return ACTIVE_STROKE_WIDTH; }, getStrokeOpacity: () => { return 1; }, - getRectangle: (element: PlaitTable) => { - return RectangleClient.getRectangleByPoints(element.points); + getRectangle: (value: T) => { + return RectangleClient.getRectangleByPoints(value.points!); }, hasResizeHandle: () => { return canResize(this.board, this.element); } }); - this.tableGenerator = new TableGenerator(this.board); + this.tableGenerator = new TableGenerator(this.board); this.initializeTextManage(); } initialize(): void { super.initialize(); this.initializeGenerator(); + this.draw(); + } + + draw() { this.tableGenerator.processDrawing(this.element, this.getElementG()); this.textGenerator.draw(this.getElementG()); this.rotateVerticalText(); @@ -64,7 +68,7 @@ export class TableComponent extends CommonElementFlavour const textManage = getTextManage(item.id); if (textManage) { const engine = getEngine(TableSymbols.table); - const rectangle = engine.getTextRectangle!(this.element, { key: item.id }); + const rectangle = engine.getTextRectangle!(this.element, { key: item.id, board: this.board }); textManage.g.classList.add('vertical-cell-text'); setAngleForG(textManage.g, RectangleClient.getCenterPoint(rectangle), degreesToRadians(-90)); } @@ -77,7 +81,8 @@ export class TableComponent extends CommonElementFlavour return { key: item.id, text: item.text!, - textHeight: item.textHeight! + textHeight: item.textHeight!, + board: this.board }; }); } @@ -85,24 +90,23 @@ export class TableComponent extends CommonElementFlavour initializeTextManage() { const texts = this.getDrawShapeTexts(this.element.cells); this.textGenerator = new TextGenerator(this.board, this.element, texts, PlaitBoard.getViewContainerRef(this.board), { - onValueChangeHandle: (textManageRef: TextManageRef, text: PlaitDrawShapeText) => { - const cells = getCellsWithPoints(this.element); + onValueChangeHandle: (value: PlaitTable, textManageRef: TextManageRef, text: PlaitDrawShapeText) => { const height = textManageRef.height / this.board.viewport.zoom; const width = textManageRef.width / this.board.viewport.zoom; if (textManageRef.newValue) { DrawTransforms.setTableText( this.board, - this.element, - cells.find(item => item.id === text.key)!, + value, + text.key, textManageRef.newValue, width, height ); } - textManageRef.operations && memorizeLatestText(this.element, textManageRef.operations); + textManageRef.operations && memorizeLatestText(value, textManageRef.operations); }, - getRenderRectangle: (element: PlaitTable, text: PlaitDrawShapeText) => { - const cell = getCellWithPoints(element, text.key); + getRenderRectangle: (value: PlaitTable, text: PlaitDrawShapeText) => { + const cell = getCellWithPoints(this.board, value, text.key); if (PlaitTableElement.isVerticalText(cell)) { const cellRectangle = RectangleClient.getRectangleByPoints(cell.points); const strokeWidth = getStrokeWidthByElement(cell); @@ -122,22 +126,21 @@ export class TableComponent extends CommonElementFlavour this.textGenerator.initialize(); } - onContextChanged( - value: PlaitPluginElementContext, - previous: PlaitPluginElementContext - ) { + onContextChanged(value: PlaitPluginElementContext, previous: PlaitPluginElementContext) { if (value.element !== previous.element) { - this.tableGenerator.processDrawing(this.element, this.getElementG()); - this.activeGenerator.processDrawing(this.element, PlaitBoard.getElementActiveHost(this.board), { selected: this.selected }); + this.tableGenerator.processDrawing(value.element, this.getElementG()); + this.activeGenerator.processDrawing(value.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()); + const currentTexts = this.getDrawShapeTexts(value.element.cells); + this.textGenerator.update(value.element, previousTexts, currentTexts, this.getElementG()); this.rotateVerticalText(); } else { const hasSameSelected = value.selected === previous.selected; const hasSameHandleState = this.activeGenerator.options.hasResizeHandle() === this.activeGenerator.hasResizeHandle; if (!hasSameSelected || !hasSameHandleState) { - this.activeGenerator.processDrawing(this.element, PlaitBoard.getElementActiveHost(this.board), { selected: this.selected }); + this.activeGenerator.processDrawing(value.element, PlaitBoard.getElementActiveHost(this.board), { + selected: this.selected + }); } } } diff --git a/packages/draw/src/transforms/index.ts b/packages/draw/src/transforms/index.ts index 61f90919c..cc19fdf67 100644 --- a/packages/draw/src/transforms/index.ts +++ b/packages/draw/src/transforms/index.ts @@ -2,6 +2,7 @@ import { insertText, insertGeometry, resizeGeometry, switchGeometryShape, insert import { setText, setTextSize } from './geometry-text'; import { insertImage } from './image'; import { connectLineToGeometry, removeLineText, resizeLine, setLineMark, setLineShape, setLineTexts } from './line'; +import { addSwimlaneColumn, addSwimlaneRow, removeSwimlaneColumn, removeSwimlaneRow } from './swimlane'; import { setDrawShapeText } from './multi-text-geometry-text'; import { setTableText } from './table-text'; @@ -21,5 +22,9 @@ export const DrawTransforms = { switchGeometryShape, connectLineToGeometry, insertGeometryByVector, - setTableText + setTableText, + addSwimlaneRow, + addSwimlaneColumn, + removeSwimlaneRow, + removeSwimlaneColumn }; diff --git a/packages/draw/src/transforms/swimlane.ts b/packages/draw/src/transforms/swimlane.ts new file mode 100644 index 000000000..348cf69a4 --- /dev/null +++ b/packages/draw/src/transforms/swimlane.ts @@ -0,0 +1,119 @@ +import { idCreator, PlaitBoard, Point, RectangleClient, Transforms } from '@plait/core'; +import { PlaitDrawElement, PlaitSwimlane, SwimlaneSymbols } from '../interfaces'; +import { PlaitTableCell } from '../interfaces/table'; +import { getCellWithPoints } from '../utils/table'; + +export const addSwimlaneRow = (board: PlaitBoard, swimlane: PlaitSwimlane, index: number) => { + if (PlaitDrawElement.isSwimlane(swimlane) && swimlane.shape === SwimlaneSymbols.swimlaneHorizontal) { + const newRows = [...swimlane.rows]; + const newRowId = idCreator(); + newRows.splice(index, 0, { id: newRowId }); + + let newCells = createNewRowCells(swimlane, newRowId); + newCells.shift(); + newCells = [...swimlane.cells, ...newCells]; + + const lastCellPoints = getCellWithPoints(board, swimlane, swimlane.cells[swimlane.cells.length - 1].id).points; + const lastRowHeight = RectangleClient.getRectangleByPoints(lastCellPoints).height; + const newPoints: Point[] = [swimlane.points[0], [swimlane.points[1][0], swimlane.points[1][1] + lastRowHeight]]; + + updateSwimlane(board, swimlane, swimlane.columns, newRows, newCells, newPoints); + } +}; + +export const addSwimlaneColumn = (board: PlaitBoard, swimlane: PlaitSwimlane, index: number) => { + if (PlaitDrawElement.isSwimlane(swimlane) && swimlane.shape === SwimlaneSymbols.swimlaneVertical) { + const newColumns = [...swimlane.columns]; + const newColumnId = idCreator(); + newColumns.splice(index, 0, { id: newColumnId }); + + let newCells = createNewColumnCells(swimlane, newColumnId); + newCells.shift(); + newCells = [...swimlane.cells, ...newCells]; + + const lastCellPoints = getCellWithPoints(board, swimlane, swimlane.cells[swimlane.cells.length - 1].id).points; + const lastColumnWidth = RectangleClient.getRectangleByPoints(lastCellPoints).width; + const newPoints: Point[] = [swimlane.points[0], [swimlane.points[1][0] + lastColumnWidth, swimlane.points[1][1]]]; + + updateSwimlane(board, swimlane, newColumns, swimlane.rows, newCells, newPoints); + } +}; + +export const removeSwimlaneRow = (board: PlaitBoard, swimlane: PlaitSwimlane, index: number) => { + if (PlaitDrawElement.isSwimlane(swimlane) && swimlane.shape === SwimlaneSymbols.swimlaneHorizontal) { + const newRows = [...swimlane.rows]; + newRows.splice(index, 1); + if (newRows.length === 0) { + const path = PlaitBoard.findPath(board, swimlane); + Transforms.removeNode(board, path); + } else { + const removeRow = swimlane.rows[index]; + const newCells = swimlane.cells.filter(item => item.rowId !== removeRow.id); + let removeRowHeight = removeRow.height; + if (!removeRowHeight) { + const cellPoints = getCellWithPoints(board, swimlane, swimlane.cells[index].id).points; + removeRowHeight = RectangleClient.getRectangleByPoints(cellPoints).height; + } + const newPoints: Point[] = [swimlane.points[0], [swimlane.points[1][0], swimlane.points[1][1] - removeRowHeight]]; + updateSwimlane(board, swimlane, swimlane.columns, newRows, newCells, newPoints); + } + } +}; + +export const removeSwimlaneColumn = (board: PlaitBoard, swimlane: PlaitSwimlane, index: number) => { + if (PlaitDrawElement.isSwimlane(swimlane) && swimlane.shape === SwimlaneSymbols.swimlaneVertical) { + const newColumns = [...swimlane.columns]; + newColumns.splice(index, 1); + if (newColumns.length === 0) { + const path = PlaitBoard.findPath(board, swimlane); + Transforms.removeNode(board, path); + } else { + const removeColumn = swimlane.columns[index]; + const newCells = swimlane.cells.filter(item => item.columnId !== removeColumn.id); + let removeColumnWidth = removeColumn.width; + if (!removeColumnWidth) { + const cellPoints = getCellWithPoints(board, swimlane, swimlane.cells[index].id).points; + removeColumnWidth = RectangleClient.getRectangleByPoints(cellPoints).width; + } + const newPoints: Point[] = [swimlane.points[0], [swimlane.points[1][0] - removeColumnWidth, swimlane.points[1][1]]]; + updateSwimlane(board, swimlane, newColumns, swimlane.rows, newCells, newPoints); + } + } +}; + +const createNewColumnCells = (swimlane: PlaitSwimlane, newColumnId: string): PlaitTableCell[] => { + return swimlane.rows.map(row => ({ + id: idCreator(), + rowId: row.id, + columnId: newColumnId + })); +}; + +const createNewRowCells = (swimlane: PlaitSwimlane, newRowId: string): PlaitTableCell[] => { + return swimlane.columns.map(column => ({ + id: idCreator(), + rowId: newRowId, + columnId: column.id + })); +}; + +const updateSwimlane = ( + board: PlaitBoard, + swimlane: PlaitSwimlane, + newColumns: { id: string; width?: number }[], + newRows: { id: string; height?: number }[], + newCells: PlaitTableCell[], + newPoints: Point[] +) => { + const path = PlaitBoard.findPath(board, swimlane); + Transforms.setNode( + board, + { + columns: newColumns, + rows: newRows, + cells: newCells, + points: newPoints + }, + path + ); +}; diff --git a/packages/draw/src/transforms/table-text.ts b/packages/draw/src/transforms/table-text.ts index 2e8d0bebe..90ec9475a 100644 --- a/packages/draw/src/transforms/table-text.ts +++ b/packages/draw/src/transforms/table-text.ts @@ -1,16 +1,18 @@ import { PlaitBoard, RectangleClient, Transforms } from '@plait/core'; import { ShapeDefaultSpace } from '../constants'; import { Element } from 'slate'; -import { PlaitTable, PlaitTableCell, PlaitTableCellWithPoints, PlaitTableElement } from '../interfaces/table'; +import { PlaitTable, PlaitTableCell, PlaitTableElement } from '../interfaces/table'; +import { getCellWithPoints } from '../utils/table'; export const setTableText = ( board: PlaitBoard, table: PlaitTable, - cell: PlaitTableCellWithPoints, + cellId: string, text: Element, textWidth: number, textHeight: number ) => { + const cell = getCellWithPoints(board, table, cellId); const cellIndex = table.cells.findIndex(item => item.id === cell.id); let rows = [...table.rows]; let columns = [...table.columns]; @@ -46,11 +48,11 @@ export const setTableText = ( } } cells[cellIndex] = { - ...cell, + ...cells[cellIndex], textHeight: textHeight, text }; - const path = board.children.findIndex(child => child === table); + const path = board.children.findIndex(child => child.id === table.id); Transforms.setNode(board, { rows, columns, cells, points }, [path]); }; diff --git a/packages/draw/src/utils/selected.ts b/packages/draw/src/utils/selected.ts index 18da33204..5ff011b73 100644 --- a/packages/draw/src/utils/selected.ts +++ b/packages/draw/src/utils/selected.ts @@ -21,3 +21,8 @@ export const getSelectedImageElements = (board: PlaitBoard) => { const selectedElements = getSelectedElements(board).filter(value => PlaitDrawElement.isImage(value)) as PlaitImage[]; return selectedElements; }; + +export const isSingleSelectSwimlane = (board: PlaitBoard) => { + const selectedElements = getSelectedElements(board); + return selectedElements && selectedElements.length === 1 && PlaitDrawElement.isSwimlane(selectedElements[0]); +}; diff --git a/packages/draw/src/utils/swimlane.ts b/packages/draw/src/utils/swimlane.ts new file mode 100644 index 000000000..aa09723d3 --- /dev/null +++ b/packages/draw/src/utils/swimlane.ts @@ -0,0 +1,28 @@ +import { PlaitDrawElement, PlaitSwimlane } from '../interfaces'; + +export function buildSwimlaneTable(element: PlaitSwimlane) { + const swimlaneElement = { ...element }; + if (PlaitDrawElement.isHorizontalSwimlane(element)) { + swimlaneElement.cells = element.cells.map((item, index) => { + if (index === 0) { + item = { + ...element.cells[0], + rowspan: element.rows.length + }; + } + if (item.text && item.textHeight && !item.text.direction) { + item.text.direction = 'vertical'; + } + return item; + }); + return swimlaneElement; + } + swimlaneElement.cells = [ + { + ...element.cells[0], + colspan: element.columns.length + }, + ...element.cells.slice(1, element.cells.length) + ]; + return swimlaneElement; +} diff --git a/packages/draw/src/utils/table.ts b/packages/draw/src/utils/table.ts index 8dd56c39a..ae858085b 100644 --- a/packages/draw/src/utils/table.ts +++ b/packages/draw/src/utils/table.ts @@ -1,8 +1,11 @@ -import { Point, RectangleClient } from '@plait/core'; +import { PlaitBoard, Point, RectangleClient } from '@plait/core'; import { PlaitTable, PlaitTableCell, PlaitTableCellWithPoints } from '../interfaces/table'; import { getTextManage } from '../generators/text.generator'; +import { PlaitTableBoard } from '../plugins/with-table'; -export function getCellsWithPoints(table: PlaitTable): PlaitTableCellWithPoints[] { + +export function getCellsWithPoints(board: PlaitBoard, element: PlaitTable): PlaitTableCellWithPoints[] { + const table = (board as PlaitTableBoard).buildTable(element); const rectangle = RectangleClient.getRectangleByPoints(table.points); const columnsCount = table.columns.length; const rowsCount = table.rows.length; @@ -41,8 +44,8 @@ export function getCellsWithPoints(table: PlaitTable): PlaitTableCellWithPoints[ return cells; } -export function getCellWithPoints(table: PlaitTable, cellId: string) { - const cells = getCellsWithPoints(table); +export function getCellWithPoints(board: PlaitBoard, table: PlaitTable, cellId: string) { + const cells = getCellsWithPoints(board as PlaitTableBoard, table); const cellIndex = table.cells.findIndex(item => item.id === cellId); return cells[cellIndex]; } @@ -82,8 +85,9 @@ function calculateCellSize(cell: PlaitTableCell, sizes: number[], index: number, return size; } -export function getHitCell(table: PlaitTable, point: Point) { - const cells = getCellsWithPoints(table); +export function getHitCell(board: PlaitTableBoard, element: PlaitTable, point: Point) { + const table = board.buildTable(element); + const cells = getCellsWithPoints(board, table); const rectangle = RectangleClient.getRectangleByPoints([point, point]); const cell = cells.find(item => { const cellRectangle = RectangleClient.getRectangleByPoints(item.points); diff --git a/src/app/components/setting-panel/setting-panel.component.html b/src/app/components/setting-panel/setting-panel.component.html index 35bb65873..4b1027188 100644 --- a/src/app/components/setting-panel/setting-panel.component.html +++ b/src/app/components/setting-panel/setting-panel.component.html @@ -411,3 +411,76 @@ > + +
+
泳道操作:
+
+ + + + +
+
diff --git a/src/app/components/setting-panel/setting-panel.component.ts b/src/app/components/setting-panel/setting-panel.component.ts index 27a5ac54b..3e229d6fe 100644 --- a/src/app/components/setting-panel/setting-panel.component.ts +++ b/src/app/components/setting-panel/setting-panel.component.ts @@ -14,20 +14,7 @@ import { hasSameAngle, canSetZIndex } from '@plait/core'; -import { - DrawTransforms, - GeometryShapes, - LineHandleKey, - LineMarkerType, - LineShape, - PlaitGeometry, - getMemorizeKey, - getSelectedGeometryElements, - getSelectedImageElements, - getSelectedLineElements, - isMultipleTextGeometry -} from '@plait/draw'; -import { MindLayoutType } from '@plait/layouts'; + import { MindElement, MindPointerType, @@ -42,6 +29,22 @@ import { AppColorPickerComponent } from '../color-picker/color-picker.component' import { FormsModule } from '@angular/forms'; import { NgClass, NgIf } from '@angular/common'; import { AlignTransform, PropertyTransforms, TextTransforms, getFirstTextEditor, getTextEditors } from '@plait/common'; +import { + LineShape, + LineMarkerType, + getSelectedLineElements, + isSingleSelectSwimlane, + getSelectedGeometryElements, + isMultipleTextGeometry, + PlaitGeometry, + getSelectedImageElements, + GeometryShapes, + DrawTransforms, + getMemorizeKey, + LineHandleKey, + PlaitSwimlane +} from '@plait/draw'; +import { MindLayoutType } from '@plait/layouts'; @Component({ selector: 'app-setting-panel', @@ -73,6 +76,8 @@ export class AppSettingPanelComponent extends PlaitIslandBaseComponent implement isSelectedLine = false; + isSelectSwimlane = false; + canSetZIndex = false; fillColor = ['#333333', '#e48483', '#69b1e4', '#e681d4', '#a287e1', '']; @@ -91,6 +96,8 @@ export class AppSettingPanelComponent extends PlaitIslandBaseComponent implement lineSourceMarker = LineMarkerType.openTriangle; + swimlaneOperation = 'addRow'; + strokeWidth = 3; angle = 0; @@ -114,6 +121,7 @@ export class AppSettingPanelComponent extends PlaitIslandBaseComponent implement const selectedLineElements = getSelectedLineElements(this.board); this.isSelectedMind = !!selectedMindElements.length; this.isSelectedLine = !!selectedLineElements.length; + this.isSelectSwimlane = isSingleSelectSwimlane(this.board); this.canSetZIndex = canSetZIndex(this.board); if (selectedMindElements.length) { const firstMindElement = selectedMindElements[0]; @@ -326,4 +334,24 @@ export class AppSettingPanelComponent extends PlaitIslandBaseComponent implement event.preventDefault(); Transforms.moveToBottom(this.board); } + + addSwimlaneRow(event: Event) { + const selectedElements = getSelectedElements(this.board) as PlaitSwimlane[]; + DrawTransforms.addSwimlaneRow(this.board, selectedElements[0], selectedElements[0].rows.length); + } + + removeSwimlaneRow(event: Event) { + const selectedElements = getSelectedElements(this.board) as PlaitSwimlane[]; + DrawTransforms.removeSwimlaneRow(this.board, selectedElements[0], selectedElements[0].rows.length - 1); + } + + addSwimlaneColumn(event: Event) { + const selectedElements = getSelectedElements(this.board) as PlaitSwimlane[]; + DrawTransforms.addSwimlaneColumn(this.board, selectedElements[0], selectedElements[0].columns.length); + } + + removeSwimlaneColumn(event: Event) { + const selectedElements = getSelectedElements(this.board) as PlaitSwimlane[]; + DrawTransforms.removeSwimlaneColumn(this.board, selectedElements[0], selectedElements[0].columns.length - 1); + } } diff --git a/src/app/editor/editor.component.ts b/src/app/editor/editor.component.ts index 867bb9b32..c70695a3e 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, mockTableData, mockMindData, mockRotateData, mockGroupData } from './mock-data'; +import { mockDrawData, mockTableData, mockMindData, mockRotateData, mockGroupData, mockSwimlaneData } from './mock-data'; import { withMind, PlaitMindBoard, PlaitMind } from '@plait/mind'; import { AbstractResizeState, MindThemeColors } from '@plait/mind'; import { withMindExtend } from '../plugins/with-mind-extend'; @@ -138,6 +138,9 @@ export class BasicEditorComponent implements OnInit { case 'table': this.value = [...mockTableData]; break; + case 'swimlane': + this.value = [...mockSwimlaneData]; + break; case 'rotate': this.value = [...mockRotateData]; break; diff --git a/src/app/editor/mock-data.ts b/src/app/editor/mock-data.ts index c078eac40..0d40f2630 100644 --- a/src/app/editor/mock-data.ts +++ b/src/app/editor/mock-data.ts @@ -706,6 +706,177 @@ export const mockTableData: PlaitDrawElement[] = [ } ] as PlaitDrawElement[]; +export const mockSwimlaneData: PlaitDrawElement[] = [ + { + id: 'swimlaneVertical', + points: [ + [-100, -100], + [200, 400] + ], + type: 'table', + shape: 'swimlaneVertical', + rows: [ + { + id: 'row-1', + height: 30 + }, + { + id: 'row-2', + height: 30 + }, + { + id: 'row-3' + } + ], + columns: [ + { + id: 'column-1' + }, + { + id: 'column-2' + } + ], + cells: [ + { + id: 'v-cell-1-1', + rowId: 'row-1', + columnId: 'column-1', + textHeight: 20, + text: { + children: [ + { + text: '垂直泳道' + } + ], + align: 'center' + } + }, + { + id: 'v-cell-2-1', + rowId: 'row-2', + textHeight: 20, + columnId: 'column-1', + text: { + children: [ + { + text: '' + } + ], + align: 'center' + } + }, + { + id: 'v-cell-2-2', + rowId: 'row-2', + textHeight: 20, + columnId: 'column-2', + text: { + children: [ + { + text: '' + } + ], + align: 'center' + } + }, + { + id: 'v-cell-3-1', + rowId: 'row-3', + columnId: 'column-1' + }, + { + id: 'v-cell-3-2', + rowId: 'row-3', + columnId: 'column-2' + } + ] + }, + { + id: 'swimlaneHorizontal', + points: [ + [300, 0], + [900, 300] + ], + type: 'table', + shape: 'swimlaneHorizontal', + rows: [ + { + id: 'row-1' + }, + { + id: 'row-2' + } + ], + columns: [ + { + id: 'column-1', + width: 30 + }, + { + id: 'column-2', + width: 30 + }, + { + id: 'column-3' + } + ], + cells: [ + { + id: 'h-cell-1-1', + rowId: 'row-1', + columnId: 'column-1', + textHeight: 20, + text: { + children: [ + { + text: '水平泳道' + } + ], + align: 'center' + } + }, + { + id: 'h-cell-1-2', + rowId: 'row-1', + columnId: 'column-2', + textHeight: 20, + text: { + children: [ + { + text: '' + } + ], + align: 'center' + } + }, + { + id: 'h-cell-1-3', + rowId: 'row-1', + columnId: 'column-3' + }, + { + id: 'h-cell-2-2', + rowId: 'row-2', + columnId: 'column-2', + textHeight: 20, + text: { + children: [ + { + text: '' + } + ], + align: 'center' + } + }, + { + id: 'h-cell-2-3', + rowId: 'row-2', + columnId: 'column-3' + } + ] + } +] as PlaitDrawElement[]; + export const mockGroupData: PlaitDrawElement[] = [ { id: 'group1',