@dou-ui/flow 0.0.0-stage → 0.1.0-alpha.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/LICENSE +21 -0
- package/README.md +10 -2
- package/dist/collaboration/index.js +360 -0
- package/dist/collaboration/index.js.map +1 -0
- package/dist/collaboration/peers.js +76 -0
- package/dist/collaboration/peers.js.map +1 -0
- package/dist/components/default-node.js +6 -0
- package/dist/components/default-node.js.map +1 -0
- package/dist/components/default-node.vue_vue_type_script_setup_true_lang.js +87 -0
- package/dist/components/default-node.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/default-node2.js +116 -0
- package/dist/components/default-node2.js.map +1 -0
- package/dist/components/flow-controls.js +6 -0
- package/dist/components/flow-controls.js.map +1 -0
- package/dist/components/flow-controls.vue_vue_type_script_setup_true_lang.js +148 -0
- package/dist/components/flow-controls.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/flow-menu.js +6 -0
- package/dist/components/flow-menu.js.map +1 -0
- package/dist/components/flow-menu.vue_vue_type_script_setup_true_lang.js +52 -0
- package/dist/components/flow-menu.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/flow-minimap.js +6 -0
- package/dist/components/flow-minimap.js.map +1 -0
- package/dist/components/flow-minimap.vue_vue_type_script_setup_true_lang.js +238 -0
- package/dist/components/flow-minimap.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/flow-port.js +6 -0
- package/dist/components/flow-port.js.map +1 -0
- package/dist/components/flow-port.vue_vue_type_script_setup_true_lang.js +57 -0
- package/dist/components/flow-port.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/flow-quick-add.js +6 -0
- package/dist/components/flow-quick-add.js.map +1 -0
- package/dist/components/flow-quick-add.vue_vue_type_script_setup_true_lang.js +126 -0
- package/dist/components/flow-quick-add.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/flow-toolbar.js +6 -0
- package/dist/components/flow-toolbar.js.map +1 -0
- package/dist/components/flow-toolbar.vue_vue_type_script_setup_true_lang.js +161 -0
- package/dist/components/flow-toolbar.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/group-node.js +6 -0
- package/dist/components/group-node.js.map +1 -0
- package/dist/components/group-node.vue_vue_type_script_setup_true_lang.js +84 -0
- package/dist/components/group-node.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/group-node2.js +42 -0
- package/dist/components/group-node2.js.map +1 -0
- package/dist/components/node-view.js +6 -0
- package/dist/components/node-view.js.map +1 -0
- package/dist/components/node-view.vue_vue_type_script_setup_true_lang.js +179 -0
- package/dist/components/node-view.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/context.js +13 -0
- package/dist/context.js.map +1 -0
- package/dist/core/clipboard.js +37 -0
- package/dist/core/clipboard.js.map +1 -0
- package/dist/core/connect.js +74 -0
- package/dist/core/connect.js.map +1 -0
- package/dist/core/edge-types.js +240 -0
- package/dist/core/edge-types.js.map +1 -0
- package/dist/core/extension.js +63 -0
- package/dist/core/extension.js.map +1 -0
- package/dist/core/geometry.js +232 -0
- package/dist/core/geometry.js.map +1 -0
- package/dist/core/groups.js +76 -0
- package/dist/core/groups.js.map +1 -0
- package/dist/core/history.js +79 -0
- package/dist/core/history.js.map +1 -0
- package/dist/core/model.js +21 -0
- package/dist/core/model.js.map +1 -0
- package/dist/core/node-types.js +32 -0
- package/dist/core/node-types.js.map +1 -0
- package/dist/core/order.js +42 -0
- package/dist/core/order.js.map +1 -0
- package/dist/core/transaction.js +288 -0
- package/dist/core/transaction.js.map +1 -0
- package/dist/document.js +220 -0
- package/dist/document.js.map +1 -0
- package/dist/export/color.js +38 -0
- package/dist/export/color.js.map +1 -0
- package/dist/export/context.js +223 -0
- package/dist/export/context.js.map +1 -0
- package/dist/export/draw.js +249 -0
- package/dist/export/draw.js.map +1 -0
- package/dist/flow-editor.js +6 -0
- package/dist/flow-editor.js.map +1 -0
- package/dist/flow-editor.vue_vue_type_script_setup_true_lang.js +3721 -0
- package/dist/flow-editor.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/kit.js +234 -0
- package/dist/kit.js.map +1 -0
- package/dist/layers.js +150 -0
- package/dist/layers.js.map +1 -0
- package/dist/locale.js +426 -0
- package/dist/locale.js.map +1 -0
- package/dist/style.css +1 -0
- package/dist/style.d.ts +1 -0
- package/dist/style.js +8 -0
- package/dist/types/collaboration/index.d.ts +42 -0
- package/dist/types/collaboration/peers.d.ts +27 -0
- package/dist/types/components/default-node.d.ts +18 -0
- package/dist/types/components/default-node.vue.d.ts +6 -0
- package/dist/types/components/flow-controls.vue.d.ts +8 -0
- package/dist/types/components/flow-menu.vue.d.ts +29 -0
- package/dist/types/components/flow-minimap.vue.d.ts +8 -0
- package/dist/types/components/flow-port.vue.d.ts +9 -0
- package/dist/types/components/flow-quick-add.vue.d.ts +31 -0
- package/dist/types/components/flow-toolbar.vue.d.ts +25 -0
- package/dist/types/components/group-node.d.ts +13 -0
- package/dist/types/components/group-node.vue.d.ts +6 -0
- package/dist/types/components/node-view.vue.d.ts +28 -0
- package/dist/types/context.d.ts +362 -0
- package/dist/types/core/clipboard.d.ts +9 -0
- package/dist/types/core/connect.d.ts +63 -0
- package/dist/types/core/edge-types.d.ts +50 -0
- package/dist/types/core/extension.d.ts +141 -0
- package/dist/types/core/geometry.d.ts +63 -0
- package/dist/types/core/groups.d.ts +23 -0
- package/dist/types/core/history.d.ts +24 -0
- package/dist/types/core/menu.d.ts +40 -0
- package/dist/types/core/model.d.ts +87 -0
- package/dist/types/core/node-types.d.ts +103 -0
- package/dist/types/core/order.d.ts +7 -0
- package/dist/types/core/transaction.d.ts +97 -0
- package/dist/types/document.d.ts +57 -0
- package/dist/types/export/color.d.ts +7 -0
- package/dist/types/export/context.d.ts +90 -0
- package/dist/types/export/draw.d.ts +72 -0
- package/dist/types/flow-editor.vue.d.ts +63 -0
- package/dist/types/index.d.ts +202 -0
- package/dist/types/kit.d.ts +22 -0
- package/dist/types/layers.d.ts +24 -0
- package/dist/types/locale.d.ts +172 -0
- package/dist/types/whiteboard/board-draft.vue.d.ts +3 -0
- package/dist/types/whiteboard/board-node.vue.d.ts +19 -0
- package/dist/types/whiteboard/connector.d.ts +12 -0
- package/dist/types/whiteboard/draw.d.ts +5 -0
- package/dist/types/whiteboard/image.d.ts +27 -0
- package/dist/types/whiteboard/index.d.ts +6 -0
- package/dist/types/whiteboard/ink.d.ts +30 -0
- package/dist/types/whiteboard/kit.d.ts +36 -0
- package/dist/types/whiteboard/model.d.ts +79 -0
- package/dist/types/whiteboard/pen.d.ts +7 -0
- package/dist/types/whiteboard/state.d.ts +37 -0
- package/dist/types/whiteboard/transform.d.ts +67 -0
- package/dist/types/whiteboard/whiteboard-toolbar.vue.d.ts +3 -0
- package/dist/whiteboard/board-draft.js +6 -0
- package/dist/whiteboard/board-draft.js.map +1 -0
- package/dist/whiteboard/board-draft.vue_vue_type_script_setup_true_lang.js +58 -0
- package/dist/whiteboard/board-draft.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/whiteboard/board-node.js +6 -0
- package/dist/whiteboard/board-node.js.map +1 -0
- package/dist/whiteboard/board-node.vue_vue_type_script_setup_true_lang.js +332 -0
- package/dist/whiteboard/board-node.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/whiteboard/connector.js +180 -0
- package/dist/whiteboard/connector.js.map +1 -0
- package/dist/whiteboard/draw.js +68 -0
- package/dist/whiteboard/draw.js.map +1 -0
- package/dist/whiteboard/image.js +104 -0
- package/dist/whiteboard/image.js.map +1 -0
- package/dist/whiteboard/index.js +5 -0
- package/dist/whiteboard/ink.js +168 -0
- package/dist/whiteboard/ink.js.map +1 -0
- package/dist/whiteboard/kit.js +406 -0
- package/dist/whiteboard/kit.js.map +1 -0
- package/dist/whiteboard/model.js +52 -0
- package/dist/whiteboard/model.js.map +1 -0
- package/dist/whiteboard/pen.js +88 -0
- package/dist/whiteboard/pen.js.map +1 -0
- package/dist/whiteboard/state.js +26 -0
- package/dist/whiteboard/state.js.map +1 -0
- package/dist/whiteboard/transform.js +256 -0
- package/dist/whiteboard/transform.js.map +1 -0
- package/dist/whiteboard/whiteboard-toolbar.js +6 -0
- package/dist/whiteboard/whiteboard-toolbar.js.map +1 -0
- package/dist/whiteboard/whiteboard-toolbar.vue_vue_type_script_setup_true_lang.js +171 -0
- package/dist/whiteboard/whiteboard-toolbar.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/package.json +74 -4
package/dist/kit.js
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { defaultNode } from "./components/default-node2.js";
|
|
2
|
+
import { bezierEdge, stepEdge, straightEdge } from "./core/edge-types.js";
|
|
3
|
+
import { defineFlowExtension } from "./core/extension.js";
|
|
4
|
+
import { groupNode } from "./components/group-node2.js";
|
|
5
|
+
import { layers, stacking } from "./layers.js";
|
|
6
|
+
import { ClipboardPasteIcon, CopyIcon, CopyPlusIcon, EditIcon, FocusIcon, GroupIcon, HierarchyHorizontalIcon, MoreHorizontalIcon, ScissorsIcon, SquareDashedIcon, TrashIcon, UngroupIcon } from "@dou-ui/icons";
|
|
7
|
+
// src/kit.ts
|
|
8
|
+
/** Copy, paste, delete and the rest, on right click. */
|
|
9
|
+
var editMenu = defineFlowExtension({
|
|
10
|
+
name: "edit-menu",
|
|
11
|
+
priority: 0,
|
|
12
|
+
contextMenu(ctx, target) {
|
|
13
|
+
const t = ctx.messages.value;
|
|
14
|
+
const editable = !ctx.readonly.value;
|
|
15
|
+
const items = [];
|
|
16
|
+
if (target.kind === "canvas") {
|
|
17
|
+
if (editable) items.push({
|
|
18
|
+
key: "paste",
|
|
19
|
+
label: t.paste,
|
|
20
|
+
icon: ClipboardPasteIcon,
|
|
21
|
+
shortcut: "mod+v",
|
|
22
|
+
run: () => void ctx.paste(target.point)
|
|
23
|
+
});
|
|
24
|
+
items.push({
|
|
25
|
+
key: "select-all",
|
|
26
|
+
label: t.selectAll,
|
|
27
|
+
icon: SquareDashedIcon,
|
|
28
|
+
shortcut: "mod+a",
|
|
29
|
+
disabled: !ctx.doc.value.nodes.length,
|
|
30
|
+
run: ctx.selectAll
|
|
31
|
+
});
|
|
32
|
+
if (editable) items.push({
|
|
33
|
+
key: "tidy",
|
|
34
|
+
label: t.tidy,
|
|
35
|
+
icon: HierarchyHorizontalIcon,
|
|
36
|
+
divided: true,
|
|
37
|
+
disabled: !ctx.doc.value.nodes.length,
|
|
38
|
+
run: () => ctx.tidy()
|
|
39
|
+
});
|
|
40
|
+
items.push({
|
|
41
|
+
key: "fit",
|
|
42
|
+
label: t.fit,
|
|
43
|
+
icon: FocusIcon,
|
|
44
|
+
divided: !editable,
|
|
45
|
+
disabled: !ctx.doc.value.nodes.length,
|
|
46
|
+
run: () => ctx.fit()
|
|
47
|
+
});
|
|
48
|
+
return items;
|
|
49
|
+
}
|
|
50
|
+
if (target.kind === "node") {
|
|
51
|
+
items.push({
|
|
52
|
+
key: "copy",
|
|
53
|
+
label: t.copy,
|
|
54
|
+
icon: CopyIcon,
|
|
55
|
+
shortcut: "mod+c",
|
|
56
|
+
run: ctx.copy
|
|
57
|
+
});
|
|
58
|
+
if (!editable) return items;
|
|
59
|
+
const selected = ctx.selection.value.nodes;
|
|
60
|
+
const locked = (selected.includes(target.node.id) ? selected : [target.node.id]).every((id) => ctx.doc.value.nodes.find((n) => n.id === id)?.locked);
|
|
61
|
+
items.push({
|
|
62
|
+
key: "cut",
|
|
63
|
+
label: t.cut,
|
|
64
|
+
icon: ScissorsIcon,
|
|
65
|
+
shortcut: "mod+x",
|
|
66
|
+
disabled: locked,
|
|
67
|
+
run: ctx.cut
|
|
68
|
+
}, {
|
|
69
|
+
key: "duplicate",
|
|
70
|
+
label: t.duplicate,
|
|
71
|
+
icon: CopyPlusIcon,
|
|
72
|
+
shortcut: "mod+d",
|
|
73
|
+
run: ctx.duplicate
|
|
74
|
+
}, {
|
|
75
|
+
key: "delete",
|
|
76
|
+
label: t.delete,
|
|
77
|
+
icon: TrashIcon,
|
|
78
|
+
shortcut: "delete",
|
|
79
|
+
tone: "danger",
|
|
80
|
+
divided: true,
|
|
81
|
+
disabled: locked,
|
|
82
|
+
run: ctx.deleteSelection
|
|
83
|
+
});
|
|
84
|
+
return items;
|
|
85
|
+
}
|
|
86
|
+
if (!editable) return items;
|
|
87
|
+
const edge = target.edge;
|
|
88
|
+
items.push({
|
|
89
|
+
key: "edit-label",
|
|
90
|
+
label: t.editLabel,
|
|
91
|
+
icon: EditIcon,
|
|
92
|
+
shortcut: "enter",
|
|
93
|
+
run: () => ctx.editLabel(edge.id)
|
|
94
|
+
}, {
|
|
95
|
+
key: "delete",
|
|
96
|
+
label: t.delete,
|
|
97
|
+
icon: TrashIcon,
|
|
98
|
+
shortcut: "delete",
|
|
99
|
+
tone: "danger",
|
|
100
|
+
divided: true,
|
|
101
|
+
run: ctx.deleteSelection
|
|
102
|
+
});
|
|
103
|
+
return items;
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
/** Duplicate, delete and the node menu, over the selected nodes; delete over selected edges. */
|
|
107
|
+
var editToolbar = defineFlowExtension({
|
|
108
|
+
name: "edit-toolbar",
|
|
109
|
+
priority: 0,
|
|
110
|
+
edgeToolbarItems(ctx) {
|
|
111
|
+
return [{
|
|
112
|
+
key: "delete",
|
|
113
|
+
label: ctx.messages.value.delete,
|
|
114
|
+
icon: TrashIcon,
|
|
115
|
+
shortcut: "delete",
|
|
116
|
+
run: ctx.deleteSelection
|
|
117
|
+
}];
|
|
118
|
+
},
|
|
119
|
+
toolbarItems(ctx, nodes) {
|
|
120
|
+
const t = ctx.messages.value;
|
|
121
|
+
const first = nodes[0];
|
|
122
|
+
const more = ctx.menuItems({
|
|
123
|
+
kind: "node",
|
|
124
|
+
node: first,
|
|
125
|
+
point: {
|
|
126
|
+
x: first.x,
|
|
127
|
+
y: first.y
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
return [
|
|
131
|
+
{
|
|
132
|
+
key: "duplicate",
|
|
133
|
+
label: t.duplicate,
|
|
134
|
+
icon: CopyPlusIcon,
|
|
135
|
+
shortcut: "mod+d",
|
|
136
|
+
run: ctx.duplicate
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
key: "delete",
|
|
140
|
+
label: t.delete,
|
|
141
|
+
icon: TrashIcon,
|
|
142
|
+
shortcut: "delete",
|
|
143
|
+
disabled: nodes.every((node) => node.locked),
|
|
144
|
+
run: ctx.deleteSelection
|
|
145
|
+
},
|
|
146
|
+
...more.length ? [{
|
|
147
|
+
key: "more",
|
|
148
|
+
label: t.more,
|
|
149
|
+
icon: MoreHorizontalIcon,
|
|
150
|
+
children: more
|
|
151
|
+
}] : []
|
|
152
|
+
];
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
/** Group nodes: the type, Ctrl / ⌘ + G to group the selection, with Shift to ungroup. */
|
|
156
|
+
var groups = defineFlowExtension({
|
|
157
|
+
name: "groups",
|
|
158
|
+
priority: 0,
|
|
159
|
+
nodeTypes: [groupNode],
|
|
160
|
+
keymap: (ctx) => ({
|
|
161
|
+
"mod+g": () => ctx.groupNodes() !== null,
|
|
162
|
+
"mod+shift+g": () => {
|
|
163
|
+
const ids = ctx.selection.value.nodes;
|
|
164
|
+
const containers = ids.filter((id) => {
|
|
165
|
+
const node = ctx.doc.value.nodes.find((n) => n.id === id);
|
|
166
|
+
return node && ctx.isContainer(node);
|
|
167
|
+
});
|
|
168
|
+
if (containers.length) return containers.filter((id) => ctx.ungroupNode(id)).length > 0;
|
|
169
|
+
return ctx.leaveGroup(ids) > 0;
|
|
170
|
+
}
|
|
171
|
+
}),
|
|
172
|
+
contextMenu(ctx, target) {
|
|
173
|
+
if (target.kind !== "node" || ctx.readonly.value) return [];
|
|
174
|
+
const t = ctx.messages.value;
|
|
175
|
+
const tree = ctx.tree.value;
|
|
176
|
+
const parents = new Set(tree.topmost(ctx.selection.value.nodes).map((id) => tree.parentOf(id)));
|
|
177
|
+
const items = [{
|
|
178
|
+
key: "group",
|
|
179
|
+
label: t.makeGroup,
|
|
180
|
+
icon: GroupIcon,
|
|
181
|
+
shortcut: "mod+g",
|
|
182
|
+
disabled: parents.size > 1,
|
|
183
|
+
run: () => void ctx.groupNodes()
|
|
184
|
+
}];
|
|
185
|
+
if (ctx.isContainer(target.node)) items.push({
|
|
186
|
+
key: "ungroup",
|
|
187
|
+
label: t.ungroup,
|
|
188
|
+
icon: UngroupIcon,
|
|
189
|
+
shortcut: "mod+shift+g",
|
|
190
|
+
run: () => void ctx.ungroupNode(target.node.id)
|
|
191
|
+
});
|
|
192
|
+
if (tree.parentOf(target.node.id)) items.push({
|
|
193
|
+
key: "leave-group",
|
|
194
|
+
label: t.leaveGroup,
|
|
195
|
+
icon: UngroupIcon,
|
|
196
|
+
shortcut: ctx.isContainer(target.node) ? void 0 : "mod+shift+g",
|
|
197
|
+
run: () => void ctx.leaveGroup()
|
|
198
|
+
});
|
|
199
|
+
return items;
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
/**
|
|
203
|
+
* The editor's built-in parts as extensions: the default node type, the
|
|
204
|
+
* `bezier`, `step` and `straight` edge types, group nodes, stacking order,
|
|
205
|
+
* the context menu and the toolbar over selected nodes. Pass it along with
|
|
206
|
+
* your own extensions, e.g. `[flowKit(), aiNodes]`; without `extensions`
|
|
207
|
+
* the editor uses it alone.
|
|
208
|
+
*/
|
|
209
|
+
function flowKit(options = {}) {
|
|
210
|
+
const { menu = true, toolbar = true, group = true, layers: layered = true } = options;
|
|
211
|
+
const kit = [
|
|
212
|
+
defineFlowExtension({
|
|
213
|
+
name: "default-node",
|
|
214
|
+
nodeTypes: [defaultNode]
|
|
215
|
+
}),
|
|
216
|
+
defineFlowExtension({
|
|
217
|
+
name: "default-edges",
|
|
218
|
+
edgeTypes: [
|
|
219
|
+
bezierEdge,
|
|
220
|
+
stepEdge,
|
|
221
|
+
straightEdge
|
|
222
|
+
]
|
|
223
|
+
}),
|
|
224
|
+
stacking
|
|
225
|
+
];
|
|
226
|
+
if (menu) kit.push(editMenu);
|
|
227
|
+
if (toolbar) kit.push(editToolbar);
|
|
228
|
+
if (group) kit.push(groups);
|
|
229
|
+
if (layered) kit.push(layers);
|
|
230
|
+
return kit;
|
|
231
|
+
}
|
|
232
|
+
export { flowKit };
|
|
233
|
+
|
|
234
|
+
//# sourceMappingURL=kit.js.map
|
package/dist/kit.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kit.js","names":[],"sources":["../src/kit.ts"],"sourcesContent":["import type { ContextMenuItem } from 'dou-ui'\nimport {\n ClipboardPasteIcon,\n CopyIcon,\n CopyPlusIcon,\n EditIcon,\n GroupIcon,\n FocusIcon,\n HierarchyHorizontalIcon,\n MoreHorizontalIcon,\n ScissorsIcon,\n SquareDashedIcon,\n TrashIcon,\n UngroupIcon,\n} from '@dou-ui/icons'\nimport { defaultNode } from './components/default-node'\nimport { groupNode } from './components/group-node'\nimport { bezierEdge, stepEdge, straightEdge } from './core/edge-types'\nimport { defineFlowExtension, type FlowExtension } from './core/extension'\nimport { layers, stacking } from './layers'\n\nexport interface FlowKitOptions {\n /** The built-in context menu items: copy, paste, delete and so on. */\n menu?: boolean\n /** The toolbar over the selected nodes: duplicate, delete and more. */\n toolbar?: boolean\n /** The `group` node type, with its menu items and `mod+g` / `mod+shift+g`. */\n group?: boolean\n /**\n * Moving nodes up and down the stack: menu items and `mod+]` / `mod+[`,\n * with Shift for the very top or bottom.\n */\n layers?: boolean\n}\n\n/** Copy, paste, delete and the rest, on right click. */\nconst editMenu = defineFlowExtension({\n name: 'edit-menu',\n // Last, so extensions' own items come first.\n priority: 0,\n contextMenu(ctx, target) {\n const t = ctx.messages.value\n const editable = !ctx.readonly.value\n const items: ContextMenuItem[] = []\n if (target.kind === 'canvas') {\n if (editable)\n items.push({\n key: 'paste',\n label: t.paste,\n icon: ClipboardPasteIcon,\n shortcut: 'mod+v',\n run: () => void ctx.paste(target.point),\n })\n items.push({\n key: 'select-all',\n label: t.selectAll,\n icon: SquareDashedIcon,\n shortcut: 'mod+a',\n disabled: !ctx.doc.value.nodes.length,\n run: ctx.selectAll,\n })\n if (editable)\n items.push({\n key: 'tidy',\n label: t.tidy,\n icon: HierarchyHorizontalIcon,\n divided: true,\n disabled: !ctx.doc.value.nodes.length,\n run: () => ctx.tidy(),\n })\n items.push({\n key: 'fit',\n label: t.fit,\n icon: FocusIcon,\n divided: !editable,\n disabled: !ctx.doc.value.nodes.length,\n run: () => ctx.fit(),\n })\n return items\n }\n if (target.kind === 'node') {\n items.push({ key: 'copy', label: t.copy, icon: CopyIcon, shortcut: 'mod+c', run: ctx.copy })\n if (!editable) return items\n // The menu acts on the selection, which the node just joined if it wasn't in it.\n const selected = ctx.selection.value.nodes\n const ids = selected.includes(target.node.id) ? selected : [target.node.id]\n const locked = ids.every((id) => ctx.doc.value.nodes.find((n) => n.id === id)?.locked)\n items.push(\n {\n key: 'cut',\n label: t.cut,\n icon: ScissorsIcon,\n shortcut: 'mod+x',\n disabled: locked,\n run: ctx.cut,\n },\n {\n key: 'duplicate',\n label: t.duplicate,\n icon: CopyPlusIcon,\n shortcut: 'mod+d',\n run: ctx.duplicate,\n },\n {\n key: 'delete',\n label: t.delete,\n icon: TrashIcon,\n shortcut: 'delete',\n tone: 'danger',\n divided: true,\n disabled: locked,\n run: ctx.deleteSelection,\n },\n )\n return items\n }\n if (!editable) return items\n const edge = target.edge\n items.push(\n {\n key: 'edit-label',\n label: t.editLabel,\n icon: EditIcon,\n shortcut: 'enter',\n run: () => ctx.editLabel(edge.id),\n },\n {\n key: 'delete',\n label: t.delete,\n icon: TrashIcon,\n shortcut: 'delete',\n tone: 'danger',\n divided: true,\n run: ctx.deleteSelection,\n },\n )\n return items\n },\n})\n\n/** Duplicate, delete and the node menu, over the selected nodes; delete over selected edges. */\nconst editToolbar = defineFlowExtension({\n name: 'edit-toolbar',\n priority: 0,\n edgeToolbarItems(ctx) {\n const t = ctx.messages.value\n return [\n {\n key: 'delete',\n label: t.delete,\n icon: TrashIcon,\n shortcut: 'delete',\n run: ctx.deleteSelection,\n },\n ]\n },\n toolbarItems(ctx, nodes) {\n const t = ctx.messages.value\n // the node menu, as a right click on the first selected node opens it\n const first = nodes[0]!\n const more = ctx.menuItems({ kind: 'node', node: first, point: { x: first.x, y: first.y } })\n return [\n {\n key: 'duplicate',\n label: t.duplicate,\n icon: CopyPlusIcon,\n shortcut: 'mod+d',\n run: ctx.duplicate,\n },\n {\n key: 'delete',\n label: t.delete,\n icon: TrashIcon,\n shortcut: 'delete',\n disabled: nodes.every((node) => node.locked),\n run: ctx.deleteSelection,\n },\n ...(more.length\n ? [{ key: 'more', label: t.more, icon: MoreHorizontalIcon, children: more }]\n : []),\n ]\n },\n})\n\n/** Group nodes: the type, Ctrl / ⌘ + G to group the selection, with Shift to ungroup. */\nconst groups = defineFlowExtension({\n name: 'groups',\n priority: 0,\n nodeTypes: [groupNode],\n keymap: (ctx) => ({\n 'mod+g': () => ctx.groupNodes() !== null,\n // A group goes away; a node inside one comes out of it.\n 'mod+shift+g': () => {\n const ids = ctx.selection.value.nodes\n const containers = ids.filter((id) => {\n const node = ctx.doc.value.nodes.find((n) => n.id === id)\n return node && ctx.isContainer(node)\n })\n if (containers.length) return containers.filter((id) => ctx.ungroupNode(id)).length > 0\n return ctx.leaveGroup(ids) > 0\n },\n }),\n contextMenu(ctx, target) {\n if (target.kind !== 'node' || ctx.readonly.value) return []\n const t = ctx.messages.value\n const tree = ctx.tree.value\n const parents = new Set(tree.topmost(ctx.selection.value.nodes).map((id) => tree.parentOf(id)))\n const items: ContextMenuItem[] = [\n {\n key: 'group',\n label: t.makeGroup,\n icon: GroupIcon,\n shortcut: 'mod+g',\n // Nodes from different groups can't share a new frame.\n disabled: parents.size > 1,\n run: () => void ctx.groupNodes(),\n },\n ]\n if (ctx.isContainer(target.node))\n items.push({\n key: 'ungroup',\n label: t.ungroup,\n icon: UngroupIcon,\n shortcut: 'mod+shift+g',\n run: () => void ctx.ungroupNode(target.node.id),\n })\n // A group inside another can be ungrouped or taken out; the shortcut ungroups.\n if (tree.parentOf(target.node.id))\n items.push({\n key: 'leave-group',\n label: t.leaveGroup,\n icon: UngroupIcon,\n shortcut: ctx.isContainer(target.node) ? undefined : 'mod+shift+g',\n run: () => void ctx.leaveGroup(),\n })\n return items\n },\n})\n\n/**\n * The editor's built-in parts as extensions: the default node type, the\n * `bezier`, `step` and `straight` edge types, group nodes, stacking order,\n * the context menu and the toolbar over selected nodes. Pass it along with\n * your own extensions, e.g. `[flowKit(), aiNodes]`; without `extensions`\n * the editor uses it alone.\n */\nexport function flowKit(options: FlowKitOptions = {}): FlowExtension[] {\n const { menu = true, toolbar = true, group = true, layers: layered = true } = options\n const kit = [\n defineFlowExtension({ name: 'default-node', nodeTypes: [defaultNode] }),\n defineFlowExtension({ name: 'default-edges', edgeTypes: [bezierEdge, stepEdge, straightEdge] }),\n stacking,\n ]\n if (menu) kit.push(editMenu)\n if (toolbar) kit.push(editToolbar)\n if (group) kit.push(groups)\n if (layered) kit.push(layers)\n return kit\n}\n"],"mappings":"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"}
|
package/dist/layers.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { intersects } from "./core/geometry.js";
|
|
2
|
+
import { defineFlowExtension } from "./core/extension.js";
|
|
3
|
+
import { ChevronDownIcon, ChevronUpIcon, ChevronsDownIcon, ChevronsUpIcon } from "@dou-ui/icons";
|
|
4
|
+
// src/layers.ts
|
|
5
|
+
/** The nodes that stack, bottom first: what the editor draws in that order. */
|
|
6
|
+
function stack(ctx) {
|
|
7
|
+
const nodes = ctx.doc.value.nodes.filter((node) => !ctx.isContainer(node));
|
|
8
|
+
const index = new Map(nodes.map((node, i) => [node.id, i]));
|
|
9
|
+
return nodes.sort((a, b) => (a.z ?? 0) - (b.z ?? 0) || index.get(a.id) - index.get(b.id));
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The selected nodes moved in the stack: to the top or bottom of everything,
|
|
13
|
+
* or one step past the nearest node each overlaps, so the step can be seen.
|
|
14
|
+
* Returns the `z` of every node whose place changed, numbering the stack
|
|
15
|
+
* from 0 at the bottom; nothing when nothing would change.
|
|
16
|
+
*/
|
|
17
|
+
function layerChanges(ctx, move) {
|
|
18
|
+
const selected = new Set(ctx.selection.value.nodes);
|
|
19
|
+
const order = stack(ctx);
|
|
20
|
+
const moving = order.filter((node) => selected.has(node.id));
|
|
21
|
+
const others = order.filter((node) => !selected.has(node.id));
|
|
22
|
+
let next;
|
|
23
|
+
if (move === "front") next = [...others, ...moving];
|
|
24
|
+
else if (move === "back") next = [...moving, ...others];
|
|
25
|
+
else {
|
|
26
|
+
const up = move === "forward";
|
|
27
|
+
next = [...order];
|
|
28
|
+
const rect = (node) => ctx.getNodeRect(node.id);
|
|
29
|
+
for (let k = 0; k < moving.length; k++) {
|
|
30
|
+
const node = moving[up ? moving.length - 1 - k : k];
|
|
31
|
+
const at = next.indexOf(node);
|
|
32
|
+
const box = rect(node);
|
|
33
|
+
let past;
|
|
34
|
+
for (let i = up ? at + 1 : at - 1; i >= 0 && i < next.length; i += up ? 1 : -1) {
|
|
35
|
+
const other = next[i];
|
|
36
|
+
const r = rect(other);
|
|
37
|
+
if (!selected.has(other.id) && box && r && intersects(box, r)) {
|
|
38
|
+
past = other;
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (!past) continue;
|
|
43
|
+
next.splice(at, 1);
|
|
44
|
+
next.splice(next.indexOf(past) + (up ? 1 : 0), 0, node);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const out = /* @__PURE__ */ new Map();
|
|
48
|
+
if (next.every((node, i) => node === order[i])) return out;
|
|
49
|
+
next.forEach((node, i) => {
|
|
50
|
+
if ((node.z ?? 0) !== i) out.set(node.id, i);
|
|
51
|
+
});
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Moves the selected nodes in the stack, as one undo step, and says so to
|
|
56
|
+
* screen readers. Returns `false` when nothing moved, e.g. when they already
|
|
57
|
+
* are at the top; that is said too, unless `announce` is `false`.
|
|
58
|
+
*/
|
|
59
|
+
function moveLayer(ctx, move, options = {}) {
|
|
60
|
+
const t = ctx.messages.value.layers;
|
|
61
|
+
const changes = layerChanges(ctx, move);
|
|
62
|
+
const say = (text) => options.announce !== false && ctx.announce(text);
|
|
63
|
+
if (!changes.size) {
|
|
64
|
+
if (ctx.selection.value.nodes.length) say({
|
|
65
|
+
front: t.top,
|
|
66
|
+
back: t.bottom,
|
|
67
|
+
forward: atEnd(ctx, "forward") ? t.top : t.noneAbove,
|
|
68
|
+
backward: atEnd(ctx, "backward") ? t.bottom : t.noneBelow
|
|
69
|
+
}[move]);
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
ctx.transact((tr) => {
|
|
73
|
+
for (const [id, z] of changes) tr.updateNode(id, { z });
|
|
74
|
+
});
|
|
75
|
+
say(t[move]);
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
/** Whether the selected nodes already are at the top (or bottom) of the whole stack. */
|
|
79
|
+
function atEnd(ctx, way) {
|
|
80
|
+
return !layerChanges(ctx, way === "forward" ? "front" : "back").size;
|
|
81
|
+
}
|
|
82
|
+
var LAYER_KEYS = [
|
|
83
|
+
[
|
|
84
|
+
"front",
|
|
85
|
+
"mod+shift+]",
|
|
86
|
+
ChevronsUpIcon
|
|
87
|
+
],
|
|
88
|
+
[
|
|
89
|
+
"forward",
|
|
90
|
+
"mod+]",
|
|
91
|
+
ChevronUpIcon
|
|
92
|
+
],
|
|
93
|
+
[
|
|
94
|
+
"backward",
|
|
95
|
+
"mod+[",
|
|
96
|
+
ChevronDownIcon
|
|
97
|
+
],
|
|
98
|
+
[
|
|
99
|
+
"back",
|
|
100
|
+
"mod+shift+[",
|
|
101
|
+
ChevronsDownIcon
|
|
102
|
+
]
|
|
103
|
+
];
|
|
104
|
+
/**
|
|
105
|
+
* Stacking order: what is added lands on top, also once nodes have been
|
|
106
|
+
* moved in the stack.
|
|
107
|
+
*/
|
|
108
|
+
var stacking = defineFlowExtension({
|
|
109
|
+
name: "stacking",
|
|
110
|
+
priority: 0,
|
|
111
|
+
transaction(tr) {
|
|
112
|
+
const ids = /* @__PURE__ */ new Set();
|
|
113
|
+
for (const step of tr.steps) if (step.type === "addNode" && step.node.z === void 0) ids.add(step.node.id);
|
|
114
|
+
if (!ids.size) return;
|
|
115
|
+
let top = 0;
|
|
116
|
+
for (const node of tr.doc.nodes) if (!ids.has(node.id)) top = Math.max(top, node.z ?? 0);
|
|
117
|
+
if (!top) return;
|
|
118
|
+
for (const id of ids) tr.updateNode(id, { z: ++top });
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
/** Moving nodes up and down the stack from the node menu and with Ctrl / ⌘ + [ and ]. */
|
|
122
|
+
var layers = defineFlowExtension({
|
|
123
|
+
name: "layers",
|
|
124
|
+
priority: 0,
|
|
125
|
+
contextMenu(ctx, target) {
|
|
126
|
+
if (target.kind !== "node" || ctx.readonly.value) return [];
|
|
127
|
+
const t = ctx.messages.value.layers;
|
|
128
|
+
return LAYER_KEYS.map(([move, shortcut, icon], i) => ({
|
|
129
|
+
key: `layer-${move}`,
|
|
130
|
+
label: t[move],
|
|
131
|
+
icon,
|
|
132
|
+
shortcut,
|
|
133
|
+
divided: i === 0,
|
|
134
|
+
disabled: !layerChanges(ctx, move).size,
|
|
135
|
+
run: () => void moveLayer(ctx, move)
|
|
136
|
+
}));
|
|
137
|
+
},
|
|
138
|
+
keymap(ctx) {
|
|
139
|
+
const keys = {};
|
|
140
|
+
const order = [...LAYER_KEYS.filter(([, key]) => key.includes("shift")), ...LAYER_KEYS];
|
|
141
|
+
for (const [move, key] of order) if (!keys[key]) keys[key] = () => {
|
|
142
|
+
if (ctx.readonly.value || !ctx.selection.value.nodes.length) return false;
|
|
143
|
+
moveLayer(ctx, move);
|
|
144
|
+
};
|
|
145
|
+
return keys;
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
export { layerChanges, layers, moveLayer, stacking };
|
|
149
|
+
|
|
150
|
+
//# sourceMappingURL=layers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layers.js","names":[],"sources":["../src/layers.ts"],"sourcesContent":["import type { ContextMenuItem } from 'dou-ui'\nimport { ChevronDownIcon, ChevronsDownIcon, ChevronsUpIcon, ChevronUpIcon } from '@dou-ui/icons'\nimport type { FlowEditorContext } from './context'\nimport { defineFlowExtension } from './core/extension'\nimport { intersects } from './core/geometry'\nimport type { FlowNode } from './core/model'\n\nexport type LayerMove = 'front' | 'forward' | 'backward' | 'back'\n\n/** The nodes that stack, bottom first: what the editor draws in that order. */\nfunction stack(ctx: FlowEditorContext): FlowNode[] {\n const nodes = ctx.doc.value.nodes.filter((node) => !ctx.isContainer(node))\n const index = new Map(nodes.map((node, i) => [node.id, i]))\n // oxlint-disable-next-line unicorn/no-array-sort -- sorts a fresh array\n return nodes.sort((a, b) => (a.z ?? 0) - (b.z ?? 0) || index.get(a.id)! - index.get(b.id)!)\n}\n\n/**\n * The selected nodes moved in the stack: to the top or bottom of everything,\n * or one step past the nearest node each overlaps, so the step can be seen.\n * Returns the `z` of every node whose place changed, numbering the stack\n * from 0 at the bottom; nothing when nothing would change.\n */\nexport function layerChanges(ctx: FlowEditorContext, move: LayerMove): Map<string, number> {\n const selected = new Set(ctx.selection.value.nodes)\n const order = stack(ctx)\n const moving = order.filter((node) => selected.has(node.id))\n const others = order.filter((node) => !selected.has(node.id))\n let next: FlowNode[]\n if (move === 'front') next = [...others, ...moving]\n else if (move === 'back') next = [...moving, ...others]\n else {\n const up = move === 'forward'\n next = [...order]\n const rect = (node: FlowNode) => ctx.getNodeRect(node.id)\n // from the end it moves towards, so the moved nodes don't pass each other\n for (let k = 0; k < moving.length; k++) {\n const node = moving[up ? moving.length - 1 - k : k]!\n const at = next.indexOf(node)\n const box = rect(node)\n // the nearest unselected node it overlaps, walking the way it moves\n let past: FlowNode | undefined\n for (let i = up ? at + 1 : at - 1; i >= 0 && i < next.length; i += up ? 1 : -1) {\n const other = next[i]!\n const r = rect(other)\n if (!selected.has(other.id) && box && r && intersects(box, r)) {\n past = other\n break\n }\n }\n if (!past) continue\n next.splice(at, 1)\n next.splice(next.indexOf(past) + (up ? 1 : 0), 0, node)\n }\n }\n const out = new Map<string, number>()\n if (next.every((node, i) => node === order[i])) return out\n next.forEach((node, i) => {\n if ((node.z ?? 0) !== i) out.set(node.id, i)\n })\n return out\n}\n\n/**\n * Moves the selected nodes in the stack, as one undo step, and says so to\n * screen readers. Returns `false` when nothing moved, e.g. when they already\n * are at the top; that is said too, unless `announce` is `false`.\n */\nexport function moveLayer(\n ctx: FlowEditorContext,\n move: LayerMove,\n options: { announce?: boolean } = {},\n): boolean {\n const t = ctx.messages.value.layers\n const changes = layerChanges(ctx, move)\n const say = (text: string) => options.announce !== false && ctx.announce(text)\n if (!changes.size) {\n if (ctx.selection.value.nodes.length)\n say(\n {\n front: t.top,\n back: t.bottom,\n forward: atEnd(ctx, 'forward') ? t.top : t.noneAbove,\n backward: atEnd(ctx, 'backward') ? t.bottom : t.noneBelow,\n }[move],\n )\n return false\n }\n ctx.transact((tr) => {\n for (const [id, z] of changes) tr.updateNode(id, { z })\n })\n say(t[move])\n return true\n}\n\n/** Whether the selected nodes already are at the top (or bottom) of the whole stack. */\nfunction atEnd(ctx: FlowEditorContext, way: 'forward' | 'backward') {\n return !layerChanges(ctx, way === 'forward' ? 'front' : 'back').size\n}\n\nconst LAYER_KEYS: [LayerMove, string, typeof ChevronsUpIcon][] = [\n ['front', 'mod+shift+]', ChevronsUpIcon],\n ['forward', 'mod+]', ChevronUpIcon],\n ['backward', 'mod+[', ChevronDownIcon],\n ['back', 'mod+shift+[', ChevronsDownIcon],\n]\n\n/**\n * Stacking order: what is added lands on top, also once nodes have been\n * moved in the stack.\n */\nexport const stacking = defineFlowExtension({\n name: 'stacking',\n priority: 0,\n transaction(tr) {\n const ids = new Set<string>()\n for (const step of tr.steps)\n if (step.type === 'addNode' && step.node.z === undefined) ids.add(step.node.id)\n if (!ids.size) return\n let top = 0\n for (const node of tr.doc.nodes) if (!ids.has(node.id)) top = Math.max(top, node.z ?? 0)\n if (!top) return\n for (const id of ids) tr.updateNode(id, { z: ++top })\n },\n})\n\n/** Moving nodes up and down the stack from the node menu and with Ctrl / ⌘ + [ and ]. */\nexport const layers = defineFlowExtension({\n name: 'layers',\n priority: 0,\n contextMenu(ctx, target) {\n if (target.kind !== 'node' || ctx.readonly.value) return []\n const t = ctx.messages.value.layers\n return LAYER_KEYS.map(([move, shortcut, icon], i): ContextMenuItem => ({\n key: `layer-${move}`,\n label: t[move],\n icon,\n shortcut,\n divided: i === 0,\n disabled: !layerChanges(ctx, move).size,\n run: () => void moveLayer(ctx, move),\n }))\n },\n keymap(ctx) {\n const keys: Record<string, () => boolean | void> = {}\n // with Shift first: without it, `mod+[` would match either\n const order = [...LAYER_KEYS.filter(([, key]) => key.includes('shift')), ...LAYER_KEYS]\n for (const [move, key] of order)\n if (!keys[key])\n keys[key] = () => {\n if (ctx.readonly.value || !ctx.selection.value.nodes.length) return false\n moveLayer(ctx, move)\n }\n return keys\n },\n})\n"],"mappings":"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"}
|