diff --git a/.changeset/great-suns-drum.md b/.changeset/great-suns-drum.md new file mode 100644 index 000000000..9e97f3b59 --- /dev/null +++ b/.changeset/great-suns-drum.md @@ -0,0 +1,7 @@ +--- +'@plait/draw': patch +--- + +support text edit in cell + +add KEY_TO_TEXT_MANAGE Map diff --git a/packages/draw/src/generators/single-text.generator.ts b/packages/draw/src/generators/single-text.generator.ts index 7d309e727..325d6bc2b 100644 --- a/packages/draw/src/generators/single-text.generator.ts +++ b/packages/draw/src/generators/single-text.generator.ts @@ -30,8 +30,8 @@ export class SingleTextGenerator extends if (!isMultipleTextGeometry((element as unknown) as PlaitCommonGeometry)) { super.update( element, - [{ text: previousText as ParagraphElement, key: element.shape, textHeight: element.textHeight }], - [{ text: currentText as ParagraphElement, key: element.shape, textHeight: element.textHeight }], + [{ text: previousText as ParagraphElement, key: element.id, textHeight: element.textHeight }], + [{ text: currentText as ParagraphElement, key: element.id, textHeight: element.textHeight }], elementG ); } diff --git a/packages/draw/src/generators/table.generator.ts b/packages/draw/src/generators/table.generator.ts index 791d221ee..f046e0f89 100644 --- a/packages/draw/src/generators/table.generator.ts +++ b/packages/draw/src/generators/table.generator.ts @@ -3,6 +3,7 @@ import { Generator } from '@plait/common'; import { RectangleClient } from '@plait/core'; import { PlaitTable } from '../interfaces/table'; import { getEngine } from '../engines'; +import { getDrawDefaultStrokeColor } from '../utils'; export interface TableData {} @@ -17,8 +18,8 @@ export class TableGenerator extends Generator { this.board, rectangle, { - strokeWidth: 1, - stroke: '#333' + strokeWidth: 2, + stroke: getDrawDefaultStrokeColor(this.board.theme.themeColorMode), }, { element: element diff --git a/packages/draw/src/generators/text.generator.ts b/packages/draw/src/generators/text.generator.ts index 8fb3f63c9..e0daa6c87 100644 --- a/packages/draw/src/generators/text.generator.ts +++ b/packages/draw/src/generators/text.generator.ts @@ -16,6 +16,20 @@ export interface TextGeneratorOptions { onValueChangeHandle: (textChangeRef: TextManageRef, text: PlaitDrawShapeText) => void; } +export const KEY_TO_TEXT_MANAGE: Map = new Map(); + +export const setTextManage = (key: string, textManage: TextManage) => { + return KEY_TO_TEXT_MANAGE.set(key, textManage); +}; + +export const getTextManage = (key: string) => { + return KEY_TO_TEXT_MANAGE.get(key); +}; + +export const deleteTextManage = (key: string) => { + return KEY_TO_TEXT_MANAGE.delete(key); +}; + export class TextGenerator { protected board: PlaitBoard; @@ -50,15 +64,18 @@ export class TextGenerator { initialize() { const textPlugins = ((this.board as PlaitOptionsBoard).getPluginOptions(WithTextPluginKey) || {}).textPlugins; this.textManages = this.texts.map(text => { - return this.createTextManage(text, textPlugins); + const textManage = this.createTextManage(text, textPlugins); + setTextManage(text.key, textManage); + return textManage; }); + ELEMENT_TO_TEXT_MANAGES.set(this.element, this.textManages); } 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) { + this.texts.forEach(drawShapeText => { + const textManage = getTextManage(drawShapeText.key); + if (drawShapeText.text && textManage) { textManage.draw(drawShapeText.text); elementG.append(textManage.g); this.element.angle && textManage.updateAngle(centerPoint, this.element.angle); @@ -68,10 +85,11 @@ export class TextGenerator { update(element: T, previousDrawShapeTexts: PlaitDrawShapeText[], currentDrawShapeTexts: PlaitDrawShapeText[], elementG: SVGElement) { this.element = element; + ELEMENT_TO_TEXT_MANAGES.set(this.element, this.textManages); const centerPoint = RectangleClient.getCenterPoint(this.board.getRectangle(this.element)!); - currentDrawShapeTexts.forEach((drawShapeText, index) => { - const textManage = this.textManages[index]; - if (drawShapeText.text) { + currentDrawShapeTexts.forEach(drawShapeText => { + const textManage = getTextManage(drawShapeText.key); + if (drawShapeText.text && textManage) { textManage.updateText(drawShapeText.text); textManage.updateRectangle(); this.element.angle && textManage.updateAngle(centerPoint, this.element.angle); @@ -117,5 +135,6 @@ export class TextGenerator { }); this.textManages = []; ELEMENT_TO_TEXT_MANAGES.delete(this.element); + KEY_TO_TEXT_MANAGE.clear(); } } diff --git a/packages/draw/src/geometry.component.ts b/packages/draw/src/geometry.component.ts index 45b1c313c..874a98b9d 100644 --- a/packages/draw/src/geometry.component.ts +++ b/packages/draw/src/geometry.component.ts @@ -90,7 +90,6 @@ export class GeometryComponent extends CommonPluginElement, previous: PlaitPluginElementContext ) { - this.initializeWeakMap(); const isChangeTheme = this.board.operations.find(op => op.type === 'set_theme'); if (value.element !== previous.element || isChangeTheme) { this.shapeGenerator.processDrawing(this.element as PlaitGeometry, this.getElementG()); @@ -162,9 +161,7 @@ export class GeometryComponent extends CommonPluginElement { - const { drawElement, getRectangle, isRectangleHit, isHit, isMovable, getDeletedFragment } = board; + const { drawElement, getRectangle, isRectangleHit, isHit, isMovable, getDeletedFragment, dblClick } = board; board.drawElement = (context: PlaitPluginElementContext) => { if (PlaitTableElement.isTable(context.element)) { return TableComponent; @@ -58,5 +62,21 @@ export const withTable = (board: PlaitBoard) => { } return isRectangleHit(element, selection); }; + + board.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); + if (hitCell && hitCell.text && hitCell.textHeight) { + editCell(hitCell); + } + } + } + dblClick(event); + }; + return board; }; diff --git a/packages/draw/src/table.component.ts b/packages/draw/src/table.component.ts index b8e90c2de..3d0889e5b 100644 --- a/packages/draw/src/table.component.ts +++ b/packages/draw/src/table.component.ts @@ -5,6 +5,9 @@ import { PlaitTable, PlaitTableCell } from './interfaces/table'; import { PlaitDrawShapeText, TextGenerator } from './generators/text.generator'; import { TableGenerator } from './generators/table.generator'; import { TextManageRef } from '@plait/text'; +import { DrawTransforms } from './transforms'; +import { getCellsWithPoints } from './utils/table'; +import { memorizeLatestText } from './utils'; @Component({ selector: 'plait-draw-table', @@ -63,23 +66,30 @@ export class TableComponent extends CommonPluginElement initializeTextManage() { const texts = this.getDrawShapeTexts(this.element.cells); this.textGenerator = new TextGenerator(this.board, this.element, texts, this.viewContainerRef, { - onValueChangeHandle: (textChangeRef: TextManageRef, text: PlaitDrawShapeText) => {} + onValueChangeHandle: (textManageRef: TextManageRef, text: PlaitDrawShapeText) => { + const cells = getCellsWithPoints(this.element); + 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)!, + textManageRef.newValue, + width, + height + ); + } + textManageRef.operations && memorizeLatestText(this.element, textManageRef.operations); + } }); 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 }); diff --git a/packages/draw/src/transforms/index.ts b/packages/draw/src/transforms/index.ts index a0d5d48bf..fa0d141d0 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 { setTableText } from './table-text'; export const DrawTransforms = { setText, @@ -17,5 +18,6 @@ export const DrawTransforms = { insertImage, switchGeometryShape, connectLineToGeometry, - insertGeometryByVector + insertGeometryByVector, + setTableText }; diff --git a/packages/draw/src/transforms/table-text.ts b/packages/draw/src/transforms/table-text.ts new file mode 100644 index 000000000..6c965fb94 --- /dev/null +++ b/packages/draw/src/transforms/table-text.ts @@ -0,0 +1,45 @@ +import { PlaitBoard, RectangleClient, Transforms } from '@plait/core'; +import { ShapeDefaultSpace } from '../constants'; +import { Element } from 'slate'; +import { PlaitTable, PlaitTableCellParagraph, PlaitTableCellWithPoints } from '../interfaces/table'; + +export const setTableText = ( + board: PlaitBoard, + table: PlaitTable, + cell: PlaitTableCellWithPoints, + text: Element, + textWidth: number, + textHeight: number +) => { + const cellIndex = table.cells.findIndex(item => item.id === cell.id); + let rows = [...table.rows]; + let cells = [...table.cells]; + let points = [...table.points]; + const { height: cellHeight } = RectangleClient.getRectangleByPoints(cell.points); + if (cell.text!.writingMode === 'vertical-lr') { + // 文字高度发生改变,修改该列的宽度 + // update table width + } else { + const rowIdx = table.rows.findIndex(row => row.id === cell.rowId); + const tableRow = table.rows[rowIdx]; + const compareHeight = tableRow.height ?? Math.max(cellHeight, cell.textHeight || 0); + if (textHeight > compareHeight) { + const defaultSpace = ShapeDefaultSpace.rectangleAndText; + const newRowHeight = textHeight + defaultSpace * 2; + rows[rowIdx] = { + ...tableRow, + height: newRowHeight + }; + // update table height + const offset = newRowHeight - compareHeight; + points = [points[0], [points[1][0], points[1][1] + offset]]; + } + } + cells[cellIndex] = { + ...cell, + textHeight: textHeight, + text: text as PlaitTableCellParagraph + }; + const path = board.children.findIndex(child => child === table); + Transforms.setNode(board, { rows, cells, points }, [path]); +}; diff --git a/packages/draw/src/utils/table.ts b/packages/draw/src/utils/table.ts index 9636ee98c..89ac8494d 100644 --- a/packages/draw/src/utils/table.ts +++ b/packages/draw/src/utils/table.ts @@ -1,5 +1,6 @@ -import { RectangleClient } from '@plait/core'; +import { Point, RectangleClient } from '@plait/core'; import { PlaitTable, PlaitTableCell, PlaitTableCellWithPoints } from '../interfaces/table'; +import { getTextManage } from '../generators/text.generator'; export function getCellsWithPoints(table: PlaitTable): PlaitTableCellWithPoints[] { const rectangle = RectangleClient.getRectangleByPoints(table.points); @@ -74,3 +75,25 @@ function calculateCellSize(cell: PlaitTableCell, sizes: number[], index: number, } return size; } + +export function getHitCell(table: PlaitTable, point: Point) { + const cells = getCellsWithPoints(table); + const rectangle = RectangleClient.getRectangleByPoints([point, point]); + const cell = cells.find(item => { + const cellRectangle = RectangleClient.getRectangleByPoints(item.points); + return RectangleClient.isHit(rectangle, cellRectangle); + }); + if (cell) { + return table.cells.find(item => item.id === cell.id); + } + return null; +} + +export function editCell(cell: PlaitTableCell) { + const textManage = getTextManageByCell(cell); + textManage && textManage.edit(); +} + +export function getTextManageByCell(cell: PlaitTableCell) { + return getTextManage(cell.id); +} diff --git a/src/app/editor/mock-data.ts b/src/app/editor/mock-data.ts index 3aea3a6a2..8e87eda00 100644 --- a/src/app/editor/mock-data.ts +++ b/src/app/editor/mock-data.ts @@ -396,7 +396,7 @@ export const mockTableData: PlaitDrawElement[] = [ ], cells: [ { - id: 'cell-1-1', + id: 'v-cell-1-1', rowId: 'row-1', columnId: 'column-1', colspan: 3, @@ -411,7 +411,7 @@ export const mockTableData: PlaitDrawElement[] = [ } }, { - id: 'cell-2-1', + id: 'v-cell-2-1', rowId: 'row-2', textHeight: 20, columnId: 'column-1', @@ -425,52 +425,116 @@ export const mockTableData: PlaitDrawElement[] = [ } }, { - id: 'cell-2-2', + id: 'v-cell-2-2', rowId: 'row-2', textHeight: 20, - columnId: 'column-2' + columnId: 'column-2', + text: { + children: [ + { + text: '' + } + ], + align: 'center' + } }, { - id: 'cell-2-3', + id: 'v-cell-2-3', rowId: 'row-2', textHeight: 20, - columnId: 'column-3' + columnId: 'column-3', + text: { + children: [ + { + text: '' + } + ], + align: 'center' + } }, { - id: 'cell-3-1', + id: 'v-cell-3-1', rowId: 'row-3', textHeight: 20, - columnId: 'column-1' + columnId: 'column-1', + text: { + children: [ + { + text: '' + } + ], + align: 'center' + } }, { - id: 'cell-3-2', + id: 'v-cell-3-2', rowId: 'row-3', textHeight: 20, - columnId: 'column-2' + columnId: 'column-2', + text: { + children: [ + { + text: '' + } + ], + align: 'center' + } }, { - id: 'cell-3-3', + id: 'v-cell-3-3', rowId: 'row-3', textHeight: 20, - columnId: 'column-3' + columnId: 'column-3', + text: { + children: [ + { + text: '' + } + ], + align: 'center' + } }, { - id: 'cell-4-1', + id: 'v-cell-4-1', rowId: 'row-4', textHeight: 20, - columnId: 'column-1' + columnId: 'column-1', + text: { + children: [ + { + text: '' + } + ], + align: 'center' + } }, { - id: 'cell-4-2', + id: 'v-cell-4-2', rowId: 'row-4', textHeight: 20, - columnId: 'column-2' + columnId: 'column-2', + text: { + children: [ + { + text: '' + } + ], + align: 'center' + } }, { - id: 'cell-4-3', + id: 'v-cell-4-3', rowId: 'row-4', textHeight: 20, - columnId: 'column-3' + columnId: 'column-3', + text: { + children: [ + { + text: '' + } + ], + align: 'center' + } } ] }, @@ -510,7 +574,7 @@ export const mockTableData: PlaitDrawElement[] = [ ], cells: [ { - id: 'cell-1-1', + id: 'h-cell-1-1', rowId: 'row-1', columnId: 'column-1', textHeight: 20, @@ -525,37 +589,77 @@ export const mockTableData: PlaitDrawElement[] = [ } }, { - id: 'cell-1-2', + id: 'h-cell-1-2', rowId: 'row-1', textHeight: 20, - columnId: 'column-2' + columnId: 'column-2', + text: { + children: [ + { + text: '' + } + ], + align: 'center' + } }, { - id: 'cell-1-3', + id: 'h-cell-1-3', rowId: 'row-1', textHeight: 20, - columnId: 'column-3' + columnId: 'column-3', + text: { + children: [ + { + text: '' + } + ], + align: 'center' + } }, { - id: 'cell-2-2', + id: 'h-cell-2-2', rowId: 'row-2', textHeight: 20, - columnId: 'column-2' + columnId: 'column-2', + text: { + children: [ + { + text: '' + } + ], + align: 'center' + } }, { - id: 'cell-2-3', + id: 'h-cell-2-3', rowId: 'row-2', textHeight: 20, - columnId: 'column-3' + columnId: 'column-3', + text: { + children: [ + { + text: '' + } + ], + align: 'center' + } }, { - id: 'cell-3-2', + id: 'h-cell-3-2', rowId: 'row-3', textHeight: 20, - columnId: 'column-2' + columnId: 'column-2', + text: { + children: [ + { + text: '' + } + ], + align: 'center' + } }, { - id: 'cell-3-3', + id: 'h-cell-3-3', rowId: 'row-3', textHeight: 20, columnId: 'column-3', @@ -569,16 +673,32 @@ export const mockTableData: PlaitDrawElement[] = [ } }, { - id: 'cell-4-2', + id: 'h-cell-4-2', rowId: 'row-4', textHeight: 20, - columnId: 'column-2' + columnId: 'column-2', + text: { + children: [ + { + text: '' + } + ], + align: 'center' + } }, { - id: 'cell-4-3', + id: 'h-cell-4-3', rowId: 'row-4', textHeight: 20, - columnId: 'column-3' + columnId: 'column-3', + text: { + children: [ + { + text: '' + } + ], + align: 'center' + } } ] }