@ankhzet/graph 0.4.0 → 0.4.1
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/CHANGELOG.md +6 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +85 -76
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/ManagedGraphNode.ts +4 -4
- package/src/graph/NodeGroups.tsx +47 -29
- package/src/graph/nodes/GraphNode.ts +3 -3
- package/src/graph/render/PIXINode.ts +19 -19
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ankhzet/graph",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
"mobx": "^6.15.4",
|
|
32
32
|
"mobx-react-lite": "^4.1.1",
|
|
33
33
|
"@ankhzet/eventual": "1.2.0",
|
|
34
|
-
"@ankhzet/ui": "0.3.0",
|
|
35
34
|
"@ankhzet/gcl": "1.2.0",
|
|
36
|
-
"@ankhzet/utils": "1.19.0"
|
|
35
|
+
"@ankhzet/utils": "1.19.0",
|
|
36
|
+
"@ankhzet/ui": "0.3.0"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"react": "^19.2.6"
|
package/src/ManagedGraphNode.ts
CHANGED
|
@@ -239,8 +239,8 @@ export class ManagedGraphNode<Item extends IItem = IItem>
|
|
|
239
239
|
}
|
|
240
240
|
|
|
241
241
|
public render(content: Container) {
|
|
242
|
-
const width =
|
|
243
|
-
const height =
|
|
242
|
+
const width = content.width - 1;
|
|
243
|
+
const height = content.height - 1;
|
|
244
244
|
|
|
245
245
|
if (this.#outline) {
|
|
246
246
|
this.#outline.hotSwap(content);
|
|
@@ -251,7 +251,7 @@ export class ManagedGraphNode<Item extends IItem = IItem>
|
|
|
251
251
|
height,
|
|
252
252
|
color: 0x33ffff,
|
|
253
253
|
lineSize: 1,
|
|
254
|
-
radius:
|
|
254
|
+
radius: 6,
|
|
255
255
|
}),
|
|
256
256
|
content,
|
|
257
257
|
);
|
|
@@ -267,7 +267,7 @@ export class ManagedGraphNode<Item extends IItem = IItem>
|
|
|
267
267
|
sizeY: height,
|
|
268
268
|
color: 0x3333ff,
|
|
269
269
|
lineSize: 3,
|
|
270
|
-
radius:
|
|
270
|
+
radius: 6,
|
|
271
271
|
});
|
|
272
272
|
}
|
|
273
273
|
|
package/src/graph/NodeGroups.tsx
CHANGED
|
@@ -16,29 +16,36 @@ export type GroupResolver<T extends GroupableItem<any>> = (node: T) => null | un
|
|
|
16
16
|
export interface NodeGroupsProps<T extends GroupableItem<any>> extends PropsWithChildren {
|
|
17
17
|
items: T[];
|
|
18
18
|
resolver: GroupResolver<T>;
|
|
19
|
-
onSelect: (
|
|
19
|
+
onSelect: (groups: ItemsGroup<T['id']>[]) => void;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
function makeGroups<T extends GroupableItem<any>>(items: T[], resolver: GroupResolver<T>):
|
|
23
|
-
|
|
22
|
+
function makeGroups<T extends GroupableItem<any>>(items: T[], resolver: GroupResolver<T>): {
|
|
23
|
+
groups: ItemsGroup<T['id']>[];
|
|
24
|
+
getAll: () => ItemsGroup<T['id']>[];
|
|
25
|
+
} {
|
|
26
|
+
const resolved: ItemsGroup<T['id']>[] = [];
|
|
24
27
|
|
|
25
28
|
for (const node of items) {
|
|
26
29
|
const group = resolver(node);
|
|
27
30
|
|
|
28
31
|
if (group) {
|
|
29
|
-
|
|
32
|
+
resolved.push(group);
|
|
30
33
|
}
|
|
31
34
|
}
|
|
32
35
|
|
|
33
|
-
|
|
36
|
+
const all: ItemsGroup<T['id']> = {
|
|
37
|
+
value: 'all',
|
|
38
|
+
ids: items.map(({ id }) => id),
|
|
39
|
+
label: `All nodes (${items.length})`,
|
|
40
|
+
};
|
|
41
|
+
const groups = resolved
|
|
34
42
|
.sort((a, b) => a.label.localeCompare(b.label))
|
|
35
|
-
.concat([
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
]);
|
|
43
|
+
.concat([all]);
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
groups,
|
|
47
|
+
getAll: () => [all],
|
|
48
|
+
};
|
|
42
49
|
}
|
|
43
50
|
|
|
44
51
|
const isSameArray = (a: any[], b: any[]) => {
|
|
@@ -57,12 +64,24 @@ const isSameArray = (a: any[], b: any[]) => {
|
|
|
57
64
|
return true;
|
|
58
65
|
};
|
|
59
66
|
|
|
60
|
-
const isValue = (
|
|
61
|
-
const
|
|
67
|
+
const isValue = (values: Array<string | number>) => {
|
|
68
|
+
const mapped = values.map(String);
|
|
62
69
|
|
|
63
|
-
return (item: { value:
|
|
70
|
+
return (item: { value: string | number }) => (
|
|
71
|
+
mapped.includes(String(item.value))
|
|
72
|
+
);
|
|
64
73
|
};
|
|
65
74
|
|
|
75
|
+
const isSameSelection = (a: ItemsGroup<any>[], b: ItemsGroup<any>[]) => (
|
|
76
|
+
a.every((group) => b.some((item) => item.value === group.value)) && isSameArray(
|
|
77
|
+
a.flatMap(mapIds),
|
|
78
|
+
b.flatMap(mapIds)
|
|
79
|
+
)
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const mapValue = (item: { value: string | number }) => item.value;
|
|
83
|
+
const mapIds = (item: ItemsGroup<any>) => item.ids;
|
|
84
|
+
|
|
66
85
|
export function NodeGroups<Item extends GroupableItem<any>>({
|
|
67
86
|
items,
|
|
68
87
|
resolver,
|
|
@@ -70,40 +89,39 @@ export function NodeGroups<Item extends GroupableItem<any>>({
|
|
|
70
89
|
children,
|
|
71
90
|
...rest
|
|
72
91
|
}: NodeGroupsProps<Item>) {
|
|
73
|
-
const groups = useMemo(() => makeGroups(items, resolver), [items, resolver]);
|
|
74
|
-
const [
|
|
92
|
+
const { groups, getAll } = useMemo(() => makeGroups(items, resolver), [items, resolver]);
|
|
93
|
+
const [selected, setSelected] = useState(getAll);
|
|
94
|
+
const currentValue = useMemo(() => selected.map((group) => String(group.value)), [selected]);
|
|
75
95
|
|
|
76
|
-
const handleGroup = useLastCallback(({ target: {
|
|
77
|
-
const found = groups.
|
|
96
|
+
const handleGroup = useLastCallback(({ target: { selectedOptions } }: ChangeEvent<HTMLSelectElement>) => {
|
|
97
|
+
const found = groups.filter(isValue(Array.from(selectedOptions, mapValue)));
|
|
78
98
|
|
|
79
99
|
if (found) {
|
|
80
|
-
|
|
100
|
+
setSelected(found);
|
|
81
101
|
}
|
|
82
102
|
});
|
|
83
103
|
|
|
84
|
-
useEffect(() => void onSelect(
|
|
104
|
+
useEffect(() => void onSelect(selected), [selected, onSelect]);
|
|
85
105
|
|
|
86
106
|
useLayoutEffect(() => {
|
|
87
|
-
const found = groups.
|
|
107
|
+
const found = groups.filter(isValue(selected.map(mapValue)));
|
|
88
108
|
|
|
89
109
|
if (found) {
|
|
90
|
-
if (found
|
|
110
|
+
if (isSameSelection(found, selected)) {
|
|
91
111
|
return;
|
|
92
112
|
}
|
|
93
113
|
|
|
94
|
-
|
|
95
|
-
setGroup(found);
|
|
96
|
-
}
|
|
114
|
+
setSelected(found);
|
|
97
115
|
} else {
|
|
98
|
-
|
|
116
|
+
setSelected(getAll);
|
|
99
117
|
}
|
|
100
|
-
}, [groups,
|
|
118
|
+
}, [groups, getAll, selected]);
|
|
101
119
|
|
|
102
120
|
return (
|
|
103
121
|
<select
|
|
104
122
|
multiple
|
|
105
123
|
size={groups.length}
|
|
106
|
-
value={
|
|
124
|
+
value={currentValue}
|
|
107
125
|
onChange={handleGroup}
|
|
108
126
|
{...rest}
|
|
109
127
|
>
|
|
@@ -140,9 +140,9 @@ export abstract class GraphNode<ID, Data = {}, R = any>
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
move(pos: Position): Position {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
return (
|
|
144
|
+
this.position = pos
|
|
145
|
+
);
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
onConnect(handler: IEventHandler<NodeConnection<ID>>): Disposer {
|
|
@@ -14,7 +14,6 @@ import { PIXIText } from './PIXIText';
|
|
|
14
14
|
export const E_NODE_CONTEXT = Symbol.for('E_NODE_CONTEXT');
|
|
15
15
|
export const E_CONNECTORS_UPDATE = Symbol.for('E_CONNECTORS_UPDATE');
|
|
16
16
|
|
|
17
|
-
export const NODE_WIDTH = 64;
|
|
18
17
|
export const LANE_WIDTH = 16;
|
|
19
18
|
export const LANE_HEIGHT = 16;
|
|
20
19
|
export const PORT_SIZE = 8;
|
|
@@ -48,8 +47,8 @@ export class PIXINode<Node extends GraphNodeInterface<any, any, Container>> exte
|
|
|
48
47
|
});
|
|
49
48
|
|
|
50
49
|
readonly #label: PIXIText;
|
|
51
|
-
#
|
|
52
|
-
#
|
|
50
|
+
#shape?: Container;
|
|
51
|
+
#connectors?: ConnectorPoint<Node>[];
|
|
53
52
|
#drag = onDragStart(this);
|
|
54
53
|
|
|
55
54
|
public data: Node;
|
|
@@ -79,8 +78,8 @@ export class PIXINode<Node extends GraphNodeInterface<any, any, Container>> exte
|
|
|
79
78
|
port.dispose();
|
|
80
79
|
}
|
|
81
80
|
|
|
82
|
-
if (this.#
|
|
83
|
-
const remove = this.#
|
|
81
|
+
if (this.#connectors) {
|
|
82
|
+
const remove = this.#connectors.map(({ port }) => `${this.id}-${port}` as ConnectorId<Node>);
|
|
84
83
|
|
|
85
84
|
if (remove.length) {
|
|
86
85
|
this.dispatchEvent(E_CONNECTORS_UPDATE, {
|
|
@@ -89,9 +88,9 @@ export class PIXINode<Node extends GraphNodeInterface<any, any, Container>> exte
|
|
|
89
88
|
}
|
|
90
89
|
}
|
|
91
90
|
|
|
92
|
-
if (this.#
|
|
93
|
-
this.#
|
|
94
|
-
this.#
|
|
91
|
+
if (this.#shape) {
|
|
92
|
+
this.#shape.removeFromParent();
|
|
93
|
+
this.#shape = undefined;
|
|
95
94
|
}
|
|
96
95
|
|
|
97
96
|
super.dispose();
|
|
@@ -113,7 +112,7 @@ export class PIXINode<Node extends GraphNodeInterface<any, any, Container>> exte
|
|
|
113
112
|
}
|
|
114
113
|
|
|
115
114
|
public get width() {
|
|
116
|
-
return Math.max(this.data.width, this.#label.width +
|
|
115
|
+
return Math.max(this.data.width, this.#label.width + 6);
|
|
117
116
|
}
|
|
118
117
|
|
|
119
118
|
public get height() {
|
|
@@ -143,18 +142,19 @@ export class PIXINode<Node extends GraphNodeInterface<any, any, Container>> exte
|
|
|
143
142
|
}
|
|
144
143
|
|
|
145
144
|
public get connectors(): ConnectorPoint<Node>[] {
|
|
146
|
-
if (this.#
|
|
147
|
-
return this.#
|
|
145
|
+
if (this.#connectors) {
|
|
146
|
+
return this.#connectors;
|
|
148
147
|
}
|
|
149
148
|
|
|
150
|
-
const
|
|
149
|
+
const width = this.width;
|
|
150
|
+
this.#connectors = [];
|
|
151
151
|
let idx1 = 1;
|
|
152
152
|
let idx2 = 1;
|
|
153
153
|
|
|
154
154
|
for (const joint of this.data.inputs) {
|
|
155
155
|
const offset = { x: LANE_WIDTH / 2, y: idx1 * LANE_HEIGHT + LANE_HEIGHT / 2 };
|
|
156
156
|
const pos = addOffset(this.data.position, offset);
|
|
157
|
-
connectors.push({
|
|
157
|
+
this.#connectors.push({
|
|
158
158
|
parent: this.data.id,
|
|
159
159
|
port: joint.port,
|
|
160
160
|
name: joint.name,
|
|
@@ -168,19 +168,19 @@ export class PIXINode<Node extends GraphNodeInterface<any, any, Container>> exte
|
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
for (const joint of this.data.outputs) {
|
|
171
|
-
connectors.push({
|
|
171
|
+
this.#connectors.push({
|
|
172
172
|
parent: this.data.id,
|
|
173
173
|
port: joint.port,
|
|
174
174
|
name: joint.name,
|
|
175
175
|
nodes: joint.nodes,
|
|
176
176
|
input: false,
|
|
177
|
-
x:
|
|
177
|
+
x: width - LANE_WIDTH / 2,
|
|
178
178
|
y: idx2 * LANE_HEIGHT + LANE_HEIGHT / 2,
|
|
179
179
|
});
|
|
180
180
|
idx2++;
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
return
|
|
183
|
+
return this.#connectors;
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
public placeConnectors(recursive = true) {
|
|
@@ -231,8 +231,8 @@ export class PIXINode<Node extends GraphNodeInterface<any, any, Container>> exte
|
|
|
231
231
|
}
|
|
232
232
|
|
|
233
233
|
public get renderable(): Container {
|
|
234
|
-
if (this.#
|
|
235
|
-
return this.#
|
|
234
|
+
if (this.#shape) {
|
|
235
|
+
return this.#shape;
|
|
236
236
|
}
|
|
237
237
|
|
|
238
238
|
const width = this.width;
|
|
@@ -289,7 +289,7 @@ export class PIXINode<Node extends GraphNodeInterface<any, any, Container>> exte
|
|
|
289
289
|
base.x = this.data.position.x;
|
|
290
290
|
base.y = this.data.position.y;
|
|
291
291
|
|
|
292
|
-
return (this.#
|
|
292
|
+
return (this.#shape = base);
|
|
293
293
|
}
|
|
294
294
|
|
|
295
295
|
public makeRoundRect({ stroke, fill, x = 0, y = 0, width, height, thickness = 1, radius = 4 }: RoundRectStyle) {
|