@flowgram.ai/free-container-plugin 0.1.25 → 0.1.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/esm/index.js CHANGED
@@ -32,7 +32,7 @@ import {
32
32
  WorkflowSelectService
33
33
  } from "@flowgram.ai/free-layout-core";
34
34
  import { HistoryService } from "@flowgram.ai/free-history-plugin";
35
- import { FlowNodeTransformData, FlowNodeRenderData } from "@flowgram.ai/document";
35
+ import { FlowNodeRenderData, FlowNodeBaseType } from "@flowgram.ai/document";
36
36
  import { PlaygroundConfigEntity, TransformData } from "@flowgram.ai/core";
37
37
  var NodeIntoContainerService = class {
38
38
  constructor() {
@@ -51,23 +51,12 @@ var NodeIntoContainerService = class {
51
51
  this.initState();
52
52
  this.toDispose.dispose();
53
53
  }
54
- /** 移除节点连线 */
55
- async removeNodeLines(node) {
56
- const lines = this.linesManager.getAllLines();
57
- lines.forEach((line) => {
58
- if (line.from.id !== node.id && line.to?.id !== node.id) {
59
- return;
60
- }
61
- line.dispose();
62
- });
63
- await this.nextFrame();
64
- }
65
54
  /** 将节点移出容器 */
66
55
  async moveOutContainer(params) {
67
56
  const { node } = params;
68
57
  const parentNode = node.parent;
69
58
  const containerNode = parentNode?.parent;
70
- const nodeJSON = await this.document.toNodeJSON(node);
59
+ const nodeJSON = this.document.toNodeJSON(node);
71
60
  if (!parentNode || !containerNode || !this.isContainer(parentNode) || !nodeJSON.meta?.position) {
72
61
  return;
73
62
  }
@@ -105,6 +94,20 @@ var NodeIntoContainerService = class {
105
94
  }
106
95
  return true;
107
96
  }
97
+ /** 移除节点所有非法连线 */
98
+ async clearInvalidLines(params) {
99
+ const { dragNode, sourceParent } = params;
100
+ if (!dragNode) {
101
+ return;
102
+ }
103
+ if (dragNode.parent === sourceParent) {
104
+ return;
105
+ }
106
+ if (dragNode.parent?.flowNodeType === FlowNodeBaseType.GROUP || sourceParent?.flowNodeType === FlowNodeBaseType.GROUP) {
107
+ return;
108
+ }
109
+ await this.removeNodeLines(dragNode);
110
+ }
108
111
  /** 初始化状态 */
109
112
  initState() {
110
113
  this.state = {
@@ -147,7 +150,10 @@ var NodeIntoContainerService = class {
147
150
  throttledDraggingNode.cancel();
148
151
  draggingNode(event);
149
152
  await this.dropNodeToContainer();
150
- await this.clearInvalidLines();
153
+ await this.clearInvalidLines({
154
+ dragNode: this.state.dragNode,
155
+ sourceParent: this.state.sourceParent
156
+ });
151
157
  this.setDropNode(void 0);
152
158
  this.initState();
153
159
  this.historyService.endTransaction();
@@ -169,18 +175,11 @@ var NodeIntoContainerService = class {
169
175
  await this.nextFrame();
170
176
  this.dragService.startDragSelectedNodes(event.triggerEvent);
171
177
  }
172
- /** 移除节点所有非法连线 */
173
- async clearInvalidLines() {
174
- const { dragNode, sourceParent } = this.state;
175
- if (!dragNode) {
176
- return;
177
- }
178
- if (dragNode.parent === sourceParent) {
179
- return;
180
- }
178
+ /** 移除节点连线 */
179
+ async removeNodeLines(node) {
181
180
  const lines = this.linesManager.getAllLines();
182
181
  lines.forEach((line) => {
183
- if (line.from.id !== dragNode.id && line.to?.id !== dragNode.id) {
182
+ if (line.from.id !== node.id && line.to?.id !== node.id) {
184
183
  return;
185
184
  }
186
185
  line.dispose();
@@ -233,13 +232,16 @@ var NodeIntoContainerService = class {
233
232
  }
234
233
  /** 获取容器节点transforms */
235
234
  getContainerTransforms() {
236
- return this.document.getRenderDatas(FlowNodeTransformData, false).filter((transform) => {
237
- const { entity } = transform;
238
- if (entity.originParent) {
239
- return entity.getNodeMeta().selectable && entity.originParent.getNodeMeta().selectable;
235
+ return this.document.getAllNodes().filter((node) => {
236
+ if (node.originParent) {
237
+ return node.getNodeMeta().selectable && node.originParent.getNodeMeta().selectable;
240
238
  }
241
- return entity.getNodeMeta().selectable;
242
- }).filter((transform) => this.isContainer(transform.entity));
239
+ return node.getNodeMeta().selectable;
240
+ }).filter((node) => this.isContainer(node)).sort((a, b) => {
241
+ const aIndex = a.renderData.stackIndex;
242
+ const bIndex = b.renderData.stackIndex;
243
+ return bIndex - aIndex;
244
+ }).map((node) => node.transform);
243
245
  }
244
246
  /** 放置节点到容器 */
245
247
  async dropNodeToContainer() {
@@ -254,44 +256,66 @@ var NodeIntoContainerService = class {
254
256
  }
255
257
  /** 拖拽节点 */
256
258
  draggingNode(nodeDragEvent) {
257
- const { dragNode, isDraggingNode, transforms } = this.state;
258
- if (!isDraggingNode || !dragNode || this.isContainer(dragNode) || !transforms?.length) {
259
+ const { dragNode, isDraggingNode, transforms = [] } = this.state;
260
+ if (!isDraggingNode || !dragNode || !transforms?.length) {
259
261
  return this.setDropNode(void 0);
260
262
  }
261
263
  const mousePos = this.playgroundConfig.getPosFromMouseEvent(nodeDragEvent.dragEvent);
264
+ const availableTransforms = transforms.filter(
265
+ (transform) => transform.entity.id !== dragNode.id
266
+ );
262
267
  const collisionTransform = this.getCollisionTransform({
263
268
  targetPoint: mousePos,
264
- transforms: this.state.transforms ?? []
269
+ transforms: availableTransforms
265
270
  });
266
271
  const dropNode = collisionTransform?.entity;
267
- if (!dropNode || dragNode.parent?.id === dropNode.id) {
272
+ const canDrop = this.canDropToContainer({
273
+ dragNode,
274
+ dropNode
275
+ });
276
+ if (!canDrop) {
268
277
  return this.setDropNode(void 0);
269
278
  }
279
+ return this.setDropNode(dropNode);
280
+ }
281
+ /** 判断能否将节点拖入容器 */
282
+ canDropToContainer(params) {
283
+ const { dragNode, dropNode } = params;
284
+ const isDropContainer = dropNode?.getNodeMeta().isContainer;
285
+ if (!dropNode || !isDropContainer || this.isParent(dragNode, dropNode)) {
286
+ return false;
287
+ }
288
+ if (dragNode.flowNodeType === FlowNodeBaseType.GROUP && dropNode.flowNodeType !== FlowNodeBaseType.GROUP) {
289
+ return false;
290
+ }
270
291
  const canDrop = this.dragService.canDropToNode({
271
292
  dragNodeType: dragNode.flowNodeType,
272
293
  dropNode
273
294
  });
274
295
  if (!canDrop.allowDrop) {
275
- return this.setDropNode(void 0);
296
+ return false;
276
297
  }
277
- return this.setDropNode(canDrop.dropNode);
298
+ return true;
299
+ }
300
+ /** 判断一个节点是否为另一个节点的父节点(向上查找直到根节点) */
301
+ isParent(node, parent) {
302
+ let current = node.parent;
303
+ while (current) {
304
+ if (current.id === parent.id) {
305
+ return true;
306
+ }
307
+ current = current.parent;
308
+ }
309
+ return false;
278
310
  }
279
311
  /** 将节点移入容器 */
280
312
  async moveIntoContainer(params) {
281
313
  const { node, containerNode } = params;
282
314
  const parentNode = node.parent;
283
- const nodeJSON = await this.document.toNodeJSON(node);
284
315
  this.operationService.moveNode(node, {
285
316
  parent: containerNode
286
317
  });
287
- this.operationService.updateNodePosition(
288
- node,
289
- this.dragService.adjustSubNodePosition(
290
- nodeJSON.type,
291
- containerNode,
292
- nodeJSON.meta.position
293
- )
294
- );
318
+ this.operationService.updateNodePosition(node, this.adjustSubNodePosition(node, containerNode));
295
319
  await this.nextFrame();
296
320
  this.emitter.fire({
297
321
  type: "in" /* In */,
@@ -300,6 +324,33 @@ var NodeIntoContainerService = class {
300
324
  targetContainer: containerNode
301
325
  });
302
326
  }
327
+ /**
328
+ * 如果存在容器节点,且传入鼠标坐标,需要用容器的坐标减去传入的鼠标坐标
329
+ */
330
+ adjustSubNodePosition(targetNode, containerNode) {
331
+ if (containerNode.flowNodeType === FlowNodeBaseType.ROOT) {
332
+ return targetNode.transform.position;
333
+ }
334
+ const nodeWorldTransform = targetNode.transform.transform.worldTransform;
335
+ const containerWorldTransform = containerNode.transform.transform.worldTransform;
336
+ const nodePosition = {
337
+ x: nodeWorldTransform.tx,
338
+ y: nodeWorldTransform.ty
339
+ };
340
+ const isParentEmpty = !containerNode.children || containerNode.children.length === 0;
341
+ const containerPadding = this.document.layout.getPadding(containerNode);
342
+ if (isParentEmpty) {
343
+ return {
344
+ x: 0,
345
+ y: containerPadding.top
346
+ };
347
+ } else {
348
+ return {
349
+ x: nodePosition.x - containerWorldTransform.tx,
350
+ y: nodePosition.y - containerWorldTransform.ty
351
+ };
352
+ }
353
+ }
303
354
  isContainer(node) {
304
355
  return node?.getNodeMeta().isContainer ?? false;
305
356
  }
@@ -342,6 +393,25 @@ NodeIntoContainerService = __decorateClass([
342
393
  injectable()
343
394
  ], NodeIntoContainerService);
344
395
 
396
+ // src/node-into-container/plugin.tsx
397
+ import { definePluginCreator } from "@flowgram.ai/core";
398
+ var createContainerNodePlugin = definePluginCreator({
399
+ onBind: ({ bind }) => {
400
+ bind(NodeIntoContainerService).toSelf().inSingletonScope();
401
+ },
402
+ onInit(ctx, options) {
403
+ ctx.get(NodeIntoContainerService).init();
404
+ },
405
+ onReady(ctx, options) {
406
+ if (options.disableNodeIntoContainer !== true) {
407
+ ctx.get(NodeIntoContainerService).ready();
408
+ }
409
+ },
410
+ onDispose(ctx) {
411
+ ctx.get(NodeIntoContainerService).dispose();
412
+ }
413
+ });
414
+
345
415
  // src/sub-canvas/hooks/use-node-size.ts
346
416
  import { useState, useEffect } from "react";
347
417
  import {
@@ -388,9 +458,23 @@ var useNodeSize = () => {
388
458
  };
389
459
  };
390
460
 
461
+ // src/sub-canvas/hooks/use-sync-node-render-size.ts
462
+ import { useLayoutEffect } from "react";
463
+ import { useCurrentEntity as useCurrentEntity2 } from "@flowgram.ai/free-layout-core";
464
+ var useSyncNodeRenderSize = (nodeSize) => {
465
+ const node = useCurrentEntity2();
466
+ useLayoutEffect(() => {
467
+ if (!nodeSize) {
468
+ return;
469
+ }
470
+ node.renderData.node.style.width = nodeSize.width + "px";
471
+ node.renderData.node.style.height = nodeSize.height + "px";
472
+ }, [nodeSize?.width, nodeSize?.height]);
473
+ };
474
+
391
475
  // src/sub-canvas/components/background/index.tsx
392
476
  import React from "react";
393
- import { useCurrentEntity as useCurrentEntity2 } from "@flowgram.ai/free-layout-core";
477
+ import { useCurrentEntity as useCurrentEntity3 } from "@flowgram.ai/free-layout-core";
394
478
 
395
479
  // src/sub-canvas/components/background/style.ts
396
480
  import styled from "styled-components";
@@ -403,7 +487,7 @@ var SubCanvasBackgroundStyle = styled.div`
403
487
 
404
488
  // src/sub-canvas/components/background/index.tsx
405
489
  var SubCanvasBackground = () => {
406
- const node = useCurrentEntity2();
490
+ const node = useCurrentEntity3();
407
491
  return /* @__PURE__ */ React.createElement(SubCanvasBackgroundStyle, { className: "sub-canvas-background", "data-flow-editor-selectable": "true" }, /* @__PURE__ */ React.createElement("svg", { width: "100%", height: "100%" }, /* @__PURE__ */ React.createElement("pattern", { id: "sub-canvas-dot-pattern", width: "20", height: "20", patternUnits: "userSpaceOnUse" }, /* @__PURE__ */ React.createElement("circle", { cx: "1", cy: "1", r: "1", stroke: "#eceeef", fillOpacity: "0.5" })), /* @__PURE__ */ React.createElement(
408
492
  "rect",
409
493
  {
@@ -466,8 +550,8 @@ var SubCanvasBorder = ({ style, children }) => /* @__PURE__ */ React2.createElem
466
550
  );
467
551
 
468
552
  // src/sub-canvas/components/render/index.tsx
469
- import React3, { useLayoutEffect } from "react";
470
- import { useCurrentEntity as useCurrentEntity3 } from "@flowgram.ai/free-layout-core";
553
+ import React5 from "react";
554
+ import { useCurrentEntity as useCurrentEntity5 } from "@flowgram.ai/free-layout-core";
471
555
 
472
556
  // src/sub-canvas/components/render/style.ts
473
557
  import styled3 from "styled-components";
@@ -476,18 +560,200 @@ var SubCanvasRenderStyle = styled3.div`
476
560
  height: 100%;
477
561
  `;
478
562
 
563
+ // src/sub-canvas/components/tips/index.tsx
564
+ import React4 from "react";
565
+
566
+ // src/sub-canvas/components/tips/use-control.ts
567
+ import { useCallback, useEffect as useEffect2, useState as useState2 } from "react";
568
+ import { useCurrentEntity as useCurrentEntity4 } from "@flowgram.ai/free-layout-core";
569
+ import { useService } from "@flowgram.ai/core";
570
+
571
+ // src/sub-canvas/components/tips/global-store.ts
572
+ var STORAGE_KEY = "workflow-move-into-sub-canvas-tip-visible";
573
+ var STORAGE_VALUE = "false";
574
+ var TipsGlobalStore = class _TipsGlobalStore {
575
+ constructor() {
576
+ this.closed = false;
577
+ }
578
+ static get instance() {
579
+ if (!this._instance) {
580
+ this._instance = new _TipsGlobalStore();
581
+ }
582
+ return this._instance;
583
+ }
584
+ isClosed() {
585
+ return this.isCloseForever() || this.closed;
586
+ }
587
+ close() {
588
+ this.closed = true;
589
+ }
590
+ isCloseForever() {
591
+ return localStorage.getItem(STORAGE_KEY) === STORAGE_VALUE;
592
+ }
593
+ closeForever() {
594
+ localStorage.setItem(STORAGE_KEY, STORAGE_VALUE);
595
+ }
596
+ };
597
+
598
+ // src/sub-canvas/components/tips/use-control.ts
599
+ var useControlTips = () => {
600
+ const node = useCurrentEntity4();
601
+ const [visible, setVisible] = useState2(false);
602
+ const globalStore = TipsGlobalStore.instance;
603
+ const nodeIntoContainerService = useService(NodeIntoContainerService);
604
+ const show = useCallback(() => {
605
+ if (globalStore.isClosed()) {
606
+ return;
607
+ }
608
+ setVisible(true);
609
+ }, [globalStore]);
610
+ const close = useCallback(() => {
611
+ globalStore.close();
612
+ setVisible(false);
613
+ }, [globalStore]);
614
+ const closeForever = useCallback(() => {
615
+ globalStore.closeForever();
616
+ close();
617
+ }, [close, globalStore]);
618
+ useEffect2(() => {
619
+ const inDisposer = nodeIntoContainerService.on((e) => {
620
+ if (e.type !== "in" /* In */) {
621
+ return;
622
+ }
623
+ if (e.targetContainer === node) {
624
+ show();
625
+ }
626
+ });
627
+ const outDisposer = nodeIntoContainerService.on((e) => {
628
+ if (e.type !== "out" /* Out */) {
629
+ return;
630
+ }
631
+ if (e.sourceContainer === node && !node.blocks.length) {
632
+ setVisible(false);
633
+ }
634
+ });
635
+ return () => {
636
+ inDisposer.dispose();
637
+ outDisposer.dispose();
638
+ };
639
+ }, [nodeIntoContainerService, node, show, close, visible]);
640
+ return {
641
+ visible,
642
+ close,
643
+ closeForever
644
+ };
645
+ };
646
+
647
+ // src/sub-canvas/components/tips/style.ts
648
+ import styled4 from "styled-components";
649
+ var SubCanvasTipsStyle = styled4.div`
650
+ position: absolute;
651
+ top: 0;
652
+
653
+ width: 100%;
654
+ height: 28px;
655
+
656
+ .container {
657
+ height: 100%;
658
+ background-color: #e4e6f5;
659
+ border-radius: 4px 4px 0 0;
660
+
661
+ .content {
662
+ overflow: hidden;
663
+ display: inline-flex;
664
+ align-items: center;
665
+ justify-content: center;
666
+
667
+ width: 100%;
668
+ height: 100%;
669
+
670
+ .text {
671
+ font-size: 14px;
672
+ font-weight: 400;
673
+ font-style: normal;
674
+ line-height: 20px;
675
+ color: rgba(15, 21, 40, 82%);
676
+ text-overflow: ellipsis;
677
+ }
678
+
679
+ .space {
680
+ width: 128px;
681
+ }
682
+ }
683
+
684
+ .actions {
685
+ position: absolute;
686
+ top: 0;
687
+ right: 0;
688
+
689
+ display: flex;
690
+ gap: 8px;
691
+ align-items: center;
692
+
693
+ height: 28px;
694
+ padding: 0 16px;
695
+
696
+ .close-forever {
697
+ cursor: pointer;
698
+
699
+ padding: 0 3px;
700
+
701
+ font-size: 12px;
702
+ font-weight: 400;
703
+ font-style: normal;
704
+ line-height: 12px;
705
+ color: rgba(32, 41, 69, 62%);
706
+ }
707
+
708
+ .close {
709
+ display: flex;
710
+ cursor: pointer;
711
+ height: 100%;
712
+ align-items: center;
713
+ }
714
+ }
715
+ }
716
+ `;
717
+
718
+ // src/sub-canvas/components/tips/is-mac-os.ts
719
+ var isMacOS = /(Macintosh|MacIntel|MacPPC|Mac68K|iPad)/.test(navigator.userAgent);
720
+
721
+ // src/sub-canvas/components/tips/icon-close.tsx
722
+ import React3 from "react";
723
+ var IconClose = () => /* @__PURE__ */ React3.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", fill: "none", viewBox: "0 0 16 16" }, /* @__PURE__ */ React3.createElement(
724
+ "path",
725
+ {
726
+ fill: "#060709",
727
+ fillOpacity: "0.5",
728
+ d: "M12.13 12.128a.5.5 0 0 0 .001-.706L8.71 8l3.422-3.423a.5.5 0 0 0-.001-.705.5.5 0 0 0-.706-.002L8.002 7.293 4.579 3.87a.5.5 0 0 0-.705.002.5.5 0 0 0-.002.705L7.295 8l-3.423 3.422a.5.5 0 0 0 .002.706c.195.195.51.197.705.001l3.423-3.422 3.422 3.422c.196.196.51.194.706-.001"
729
+ }
730
+ ));
731
+
732
+ // src/sub-canvas/components/tips/index.tsx
733
+ var SubCanvasTips = () => {
734
+ const { visible, close, closeForever } = useControlTips();
735
+ if (!visible) {
736
+ return null;
737
+ }
738
+ return /* @__PURE__ */ React4.createElement(SubCanvasTipsStyle, { className: "sub-canvas-tips" }, /* @__PURE__ */ React4.createElement("div", { className: "container" }, /* @__PURE__ */ React4.createElement("div", { className: "content" }, /* @__PURE__ */ React4.createElement("p", { className: "text" }, `Hold ${isMacOS ? "Cmd \u2318" : "Ctrl"} to drag node out`), /* @__PURE__ */ React4.createElement(
739
+ "div",
740
+ {
741
+ className: "space",
742
+ style: {
743
+ width: 0
744
+ }
745
+ }
746
+ )), /* @__PURE__ */ React4.createElement("div", { className: "actions" }, /* @__PURE__ */ React4.createElement("p", { className: "close-forever", onClick: closeForever }, "Never Remind"), /* @__PURE__ */ React4.createElement("div", { className: "close", onClick: close }, /* @__PURE__ */ React4.createElement(IconClose, null)))));
747
+ };
748
+
479
749
  // src/sub-canvas/components/render/index.tsx
480
750
  var SubCanvasRender = ({ className, style }) => {
481
- const node = useCurrentEntity3();
751
+ const node = useCurrentEntity5();
482
752
  const nodeSize = useNodeSize();
483
- const { height, width } = nodeSize ?? {};
484
753
  const nodeHeight = nodeSize?.height ?? 0;
485
754
  const { padding } = node.transform;
486
- useLayoutEffect(() => {
487
- node.renderData.node.style.width = width + "px";
488
- node.renderData.node.style.height = height + "px";
489
- }, [height, width]);
490
- return /* @__PURE__ */ React3.createElement(
755
+ useSyncNodeRenderSize(nodeSize);
756
+ return /* @__PURE__ */ React5.createElement(
491
757
  SubCanvasRenderStyle,
492
758
  {
493
759
  className: `sub-canvas-render ${className ?? ""}`,
@@ -500,35 +766,18 @@ var SubCanvasRender = ({ className, style }) => {
500
766
  e.stopPropagation();
501
767
  }
502
768
  },
503
- /* @__PURE__ */ React3.createElement(SubCanvasBorder, null, /* @__PURE__ */ React3.createElement(SubCanvasBackground, null))
769
+ /* @__PURE__ */ React5.createElement(SubCanvasBorder, null, /* @__PURE__ */ React5.createElement(SubCanvasBackground, null), /* @__PURE__ */ React5.createElement(SubCanvasTips, null))
504
770
  );
505
771
  };
506
-
507
- // src/plugin.tsx
508
- import { definePluginCreator } from "@flowgram.ai/core";
509
- var createContainerNodePlugin = definePluginCreator({
510
- onBind: ({ bind }) => {
511
- bind(NodeIntoContainerService).toSelf().inSingletonScope();
512
- },
513
- onInit(ctx, options) {
514
- ctx.get(NodeIntoContainerService).init();
515
- },
516
- onReady(ctx, options) {
517
- if (options.disableNodeIntoContainer !== true) {
518
- ctx.get(NodeIntoContainerService).ready();
519
- }
520
- },
521
- onDispose(ctx) {
522
- ctx.get(NodeIntoContainerService).dispose();
523
- }
524
- });
525
772
  export {
526
773
  NodeIntoContainerService,
527
774
  NodeIntoContainerType,
528
775
  SubCanvasBackground,
529
776
  SubCanvasBorder,
530
777
  SubCanvasRender,
778
+ SubCanvasTips,
531
779
  createContainerNodePlugin,
532
- useNodeSize
780
+ useNodeSize,
781
+ useSyncNodeRenderSize
533
782
  };
534
783
  //# sourceMappingURL=index.js.map