From f5c6ee9abadbfd2fd8f890b25b5dea5c6a7901e5 Mon Sep 17 00:00:00 2001 From: R0ger1tlearn Date: Thu, 20 Jun 2024 15:34:13 +0800 Subject: [PATCH 01/10] =?UTF-8?q?feat(core):=20=E5=B0=86=E8=8A=82=E7=82=B9?= =?UTF-8?q?=20Resize=20=E5=8A=9F=E8=83=BD=E5=86=85=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 Control 组件,用于控制节点放大缩小,并在 BaseNodeModel 中增加 resize 方法 - 更新 GitHub Actions 任务名 - 重命名 allowRotation -> allowRotate;后续增加 allowResize 选项,保持命名一致性 --- .github/workflows/docs.yaml | 4 +- .github/workflows/issue_checker.yaml | 2 +- .../src/pages/graph/index.tsx | 2 +- .../src/pages/graph/index.tsx | 2 +- examples/next-app/src/app/page.tsx | 2 +- examples/vue3-app/src/views/LogicFlowView.vue | 2 +- packages/core/src/LogicFlow.tsx | 13 +- packages/core/src/constant/index.ts | 1 + packages/core/src/model/EditConfigModel.ts | 10 +- packages/core/src/model/node/BaseNodeModel.ts | 50 ++- packages/core/src/options.ts | 5 +- packages/core/src/util/theme.ts | 7 + packages/core/src/view/Control.tsx | 371 ++++++++++++++++++ packages/core/src/view/Rotate.tsx | 8 +- packages/core/src/view/node/BaseNode.tsx | 4 +- .../src/NodeResize/node/RectResize.tsx | 7 +- 16 files changed, 463 insertions(+), 27 deletions(-) create mode 100644 packages/core/src/view/Control.tsx diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index ede3b7728..354d1151e 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -1,4 +1,4 @@ -name: docs +name: Docs Deployment on: [push] @@ -43,4 +43,4 @@ jobs: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./deploy cname: logic-flow.org - + diff --git a/.github/workflows/issue_checker.yaml b/.github/workflows/issue_checker.yaml index 20922ed60..4e02f36a1 100644 --- a/.github/workflows/issue_checker.yaml +++ b/.github/workflows/issue_checker.yaml @@ -1,4 +1,4 @@ -name: issue checker +name: Issue Checker on: issues: diff --git a/examples/engine-browser-examples/src/pages/graph/index.tsx b/examples/engine-browser-examples/src/pages/graph/index.tsx index e0250ff2c..2d63d0b6c 100644 --- a/examples/engine-browser-examples/src/pages/graph/index.tsx +++ b/examples/engine-browser-examples/src/pages/graph/index.tsx @@ -149,7 +149,7 @@ export default function BasicNode() { // stopMoveGraph: true, adjustEdgeStartAndEnd: true, // adjustEdge: false, - allowRotation: true, + allowRotate: true, edgeTextEdit: true, keyboard: { enabled: true, diff --git a/examples/feature-examples/src/pages/graph/index.tsx b/examples/feature-examples/src/pages/graph/index.tsx index 72e37cae6..f59586ed6 100644 --- a/examples/feature-examples/src/pages/graph/index.tsx +++ b/examples/feature-examples/src/pages/graph/index.tsx @@ -136,7 +136,7 @@ export default function BasicNode() { // stopMoveGraph: true, adjustEdgeStartAndEnd: true, // adjustEdge: false, - allowRotation: true, + allowRotate: true, edgeTextEdit: true, keyboard: { enabled: true, diff --git a/examples/next-app/src/app/page.tsx b/examples/next-app/src/app/page.tsx index 2538ea77d..8e5a7b294 100644 --- a/examples/next-app/src/app/page.tsx +++ b/examples/next-app/src/app/page.tsx @@ -150,7 +150,7 @@ export default function Graph() { // stopMoveGraph: true, adjustEdgeStartAndEnd: true, // adjustEdge: false, - allowRotation: true, + allowRotate: true, edgeTextEdit: true, keyboard: { enabled: true, diff --git a/examples/vue3-app/src/views/LogicFlowView.vue b/examples/vue3-app/src/views/LogicFlowView.vue index 78b1ea4f4..b75ea9127 100644 --- a/examples/vue3-app/src/views/LogicFlowView.vue +++ b/examples/vue3-app/src/views/LogicFlowView.vue @@ -135,7 +135,7 @@ onMounted(() => { // stopMoveGraph: true, adjustEdgeStartAndEnd: true, // adjustEdge: false, - allowRotation: true, + allowRotate: true, edgeTextEdit: true, keyboard: { enabled: true diff --git a/packages/core/src/LogicFlow.tsx b/packages/core/src/LogicFlow.tsx index defa50b9c..e243b4192 100644 --- a/packages/core/src/LogicFlow.tsx +++ b/packages/core/src/LogicFlow.tsx @@ -1359,9 +1359,17 @@ export namespace LogicFlow { [key: string]: string | undefined } - export type PropertiesType = Record + export type PropertiesType = { + width?: number + height?: number + rx?: number + ry?: number + } & Record export type AttributesType = Record - + export type VectorData = { + deltaX: number + deltaY: number + } export type OffsetData = { dx: number dy: number @@ -1713,6 +1721,7 @@ export namespace LogicFlow { arrow: ArrowTheme // 边上箭头的样式 snapline: EdgeTheme // 对齐线样式 rotateControl: CommonTheme // 节点旋转控制点样式 + resizeControl: CommonTheme // 节点旋转控制点样式 /** * REMIND: 当开启了跳转边的起点和终点(adjustEdgeStartAndEnd:true)后 diff --git a/packages/core/src/constant/index.ts b/packages/core/src/constant/index.ts index 11da9a1d0..cd454e0ab 100644 --- a/packages/core/src/constant/index.ts +++ b/packages/core/src/constant/index.ts @@ -53,6 +53,7 @@ export enum EventType { NODE_MOUSELEAVE = 'node:mouseleave', NODE_CONTEXTMENU = 'node:contextmenu', NODE_ROTATE = 'node:rotate', + NODE_RESIZE = 'node:resize', // 节点 properties 变化事件 NODE_PROPERTIES_CHANGE = 'node:properties-change', diff --git a/packages/core/src/model/EditConfigModel.ts b/packages/core/src/model/EditConfigModel.ts index 3a26a9647..6667de2ae 100644 --- a/packages/core/src/model/EditConfigModel.ts +++ b/packages/core/src/model/EditConfigModel.ts @@ -45,7 +45,7 @@ export interface EditConfigInterface { /** * 是否允许节点旋转(旋转点的显隐) */ - allowRotation?: boolean + allowRotate?: boolean /** * 显示节点悬浮时的外框 */ @@ -93,7 +93,7 @@ const SilentConfig = { adjustEdgeStartAndEnd: false, adjustNodePosition: false, hideAnchors: true, - allowRotation: false, + allowRotate: false, nodeSelectedOutline: true, nodeTextEdit: false, edgeTextEdit: false, @@ -111,7 +111,7 @@ const keys = [ 'adjustEdgeStartAndEnd', 'adjustNodePosition', 'hideAnchors', - 'allowRotation', + 'allowRotate', 'hoverOutline', 'nodeSelectedOutline', 'edgeSelectedOutline', @@ -135,7 +135,7 @@ export class EditConfigModel { @observable adjustEdgeStartAndEnd = false @observable adjustNodePosition = true @observable hideAnchors = false - @observable allowRotation = false + @observable allowRotate = false @observable hoverOutline = true @observable nodeSelectedOutline = true @observable edgeSelectedOutline = true @@ -177,7 +177,7 @@ export class EditConfigModel { adjustEdgeStartAndEnd: this.adjustEdgeStartAndEnd, adjustNodePosition: this.adjustNodePosition, hideAnchors: this.hideAnchors, - allowRotation: this.allowRotation, + allowRotate: this.allowRotate, hoverOutline: this.hoverOutline, nodeSelectedOutline: this.nodeSelectedOutline, edgeSelectedOutline: this.edgeSelectedOutline, diff --git a/packages/core/src/model/node/BaseNodeModel.ts b/packages/core/src/model/node/BaseNodeModel.ts index 5a4a008f5..265bea815 100644 --- a/packages/core/src/model/node/BaseNodeModel.ts +++ b/packages/core/src/model/node/BaseNodeModel.ts @@ -18,6 +18,7 @@ import { ElementState, EventType, } from '../../constant' +import { ResizeControl } from '../../view/Control' import AnchorConfig = Model.AnchorConfig import GraphElements = LogicFlow.GraphElements @@ -27,6 +28,10 @@ import NodeData = LogicFlow.NodeData import Point = LogicFlow.Point import CommonTheme = LogicFlow.CommonTheme +import ResizeInfo = ResizeControl.ResizeInfo +import ResizeNodeData = ResizeControl.ResizeNodeData +import PCTResizeParams = ResizeControl.PCTResizeParams + export interface IBaseNodeModel extends Model.BaseModel { /** * model基础类型,固定为node @@ -63,6 +68,7 @@ export class BaseNodeModel implements IBaseNodeModel { public get width() { return this._width } + public set width(value: number) { this._width = value } @@ -71,10 +77,17 @@ export class BaseNodeModel implements IBaseNodeModel { public get height() { return this._height } + public set height(value: number) { this._height = value } + minWidth: number = 30 + minHeight: number = 30 + maxWidth: number = 2000 + maxHeight: number = 2000 + PCTResizeInfo?: PCTResizeParams + // 根据与 (x, y) 的偏移量计算 anchors 的坐标 @observable anchorsOffset: BaseNodeModel.AnchorsOffsetItem[] = [] @@ -103,6 +116,7 @@ export class BaseNodeModel implements IBaseNodeModel { get rotate() { return this._rotate } + set rotate(value: number) { this._rotate = value const { x = 0, y = 0 } = this @@ -121,7 +135,7 @@ export class BaseNodeModel implements IBaseNodeModel { moveRules: Model.NodeMoveRule[] = [] // 节点移动之前的hook hasSetTargetRules = false // 用来限制rules的重复值 hasSetSourceRules = false; // 用来限制rules的重复值 - [propName: string]: unknown // 支持用户自定义属性 + [propName: string]: any // 支持用户自定义属性 constructor(data: NodeConfig, graphModel: GraphModel) { this.graphModel = graphModel @@ -161,6 +175,10 @@ export class BaseNodeModel implements IBaseNodeModel { data.properties = {} } + const { width, height } = data.properties + if (width) this.width = width + if (height) this.height = height + if (!data.id) { // 自定义节点id > 全局定义id > 内置 const { idGenerator } = this.graphModel @@ -226,6 +244,26 @@ export class BaseNodeModel implements IBaseNodeModel { } } + /** + * @overridable 支持重写 + * 计算节点 resize 时 + */ + // TODO:重新计算宽高,还是用 svg 的 + resize(resizeInfo: ResizeInfo): ResizeNodeData { + const { width, height, deltaX, deltaY } = resizeInfo + // 移动节点以及文本内容 + this.move(deltaX, deltaY) + + this.width = resizeInfo.width + this.height = resizeInfo.height + this.setProperties({ width, height }) + + return this.getData() + } + + // TODO: 等比例缩放 + proportionalResize() {} + /** * 获取被保存时返回的数据 * @overridable 支持重写 @@ -317,6 +355,15 @@ export class BaseNodeModel implements IBaseNodeModel { return cloneDeep(rotateControl) } + /** + * @overrideable 支持重写 + * 获取当前节点缩放控制节点的样式 + */ + getResizeControlStyle() { + const { resizeControl } = this.graphModel.theme + return cloneDeep(resizeControl) + } + /** * @overridable 支持重写 * 获取当前节点锚点样式 @@ -690,6 +737,7 @@ export class BaseNodeModel implements IBaseNodeModel { setHitable(flag = true): void { this.isHitable = flag } + @action setHittable(flag = true): void { this.isHittable = flag diff --git a/packages/core/src/options.ts b/packages/core/src/options.ts index d4cf341e7..0a6cc4f8e 100644 --- a/packages/core/src/options.ts +++ b/packages/core/src/options.ts @@ -75,7 +75,10 @@ export namespace Options { style?: Partial // 主题配置 edgeType?: EdgeType adjustEdge?: boolean - allowRotation?: boolean + + allowRotate?: boolean // 允许节点旋转 + allowResize?: boolean // 是否允许缩放 + isSilentMode?: boolean stopScrollGraph?: boolean stopZoomGraph?: boolean diff --git a/packages/core/src/util/theme.ts b/packages/core/src/util/theme.ts index d60de6fb2..49a1dc348 100644 --- a/packages/core/src/util/theme.ts +++ b/packages/core/src/util/theme.ts @@ -120,6 +120,13 @@ export const defaultTheme: LogicFlow.Theme = { fill: '#fff', strokeWidth: 1.5, }, + + resizeControl: { + width: 7, + height: 7, + fill: '#fff', + stroke: '#000', + }, } /* 主题(全局样式)相关工具方法 */ diff --git a/packages/core/src/view/Control.tsx b/packages/core/src/view/Control.tsx new file mode 100644 index 000000000..9441bb791 --- /dev/null +++ b/packages/core/src/view/Control.tsx @@ -0,0 +1,371 @@ +import { createElement as h, Component } from 'preact/compat' +import { cloneDeep, find, forEach } from 'lodash-es' +import { Rect } from './shape' +import LogicFlow from '../LogicFlow' +import { IDragParams, StepDrag } from '../util' +import { BaseNodeModel, GraphModel } from '../model' + +import NodeData = LogicFlow.NodeData +import VectorData = LogicFlow.VectorData +import { EventType } from '../constant' +import ResizeInfo = ResizeControl.ResizeInfo +import ResizeNodeData = ResizeControl.ResizeNodeData + +export enum ResizeControlIndex { + LEFT_TOP = 0, + RIGHT_TOP = 1, + RIGHT_BOTTOM = 2, + LEFT_BOTTOM = 3, +} + +export interface IResizeControlProps { + index: ResizeControlIndex + x: number + y: number + model: BaseNodeModel + graphModel: GraphModel +} + +export interface IResizeControlState { + startX: number + startY: number + endX: number + endY: number + dragging: boolean +} + +export class ResizeControl extends Component< + IResizeControlProps, + IResizeControlState +> { + readonly index: number + readonly nodeModel: BaseNodeModel + readonly graphModel: GraphModel + readonly dragHandler: StepDrag + + constructor(props: IResizeControlProps) { + super() + const { index, model, graphModel } = props + this.index = index + this.nodeModel = model + this.graphModel = graphModel + + // 初始化拖拽工具 + this.dragHandler = new StepDrag({ + onDragging: this.onDragging, + onDragEnd: this.onDragEnd, + step: 1, + }) + } + + updateEdgePointByAnchors = () => { + // https://github.com/didi/LogicFlow/issues/807 + // https://github.com/didi/LogicFlow/issues/875 + // 之前的做法,比如Rect是使用getRectResizeEdgePoint()计算边的point缩放后的位置 + // getRectResizeEdgePoint()考虑了瞄点在四条边以及在4个圆角的情况 + // 使用的是一种等比例缩放的模式,比如: + // const pct = (y - beforeNode.y) / (beforeNode.height / 2 - radius) + // afterPoint.y = afterNode.y + (afterNode.height / 2 - radius) * pct + // 但是用户自定义的getDefaultAnchor()不一定是按照比例编写的 + // 它可能是 x: x + 20:每次缩放都会保持在x右边20的位置,因此用户自定义瞄点时,然后产生无法跟随的问题 + // 现在的做法是:直接获取用户自定义瞄点的位置,然后用这个位置作为边的新的起点,而不是自己进行计算 + const { id, anchors } = this.nodeModel + const edges = this.graphModel.getNodeEdges(id) + // 更新边 + forEach(edges, (edge) => { + if (edge.sourceNodeId === id) { + // 边是以该节点为 sourceNode 时 + const anchorItem = find( + anchors, + (anchor) => anchor.id === edge.sourceAnchorId, + ) + + if (anchorItem) { + edge.updateStartPoint({ + x: anchorItem.x, + y: anchorItem.y, + }) + } + } else if (edge.targetNodeId === id) { + // 边是以该节点为 targetNode 时 + const anchorItem = find( + anchors, + (anchor) => anchor.id === edge.targetAnchorId, + ) + + if (anchorItem) { + edge.updateEndPoint({ + x: anchorItem.x, + y: anchorItem.y, + }) + } + } + }) + } + + triggerResizeEvent = ( + preNodeData: ResizeNodeData, + nextNodeData: ResizeNodeData, + ) => { + this.graphModel.eventCenter.emit(EventType.NODE_RESIZE, { + preNodeData, + nextNodeData, + }) + } + + /** + * 计算 Control 拖动后,节点的高度信息 + * @param index + * @param resizeInfo + * @param pct + * @param freezeWidth + * @param freezeHeight + */ + reCalcResizeInfo = ( + index: ResizeControlIndex, + resizeInfo: ResizeInfo, + pct = 1, + freezeWidth = false, + freezeHeight = false, + ): ResizeInfo => { + const nextResizeInfo = cloneDeep(resizeInfo) + let { deltaX, deltaY } = resizeInfo + const { width, height, PCTResizeInfo } = resizeInfo + if (PCTResizeInfo) { + const sensitivity = 4 // 越低越灵敏 + let deltaScale = 0 + let combineDelta = 0 + switch (index) { + case ResizeControlIndex.LEFT_TOP: + combineDelta = (deltaX * -1 - deltaY) / sensitivity + break + case ResizeControlIndex.RIGHT_TOP: + combineDelta = (deltaX - deltaY) / sensitivity + break + case ResizeControlIndex.RIGHT_BOTTOM: + combineDelta = (deltaX + deltaY) / sensitivity + break + case ResizeControlIndex.LEFT_BOTTOM: + combineDelta = (deltaX * -1 + deltaY) / sensitivity + break + default: + break + } + + if (combineDelta !== 0) { + deltaScale = + Math.round( + (combineDelta / PCTResizeInfo.ResizeBasis.basisHeight) * 100000, + ) / 1000 + } + + PCTResizeInfo.ResizePCT.widthPCT = Math.max( + Math.min( + PCTResizeInfo.ResizePCT.widthPCT + deltaScale, + PCTResizeInfo.ScaleLimit.maxScaleLimit, + ), + PCTResizeInfo.ScaleLimit.minScaleLimit, + ) + PCTResizeInfo.ResizePCT.heightPCT = Math.max( + Math.min( + PCTResizeInfo.ResizePCT.heightPCT + deltaScale, + PCTResizeInfo.ScaleLimit.maxScaleLimit, + ), + PCTResizeInfo.ScaleLimit.minScaleLimit, + ) + const spcWidth = Math.round( + (PCTResizeInfo.ResizePCT.widthPCT * + PCTResizeInfo.ResizeBasis.basisWidth) / + 100, + ) + const spcHeight = Math.round( + (PCTResizeInfo.ResizePCT.heightPCT * + PCTResizeInfo.ResizeBasis.basisHeight) / + 100, + ) + + switch (index) { + case ResizeControlIndex.LEFT_TOP: + deltaX = width - spcWidth + deltaY = height - spcHeight + break + case ResizeControlIndex.RIGHT_TOP: + deltaX = spcWidth - width + deltaY = height - spcHeight + break + case ResizeControlIndex.RIGHT_BOTTOM: + deltaX = spcWidth - width + deltaY = spcHeight - height + break + case ResizeControlIndex.LEFT_BOTTOM: + deltaX = width - spcWidth + deltaY = spcHeight - height + break + default: + break + } + return nextResizeInfo + } + // 如果限制了宽/高不变,对应的 width/height 保持一致 + switch (index) { + case ResizeControlIndex.LEFT_TOP: + nextResizeInfo.width = freezeWidth ? width : width - deltaX * pct + nextResizeInfo.height = freezeHeight ? height : height - deltaY * pct + break + case ResizeControlIndex.RIGHT_TOP: + nextResizeInfo.width = freezeWidth ? width : width + deltaX * pct + nextResizeInfo.height = freezeHeight ? height : height - deltaY * pct + break + case ResizeControlIndex.RIGHT_BOTTOM: + nextResizeInfo.width = freezeWidth ? width : width + deltaX * pct + nextResizeInfo.height = freezeHeight ? height : height + deltaY * pct + break + case ResizeControlIndex.LEFT_BOTTOM: + nextResizeInfo.width = freezeWidth ? width : width - deltaX * pct + nextResizeInfo.height = freezeHeight ? height : height + deltaY * pct + break + default: + break + } + return nextResizeInfo + } + + resizeNode = ({ deltaX, deltaY }: VectorData) => { + // TODO: 调用每个节点中更新缩放时的方法 updateNode 函数,用来各节点缩放的方法 + // 1. 计算当前 Control 的一些信息, + const { + rx, + ry, + width, + height, + PCTResizeInfo, + + minWidth, + minHeight, + maxWidth, + maxHeight, + } = this.nodeModel + const isFreezeWidth = minWidth === maxWidth + const isFreezeHeight = minHeight === maxHeight + + const resizeInfo = { + width: width || rx, + height: height || ry, + deltaX, + deltaY, + PCTResizeInfo, + } + + const nextSize = this.reCalcResizeInfo( + this.index, + resizeInfo, + 1, + isFreezeWidth, + isFreezeHeight, + ) + const { + width: nextWidth, + height: nextHeight, + deltaX: nextDeltaX, + deltaY: nextDeltaY, + } = nextSize + + // 限制放大缩小的最大最小范围 + if ( + nextWidth < minWidth || + nextWidth > maxWidth || + nextHeight < minHeight || + nextHeight > maxHeight + ) { + this.dragHandler.cancelDrag() + return + } + // 如果限制了宽高不变,对应的 x/y 不产生位移 + nextSize.deltaX = isFreezeWidth ? 0 : nextDeltaX + nextSize.deltaY = isFreezeWidth ? 0 : nextDeltaY + + const preNodeData = this.nodeModel.getData() + const nextNodeData = this.nodeModel.resize(nextSize) + + // 更新边 + this.updateEdgePointByAnchors() + // 触发 resize 事件 + this.triggerResizeEvent(preNodeData, nextNodeData) + } + + onDragging = ({ deltaX, deltaY }: IDragParams) => { + const { transformModel } = this.graphModel + const [dx, dy] = transformModel.fixDeltaXY(deltaX, deltaY) + + this.resizeNode({ + deltaX: dx, + deltaY: dy, + }) + } + + // 由于将拖拽放大缩小改成丝滑模式,这个时候需要再拖拽结束的时候,将节点的位置更新到 grid 上。 + onDragEnd = () => { + const { gridSize = 1 } = this.graphModel + const x = gridSize * Math.round(this.nodeModel.x / gridSize) + const y = gridSize * Math.round(this.nodeModel.y / gridSize) + this.nodeModel.moveTo(x, y) + + // 先触发 onDragging() -> 更新边 -> 再触发用户自定义的 getDefaultAnchor(),所以 onDragging() + // 拿到的 anchors 是滞后的,为了正确的设置最终的位置,应该在拖拽结束的时候,再设置一次边的 Point 位置, + // 此时拿到的 anchors 是最新的 + this.updateEdgePointByAnchors() + } + + render(): h.JSX.Element { + const { x, y, index, model } = this.props + const style = model.getResizeControlStyle() + return ( + + + + ) + } +} + +// interface IResizeControlGroupProps { +// } + +// export class ResizeControlGroup extends Component { +// +// } + +export namespace ResizeControl { + export type RectShapeResizeProps = { + width: number + height: number + } + + export type PolygonShapeResizerProps = { + rx: number + ry: number + } + export type ResizeProps = RectShapeResizeProps | PolygonShapeResizerProps + + export type ResizeInfo = { + width: number + height: number + deltaX: number + deltaY: number + PCTResizeInfo?: PCTResizeParams + } + export type ResizeNodeData = NodeData & Partial + + export type PCTResizeParams = { + ResizePCT: { widthPCT: number; heightPCT: number } + ResizeBasis: { basisWidth: number; basisHeight: number } + ScaleLimit: { maxScaleLimit: number; minScaleLimit: number } + } +} + +export default ResizeControl diff --git a/packages/core/src/view/Rotate.tsx b/packages/core/src/view/Rotate.tsx index 2fc8b6f67..453b79c9f 100644 --- a/packages/core/src/view/Rotate.tsx +++ b/packages/core/src/view/Rotate.tsx @@ -7,20 +7,20 @@ import EventEmitter from '../event/eventEmitter' import { CommonTheme } from '../constant/DefaultTheme' import { EventType } from '../constant' -interface IProps { +interface IRotateControlProps { graphModel: GraphModel nodeModel: BaseNodeModel eventCenter: EventEmitter style: CommonTheme } -class RotateControlPoint extends Component { - private style = {} +class RotateControlPoint extends Component { + readonly style = {} private defaultAngle!: number normal!: Vector stepperDrag: any - constructor(props: IProps) { + constructor(props: IRotateControlProps) { super(props) this.style = props.style this.stepperDrag = new StepDrag({ diff --git a/packages/core/src/view/node/BaseNode.tsx b/packages/core/src/view/node/BaseNode.tsx index 03c0b1841..35314ce95 100644 --- a/packages/core/src/view/node/BaseNode.tsx +++ b/packages/core/src/view/node/BaseNode.tsx @@ -431,7 +431,7 @@ export abstract class BaseNode

extends Component< render() { const { model, graphModel } = this.props const { - editConfigModel: { hideAnchors, adjustNodePosition, allowRotation }, + editConfigModel: { hideAnchors, adjustNodePosition, allowRotate }, gridSize, transformModel: { SCALE_X }, } = graphModel @@ -442,7 +442,7 @@ export abstract class BaseNode

extends Component< {this.getShape()} {this.getText()} - {allowRotation && this.getRotateControl()} + {allowRotate && this.getRotateControl()} {!hideAnchors && this.getAnchors()} diff --git a/packages/extension/src/NodeResize/node/RectResize.tsx b/packages/extension/src/NodeResize/node/RectResize.tsx index 2cfa41bb3..f2ff3c0c3 100644 --- a/packages/extension/src/NodeResize/node/RectResize.tsx +++ b/packages/extension/src/NodeResize/node/RectResize.tsx @@ -16,6 +16,7 @@ export type ResizeNodeConfig = NodeConfig & { } } & Record } + export type PCTResizeParams = { ResizePCT: { widthPCT: number; heightPCT: number } ResizeBasis: { basisWidth: number; basisHeight: number } @@ -23,7 +24,7 @@ export type PCTResizeParams = { } export class RectResizeModel extends RectNodeModel { - private PCTResizeInfo?: PCTResizeParams + PCTResizeInfo?: PCTResizeParams minWidth!: number minHeight!: number maxWidth!: number @@ -77,10 +78,6 @@ export class RectResizeModel extends RectNodeModel { } } - resize(deltaX: number, deltaY: number) { - console.log(deltaX, deltaY) - } - // 该方法需要在重设宽高和最大、最小限制后被调用,不建议在 initNodeData() 方法中使用 enableProportionResize(turnOn = true) { if (turnOn) { From 64ff799945c10b23b46265dd5c2b0e36fb0306a8 Mon Sep 17 00:00:00 2001 From: boyongjiong Date: Thu, 20 Jun 2024 21:34:02 +0800 Subject: [PATCH 02/10] feat(chore): built-in node resize function - add ResizeControl and ResizeControlGroup into project, and use them in NodeView - add defaultTheme and other options, such as allowResize (default to be false) --- packages/core/src/LogicFlow.tsx | 1 + packages/core/src/model/node/BaseNodeModel.ts | 77 ++++++++---------- packages/core/src/util/theme.ts | 11 ++- packages/core/src/view/Control.tsx | 78 +++++++++++++++++-- packages/core/src/view/node/BaseNode.tsx | 28 +++++-- 5 files changed, 134 insertions(+), 61 deletions(-) diff --git a/packages/core/src/LogicFlow.tsx b/packages/core/src/LogicFlow.tsx index e243b4192..9c1549436 100644 --- a/packages/core/src/LogicFlow.tsx +++ b/packages/core/src/LogicFlow.tsx @@ -1722,6 +1722,7 @@ export namespace LogicFlow { snapline: EdgeTheme // 对齐线样式 rotateControl: CommonTheme // 节点旋转控制点样式 resizeControl: CommonTheme // 节点旋转控制点样式 + resizeOutline: CommonTheme // 节点调整大小时的外框样式 /** * REMIND: 当开启了跳转边的起点和终点(adjustEdgeStartAndEnd:true)后 diff --git a/packages/core/src/model/node/BaseNodeModel.ts b/packages/core/src/model/node/BaseNodeModel.ts index 265bea815..3f020c285 100644 --- a/packages/core/src/model/node/BaseNodeModel.ts +++ b/packages/core/src/model/node/BaseNodeModel.ts @@ -256,7 +256,10 @@ export class BaseNodeModel implements IBaseNodeModel { this.width = resizeInfo.width this.height = resizeInfo.height - this.setProperties({ width, height }) + this.setProperties({ + width, + height, + }) return this.getData() } @@ -364,6 +367,11 @@ export class BaseNodeModel implements IBaseNodeModel { return cloneDeep(resizeControl) } + getResizeOutlineStyle() { + const { resizeOutline } = this.graphModel.theme + return cloneDeep(resizeOutline) + } + /** * @overridable 支持重写 * 获取当前节点锚点样式 @@ -604,15 +612,13 @@ export class BaseNodeModel implements IBaseNodeModel { } } - @action - addNodeMoveRules(fn: Model.NodeMoveRule) { + @action addNodeMoveRules(fn: Model.NodeMoveRule) { if (!this.moveRules.includes(fn)) { this.moveRules.push(fn) } } - @action - move(deltaX: number, deltaY: number, isIgnoreRule = false): boolean { + @action move(deltaX: number, deltaY: number, isIgnoreRule = false): boolean { let isAllowMoveX = false let isAllowMoveY = false if (isIgnoreRule) { @@ -641,8 +647,7 @@ export class BaseNodeModel implements IBaseNodeModel { return isAllowMoveX || isAllowMoveY } - @action - getMoveDistance( + @action getMoveDistance( deltaX: number, deltaY: number, isIgnoreRule = false, @@ -679,11 +684,12 @@ export class BaseNodeModel implements IBaseNodeModel { return [moveX, moveY] } - @action - moveTo(x: number, y: number, isIgnoreRule = false): boolean { + @action moveTo(x: number, y: number, isIgnoreRule = false): boolean { const deltaX = x - this.x const deltaY = y - this.y - if (!isIgnoreRule && !this.isAllowMoveNode(deltaX, deltaY)) return false + if (!isIgnoreRule && !this.isAllowMoveNode(deltaX, deltaY)) { + return false + } if (this.text) { this.text && this.moveText(deltaX, deltaY) } @@ -692,8 +698,7 @@ export class BaseNodeModel implements IBaseNodeModel { return true } - @action - moveText(deltaX: number, deltaY: number): void { + @action moveText(deltaX: number, deltaY: number): void { const { x, y, value, draggable, editable } = this.text this.text = { value, @@ -704,47 +709,39 @@ export class BaseNodeModel implements IBaseNodeModel { } } - @action - updateText(value: string): void { + @action updateText(value: string): void { this.text = { ...toJS(this.text), value, } } - @action - setSelected(flag = true): void { + @action setSelected(flag = true): void { this.isSelected = flag } - @action - setHovered(flag = true): void { + @action setHovered(flag = true): void { this.isHovered = flag this.setIsShowAnchor(flag) } - @action - setIsShowAnchor(flag = true): void { + @action setIsShowAnchor(flag = true): void { this.isShowAnchor = flag } - @action - setEnableRotate(flag = true): void { + @action setEnableRotate(flag = true): void { this.enableRotate = flag } - @action - setHitable(flag = true): void { + @action setHitable(flag = true): void { this.isHitable = flag } - @action - setHittable(flag = true): void { + @action setHittable(flag = true): void { this.isHittable = flag } - @action - setElementState( + @action setElementState( state: number, additionStateData?: Model.AdditionStateDataType, ): void { @@ -753,8 +750,7 @@ export class BaseNodeModel implements IBaseNodeModel { } // TODO: 处理重复代码,setProperty 和 setProperties -> 公用代码提到 updateProperties 中? - @action - setProperty(key: string, val: any): void { + @action setProperty(key: string, val: any): void { const preProperties = toJS(this.properties) const nextProperties = { ...preProperties, @@ -772,8 +768,7 @@ export class BaseNodeModel implements IBaseNodeModel { }) } - @action - setProperties(properties: Record): void { + @action setProperties(properties: Record): void { const preProperties = toJS(this.properties) const nextProperties = { ...preProperties, @@ -802,42 +797,36 @@ export class BaseNodeModel implements IBaseNodeModel { }) } - @action - deleteProperty(key: string): void { + @action deleteProperty(key: string): void { delete this.properties[key] this.setAttributes() } - @action - setStyle(key: string, val: any): void { + @action setStyle(key: string, val: any): void { this.style = { ...this.style, [key]: formatData(val), } } - @action - setStyles(styles: Record): void { + @action setStyles(styles: Record): void { this.style = { ...this.style, ...formatData(styles), } } - @action - updateStyles(styles: Record): void { + @action updateStyles(styles: Record): void { this.style = { ...formatData(styles), } } - @action - setZIndex(zIndex = 1): void { + @action setZIndex(zIndex = 1): void { this.zIndex = zIndex } - @action - updateAttributes(attributes) { + @action updateAttributes(attributes) { assign(this, attributes) } } diff --git a/packages/core/src/util/theme.ts b/packages/core/src/util/theme.ts index 49a1dc348..0813ab6e8 100644 --- a/packages/core/src/util/theme.ts +++ b/packages/core/src/util/theme.ts @@ -127,12 +127,19 @@ export const defaultTheme: LogicFlow.Theme = { fill: '#fff', stroke: '#000', }, + + resizeOutline: { + fill: 'none', + stroke: 'transparent', // 矩形默认不显示调整边框 + strokeWidth: 1, + strokeDasharray: '3,3', + }, } /* 主题(全局样式)相关工具方法 */ -export const setupTheme: ( +export const setupTheme = ( customTheme?: Partial, -) => LogicFlow.Theme = function (customTheme) { +): LogicFlow.Theme => { let theme = cloneDeep(defaultTheme) if (customTheme) { diff --git a/packages/core/src/view/Control.tsx b/packages/core/src/view/Control.tsx index 9441bb791..c030ac7f6 100644 --- a/packages/core/src/view/Control.tsx +++ b/packages/core/src/view/Control.tsx @@ -1,8 +1,8 @@ import { createElement as h, Component } from 'preact/compat' -import { cloneDeep, find, forEach } from 'lodash-es' +import { cloneDeep, find, forEach, map } from 'lodash-es' import { Rect } from './shape' import LogicFlow from '../LogicFlow' -import { IDragParams, StepDrag } from '../util' +import { getNodeBBox, IDragParams, StepDrag } from '../util' import { BaseNodeModel, GraphModel } from '../model' import NodeData = LogicFlow.NodeData @@ -10,6 +10,7 @@ import VectorData = LogicFlow.VectorData import { EventType } from '../constant' import ResizeInfo = ResizeControl.ResizeInfo import ResizeNodeData = ResizeControl.ResizeNodeData +import ControlItemProps = ResizeControl.ControlItemProps export enum ResizeControlIndex { LEFT_TOP = 0, @@ -333,12 +334,67 @@ export class ResizeControl extends Component< } } -// interface IResizeControlGroupProps { -// } +interface IResizeControlGroupProps { + style: LogicFlow.CommonTheme + model: BaseNodeModel + graphModel: GraphModel +} + +export class ResizeControlGroup extends Component { + constructor() { + super() + } + + getResizeControl(): h.JSX.Element[] { + const { model, graphModel } = this.props + const { minX, minY, maxX, maxY } = getNodeBBox(model) + const controlList: ControlItemProps[] = [ + // 左上角 + { + index: ResizeControlIndex.LEFT_TOP, + x: minX, + y: minY, + }, + // 右上角 + { + index: ResizeControlIndex.RIGHT_TOP, + x: maxX, + y: minY, + }, + // 右下角 + { + index: ResizeControlIndex.RIGHT_BOTTOM, + x: maxX, + y: maxY, + }, + // 左下角 + { + index: ResizeControlIndex.LEFT_BOTTOM, + x: minX, + y: maxY, + }, + ] + return map(controlList, (control) => ( + + )) + } + + getResizeOutline() { + const { model } = this.props + const { x, y, width, height } = model + const style = model.getResizeOutlineStyle() + return + } -// export class ResizeControlGroup extends Component { -// -// } + render(): h.JSX.Element { + return ( + + {this.getResizeOutline()} + {this.getResizeControl()} + + ) + } +} export namespace ResizeControl { export type RectShapeResizeProps = { @@ -361,6 +417,12 @@ export namespace ResizeControl { } export type ResizeNodeData = NodeData & Partial + export type ControlItemProps = { + index: ResizeControlIndex + x: number + y: number + } + export type PCTResizeParams = { ResizePCT: { widthPCT: number; heightPCT: number } ResizeBasis: { basisWidth: number; basisHeight: number } @@ -368,4 +430,4 @@ export namespace ResizeControl { } } -export default ResizeControl +export default ResizeControlGroup diff --git a/packages/core/src/view/node/BaseNode.tsx b/packages/core/src/view/node/BaseNode.tsx index 35314ce95..421d7576d 100644 --- a/packages/core/src/view/node/BaseNode.tsx +++ b/packages/core/src/view/node/BaseNode.tsx @@ -19,6 +19,7 @@ import { // RotateMatrix, } from '../../util' import RotateControlPoint from '../Rotate' +import ResizeControlGroup from '../Control' type IProps = { model: BaseNodeModel @@ -33,15 +34,12 @@ export abstract class BaseNode

extends Component< P, IState > { - t: any - moveOffset?: LogicFlow.OffsetData - - static getModel(defaultModel) { - return defaultModel - } static isObserved: boolean = false static extendsKey?: string + t: any + moveOffset?: LogicFlow.OffsetData + stepDrag: StepDrag mouseUpDrag?: boolean startTime?: number @@ -94,7 +92,7 @@ export abstract class BaseNode

extends Component< console.log('componentDidUpdate --->>>') } - abstract getShape() + abstract getShape(): h.JSX.Element // eslint-disable-next-line @typescript-eslint/no-unused-vars getAnchorShape(_anchorData?: Model.AnchorConfig): h.JSX.Element | null { @@ -141,6 +139,21 @@ export abstract class BaseNode

extends Component< } } + getResizeControl() { + const { model, graphModel } = this.props + const { isSelected, isHitable, enableRotate, isHovered } = model + const style = model.getResizeControlStyle() + if (isHitable && (isSelected || isHovered) && enableRotate) { + return ( + + ) + } + } + getText(): h.JSX.Element | null { const { model, graphModel } = this.props // 文本被编辑的时候,显示编辑框,不显示文本。 @@ -442,6 +455,7 @@ export abstract class BaseNode

extends Component< {this.getShape()} {this.getText()} + {this.getResizeControl()} {allowRotate && this.getRotateControl()} {!hideAnchors && this.getAnchors()} From ef6e6d9eaed16ca5559f52955159af878fa3e68e Mon Sep 17 00:00:00 2001 From: boyongjiong Date: Fri, 21 Jun 2024 18:50:05 +0800 Subject: [PATCH 03/10] feat(chore): continue built-in resize - add allowResize options - test resize function in BaseNodeModel and Control --- .../src/pages/graph/index.less | 16 +-- .../src/pages/graph/index.tsx | 127 ++++++++++++++---- packages/core/src/LogicFlow.tsx | 12 +- packages/core/src/model/EditConfigModel.ts | 8 ++ packages/core/src/model/node/BaseNodeModel.ts | 82 +++++------ .../core/src/model/node/DiamondNodeModel.ts | 41 ++++-- packages/core/src/model/node/TextNodeModel.ts | 2 + packages/core/src/style/index.less | 32 +++++ packages/core/src/style/raw.ts | 24 ++++ packages/core/src/view/Control.tsx | 80 ++++++----- packages/core/src/view/behavior/DnD.ts | 13 +- packages/core/src/view/node/BaseNode.tsx | 38 +++--- packages/core/src/view/node/TextNode.tsx | 4 + 13 files changed, 318 insertions(+), 161 deletions(-) diff --git a/examples/feature-examples/src/pages/graph/index.less b/examples/feature-examples/src/pages/graph/index.less index 396a44e1f..49ebc2a09 100644 --- a/examples/feature-examples/src/pages/graph/index.less +++ b/examples/feature-examples/src/pages/graph/index.less @@ -9,19 +9,19 @@ outline: none; } - .rect { - width: 50px; - height: 50px; - background: #fff; - border: 2px solid #000; + .dnd-item { + display: flex; + align-items: center; + justify-content: center; + cursor: grab; + user-select: none; } - .circle { - width: 50px; + .wrapper { + width: 80px; height: 50px; background: #fff; border: 2px solid #000; - border-radius: 50%; } .uml-wrapper { diff --git a/examples/feature-examples/src/pages/graph/index.tsx b/examples/feature-examples/src/pages/graph/index.tsx index f59586ed6..b13971740 100644 --- a/examples/feature-examples/src/pages/graph/index.tsx +++ b/examples/feature-examples/src/pages/graph/index.tsx @@ -7,8 +7,10 @@ import { useEffect, useRef } from 'react' import { combine, square, star, uml, user } from './nodes' import { animation, connection } from './edges' +import GraphData = LogicFlow.GraphData import styles from './index.less' -import GraphConfigData = LogicFlow.GraphConfigData + +import OnDragNodeConfig = LogicFlow.OnDragNodeConfig const config: Partial = { isSilentMode: false, @@ -78,12 +80,24 @@ const data = { text: { x: 600, y: 200, - value: 'xxxxx', + value: 'node-1', }, type: 'rect', x: 600, y: 200, }, + + { + id: 'custom-node-2', + text: { + x: 200, + y: 200, + value: 'node-2', + }, + type: 'rect', + x: 200, + y: 200, + }, ], } @@ -122,7 +136,7 @@ export default function BasicNode() { container: containerRef.current as HTMLElement, // hideAnchors: true, // width: 1200, - height: 400, + // height: 400, // adjustNodePosition: false, // isSilentMode: true, // overlapMode: 1, @@ -137,6 +151,7 @@ export default function BasicNode() { adjustEdgeStartAndEnd: true, // adjustEdge: false, allowRotate: true, + allowResize: true, edgeTextEdit: true, keyboard: { enabled: true, @@ -249,7 +264,7 @@ export default function BasicNode() { const handleRefreshGraph = () => { const lf = lfRef.current if (lf) { - const data = lf.getGraphData() + const data = lf.getGraphRawData() console.log('current graph data', data) const refreshData = LogicFlowUtil.refreshGraphId(data) console.log('after refresh graphId', data) @@ -276,7 +291,7 @@ export default function BasicNode() { const handleTurnAnimationOn = () => { if (lfRef.current) { - const { edges } = lfRef.current.getGraphData() as GraphConfigData + const { edges } = lfRef.current.getGraphData() as GraphData forEach(edges, (edge) => { lfRef.current?.openEdgeAnimation(edge.id) }) @@ -284,30 +299,15 @@ export default function BasicNode() { } const handleTurnAnimationOff = () => { if (lfRef.current) { - const { edges } = lfRef.current.getGraphData() as GraphConfigData + const { edges } = lfRef.current.getGraphData() as GraphData forEach(edges, (edge) => { lfRef.current?.closeEdgeAnimation(edge.id) }) } } - const handleDragRect = () => { - lfRef?.current?.dnd.startDrag({ - type: 'rect', - text: 'xxxxx', - }) - } - const handleDragCircle = () => { - lfRef?.current?.dnd.startDrag({ - type: 'circle', - r: 25, - }) - } - const handleDragText = () => { - lfRef?.current?.dnd.startDrag({ - type: 'text', - text: '文本', - }) + const handleDragItem = (node: OnDragNodeConfig) => { + lfRef?.current?.dnd.startDrag(node) } return ( @@ -446,12 +446,81 @@ export default function BasicNode() { 节点面板 - {/* */} - {/* */} - {/* */} -

-
-
+
+ handleDragItem({ + type: 'rect', + text: 'rect', + }) + } + > + rect +
+
{ + handleDragItem({ + type: 'circle', + text: 'circle', + }) + }} + > + circle +
+
{ + handleDragItem({ + type: 'diamond', + text: 'diamond', + }) + }} + > + diamond +
+
{ + handleDragItem({ + type: 'ellipse', + text: 'ellipse', + }) + }} + > + ellipse +
+
{ + handleDragItem({ + type: 'html', + text: 'html', + }) + }} + > + html +
+
{ + handleDragItem({ + type: 'polygon', + text: 'polygon', + }) + }} + > + polygon +
+
{ + handleDragItem({ + type: 'text', + text: '文本', + }) + }} + > 文本
diff --git a/packages/core/src/LogicFlow.tsx b/packages/core/src/LogicFlow.tsx index 9c1549436..e261627e2 100644 --- a/packages/core/src/LogicFlow.tsx +++ b/packages/core/src/LogicFlow.tsx @@ -1022,8 +1022,8 @@ export class LogicFlow { * 注意: getGraphData返回的数据受到adapter影响,所以其数据格式不一定是logicflow内部图数据格式。 * 如果实现通用插件,请使用getGraphRawData */ - getGraphData(...params: any): GraphData | any { - const data = this.graphModel.modelToGraphData() + getGraphData(...params: any): GraphData | unknown { + const data = this.getGraphRawData() if (this.adapterOut) { return this.adapterOut(data, ...params) } @@ -1451,6 +1451,14 @@ export namespace LogicFlow { [key: string]: unknown } + // DnD 拖拽插件使用的 NodeConfig + export type OnDragNodeConfig = { + type: string + text?: TextConfig | string + properties?: Record + [key: string]: any + } + export interface NodeConfig { id?: string type: string diff --git a/packages/core/src/model/EditConfigModel.ts b/packages/core/src/model/EditConfigModel.ts index 6667de2ae..82d729bdd 100644 --- a/packages/core/src/model/EditConfigModel.ts +++ b/packages/core/src/model/EditConfigModel.ts @@ -46,6 +46,10 @@ export interface EditConfigInterface { * 是否允许节点旋转(旋转点的显隐) */ allowRotate?: boolean + /** + * 是否允许节点缩放(缩放调整点的显隐) + */ + allowResize?: boolean /** * 显示节点悬浮时的外框 */ @@ -94,6 +98,7 @@ const SilentConfig = { adjustNodePosition: false, hideAnchors: true, allowRotate: false, + allowResize: false, nodeSelectedOutline: true, nodeTextEdit: false, edgeTextEdit: false, @@ -112,6 +117,7 @@ const keys = [ 'adjustNodePosition', 'hideAnchors', 'allowRotate', + 'allowResize', 'hoverOutline', 'nodeSelectedOutline', 'edgeSelectedOutline', @@ -136,6 +142,7 @@ export class EditConfigModel { @observable adjustNodePosition = true @observable hideAnchors = false @observable allowRotate = false + @observable allowResize = false @observable hoverOutline = true @observable nodeSelectedOutline = true @observable edgeSelectedOutline = true @@ -178,6 +185,7 @@ export class EditConfigModel { adjustNodePosition: this.adjustNodePosition, hideAnchors: this.hideAnchors, allowRotate: this.allowRotate, + allowResize: this.allowResize, hoverOutline: this.hoverOutline, nodeSelectedOutline: this.nodeSelectedOutline, edgeSelectedOutline: this.edgeSelectedOutline, diff --git a/packages/core/src/model/node/BaseNodeModel.ts b/packages/core/src/model/node/BaseNodeModel.ts index 3f020c285..1b998a94f 100644 --- a/packages/core/src/model/node/BaseNodeModel.ts +++ b/packages/core/src/model/node/BaseNodeModel.ts @@ -1,25 +1,24 @@ -import { observable, action, toJS, isObservable, computed } from 'mobx' +import { action, computed, isObservable, observable, toJS } from 'mobx' import { assign, cloneDeep, has, isNil, mapKeys } from 'lodash-es' import { GraphModel, Model } from '..' import LogicFlow from '../../LogicFlow' import { createUuid, + formatData, + getClosestAnchor, getZIndex, Matrix, - TranslateMatrix, - getClosestAnchor, pickNodeConfig, - formatData, + TranslateMatrix, } from '../../util' import { - ModelType, - ElementType, - OverlapMode, ElementState, + ElementType, EventType, + ModelType, + OverlapMode, } from '../../constant' import { ResizeControl } from '../../view/Control' - import AnchorConfig = Model.AnchorConfig import GraphElements = LogicFlow.GraphElements import TextConfig = LogicFlow.TextConfig @@ -102,6 +101,7 @@ export class BaseNodeModel implements IBaseNodeModel { @observable draggable = true @observable visible = true @observable enableRotate = true + @observable enableResize = true // 其它属性 graphModel: GraphModel @@ -218,7 +218,7 @@ export class BaseNodeModel implements IBaseNodeModel { } /** - * 初始化文本属性 + * 始化文本属性 */ private formatText(data: NodeConfig): void { if (!data.text) { @@ -248,14 +248,13 @@ export class BaseNodeModel implements IBaseNodeModel { * @overridable 支持重写 * 计算节点 resize 时 */ - // TODO:重新计算宽高,还是用 svg 的 resize(resizeInfo: ResizeInfo): ResizeNodeData { const { width, height, deltaX, deltaY } = resizeInfo // 移动节点以及文本内容 - this.move(deltaX, deltaY) + this.move(deltaX / 2, deltaY / 2) - this.width = resizeInfo.width - this.height = resizeInfo.height + this.width = width + this.height = height this.setProperties({ width, height, @@ -618,9 +617,9 @@ export class BaseNodeModel implements IBaseNodeModel { } } - @action move(deltaX: number, deltaY: number, isIgnoreRule = false): boolean { - let isAllowMoveX = false - let isAllowMoveY = false + isAllowMoveByXORY(deltaX: number, deltaY: number, isIgnoreRule: boolean) { + let isAllowMoveX: boolean + let isAllowMoveY: boolean if (isIgnoreRule) { isAllowMoveX = true isAllowMoveY = true @@ -634,14 +633,24 @@ export class BaseNodeModel implements IBaseNodeModel { isAllowMoveY = r.y } } + return { + isAllowMoveX, + isAllowMoveY, + } + } + + @action move(deltaX: number, deltaY: number, isIgnoreRule = false): boolean { + const { isAllowMoveX, isAllowMoveY } = this.isAllowMoveByXORY( + deltaX, + deltaY, + isIgnoreRule, + ) if (isAllowMoveX) { - const targetX = this.x + deltaX - this.x = targetX + this.x = this.x + deltaX this.text && this.moveText(deltaX, 0) } if (isAllowMoveY) { - const targetY = this.y + deltaY - this.y = targetY + this.y = this.y + deltaY this.text && this.moveText(0, deltaY) } return isAllowMoveX || isAllowMoveY @@ -652,32 +661,21 @@ export class BaseNodeModel implements IBaseNodeModel { deltaY: number, isIgnoreRule = false, ): [number, number] { - let isAllowMoveX = false - let isAllowMoveY = false + const { isAllowMoveX, isAllowMoveY } = this.isAllowMoveByXORY( + deltaX, + deltaY, + isIgnoreRule, + ) let moveX = 0 let moveY = 0 - if (isIgnoreRule) { - isAllowMoveX = true - isAllowMoveY = true - } else { - const r = this.isAllowMoveNode(deltaX, deltaY) - if (typeof r === 'boolean') { - isAllowMoveX = r - isAllowMoveY = r - } else { - isAllowMoveX = r.x - isAllowMoveY = r.y - } - } + if (isAllowMoveX && deltaX) { - const targetX = this.x + deltaX - this.x = targetX + this.x = this.x + deltaX this.text && this.moveText(deltaX, 0) moveX = deltaX } if (isAllowMoveY && deltaY) { - const targetY = this.y + deltaY - this.y = targetY + this.y = this.y + deltaY this.text && this.moveText(0, deltaY) moveY = deltaY } @@ -733,6 +731,10 @@ export class BaseNodeModel implements IBaseNodeModel { this.enableRotate = flag } + @action setEnableResize(flag = true): void { + this.enableResize = flag + } + @action setHitable(flag = true): void { this.isHitable = flag } @@ -826,7 +828,7 @@ export class BaseNodeModel implements IBaseNodeModel { this.zIndex = zIndex } - @action updateAttributes(attributes) { + @action updateAttributes(attributes: any) { assign(this, attributes) } } diff --git a/packages/core/src/model/node/DiamondNodeModel.ts b/packages/core/src/model/node/DiamondNodeModel.ts index 28c7b4779..bcd0ff070 100644 --- a/packages/core/src/model/node/DiamondNodeModel.ts +++ b/packages/core/src/model/node/DiamondNodeModel.ts @@ -1,16 +1,21 @@ -import { cloneDeep } from 'lodash-es' +import { cloneDeep, forEach, map } from 'lodash-es' import { computed, observable } from 'mobx' import BaseNodeModel from './BaseNodeModel' import LogicFlow from '../../LogicFlow' import { ModelType } from '../../constant' - import PointTuple = LogicFlow.PointTuple import Point = LogicFlow.Point +import { ResizeControl } from '../../view/Control' + +import ResizeInfo = ResizeControl.ResizeInfo +import ResizeNodeData = ResizeControl.ResizeNodeData export class DiamondNodeModel extends BaseNodeModel { modelType = ModelType.DIAMOND_NODE @observable rx = 30 @observable ry = 50 + @observable properties: any = {} + getNodeStyle() { const style = super.getNodeStyle() const { @@ -23,6 +28,7 @@ export class DiamondNodeModel extends BaseNodeModel { ...cloneDeep(diamond), } } + @computed get points(): PointTuple[] { const { x, y, rx, ry } = this return [ @@ -34,17 +40,13 @@ export class DiamondNodeModel extends BaseNodeModel { } @computed get pointsPosition(): Point[] { - const pointsPosition = this.points.map((item) => ({ - x: item[0], - y: item[1], - })) - return pointsPosition + return map(this.points, ([x, y]) => ({ x, y })) } @computed get width(): number { let min = Number.MAX_SAFE_INTEGER let max = Number.MIN_SAFE_INTEGER - this.points.forEach(([x]) => { + forEach(this.points, ([x]) => { if (x < min) { min = x } @@ -58,7 +60,7 @@ export class DiamondNodeModel extends BaseNodeModel { @computed get height(): number { let min = Number.MAX_SAFE_INTEGER let max = Number.MIN_SAFE_INTEGER - this.points.forEach(([, y]) => { + forEach(this.points, ([, y]) => { if (y < min) { min = y } @@ -70,12 +72,27 @@ export class DiamondNodeModel extends BaseNodeModel { } getDefaultAnchor() { - return this.points.map(([x1, y1], idx) => ({ - x: x1, - y: y1, + return map(this.points, ([x, y], idx) => ({ + x, + y, id: `${this.id}_${idx}`, })) } + + resize(resizeInfo: ResizeInfo): ResizeNodeData { + const { width, height, deltaX, deltaY } = resizeInfo + // 移动节点以及文本内容 + this.move(deltaX / 2, deltaY / 2) + + this.rx = width + this.ry = height + // this.setProperties({ + // rx, + // ry, + // }) + + return this.getData() + } } export default DiamondNodeModel diff --git a/packages/core/src/model/node/TextNodeModel.ts b/packages/core/src/model/node/TextNodeModel.ts index d5e8d7fcb..932100d30 100644 --- a/packages/core/src/model/node/TextNodeModel.ts +++ b/packages/core/src/model/node/TextNodeModel.ts @@ -6,6 +6,7 @@ import { getSvgTextWidthHeight } from '../../util' export class TextNodeModel extends BaseNodeModel { modelType = ModelType.TEXT_NODE + getTextStyle() { const style = super.getTextStyle() const { text } = this.graphModel.theme @@ -24,6 +25,7 @@ export class TextNodeModel extends BaseNodeModel { }) return width } + @computed get height(): number { const rows = String(this.text.value).split(/[\r\n]/g) const { fontSize } = this.getTextStyle() diff --git a/packages/core/src/style/index.less b/packages/core/src/style/index.less index 05b6fa2f7..6b3cb45ff 100644 --- a/packages/core/src/style/index.less +++ b/packages/core/src/style/index.less @@ -222,3 +222,35 @@ .lf-rotate { cursor: grabbing; } + +.lf-resize-control-nw { + cursor: nw-resize; +} + +.lf-resize-control-n { + cursor: n-resize; +} + +.lf-resize-control-ne { + cursor: ne-resize; +} + +.lf-resize-control-e { + cursor: e-resize; +} + +.lf-resize-control-se { + cursor: se-resize; +} + +.lf-resize-control-s { + cursor: s-resize; +} + +.lf-resize-control-sw { + cursor: sw-resize; +} + +.lf-resize-control-w { + cursor: w-resize; +} diff --git a/packages/core/src/style/raw.ts b/packages/core/src/style/raw.ts index 1bd092f01..00df97325 100644 --- a/packages/core/src/style/raw.ts +++ b/packages/core/src/style/raw.ts @@ -190,4 +190,28 @@ export const content = `.lf-graph { .lf-rotate { cursor: grabbing; } +.lf-resize-control-nw { + cursor: nw-resize; +} +.lf-resize-control-n { + cursor: n-resize; +} +.lf-resize-control-ne { + cursor: ne-resize; +} +.lf-resize-control-e { + cursor: e-resize; +} +.lf-resize-control-se { + cursor: se-resize; +} +.lf-resize-control-s { + cursor: s-resize; +} +.lf-resize-control-sw { + cursor: sw-resize; +} +.lf-resize-control-w { + cursor: w-resize; +} ` diff --git a/packages/core/src/view/Control.tsx b/packages/core/src/view/Control.tsx index c030ac7f6..8eb8f0420 100644 --- a/packages/core/src/view/Control.tsx +++ b/packages/core/src/view/Control.tsx @@ -19,15 +19,12 @@ export enum ResizeControlIndex { LEFT_BOTTOM = 3, } -export interface IResizeControlProps { - index: ResizeControlIndex - x: number - y: number +export type IResizeControlProps = { model: BaseNodeModel graphModel: GraphModel -} +} & ControlItemProps -export interface IResizeControlState { +export type IResizeControlState = { startX: number startY: number endX: number @@ -39,7 +36,7 @@ export class ResizeControl extends Component< IResizeControlProps, IResizeControlState > { - readonly index: number + readonly index: ResizeControlIndex readonly nodeModel: BaseNodeModel readonly graphModel: GraphModel readonly dragHandler: StepDrag @@ -48,6 +45,7 @@ export class ResizeControl extends Component< super() const { index, model, graphModel } = props this.index = index + console.log('this.index', index) this.nodeModel = model this.graphModel = graphModel @@ -122,7 +120,7 @@ export class ResizeControl extends Component< * @param freezeWidth * @param freezeHeight */ - reCalcResizeInfo = ( + recalcResizeInfo = ( index: ResizeControlIndex, resizeInfo: ResizeInfo, pct = 1, @@ -130,8 +128,8 @@ export class ResizeControl extends Component< freezeHeight = false, ): ResizeInfo => { const nextResizeInfo = cloneDeep(resizeInfo) - let { deltaX, deltaY } = resizeInfo - const { width, height, PCTResizeInfo } = resizeInfo + let { deltaX, deltaY } = nextResizeInfo + const { width, height, PCTResizeInfo } = nextResizeInfo if (PCTResizeInfo) { const sensitivity = 4 // 越低越灵敏 let deltaScale = 0 @@ -174,6 +172,7 @@ export class ResizeControl extends Component< ), PCTResizeInfo.ScaleLimit.minScaleLimit, ) + const spcWidth = Math.round( (PCTResizeInfo.ResizePCT.widthPCT * PCTResizeInfo.ResizeBasis.basisWidth) / @@ -207,6 +206,7 @@ export class ResizeControl extends Component< } return nextResizeInfo } + // 如果限制了宽/高不变,对应的 width/height 保持一致 switch (index) { case ResizeControlIndex.LEFT_TOP: @@ -228,6 +228,7 @@ export class ResizeControl extends Component< default: break } + return nextResizeInfo } @@ -257,33 +258,27 @@ export class ResizeControl extends Component< PCTResizeInfo, } - const nextSize = this.reCalcResizeInfo( + const nextSize = this.recalcResizeInfo( this.index, resizeInfo, 1, isFreezeWidth, isFreezeHeight, ) - const { - width: nextWidth, - height: nextHeight, - deltaX: nextDeltaX, - deltaY: nextDeltaY, - } = nextSize // 限制放大缩小的最大最小范围 if ( - nextWidth < minWidth || - nextWidth > maxWidth || - nextHeight < minHeight || - nextHeight > maxHeight + nextSize.width < minWidth || + nextSize.width > maxWidth || + nextSize.height < minHeight || + nextSize.height > maxHeight ) { this.dragHandler.cancelDrag() return } // 如果限制了宽高不变,对应的 x/y 不产生位移 - nextSize.deltaX = isFreezeWidth ? 0 : nextDeltaX - nextSize.deltaY = isFreezeWidth ? 0 : nextDeltaY + nextSize.deltaX = isFreezeWidth ? 0 : nextSize.deltaX + nextSize.deltaY = isFreezeWidth ? 0 : nextSize.deltaY const preNodeData = this.nodeModel.getData() const nextNodeData = this.nodeModel.resize(nextSize) @@ -318,12 +313,12 @@ export class ResizeControl extends Component< } render(): h.JSX.Element { - const { x, y, index, model } = this.props + const { x, y, direction, model } = this.props const style = model.getResizeControlStyle() return ( - + { const { model, graphModel } = this.props const { minX, minY, maxX, maxY } = getNodeBBox(model) const controlList: ControlItemProps[] = [ - // 左上角 { index: ResizeControlIndex.LEFT_TOP, + direction: 'nw', x: minX, y: minY, - }, - // 右上角 + }, // 左上角 { index: ResizeControlIndex.RIGHT_TOP, + direction: 'ne', x: maxX, y: minY, - }, - // 右下角 + }, // 右上角 { index: ResizeControlIndex.RIGHT_BOTTOM, + direction: 'se', x: maxX, y: maxY, - }, - // 左下角 + }, // 右下角 { index: ResizeControlIndex.LEFT_BOTTOM, + direction: 'sw', x: minX, y: maxY, - }, + }, // 左下角 ] return map(controlList, (control) => ( @@ -417,16 +412,27 @@ export namespace ResizeControl { } export type ResizeNodeData = NodeData & Partial + export type Direction = 'nw' | 'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w' export type ControlItemProps = { index: ResizeControlIndex + direction: Direction x: number y: number } export type PCTResizeParams = { - ResizePCT: { widthPCT: number; heightPCT: number } - ResizeBasis: { basisWidth: number; basisHeight: number } - ScaleLimit: { maxScaleLimit: number; minScaleLimit: number } + ResizePCT: { + widthPCT: number + heightPCT: number + } + ResizeBasis: { + basisWidth: number + basisHeight: number + } + ScaleLimit: { + maxScaleLimit: number + minScaleLimit: number + } } } diff --git a/packages/core/src/view/behavior/DnD.ts b/packages/core/src/view/behavior/DnD.ts index ddd860d00..937005aed 100644 --- a/packages/core/src/view/behavior/DnD.ts +++ b/packages/core/src/view/behavior/DnD.ts @@ -4,18 +4,11 @@ import { BaseNodeModel } from '../../model' import { snapToGrid } from '../../util' import { EventType } from '../../constant' -import TextConfig = LogicFlow.TextConfig import Position = LogicFlow.Position - -export type NewNodeConfig = { - type: string - text?: TextConfig | string - properties?: Record - [key: string]: any -} +import OnDragNodeConfig = LogicFlow.OnDragNodeConfig export class Dnd { - nodeConfig: NewNodeConfig | null = null + nodeConfig: OnDragNodeConfig | null = null lf: LogicFlow fakeNode: BaseNodeModel | null = null @@ -40,7 +33,7 @@ export class Dnd { } } - startDrag(nodeConfig: NewNodeConfig) { + startDrag(nodeConfig: OnDragNodeConfig) { const { editConfigModel } = this.lf.graphModel if (!editConfigModel?.isSilentMode) { this.nodeConfig = nodeConfig diff --git a/packages/core/src/view/node/BaseNode.tsx b/packages/core/src/view/node/BaseNode.tsx index 421d7576d..7eaff49f0 100644 --- a/packages/core/src/view/node/BaseNode.tsx +++ b/packages/core/src/view/node/BaseNode.tsx @@ -30,10 +30,7 @@ type IState = { isDragging?: boolean } -export abstract class BaseNode

extends Component< - P, - IState -> { +export abstract class BaseNode

extends Component { static isObserved: boolean = false static extendsKey?: string @@ -78,21 +75,16 @@ export abstract class BaseNode

extends Component< } componentWillUnmount() { - console.log('componentWillUnmount --->>>') if (this.modelDisposer) { this.modelDisposer() } } - componentDidMount() { - console.log('componentDidMount --->>>') - } + componentDidMount() {} - componentDidUpdate() { - console.log('componentDidUpdate --->>>') - } + componentDidUpdate() {} - abstract getShape(): h.JSX.Element + abstract getShape(): h.JSX.Element | null // eslint-disable-next-line @typescript-eslint/no-unused-vars getAnchorShape(_anchorData?: Model.AnchorConfig): h.JSX.Element | null { @@ -139,11 +131,11 @@ export abstract class BaseNode

extends Component< } } - getResizeControl() { + getResizeControl(): h.JSX.Element | null { const { model, graphModel } = this.props - const { isSelected, isHitable, enableRotate, isHovered } = model + const { isSelected, isHitable, enableResize, isHovered } = model const style = model.getResizeControlStyle() - if (isHitable && (isSelected || isHovered) && enableRotate) { + if (isHitable && (isSelected || isHovered) && enableResize) { return ( extends Component< /> ) } + return null } getText(): h.JSX.Element | null { const { model, graphModel } = this.props // 文本被编辑的时候,显示编辑框,不显示文本。 - console.log('model.state --->>>', model.state) if (model.state === ElementState.TEXT_EDIT) { return null } @@ -340,8 +332,6 @@ export abstract class BaseNode

extends Component< // 这里 IE 11不能正确显示 const isDoubleClick = e.detail === 2 - console.log('isDoubleClick --->>>', isDoubleClick) - // 判断是否有右击,如果有右击则取消点击事件触发 if (isRightClick) return @@ -444,7 +434,12 @@ export abstract class BaseNode

extends Component< render() { const { model, graphModel } = this.props const { - editConfigModel: { hideAnchors, adjustNodePosition, allowRotate }, + editConfigModel: { + hideAnchors, + adjustNodePosition, + allowRotate, + allowResize, + }, gridSize, transformModel: { SCALE_X }, } = graphModel @@ -455,8 +450,8 @@ export abstract class BaseNode

extends Component< {this.getShape()} {this.getText()} - {this.getResizeControl()} {allowRotate && this.getRotateControl()} + {allowResize && this.getResizeControl()} {!hideAnchors && this.getAnchors()} @@ -481,9 +476,6 @@ export abstract class BaseNode

extends Component< onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp} onClick={this.handleClick} - onDblClick={() => { - console.log('onDblClick --->>>') - }} onMouseEnter={this.setHoverON} onMouseOver={this.setHoverON} onMouseLeave={this.setHoverOFF} diff --git a/packages/core/src/view/node/TextNode.tsx b/packages/core/src/view/node/TextNode.tsx index 60f330e08..2fd564e5c 100644 --- a/packages/core/src/view/node/TextNode.tsx +++ b/packages/core/src/view/node/TextNode.tsx @@ -25,6 +25,10 @@ export class TextNode extends BaseNode { return } + getResizeControl() { + return null + } + getShape() { return {this.getBackground()} } From 399c5fb3f998d7d6b319f35403c8949d28e570e0 Mon Sep 17 00:00:00 2001 From: R0ger1tlearn Date: Sat, 22 Jun 2024 11:44:01 +0800 Subject: [PATCH 04/10] =?UTF-8?q?refactor(chore):=20=E6=8C=89=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E5=91=BD=E5=90=8D=E8=A7=84=E5=88=99=E9=87=8D=E5=91=BD?= =?UTF-8?q?=E5=90=8D=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DefaultAnimation.ts -> defaultAnimation.ts - DefatultTheme.ts -> defaultTheme.ts - history/History.ts -> history/index.ts - 并更新项目中各个文件的引用地址 --- packages/core/src/LogicFlow.tsx | 4 ++-- .../src/constant/{DefaultAnimation.ts => defaultAnimation.ts} | 0 .../core/src/constant/{DefaultTheme.ts => defaultTheme.ts} | 0 packages/core/src/constant/index.ts | 3 +-- packages/core/src/history/{History.ts => index.ts} | 0 packages/core/src/model/GraphModel.ts | 4 ++-- packages/core/src/view/Graph.tsx | 2 +- packages/core/src/view/Rotate.tsx | 2 +- packages/core/src/view/behavior/{DnD.ts => dnd.ts} | 0 packages/core/src/view/overlay/CanvasOverlay.tsx | 2 +- 10 files changed, 8 insertions(+), 9 deletions(-) rename packages/core/src/constant/{DefaultAnimation.ts => defaultAnimation.ts} (100%) rename packages/core/src/constant/{DefaultTheme.ts => defaultTheme.ts} (100%) rename packages/core/src/history/{History.ts => index.ts} (100%) rename packages/core/src/view/behavior/{DnD.ts => dnd.ts} (100%) diff --git a/packages/core/src/LogicFlow.tsx b/packages/core/src/LogicFlow.tsx index e261627e2..81fe17e37 100644 --- a/packages/core/src/LogicFlow.tsx +++ b/packages/core/src/LogicFlow.tsx @@ -16,11 +16,11 @@ import Graph from './view/Graph' import * as _View from './view' import { formatData } from './util' -import Dnd from './view/behavior/DnD' +import Dnd from './view/behavior/dnd' import Tool from './tool/tool' import { snapline } from './tool' import Keyboard from './keyboard' -import History from './history/History' +import History from './history' import { CallbackType, EventArgs } from './event/eventEmitter' import { ElementType, EventType, SegmentDirection } from './constant' import { initDefaultShortcut } from './keyboard/shortcut' diff --git a/packages/core/src/constant/DefaultAnimation.ts b/packages/core/src/constant/defaultAnimation.ts similarity index 100% rename from packages/core/src/constant/DefaultAnimation.ts rename to packages/core/src/constant/defaultAnimation.ts diff --git a/packages/core/src/constant/DefaultTheme.ts b/packages/core/src/constant/defaultTheme.ts similarity index 100% rename from packages/core/src/constant/DefaultTheme.ts rename to packages/core/src/constant/defaultTheme.ts diff --git a/packages/core/src/constant/index.ts b/packages/core/src/constant/index.ts index cd454e0ab..bbd8de673 100644 --- a/packages/core/src/constant/index.ts +++ b/packages/core/src/constant/index.ts @@ -1,4 +1,5 @@ export const DEFAULT_VISIBLE_SPACE = 200 +export const ELEMENT_MAX_Z_INDEX = 9999 export enum ElementState { DEFAULT = 1, // 默认显示 @@ -122,5 +123,3 @@ export enum SegmentDirection { HORIZONTAL = 'horizontal', VERTICAL = 'vertical', } - -export const ElementMaxZIndex = 9999 diff --git a/packages/core/src/history/History.ts b/packages/core/src/history/index.ts similarity index 100% rename from packages/core/src/history/History.ts rename to packages/core/src/history/index.ts diff --git a/packages/core/src/model/GraphModel.ts b/packages/core/src/model/GraphModel.ts index 9bfb82ca8..4b0c31c7c 100644 --- a/packages/core/src/model/GraphModel.ts +++ b/packages/core/src/model/GraphModel.ts @@ -10,7 +10,7 @@ import { } from '.' import { DEFAULT_VISIBLE_SPACE, - ElementMaxZIndex, + ELEMENT_MAX_Z_INDEX, ElementState, ElementType, EventType, @@ -657,7 +657,7 @@ export class GraphModel { if (element) { if (this.overlapMode === OverlapMode.DEFAULT) { this.topElement?.setZIndex() - element.setZIndex(ElementMaxZIndex) + element.setZIndex(ELEMENT_MAX_Z_INDEX) this.topElement = element } if (this.overlapMode === OverlapMode.INCREASE) { diff --git a/packages/core/src/view/Graph.tsx b/packages/core/src/view/Graph.tsx index dd2676a5e..83a324b0b 100644 --- a/packages/core/src/view/Graph.tsx +++ b/packages/core/src/view/Graph.tsx @@ -10,7 +10,7 @@ import { BezierAdjustOverlay, ModificationOverlay, } from './overlay' -import DnD from './behavior/DnD' +import DnD from './behavior/dnd' import { observer } from '..' import { Options as LFOptions } from '../options' import Tool from '../tool/tool' diff --git a/packages/core/src/view/Rotate.tsx b/packages/core/src/view/Rotate.tsx index 453b79c9f..5cf5156ca 100644 --- a/packages/core/src/view/Rotate.tsx +++ b/packages/core/src/view/Rotate.tsx @@ -4,7 +4,7 @@ import Circle from './shape/Circle' import { GraphModel, BaseNodeModel } from '../model' import { StepDrag, TranslateMatrix, Vector } from '../util' import EventEmitter from '../event/eventEmitter' -import { CommonTheme } from '../constant/DefaultTheme' +import { CommonTheme } from '../constant/defaultTheme' import { EventType } from '../constant' interface IRotateControlProps { diff --git a/packages/core/src/view/behavior/DnD.ts b/packages/core/src/view/behavior/dnd.ts similarity index 100% rename from packages/core/src/view/behavior/DnD.ts rename to packages/core/src/view/behavior/dnd.ts diff --git a/packages/core/src/view/overlay/CanvasOverlay.tsx b/packages/core/src/view/overlay/CanvasOverlay.tsx index 25f832bd8..a18be2456 100644 --- a/packages/core/src/view/overlay/CanvasOverlay.tsx +++ b/packages/core/src/view/overlay/CanvasOverlay.tsx @@ -1,9 +1,9 @@ import { Component } from 'preact/compat' +import Dnd from '../behavior/dnd' import { observer } from '../..' import GraphModel from '../../model/GraphModel' import { EventType } from '../../constant' import { StepDrag, IDragParams } from '../../util' -import Dnd from '../behavior/DnD' type IProps = { graphModel: GraphModel From 595edbb198437947b11c8b1154a1ec16d78453a6 Mon Sep 17 00:00:00 2001 From: R0ger1tlearn Date: Sat, 22 Jun 2024 11:45:46 +0800 Subject: [PATCH 05/10] =?UTF-8?q?feat(chore):=20circle/diamond/ellipse/pol?= =?UTF-8?q?ygon=20=E7=AD=89=E8=8A=82=E7=82=B9=E5=A2=9E=E5=8A=A0=20resize?= =?UTF-8?q?=20=E8=83=BD=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/src/model/node/CircleNodeModel.ts | 38 +++++++++++++++++ .../core/src/model/node/DiamondNodeModel.ts | 42 +++++++++++++++---- .../core/src/model/node/EllipseNodeModel.ts | 34 +++++++++++++++ .../core/src/model/node/PolygonNodeModel.ts | 8 ++-- packages/core/src/model/node/RectNodeModel.ts | 2 + packages/core/src/view/Control.tsx | 10 +++-- 6 files changed, 120 insertions(+), 14 deletions(-) diff --git a/packages/core/src/model/node/CircleNodeModel.ts b/packages/core/src/model/node/CircleNodeModel.ts index bbe957344..542ceb924 100644 --- a/packages/core/src/model/node/CircleNodeModel.ts +++ b/packages/core/src/model/node/CircleNodeModel.ts @@ -4,16 +4,29 @@ import BaseNodeModel from './BaseNodeModel' import GraphModel from '../GraphModel' import LogicFlow from '../../LogicFlow' import { ModelType } from '../../constant' +import { ResizeControl } from '../../view/Control' import NodeConfig = LogicFlow.NodeConfig +import ResizeInfo = ResizeControl.ResizeInfo +import ResizeNodeData = ResizeControl.ResizeNodeData + +export type ICircleNodeProperties = { + r?: number + style?: LogicFlow.CommonTheme + textStyle?: LogicFlow.CommonTheme + + [key: string]: any +} export class CircleNodeModel extends BaseNodeModel { modelType = ModelType.CIRCLE_NODE + @observable properties: ICircleNodeProperties = {} @observable r = 50 @computed get width(): number { return this.r * 2 } + @computed get height(): number { return this.r * 2 } @@ -23,6 +36,15 @@ export class CircleNodeModel extends BaseNodeModel { this.setAttributes() } + setAttributes() { + super.setAttributes() + + const { r } = this.properties + if (r) { + this.r = r + } + } + getNodeStyle() { const style = super.getNodeStyle() const { @@ -30,11 +52,14 @@ export class CircleNodeModel extends BaseNodeModel { theme: { circle }, }, } = this + const { style: customStyle } = this.properties return { ...style, + ...(customStyle ?? {}), ...cloneDeep(circle), } } + getDefaultAnchor() { const { x, y, r } = this return [ @@ -44,6 +69,19 @@ export class CircleNodeModel extends BaseNodeModel { { x: x - r, y, id: `${this.id}_3` }, ] } + + resize(resizeInfo: ResizeInfo): ResizeNodeData { + const { width, deltaX, deltaY } = resizeInfo + // 移动节点以及文本内容 + this.move(deltaX / 2, deltaY / 2) + + this.r = width + this.setProperties({ + r: width, + }) + + return this.getData() + } } export default CircleNodeModel diff --git a/packages/core/src/model/node/DiamondNodeModel.ts b/packages/core/src/model/node/DiamondNodeModel.ts index bcd0ff070..633a88b4c 100644 --- a/packages/core/src/model/node/DiamondNodeModel.ts +++ b/packages/core/src/model/node/DiamondNodeModel.ts @@ -1,20 +1,48 @@ import { cloneDeep, forEach, map } from 'lodash-es' import { computed, observable } from 'mobx' +import GraphModel from '../GraphModel' import BaseNodeModel from './BaseNodeModel' import LogicFlow from '../../LogicFlow' import { ModelType } from '../../constant' -import PointTuple = LogicFlow.PointTuple -import Point = LogicFlow.Point import { ResizeControl } from '../../view/Control' +import Point = LogicFlow.Point +import PointTuple = LogicFlow.PointTuple +import NodeConfig = LogicFlow.NodeConfig import ResizeInfo = ResizeControl.ResizeInfo import ResizeNodeData = ResizeControl.ResizeNodeData +export type IDiamondNodeProperties = { + rx?: number + ry?: number + style?: LogicFlow.CommonTheme + textStyle?: LogicFlow.CommonTheme + + [key: string]: any +} + export class DiamondNodeModel extends BaseNodeModel { modelType = ModelType.DIAMOND_NODE @observable rx = 30 @observable ry = 50 - @observable properties: any = {} + @observable properties: IDiamondNodeProperties = {} + + constructor(data: NodeConfig, graphModel: GraphModel) { + super(data, graphModel) + this.setAttributes() + } + + setAttributes() { + super.setAttributes() + + const { rx, ry } = this.properties + if (rx) { + this.rx = rx + } + if (ry) { + this.ry = ry + } + } getNodeStyle() { const style = super.getNodeStyle() @@ -86,10 +114,10 @@ export class DiamondNodeModel extends BaseNodeModel { this.rx = width this.ry = height - // this.setProperties({ - // rx, - // ry, - // }) + this.setProperties({ + rx: width, + ry: height, + }) return this.getData() } diff --git a/packages/core/src/model/node/EllipseNodeModel.ts b/packages/core/src/model/node/EllipseNodeModel.ts index ae5d3d983..abde845b9 100644 --- a/packages/core/src/model/node/EllipseNodeModel.ts +++ b/packages/core/src/model/node/EllipseNodeModel.ts @@ -1,12 +1,44 @@ import { cloneDeep } from 'lodash-es' import { computed, observable } from 'mobx' +import GraphModel from '../GraphModel' import BaseNodeModel from './BaseNodeModel' +import LogicFlow from '../../LogicFlow' import { ModelType } from '../../constant' +import NodeConfig = LogicFlow.NodeConfig + +export type IEllipseNodeProperties = { + rx?: number + ry?: number + style?: LogicFlow.CommonTheme + textStyle?: LogicFlow.CommonTheme + + [key: string]: any +} + export class EllipseNodeModel extends BaseNodeModel { modelType = ModelType.ELLIPSE_NODE @observable rx = 30 @observable ry = 45 + @observable properties: IEllipseNodeProperties = {} + + constructor(data: NodeConfig, graphModel: GraphModel) { + super(data, graphModel) + this.setAttributes() + } + + setAttributes() { + super.setAttributes() + + const { rx, ry } = this.properties + if (rx) { + this.rx = rx + } + if (ry) { + this.ry = ry + } + } + getNodeStyle() { const style = super.getNodeStyle() const { @@ -19,9 +51,11 @@ export class EllipseNodeModel extends BaseNodeModel { ...cloneDeep(ellipse), } } + @computed get width(): number { return this.rx * 2 } + @computed get height(): number { return this.ry * 2 } diff --git a/packages/core/src/model/node/PolygonNodeModel.ts b/packages/core/src/model/node/PolygonNodeModel.ts index 983cc5cf4..712835ea6 100644 --- a/packages/core/src/model/node/PolygonNodeModel.ts +++ b/packages/core/src/model/node/PolygonNodeModel.ts @@ -3,7 +3,6 @@ import { computed, observable } from 'mobx' import BaseNodeModel from './BaseNodeModel' import LogicFlow from '../../LogicFlow' import { ModelType } from '../../constant' - import PointTuple = LogicFlow.PointTuple import Point = LogicFlow.Point @@ -15,6 +14,7 @@ export class PolygonNodeModel extends BaseNodeModel { [50, 100], [0, 50], ] + getNodeStyle() { const style = super.getNodeStyle() const { @@ -27,6 +27,7 @@ export class PolygonNodeModel extends BaseNodeModel { ...cloneDeep(polygon), } } + /** * 由于大多数情况下,我们初始化拿到的多边形坐标都是基于原点的(例如绘图工具到处的svg)。 * 在logicflow中对多边形进行移动,我们不需要去更新points, @@ -34,12 +35,12 @@ export class PolygonNodeModel extends BaseNodeModel { */ @computed get pointsPosition(): Point[] { const { x, y, width, height } = this - const pointsPosition = this.points.map((item) => ({ + return this.points.map((item) => ({ x: item[0] + x - width / 2, y: item[1] + y - height / 2, })) - return pointsPosition } + @computed get width(): number { let min = Number.MAX_SAFE_INTEGER let max = Number.MIN_SAFE_INTEGER @@ -53,6 +54,7 @@ export class PolygonNodeModel extends BaseNodeModel { }) return max - min } + @computed get height(): number { let min = Number.MAX_SAFE_INTEGER let max = Number.MIN_SAFE_INTEGER diff --git a/packages/core/src/model/node/RectNodeModel.ts b/packages/core/src/model/node/RectNodeModel.ts index 21737ba08..faaf1beb2 100644 --- a/packages/core/src/model/node/RectNodeModel.ts +++ b/packages/core/src/model/node/RectNodeModel.ts @@ -6,6 +6,7 @@ import { ModelType } from '../../constant' export class RectNodeModel extends BaseNodeModel { modelType = ModelType.RECT_NODE @observable radius = 0 + getDefaultAnchor() { const { x, y, width, height } = this return [ @@ -15,6 +16,7 @@ export class RectNodeModel extends BaseNodeModel { { x: x - width / 2, y, id: `${this.id}_3` }, ] } + getNodeStyle() { const style = super.getNodeStyle() const { rect } = this.graphModel.theme diff --git a/packages/core/src/view/Control.tsx b/packages/core/src/view/Control.tsx index 8eb8f0420..4ac99b582 100644 --- a/packages/core/src/view/Control.tsx +++ b/packages/core/src/view/Control.tsx @@ -236,6 +236,7 @@ export class ResizeControl extends Component< // TODO: 调用每个节点中更新缩放时的方法 updateNode 函数,用来各节点缩放的方法 // 1. 计算当前 Control 的一些信息, const { + r, rx, ry, width, @@ -251,17 +252,18 @@ export class ResizeControl extends Component< const isFreezeHeight = minHeight === maxHeight const resizeInfo = { - width: width || rx, - height: height || ry, + width: width || rx || r, + height: height || ry || r, deltaX, deltaY, PCTResizeInfo, } + const pct = width ? 1 : 1 / 2 const nextSize = this.recalcResizeInfo( this.index, resizeInfo, - 1, + pct, isFreezeWidth, isFreezeHeight, ) @@ -383,7 +385,7 @@ export class ResizeControlGroup extends Component { render(): h.JSX.Element { return ( - + {this.getResizeOutline()} {this.getResizeControl()} From b55d8bcd30a8250b59c8964b8ade0f2225486deb Mon Sep 17 00:00:00 2001 From: boyongjiong Date: Sat, 22 Jun 2024 18:03:17 +0800 Subject: [PATCH 06/10] =?UTF-8?q?feat(core):=20=E8=8A=82=E7=82=B9=20model?= =?UTF-8?q?=20=E4=B8=AD=E5=A2=9E=E5=8A=A0=E5=88=9D=E5=A7=8B=E5=8C=96?= =?UTF-8?q?=E6=97=B6=20properties=20=E7=9A=84=E4=BF=9D=E5=AD=98=EF=BC=8C?= =?UTF-8?q?=E8=B0=83=E8=AF=95=E5=A4=9A=E8=BE=B9=E5=BD=A2=E8=8A=82=E7=82=B9?= =?UTF-8?q?=20outline=20=E4=B8=8D=E7=AC=A6=E5=90=88=E9=A2=84=E6=9C=9F?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/graph/index.tsx | 20 +++--- packages/core/src/model/node/BaseNodeModel.ts | 2 + .../core/src/model/node/CircleNodeModel.ts | 2 + .../core/src/model/node/DiamondNodeModel.ts | 2 + .../core/src/model/node/EllipseNodeModel.ts | 22 +++++- .../core/src/model/node/PolygonNodeModel.ts | 68 +++++++++++++++++-- packages/core/src/style/index.less | 2 +- packages/core/src/style/raw.ts | 2 +- packages/core/src/util/theme.ts | 3 +- packages/core/src/view/Control.tsx | 20 ++++-- packages/core/src/view/node/PolygonNode.tsx | 5 ++ .../core/src/view/overlay/OutlineOverlay.tsx | 12 +++- 12 files changed, 133 insertions(+), 27 deletions(-) diff --git a/examples/feature-examples/src/pages/graph/index.tsx b/examples/feature-examples/src/pages/graph/index.tsx index b13971740..2e2144ad4 100644 --- a/examples/feature-examples/src/pages/graph/index.tsx +++ b/examples/feature-examples/src/pages/graph/index.tsx @@ -89,14 +89,10 @@ const data = { { id: 'custom-node-2', - text: { - x: 200, - y: 200, - value: 'node-2', - }, - type: 'rect', - x: 200, - y: 200, + text: 'node-2', + type: 'polygon', + x: 0, + y: 0, }, ], } @@ -507,6 +503,14 @@ export default function BasicNode() { handleDragItem({ type: 'polygon', text: 'polygon', + properties: { + style: { + fill: '#ffd591', + stroke: '#ffa940', + strokeWidth: 2, + fillRule: 'evenodd', + }, + }, }) }} > diff --git a/packages/core/src/model/node/BaseNodeModel.ts b/packages/core/src/model/node/BaseNodeModel.ts index 1b998a94f..05db4fa6f 100644 --- a/packages/core/src/model/node/BaseNodeModel.ts +++ b/packages/core/src/model/node/BaseNodeModel.ts @@ -139,6 +139,8 @@ export class BaseNodeModel implements IBaseNodeModel { constructor(data: NodeConfig, graphModel: GraphModel) { this.graphModel = graphModel + this.properties = data.properties || {} + this.initNodeData(data) this.setAttributes() } diff --git a/packages/core/src/model/node/CircleNodeModel.ts b/packages/core/src/model/node/CircleNodeModel.ts index 542ceb924..eb70a953a 100644 --- a/packages/core/src/model/node/CircleNodeModel.ts +++ b/packages/core/src/model/node/CircleNodeModel.ts @@ -33,6 +33,8 @@ export class CircleNodeModel extends BaseNodeModel { constructor(data: NodeConfig, graphModel: GraphModel) { super(data, graphModel) + this.properties = data.properties || {} + this.setAttributes() } diff --git a/packages/core/src/model/node/DiamondNodeModel.ts b/packages/core/src/model/node/DiamondNodeModel.ts index 633a88b4c..915672277 100644 --- a/packages/core/src/model/node/DiamondNodeModel.ts +++ b/packages/core/src/model/node/DiamondNodeModel.ts @@ -29,6 +29,8 @@ export class DiamondNodeModel extends BaseNodeModel { constructor(data: NodeConfig, graphModel: GraphModel) { super(data, graphModel) + this.properties = data.properties || {} + this.setAttributes() } diff --git a/packages/core/src/model/node/EllipseNodeModel.ts b/packages/core/src/model/node/EllipseNodeModel.ts index abde845b9..f95590ce1 100644 --- a/packages/core/src/model/node/EllipseNodeModel.ts +++ b/packages/core/src/model/node/EllipseNodeModel.ts @@ -1,11 +1,14 @@ import { cloneDeep } from 'lodash-es' import { computed, observable } from 'mobx' -import GraphModel from '../GraphModel' import BaseNodeModel from './BaseNodeModel' +import GraphModel from '../GraphModel' import LogicFlow from '../../LogicFlow' import { ModelType } from '../../constant' +import { ResizeControl } from '../../view/Control' import NodeConfig = LogicFlow.NodeConfig +import ResizeInfo = ResizeControl.ResizeInfo +import ResizeNodeData = ResizeControl.ResizeNodeData export type IEllipseNodeProperties = { rx?: number @@ -24,6 +27,8 @@ export class EllipseNodeModel extends BaseNodeModel { constructor(data: NodeConfig, graphModel: GraphModel) { super(data, graphModel) + this.properties = data.properties || {} + this.setAttributes() } @@ -69,6 +74,21 @@ export class EllipseNodeModel extends BaseNodeModel { { x: x - rx, y, id: `${this.id}_3` }, ] } + + resize(resizeInfo: ResizeInfo): ResizeNodeData { + const { width, height, deltaX, deltaY } = resizeInfo + // 移动节点以及文本内容 + this.move(deltaX / 2, deltaY / 2) + + this.rx = width + this.ry = height + this.setProperties({ + rx: width, + ry: height, + }) + + return this.getData() + } } export default EllipseNodeModel diff --git a/packages/core/src/model/node/PolygonNodeModel.ts b/packages/core/src/model/node/PolygonNodeModel.ts index 712835ea6..75f97e43b 100644 --- a/packages/core/src/model/node/PolygonNodeModel.ts +++ b/packages/core/src/model/node/PolygonNodeModel.ts @@ -1,19 +1,52 @@ -import { cloneDeep } from 'lodash-es' +import { cloneDeep, map } from 'lodash-es' import { computed, observable } from 'mobx' import BaseNodeModel from './BaseNodeModel' +import GraphModel from '../GraphModel' import LogicFlow from '../../LogicFlow' import { ModelType } from '../../constant' -import PointTuple = LogicFlow.PointTuple +import { ResizeControl } from '../../view/Control' import Point = LogicFlow.Point +import PointTuple = LogicFlow.PointTuple +import NodeConfig = LogicFlow.NodeConfig +import ResizeInfo = ResizeControl.ResizeInfo +import ResizeNodeData = ResizeControl.ResizeNodeData + +export type IPolygonNodeProperties = { + points?: PointTuple[] + style?: LogicFlow.CommonTheme + textStyle?: LogicFlow.CommonTheme + + [key: string]: any +} export class PolygonNodeModel extends BaseNodeModel { modelType = ModelType.POLYGON_NODE @observable points: PointTuple[] = [ - [50, 0], - [100, 50], - [50, 100], - [0, 50], + // [50, 0], [100, 50], [50, 100], [0, 50] // 菱形 + // [0,100], [50,25], [50,75], [100,0] // 闪电 + [100, 10], + [40, 198], + [190, 78], + [10, 78], + [160, 198], // 五角星 ] + @observable properties: IPolygonNodeProperties = {} + + constructor(data: NodeConfig, graphModel: GraphModel) { + super(data, graphModel) + this.properties = data.properties || {} + + this.setAttributes() + } + + setAttributes() { + super.setAttributes() + + const { points } = this.properties + if (points) { + this.points = points + } + } getNodeStyle() { const style = super.getNodeStyle() @@ -22,9 +55,11 @@ export class PolygonNodeModel extends BaseNodeModel { theme: { polygon }, }, } = this + const { style: customStyle } = this.properties return { ...style, ...cloneDeep(polygon), + ...(cloneDeep(customStyle) || {}), } } @@ -77,6 +112,27 @@ export class PolygonNodeModel extends BaseNodeModel { id: `${this.id}_${idx}`, })) } + + resize(resizeInfo: ResizeInfo): ResizeNodeData { + const { width, height, deltaX, deltaY } = resizeInfo + // 移动节点以及文本内容 + this.move(deltaX / 2, deltaY / 2) + + const nextPoints: PointTuple[] = map(this.points, ([x, y]) => [ + (x * width) / this.width, + (y * height) / this.height, + ]) + this.points = nextPoints + console.log('nextPoints', nextPoints) + console.log('gogo this', this.x, this.y, this.width, this.height) + + this.properties.points = nextPoints + // this.setProperties({ + // points: toJS(nextPoints), + // }) + + return this.getData() + } } export default PolygonNodeModel diff --git a/packages/core/src/style/index.less b/packages/core/src/style/index.less index 6b3cb45ff..6cbfb8cb4 100644 --- a/packages/core/src/style/index.less +++ b/packages/core/src/style/index.less @@ -219,7 +219,7 @@ cursor: move; } -.lf-rotate { +.lf-rotate-control { cursor: grabbing; } diff --git a/packages/core/src/style/raw.ts b/packages/core/src/style/raw.ts index 00df97325..b73733f07 100644 --- a/packages/core/src/style/raw.ts +++ b/packages/core/src/style/raw.ts @@ -187,7 +187,7 @@ export const content = `.lf-graph { .lf-edge-adjust-point { cursor: move; } -.lf-rotate { +.lf-rotate-control { cursor: grabbing; } .lf-resize-control-nw { diff --git a/packages/core/src/util/theme.ts b/packages/core/src/util/theme.ts index 0813ab6e8..4e076fdbb 100644 --- a/packages/core/src/util/theme.ts +++ b/packages/core/src/util/theme.ts @@ -130,7 +130,8 @@ export const defaultTheme: LogicFlow.Theme = { resizeOutline: { fill: 'none', - stroke: 'transparent', // 矩形默认不显示调整边框 + // stroke: 'transparent', // 矩形默认不显示调整边框 + stroke: 'red', // 矩形默认不显示调整边框 strokeWidth: 1, strokeDasharray: '3,3', }, diff --git a/packages/core/src/view/Control.tsx b/packages/core/src/view/Control.tsx index 4ac99b582..c1f3d8b2f 100644 --- a/packages/core/src/view/Control.tsx +++ b/packages/core/src/view/Control.tsx @@ -45,7 +45,6 @@ export class ResizeControl extends Component< super() const { index, model, graphModel } = props this.index = index - console.log('this.index', index) this.nodeModel = model this.graphModel = graphModel @@ -236,10 +235,10 @@ export class ResizeControl extends Component< // TODO: 调用每个节点中更新缩放时的方法 updateNode 函数,用来各节点缩放的方法 // 1. 计算当前 Control 的一些信息, const { - r, - rx, + r, // circle + rx, // ellipse/diamond ry, - width, + width, // rect/html height, PCTResizeInfo, @@ -252,14 +251,14 @@ export class ResizeControl extends Component< const isFreezeHeight = minHeight === maxHeight const resizeInfo = { - width: width || rx || r, - height: height || ry || r, + width: r || rx || width, + height: r || ry || height, deltaX, deltaY, PCTResizeInfo, } - const pct = width ? 1 : 1 / 2 + const pct = r || (rx && ry) ? 1 / 2 : 1 const nextSize = this.recalcResizeInfo( this.index, resizeInfo, @@ -285,6 +284,8 @@ export class ResizeControl extends Component< const preNodeData = this.nodeModel.getData() const nextNodeData = this.nodeModel.resize(nextSize) + console.log('nextNodeData ===>>>', nextNodeData) + // 更新边 this.updateEdgePointByAnchors() // 触发 resize 事件 @@ -316,6 +317,7 @@ export class ResizeControl extends Component< render(): h.JSX.Element { const { x, y, direction, model } = this.props + console.log('polygon index', this.index, 'x', x, 'y', y) const style = model.getResizeControlStyle() return ( @@ -344,7 +346,9 @@ export class ResizeControlGroup extends Component { getResizeControl(): h.JSX.Element[] { const { model, graphModel } = this.props + console.log('polygon node', model.x, model.y, model.width, model.height) const { minX, minY, maxX, maxY } = getNodeBBox(model) + const controlList: ControlItemProps[] = [ { index: ResizeControlIndex.LEFT_TOP, @@ -371,6 +375,8 @@ export class ResizeControlGroup extends Component { y: maxY, }, // 左下角 ] + + console.log('controlList --->>>', controlList) return map(controlList, (control) => ( )) diff --git a/packages/core/src/view/node/PolygonNode.tsx b/packages/core/src/view/node/PolygonNode.tsx index 1c5799ae8..5b540b32a 100644 --- a/packages/core/src/view/node/PolygonNode.tsx +++ b/packages/core/src/view/node/PolygonNode.tsx @@ -8,8 +8,13 @@ export type IPolygonNodeProps = { } export class PolygonNode extends BaseNode { + getText() { + return null + } + getShape() { const { model } = this.props + console.log('polygon model', model) const { x, y, width, height, points } = model as PolygonNodeModel const style = model.getNodeStyle() const attr = { diff --git a/packages/core/src/view/overlay/OutlineOverlay.tsx b/packages/core/src/view/overlay/OutlineOverlay.tsx index 97eeb6e62..1b45fb2f8 100644 --- a/packages/core/src/view/overlay/OutlineOverlay.tsx +++ b/packages/core/src/view/overlay/OutlineOverlay.tsx @@ -4,7 +4,6 @@ import { observer } from '../..' import { ModelType } from '../../constant' import { GraphModel, - BaseNodeModel, LineEdgeModel, BezierEdgeModel, PolylineEdgeModel, @@ -28,11 +27,18 @@ export class OutlineOverlay extends Component { nodes.forEach((element) => { if (element.isHovered || element.isSelected) { const { isHovered, isSelected, x, y, width, height } = element + console.log( + 'gogo element', + element.x, + element.y, + element.width, + element.height, + ) if ( (nodeSelectedOutline && isSelected) || (hoverOutline && isHovered) ) { - const style = (element as BaseNodeModel).getOutlineStyle() + const style = element.getOutlineStyle() let attributes = {} Object.keys(style).forEach((key) => { if (key !== 'hover') { @@ -53,6 +59,8 @@ export class OutlineOverlay extends Component { {...{ x, y, + // width: width + 10, + // height: height + 10, width: width + 10, height: height + 10, }} From 53c9ea09e91d4df58634365f89da942cb88ae2d5 Mon Sep 17 00:00:00 2001 From: R0ger1tlearn Date: Sun, 23 Jun 2024 01:40:32 +0800 Subject: [PATCH 07/10] =?UTF-8?q?feat(core):=20=E5=AE=8C=E6=88=90=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E8=8A=82=E7=82=B9=E5=86=85=E7=BD=AE=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E7=9A=84=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除目前系统中无用的 defaultTheme 和 defaultAnimation 文件 - 通过 points 置原点并基于设定的 width 和 height 缩放的方法,解决多边形形状与边框定位异常的问题 --- .../src/pages/graph/index.tsx | 18 +- packages/core/src/LogicFlow.tsx | 2 +- .../core/src/constant/defaultAnimation.ts | 28 -- packages/core/src/constant/defaultTheme.ts | 392 ------------------ packages/core/src/model/node/BaseNodeModel.ts | 4 - packages/core/src/model/node/HtmlNodeModel.ts | 29 ++ .../core/src/model/node/PolygonNodeModel.ts | 21 +- packages/core/src/model/node/RectNodeModel.ts | 27 ++ packages/core/src/util/geometry.ts | 43 ++ packages/core/src/util/theme.ts | 3 +- packages/core/src/view/Control.tsx | 1 - packages/core/src/view/Rotate.tsx | 8 +- .../core/src/view/overlay/CanvasOverlay.tsx | 3 + packages/core/src/view/shape/Polygon.tsx | 11 +- 14 files changed, 150 insertions(+), 440 deletions(-) delete mode 100644 packages/core/src/constant/defaultAnimation.ts delete mode 100644 packages/core/src/constant/defaultTheme.ts diff --git a/examples/feature-examples/src/pages/graph/index.tsx b/examples/feature-examples/src/pages/graph/index.tsx index 2e2144ad4..b98072ce0 100644 --- a/examples/feature-examples/src/pages/graph/index.tsx +++ b/examples/feature-examples/src/pages/graph/index.tsx @@ -85,14 +85,17 @@ const data = { type: 'rect', x: 600, y: 200, + properties: { + width: 80, + height: 120, + }, }, - { id: 'custom-node-2', text: 'node-2', type: 'polygon', - x: 0, - y: 0, + x: 90, + y: 94, }, ], } @@ -174,6 +177,9 @@ export default function BasicNode() { color: '#FFFFFF', }, grid: true, + // grid: { + // size: 1, + // }, edgeTextDraggable: true, edgeType: 'bezier', style: { @@ -481,6 +487,10 @@ export default function BasicNode() { handleDragItem({ type: 'ellipse', text: 'ellipse', + properties: { + rx: 40, + ry: 80, + }, }) }} > @@ -504,6 +514,8 @@ export default function BasicNode() { type: 'polygon', text: 'polygon', properties: { + width: 110, + height: 100, style: { fill: '#ffd591', stroke: '#ffa940', diff --git a/packages/core/src/LogicFlow.tsx b/packages/core/src/LogicFlow.tsx index 81fe17e37..0d98e635b 100644 --- a/packages/core/src/LogicFlow.tsx +++ b/packages/core/src/LogicFlow.tsx @@ -1530,7 +1530,6 @@ export namespace LogicFlow { */ export type DashArray = string export type CommonTheme = { - path?: string fill?: Color // 填充颜色 stroke?: Color // 边框颜色 strokeWidth?: number // 边框宽度 TODO: svg 实际可赋值类型:NumberOrPercent @@ -1544,6 +1543,7 @@ export namespace LogicFlow { radius?: number rx?: number ry?: number + path?: string [key: string]: unknown } export type CommonThemePropTypes = CommonTheme[keyof CommonTheme] diff --git a/packages/core/src/constant/defaultAnimation.ts b/packages/core/src/constant/defaultAnimation.ts deleted file mode 100644 index 3bee9933b..000000000 --- a/packages/core/src/constant/defaultAnimation.ts +++ /dev/null @@ -1,28 +0,0 @@ -export type AnimationConfig = { - edge: boolean - node: boolean -} - -export type Animation = { - stroke: string - strokeDasharray: string - className: string -} - -// 不填或者false返回的配置,表示不开启所有动画 -export const defaultAnimationCloseConfig: AnimationConfig = { - node: false, - edge: false, -} - -// 仅使用true的时候返回的配置,表示开启所有动画 -export const defaultAnimationOpenConfig: AnimationConfig = { - node: true, - edge: true, -} - -export const defaultAnimationData: Animation = { - stroke: 'red', - strokeDasharray: '10 200', - className: 'lf-edge-animation', -} diff --git a/packages/core/src/constant/defaultTheme.ts b/packages/core/src/constant/defaultTheme.ts deleted file mode 100644 index de59bed17..000000000 --- a/packages/core/src/constant/defaultTheme.ts +++ /dev/null @@ -1,392 +0,0 @@ -/** - * 颜色 - * CSS属性用颜色 - * 如#000000,reg(0,0,0,0) - * 如果是透明,可以传'none' - */ -export type Color = string - -/** - * svg虚线 - * 格式为逗号分割字符串,如 - * @see https://developer.mozilla.org/zh-CN/docs/Web/SVG/Attribute/stroke-dasharray - */ -export type DashArray = string - -export type CommonTheme = { - /** - * 填充颜色 - */ - fill?: Color - /** - * 边框颜色 - */ - stroke?: Color - /** - * 边框宽度 - */ - strokeWidth?: number - /** - * 其他属性 - * 我们会把你定义的所有属性最终传递到DOM上 - * 详情请参考svg属性规范 - * https://developer.mozilla.org/zh-CN/docs/Web/SVG/Attribute - * 注意: 请不要在主题中设置“形状属性”,例如:x、y、width、height、radius、r、rx、ry - * @see https://docs.logic-flow.cn/docs/#/zh/api/themeApi?id=%e5%bd%a2%e7%8a%b6%e5%b1%9e%e6%80%a7) - */ - [key: string]: any -} - -/** - * rect主题样式 - * svg基础图形-矩形 - * https://developer.mozilla.org/zh-CN/docs/Web/SVG/Element/rect - */ -export type RectTheme = CommonTheme - -/** - * circle主题样式 - * svg基础图形-圆形 - * https://developer.mozilla.org/zh-CN/docs/Web/SVG/Element/circle - */ -export type CircleTheme = CommonTheme -/** - * polygon主题样式 - * svg基础图形-多边形 - * https://developer.mozilla.org/zh-CN/docs/Web/SVG/Element/polygon - */ -export type PolygonTheme = CommonTheme - -/** - * ellipse主题样式 - * svg基础图形-椭圆 - * https://developer.mozilla.org/zh-CN/docs/Web/SVG/Element/ellipse - */ -export type EllipseTheme = CommonTheme - -/** - * 锚点样式 - * svg基础图形-圆 - */ -export type AnchorTheme = { - r?: number - hover?: { - r: number - } & CommonTheme -} & CommonTheme - -/** - * 文本样式 - * svg文本 - * https://developer.mozilla.org/zh-CN/docs/Web/SVG/Element/text - */ -export type TextTheme = { - /** - * 文本颜色 - */ - color?: Color - /** - * 文本大小 - */ - fontSize?: number -} & CommonTheme - -/** - * 文本节点样式 - */ -export type TextNodeTheme = { - background?: RectTheme -} & TextTheme - -/** - * 节点上文本样式 - */ -export type NodeTextTheme = { - /** - * 文本超出指定宽度处理方式 - * default: 不特殊处理,允许超出 - * autoWrap: 超出自动换行 - * ellipsis: 超出省略 - */ - overflowMode?: string - background?: RectTheme -} & TextTheme - -/** - * 边上文本样式 - */ -export type EdgeTextTheme = { - /** - * 文本超出指定宽度处理方式 - * default: 不特殊处理,允许超出 - * autoWrap: 超出自动换行 - * ellipsis: 超出省略 - */ - overflowMode?: string - /** - * 文本一行最大宽度 - */ - textWidth?: number - /** - * 文本背景样式 - */ - background?: { - /** - * 背景区域padding - * wrapPadding: '5px,10px' - */ - wrapPadding?: string - } & RectTheme - /** - * hover状态下文本样式 - */ - hover?: EdgeTextTheme -} & TextTheme - -export type EdgeTheme = CommonTheme - -export type EdgePolylineTheme = EdgeTheme - -export type EdgeBezierTheme = { - /** - * 贝塞尔调整线主题 - */ - adjustLine?: EdgeTheme - /** - * 贝塞尔调整锚点主题 - */ - adjustAnchor?: CircleTheme -} & EdgeTheme - -/** - * 箭头主题 - */ -export type ArrowTheme = { - /** - * 箭头长度. - * 以符号"->"为例, offset表示箭头大于号的宽度。 - */ - offset?: number - /** - * 箭头垂直于边的距离 - * 以符号"->"为例, verticalLength表示箭头大于号的高度 - */ - verticalLength?: number -} & CommonTheme - -export type OutlineTheme = { - /** - * hover状态下样式 - */ - hover?: CommonTheme -} & CommonTheme - -/** - * 边动画主题 - */ -export type EdgeAnimation = { - stroke?: string - strokeDasharray?: string - strokeDashoffset?: string - animationName?: string - animationDuration?: string - animationIterationCount?: string - animationTimingFunction?: string - animationDirection?: string -} - -export type Theme = { - /** - * 所有节点的通用主题设置 - */ - baseNode?: CommonTheme - /** - * 基础图形-矩形样式 - */ - rect?: RectTheme - /** - * 基础图形-圆形样式 - */ - circle?: CircleTheme - /** - * 基础图形-菱形样式 - */ - diamond?: PolygonTheme - /** - * 基础图形-椭圆样式 - */ - ellipse?: EllipseTheme - /** - * 基础图形-多边形样式 - */ - polygon?: PolygonTheme - /** - * 所有边的通用主题设置 - */ - baseEdge?: EdgeTheme - /** - * 基础图形-直线样式 - */ - line?: EdgeTheme - /** - * 基础图形-折现样式 - */ - polyline?: EdgePolylineTheme - /** - * 基础图形-贝塞尔曲线样式 - */ - bezier?: EdgeBezierTheme - /** - * 锚点样式 - */ - anchor?: AnchorTheme - /** - * 文本节点样式 - */ - text?: TextTheme - /** - * 节点文本样式 - */ - nodeText?: NodeTextTheme - /** - * 边文本样式 - */ - edgeText?: EdgeTextTheme - /** - * 边上箭头的样式 - */ - arrow?: ArrowTheme - /** - * 从锚点拉出的边的样式 - */ - anchorLine?: EdgeTheme - /** - * 对齐线样式 - */ - snapline?: EdgeTheme - /** - * 当开启了跳转边的起点和终点(adjustEdgeStartAndEnd:true)后 - * 边的两端会出现调整按钮 - * 边连段的调整点样式 - */ - edgeAdjust?: CircleTheme - /** - * 节点选择状态下外侧的选框样式 - */ - outline?: OutlineTheme - /** - * 边动画样式 - */ - edgeAnimation?: EdgeAnimation - /** - * 节点旋转控制点样式 - */ - rotateControl?: CommonTheme - inputText?: CommonTheme -} - -export const defaultTheme: Theme = { - baseNode: { - fill: '#FFFFFF', - stroke: '#000000', - strokeWidth: 2, - }, - baseEdge: { - stroke: '#000000', - strokeWidth: 2, - }, - rect: {}, - circle: {}, - diamond: {}, - ellipse: {}, - polygon: {}, - text: { - color: '#000000', - stroke: 'none', - fontSize: 12, - background: { - fill: 'transparent', - }, - }, - anchor: { - stroke: '#000000', - fill: '#FFFFFF', - r: 4, - hover: { - fill: '#949494', - fillOpacity: 0.5, - stroke: '#949494', - r: 10, - }, - }, - nodeText: { - color: '#000000', - overflowMode: 'default', - lineHeight: 1.2, - fontSize: 12, - }, - edgeText: { - textWidth: 100, - overflowMode: 'default', - fontSize: 12, - background: { - fill: '#FFFFFF', - }, - }, - line: {}, - polyline: {}, - bezier: { - fill: 'none', - adjustLine: { - stroke: '#949494', - }, - adjustAnchor: { - r: 4, - fill: '#949494', - stroke: '#949494', - fillOpacity: 1, - }, - }, - arrow: { - offset: 10, // 箭头长度 - verticalLength: 5, // 箭头垂直于边的距离 - }, - anchorLine: { - stroke: '#000000', - strokeWidth: 2, - strokeDasharray: '3,2', - }, - snapline: { - stroke: '#949494', - strokeWidth: 1, - }, - edgeAdjust: { - r: 4, - fill: '#FFFFFF', - stroke: '#949494', - strokeWidth: 2, - }, - outline: { - fill: 'transparent', - stroke: '#949494', - strokeDasharray: '3,3', - hover: { - stroke: '#949494', - }, - }, - edgeAnimation: { - stroke: 'red', - strokeDasharray: '10 10', - strokeDashoffset: '100%', - animationName: 'lf_animate_dash', - animationDuration: '20s', - animationIterationCount: 'infinite', - animationTimingFunction: 'linear', - animationDirection: 'normal', - }, - rotateControl: { - stroke: '#000', - fill: '#fff', - strokeWidth: 1.5, - }, -} diff --git a/packages/core/src/model/node/BaseNodeModel.ts b/packages/core/src/model/node/BaseNodeModel.ts index 05db4fa6f..0a6370b01 100644 --- a/packages/core/src/model/node/BaseNodeModel.ts +++ b/packages/core/src/model/node/BaseNodeModel.ts @@ -177,10 +177,6 @@ export class BaseNodeModel implements IBaseNodeModel { data.properties = {} } - const { width, height } = data.properties - if (width) this.width = width - if (height) this.height = height - if (!data.id) { // 自定义节点id > 全局定义id > 内置 const { idGenerator } = this.graphModel diff --git a/packages/core/src/model/node/HtmlNodeModel.ts b/packages/core/src/model/node/HtmlNodeModel.ts index 79d184bb7..41ae1c50f 100644 --- a/packages/core/src/model/node/HtmlNodeModel.ts +++ b/packages/core/src/model/node/HtmlNodeModel.ts @@ -3,9 +3,38 @@ import { Model } from '../BaseModel' import { ModelType } from '../../constant' import AnchorConfig = Model.AnchorConfig +import { observable } from 'mobx' +import LogicFlow from '../../LogicFlow' +import GraphModel from '../GraphModel' + +export type IHtmlNodeModel = { + width?: number + height?: number + style?: LogicFlow.CommonTheme + textStyle?: LogicFlow.CommonTheme + + [key: string]: any +} export class HtmlNodeModel extends BaseNodeModel { modelType = ModelType.HTML_NODE + @observable properties: IHtmlNodeModel = {} + + constructor(data: LogicFlow.NodeConfig, graphModel: GraphModel) { + super(data, graphModel) + this.properties = data.properties || {} + + this.setAttributes() + } + + setAttributes() { + super.setAttributes() + + const { width, height } = this.properties + if (width) this.width = width + if (height) this.height = height + } + getDefaultAnchor(): AnchorConfig[] { const { x, y, width, height } = this return [ diff --git a/packages/core/src/model/node/PolygonNodeModel.ts b/packages/core/src/model/node/PolygonNodeModel.ts index 75f97e43b..1a457c9e2 100644 --- a/packages/core/src/model/node/PolygonNodeModel.ts +++ b/packages/core/src/model/node/PolygonNodeModel.ts @@ -10,9 +10,12 @@ import PointTuple = LogicFlow.PointTuple import NodeConfig = LogicFlow.NodeConfig import ResizeInfo = ResizeControl.ResizeInfo import ResizeNodeData = ResizeControl.ResizeNodeData +import { normalizePolygon } from '../../util' export type IPolygonNodeProperties = { points?: PointTuple[] + width?: number + height?: number style?: LogicFlow.CommonTheme textStyle?: LogicFlow.CommonTheme @@ -29,6 +32,11 @@ export class PolygonNodeModel extends BaseNodeModel { [190, 78], [10, 78], [160, 198], // 五角星 + // [90, 0], + // [30, 188], + // [180, 68], + // [0, 68], + // [150, 188], // 五角星 ] @observable properties: IPolygonNodeProperties = {} @@ -42,10 +50,15 @@ export class PolygonNodeModel extends BaseNodeModel { setAttributes() { super.setAttributes() - const { points } = this.properties - if (points) { - this.points = points - } + const { points, width, height } = this.properties + // DONE: 如果设置了 points,又设置了节点的宽高,则需要做如下操作 + // 1. 将 points 的位置置零,即将图形向原点移动(找到 points 中 x,y 的最小值,所有坐标值减掉该值) + // 2. 按宽高的比例重新计算最新的 points + // if (points) { + // this.points = points + // } + const nextPoints = points || this.points + this.points = normalizePolygon(nextPoints, width, height) } getNodeStyle() { diff --git a/packages/core/src/model/node/RectNodeModel.ts b/packages/core/src/model/node/RectNodeModel.ts index faaf1beb2..7142eceae 100644 --- a/packages/core/src/model/node/RectNodeModel.ts +++ b/packages/core/src/model/node/RectNodeModel.ts @@ -1,11 +1,38 @@ import { cloneDeep } from 'lodash-es' import { observable } from 'mobx' import BaseNodeModel from './BaseNodeModel' +import GraphModel from '../GraphModel' +import LogicFlow from '../../LogicFlow' import { ModelType } from '../../constant' +export type IRectNodeModel = { + width?: number + height?: number + style?: LogicFlow.CommonTheme + textStyle?: LogicFlow.CommonTheme + + [key: string]: any +} + export class RectNodeModel extends BaseNodeModel { modelType = ModelType.RECT_NODE @observable radius = 0 + @observable properties: IRectNodeModel = {} + + constructor(data: LogicFlow.NodeConfig, graphModel: GraphModel) { + super(data, graphModel) + this.properties = data.properties || {} + + this.setAttributes() + } + + setAttributes() { + super.setAttributes() + + const { width, height } = this.properties + if (width) this.width = width + if (height) this.height = height + } getDefaultAnchor() { const { x, y, width, height } = this diff --git a/packages/core/src/util/geometry.ts b/packages/core/src/util/geometry.ts index e3e43f2a7..313fddb62 100644 --- a/packages/core/src/util/geometry.ts +++ b/packages/core/src/util/geometry.ts @@ -1,3 +1,6 @@ +import LogicFlow from '../LogicFlow' +import PointTuple = LogicFlow.PointTuple + export function snapToGrid(point: number, gridSize: number) { // 保证 x, y 的值为 gridSize 的整数倍 return gridSize * Math.round(point / gridSize) || point @@ -8,3 +11,43 @@ export function snapToGrid(point: number, gridSize: number) { export function getGridOffset(distance: number, gridSize: number) { return distance % gridSize } + +/** + * 多边形设置 points 后,坐标平移至原点 并 根据 width、height 缩放 + * @param points + * @param width + * @param height + */ +export function normalizePolygon( + points?: PointTuple[], + width?: number, + height?: number, +): PointTuple[] { + if (!points) return [] + + // 计算边界框 + const minX = Math.min(...points.map((p) => p[0])) + const maxX = Math.max(...points.map((p) => p[0])) + const minY = Math.min(...points.map((p) => p[1])) + const maxY = Math.max(...points.map((p) => p[1])) + + // 平移至原点 + const dx = -minX + const dy = -minY + const translatedPoints: PointTuple[] = points.map(([x, y]) => [ + x + dx, + y + dy, + ]) + + // 计算边界框的宽度和高度 + const bboxWidth = maxX - minX + const bboxHeight = maxY - minY + + // 计算缩放因子 + const scaleX = width ? width / bboxWidth : 1 + const scaleY = height ? height / bboxHeight : 1 + const scaleFactor = Math.min(scaleX, scaleY) + + // 缩放顶点 + return translatedPoints.map(([x, y]) => [x * scaleFactor, y * scaleFactor]) +} diff --git a/packages/core/src/util/theme.ts b/packages/core/src/util/theme.ts index 4e076fdbb..0813ab6e8 100644 --- a/packages/core/src/util/theme.ts +++ b/packages/core/src/util/theme.ts @@ -130,8 +130,7 @@ export const defaultTheme: LogicFlow.Theme = { resizeOutline: { fill: 'none', - // stroke: 'transparent', // 矩形默认不显示调整边框 - stroke: 'red', // 矩形默认不显示调整边框 + stroke: 'transparent', // 矩形默认不显示调整边框 strokeWidth: 1, strokeDasharray: '3,3', }, diff --git a/packages/core/src/view/Control.tsx b/packages/core/src/view/Control.tsx index c1f3d8b2f..550b53037 100644 --- a/packages/core/src/view/Control.tsx +++ b/packages/core/src/view/Control.tsx @@ -376,7 +376,6 @@ export class ResizeControlGroup extends Component { }, // 左下角 ] - console.log('controlList --->>>', controlList) return map(controlList, (control) => ( )) diff --git a/packages/core/src/view/Rotate.tsx b/packages/core/src/view/Rotate.tsx index 5cf5156ca..6831cb15c 100644 --- a/packages/core/src/view/Rotate.tsx +++ b/packages/core/src/view/Rotate.tsx @@ -1,17 +1,17 @@ import { Component } from 'preact/compat' import { map, reduce } from 'lodash-es' import Circle from './shape/Circle' +import LogicFlow from '../LogicFlow' +import { EventType } from '../constant' +import EventEmitter from '../event/eventEmitter' import { GraphModel, BaseNodeModel } from '../model' import { StepDrag, TranslateMatrix, Vector } from '../util' -import EventEmitter from '../event/eventEmitter' -import { CommonTheme } from '../constant/defaultTheme' -import { EventType } from '../constant' interface IRotateControlProps { graphModel: GraphModel nodeModel: BaseNodeModel eventCenter: EventEmitter - style: CommonTheme + style: LogicFlow.CommonTheme } class RotateControlPoint extends Component { diff --git a/packages/core/src/view/overlay/CanvasOverlay.tsx b/packages/core/src/view/overlay/CanvasOverlay.tsx index a18be2456..d0da7d7e0 100644 --- a/packages/core/src/view/overlay/CanvasOverlay.tsx +++ b/packages/core/src/view/overlay/CanvasOverlay.tsx @@ -18,6 +18,7 @@ export class CanvasOverlay extends Component { stepDrag: StepDrag stepScrollX = 0 stepScrollY = 0 + constructor(props: IProps) { super() const { @@ -37,6 +38,7 @@ export class CanvasOverlay extends Component { isDragging: false, } } + // get InjectedProps() { // return this.props as InjectedProps; // } @@ -144,6 +146,7 @@ export class CanvasOverlay extends Component { this.clickHandler(ev) } } + render() { const { graphModel: { transformModel }, diff --git a/packages/core/src/view/shape/Polygon.tsx b/packages/core/src/view/shape/Polygon.tsx index 63bb3f7ad..61a607297 100644 --- a/packages/core/src/view/shape/Polygon.tsx +++ b/packages/core/src/view/shape/Polygon.tsx @@ -1,4 +1,13 @@ -export function Polygon(props) { +import LogicFlow from '../../LogicFlow' + +// TODO: 定义基础图形的类型 +export type IPolygonProps = { + points: LogicFlow.PointTuple[] + className?: string + [key: string]: any +} + +export function Polygon(props: IPolygonProps) { const { points, className } = props const attrs: Record = { fill: 'transparent', From cc61791bd4b39d28d44fdb909fe34dcdce213232 Mon Sep 17 00:00:00 2001 From: R0ger1tlearn Date: Sun, 23 Jun 2024 10:50:35 +0800 Subject: [PATCH 08/10] =?UTF-8?q?feat(core):=20=E8=A7=A3=E5=86=B3resize=20?= =?UTF-8?q?=E7=BB=93=E6=9D=9F=E5=90=8E=E8=8A=82=E7=82=B9=E9=87=8D=E6=96=B0?= =?UTF-8?q?=E5=AE=9A=E4=BD=8D=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Control.tsx 中 new StepDrag 参数中 step 传入 graphModel.gridSize,保证调整时拖拽以 gridSize 为步长移动 --- packages/core/src/model/node/PolygonNodeModel.ts | 2 -- packages/core/src/util/drag.ts | 2 ++ packages/core/src/view/Control.tsx | 12 ++++++------ packages/core/src/view/node/PolygonNode.tsx | 1 - packages/core/src/view/overlay/OutlineOverlay.tsx | 7 ------- 5 files changed, 8 insertions(+), 16 deletions(-) diff --git a/packages/core/src/model/node/PolygonNodeModel.ts b/packages/core/src/model/node/PolygonNodeModel.ts index 1a457c9e2..29cc944cc 100644 --- a/packages/core/src/model/node/PolygonNodeModel.ts +++ b/packages/core/src/model/node/PolygonNodeModel.ts @@ -136,8 +136,6 @@ export class PolygonNodeModel extends BaseNodeModel { (y * height) / this.height, ]) this.points = nextPoints - console.log('nextPoints', nextPoints) - console.log('gogo this', this.x, this.y, this.width, this.height) this.properties.points = nextPoints // this.setProperties({ diff --git a/packages/core/src/util/drag.ts b/packages/core/src/util/drag.ts index f332d7b02..75985b592 100644 --- a/packages/core/src/util/drag.ts +++ b/packages/core/src/util/drag.ts @@ -102,6 +102,7 @@ export class StepDrag { }) this.startTime = new Date().getTime() } + handleMouseMove = (e: MouseEvent) => { if (this.isStopPropagation) e.stopPropagation() if (!this.isStartDragging) return @@ -150,6 +151,7 @@ export class StepDrag { }) } } + handleMouseUp = (e: MouseEvent) => { const DOC = window.document diff --git a/packages/core/src/view/Control.tsx b/packages/core/src/view/Control.tsx index 550b53037..4bb72a5b1 100644 --- a/packages/core/src/view/Control.tsx +++ b/packages/core/src/view/Control.tsx @@ -52,7 +52,7 @@ export class ResizeControl extends Component< this.dragHandler = new StepDrag({ onDragging: this.onDragging, onDragEnd: this.onDragEnd, - step: 1, + step: graphModel.gridSize, }) } @@ -304,9 +304,11 @@ export class ResizeControl extends Component< // 由于将拖拽放大缩小改成丝滑模式,这个时候需要再拖拽结束的时候,将节点的位置更新到 grid 上。 onDragEnd = () => { - const { gridSize = 1 } = this.graphModel - const x = gridSize * Math.round(this.nodeModel.x / gridSize) - const y = gridSize * Math.round(this.nodeModel.y / gridSize) + // const { gridSize = 1 } = this.graphModel + // const x = gridSize * Math.round(this.nodeModel.x / gridSize) + // const y = gridSize * Math.round(this.nodeModel.y / gridSize) + const x = this.nodeModel.x + const y = this.nodeModel.y this.nodeModel.moveTo(x, y) // 先触发 onDragging() -> 更新边 -> 再触发用户自定义的 getDefaultAnchor(),所以 onDragging() @@ -317,7 +319,6 @@ export class ResizeControl extends Component< render(): h.JSX.Element { const { x, y, direction, model } = this.props - console.log('polygon index', this.index, 'x', x, 'y', y) const style = model.getResizeControlStyle() return ( @@ -346,7 +347,6 @@ export class ResizeControlGroup extends Component { getResizeControl(): h.JSX.Element[] { const { model, graphModel } = this.props - console.log('polygon node', model.x, model.y, model.width, model.height) const { minX, minY, maxX, maxY } = getNodeBBox(model) const controlList: ControlItemProps[] = [ diff --git a/packages/core/src/view/node/PolygonNode.tsx b/packages/core/src/view/node/PolygonNode.tsx index 5b540b32a..0eaa24342 100644 --- a/packages/core/src/view/node/PolygonNode.tsx +++ b/packages/core/src/view/node/PolygonNode.tsx @@ -14,7 +14,6 @@ export class PolygonNode extends BaseNode { getShape() { const { model } = this.props - console.log('polygon model', model) const { x, y, width, height, points } = model as PolygonNodeModel const style = model.getNodeStyle() const attr = { diff --git a/packages/core/src/view/overlay/OutlineOverlay.tsx b/packages/core/src/view/overlay/OutlineOverlay.tsx index 1b45fb2f8..97eabb7f6 100644 --- a/packages/core/src/view/overlay/OutlineOverlay.tsx +++ b/packages/core/src/view/overlay/OutlineOverlay.tsx @@ -27,13 +27,6 @@ export class OutlineOverlay extends Component { nodes.forEach((element) => { if (element.isHovered || element.isSelected) { const { isHovered, isSelected, x, y, width, height } = element - console.log( - 'gogo element', - element.x, - element.y, - element.width, - element.height, - ) if ( (nodeSelectedOutline && isSelected) || (hoverOutline && isHovered) From 29a5d951ca9207b1870768f26d80323fb2f9bd97 Mon Sep 17 00:00:00 2001 From: boyongjiong Date: Mon, 24 Jun 2024 10:32:21 +0800 Subject: [PATCH 09/10] =?UTF-8?q?fix(core):=20=E7=A7=BB=E9=99=A4polygon=20?= =?UTF-8?q?node=20=E4=B8=AD=20getText=20=E8=BF=94=E5=9B=9E=E7=A9=BA?= =?UTF-8?q?=E7=9A=84=E6=B5=8B=E8=AF=95=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/src/view/node/PolygonNode.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/core/src/view/node/PolygonNode.tsx b/packages/core/src/view/node/PolygonNode.tsx index 0eaa24342..1c5799ae8 100644 --- a/packages/core/src/view/node/PolygonNode.tsx +++ b/packages/core/src/view/node/PolygonNode.tsx @@ -8,10 +8,6 @@ export type IPolygonNodeProps = { } export class PolygonNode extends BaseNode { - getText() { - return null - } - getShape() { const { model } = this.props const { x, y, width, height, points } = model as PolygonNodeModel From 644b351682ef221eb4ed961b6360b38ecb0cd4be Mon Sep 17 00:00:00 2001 From: boyongjiong Date: Mon, 24 Jun 2024 16:46:05 +0800 Subject: [PATCH 10/10] =?UTF-8?q?fix(docs):=20=E8=A7=A3=E5=86=B3=20docs=20?= =?UTF-8?q?=E5=90=AF=E5=8A=A8=E6=97=B6=E3=80=8CThe=20same=20observable=20o?= =?UTF-8?q?bject=20cannot=20appear=20twice=20in=20the=20same=20tree?= =?UTF-8?q?=E3=80=8D=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 节点定义写法有问题,observer 属性赋值给另一个 observer 属性,导致触发上面错误 - 更新包版本 - DEFAULT_GRID_SIZE 将默认的 gridSize 值提成常量值,放在 constant 中,方便修改 - history 功能需要测试一下 cloneDeep(model) 的部分 --- examples/feature-examples/package.json | 2 +- examples/material-ui-demo/package.json | 1 - examples/next-app/package.json | 2 +- examples/vue3-app/package.json | 2 +- examples/vue3-memory-leak/package.json | 2 +- packages/core/package.json | 2 +- packages/core/src/constant/index.ts | 2 ++ packages/core/src/history/index.ts | 21 ++++++++++++--- packages/core/src/model/node/BaseNodeModel.ts | 2 +- packages/core/src/options.ts | 4 +-- packages/core/src/view/overlay/Grid.tsx | 7 ++--- packages/extension/package.json | 2 +- packages/react-node-registry/package.json | 2 +- packages/vue-node-registry/package.json | 2 +- .../index/components/demo/node/circleNode.ts | 7 ++--- .../index/components/demo/node/stepNode.ts | 27 ++++++++++++------- 16 files changed, 56 insertions(+), 31 deletions(-) diff --git a/examples/feature-examples/package.json b/examples/feature-examples/package.json index 44f5b6ecc..27eea2048 100644 --- a/examples/feature-examples/package.json +++ b/examples/feature-examples/package.json @@ -1,6 +1,6 @@ { - "private": true, "name": "@logicflow/feature-examples", + "private": true, "author": "boyongjiong ", "scripts": { "dev": "umi dev", diff --git a/examples/material-ui-demo/package.json b/examples/material-ui-demo/package.json index 54dd894b4..1e1376ab6 100644 --- a/examples/material-ui-demo/package.json +++ b/examples/material-ui-demo/package.json @@ -1,6 +1,5 @@ { "name": "material-ui-app", - "version": "1.3.0", "private": true, "dependencies": { "@emotion/cache": "^11.9.3", diff --git a/examples/next-app/package.json b/examples/next-app/package.json index 96121a090..13ad88c6b 100644 --- a/examples/next-app/package.json +++ b/examples/next-app/package.json @@ -1,6 +1,6 @@ { "name": "next-app", - "version": "0.1.0", + "version": "1.0.0", "private": true, "scripts": { "dev": "next dev", diff --git a/examples/vue3-app/package.json b/examples/vue3-app/package.json index 0c48b79f2..eb9d2c665 100644 --- a/examples/vue3-app/package.json +++ b/examples/vue3-app/package.json @@ -1,6 +1,6 @@ { "name": "vue3-app", - "version": "0.0.0", + "version": "1.0.0", "private": true, "type": "module", "scripts": { diff --git a/examples/vue3-memory-leak/package.json b/examples/vue3-memory-leak/package.json index c5972fa7e..87857df57 100644 --- a/examples/vue3-memory-leak/package.json +++ b/examples/vue3-memory-leak/package.json @@ -1,7 +1,7 @@ { "name": "vue3-memory-leak", "private": true, - "version": "0.0.0", + "version": "1.0.0", "scripts": { "dev": "vite", "build": "vite build", diff --git a/packages/core/package.json b/packages/core/package.json index 6242cf330..a68ea557d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@logicflow/core", - "version": "1.3.0", + "version": "2.0.0-beta.1", "description": "LogicFlow, help you quickly create flowcharts", "main": "dist/index.js", "module": "es/index.js", diff --git a/packages/core/src/constant/index.ts b/packages/core/src/constant/index.ts index bbd8de673..a4b349162 100644 --- a/packages/core/src/constant/index.ts +++ b/packages/core/src/constant/index.ts @@ -1,6 +1,8 @@ export const DEFAULT_VISIBLE_SPACE = 200 export const ELEMENT_MAX_Z_INDEX = 9999 +export const DEFAULT_GRID_SIZE = 10 + export enum ElementState { DEFAULT = 1, // 默认显示 TEXT_EDIT, // 此元素正在进行文本编辑 diff --git a/packages/core/src/history/index.ts b/packages/core/src/history/index.ts index 976459d8c..b15396d87 100644 --- a/packages/core/src/history/index.ts +++ b/packages/core/src/history/index.ts @@ -81,13 +81,28 @@ export class History { this.undos.push(model.modelToGraphData()) this.stopWatch = deepObserve( + // TODO:避免用户触发「The same observable object cannot appear twice in the same tree」 错误 + // 例如:在自定义节点的 setAttributes 方法中,将 nodeModel 属性赋值给另一个 observable 属性 + // eg: + // setAttributes() { + // this.width = 120 + // this.height = 50 + // + // if (this.text) { + // this.properties.text = this.text; + // this.text.value = ''; + // } + // } + // 解决方案:使用 cloneDeep 方法,将 observable 对象克隆一份。需要测试下面操作是否会造成其它问题 + // https://stackoverflow.com/questions/55328504/a-node-cannot-exists-twice-in-the-state-tree-mobx-state-tree + // cloneDeep(model), model, debounce(() => { - // 数据变更后,把最新的当前model数据存起来,并清空redos。 - // 因为这个回调函数的触发,一般是用户交互而引起的,所以按正常逻辑需要清空redos。 + // 数据变更后,把最新的当前model数据存起来,并清空 redos。 + // 因为这个回调函数的触发,一般是用户交互而引起的,所以按正常逻辑需要清空 redos。 const data = model.modelToHistoryData() if (data) { - this.add(data) + this.add({ ...data }) } }, this.waitTime), ) diff --git a/packages/core/src/model/node/BaseNodeModel.ts b/packages/core/src/model/node/BaseNodeModel.ts index 0a6370b01..79b9e610d 100644 --- a/packages/core/src/model/node/BaseNodeModel.ts +++ b/packages/core/src/model/node/BaseNodeModel.ts @@ -186,7 +186,7 @@ export class BaseNodeModel implements IBaseNodeModel { } this.formatText(data) - assign(this, pickNodeConfig(data)) + assign(this, pickNodeConfig(data)) // TODO: 确认 constructor 中赋值 properties 是否必要 const { overlapMode } = this.graphModel if (overlapMode === OverlapMode.INCREASE) { this.zIndex = data.zIndex || getZIndex() diff --git a/packages/core/src/options.ts b/packages/core/src/options.ts index 0a6cc4f8e..a1bec1e30 100644 --- a/packages/core/src/options.ts +++ b/packages/core/src/options.ts @@ -3,7 +3,7 @@ import { createElement as h } from 'preact/compat' import LogicFlow from './LogicFlow' import { GraphModel } from './model' import { KeyboardDef } from './keyboard' -import { OverlapMode } from './constant' +import { DEFAULT_GRID_SIZE, OverlapMode } from './constant' export namespace Options { import NodeData = LogicFlow.NodeData @@ -135,7 +135,7 @@ export namespace Options { const result = assign({}, defaults, others) as Options.Definition const defaultGrid: GridOptions = { - size: 20, + size: DEFAULT_GRID_SIZE, type: 'dot', visible: true, config: { diff --git a/packages/core/src/view/overlay/Grid.tsx b/packages/core/src/view/overlay/Grid.tsx index d40002598..7ee6915f0 100644 --- a/packages/core/src/view/overlay/Grid.tsx +++ b/packages/core/src/view/overlay/Grid.tsx @@ -1,7 +1,8 @@ import { Component } from 'preact/compat' import { observer } from '../..' -import { GraphModel } from '../../model' import { createUuid } from '../../util' +import { GraphModel } from '../../model' +import { DEFAULT_GRID_SIZE } from '../../constant' export type GridOptions = { /** @@ -37,7 +38,7 @@ export class Grid extends Component { const { color, thickness = 2 } = config ?? {} - const length = Math.min(Math.max(2, thickness), size / 2) // 2 < length < size /2 + const length = Math.min(Math.max(1.5, thickness), size / 2) // 2 < length < size /2 let opacity = 1 if (!visible) { opacity = 0 @@ -122,7 +123,7 @@ export class Grid extends Component { } Grid.defaultProps = { - size: 20, + size: DEFAULT_GRID_SIZE, visible: true, type: 'dot', config: { diff --git a/packages/extension/package.json b/packages/extension/package.json index bc60ffd67..5a65e04b1 100644 --- a/packages/extension/package.json +++ b/packages/extension/package.json @@ -1,6 +1,6 @@ { "name": "@logicflow/extension", - "version": "1.3.0", + "version": "2.0.0-beta.1", "description": "LogicFlow Extensions", "main": "dist/index.js", "module": "es/index.js", diff --git a/packages/react-node-registry/package.json b/packages/react-node-registry/package.json index a0a44bbf4..560e0cf71 100644 --- a/packages/react-node-registry/package.json +++ b/packages/react-node-registry/package.json @@ -1,6 +1,6 @@ { "name": "@logicflow/react-node-registry", - "version": "1.3.0", + "version": "1.0.0-beta.1", "description": "LogicFlow React Shape", "main": "lib/index.js", "module": "es/index.js", diff --git a/packages/vue-node-registry/package.json b/packages/vue-node-registry/package.json index 739c01165..65d9ae62e 100644 --- a/packages/vue-node-registry/package.json +++ b/packages/vue-node-registry/package.json @@ -1,6 +1,6 @@ { "name": "@logicflow/vue-node-registry", - "version": "1.3.0", + "version": "1.0.0-beta.1", "description": "LogicFlow Vue Component Node Registry", "main": "lib/index.js", "module": "es/index.js", diff --git a/sites/docs/.dumi/pages/index/components/demo/node/circleNode.ts b/sites/docs/.dumi/pages/index/components/demo/node/circleNode.ts index 42669c90a..e460fc2a2 100644 --- a/sites/docs/.dumi/pages/index/components/demo/node/circleNode.ts +++ b/sites/docs/.dumi/pages/index/components/demo/node/circleNode.ts @@ -1,7 +1,8 @@ import { HtmlNode, HtmlNodeModel, h } from '@logicflow/core'; class CircleNodeView extends HtmlNode { - setHtml(rootEl: HTMLElement) { + setHtml(rootEl: SVGForeignObjectElement) { + const nodeData = this.props.model.getData(); const { properties } = this.props.model; const text: any = properties.text; const innerText = text.value; @@ -11,7 +12,7 @@ class CircleNodeView extends HtmlNode { el.className = `step-wrapper circle-wrapper spin ${ isAnimation ? 'is-animate' : '' }`; - const html = `

${innerText}
`; + const html = `
${nodeData.text?.value}
`; const animationDom = `
`; // 需要先把之前渲染的子节点清除掉。 el.innerHTML = isAnimation ? html + animationDom : html; @@ -25,7 +26,7 @@ class CircleNodeModel extends HtmlNodeModel { this.width = 80; this.height = 80; if (this.text) { - this.properties.text = this.text; + // this.properties.text = this.text; this.text.value = ''; } } diff --git a/sites/docs/.dumi/pages/index/components/demo/node/stepNode.ts b/sites/docs/.dumi/pages/index/components/demo/node/stepNode.ts index 9286fd4a3..2cbd27ac5 100644 --- a/sites/docs/.dumi/pages/index/components/demo/node/stepNode.ts +++ b/sites/docs/.dumi/pages/index/components/demo/node/stepNode.ts @@ -1,15 +1,20 @@ import { HtmlNode, HtmlNodeModel, h } from '@logicflow/core'; class StepNodeView extends HtmlNode { - setHtml(rootEl: HTMLElement) { - const { properties } = this.props.model; - const text: any = properties.text; - const innerText = text.value; - const isAnimation = properties.isAnimation; + getText() { + return null; + } + + setHtml(rootEl: SVGForeignObjectElement) { + const { model } = this.props; + const { + properties: { isAnimation }, + } = model; + const nodeData = model.getData(); const el = document.createElement('div'); el.className = `step-wrapper spin ${isAnimation ? 'is-animate' : ''}`; - const html = `
${innerText}
`; + const html = `
${nodeData.text?.value}
`; const animationDom = `
`; // 需要先把之前渲染的子节点清除掉。 el.innerHTML = isAnimation ? html + animationDom : html; @@ -22,10 +27,12 @@ class StepNodeModel extends HtmlNodeModel { setAttributes() { this.width = 120; this.height = 50; - if (this.text) { - this.properties.text = this.text; - this.text.value = ''; - } + + // 错误之源 -> 这种错误写法,应该写入教科书 + // if (this.text) { + // this.properties.text = this.text; + // this.text.value = ''; + // } } }