Skip to content

Commit 631fe0b

Browse files
authored
Merge pull request #79 from easyops-cn/steve/lock-cells
feat(eo-draw-canvas): support lock cells
2 parents 0fc95e1 + 7f0cc71 commit 631fe0b

23 files changed

+841
-82
lines changed

bricks/diagram/docs/eo-draw-canvas.md

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@
114114
%>
115115
- name: dragging
116116
- name: activeTarget
117-
- name: targetCell
117+
- name: contextMenuTarget
118118
- name: scale
119119
value: 1
120120
children:
@@ -348,7 +348,7 @@
348348
- useBrick:
349349
brick: diagram.experimental-node
350350
properties:
351-
textContent: <% `Node ${DATA.node.id}` %>
351+
textContent: '<% `Node ${DATA.node.id}${DATA.node.locked ? " (locked)" : ""}` %>'
352352
status: |
353353
<%=
354354
(CTX.activeTarget?.type === "multi"
@@ -369,9 +369,9 @@
369369
dotted: true
370370
showStartArrow: true
371371
markers:
372-
- placement: end
373-
type: circle
374372
- placement: start
373+
type: circle
374+
- placement: end
375375
type: arrow
376376
cells: <% CTX.initialCells %>
377377
lineConnector: true
@@ -405,8 +405,8 @@
405405
- <% EVENT.detail.clientY %>
406406
- action: context.replace
407407
args:
408-
- targetCell
409-
- <% EVENT.detail.cell %>
408+
- contextMenuTarget
409+
- <% EVENT.detail %>
410410
edge.add:
411411
action: message.info
412412
args:
@@ -457,21 +457,26 @@
457457
properties:
458458
actions: |
459459
<%=
460-
(["node"].includes(CTX.targetCell?.type )||CTX.targetCell?.decorator=="area") ? [
461-
{
460+
!CTX.contextMenuTarget
461+
? []
462+
: [
463+
...(CTX.contextMenuTarget.locked ? [] : [{
462464
text: "添加边",
463465
event: "add-edge",
464-
}
465-
] : [
466+
},{
467+
text: "移除",
468+
event: "remove"
469+
}]),
466470
{
467-
text: `Test ${CTX.targetCell?.type}`,
468-
event: `test-${CTX.targetCell?.type}`,
471+
text: "锁定/取消锁定",
472+
event: "toggle-lock",
469473
},
470-
{
471-
text: "Remove",
472-
event: "remove"
473-
}
474-
]
474+
].filter((action) =>
475+
CTX.contextMenuTarget.cell.type === "node" || (
476+
CTX.contextMenuTarget.cell.type === "decorator" &&
477+
CTX.contextMenuTarget.cell.decorator === "area"
478+
) || action.event !== "add-edge"
479+
)
475480
%>
476481
events:
477482
remove:
@@ -481,21 +486,39 @@
481486
- |
482487
<%
483488
CTX.initialCells.filter((cell) =>
484-
!(cell.type === "edge" && CTX.targetCell.source === cell.source && CTX.targetCell.target === cell.target)
489+
!(
490+
CTX.contextMenuTarget.cell.type === "edge"
491+
? cell.type === "edge" && CTX.contextMenuTarget.cell.source === cell.source && CTX.contextMenuTarget.cell.target === cell.target
492+
: cell.id === CTX.contextMenuTarget.cell.id ||
493+
(cell.type === "edge" && (
494+
CTX.contextMenuTarget.cell.id === cell.source ||
495+
CTX.contextMenuTarget.cell.id === cell.target))
496+
)
485497
)
486498
%>
487499
add-edge:
488500
target: eo-draw-canvas
489501
method: manuallyConnectNodes
490502
args:
491-
- <% CTX.targetCell.id %>
503+
- <% CTX.contextMenuTarget.cell.id %>
492504
callback:
493505
success:
494506
- target: eo-draw-canvas
495507
method: addEdge
496508
args:
497509
- source: <% EVENT.detail.source.id %>
498510
target: <% EVENT.detail.target.id %>
511+
toggle-lock:
512+
target: eo-draw-canvas
513+
method: toggleLock
514+
args:
515+
- <% CTX.contextMenuTarget.cell %>
516+
callback:
517+
success:
518+
action: console.log
519+
args:
520+
- "Updated cells after toggle lock:"
521+
- <% EVENT.detail %>
499522
```
500523
501524
### Line labels

bricks/diagram/src/draw-canvas/CellComponent.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ export interface CellComponentProps {
5454
dragoverContainer?: boolean;
5555
allowEdgeToArea?: boolean;
5656
curActiveEditableLine?: EditableLineCell | null;
57+
locked?: boolean;
58+
containerLocked?: boolean;
5759
updateCurActiveEditableLine?: (
5860
activeEditableLine: EditableLineCell | null
5961
) => void;
@@ -89,6 +91,8 @@ export function CellComponent({
8991
unrelatedCells,
9092
allowEdgeToArea,
9193
curActiveEditableLine,
94+
locked,
95+
containerLocked,
9296
updateCurActiveEditableLine,
9397
onCellsMoving,
9498
onCellsMoved,
@@ -267,9 +271,10 @@ export function CellComponent({
267271
cell,
268272
clientX: event.clientX,
269273
clientY: event.clientY,
274+
locked,
270275
});
271276
},
272-
[cell, onCellContextMenu, onSwitchActiveTarget, readOnly]
277+
[cell, onCellContextMenu, onSwitchActiveTarget, readOnly, locked]
273278
);
274279

275280
const handleCellClick = useCallback(
@@ -281,9 +286,10 @@ export function CellComponent({
281286
cell,
282287
clientX: event.clientX,
283288
clientY: event.clientY,
289+
locked,
284290
});
285291
},
286-
[cell, onCellClick]
292+
[cell, onCellClick, locked]
287293
);
288294

289295
const handleMouseEnter = useCallback(() => {
@@ -326,6 +332,8 @@ export function CellComponent({
326332
degraded={degraded}
327333
degradedNodeLabel={degradedNodeLabel}
328334
defaultNodeBricks={defaultNodeBricks}
335+
locked={locked}
336+
containerLocked={containerLocked}
329337
onResize={onNodeBrickResize}
330338
/>
331339
) : isEdgeCell(cell) ? (
@@ -351,6 +359,7 @@ export function CellComponent({
351359
cells={cells}
352360
lineConfMap={lineConfMap}
353361
editableLineMap={editableLineMap}
362+
locked={locked}
354363
onCellResizing={onCellResizing}
355364
onCellResized={onCellResized}
356365
onSwitchActiveTarget={onSwitchActiveTarget}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import React from "react";
2+
3+
export interface LockIconProps {
4+
x: number;
5+
y: number;
6+
}
7+
8+
export function LockIcon({ x, y }: LockIconProps) {
9+
// 12 / 512 = 0.0234375
10+
return (
11+
<g
12+
transform={`translate(${x}, ${y}) scale(0.0234375)`}
13+
className="lock-icon"
14+
>
15+
<path
16+
d="M144 144l0 48 160 0 0-48c0-44.2-35.8-80-80-80s-80 35.8-80 80zM80 192l0-48C80 64.5 144.5 0 224 0s144 64.5 144 144l0 48 16 0c35.3 0 64 28.7 64 64l0 192c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 256c0-35.3 28.7-64 64-64l16 0z"
17+
fill="var(--color-secondary-text)"
18+
stroke="none"
19+
/>
20+
</g>
21+
);
22+
}

bricks/diagram/src/draw-canvas/NodeComponent.tsx

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { isEqual } from "lodash";
1111
import ResizeObserver from "resize-observer-polyfill";
1212
import type { NodeBrickCell, NodeBrickConf, NodeCell } from "./interfaces";
1313
import type { SizeTuple } from "../diagram/interfaces";
14+
import { LockIcon } from "./LockIcon";
1415

1516
export interface NodeComponentProps {
1617
node: NodeCell;
@@ -19,6 +20,8 @@ export interface NodeComponentProps {
1920
degraded: boolean;
2021
degradedNodeLabel?: string;
2122
defaultNodeBricks?: NodeBrickConf[];
23+
locked?: boolean;
24+
containerLocked?: boolean;
2225
onResize(id: string, size: SizeTuple | null): void;
2326
}
2427

@@ -29,9 +32,13 @@ export function NodeComponent({
2932
degraded,
3033
degradedNodeLabel,
3134
defaultNodeBricks,
35+
locked,
36+
containerLocked,
3237
onResize,
3338
}: NodeComponentProps): JSX.Element | null {
34-
const memoizedData = useDeepMemo({ node: { id: node.id, data: node.data } });
39+
const memoizedData = useDeepMemo({
40+
node: { id: node.id, data: node.data, locked: !!locked },
41+
});
3542
const specifiedUseBrick = (node as NodeBrickCell).useBrick;
3643
const observerRef = useRef<ResizeObserver | null>(null);
3744

@@ -137,30 +144,37 @@ export function NodeComponent({
137144
return () => clearTimeout(timeoutId);
138145
}, []);
139146

140-
return useBrick ? (
141-
<foreignObject
142-
// Make a large size to avoid the brick inside to be clipped by the foreignObject.
143-
width={9999}
144-
height={9999}
145-
className="node"
146-
ref={foreignObjectRef}
147-
>
148-
{useBrick && (
149-
<ReactUseBrick
150-
useBrick={useBrick}
151-
data={memoizedData}
152-
refCallback={refCallback}
153-
/>
147+
return (
148+
<>
149+
{useBrick ? (
150+
<foreignObject
151+
// Make a large size to avoid the brick inside to be clipped by the foreignObject.
152+
width={9999}
153+
height={9999}
154+
className="node"
155+
ref={foreignObjectRef}
156+
>
157+
{useBrick && (
158+
<ReactUseBrick
159+
useBrick={useBrick}
160+
data={memoizedData}
161+
refCallback={refCallback}
162+
/>
163+
)}
164+
</foreignObject>
165+
) : degraded ? (
166+
<g className="degraded" ref={degradedRefCallBack}>
167+
<circle cx={8} cy={8} r={8} />
168+
<text x={8} y={32}>
169+
{label}
170+
</text>
171+
</g>
172+
) : null}
173+
{locked && !containerLocked && x != null && y != null && (
174+
<LockIcon x={x + node.view.width + 4} y={y + node.view.height - 12} />
154175
)}
155-
</foreignObject>
156-
) : degraded ? (
157-
<g className="degraded" ref={degradedRefCallBack}>
158-
<circle cx={8} cy={8} r={8} />
159-
<text x={8} y={32}>
160-
{label}
161-
</text>
162-
</g>
163-
) : null;
176+
</>
177+
);
164178
}
165179

166180
function useDeepMemo<T>(value: T): T {

bricks/diagram/src/draw-canvas/decorators/DecoratorArea.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, { useEffect } from "react";
22
import type { BasicDecoratorProps } from "../interfaces";
33
import { handleMouseDown } from "../processors/handleMouseDown";
4+
import { LockIcon } from "../LockIcon";
45

56
export function DecoratorArea({
67
cell,
@@ -9,6 +10,7 @@ export function DecoratorArea({
910
layoutOptions,
1011
activeTarget,
1112
cells,
13+
locked,
1214
onCellResizing,
1315
onCellResized,
1416
onSwitchActiveTarget,
@@ -61,7 +63,7 @@ export function DecoratorArea({
6163
}}
6264
/>
6365
</foreignObject>
64-
{!readOnly && (
66+
{!readOnly && !locked && (
6567
<g
6668
ref={resizeHandleRef}
6769
className="resize-handle"
@@ -71,6 +73,9 @@ export function DecoratorArea({
7173
<path d="M10 18L18 10 M15 18L18 15" />
7274
</g>
7375
)}
76+
{locked && (
77+
<LockIcon x={cell.view.width - 16} y={cell.view.height - 16} />
78+
)}
7479
</g>
7580
);
7681
}

bricks/diagram/src/draw-canvas/decorators/DecoratorContainer.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { get } from "lodash";
66
import { selectAllText } from "./DecoratorText";
77
import { isNoManualLayout } from "../processors/asserts";
88
import { uuidV4 } from "..";
9+
import { LockIcon } from "../LockIcon";
10+
import { getContentEditable } from "../processors/getContentEditable";
911

1012
export function DecoratorContainer({
1113
cell,
@@ -15,6 +17,7 @@ export function DecoratorContainer({
1517
view,
1618
activeTarget,
1719
cells,
20+
locked,
1821
onCellResizing,
1922
onCellResized,
2023
onSwitchActiveTarget,
@@ -37,14 +40,14 @@ export function DecoratorContainer({
3740
});
3841
const handleEnableEdit = useCallback(
3942
(e: React.MouseEvent) => {
40-
if (readOnly) {
43+
if (readOnly || locked) {
4144
return;
4245
}
4346
e.preventDefault();
4447
e.stopPropagation();
4548
setEditingLabel(true);
4649
},
47-
[readOnly]
50+
[readOnly, locked]
4851
);
4952
const handleInput = useCallback(
5053
(event: React.FormEvent<HTMLDivElement>) => {
@@ -163,7 +166,7 @@ export function DecoratorContainer({
163166
>
164167
<div
165168
className="text"
166-
contentEditable={editingLabel}
169+
contentEditable={getContentEditable(editingLabel)}
167170
ref={textRef}
168171
onInput={handleInput}
169172
onBlur={handleBlur}
@@ -180,7 +183,7 @@ export function DecoratorContainer({
180183
}}
181184
/>
182185
</foreignObject>
183-
{!readOnly && !isNoManualLayout(layout) && (
186+
{!readOnly && !locked && !isNoManualLayout(layout) && (
184187
<g
185188
ref={resizeHandleRef}
186189
className="resize-handle"
@@ -190,6 +193,9 @@ export function DecoratorContainer({
190193
<path d="M10 18L18 10 M15 18L18 15" />
191194
</g>
192195
)}
196+
{locked && (
197+
<LockIcon x={cell.view.width - 16} y={cell.view.height - 16} />
198+
)}
193199
</g>
194200
);
195201
}

0 commit comments

Comments
 (0)