@dou-ui/flow 0.0.0-stage → 0.1.0-alpha.0

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.
Files changed (173) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -2
  3. package/dist/collaboration/index.js +360 -0
  4. package/dist/collaboration/index.js.map +1 -0
  5. package/dist/collaboration/peers.js +76 -0
  6. package/dist/collaboration/peers.js.map +1 -0
  7. package/dist/components/default-node.js +6 -0
  8. package/dist/components/default-node.js.map +1 -0
  9. package/dist/components/default-node.vue_vue_type_script_setup_true_lang.js +87 -0
  10. package/dist/components/default-node.vue_vue_type_script_setup_true_lang.js.map +1 -0
  11. package/dist/components/default-node2.js +116 -0
  12. package/dist/components/default-node2.js.map +1 -0
  13. package/dist/components/flow-controls.js +6 -0
  14. package/dist/components/flow-controls.js.map +1 -0
  15. package/dist/components/flow-controls.vue_vue_type_script_setup_true_lang.js +148 -0
  16. package/dist/components/flow-controls.vue_vue_type_script_setup_true_lang.js.map +1 -0
  17. package/dist/components/flow-menu.js +6 -0
  18. package/dist/components/flow-menu.js.map +1 -0
  19. package/dist/components/flow-menu.vue_vue_type_script_setup_true_lang.js +52 -0
  20. package/dist/components/flow-menu.vue_vue_type_script_setup_true_lang.js.map +1 -0
  21. package/dist/components/flow-minimap.js +6 -0
  22. package/dist/components/flow-minimap.js.map +1 -0
  23. package/dist/components/flow-minimap.vue_vue_type_script_setup_true_lang.js +238 -0
  24. package/dist/components/flow-minimap.vue_vue_type_script_setup_true_lang.js.map +1 -0
  25. package/dist/components/flow-port.js +6 -0
  26. package/dist/components/flow-port.js.map +1 -0
  27. package/dist/components/flow-port.vue_vue_type_script_setup_true_lang.js +57 -0
  28. package/dist/components/flow-port.vue_vue_type_script_setup_true_lang.js.map +1 -0
  29. package/dist/components/flow-quick-add.js +6 -0
  30. package/dist/components/flow-quick-add.js.map +1 -0
  31. package/dist/components/flow-quick-add.vue_vue_type_script_setup_true_lang.js +126 -0
  32. package/dist/components/flow-quick-add.vue_vue_type_script_setup_true_lang.js.map +1 -0
  33. package/dist/components/flow-toolbar.js +6 -0
  34. package/dist/components/flow-toolbar.js.map +1 -0
  35. package/dist/components/flow-toolbar.vue_vue_type_script_setup_true_lang.js +161 -0
  36. package/dist/components/flow-toolbar.vue_vue_type_script_setup_true_lang.js.map +1 -0
  37. package/dist/components/group-node.js +6 -0
  38. package/dist/components/group-node.js.map +1 -0
  39. package/dist/components/group-node.vue_vue_type_script_setup_true_lang.js +84 -0
  40. package/dist/components/group-node.vue_vue_type_script_setup_true_lang.js.map +1 -0
  41. package/dist/components/group-node2.js +42 -0
  42. package/dist/components/group-node2.js.map +1 -0
  43. package/dist/components/node-view.js +6 -0
  44. package/dist/components/node-view.js.map +1 -0
  45. package/dist/components/node-view.vue_vue_type_script_setup_true_lang.js +179 -0
  46. package/dist/components/node-view.vue_vue_type_script_setup_true_lang.js.map +1 -0
  47. package/dist/context.js +13 -0
  48. package/dist/context.js.map +1 -0
  49. package/dist/core/clipboard.js +37 -0
  50. package/dist/core/clipboard.js.map +1 -0
  51. package/dist/core/connect.js +74 -0
  52. package/dist/core/connect.js.map +1 -0
  53. package/dist/core/edge-types.js +240 -0
  54. package/dist/core/edge-types.js.map +1 -0
  55. package/dist/core/extension.js +63 -0
  56. package/dist/core/extension.js.map +1 -0
  57. package/dist/core/geometry.js +232 -0
  58. package/dist/core/geometry.js.map +1 -0
  59. package/dist/core/groups.js +76 -0
  60. package/dist/core/groups.js.map +1 -0
  61. package/dist/core/history.js +79 -0
  62. package/dist/core/history.js.map +1 -0
  63. package/dist/core/model.js +21 -0
  64. package/dist/core/model.js.map +1 -0
  65. package/dist/core/node-types.js +32 -0
  66. package/dist/core/node-types.js.map +1 -0
  67. package/dist/core/order.js +42 -0
  68. package/dist/core/order.js.map +1 -0
  69. package/dist/core/transaction.js +288 -0
  70. package/dist/core/transaction.js.map +1 -0
  71. package/dist/document.js +220 -0
  72. package/dist/document.js.map +1 -0
  73. package/dist/export/color.js +38 -0
  74. package/dist/export/color.js.map +1 -0
  75. package/dist/export/context.js +223 -0
  76. package/dist/export/context.js.map +1 -0
  77. package/dist/export/draw.js +249 -0
  78. package/dist/export/draw.js.map +1 -0
  79. package/dist/flow-editor.js +6 -0
  80. package/dist/flow-editor.js.map +1 -0
  81. package/dist/flow-editor.vue_vue_type_script_setup_true_lang.js +3721 -0
  82. package/dist/flow-editor.vue_vue_type_script_setup_true_lang.js.map +1 -0
  83. package/dist/index.js +31 -0
  84. package/dist/index.js.map +1 -0
  85. package/dist/kit.js +234 -0
  86. package/dist/kit.js.map +1 -0
  87. package/dist/layers.js +150 -0
  88. package/dist/layers.js.map +1 -0
  89. package/dist/locale.js +426 -0
  90. package/dist/locale.js.map +1 -0
  91. package/dist/style.css +1 -0
  92. package/dist/style.d.ts +1 -0
  93. package/dist/style.js +8 -0
  94. package/dist/types/collaboration/index.d.ts +42 -0
  95. package/dist/types/collaboration/peers.d.ts +27 -0
  96. package/dist/types/components/default-node.d.ts +18 -0
  97. package/dist/types/components/default-node.vue.d.ts +6 -0
  98. package/dist/types/components/flow-controls.vue.d.ts +8 -0
  99. package/dist/types/components/flow-menu.vue.d.ts +29 -0
  100. package/dist/types/components/flow-minimap.vue.d.ts +8 -0
  101. package/dist/types/components/flow-port.vue.d.ts +9 -0
  102. package/dist/types/components/flow-quick-add.vue.d.ts +31 -0
  103. package/dist/types/components/flow-toolbar.vue.d.ts +25 -0
  104. package/dist/types/components/group-node.d.ts +13 -0
  105. package/dist/types/components/group-node.vue.d.ts +6 -0
  106. package/dist/types/components/node-view.vue.d.ts +28 -0
  107. package/dist/types/context.d.ts +362 -0
  108. package/dist/types/core/clipboard.d.ts +9 -0
  109. package/dist/types/core/connect.d.ts +63 -0
  110. package/dist/types/core/edge-types.d.ts +50 -0
  111. package/dist/types/core/extension.d.ts +141 -0
  112. package/dist/types/core/geometry.d.ts +63 -0
  113. package/dist/types/core/groups.d.ts +23 -0
  114. package/dist/types/core/history.d.ts +24 -0
  115. package/dist/types/core/menu.d.ts +40 -0
  116. package/dist/types/core/model.d.ts +87 -0
  117. package/dist/types/core/node-types.d.ts +103 -0
  118. package/dist/types/core/order.d.ts +7 -0
  119. package/dist/types/core/transaction.d.ts +97 -0
  120. package/dist/types/document.d.ts +57 -0
  121. package/dist/types/export/color.d.ts +7 -0
  122. package/dist/types/export/context.d.ts +90 -0
  123. package/dist/types/export/draw.d.ts +72 -0
  124. package/dist/types/flow-editor.vue.d.ts +63 -0
  125. package/dist/types/index.d.ts +202 -0
  126. package/dist/types/kit.d.ts +22 -0
  127. package/dist/types/layers.d.ts +24 -0
  128. package/dist/types/locale.d.ts +172 -0
  129. package/dist/types/whiteboard/board-draft.vue.d.ts +3 -0
  130. package/dist/types/whiteboard/board-node.vue.d.ts +19 -0
  131. package/dist/types/whiteboard/connector.d.ts +12 -0
  132. package/dist/types/whiteboard/draw.d.ts +5 -0
  133. package/dist/types/whiteboard/image.d.ts +27 -0
  134. package/dist/types/whiteboard/index.d.ts +6 -0
  135. package/dist/types/whiteboard/ink.d.ts +30 -0
  136. package/dist/types/whiteboard/kit.d.ts +36 -0
  137. package/dist/types/whiteboard/model.d.ts +79 -0
  138. package/dist/types/whiteboard/pen.d.ts +7 -0
  139. package/dist/types/whiteboard/state.d.ts +37 -0
  140. package/dist/types/whiteboard/transform.d.ts +67 -0
  141. package/dist/types/whiteboard/whiteboard-toolbar.vue.d.ts +3 -0
  142. package/dist/whiteboard/board-draft.js +6 -0
  143. package/dist/whiteboard/board-draft.js.map +1 -0
  144. package/dist/whiteboard/board-draft.vue_vue_type_script_setup_true_lang.js +58 -0
  145. package/dist/whiteboard/board-draft.vue_vue_type_script_setup_true_lang.js.map +1 -0
  146. package/dist/whiteboard/board-node.js +6 -0
  147. package/dist/whiteboard/board-node.js.map +1 -0
  148. package/dist/whiteboard/board-node.vue_vue_type_script_setup_true_lang.js +332 -0
  149. package/dist/whiteboard/board-node.vue_vue_type_script_setup_true_lang.js.map +1 -0
  150. package/dist/whiteboard/connector.js +180 -0
  151. package/dist/whiteboard/connector.js.map +1 -0
  152. package/dist/whiteboard/draw.js +68 -0
  153. package/dist/whiteboard/draw.js.map +1 -0
  154. package/dist/whiteboard/image.js +104 -0
  155. package/dist/whiteboard/image.js.map +1 -0
  156. package/dist/whiteboard/index.js +5 -0
  157. package/dist/whiteboard/ink.js +168 -0
  158. package/dist/whiteboard/ink.js.map +1 -0
  159. package/dist/whiteboard/kit.js +406 -0
  160. package/dist/whiteboard/kit.js.map +1 -0
  161. package/dist/whiteboard/model.js +52 -0
  162. package/dist/whiteboard/model.js.map +1 -0
  163. package/dist/whiteboard/pen.js +88 -0
  164. package/dist/whiteboard/pen.js.map +1 -0
  165. package/dist/whiteboard/state.js +26 -0
  166. package/dist/whiteboard/state.js.map +1 -0
  167. package/dist/whiteboard/transform.js +256 -0
  168. package/dist/whiteboard/transform.js.map +1 -0
  169. package/dist/whiteboard/whiteboard-toolbar.js +6 -0
  170. package/dist/whiteboard/whiteboard-toolbar.js.map +1 -0
  171. package/dist/whiteboard/whiteboard-toolbar.vue_vue_type_script_setup_true_lang.js +171 -0
  172. package/dist/whiteboard/whiteboard-toolbar.vue_vue_type_script_setup_true_lang.js.map +1 -0
  173. package/package.json +74 -4
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 daidr
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,11 @@
1
- # Temporary Holding Version
1
+ # @dou-ui/flow
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [DouUI](https://github.com/daidr/dou-ui) 的流程编辑器与白板:在无限画布上拖动节点、连接端口,支持分组、导出图片与协同编辑。
4
+
5
+ ```sh
6
+ bun add @dou-ui/flow dou-ui
7
+ ```
8
+
9
+ 用法见 [文档](https://ui.daidr.me/flow/)。
10
+
11
+ 许可证:MIT
@@ -0,0 +1,360 @@
1
+ import { defineFlowExtension } from "../core/extension.js";
2
+ import { createPeers } from "./peers.js";
3
+ import { collaborationColors } from "dou-ui";
4
+ import { toRaw, watch } from "vue";
5
+ import * as Y from "yjs";
6
+ // src/collaboration/index.ts
7
+ /** Whether a group is folded is each person's own view: it stays out of the shared document. */
8
+ var LOCAL_DATA = "collapsed";
9
+ var same = (a, b) => a === b || typeof a === "object" && JSON.stringify(a) === JSON.stringify(b);
10
+ /** `data` as it is shared: without the local fields. */
11
+ function sharedData(data) {
12
+ if (!data || typeof data !== "object" || !(LOCAL_DATA in data)) return data;
13
+ const { [LOCAL_DATA]: _, ...rest } = data;
14
+ return rest;
15
+ }
16
+ /** A node or edge as the map holds it, or `null` when it holds none. */
17
+ function read(map, id) {
18
+ const fields = map.get(id);
19
+ if (!(fields instanceof Y.Map)) return null;
20
+ return {
21
+ ...fields.toJSON(),
22
+ id
23
+ };
24
+ }
25
+ /** Where in `value` something JSON can't carry sits, e.g. a component or a function. */
26
+ function unshareable(value, path) {
27
+ if (value === null || [
28
+ "string",
29
+ "number",
30
+ "boolean",
31
+ "undefined"
32
+ ].includes(typeof value)) return null;
33
+ if (typeof value !== "object") return path;
34
+ const proto = Object.getPrototypeOf(value);
35
+ if (Array.isArray(value)) {
36
+ for (const [i, item] of value.entries()) {
37
+ const found = unshareable(item, `${path}[${i}]`);
38
+ if (found) return found;
39
+ }
40
+ return null;
41
+ }
42
+ if (proto !== Object.prototype && proto !== null) return path;
43
+ for (const [key, item] of Object.entries(value)) {
44
+ const found = unshareable(item, `${path}.${key}`);
45
+ if (found) return found;
46
+ }
47
+ return null;
48
+ }
49
+ var warned = /* @__PURE__ */ new Set();
50
+ /** Others would get a broken copy of such a value while this editor shows it fine. */
51
+ function warnUnshareable(id, key, value) {
52
+ if (process.env.NODE_ENV === "production") return;
53
+ const path = unshareable(value, key);
54
+ if (!path || warned.has(`${id}:${path}`)) return;
55
+ warned.add(`${id}:${path}`);
56
+ console.warn(`[@dou-ui/flow] "${id}" has ${path}, which can't be shared: only JSON values reach the others. Store a name and look the component or function up where it is drawn.`);
57
+ }
58
+ var warnedDataUrl = false;
59
+ /** A data URL goes into the shared document whole, to everyone, every time it loads. */
60
+ function warnDataUrl(data) {
61
+ if (process.env.NODE_ENV === "production" || warnedDataUrl || !data || typeof data !== "object") return;
62
+ for (const value of Object.values(data)) if (typeof value === "string" && value.length > 1e5 && value.startsWith("data:")) {
63
+ warnedDataUrl = true;
64
+ console.warn(`[@dou-ui/flow] a node keeps a data URL of ${Math.round(value.length / 1024)} KB, which every collaborator downloads with the document. Upload such files and keep their address instead, e.g. whiteboardKit({ upload }).`);
65
+ return;
66
+ }
67
+ }
68
+ /** Writes a node's or edge's fields, only those that changed; `keys` limits which. */
69
+ function write(map, value, keys) {
70
+ const item = value;
71
+ const existing = map.get(value.id);
72
+ const fields = existing instanceof Y.Map ? existing : new Y.Map();
73
+ const fresh = existing !== fields;
74
+ for (const [key, raw] of Object.entries(item)) {
75
+ if (key === "id" || raw === void 0 || keys && !fresh && !keys.has(key)) continue;
76
+ const field = key === "data" ? sharedData(raw) : raw;
77
+ warnUnshareable(value.id, key, field);
78
+ if (key === "data") warnDataUrl(field);
79
+ if (fresh || !same(fields.get(key), field)) fields.set(key, field);
80
+ }
81
+ if (fresh) return void map.set(value.id, fields);
82
+ for (const key of Array.from(fields.keys())) if (item[key] === void 0 && (!keys || keys.has(key))) fields.delete(key);
83
+ }
84
+ /** The change that makes `local` match `shared`, leaving out `skip` and the local fields. */
85
+ function patchFor(local, shared, skip) {
86
+ const a = local;
87
+ const b = shared;
88
+ const patch = {};
89
+ for (const key of /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)])) {
90
+ if (key === "id" || skip?.has(key)) continue;
91
+ if (key === "data") {
92
+ if (same(sharedData(a.data), b.data)) continue;
93
+ const mine = a.data?.[LOCAL_DATA];
94
+ patch.data = mine === void 0 ? b.data : {
95
+ ...b.data,
96
+ [LOCAL_DATA]: mine
97
+ };
98
+ } else if (!same(a[key], b[key])) patch[key] = b[key];
99
+ }
100
+ return Object.keys(patch).length ? patch : null;
101
+ }
102
+ /**
103
+ * Writes nodes and edges into empty maps, e.g. when a flow that so far was
104
+ * saved as JSON starts being edited together. Do it once, where the shared
105
+ * document is created.
106
+ */
107
+ function fillMaps(maps, doc) {
108
+ const fill = () => {
109
+ for (const node of doc.nodes) write(maps.nodes, node);
110
+ for (const edge of doc.edges) write(maps.edges, edge);
111
+ };
112
+ const ydoc = maps.nodes.doc;
113
+ if (ydoc) ydoc.transact(fill);
114
+ else fill();
115
+ }
116
+ /** The fields `touched` holds for an item, when it holds only some. */
117
+ function heldKeys(touched, id) {
118
+ const keys = touched.get(id);
119
+ return keys === true ? void 0 : keys;
120
+ }
121
+ function mark(into, id, keys) {
122
+ const current = into.get(id);
123
+ if (current === true) return;
124
+ if (keys === true) return void into.set(id, true);
125
+ const set = current ?? /* @__PURE__ */ new Set();
126
+ for (const key of keys) set.add(key);
127
+ into.set(id, set);
128
+ }
129
+ /** The nodes and edges a transaction added, removed or changed, with the fields it changed. */
130
+ function touch(steps, into) {
131
+ for (const step of steps) if (step.type === "addNode") mark(into.nodes, step.node.id, true);
132
+ else if (step.type === "removeNode") mark(into.nodes, step.id, true);
133
+ else if (step.type === "updateNode") mark(into.nodes, step.id, Object.keys(step.patch));
134
+ else if (step.type === "addEdge") mark(into.edges, step.edge.id, true);
135
+ else if (step.type === "removeEdge") mark(into.edges, step.id, true);
136
+ else mark(into.edges, step.id, Object.keys(step.patch));
137
+ }
138
+ /**
139
+ * Edit a flow together through a Yjs document. Nodes and edges live in two
140
+ * `Y.Map`s, each field on its own, so two people changing different fields
141
+ * of one node both keep their change. The editor's `v-model` only mirrors
142
+ * them: changes made to `v-model` from outside are not written back. While
143
+ * someone drags, the others see an outline; the document changes once they
144
+ * let go. Undo only takes back this user's own changes. Folding a group
145
+ * stays local.
146
+ */
147
+ function collaboration(options) {
148
+ const { nodes, edges } = options;
149
+ const ydoc = nodes.doc;
150
+ if (!ydoc || edges.doc !== ydoc) throw new Error("[@dou-ui/flow] nodes and edges must be maps of the same Y.Doc");
151
+ const { awareness, user } = options;
152
+ if (awareness && user) {
153
+ const color = user.color ?? collaborationColors[ydoc.clientID % collaborationColors.length];
154
+ awareness.setLocalStateField("user", {
155
+ name: user.name,
156
+ color
157
+ });
158
+ }
159
+ /** This editor's own changes, which undo can take back. */
160
+ const tracked = { flow: "tracked" };
161
+ /** Changes that undo leaves alone, e.g. filling the maps or clearing dangling edges. */
162
+ const untracked = { flow: "untracked" };
163
+ const undoManager = new Y.UndoManager([nodes, edges], {
164
+ trackedOrigins: /* @__PURE__ */ new Set([tracked]),
165
+ captureTimeout: 500
166
+ });
167
+ /** Set while shared changes are applied to the editor, so they aren't written back. */
168
+ let applying = false;
169
+ /** What undo or redo is running, to name the change. */
170
+ let travelling = null;
171
+ let lastGroup;
172
+ /** What changed during the current drag, written once it ends. */
173
+ const held = {
174
+ nodes: /* @__PURE__ */ new Map(),
175
+ edges: /* @__PURE__ */ new Map(),
176
+ tracking: false
177
+ };
178
+ function writeChanges(doc, changed, tracking) {
179
+ const nodeById = new Map(doc.nodes.map((node) => [node.id, node]));
180
+ const edgeById = new Map(doc.edges.map((edge) => [edge.id, edge]));
181
+ ydoc.transact(() => {
182
+ for (const [id, keys] of changed.nodes) {
183
+ const node = nodeById.get(id);
184
+ if (node) write(nodes, node, keys === true ? void 0 : keys);
185
+ else nodes.delete(id);
186
+ }
187
+ for (const [id, keys] of changed.edges) {
188
+ const edge = edgeById.get(id);
189
+ if (edge) write(edges, edge, keys === true ? void 0 : keys);
190
+ else edges.delete(id);
191
+ }
192
+ }, tracking ? tracked : untracked);
193
+ }
194
+ /** Writes what a drag changed, as one undo step. */
195
+ function release(context) {
196
+ if (!held.nodes.size && !held.edges.size) return;
197
+ if (held.tracking) undoManager.stopCapturing();
198
+ writeChanges(toRaw(context.doc.value), held, held.tracking);
199
+ if (held.tracking) undoManager.stopCapturing();
200
+ lastGroup = void 0;
201
+ held.nodes.clear();
202
+ held.edges.clear();
203
+ held.tracking = false;
204
+ }
205
+ /**
206
+ * Adds steps to `tr` that bring the given nodes and edges in line with the
207
+ * maps; returns edges left without a node, to clear from the maps.
208
+ */
209
+ function pull(tr, nodeIds, edgeIds) {
210
+ for (const id of nodeIds) {
211
+ const shared = read(nodes, id);
212
+ const local = tr.getNode(id);
213
+ if (held.nodes.get(id) === true) continue;
214
+ if (!shared) {
215
+ if (local) tr.removeNode(id);
216
+ } else if (!local) tr.addNode(shared);
217
+ else {
218
+ const patch = patchFor(local, shared, heldKeys(held.nodes, id));
219
+ if (patch) tr.updateNode(id, patch);
220
+ }
221
+ }
222
+ for (const id of edgeIds) {
223
+ const shared = read(edges, id);
224
+ const local = tr.getEdge(id);
225
+ if (held.edges.get(id) === true) continue;
226
+ if (!shared) {
227
+ if (local) tr.removeEdge(id);
228
+ } else if (!local) tr.addEdge(shared);
229
+ else {
230
+ const patch = patchFor(local, shared, heldKeys(held.edges, id));
231
+ if (patch) tr.updateEdge(id, patch);
232
+ }
233
+ }
234
+ const dangling = [];
235
+ for (const edge of tr.doc.edges) if (!tr.getNode(edge.source.node) || !tr.getNode(edge.target.node)) dangling.push(edge.id);
236
+ for (const id of dangling) tr.removeEdge(id);
237
+ return dangling;
238
+ }
239
+ return defineFlowExtension({
240
+ name: "collaboration",
241
+ priority: 0,
242
+ transaction(tr, context) {
243
+ if (applying) return;
244
+ const tracking = tr.meta.history !== false;
245
+ if (context.gesturing.value) {
246
+ touch(tr.steps, held);
247
+ held.tracking ||= tracking;
248
+ return;
249
+ }
250
+ const changed = {
251
+ nodes: /* @__PURE__ */ new Map(),
252
+ edges: /* @__PURE__ */ new Map()
253
+ };
254
+ touch(tr.steps, changed);
255
+ if (tracking && (!tr.meta.group || tr.meta.group !== lastGroup)) undoManager.stopCapturing();
256
+ if (tracking) lastGroup = tr.meta.group;
257
+ writeChanges(tr.doc, changed, tracking);
258
+ },
259
+ history: () => ({
260
+ undo: () => run("undo"),
261
+ redo: () => run("redo"),
262
+ canUndo: () => undoManager.undoStack.length > 0,
263
+ canRedo: () => undoManager.redoStack.length > 0
264
+ }),
265
+ layers: awareness ? { above: [createPeers(awareness)] } : void 0,
266
+ setup(context) {
267
+ const apply = (fn, origin) => {
268
+ let dangling = [];
269
+ applying = true;
270
+ try {
271
+ context.transact((tr) => dangling = fn(tr), {
272
+ history: false,
273
+ origin
274
+ });
275
+ } finally {
276
+ applying = false;
277
+ }
278
+ if (dangling.length) queueMicrotask(() => ydoc.transact(() => {
279
+ for (const id of dangling) edges.delete(id);
280
+ }, untracked));
281
+ };
282
+ const current = () => toRaw(context.doc.value);
283
+ if (!nodes.size && !edges.size) ydoc.transact(() => fillMaps({
284
+ nodes,
285
+ edges
286
+ }, current()), untracked);
287
+ const doc = current();
288
+ apply((tr) => pull(tr, /* @__PURE__ */ new Set([...doc.nodes.map((node) => node.id), ...nodes.keys()]), /* @__PURE__ */ new Set([...doc.edges.map((edge) => edge.id), ...edges.keys()])), "remote");
289
+ const pending = {
290
+ nodes: /* @__PURE__ */ new Set(),
291
+ edges: /* @__PURE__ */ new Set()
292
+ };
293
+ const gather = (map, into) => (events, transaction) => {
294
+ if (transaction.origin === tracked || transaction.origin === untracked) return;
295
+ for (const event of events) if (event.target === map) for (const key of event.changes.keys.keys()) into.add(key);
296
+ else if (event.path.length) into.add(String(event.path[0]));
297
+ };
298
+ const onNodes = gather(nodes, pending.nodes);
299
+ const onEdges = gather(edges, pending.edges);
300
+ const flush = () => {
301
+ if (!pending.nodes.size && !pending.edges.size) return;
302
+ const nodeIds = [...pending.nodes];
303
+ const edgeIds = [...pending.edges];
304
+ pending.nodes.clear();
305
+ pending.edges.clear();
306
+ apply((tr) => pull(tr, nodeIds, edgeIds), travelling ?? "remote");
307
+ };
308
+ nodes.observeDeep(onNodes);
309
+ edges.observeDeep(onEdges);
310
+ ydoc.on("afterTransaction", flush);
311
+ const stopRelease = watch(context.gesturing, (on) => !on && release(context), { flush: "sync" });
312
+ let stopPresence;
313
+ if (awareness) stopPresence = watch([
314
+ context.gesturing,
315
+ context.doc,
316
+ context.selection,
317
+ context.connectingPath
318
+ ], ([on, , selection, path]) => {
319
+ const moving = [];
320
+ if (on) for (const id of held.nodes.keys()) {
321
+ if (moving.length >= 100) break;
322
+ const rect = context.getNodeRect(id);
323
+ if (rect) moving.push({
324
+ id,
325
+ ...rect
326
+ });
327
+ }
328
+ awareness.setLocalStateField("flow", {
329
+ moving,
330
+ selection: {
331
+ nodes: selection.nodes.slice(0, 100),
332
+ edges: selection.edges.slice(0, 100)
333
+ },
334
+ path
335
+ });
336
+ }, { immediate: true });
337
+ return () => {
338
+ nodes.unobserveDeep(onNodes);
339
+ edges.unobserveDeep(onEdges);
340
+ ydoc.off("afterTransaction", flush);
341
+ stopRelease();
342
+ stopPresence?.();
343
+ awareness?.setLocalStateField("flow", null);
344
+ };
345
+ }
346
+ });
347
+ function run(action) {
348
+ if (!(action === "undo" ? undoManager.undoStack : undoManager.redoStack).length) return false;
349
+ travelling = action;
350
+ try {
351
+ undoManager[action]();
352
+ } finally {
353
+ travelling = null;
354
+ }
355
+ return true;
356
+ }
357
+ }
358
+ export { collaboration, collaborationColors, fillMaps };
359
+
360
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/collaboration/index.ts"],"sourcesContent":["import { collaborationColors } from 'dou-ui'\nimport { toRaw, watch } from 'vue'\nimport * as Y from 'yjs'\nimport type { FlowEditorContext } from '../context'\nimport { defineFlowExtension, type FlowExtension } from '../core/extension'\nimport type { FlowDocument, FlowEdge, FlowNode } from '../core/model'\nimport type { FlowStep, FlowTransaction } from '../core/transaction'\nimport { createPeers, type AwarenessLike, type FlowPresence } from './peers'\n\n/** How the people editing together see each other. */\nexport interface CollaborationUser {\n name: string\n /** The color of this user's outlines, any CSS color. Picked from the client id when left out. */\n color?: string\n}\n\nexport interface CollaborationOptions {\n /** Where the nodes live, e.g. `ydoc.getMap('nodes')`. */\n nodes: Y.Map<unknown>\n /** Where the edges live, e.g. `ydoc.getMap('edges')`; in the same `Y.Doc` as `nodes`. */\n edges: Y.Map<unknown>\n /** Shares what each person selects, drags and connects (`y-protocols/awareness`). */\n awareness?: AwarenessLike\n /** This user, shown to the others. Needs `awareness`. */\n user?: CollaborationUser\n}\n\nexport { collaborationColors }\n\ntype Shared = Y.Map<unknown>\ntype Item = Record<string, unknown>\n/** The fields of one node or edge a change touched; `true` for all of them. */\ntype Touched = Map<string, Set<string> | true>\n\n/** Whether a group is folded is each person's own view: it stays out of the shared document. */\nconst LOCAL_DATA = 'collapsed'\n\nconst same = (a: unknown, b: unknown) =>\n a === b || (typeof a === 'object' && JSON.stringify(a) === JSON.stringify(b))\n\n/** `data` as it is shared: without the local fields. */\nfunction sharedData(data: unknown) {\n if (!data || typeof data !== 'object' || !(LOCAL_DATA in data)) return data\n const { [LOCAL_DATA]: _, ...rest } = data as Item\n return rest\n}\n\n/** A node or edge as the map holds it, or `null` when it holds none. */\nfunction read<T>(map: Shared, id: string): T | null {\n const fields = map.get(id)\n if (!(fields instanceof Y.Map)) return null\n return { ...(fields.toJSON() as Item), id } as T\n}\n\n/** Where in `value` something JSON can't carry sits, e.g. a component or a function. */\nfunction unshareable(value: unknown, path: string): string | null {\n if (value === null || ['string', 'number', 'boolean', 'undefined'].includes(typeof value))\n return null\n if (typeof value !== 'object') return path\n const proto = Object.getPrototypeOf(value)\n if (Array.isArray(value)) {\n for (const [i, item] of value.entries()) {\n const found = unshareable(item, `${path}[${i}]`)\n if (found) return found\n }\n return null\n }\n if (proto !== Object.prototype && proto !== null) return path\n for (const [key, item] of Object.entries(value)) {\n const found = unshareable(item, `${path}.${key}`)\n if (found) return found\n }\n return null\n}\n\nconst warned = new Set<string>()\n/** Others would get a broken copy of such a value while this editor shows it fine. */\nfunction warnUnshareable(id: string, key: string, value: unknown) {\n // the app's bundler replaces this, as for Vue's own warnings\n if (process.env.NODE_ENV === 'production') return\n const path = unshareable(value, key)\n if (!path || warned.has(`${id}:${path}`)) return\n warned.add(`${id}:${path}`)\n console.warn(\n `[@dou-ui/flow] \"${id}\" has ${path}, which can't be shared: only JSON values reach the others. Store a name and look the component or function up where it is drawn.`,\n )\n}\n\nlet warnedDataUrl = false\n/** A data URL goes into the shared document whole, to everyone, every time it loads. */\nfunction warnDataUrl(data: unknown) {\n if (process.env.NODE_ENV === 'production' || warnedDataUrl || !data || typeof data !== 'object')\n return\n for (const value of Object.values(data))\n if (typeof value === 'string' && value.length > 100_000 && value.startsWith('data:')) {\n warnedDataUrl = true\n console.warn(\n `[@dou-ui/flow] a node keeps a data URL of ${Math.round(value.length / 1024)} KB, which every collaborator downloads with the document. Upload such files and keep their address instead, e.g. whiteboardKit({ upload }).`,\n )\n return\n }\n}\n\n/** Writes a node's or edge's fields, only those that changed; `keys` limits which. */\nfunction write(map: Shared, value: FlowNode | FlowEdge, keys?: Set<string>) {\n const item = value as unknown as Item\n const existing = map.get(value.id)\n const fields: Shared = existing instanceof Y.Map ? existing : new Y.Map<unknown>()\n const fresh = existing !== fields\n for (const [key, raw] of Object.entries(item)) {\n if (key === 'id' || raw === undefined || (keys && !fresh && !keys.has(key))) continue\n const field = key === 'data' ? sharedData(raw) : raw\n warnUnshareable(value.id, key, field)\n if (key === 'data') warnDataUrl(field)\n // a new map can't be read before it is in the document\n if (fresh || !same(fields.get(key), field)) fields.set(key, field)\n }\n if (fresh) return void map.set(value.id, fields)\n for (const key of Array.from(fields.keys()))\n if (item[key] === undefined && (!keys || keys.has(key))) fields.delete(key)\n}\n\n/** The change that makes `local` match `shared`, leaving out `skip` and the local fields. */\nfunction patchFor<T extends object>(local: T, shared: T, skip?: Set<string>) {\n const a = local as Item\n const b = shared as Item\n const patch: Item = {}\n for (const key of new Set([...Object.keys(a), ...Object.keys(b)])) {\n if (key === 'id' || skip?.has(key)) continue\n if (key === 'data') {\n if (same(sharedData(a.data), b.data)) continue\n // keep this person's folding\n const mine = (a.data as Item | undefined)?.[LOCAL_DATA]\n patch.data = mine === undefined ? b.data : { ...(b.data as Item), [LOCAL_DATA]: mine }\n } else if (!same(a[key], b[key])) patch[key] = b[key]\n }\n return Object.keys(patch).length ? (patch as Partial<T>) : null\n}\n\n/**\n * Writes nodes and edges into empty maps, e.g. when a flow that so far was\n * saved as JSON starts being edited together. Do it once, where the shared\n * document is created.\n */\nexport function fillMaps(\n maps: { nodes: Y.Map<unknown>; edges: Y.Map<unknown> },\n doc: FlowDocument,\n) {\n const fill = () => {\n for (const node of doc.nodes) write(maps.nodes, node)\n for (const edge of doc.edges) write(maps.edges, edge)\n }\n const ydoc = maps.nodes.doc\n if (ydoc) ydoc.transact(fill)\n else fill()\n}\n\n/** The fields `touched` holds for an item, when it holds only some. */\nfunction heldKeys(touched: Touched, id: string) {\n const keys = touched.get(id)\n return keys === true ? undefined : keys\n}\n\nfunction mark(into: Touched, id: string, keys: string[] | true) {\n const current = into.get(id)\n if (current === true) return\n if (keys === true) return void into.set(id, true)\n const set = current ?? new Set<string>()\n for (const key of keys) set.add(key)\n into.set(id, set)\n}\n\n/** The nodes and edges a transaction added, removed or changed, with the fields it changed. */\nfunction touch(steps: readonly FlowStep[], into: { nodes: Touched; edges: Touched }) {\n for (const step of steps) {\n if (step.type === 'addNode') mark(into.nodes, step.node.id, true)\n else if (step.type === 'removeNode') mark(into.nodes, step.id, true)\n else if (step.type === 'updateNode') mark(into.nodes, step.id, Object.keys(step.patch))\n else if (step.type === 'addEdge') mark(into.edges, step.edge.id, true)\n else if (step.type === 'removeEdge') mark(into.edges, step.id, true)\n else mark(into.edges, step.id, Object.keys(step.patch))\n }\n}\n\n/**\n * Edit a flow together through a Yjs document. Nodes and edges live in two\n * `Y.Map`s, each field on its own, so two people changing different fields\n * of one node both keep their change. The editor's `v-model` only mirrors\n * them: changes made to `v-model` from outside are not written back. While\n * someone drags, the others see an outline; the document changes once they\n * let go. Undo only takes back this user's own changes. Folding a group\n * stays local.\n */\nexport function collaboration(options: CollaborationOptions): FlowExtension {\n const { nodes, edges } = options\n const ydoc = nodes.doc\n if (!ydoc || edges.doc !== ydoc)\n throw new Error('[@dou-ui/flow] nodes and edges must be maps of the same Y.Doc')\n const { awareness, user } = options\n if (awareness && user) {\n const color = user.color ?? collaborationColors[ydoc.clientID % collaborationColors.length]\n awareness.setLocalStateField('user', { name: user.name, color })\n }\n\n /** This editor's own changes, which undo can take back. */\n const tracked = { flow: 'tracked' }\n /** Changes that undo leaves alone, e.g. filling the maps or clearing dangling edges. */\n const untracked = { flow: 'untracked' }\n const undoManager = new Y.UndoManager([nodes, edges], {\n trackedOrigins: new Set([tracked]),\n captureTimeout: 500,\n })\n /** Set while shared changes are applied to the editor, so they aren't written back. */\n let applying = false\n /** What undo or redo is running, to name the change. */\n let travelling: 'undo' | 'redo' | null = null\n let lastGroup: string | undefined\n /** What changed during the current drag, written once it ends. */\n const held = { nodes: new Map() as Touched, edges: new Map() as Touched, tracking: false }\n\n function writeChanges(\n doc: FlowDocument,\n changed: { nodes: Touched; edges: Touched },\n tracking: boolean,\n ) {\n const nodeById = new Map(doc.nodes.map((node) => [node.id, node]))\n const edgeById = new Map(doc.edges.map((edge) => [edge.id, edge]))\n ydoc!.transact(\n () => {\n for (const [id, keys] of changed.nodes) {\n const node = nodeById.get(id)\n if (node) write(nodes, node, keys === true ? undefined : keys)\n else nodes.delete(id)\n }\n for (const [id, keys] of changed.edges) {\n const edge = edgeById.get(id)\n if (edge) write(edges, edge, keys === true ? undefined : keys)\n else edges.delete(id)\n }\n },\n tracking ? tracked : untracked,\n )\n }\n\n /** Writes what a drag changed, as one undo step. */\n function release(context: FlowEditorContext) {\n if (!held.nodes.size && !held.edges.size) return\n if (held.tracking) undoManager.stopCapturing()\n writeChanges(toRaw(context.doc.value), held, held.tracking)\n if (held.tracking) undoManager.stopCapturing()\n lastGroup = undefined\n held.nodes.clear()\n held.edges.clear()\n held.tracking = false\n }\n\n /**\n * Adds steps to `tr` that bring the given nodes and edges in line with the\n * maps; returns edges left without a node, to clear from the maps.\n */\n function pull(tr: FlowTransaction, nodeIds: Iterable<string>, edgeIds: Iterable<string>) {\n for (const id of nodeIds) {\n const shared = read<FlowNode>(nodes, id)\n const local = tr.getNode(id)\n // a node being dragged here keeps the fields the drag changes\n if (held.nodes.get(id) === true) continue\n if (!shared) {\n if (local) tr.removeNode(id)\n } else if (!local) tr.addNode(shared)\n else {\n const patch = patchFor(local, shared, heldKeys(held.nodes, id))\n if (patch) tr.updateNode(id, patch)\n }\n }\n for (const id of edgeIds) {\n const shared = read<FlowEdge>(edges, id)\n const local = tr.getEdge(id)\n if (held.edges.get(id) === true) continue\n if (!shared) {\n if (local) tr.removeEdge(id)\n } else if (!local) tr.addEdge(shared)\n else {\n const patch = patchFor(local, shared, heldKeys(held.edges, id))\n if (patch) tr.updateEdge(id, patch)\n }\n }\n // One person deleted a node while another connected to it: the edge goes too.\n const dangling: string[] = []\n for (const edge of tr.doc.edges)\n if (!tr.getNode(edge.source.node) || !tr.getNode(edge.target.node)) dangling.push(edge.id)\n for (const id of dangling) tr.removeEdge(id)\n return dangling\n }\n\n return defineFlowExtension({\n name: 'collaboration',\n // after the other extensions, so it writes what they added\n priority: 0,\n transaction(tr, context) {\n if (applying) return\n const tracking = tr.meta.history !== false\n if (context.gesturing.value) {\n touch(tr.steps, held)\n held.tracking ||= tracking\n return\n }\n const changed = { nodes: new Map() as Touched, edges: new Map() as Touched }\n touch(tr.steps, changed)\n // like the editor's own history: only changes in one group undo together\n if (tracking && (!tr.meta.group || tr.meta.group !== lastGroup)) undoManager.stopCapturing()\n if (tracking) lastGroup = tr.meta.group\n writeChanges(tr.doc, changed, tracking)\n },\n history: () => ({\n undo: () => run('undo'),\n redo: () => run('redo'),\n canUndo: () => undoManager.undoStack.length > 0,\n canRedo: () => undoManager.redoStack.length > 0,\n }),\n layers: awareness ? { above: [createPeers(awareness)] } : undefined,\n setup(context) {\n const apply = (fn: (tr: FlowTransaction) => string[], origin: string) => {\n let dangling: string[] = []\n applying = true\n try {\n context.transact((tr) => (dangling = fn(tr)), { history: false, origin })\n } finally {\n applying = false\n }\n if (dangling.length)\n queueMicrotask(() =>\n ydoc.transact(() => {\n for (const id of dangling) edges.delete(id)\n }, untracked),\n )\n }\n const current = () => toRaw(context.doc.value)\n\n // the first one to open the flow brings its nodes; everyone else reads the maps\n if (!nodes.size && !edges.size)\n ydoc.transact(() => fillMaps({ nodes, edges }, current()), untracked)\n const doc = current()\n apply(\n (tr) =>\n pull(\n tr,\n new Set([...doc.nodes.map((node) => node.id), ...nodes.keys()]),\n new Set([...doc.edges.map((edge) => edge.id), ...edges.keys()]),\n ),\n 'remote',\n )\n\n // changes from others, and from undo: gathered per Yjs transaction\n const pending = { nodes: new Set<string>(), edges: new Set<string>() }\n const gather =\n (map: Shared, into: Set<string>) =>\n (events: Y.YEvent<Y.Map<unknown>>[], transaction: Y.Transaction) => {\n if (transaction.origin === tracked || transaction.origin === untracked) return\n for (const event of events) {\n if (event.target === map) for (const key of event.changes.keys.keys()) into.add(key)\n else if (event.path.length) into.add(String(event.path[0]))\n }\n }\n const onNodes = gather(nodes, pending.nodes)\n const onEdges = gather(edges, pending.edges)\n const flush = () => {\n if (!pending.nodes.size && !pending.edges.size) return\n const nodeIds = [...pending.nodes]\n const edgeIds = [...pending.edges]\n pending.nodes.clear()\n pending.edges.clear()\n apply((tr) => pull(tr, nodeIds, edgeIds), travelling ?? 'remote')\n }\n nodes.observeDeep(onNodes)\n edges.observeDeep(onEdges)\n ydoc.on('afterTransaction', flush)\n\n // a drag is written once it ends, cancelled or not\n const stopRelease = watch(context.gesturing, (on) => !on && release(context), {\n flush: 'sync',\n })\n\n let stopPresence: (() => void) | undefined\n if (awareness) {\n // what this user is dragging, connecting and has selected, for the others to see\n stopPresence = watch(\n [context.gesturing, context.doc, context.selection, context.connectingPath],\n ([on, , selection, path]) => {\n const moving: FlowPresence['moving'] = []\n if (on)\n for (const id of held.nodes.keys()) {\n if (moving.length >= 100) break\n const rect = context.getNodeRect(id)\n if (rect) moving.push({ id, ...rect })\n }\n awareness.setLocalStateField('flow', {\n moving,\n selection: {\n nodes: selection.nodes.slice(0, 100),\n edges: selection.edges.slice(0, 100),\n },\n path,\n } satisfies FlowPresence)\n },\n { immediate: true },\n )\n }\n\n return () => {\n nodes.unobserveDeep(onNodes)\n edges.unobserveDeep(onEdges)\n ydoc.off('afterTransaction', flush)\n stopRelease()\n stopPresence?.()\n awareness?.setLocalStateField('flow', null)\n }\n },\n })\n\n function run(action: 'undo' | 'redo') {\n const stack = action === 'undo' ? undoManager.undoStack : undoManager.redoStack\n if (!stack.length) return false\n travelling = action\n try {\n undoManager[action]()\n } finally {\n travelling = null\n }\n return true\n }\n}\n\nexport type { AwarenessLike, FlowPresence }\n"],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ import { useFlowEditor } from "../context.js";
2
+ import { defineComponent, h, onBeforeUnmount, shallowRef } from "vue";
3
+ // src/collaboration/peers.ts
4
+ function box(key, rect, rotation, kind, name) {
5
+ return h("div", {
6
+ key,
7
+ class: ["dou-flow-peer", `is-${kind}`],
8
+ style: {
9
+ translate: `${rect.x}px ${rect.y}px`,
10
+ width: `${rect.width}px`,
11
+ height: `${rect.height}px`,
12
+ rotate: rotation ? `${rotation}deg` : void 0
13
+ }
14
+ }, name ? h("span", {
15
+ class: "dou-flow-peer__name",
16
+ style: rotation ? { rotate: `${-rotation}deg` } : void 0
17
+ }, name) : void 0);
18
+ }
19
+ /**
20
+ * A layer that shows what other people are doing, in their colors: dashed
21
+ * outlines where the nodes they drag would land, outlines around what they
22
+ * have selected, and the edge they are drawing.
23
+ */
24
+ function createPeers(awareness) {
25
+ return defineComponent({
26
+ name: "DouFlowPeers",
27
+ setup() {
28
+ const ctx = useFlowEditor();
29
+ const states = shallowRef([]);
30
+ const read = () => {
31
+ const out = [];
32
+ awareness.getStates().forEach((state, client) => {
33
+ if (client === awareness.clientID) return;
34
+ const user = state.user;
35
+ const presence = state.flow;
36
+ if (!user || !presence) return;
37
+ out.push({
38
+ client,
39
+ name: user.name ?? "",
40
+ color: user.color ?? "currentColor",
41
+ presence
42
+ });
43
+ });
44
+ states.value = out;
45
+ };
46
+ read();
47
+ awareness.on("change", read);
48
+ onBeforeUnmount(() => awareness.off("change", read));
49
+ return () => states.value.map((peer) => {
50
+ const { presence } = peer;
51
+ const moving = new Set(presence.moving.map((item) => item.id));
52
+ const children = [];
53
+ let named = false;
54
+ const label = () => named ? void 0 : (named = true, peer.name);
55
+ for (const item of presence.moving) children.push(box(`m-${item.id}`, item, ctx.nodeRotation(item.id), "moving", label()));
56
+ for (const id of presence.selection?.nodes ?? []) {
57
+ const rect = !moving.has(id) && ctx.getNodeRect(id);
58
+ if (rect) children.push(box(`s-${id}`, rect, ctx.nodeRotation(id), "selected", label()));
59
+ }
60
+ if (presence.path) children.push(h("svg", {
61
+ key: "path",
62
+ class: "dou-flow-peer__lines",
63
+ "aria-hidden": "true"
64
+ }, [h("path", { d: presence.path })]));
65
+ return h("div", {
66
+ key: peer.client,
67
+ class: "dou-flow-peers",
68
+ style: { "--dou-flow-peer-color": peer.color }
69
+ }, children);
70
+ });
71
+ }
72
+ });
73
+ }
74
+ export { createPeers };
75
+
76
+ //# sourceMappingURL=peers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"peers.js","names":[],"sources":["../../src/collaboration/peers.ts"],"sourcesContent":["import { defineComponent, h, onBeforeUnmount, shallowRef, type Component, type VNode } from 'vue'\nimport { useFlowEditor } from '../context'\nimport type { FlowSelection, Rect } from '../core/model'\n\n/** The part of an `Awareness` (`y-protocols/awareness`) the editor uses. */\nexport interface AwarenessLike {\n clientID: number\n getLocalState(): Record<string, unknown> | null\n setLocalStateField(field: string, value: unknown): void\n getStates(): Map<number, Record<string, unknown>>\n on(event: 'change', listener: (...args: unknown[]) => void): void\n off(event: 'change', listener: (...args: unknown[]) => void): void\n}\n\n/** What each person shares under the awareness field `flow`. */\nexport interface FlowPresence {\n /** The nodes they are dragging or resizing, where they would land. */\n moving: ({ id: string } & Rect)[]\n selection: FlowSelection\n /** The edge they are drawing, as an SVG path in canvas coordinates. */\n path: string | null\n}\n\ninterface Peer {\n client: number\n name: string\n color: string\n presence: FlowPresence\n}\n\nfunction box(key: string, rect: Rect, rotation: number, kind: string, name?: string) {\n return h(\n 'div',\n {\n key,\n class: ['dou-flow-peer', `is-${kind}`],\n style: {\n // `translate`, not `transform`: it applies before `rotate`, so the\n // outline turns about the node's own centre, as the node is drawn\n translate: `${rect.x}px ${rect.y}px`,\n width: `${rect.width}px`,\n height: `${rect.height}px`,\n rotate: rotation ? `${rotation}deg` : undefined,\n },\n },\n // the name, once per person, upright on the outline's corner\n name\n ? h(\n 'span',\n {\n class: 'dou-flow-peer__name',\n style: rotation ? { rotate: `${-rotation}deg` } : undefined,\n },\n name,\n )\n : undefined,\n )\n}\n\n/**\n * A layer that shows what other people are doing, in their colors: dashed\n * outlines where the nodes they drag would land, outlines around what they\n * have selected, and the edge they are drawing.\n */\nexport function createPeers(awareness: AwarenessLike): Component {\n return defineComponent({\n name: 'DouFlowPeers',\n setup() {\n const ctx = useFlowEditor()\n const states = shallowRef<Peer[]>([])\n const read = () => {\n const out: Peer[] = []\n awareness.getStates().forEach((state, client) => {\n if (client === awareness.clientID) return\n const user = state.user as { name?: string; color?: string } | undefined\n const presence = state.flow as FlowPresence | null | undefined\n if (!user || !presence) return\n out.push({ client, name: user.name ?? '', color: user.color ?? 'currentColor', presence })\n })\n states.value = out\n }\n read()\n awareness.on('change', read)\n onBeforeUnmount(() => awareness.off('change', read))\n\n return () =>\n states.value.map((peer) => {\n const { presence } = peer\n const moving = new Set(presence.moving.map((item) => item.id))\n const children: VNode[] = []\n let named = false\n const label = () => (named ? undefined : ((named = true), peer.name))\n for (const item of presence.moving)\n children.push(box(`m-${item.id}`, item, ctx.nodeRotation(item.id), 'moving', label()))\n for (const id of presence.selection?.nodes ?? []) {\n const rect = !moving.has(id) && ctx.getNodeRect(id)\n if (rect) children.push(box(`s-${id}`, rect, ctx.nodeRotation(id), 'selected', label()))\n }\n if (presence.path)\n children.push(\n h('svg', { key: 'path', class: 'dou-flow-peer__lines', 'aria-hidden': 'true' }, [\n h('path', { d: presence.path }),\n ]),\n )\n return h(\n 'div',\n {\n key: peer.client,\n class: 'dou-flow-peers',\n style: { '--dou-flow-peer-color': peer.color },\n },\n children,\n )\n })\n },\n })\n}\n"],"mappings":";;;AA8BA,SAAS,IAAI,KAAa,MAAY,UAAkB,MAAc,MAAe;CACnF,OAAO,EACL,OACA;EACE;EACA,OAAO,CAAC,iBAAiB,MAAM,MAAM;EACrC,OAAO;GAGL,WAAW,GAAG,KAAK,EAAE,KAAK,KAAK,EAAE;GACjC,OAAO,GAAG,KAAK,MAAM;GACrB,QAAQ,GAAG,KAAK,OAAO;GACvB,QAAQ,WAAW,GAAG,SAAS,OAAO,KAAA;EACxC;CACF,GAEA,OACI,EACE,QACA;EACE,OAAO;EACP,OAAO,WAAW,EAAE,QAAQ,GAAG,CAAC,SAAS,KAAK,IAAI,KAAA;CACpD,GACA,IACF,IACA,KAAA,CACN;AACF;;;;;;AAOA,SAAgB,YAAY,WAAqC;CAC/D,OAAO,gBAAgB;EACrB,MAAM;EACN,QAAQ;GACN,MAAM,MAAM,cAAc;GAC1B,MAAM,SAAS,WAAmB,CAAC,CAAC;GACpC,MAAM,aAAa;IACjB,MAAM,MAAc,CAAC;IACrB,UAAU,UAAU,CAAC,CAAC,SAAS,OAAO,WAAW;KAC/C,IAAI,WAAW,UAAU,UAAU;KACnC,MAAM,OAAO,MAAM;KACnB,MAAM,WAAW,MAAM;KACvB,IAAI,CAAC,QAAQ,CAAC,UAAU;KACxB,IAAI,KAAK;MAAE;MAAQ,MAAM,KAAK,QAAQ;MAAI,OAAO,KAAK,SAAS;MAAgB;KAAS,CAAC;IAC3F,CAAC;IACD,OAAO,QAAQ;GACjB;GACA,KAAK;GACL,UAAU,GAAG,UAAU,IAAI;GAC3B,sBAAsB,UAAU,IAAI,UAAU,IAAI,CAAC;GAEnD,aACE,OAAO,MAAM,KAAK,SAAS;IACzB,MAAM,EAAE,aAAa;IACrB,MAAM,SAAS,IAAI,IAAI,SAAS,OAAO,KAAK,SAAS,KAAK,EAAE,CAAC;IAC7D,MAAM,WAAoB,CAAC;IAC3B,IAAI,QAAQ;IACZ,MAAM,cAAe,QAAQ,KAAA,KAAc,QAAQ,MAAO,KAAK;IAC/D,KAAK,MAAM,QAAQ,SAAS,QAC1B,SAAS,KAAK,IAAI,KAAK,KAAK,MAAM,MAAM,IAAI,aAAa,KAAK,EAAE,GAAG,UAAU,MAAM,CAAC,CAAC;IACvF,KAAK,MAAM,MAAM,SAAS,WAAW,SAAS,CAAC,GAAG;KAChD,MAAM,OAAO,CAAC,OAAO,IAAI,EAAE,KAAK,IAAI,YAAY,EAAE;KAClD,IAAI,MAAM,SAAS,KAAK,IAAI,KAAK,MAAM,MAAM,IAAI,aAAa,EAAE,GAAG,YAAY,MAAM,CAAC,CAAC;IACzF;IACA,IAAI,SAAS,MACX,SAAS,KACP,EAAE,OAAO;KAAE,KAAK;KAAQ,OAAO;KAAwB,eAAe;IAAO,GAAG,CAC9E,EAAE,QAAQ,EAAE,GAAG,SAAS,KAAK,CAAC,CAChC,CAAC,CACH;IACF,OAAO,EACL,OACA;KACE,KAAK,KAAK;KACV,OAAO;KACP,OAAO,EAAE,yBAAyB,KAAK,MAAM;IAC/C,GACA,QACF;GACF,CAAC;EACL;CACF,CAAC;AACH"}
@@ -0,0 +1,6 @@
1
+ import default_node_vue_vue_type_script_setup_true_lang_default from "./default-node.vue_vue_type_script_setup_true_lang.js";
2
+ // src/components/default-node.vue
3
+ var default_node_default = default_node_vue_vue_type_script_setup_true_lang_default;
4
+ export { default_node_default as default };
5
+
6
+ //# sourceMappingURL=default-node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"default-node.js","names":["_sfc_main"],"sources":["../../src/components/default-node.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { portSide, type NodeViewProps } from '../core/node-types'\nimport { useFlowEditor } from '../context'\nimport type { DefaultNodeData } from './default-node'\nimport FlowPortView from './flow-port.vue'\n\nconst props = defineProps<NodeViewProps<DefaultNodeData>>()\nconst { messages } = useFlowEditor()\n\n// Ports on the top or bottom are placed by the editor.\nconst inputs = computed(() =>\n props.ports.filter((p) => p.kind === 'input' && portSide(p) === 'left'),\n)\nconst outputs = computed(() =>\n props.ports.filter((p) => p.kind === 'output' && portSide(p) === 'right'),\n)\n// A single unnamed input and output sit on the title row.\nconst inline = computed(\n () =>\n inputs.value.length <= 1 &&\n outputs.value.length <= 1 &&\n [...inputs.value, ...outputs.value].every((p) => !p.label),\n)\nconst rows = computed(() => {\n if (inline.value) return []\n const count = Math.max(inputs.value.length, outputs.value.length)\n return Array.from({ length: count }, (_, i) => ({\n input: inputs.value[i],\n output: outputs.value[i],\n }))\n})\n</script>\n\n<template>\n <div class=\"dou-flow-default-node\">\n <div class=\"dou-flow-default-node__head\">\n <FlowPortView v-if=\"inline && inputs[0]\" :id=\"inputs[0].id\" />\n <span v-if=\"data.icon\" class=\"dou-flow-default-node__icon\">\n <component :is=\"data.icon\" />\n </span>\n <span class=\"dou-flow-default-node__title\">{{ data.label }}</span>\n <span\n v-if=\"data.status\"\n class=\"dou-flow-default-node__status\"\n :class=\"`is-${data.status}`\"\n role=\"img\"\n :aria-label=\"messages.status[data.status]\"\n />\n <FlowPortView v-if=\"inline && outputs[0]\" :id=\"outputs[0].id\" />\n </div>\n <p v-if=\"data.description\" class=\"dou-flow-default-node__description\">\n {{ data.description }}\n </p>\n <div v-if=\"rows.length\" class=\"dou-flow-default-node__ports\">\n <div v-for=\"(row, i) in rows\" :key=\"i\" class=\"dou-flow-default-node__row\">\n <span v-if=\"row.input\" class=\"dou-flow-default-node__port is-input\">\n <FlowPortView :id=\"row.input.id\" />\n {{ row.input.label ?? messages.input }}\n </span>\n <span v-if=\"row.output\" class=\"dou-flow-default-node__port is-output\">\n {{ row.output.label ?? messages.output }}\n <FlowPortView :id=\"row.output.id\" />\n </span>\n </div>\n </div>\n </div>\n</template>\n"],"mappings":""}
@@ -0,0 +1,87 @@
1
+ import { useFlowEditor } from "../context.js";
2
+ import { portSide } from "../core/node-types.js";
3
+ import flow_port_default from "./flow-port.js";
4
+ import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, normalizeClass, openBlock, renderList, resolveDynamicComponent, toDisplayString, unref } from "vue";
5
+ // src/components/default-node.vue?vue&type=script&setup=true&lang.ts
6
+ var _hoisted_1 = { class: "dou-flow-default-node" };
7
+ var _hoisted_2 = { class: "dou-flow-default-node__head" };
8
+ var _hoisted_3 = {
9
+ key: 1,
10
+ class: "dou-flow-default-node__icon"
11
+ };
12
+ var _hoisted_4 = { class: "dou-flow-default-node__title" };
13
+ var _hoisted_5 = ["aria-label"];
14
+ var _hoisted_6 = {
15
+ key: 0,
16
+ class: "dou-flow-default-node__description"
17
+ };
18
+ var _hoisted_7 = {
19
+ key: 1,
20
+ class: "dou-flow-default-node__ports"
21
+ };
22
+ var _hoisted_8 = {
23
+ key: 0,
24
+ class: "dou-flow-default-node__port is-input"
25
+ };
26
+ var _hoisted_9 = {
27
+ key: 1,
28
+ class: "dou-flow-default-node__port is-output"
29
+ };
30
+ var default_node_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
31
+ __name: "default-node",
32
+ props: {
33
+ node: {},
34
+ data: {},
35
+ ports: {},
36
+ selected: { type: Boolean },
37
+ readonly: { type: Boolean },
38
+ updateData: { type: Function }
39
+ },
40
+ setup(__props) {
41
+ const props = __props;
42
+ const { messages } = useFlowEditor();
43
+ const inputs = computed(() => props.ports.filter((p) => p.kind === "input" && portSide(p) === "left"));
44
+ const outputs = computed(() => props.ports.filter((p) => p.kind === "output" && portSide(p) === "right"));
45
+ const inline = computed(() => inputs.value.length <= 1 && outputs.value.length <= 1 && [...inputs.value, ...outputs.value].every((p) => !p.label));
46
+ const rows = computed(() => {
47
+ if (inline.value) return [];
48
+ const count = Math.max(inputs.value.length, outputs.value.length);
49
+ return Array.from({ length: count }, (_, i) => ({
50
+ input: inputs.value[i],
51
+ output: outputs.value[i]
52
+ }));
53
+ });
54
+ return (_ctx, _cache) => {
55
+ return openBlock(), createElementBlock("div", _hoisted_1, [
56
+ createElementVNode("div", _hoisted_2, [
57
+ inline.value && inputs.value[0] ? (openBlock(), createBlock(flow_port_default, {
58
+ key: 0,
59
+ id: inputs.value[0].id
60
+ }, null, 8, ["id"])) : createCommentVNode("", true),
61
+ __props.data.icon ? (openBlock(), createElementBlock("span", _hoisted_3, [(openBlock(), createBlock(resolveDynamicComponent(__props.data.icon)))])) : createCommentVNode("", true),
62
+ createElementVNode("span", _hoisted_4, toDisplayString(__props.data.label), 1),
63
+ __props.data.status ? (openBlock(), createElementBlock("span", {
64
+ key: 2,
65
+ class: normalizeClass(["dou-flow-default-node__status", `is-${__props.data.status}`]),
66
+ role: "img",
67
+ "aria-label": unref(messages).status[__props.data.status]
68
+ }, null, 10, _hoisted_5)) : createCommentVNode("", true),
69
+ inline.value && outputs.value[0] ? (openBlock(), createBlock(flow_port_default, {
70
+ key: 3,
71
+ id: outputs.value[0].id
72
+ }, null, 8, ["id"])) : createCommentVNode("", true)
73
+ ]),
74
+ __props.data.description ? (openBlock(), createElementBlock("p", _hoisted_6, toDisplayString(__props.data.description), 1)) : createCommentVNode("", true),
75
+ rows.value.length ? (openBlock(), createElementBlock("div", _hoisted_7, [(openBlock(true), createElementBlock(Fragment, null, renderList(rows.value, (row, i) => {
76
+ return openBlock(), createElementBlock("div", {
77
+ key: i,
78
+ class: "dou-flow-default-node__row"
79
+ }, [row.input ? (openBlock(), createElementBlock("span", _hoisted_8, [createVNode(flow_port_default, { id: row.input.id }, null, 8, ["id"]), createTextVNode(" " + toDisplayString(row.input.label ?? unref(messages).input), 1)])) : createCommentVNode("", true), row.output ? (openBlock(), createElementBlock("span", _hoisted_9, [createTextVNode(toDisplayString(row.output.label ?? unref(messages).output) + " ", 1), createVNode(flow_port_default, { id: row.output.id }, null, 8, ["id"])])) : createCommentVNode("", true)]);
80
+ }), 128))])) : createCommentVNode("", true)
81
+ ]);
82
+ };
83
+ }
84
+ });
85
+ export { default_node_vue_vue_type_script_setup_true_lang_default as default };
86
+
87
+ //# sourceMappingURL=default-node.vue_vue_type_script_setup_true_lang.js.map