@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.
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
@@ -0,0 +1,406 @@
1
+ import { defineNodeType } from "../core/node-types.js";
2
+ import { defineFlowExtension } from "../core/extension.js";
3
+ import { format } from "../locale.js";
4
+ import { createId } from "../core/model.js";
5
+ import { anchorPorts, whiteboardKeys } from "./model.js";
6
+ import { boardState } from "./state.js";
7
+ import board_node_default from "./board-node.js";
8
+ import board_draft_default from "./board-draft.js";
9
+ import { imageFiles, insertImages, pickImages } from "./image.js";
10
+ import whiteboard_toolbar_default from "./whiteboard-toolbar.js";
11
+ import { connectorEdge, connectorToolbar } from "./connector.js";
12
+ import { drawBoardNode } from "./draw.js";
13
+ import { drawInk, erase } from "./pen.js";
14
+ import { watch } from "vue";
15
+ import { RotateCwIcon } from "@dou-ui/icons";
16
+ // src/whiteboard/kit.ts
17
+ /** The node types the whiteboard adds; only they turn. */
18
+ var BOARD_TYPES = /* @__PURE__ */ new Set([
19
+ "shape",
20
+ "sticky",
21
+ "text",
22
+ "ink",
23
+ "image"
24
+ ]);
25
+ /** "Rectangle: text", or just "Rectangle" while it has none. */
26
+ var nameOf = (kind, text, t) => text ? format(t.board.named, {
27
+ kind,
28
+ text
29
+ }) : kind;
30
+ /** How far a board node is turned, from `data.rotation`. */
31
+ var turnedBy = (node) => node.data?.rotation ?? 0;
32
+ /** A rectangle, ellipse or diamond with centred text; `data.shape` says which. */
33
+ var shapeNode = defineNodeType({
34
+ type: "shape",
35
+ component: board_node_default,
36
+ ports: anchorPorts,
37
+ frame: false,
38
+ resizable: {
39
+ minWidth: 24,
40
+ minHeight: 24
41
+ },
42
+ rotation: turnedBy,
43
+ draw: drawBoardNode,
44
+ size: {
45
+ width: 160,
46
+ height: 100
47
+ },
48
+ defaults: () => ({ shape: "rect" }),
49
+ label: (node, t) => nameOf(t.board[node.data?.shape ?? "rect"], node.data?.text, t)
50
+ });
51
+ /** A square note in a soft color. */
52
+ var stickyNode = defineNodeType({
53
+ type: "sticky",
54
+ component: board_node_default,
55
+ ports: anchorPorts,
56
+ frame: false,
57
+ resizable: {
58
+ minWidth: 64,
59
+ minHeight: 64
60
+ },
61
+ rotation: turnedBy,
62
+ draw: drawBoardNode,
63
+ size: {
64
+ width: 160,
65
+ height: 160
66
+ },
67
+ defaults: () => ({}),
68
+ label: (node, t) => nameOf(t.board.sticky, node.data?.text, t)
69
+ });
70
+ /** Text on its own; it grows as it is typed. */
71
+ var textNode = defineNodeType({
72
+ type: "text",
73
+ component: board_node_default,
74
+ ports: anchorPorts,
75
+ frame: false,
76
+ resizable: {
77
+ minWidth: 24,
78
+ minHeight: 24
79
+ },
80
+ rotation: turnedBy,
81
+ draw: drawBoardNode,
82
+ size: {
83
+ width: 200,
84
+ height: 40
85
+ },
86
+ defaults: () => ({}),
87
+ label: (node, t) => nameOf(t.board.text, node.data?.text, t)
88
+ });
89
+ /** A pen stroke; it stretches with its box. */
90
+ var inkNode = defineNodeType({
91
+ type: "ink",
92
+ component: board_node_default,
93
+ frame: false,
94
+ resizable: {
95
+ minWidth: 8,
96
+ minHeight: 8
97
+ },
98
+ rotation: turnedBy,
99
+ draw: drawBoardNode,
100
+ defaults: () => ({
101
+ points: [],
102
+ box: [0, 0]
103
+ }),
104
+ label: (_, t) => t.board.ink
105
+ });
106
+ /** An image; `data.src` is its address. */
107
+ var imageNode = defineNodeType({
108
+ type: "image",
109
+ component: board_node_default,
110
+ ports: anchorPorts,
111
+ frame: false,
112
+ resizable: {
113
+ minWidth: 24,
114
+ minHeight: 24
115
+ },
116
+ rotation: turnedBy,
117
+ draw: drawBoardNode,
118
+ size: {
119
+ width: 240,
120
+ height: 180
121
+ },
122
+ defaults: () => ({ src: "" }),
123
+ label: (node, t) => nameOf(t.board.image, node.data?.alt, t)
124
+ });
125
+ /** The node type and data each drawing tool adds. */
126
+ function kindOf(tool) {
127
+ if (tool === "sticky" || tool === "text") return {
128
+ type: tool,
129
+ data: {}
130
+ };
131
+ return {
132
+ type: "shape",
133
+ data: { shape: tool }
134
+ };
135
+ }
136
+ function nodeFor(tool, rect) {
137
+ const height = tool === "text" ? void 0 : rect.height;
138
+ return {
139
+ id: createId(),
140
+ x: rect.x,
141
+ y: rect.y,
142
+ width: rect.width,
143
+ height,
144
+ ...kindOf(tool)
145
+ };
146
+ }
147
+ var SIZES = {
148
+ rect: {
149
+ width: 160,
150
+ height: 100
151
+ },
152
+ ellipse: {
153
+ width: 140,
154
+ height: 100
155
+ },
156
+ diamond: {
157
+ width: 140,
158
+ height: 120
159
+ },
160
+ sticky: {
161
+ width: 160,
162
+ height: 160
163
+ },
164
+ text: {
165
+ width: 200,
166
+ height: 40
167
+ }
168
+ };
169
+ /** After a tool put down a node: back to select unless kept, and notes and text open to be written in. */
170
+ function added(ctx, tool, id) {
171
+ const state = boardState(ctx);
172
+ if (!state.locked.value) state.tool.value = "select";
173
+ if (tool === "sticky" || tool === "text") state.editing.value = id;
174
+ }
175
+ /**
176
+ * The whiteboard: shape, sticky note and text nodes, the `connector` edge,
177
+ * the drawing tools with their keys, and a toolbar. Add it after `flowKit()`.
178
+ */
179
+ function whiteboardKit(options = {}) {
180
+ return defineFlowExtension({
181
+ name: "whiteboard",
182
+ nodeTypes: [
183
+ shapeNode,
184
+ stickyNode,
185
+ textNode,
186
+ inkNode,
187
+ imageNode
188
+ ],
189
+ edgeTypes: [connectorEdge],
190
+ edgeToolbarItems: connectorToolbar,
191
+ edgeType: "connector",
192
+ layers: { above: [board_draft_default] },
193
+ overlays: options.toolbar === false ? [] : [whiteboard_toolbar_default],
194
+ setup(ctx) {
195
+ const state = boardState(ctx);
196
+ state.upload = options.upload;
197
+ const stops = [];
198
+ if (options.tool) {
199
+ const outer = options.tool;
200
+ state.tool.value = outer.value;
201
+ stops.push(watch(outer, (tool) => state.tool.value = tool), watch(state.tool, (tool) => outer.value = tool));
202
+ }
203
+ stops.push(watch(state.tool, (tool) => {
204
+ const root = ctx.root.value;
205
+ if (root) root.dataset.boardTool = tool;
206
+ }, { immediate: true }), watch(state.panning, (panning) => {
207
+ const root = ctx.root.value;
208
+ if (root) root.toggleAttribute("data-board-panning", panning);
209
+ }));
210
+ const root = ctx.root.value;
211
+ const onDragOver = (event) => {
212
+ if (ctx.readonly.value || !event.dataTransfer?.types.includes("Files")) return;
213
+ event.preventDefault();
214
+ event.dataTransfer.dropEffect = "copy";
215
+ };
216
+ const onDrop = (event) => {
217
+ const files = imageFiles(event.dataTransfer?.files);
218
+ if (ctx.readonly.value || !files.length) return;
219
+ event.preventDefault();
220
+ const at = ctx.toCanvas({
221
+ x: event.clientX,
222
+ y: event.clientY
223
+ });
224
+ insertImages(ctx, files, { at });
225
+ };
226
+ const onPaste = (event) => {
227
+ const active = document.activeElement;
228
+ if (ctx.readonly.value || !root || !active || !root.contains(active)) return;
229
+ if (active.closest("input, textarea, [contenteditable]")) return;
230
+ const files = imageFiles(event.clipboardData?.files);
231
+ if (!files.length) return;
232
+ event.preventDefault();
233
+ event.stopImmediatePropagation();
234
+ insertImages(ctx, files);
235
+ };
236
+ root?.addEventListener("dragover", onDragOver);
237
+ root?.addEventListener("drop", onDrop);
238
+ document.addEventListener("paste", onPaste, true);
239
+ return () => {
240
+ for (const stop of stops) stop();
241
+ root?.removeEventListener("dragover", onDragOver);
242
+ root?.removeEventListener("drop", onDrop);
243
+ document.removeEventListener("paste", onPaste, true);
244
+ delete ctx.root.value?.dataset.boardTool;
245
+ ctx.root.value?.removeAttribute("data-board-panning");
246
+ };
247
+ },
248
+ press(ctx, press) {
249
+ const state = boardState(ctx);
250
+ const tool = state.tool.value;
251
+ if (tool === "select") return;
252
+ state.editing.value = null;
253
+ if (tool === "hand") {
254
+ const start = {
255
+ x: press.event.clientX,
256
+ y: press.event.clientY
257
+ };
258
+ const origin = { ...ctx.viewport.value };
259
+ state.panning.value = true;
260
+ const stop = () => state.panning.value = false;
261
+ return {
262
+ end: stop,
263
+ cancel: stop,
264
+ move(_, event) {
265
+ ctx.setViewport({
266
+ ...origin,
267
+ x: origin.x + event.clientX - start.x,
268
+ y: origin.y + event.clientY - start.y
269
+ });
270
+ }
271
+ };
272
+ }
273
+ if (tool === "pen") return drawInk(ctx, press.event);
274
+ if (tool === "eraser") return erase(ctx, press.point);
275
+ const start = press.point;
276
+ const rectTo = (point, square) => {
277
+ let width = Math.abs(point.x - start.x);
278
+ let height = Math.abs(point.y - start.y);
279
+ if (square) width = height = Math.max(width, height);
280
+ return {
281
+ x: point.x < start.x ? start.x - width : start.x,
282
+ y: point.y < start.y ? start.y - height : start.y,
283
+ width,
284
+ height
285
+ };
286
+ };
287
+ const done = (rect) => {
288
+ state.draft.value = null;
289
+ if (!rect) return;
290
+ const node = nodeFor(tool, rect);
291
+ ctx.transact((tr) => tr.addNode(node));
292
+ ctx.selection.value = {
293
+ nodes: [node.id],
294
+ edges: []
295
+ };
296
+ ctx.announce(format(ctx.messages.value.added, { node: ctx.messages.value.board[tool] }));
297
+ added(ctx, tool, node.id);
298
+ };
299
+ return {
300
+ move(point, event) {
301
+ state.draft.value = {
302
+ ...rectTo(point, event.shiftKey),
303
+ tool
304
+ };
305
+ },
306
+ end(point, event) {
307
+ const rect = rectTo(point, event.shiftKey);
308
+ const min = 8 / ctx.viewport.value.zoom;
309
+ if (rect.width < min && rect.height < min) {
310
+ const size = SIZES[tool];
311
+ done({
312
+ x: start.x - size.width / 2,
313
+ y: start.y - size.height / 2,
314
+ ...size
315
+ });
316
+ } else done({
317
+ ...rect,
318
+ width: Math.max(rect.width, 24),
319
+ height: tool === "text" ? Math.max(rect.height, 40) : Math.max(rect.height, 24)
320
+ });
321
+ },
322
+ cancel: () => done(null)
323
+ };
324
+ },
325
+ contextMenu(ctx, target) {
326
+ if (target.kind !== "node" || ctx.readonly.value) return [];
327
+ const t = ctx.messages.value.board;
328
+ const items = [];
329
+ const node = target.node;
330
+ if (BOARD_TYPES.has(node.type ?? "") && ctx.selection.value.nodes.length <= 1) {
331
+ const rotation = node.data?.rotation ?? 0;
332
+ const rotate = (to) => {
333
+ ctx.transact((tr) => tr.updateData(node.id, { rotation: to || void 0 }));
334
+ ctx.announce(format(t.turned, { degrees: to }));
335
+ };
336
+ items.push({
337
+ key: "rotate",
338
+ label: t.rotate,
339
+ icon: RotateCwIcon,
340
+ divided: true,
341
+ disabled: !!node.locked,
342
+ run: () => rotate((rotation + 90) % 360)
343
+ }, {
344
+ key: "unrotate",
345
+ label: t.unrotate,
346
+ disabled: !rotation || !!node.locked,
347
+ run: () => rotate(0)
348
+ });
349
+ }
350
+ return items;
351
+ },
352
+ keymap(ctx) {
353
+ const state = boardState(ctx);
354
+ const pick = (tool) => () => {
355
+ state.tool.value = tool;
356
+ state.locked.value = false;
357
+ const t = ctx.messages.value.board;
358
+ ctx.announce(format(t.picked, { tool: t[tool] }));
359
+ };
360
+ const keys = {};
361
+ for (const [tool, key] of Object.entries(whiteboardKeys)) keys[key] = pick(tool);
362
+ return {
363
+ ...keys,
364
+ ["i"]: () => {
365
+ if (ctx.readonly.value) return false;
366
+ pickImages(ctx);
367
+ },
368
+ enter: () => {
369
+ if (ctx.readonly.value) return false;
370
+ const tool = state.tool.value;
371
+ if (tool !== "select" && tool !== "hand" && tool !== "pen" && tool !== "eraser") {
372
+ const { type, data } = kindOf(tool);
373
+ const size = tool === "text" ? {
374
+ width: SIZES.text.width,
375
+ height: void 0
376
+ } : SIZES[tool];
377
+ const writes = tool === "sticky" || tool === "text";
378
+ const id = ctx.insertNode(type, {
379
+ data,
380
+ size,
381
+ focus: !writes
382
+ });
383
+ if (id) added(ctx, tool, id);
384
+ return;
385
+ }
386
+ const { nodes, edges } = ctx.selection.value;
387
+ if (nodes.length !== 1 || edges.length || ctx.readonly.value) return false;
388
+ const node = ctx.doc.value.nodes.find((n) => n.id === nodes[0]);
389
+ if (!node || ![
390
+ "shape",
391
+ "sticky",
392
+ "text"
393
+ ].includes(node.type ?? "")) return false;
394
+ state.editing.value = node.id;
395
+ },
396
+ escape: () => {
397
+ if (state.tool.value === "select") return false;
398
+ state.tool.value = "select";
399
+ }
400
+ };
401
+ }
402
+ });
403
+ }
404
+ export { connectorEdge, imageNode, inkNode, shapeNode, stickyNode, textNode, whiteboardKit };
405
+
406
+ //# sourceMappingURL=kit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kit.js","names":["BoardNode","BoardDraft","WhiteboardToolbar"],"sources":["../../src/whiteboard/kit.ts"],"sourcesContent":["import type { ContextMenuItem } from 'dou-ui'\nimport { RotateCwIcon } from '@dou-ui/icons'\nimport { watch, type Ref } from 'vue'\nimport type { FlowEditorContext } from '../context'\nimport { defineFlowExtension, type FlowExtension } from '../core/extension'\nimport { createId, type FlowData, type FlowNode, type Point, type Rect } from '../core/model'\nimport { defineNodeType } from '../core/node-types'\nimport { format, type FlowMessages } from '../locale'\nimport BoardNode from './board-node.vue'\nimport BoardDraft from './board-draft.vue'\nimport WhiteboardToolbar from './whiteboard-toolbar.vue'\nimport {\n anchorPorts,\n imageKey,\n type WhiteboardImageData,\n type WhiteboardInkData,\n type WhiteboardNodeData,\n type WhiteboardShapeData,\n type WhiteboardShapeTool,\n type WhiteboardStickyData,\n type WhiteboardTextData,\n type WhiteboardTool,\n whiteboardKeys,\n} from './model'\nimport { imageFiles, insertImages, pickImages, type WhiteboardUpload } from './image'\nimport { connectorEdge, connectorToolbar } from './connector'\nimport { drawBoardNode } from './draw'\nimport { drawInk, erase } from './pen'\nimport { boardState } from './state'\n\n/** The node types the whiteboard adds; only they turn. */\nconst BOARD_TYPES = new Set(['shape', 'sticky', 'text', 'ink', 'image'])\n\n/** \"Rectangle: text\", or just \"Rectangle\" while it has none. */\nconst nameOf = (kind: string, text: string | undefined, t: FlowMessages) =>\n text ? format(t.board.named, { kind, text }) : kind\n\n/** How far a board node is turned, from `data.rotation`. */\nconst turnedBy = (node: FlowNode<WhiteboardNodeData>) => node.data?.rotation ?? 0\n\n/** A rectangle, ellipse or diamond with centred text; `data.shape` says which. */\nexport const shapeNode = defineNodeType<WhiteboardShapeData>({\n type: 'shape',\n component: BoardNode,\n ports: anchorPorts,\n frame: false,\n resizable: { minWidth: 24, minHeight: 24 },\n rotation: turnedBy,\n draw: drawBoardNode,\n size: { width: 160, height: 100 },\n defaults: () => ({ shape: 'rect' }),\n label: (node, t) => nameOf(t.board[node.data?.shape ?? 'rect'], node.data?.text, t),\n})\n\n/** A square note in a soft color. */\nexport const stickyNode = defineNodeType<WhiteboardStickyData>({\n type: 'sticky',\n component: BoardNode,\n ports: anchorPorts,\n frame: false,\n resizable: { minWidth: 64, minHeight: 64 },\n rotation: turnedBy,\n draw: drawBoardNode,\n size: { width: 160, height: 160 },\n defaults: () => ({}),\n label: (node, t) => nameOf(t.board.sticky, node.data?.text, t),\n})\n\n/** Text on its own; it grows as it is typed. */\nexport const textNode = defineNodeType<WhiteboardTextData>({\n type: 'text',\n component: BoardNode,\n ports: anchorPorts,\n frame: false,\n resizable: { minWidth: 24, minHeight: 24 },\n rotation: turnedBy,\n draw: drawBoardNode,\n size: { width: 200, height: 40 },\n defaults: () => ({}),\n label: (node, t) => nameOf(t.board.text, node.data?.text, t),\n})\n\n/** A pen stroke; it stretches with its box. */\nexport const inkNode = defineNodeType<WhiteboardInkData>({\n type: 'ink',\n component: BoardNode,\n frame: false,\n resizable: { minWidth: 8, minHeight: 8 },\n rotation: turnedBy,\n draw: drawBoardNode,\n defaults: () => ({ points: [], box: [0, 0] }),\n label: (_, t) => t.board.ink,\n})\n\n/** An image; `data.src` is its address. */\nexport const imageNode = defineNodeType<WhiteboardImageData>({\n type: 'image',\n component: BoardNode,\n ports: anchorPorts,\n frame: false,\n resizable: { minWidth: 24, minHeight: 24 },\n rotation: turnedBy,\n draw: drawBoardNode,\n size: { width: 240, height: 180 },\n defaults: () => ({ src: '' }),\n label: (node, t) => nameOf(t.board.image, node.data?.alt, t),\n})\n\n/** The node type and data each drawing tool adds. */\nfunction kindOf(tool: WhiteboardShapeTool): { type: string; data: FlowData } {\n if (tool === 'sticky' || tool === 'text') return { type: tool, data: {} }\n return { type: 'shape', data: { shape: tool } }\n}\n\nfunction nodeFor(tool: WhiteboardShapeTool, rect: Rect): FlowNode {\n // text grows with what is typed: it keeps no height\n const height = tool === 'text' ? undefined : rect.height\n return { id: createId(), x: rect.x, y: rect.y, width: rect.width, height, ...kindOf(tool) }\n}\n\nconst SIZES: Record<WhiteboardShapeTool, { width: number; height: number }> = {\n rect: { width: 160, height: 100 },\n ellipse: { width: 140, height: 100 },\n diamond: { width: 140, height: 120 },\n sticky: { width: 160, height: 160 },\n text: { width: 200, height: 40 },\n}\n\n/** After a tool put down a node: back to select unless kept, and notes and text open to be written in. */\nfunction added(ctx: FlowEditorContext, tool: WhiteboardShapeTool, id: string) {\n const state = boardState(ctx)\n if (!state.locked.value) state.tool.value = 'select'\n if (tool === 'sticky' || tool === 'text') state.editing.value = id\n}\n\nexport interface WhiteboardKitOptions {\n /**\n * The current tool, to share with your own toolbar. Made inside the kit\n * when left out.\n */\n tool?: Ref<WhiteboardTool>\n /** The built-in toolbar at the top of the editor. Defaults to `true`. */\n toolbar?: boolean\n /**\n * Turns an image file that is dropped, pasted or picked into the `src` it\n * is kept as, e.g. by uploading it and returning its address. Without it,\n * images are kept in the document as data URLs.\n */\n upload?: WhiteboardUpload\n}\n\n/**\n * The whiteboard: shape, sticky note and text nodes, the `connector` edge,\n * the drawing tools with their keys, and a toolbar. Add it after `flowKit()`.\n */\nexport function whiteboardKit(options: WhiteboardKitOptions = {}): FlowExtension {\n return defineFlowExtension({\n name: 'whiteboard',\n nodeTypes: [shapeNode, stickyNode, textNode, inkNode, imageNode],\n edgeTypes: [connectorEdge],\n edgeToolbarItems: connectorToolbar,\n edgeType: 'connector',\n layers: { above: [BoardDraft] },\n overlays: options.toolbar === false ? [] : [WhiteboardToolbar],\n\n setup(ctx) {\n const state = boardState(ctx)\n state.upload = options.upload\n const stops: (() => void)[] = []\n if (options.tool) {\n const outer = options.tool\n state.tool.value = outer.value\n stops.push(\n watch(outer, (tool) => (state.tool.value = tool)),\n watch(state.tool, (tool) => (outer.value = tool)),\n )\n }\n // the cursor says what a press will do\n stops.push(\n watch(\n state.tool,\n (tool) => {\n const root = ctx.root.value\n if (root) root.dataset.boardTool = tool\n },\n { immediate: true },\n ),\n watch(state.panning, (panning) => {\n const root = ctx.root.value\n if (root) root.toggleAttribute('data-board-panning', panning)\n }),\n )\n // images dropped on the board or pasted into it\n const root = ctx.root.value\n const onDragOver = (event: DragEvent) => {\n if (ctx.readonly.value || !event.dataTransfer?.types.includes('Files')) return\n event.preventDefault()\n event.dataTransfer.dropEffect = 'copy'\n }\n const onDrop = (event: DragEvent) => {\n const files = imageFiles(event.dataTransfer?.files)\n if (ctx.readonly.value || !files.length) return\n event.preventDefault()\n const at = ctx.toCanvas({ x: event.clientX, y: event.clientY })\n void insertImages(ctx, files, { at })\n }\n const onPaste = (event: ClipboardEvent) => {\n const active = document.activeElement\n if (ctx.readonly.value || !root || !active || !root.contains(active)) return\n // pasting into a field is the field's\n if (active.closest('input, textarea, [contenteditable]')) return\n const files = imageFiles(event.clipboardData?.files)\n if (!files.length) return\n event.preventDefault()\n event.stopImmediatePropagation()\n void insertImages(ctx, files)\n }\n root?.addEventListener('dragover', onDragOver)\n root?.addEventListener('drop', onDrop)\n // before the editor's own paste, which reads nodes from text\n document.addEventListener('paste', onPaste, true)\n return () => {\n for (const stop of stops) stop()\n root?.removeEventListener('dragover', onDragOver)\n root?.removeEventListener('drop', onDrop)\n document.removeEventListener('paste', onPaste, true)\n delete ctx.root.value?.dataset.boardTool\n ctx.root.value?.removeAttribute('data-board-panning')\n }\n },\n\n press(ctx, press) {\n const state = boardState(ctx)\n const tool = state.tool.value\n if (tool === 'select') return\n // a press puts down whatever text was being typed\n state.editing.value = null\n if (tool === 'hand') {\n const start = { x: press.event.clientX, y: press.event.clientY }\n const origin = { ...ctx.viewport.value }\n state.panning.value = true\n const stop = () => (state.panning.value = false)\n return {\n end: stop,\n cancel: stop,\n move(_, event) {\n ctx.setViewport({\n ...origin,\n x: origin.x + event.clientX - start.x,\n y: origin.y + event.clientY - start.y,\n })\n },\n }\n }\n if (tool === 'pen') return drawInk(ctx, press.event)\n if (tool === 'eraser') return erase(ctx, press.point)\n const start = press.point\n const rectTo = (point: Point, square: boolean) => {\n let width = Math.abs(point.x - start.x)\n let height = Math.abs(point.y - start.y)\n if (square) width = height = Math.max(width, height)\n return {\n x: point.x < start.x ? start.x - width : start.x,\n y: point.y < start.y ? start.y - height : start.y,\n width,\n height,\n }\n }\n const done = (rect: Rect | null) => {\n state.draft.value = null\n if (!rect) return\n const node = nodeFor(tool, rect)\n ctx.transact((tr) => tr.addNode(node))\n ctx.selection.value = { nodes: [node.id], edges: [] }\n ctx.announce(format(ctx.messages.value.added, { node: ctx.messages.value.board[tool] }))\n added(ctx, tool, node.id)\n }\n return {\n move(point, event) {\n state.draft.value = { ...rectTo(point, event.shiftKey), tool }\n },\n end(point, event) {\n const rect = rectTo(point, event.shiftKey)\n const min = 8 / ctx.viewport.value.zoom\n if (rect.width < min && rect.height < min) {\n // a click: the default size, centred where it was clicked\n const size = SIZES[tool]\n done({ x: start.x - size.width / 2, y: start.y - size.height / 2, ...size })\n } else {\n done({\n ...rect,\n width: Math.max(rect.width, 24),\n height: tool === 'text' ? Math.max(rect.height, 40) : Math.max(rect.height, 24),\n })\n }\n },\n cancel: () => done(null),\n }\n },\n\n contextMenu(ctx, target) {\n if (target.kind !== 'node' || ctx.readonly.value) return []\n const t = ctx.messages.value.board\n const items: ContextMenuItem[] = []\n const node = target.node\n if (BOARD_TYPES.has(node.type ?? '') && ctx.selection.value.nodes.length <= 1) {\n const rotation = (node.data?.rotation as number | undefined) ?? 0\n const rotate = (to: number) => {\n ctx.transact((tr) => tr.updateData(node.id, { rotation: to || undefined }))\n ctx.announce(format(t.turned, { degrees: to }))\n }\n items.push(\n {\n key: 'rotate',\n label: t.rotate,\n icon: RotateCwIcon,\n divided: true,\n disabled: !!node.locked,\n run: () => rotate((rotation + 90) % 360),\n },\n {\n key: 'unrotate',\n label: t.unrotate,\n disabled: !rotation || !!node.locked,\n run: () => rotate(0),\n },\n )\n }\n return items\n },\n\n keymap(ctx) {\n const state = boardState(ctx)\n const pick = (tool: WhiteboardTool) => () => {\n state.tool.value = tool\n state.locked.value = false\n const t = ctx.messages.value.board\n ctx.announce(format(t.picked, { tool: t[tool] }))\n }\n const keys: Record<string, () => boolean | void> = {}\n for (const [tool, key] of Object.entries(whiteboardKeys))\n keys[key] = pick(tool as WhiteboardTool)\n return {\n ...keys,\n [imageKey]: () => {\n if (ctx.readonly.value) return false\n pickImages(ctx)\n },\n enter: () => {\n if (ctx.readonly.value) return false\n const tool = state.tool.value\n // with a drawing tool, Enter puts one down: after the selected node, or mid-view\n if (tool !== 'select' && tool !== 'hand' && tool !== 'pen' && tool !== 'eraser') {\n const { type, data } = kindOf(tool)\n // text keeps no height, as when drawn\n const size =\n tool === 'text' ? { width: SIZES.text.width, height: undefined } : SIZES[tool]\n // notes and text take focus in their own field\n const writes = tool === 'sticky' || tool === 'text'\n const id = ctx.insertNode(type, { data, size, focus: !writes })\n if (id) added(ctx, tool, id)\n return\n }\n // otherwise it writes in the selected note or shape\n const { nodes, edges } = ctx.selection.value\n if (nodes.length !== 1 || edges.length || ctx.readonly.value) return false\n const node = ctx.doc.value.nodes.find((n) => n.id === nodes[0])\n if (!node || !['shape', 'sticky', 'text'].includes(node.type ?? '')) return false\n state.editing.value = node.id\n },\n escape: () => {\n if (state.tool.value === 'select') return false\n state.tool.value = 'select'\n },\n }\n },\n })\n}\n\nexport { connectorEdge }\n"],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ // src/whiteboard/model.ts
2
+ /** Keys for the tools. */
3
+ var whiteboardKeys = {
4
+ select: "v",
5
+ hand: "h",
6
+ rect: "r",
7
+ ellipse: "o",
8
+ diamond: "d",
9
+ sticky: "s",
10
+ text: "t",
11
+ pen: "p",
12
+ eraser: "x"
13
+ };
14
+ /** The key that inserts an image. */
15
+ var imageKey = "i";
16
+ var SIDES = [
17
+ "top",
18
+ "right",
19
+ "bottom",
20
+ "left"
21
+ ];
22
+ /**
23
+ * Four anchors, one on each side. Each is an output and an input at the
24
+ * same spot, so a connector can start and end on any of them. Edges in
25
+ * data go from an output (`right`) to an input (`left-in`). The order puts
26
+ * `right` and `left-in` first, so a node added after another joins it left
27
+ * to right.
28
+ */
29
+ var anchorPorts = [...[
30
+ "right",
31
+ "bottom",
32
+ "left",
33
+ "top"
34
+ ].map((side) => ({
35
+ id: side,
36
+ kind: "output",
37
+ side
38
+ })), ...[
39
+ "left",
40
+ "top",
41
+ "right",
42
+ "bottom"
43
+ ].map((side) => ({
44
+ id: `${side}-in`,
45
+ kind: "input",
46
+ side,
47
+ max: Number.POSITIVE_INFINITY
48
+ }))];
49
+ var anchorSides = SIDES;
50
+ export { anchorPorts, anchorSides, imageKey, whiteboardKeys };
51
+
52
+ //# sourceMappingURL=model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.js","names":[],"sources":["../../src/whiteboard/model.ts"],"sourcesContent":["import type { Tone } from 'dou-ui'\nimport type { FlowPortSpec } from '../core/model'\n\n/** What a press on the board does. */\nexport type WhiteboardTool =\n 'select' | 'hand' | 'rect' | 'ellipse' | 'diamond' | 'sticky' | 'text' | 'pen' | 'eraser'\n\n/** The tools that put down a node with a box. */\nexport type WhiteboardShapeTool = Exclude<WhiteboardTool, 'select' | 'hand' | 'pen' | 'eraser'>\n\nexport type WhiteboardShape = 'rect' | 'ellipse' | 'diamond'\n\n/** What every board node can carry. */\nexport interface WhiteboardNodeData {\n [key: string]: unknown\n /** Degrees clockwise around the node's centre. */\n rotation?: number\n}\n\nexport interface WhiteboardShapeData extends WhiteboardNodeData {\n shape: WhiteboardShape\n text?: string\n /** `neutral` by default. */\n tone?: Tone\n}\n\nexport interface WhiteboardStickyData extends WhiteboardNodeData {\n text?: string\n /** `warning` (yellow) by default. */\n tone?: Tone\n}\n\nexport interface WhiteboardTextData extends WhiteboardNodeData {\n text?: string\n}\n\n/** A pen stroke. */\nexport interface WhiteboardInkData extends WhiteboardNodeData {\n /**\n * The points as `x, y, pressure` triples, flat, in the node's own units:\n * `0, 0` is its top left corner when it is `box` in size.\n */\n points: number[]\n /** The size the points were drawn at; the stroke stretches with the node. */\n box: [number, number]\n /**\n * The stroke's width drawn with a mouse or finger. `4` by default; a pen\n * makes it 0.4 to 1.6 times as wide as it presses lighter or harder.\n */\n size?: number\n /** The text color when left out. */\n tone?: Tone\n}\n\n/** How a connector runs: one straight line, right-angled turns, or a smooth curve. */\nexport type WhiteboardRoute = 'straight' | 'elbow' | 'curve'\nexport type WhiteboardDash = 'solid' | 'dashed' | 'dotted' | 'dash-dot'\n/** Which ends of a connector have an arrowhead. */\nexport type WhiteboardArrow = 'none' | 'end' | 'both'\n\n/** A `connector` edge's look. */\nexport interface WhiteboardConnectorData {\n [key: string]: unknown\n /** `straight` by default. */\n route?: WhiteboardRoute\n /** `solid` by default. */\n dash?: WhiteboardDash\n /** `end` by default. */\n arrow?: WhiteboardArrow\n /** Where an `elbow` connector's middle run sits, from 0 to 1; set by dragging it. */\n bend?: number\n}\n\nexport interface WhiteboardImageData extends WhiteboardNodeData {\n src: string\n /** Read by screen readers. */\n alt?: string\n}\n\n/** Keys for the tools. */\nexport const whiteboardKeys: Record<WhiteboardTool, string> = {\n select: 'v',\n hand: 'h',\n rect: 'r',\n ellipse: 'o',\n diamond: 'd',\n sticky: 's',\n text: 't',\n pen: 'p',\n eraser: 'x',\n}\n\n/** The key that inserts an image. */\nexport const imageKey = 'i'\n\nconst SIDES = ['top', 'right', 'bottom', 'left'] as const\n\n/**\n * Four anchors, one on each side. Each is an output and an input at the\n * same spot, so a connector can start and end on any of them. Edges in\n * data go from an output (`right`) to an input (`left-in`). The order puts\n * `right` and `left-in` first, so a node added after another joins it left\n * to right.\n */\nexport const anchorPorts: FlowPortSpec[] = [\n ...(['right', 'bottom', 'left', 'top'] as const).map((side) => ({\n id: side,\n kind: 'output' as const,\n side,\n })),\n ...(['left', 'top', 'right', 'bottom'] as const).map((side) => ({\n id: `${side}-in`,\n kind: 'input' as const,\n side,\n max: Number.POSITIVE_INFINITY,\n })),\n]\n\nexport const anchorSides = SIDES\n"],"mappings":";;AAgFA,IAAa,iBAAiD;CAC5D,QAAQ;CACR,MAAM;CACN,MAAM;CACN,SAAS;CACT,SAAS;CACT,QAAQ;CACR,MAAM;CACN,KAAK;CACL,QAAQ;AACV;;AAGA,IAAa,WAAW;AAExB,IAAM,QAAQ;CAAC;CAAO;CAAS;CAAU;AAAM;;;;;;;;AAS/C,IAAa,cAA8B,CACzC,GAAI;CAAC;CAAS;CAAU;CAAQ;AAAK,CAAC,CAAW,KAAK,UAAU;CAC9D,IAAI;CACJ,MAAM;CACN;AACF,EAAE,GACF,GAAI;CAAC;CAAQ;CAAO;CAAS;AAAQ,CAAC,CAAW,KAAK,UAAU;CAC9D,IAAI,GAAG,KAAK;CACZ,MAAM;CACN;CACA,KAAK,OAAO;AACd,EAAE,CACJ;AAEA,IAAa,cAAc"}
@@ -0,0 +1,88 @@
1
+ import { format } from "../locale.js";
2
+ import { createId } from "../core/model.js";
3
+ import { follow, inkHit, strokeNode } from "./ink.js";
4
+ import { boardState } from "./state.js";
5
+ // src/whiteboard/pen.ts
6
+ /** The points of a pointer event, with the ones the browser merged into it. */
7
+ function samples(ctx, event) {
8
+ const merged = event.getCoalescedEvents?.() ?? [];
9
+ return (merged.length ? merged : [event]).map((e) => ({
10
+ ...ctx.toCanvas({
11
+ x: e.clientX,
12
+ y: e.clientY
13
+ }),
14
+ pressure: e.pointerType === "pen" && e.pressure > 0 ? e.pressure : .5
15
+ }));
16
+ }
17
+ /** Draws a stroke with the pen; it becomes an `ink` node once let go. */
18
+ function drawInk(ctx, event) {
19
+ const state = boardState(ctx);
20
+ const gap = 1 / ctx.viewport.value.zoom;
21
+ const points = [];
22
+ for (const point of samples(ctx, event)) follow(points, point, gap);
23
+ state.ink.value = [...points];
24
+ return {
25
+ move(_, e) {
26
+ for (const point of samples(ctx, e)) follow(points, point, gap);
27
+ state.ink.value = [...points];
28
+ },
29
+ end(_, e) {
30
+ state.ink.value = null;
31
+ const [last] = samples(ctx, e).slice(-1);
32
+ const tail = points[points.length - 1];
33
+ if (last && tail && Math.hypot(last.x - tail.x, last.y - tail.y) >= gap) points.push(last);
34
+ const node = strokeNode(createId(), points);
35
+ ctx.transact((tr) => tr.addNode(node));
36
+ ctx.announce(format(ctx.messages.value.added, { node: ctx.messages.value.board.ink }));
37
+ },
38
+ cancel() {
39
+ state.ink.value = null;
40
+ }
41
+ };
42
+ }
43
+ /** Wipes the strokes the eraser passes over; they go once it lets go. */
44
+ function erase(ctx, start) {
45
+ const state = boardState(ctx);
46
+ const radius = 8 / ctx.viewport.value.zoom;
47
+ const touched = /* @__PURE__ */ new Set();
48
+ const at = (point) => {
49
+ state.eraser.value = point;
50
+ let changed = false;
51
+ for (const node of ctx.doc.value.nodes) {
52
+ if (node.type !== "ink" || node.locked || touched.has(node.id)) continue;
53
+ if (!inkHit(node, point, radius)) continue;
54
+ touched.add(node.id);
55
+ changed = true;
56
+ }
57
+ if (changed) state.erasing.value = new Set(touched);
58
+ };
59
+ let last = start;
60
+ const sweep = (point) => {
61
+ const steps = Math.max(1, Math.ceil(Math.hypot(point.x - last.x, point.y - last.y) / radius));
62
+ for (let i = 1; i <= steps; i++) at({
63
+ x: last.x + (point.x - last.x) * i / steps,
64
+ y: last.y + (point.y - last.y) * i / steps
65
+ });
66
+ last = point;
67
+ };
68
+ at(start);
69
+ const clear = () => {
70
+ state.eraser.value = null;
71
+ state.erasing.value = /* @__PURE__ */ new Set();
72
+ };
73
+ return {
74
+ move: sweep,
75
+ end() {
76
+ clear();
77
+ if (!touched.size) return;
78
+ ctx.transact((tr) => {
79
+ for (const id of touched) tr.removeNode(id);
80
+ });
81
+ ctx.announce(format(ctx.messages.value.board.erased, { count: touched.size }));
82
+ },
83
+ cancel: clear
84
+ };
85
+ }
86
+ export { drawInk, erase };
87
+
88
+ //# sourceMappingURL=pen.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pen.js","names":[],"sources":["../../src/whiteboard/pen.ts"],"sourcesContent":["import type { FlowEditorContext } from '../context'\nimport type { FlowPressHandler } from '../core/extension'\nimport { createId, type Point } from '../core/model'\nimport { format } from '../locale'\nimport { follow, inkHit, strokeNode, type InkPoint } from './ink'\nimport { boardState } from './state'\n\n/** The points of a pointer event, with the ones the browser merged into it. */\nfunction samples(ctx: FlowEditorContext, event: PointerEvent): InkPoint[] {\n const merged = event.getCoalescedEvents?.() ?? []\n return (merged.length ? merged : [event]).map((e) => ({\n ...ctx.toCanvas({ x: e.clientX, y: e.clientY }),\n // only a pen tells pressure apart; a mouse or finger draws the plain width\n pressure: e.pointerType === 'pen' && e.pressure > 0 ? e.pressure : 0.5,\n }))\n}\n\n/** Draws a stroke with the pen; it becomes an `ink` node once let go. */\nexport function drawInk(ctx: FlowEditorContext, event: PointerEvent): FlowPressHandler {\n const state = boardState(ctx)\n const gap = 1 / ctx.viewport.value.zoom\n const points: InkPoint[] = []\n for (const point of samples(ctx, event)) follow(points, point, gap)\n state.ink.value = [...points]\n return {\n move(_, e) {\n for (const point of samples(ctx, e)) follow(points, point, gap)\n state.ink.value = [...points]\n },\n end(_, e) {\n state.ink.value = null\n // the lag that smooths the line stops short of where it was let go\n const [last] = samples(ctx, e).slice(-1)\n const tail = points[points.length - 1]\n if (last && tail && Math.hypot(last.x - tail.x, last.y - tail.y) >= gap) points.push(last)\n const node = strokeNode(createId(), points)\n ctx.transact((tr) => tr.addNode(node))\n ctx.announce(format(ctx.messages.value.added, { node: ctx.messages.value.board.ink }))\n },\n cancel() {\n state.ink.value = null\n },\n }\n}\n\n/** Wipes the strokes the eraser passes over; they go once it lets go. */\nexport function erase(ctx: FlowEditorContext, start: Point): FlowPressHandler {\n const state = boardState(ctx)\n const radius = 8 / ctx.viewport.value.zoom\n const touched = new Set<string>()\n const at = (point: Point) => {\n state.eraser.value = point\n let changed = false\n for (const node of ctx.doc.value.nodes) {\n if (node.type !== 'ink' || node.locked || touched.has(node.id)) continue\n if (!inkHit(node, point, radius)) continue\n touched.add(node.id)\n changed = true\n }\n if (changed) state.erasing.value = new Set(touched)\n }\n // between two samples, so a quick swipe misses nothing\n let last = start\n const sweep = (point: Point) => {\n const steps = Math.max(1, Math.ceil(Math.hypot(point.x - last.x, point.y - last.y) / radius))\n for (let i = 1; i <= steps; i++)\n at({\n x: last.x + ((point.x - last.x) * i) / steps,\n y: last.y + ((point.y - last.y) * i) / steps,\n })\n last = point\n }\n at(start)\n const clear = () => {\n state.eraser.value = null\n state.erasing.value = new Set()\n }\n return {\n move: sweep,\n end() {\n clear()\n if (!touched.size) return\n ctx.transact((tr) => {\n for (const id of touched) tr.removeNode(id)\n })\n ctx.announce(format(ctx.messages.value.board.erased, { count: touched.size }))\n },\n cancel: clear,\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import { ref, shallowRef } from "vue";
2
+ // src/whiteboard/state.ts
3
+ var states = /* @__PURE__ */ new WeakMap();
4
+ /** The board's state in an editor, made on first use. */
5
+ function boardState(context) {
6
+ let state = states.get(context);
7
+ if (!state) {
8
+ state = {
9
+ tool: ref("select"),
10
+ locked: ref(false),
11
+ panning: ref(false),
12
+ editing: shallowRef(null),
13
+ draft: shallowRef(null),
14
+ ink: shallowRef(null),
15
+ erasing: shallowRef(/* @__PURE__ */ new Set()),
16
+ eraser: shallowRef(null),
17
+ handles: shallowRef(null),
18
+ uploading: ref(0)
19
+ };
20
+ states.set(context, state);
21
+ }
22
+ return state;
23
+ }
24
+ export { boardState };
25
+
26
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.js","names":[],"sources":["../../src/whiteboard/state.ts"],"sourcesContent":["import { ref, shallowRef, type Ref, type ShallowRef } from 'vue'\nimport type { FlowEditorContext } from '../context'\nimport type { Point, Rect } from '../core/model'\nimport type { WhiteboardUpload } from './image'\nimport type { InkPoint } from './ink'\nimport type { WhiteboardShapeTool, WhiteboardTool } from './model'\n\n/** What the board's pieces share within one editor. */\nexport interface WhiteboardState {\n tool: Ref<WhiteboardTool>\n /** Keeps the tool after drawing, instead of going back to select. */\n locked: Ref<boolean>\n /** The hand is dragging the view. */\n panning: Ref<boolean>\n /** The node whose text is being typed. */\n editing: ShallowRef<string | null>\n /** The box being drawn, with the tool drawing it. */\n draft: ShallowRef<(Rect & { tool: WhiteboardShapeTool }) | null>\n /** The stroke being drawn with the pen, in canvas coordinates. */\n ink: ShallowRef<InkPoint[] | null>\n /** Strokes the eraser has touched, removed once it lets go. */\n erasing: ShallowRef<ReadonlySet<string>>\n /** Where the eraser is, while it is pressed. */\n eraser: ShallowRef<Point | null>\n /**\n * Where the turn and corner handles of the selected node are drawn: a\n * layer above every node, so a neighbour never covers them.\n */\n handles: ShallowRef<HTMLElement | null>\n /** Images being uploaded. */\n uploading: Ref<number>\n /** The kit's `upload` option. */\n upload?: WhiteboardUpload\n}\n\nconst states = new WeakMap<FlowEditorContext, WhiteboardState>()\n\n/** The board's state in an editor, made on first use. */\nexport function boardState(context: FlowEditorContext): WhiteboardState {\n let state = states.get(context)\n if (!state) {\n state = {\n tool: ref('select'),\n locked: ref(false),\n panning: ref(false),\n editing: shallowRef(null),\n draft: shallowRef(null),\n ink: shallowRef(null),\n erasing: shallowRef(new Set()),\n eraser: shallowRef(null),\n handles: shallowRef(null),\n uploading: ref(0),\n }\n states.set(context, state)\n }\n return state\n}\n"],"mappings":";;AAmCA,IAAM,yBAAS,IAAI,QAA4C;;AAG/D,SAAgB,WAAW,SAA6C;CACtE,IAAI,QAAQ,OAAO,IAAI,OAAO;CAC9B,IAAI,CAAC,OAAO;EACV,QAAQ;GACN,MAAM,IAAI,QAAQ;GAClB,QAAQ,IAAI,KAAK;GACjB,SAAS,IAAI,KAAK;GAClB,SAAS,WAAW,IAAI;GACxB,OAAO,WAAW,IAAI;GACtB,KAAK,WAAW,IAAI;GACpB,SAAS,2BAAW,IAAI,IAAI,CAAC;GAC7B,QAAQ,WAAW,IAAI;GACvB,SAAS,WAAW,IAAI;GACxB,WAAW,IAAI,CAAC;EAClB;EACA,OAAO,IAAI,SAAS,KAAK;CAC3B;CACA,OAAO;AACT"}