diff --git a/apps/web/src/shared/gov/flow/flow-editor-core.ts b/apps/web/src/shared/gov/flow/flow-editor-core.ts index 7f75b7b..45b0493 100644 --- a/apps/web/src/shared/gov/flow/flow-editor-core.ts +++ b/apps/web/src/shared/gov/flow/flow-editor-core.ts @@ -441,36 +441,92 @@ export const PALETTE_SPECS: Array<{ key: string; label: string; className: strin ]; class FlowPalette { - static $inject = ["palette", "create", "elementFactory", "elementRegistry", "flowEditorConfig"]; + static $inject = ["palette", "create", "elementFactory", "elementRegistry", "flowEditorConfig", "modeling", "canvas"]; private _create: Any; private _elementFactory: Any; private _elementRegistry: Any; private _config: Any; + private _modeling: Any; + private _canvas: Any; - constructor(palette: Any, create: Any, elementFactory: Any, elementRegistry: Any, flowEditorConfig: Any) { + constructor( + palette: Any, + create: Any, + elementFactory: Any, + elementRegistry: Any, + flowEditorConfig: Any, + modeling: Any, + canvas: Any, + ) { this._create = create; this._elementFactory = elementFactory; this._elementRegistry = elementRegistry; this._config = flowEditorConfig; + this._modeling = modeling; + this._canvas = canvas; palette.registerProvider(this); } + /** 造一枚待落的新节点(id 现取,避开已用与保留号) */ + private _mint(spec: NewNodeSpec): Any { + const bo = specNode(spec); + bo.id = mintNodeId(spec.kind, usedIds(this._elementRegistry), this._config.reserved); + const size = MANUAL_SIZES[spec.kind]; + return this._elementFactory.createShape({ width: size.w, height: size.h, businessObject: bo }); + } + + /** + * 点击落子的落点:视口中心起,撞车就往下让一格,直到空位。 + * + * 为什么不复用拖拽那条路:`create.start` 靠鼠标**移动**取 hover 目标才算 `canExecute`, + * 而 click 事件到达时鼠标早已抬起 —— 拖拽在同一个调用栈里就以 `create.rejected` 收场, + * 一颗子也落不下(实测)。所以点击这条路直接建,不走拖拽。 + */ + private _freeSpot(shape: Any): { x: number; y: number } { + const viewbox = this._canvas.viewbox(); + let x = snap(viewbox.x + viewbox.width / 2); + let y = snap(viewbox.y + Math.min(viewbox.height / 2, 200)); + const hits = (px: number, py: number) => + this._elementRegistry + .getAll() + .some( + (el: Any) => + !el.waypoints && + el.parent && + !el.labelTarget && + Math.abs(el.x + el.width / 2 - px) < (el.width + shape.width) / 2 + 20 && + Math.abs(el.y + el.height / 2 - py) < (el.height + shape.height) / 2 + 20, + ); + let guard = 0; + while (hits(x, y) && guard < 40) { + y = snap(y + 70); + guard += 1; + // 一列排满就换到右边一列重来,别无限往下掉出视野 + if (guard % 8 === 0) { + x = snap(x + 160); + y = snap(viewbox.y + Math.min(viewbox.height / 2, 200)); + } + } + return { x, y }; + } + getPaletteEntries(): Record { const entries: Record = {}; for (const item of PALETTE_SPECS) { - const action = (event: Any) => { - const bo = specNode(item.spec); - bo.id = mintNodeId(item.spec.kind, usedIds(this._elementRegistry), this._config.reserved); - const size = MANUAL_SIZES[item.spec.kind]; - const shape = this._elementFactory.createShape({ width: size.w, height: size.h, businessObject: bo }); - this._create.start(event, shape); - }; entries[item.key] = { group: "flow", className: item.className, - title: `${item.label} — 拖到画布或点击后点画布落子`, - action: { dragstart: action, click: action }, + title: `${item.label} — 拖到画布,或直接点击(落在空位上,再拖到想要的地方)`, + action: { + // 拖:鼠标一路带着走,落点由手定 + dragstart: (event: Any) => this._create.start(event, this._mint(item.spec)), + // 点:当场落在空位上 —— 见 `_freeSpot` 注释,点击走不通拖拽那条路 + click: () => { + const shape = this._mint(item.spec); + this._modeling.createShape(shape, this._freeSpot(shape), this._canvas.getRootElement()); + }, + }, }; } return entries;