Skip to content

Commit

Permalink
feat(draw): support table and cell resize #WIK-15356 (#886)
Browse files Browse the repository at this point in the history
  • Loading branch information
huanhuanwa authored May 22, 2024
1 parent c91c72c commit ce5ee71
Show file tree
Hide file tree
Showing 12 changed files with 239 additions and 48 deletions.
6 changes: 6 additions & 0 deletions .changeset/wild-carrots-sparkle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@plait/common': minor
'@plait/draw': minor
---

support table and cell resize
2 changes: 1 addition & 1 deletion docs/guides/solutions/with-resize.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ order: 5
思维导图节点内图片,图形,连线都有调整大小功能,并且流程基本相似,所以抽取通用插件 with-resize 处理通用功能,其他部分根据配置信息进行处理。

```
export interface WithResizeOptions {
export interface WithResizeOptions> {
key: string;
//是否能resize
canResize: () => boolean;
Expand Down
17 changes: 9 additions & 8 deletions packages/common/src/plugins/with-resize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,23 @@ import {
} from '@plait/core';
import { ResizeHandle } from '../constants/resize';
import { addResizing, isResizing, removeResizing } from '../utils/resize';
import { PlaitElementOrArray, ResizeHitTestRef, ResizeRef, WithResizeOptions } from '../types/resize';
import { PlaitElementOrArray, ResizeOptions, ResizeHitTestRef, ResizeRef, WithResizeOptions } from '../types/resize';

const generalCanResize = (board: PlaitBoard, event: PointerEvent) => {
return (
!PlaitBoard.isReadonly(board) && !PlaitBoard.hasBeenTextEditing(board) && PlaitBoard.isPointer(board, PlaitPointerType.selection)
);
};

export const withResize = <T extends PlaitElementOrArray = PlaitElementOrArray, K = ResizeHandle>(
export const withResize = <T extends PlaitElementOrArray = PlaitElementOrArray, K = ResizeHandle, P = ResizeOptions>(
board: PlaitBoard,
options: WithResizeOptions<T, K>
options: WithResizeOptions<T, K, P>
) => {
const { pointerDown, pointerMove, globalPointerUp } = board;
let resizeHitTestRef: ResizeHitTestRef<T, K> | null = null;
let resizeRef: ResizeRef<T, K> | null = null;
let resizeHitTestRef: ResizeHitTestRef<T, K, P> | null = null;
let resizeRef: ResizeRef<T, K, P> | null = null;
let startPoint: Point | null = null;
let hoverHitTestRef: ResizeHitTestRef<T, K> | null = null;
let hoverHitTestRef: ResizeHitTestRef<T, K, P> | null = null;

board.pointerDown = (event: PointerEvent) => {
if (!options.canResize() || !generalCanResize(board, event) || !isMainPointer(event)) {
Expand All @@ -53,7 +53,8 @@ export const withResize = <T extends PlaitElementOrArray = PlaitElementOrArray,
element: resizeHitTestRef.element,
handle: resizeHitTestRef.handle,
handleIndex: resizeHitTestRef.handleIndex,
rectangle: resizeHitTestRef.rectangle
rectangle: resizeHitTestRef.rectangle,
options: resizeHitTestRef.options
};
preventTouchMove(board, event, true);
// prevent text from being selected when user pressed shift and pointer down
Expand Down Expand Up @@ -136,7 +137,7 @@ export const withResize = <T extends PlaitElementOrArray = PlaitElementOrArray,
}
hoverHitTestRef = null;
}
}
};

return board;
};
18 changes: 11 additions & 7 deletions packages/common/src/types/resize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,24 @@ import { ResizeHandle } from '../constants/resize';

export type PlaitElementOrArray = PlaitElement | PlaitElement[];

export interface WithResizeOptions<T extends PlaitElementOrArray = PlaitElementOrArray, K = ResizeHandle> {
export interface ResizeOptions {}

export interface WithResizeOptions<T extends PlaitElementOrArray = PlaitElementOrArray, K = ResizeHandle, P = ResizeOptions> {
key: string;
canResize: () => boolean;
hitTest: (point: Point) => ResizeHitTestRef<T, K> | null;
onResize: (resizeRef: ResizeRef<T, K>, resizeState: ResizeState) => void;
afterResize?: (resizeRef: ResizeRef<T, K>) => void;
beforeResize?: (resizeRef: ResizeRef<T, K>) => void;
hitTest: (point: Point) => ResizeHitTestRef<T, K, P> | null;
onResize: (resizeRef: ResizeRef<T, K, P>, resizeState: ResizeState) => void;
afterResize?: (resizeRef: ResizeRef<T, K, P>) => void;
beforeResize?: (resizeRef: ResizeRef<T, K, P>) => void;
}

export interface ResizeHitTestRef<T extends PlaitElementOrArray = PlaitElementOrArray, K = ResizeHandle> {
export interface ResizeHitTestRef<T extends PlaitElementOrArray = PlaitElementOrArray, K = ResizeHandle, P = ResizeOptions> {
element: T;
rectangle?: RectangleClient;
handle: K;
handleIndex?: number;
cursorClass?: ResizeCursorClass;
options?: P;
}

export interface ResizeState {
Expand All @@ -26,10 +29,11 @@ export interface ResizeState {
isShift: boolean;
}

export interface ResizeRef<T extends PlaitElementOrArray = PlaitElementOrArray, K = ResizeHandle> {
export interface ResizeRef<T extends PlaitElementOrArray = PlaitElementOrArray, K = ResizeHandle, P = ResizeOptions> {
element: T;
rectangle?: RectangleClient;
path: Path | Path[];
handle: K;
handleIndex?: number;
options?: P;
}
4 changes: 2 additions & 2 deletions packages/common/src/utils/resize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ const getResizeSideRectangles = (cornerPoints: Point[], offset: number): Rectang
return result;
};

export const IS_RESIZING = new WeakMap<PlaitBoard, ResizeRef<any, any>>();
export const IS_RESIZING = new WeakMap<PlaitBoard, ResizeRef<any, any, any>>();

export const isResizing = (board: PlaitBoard) => {
return !!IS_RESIZING.get(board);
Expand All @@ -120,7 +120,7 @@ export const isResizingByCondition = <T extends PlaitElementOrArray, K>(
return isResizing(board) && match(IS_RESIZING.get(board)!);
};

export const addResizing = <T extends PlaitElementOrArray, K>(board: PlaitBoard, resizeRef: ResizeRef<T, K>, key: string) => {
export const addResizing = <T extends PlaitElementOrArray, K, P>(board: PlaitBoard, resizeRef: ResizeRef<T, K, P>, key: string) => {
PlaitBoard.getBoardContainer(board).classList.add(`${key}-resizing`);
IS_RESIZING.set(board, resizeRef);
setDragging(board, true);
Expand Down
29 changes: 16 additions & 13 deletions packages/draw/src/plugins/with-draw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { getLinePoints, getLineTextRectangle } from '../utils/line/line-basic';
import { withDrawRotate } from './with-draw-rotate';
import { withTable } from './with-table';
import { withSwimlane } from './with-swimlane';
import { withTableResize } from './with-table-resize';

export const withDraw = (board: PlaitBoard) => {
const { drawElement, getRectangle, isRectangleHit, isHit, isInsidePoint, isMovable, isAlign, getRelatedFragment } = board;
Expand Down Expand Up @@ -127,19 +128,21 @@ export const withDraw = (board: PlaitBoard) => {
};

return withSwimlane(
withTable(
withDrawResize(
withLineAutoCompleteReaction(
withLineBoundReaction(
withLineResize(
withLineTextMove(
withLineText(
withGeometryResize(
withDrawRotate(
withLineCreateByDraw(
withLineAutoComplete(
withGeometryCreateByDrag(
withGeometryCreateByDrawing(withDrawFragment(withDrawHotkey(board)))
withTableResize(
withTable(
withDrawResize(
withLineAutoCompleteReaction(
withLineBoundReaction(
withLineResize(
withLineTextMove(
withLineText(
withGeometryResize(
withDrawRotate(
withLineCreateByDraw(
withLineAutoComplete(
withGeometryCreateByDrag(
withGeometryCreateByDrawing(withDrawFragment(withDrawHotkey(board)))
)
)
)
)
Expand Down
1 change: 0 additions & 1 deletion packages/draw/src/plugins/with-geometry-resize.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {
Path,
PlaitBoard,
PlaitElement,
Point,
RectangleClient,
Transforms,
Expand Down
154 changes: 154 additions & 0 deletions packages/draw/src/plugins/with-table-resize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { getHitElementByPoint, PlaitBoard, Point, RectangleClient, Transforms, isSelectedElement } from '@plait/core';
import { PlaitTable, PlaitTableCellWithPoints, PlaitTableElement } from '../interfaces/table';
import {
getIndexByResizeHandle,
isCornerHandle,
ResizeOptions,
ResizeHandle,
ResizeRef,
ResizeState,
withResize,
WithResizeOptions,
normalizeShapePoints
} from '@plait/common';
import { getCellsWithPoints, updateColumns, updateRows } from '../utils/table';
import { getHitRectangleResizeHandleRef } from '../utils/position/geometry';
import { PlaitTableBoard } from './with-table';
import { getResizeOriginPointAndHandlePoint, getResizeZoom, movePointByZoomAndOriginPoint } from './with-draw-resize';
import { isSingleSelectTable } from '../utils';
import { getSnapResizingRef, getSnapResizingRefOptions } from '../utils/snap-resizing';

interface TableResizeOptions extends ResizeOptions {
cell: PlaitTableCellWithPoints;
}

const MIN_CELL_SIZE = 20;

export function withTableResize(board: PlaitTableBoard) {
let snapG: SVGGElement | null;

const options: WithResizeOptions<PlaitTable, ResizeHandle, TableResizeOptions> = {
key: 'draw-table',
canResize: () => {
return true;
},
hitTest: (point: Point) => {
const hitElement = getHitElementByPoint(board, point);
if (hitElement && PlaitTableElement.isTable(hitElement)) {
let rectangle = board.getRectangle(hitElement) as RectangleClient;
let handleRef = getHitRectangleResizeHandleRef(board, rectangle, point, hitElement.angle);
if (handleRef) {
const selectElement = isSelectedElement(board, hitElement);
if ((selectElement && isSingleSelectTable(board)) || (!selectElement && !isCornerHandle(board, handleRef.handle))) {
return {
element: hitElement,
handle: handleRef.handle,
cursorClass: handleRef.cursorClass,
rectangle
};
}
}
const cells = getCellsWithPoints(board, hitElement);
for (let i = 0; i < cells.length; i++) {
rectangle = RectangleClient.getRectangleByPoints(cells[i].points);
handleRef = getHitRectangleResizeHandleRef(board, rectangle, point, 0);
if (handleRef && !isCornerHandle(board, handleRef.handle)) {
return {
element: hitElement,
handle: handleRef.handle,
cursorClass: handleRef.cursorClass,
rectangle,
options: {
cell: cells[i]
}
};
}
}
}
return null;
},
onResize: (resizeRef: ResizeRef<PlaitTable, ResizeHandle, TableResizeOptions>, resizeState: ResizeState) => {
snapG?.remove();
const path = PlaitBoard.findPath(board, resizeRef.element);
if (resizeRef.options?.cell && resizeRef.rectangle) {
const handleIndex = getIndexByResizeHandle(resizeRef.handle);
const { originPoint, handlePoint } = getResizeOriginPointAndHandlePoint(board, handleIndex, resizeRef.rectangle!);
const resizePoints: [Point, Point] = [resizeState.startPoint, resizeState.endPoint];
const { xZoom, yZoom } = getResizeZoom(resizePoints, originPoint, handlePoint, false, false);
const originPoints = resizeRef.options?.cell.points;
const targetPoints = originPoints.map(p => {
return movePointByZoomAndOriginPoint(p, originPoint, xZoom, yZoom);
}) as [Point, Point];
const offsetX = targetPoints[1][0] - originPoints[1][0];
const offsetY = targetPoints[1][1] - originPoints[1][1];
const width = targetPoints[1][0] - targetPoints[0][0];
const height = targetPoints[1][1] - targetPoints[0][1];
if (offsetX !== 0 && width >= MIN_CELL_SIZE) {
const { columns, points } = updateColumns(resizeRef.element, resizeRef.options?.cell.columnId, width, offsetX);
Transforms.setNode(board, { columns, points }, path);
} else if (offsetY !== 0 && height >= MIN_CELL_SIZE) {
const { rows, points } = updateRows(resizeRef.element, resizeRef.options?.cell.rowId, height, offsetY);
Transforms.setNode(board, { rows, points }, path);
}
} else {
const isFromCorner = isCornerHandle(board, resizeRef.handle);
const isAspectRatio = resizeState.isShift;
const handleIndex = getIndexByResizeHandle(resizeRef.handle);
const { originPoint, handlePoint } = getResizeOriginPointAndHandlePoint(board, handleIndex, resizeRef.rectangle!);
const resizeSnapRefOptions = getSnapResizingRefOptions(
board,
resizeRef,
resizeState,
{
originPoint,
handlePoint
},
isAspectRatio,
isFromCorner
);
const resizeSnapRef = getSnapResizingRef(board, [resizeRef.element], resizeSnapRefOptions);
snapG = resizeSnapRef.snapG;
PlaitBoard.getElementActiveHost(board).append(snapG);
const points = resizeSnapRef.activePoints as [Point, Point];
const originPoints = resizeRef.element.points;
const originRect = RectangleClient.getRectangleByPoints(originPoints);
const targetRect = RectangleClient.getRectangleByPoints(points);
const offsetWidth = targetRect.width - originRect.width;
const offsetHeight = targetRect.height - originRect.height;
let columns = [...resizeRef.element.columns];
let rows = [...resizeRef.element.rows];
if (offsetWidth !== 0) {
columns = columns.map(item => {
if (item.width) {
return {
...item,
width: item.width + offsetWidth * (item.width / originRect.width)
};
}
return item;
});
}
if (offsetHeight !== 0) {
rows = rows.map(item => {
if (item.height) {
return {
...item,
height: item.height + offsetHeight * (item.height / originRect.height)
};
}
return item;
});
}
Transforms.setNode(board, { points: normalizeShapePoints(points), columns, rows }, path);
}
},
afterResize: (resizeRef: ResizeRef<PlaitTable, ResizeHandle, TableResizeOptions>) => {
snapG?.remove();
snapG = null;
}
};

withResize<PlaitTable, ResizeHandle, TableResizeOptions>(board, options);

return board;
}
20 changes: 7 additions & 13 deletions packages/draw/src/transforms/table-text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { PlaitBoard, RectangleClient, Transforms } from '@plait/core';
import { ShapeDefaultSpace } from '../constants';
import { Element } from 'slate';
import { PlaitTable, PlaitTableCell, PlaitTableElement } from '../interfaces/table';
import { getCellWithPoints } from '../utils/table';
import { getCellWithPoints, updateColumns, updateRows } from '../utils/table';

export const setTableText = (
board: PlaitBoard,
Expand All @@ -22,29 +22,23 @@ export const setTableText = (
const defaultSpace = ShapeDefaultSpace.rectangleAndText;
if (PlaitTableElement.isVerticalText(cell as PlaitTableCell)) {
const columnIdx = table.columns.findIndex(column => column.id === cell.columnId);
const tableColumn = table.columns[columnIdx];
if (textHeight > cellWidth) {
const newColumnWidth = textHeight + defaultSpace * 2;
columns[columnIdx] = {
...tableColumn,
width: newColumnWidth
};
const offset = newColumnWidth - cellWidth;
points = [points[0], [points[1][0] + offset, points[1][1]]];
const result = updateColumns(table, table.columns[columnIdx].id, newColumnWidth, offset);
points = result.points;
columns = result.columns;
}
} else {
const rowIdx = table.rows.findIndex(row => row.id === cell.rowId);
const tableRow = table.rows[rowIdx];
const compareHeight = tableRow.height ?? Math.max(cellHeight, cell.textHeight || 0);
if (textHeight > compareHeight) {
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]];
const result = updateRows(table, table.rows[rowIdx].id, newRowHeight, offset);
points = result.points;
rows = result.rows;
}
}
cells[cellIndex] = {
Expand Down
Loading

0 comments on commit ce5ee71

Please sign in to comment.