@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
@@ -0,0 +1,104 @@
1
+ import { format } from "../locale.js";
2
+ import { boardState } from "./state.js";
3
+ // src/whiteboard/image.ts
4
+ /** Without an upload, images are kept in the document as data URLs. */
5
+ var readAsDataUrl = (file) => new Promise((resolve, reject) => {
6
+ const reader = new FileReader();
7
+ reader.addEventListener("load", () => resolve(String(reader.result)));
8
+ reader.addEventListener("error", () => reject(reader.error));
9
+ reader.readAsDataURL(file);
10
+ });
11
+ /** An image's own size, or `null` when it can't be loaded. */
12
+ function naturalSize(src) {
13
+ return new Promise((resolve) => {
14
+ const img = new Image();
15
+ img.addEventListener("load", () => resolve({
16
+ width: img.naturalWidth,
17
+ height: img.naturalHeight
18
+ }));
19
+ img.addEventListener("error", () => resolve(null));
20
+ setTimeout(() => resolve(null), 4e3);
21
+ img.src = src;
22
+ });
23
+ }
24
+ /** New images are at most this wide or tall, in canvas units. */
25
+ var MAX_SIDE = 320;
26
+ var imageFiles = (files) => [...files ?? []].filter((file) => file.type.startsWith("image/"));
27
+ /**
28
+ * Adds an image node for each file, centred on `at` and stepped down and
29
+ * right when there are several. Files go through the kit's `upload` option.
30
+ */
31
+ async function insertImages(ctx, files, options = {}) {
32
+ const state = boardState(ctx);
33
+ const upload = options.upload ?? state.upload ?? readAsDataUrl;
34
+ const at = options.at;
35
+ const t = ctx.messages.value.board;
36
+ const root = ctx.root.value;
37
+ if (!root || ctx.readonly.value) return;
38
+ const box = root.getBoundingClientRect();
39
+ const centre = at ?? ctx.toCanvas({
40
+ x: box.left + box.width / 2,
41
+ y: box.top + box.height / 2
42
+ });
43
+ state.uploading.value += files.length;
44
+ const ids = [];
45
+ try {
46
+ await Promise.all(files.map(async (file, i) => {
47
+ let src;
48
+ let alt = file.name.replace(/\.[^.]+$/, "");
49
+ try {
50
+ const done = await upload(file);
51
+ if (typeof done === "string") src = done;
52
+ else {
53
+ src = done.src;
54
+ if (done.alt !== void 0) alt = done.alt;
55
+ }
56
+ } catch {
57
+ ctx.announce(format(t.imageFailed, { name: file.name }));
58
+ return;
59
+ }
60
+ const size = await naturalSize(src) ?? {
61
+ width: MAX_SIDE,
62
+ height: MAX_SIDE * .75
63
+ };
64
+ const scale = Math.min(1, MAX_SIDE / Math.max(size.width, size.height));
65
+ const id = ctx.insertNode("image", {
66
+ at: {
67
+ x: centre.x + i * 24,
68
+ y: centre.y + i * 24
69
+ },
70
+ data: {
71
+ src,
72
+ alt
73
+ },
74
+ size: {
75
+ width: Math.round(size.width * scale),
76
+ height: Math.round(size.height * scale)
77
+ },
78
+ focus: false
79
+ });
80
+ if (id) ids.push(id);
81
+ }));
82
+ } finally {
83
+ state.uploading.value -= files.length;
84
+ }
85
+ if (ids.length > 1) ctx.selection.value = {
86
+ nodes: ids,
87
+ edges: []
88
+ };
89
+ }
90
+ /** Opens the file picker and inserts what is picked, as `insertImages` does. */
91
+ function pickImages(ctx, options = {}) {
92
+ const input = document.createElement("input");
93
+ input.type = "file";
94
+ input.accept = "image/*";
95
+ input.multiple = true;
96
+ input.addEventListener("change", () => {
97
+ const files = imageFiles(input.files);
98
+ if (files.length) insertImages(ctx, files, options);
99
+ });
100
+ input.click();
101
+ }
102
+ export { imageFiles, insertImages, pickImages };
103
+
104
+ //# sourceMappingURL=image.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image.js","names":[],"sources":["../../src/whiteboard/image.ts"],"sourcesContent":["import type { FlowEditorContext } from '../context'\nimport type { Point } from '../core/model'\nimport { format } from '../locale'\nimport { boardState } from './state'\n\n/**\n * Turns a file into the `src` an image node keeps, e.g. by uploading it.\n * Return `{ src, alt }` to give the image its description; it is the file's\n * name otherwise.\n */\nexport type WhiteboardUpload = (file: File) => WhiteboardUploaded | Promise<WhiteboardUploaded>\n\n/** An image's address, or its address and description. */\nexport type WhiteboardUploaded = string | { src: string; alt?: string }\n\nexport interface WhiteboardImageOptions {\n /** Where the images go, in canvas units; the middle of the view by default. */\n at?: Point\n /** Overrides the kit's `upload` option for these images. */\n upload?: WhiteboardUpload\n}\n\n/** Without an upload, images are kept in the document as data URLs. */\nconst readAsDataUrl: WhiteboardUpload = (file) =>\n new Promise((resolve, reject) => {\n const reader = new FileReader()\n reader.addEventListener('load', () => resolve(String(reader.result)))\n reader.addEventListener('error', () => reject(reader.error))\n reader.readAsDataURL(file)\n })\n\n/** An image's own size, or `null` when it can't be loaded. */\nfunction naturalSize(src: string): Promise<{ width: number; height: number } | null> {\n return new Promise((resolve) => {\n const img = new Image()\n img.addEventListener('load', () =>\n resolve({ width: img.naturalWidth, height: img.naturalHeight }),\n )\n img.addEventListener('error', () => resolve(null))\n // an image that never answers gets the default size\n setTimeout(() => resolve(null), 4000)\n img.src = src\n })\n}\n\n/** New images are at most this wide or tall, in canvas units. */\nconst MAX_SIDE = 320\n\nexport const imageFiles = (files: Iterable<File> | null | undefined) =>\n [...(files ?? [])].filter((file) => file.type.startsWith('image/'))\n\n/**\n * Adds an image node for each file, centred on `at` and stepped down and\n * right when there are several. Files go through the kit's `upload` option.\n */\nexport async function insertImages(\n ctx: FlowEditorContext,\n files: File[],\n options: WhiteboardImageOptions = {},\n) {\n const state = boardState(ctx)\n const upload = options.upload ?? state.upload ?? readAsDataUrl\n const at = options.at\n const t = ctx.messages.value.board\n const root = ctx.root.value\n if (!root || ctx.readonly.value) return\n const box = root.getBoundingClientRect()\n const centre = at ?? ctx.toCanvas({ x: box.left + box.width / 2, y: box.top + box.height / 2 })\n state.uploading.value += files.length\n const ids: string[] = []\n try {\n await Promise.all(\n files.map(async (file, i) => {\n let src: string\n let alt = file.name.replace(/\\.[^.]+$/, '')\n try {\n const done = await upload(file)\n if (typeof done === 'string') src = done\n else {\n src = done.src\n if (done.alt !== undefined) alt = done.alt\n }\n } catch {\n ctx.announce(format(t.imageFailed, { name: file.name }))\n return\n }\n const size = (await naturalSize(src)) ?? { width: MAX_SIDE, height: MAX_SIDE * 0.75 }\n const scale = Math.min(1, MAX_SIDE / Math.max(size.width, size.height))\n const id = ctx.insertNode('image', {\n at: { x: centre.x + i * 24, y: centre.y + i * 24 },\n data: { src, alt },\n size: { width: Math.round(size.width * scale), height: Math.round(size.height * scale) },\n focus: false,\n })\n if (id) ids.push(id)\n }),\n )\n } finally {\n state.uploading.value -= files.length\n }\n if (ids.length > 1) ctx.selection.value = { nodes: ids, edges: [] }\n}\n\n/** Opens the file picker and inserts what is picked, as `insertImages` does. */\nexport function pickImages(ctx: FlowEditorContext, options: WhiteboardImageOptions = {}) {\n const input = document.createElement('input')\n input.type = 'file'\n input.accept = 'image/*'\n input.multiple = true\n input.addEventListener('change', () => {\n const files = imageFiles(input.files)\n if (files.length) void insertImages(ctx, files, options)\n })\n input.click()\n}\n"],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import { imageKey, whiteboardKeys } from "./model.js";
2
+ import { insertImages, pickImages } from "./image.js";
3
+ import { connectorEdge } from "./connector.js";
4
+ import { imageNode, inkNode, shapeNode, stickyNode, textNode, whiteboardKit } from "./kit.js";
5
+ export { connectorEdge, imageKey, imageNode, inkNode, insertImages, pickImages, shapeNode, stickyNode, textNode, whiteboardKeys, whiteboardKit };
@@ -0,0 +1,168 @@
1
+ import { turn } from "../core/geometry.js";
2
+ var round = (v) => Math.round(v * 10) / 10;
3
+ /** The stroke's width at a pressure; a mouse, always 0.5, gets the plain size. */
4
+ var widthAt = (size, pressure) => size * (.4 + 1.2 * pressure);
5
+ /**
6
+ * Follows the pointer with a little lag, which smooths out the shake of a
7
+ * hand; samples closer than `gap` to the last one are dropped.
8
+ */
9
+ function follow(points, next, gap) {
10
+ const last = points[points.length - 1];
11
+ if (!last) return void points.push(next);
12
+ const point = {
13
+ x: last.x + (next.x - last.x) * .5,
14
+ y: last.y + (next.y - last.y) * .5,
15
+ pressure: last.pressure + (next.pressure - last.pressure) * .5
16
+ };
17
+ if (Math.hypot(point.x - last.x, point.y - last.y) >= gap) points.push(point);
18
+ }
19
+ /** The node a finished stroke becomes: its box with room for the stroke's width. */
20
+ function strokeNode(id, points, size = 4) {
21
+ const pad = widthAt(size, 1) / 2 + 1;
22
+ let minX = Infinity;
23
+ let minY = Infinity;
24
+ let maxX = -Infinity;
25
+ let maxY = -Infinity;
26
+ for (const p of points) {
27
+ minX = Math.min(minX, p.x);
28
+ minY = Math.min(minY, p.y);
29
+ maxX = Math.max(maxX, p.x);
30
+ maxY = Math.max(maxY, p.y);
31
+ }
32
+ const x = minX - pad;
33
+ const y = minY - pad;
34
+ const width = round(maxX - minX + pad * 2);
35
+ const height = round(maxY - minY + pad * 2);
36
+ const flat = [];
37
+ for (const p of points) flat.push(round(p.x - x), round(p.y - y), round(p.pressure));
38
+ return {
39
+ id,
40
+ type: "ink",
41
+ x: round(x),
42
+ y: round(y),
43
+ width,
44
+ height,
45
+ data: {
46
+ points: flat,
47
+ box: [width, height]
48
+ }
49
+ };
50
+ }
51
+ /**
52
+ * A stroke's points in a box of `width` by `height`: the line itself
53
+ * stretches, but the room left around it for the pen's width does not, so
54
+ * a stretched stroke keeps its thickness.
55
+ */
56
+ function fitInk(points, box, width, height, size = 4) {
57
+ const pad = widthAt(size, 1) / 2 + 1;
58
+ const axis = (from, to) => {
59
+ const span = from - pad * 2;
60
+ return span > .01 ? {
61
+ scale: (to - pad * 2) / span,
62
+ shift: 0
63
+ } : {
64
+ scale: 1,
65
+ shift: (to - from) / 2
66
+ };
67
+ };
68
+ const ax = axis(box[0], width);
69
+ const ay = axis(box[1], height);
70
+ if (ax.scale === 1 && ay.scale === 1 && !ax.shift && !ay.shift) return [...points];
71
+ return points.map((p) => ({
72
+ x: pad + (p.x - pad) * ax.scale + ax.shift,
73
+ y: pad + (p.y - pad) * ay.scale + ay.shift,
74
+ pressure: p.pressure
75
+ }));
76
+ }
77
+ /** The triples of `data.points` as points. */
78
+ function unpack(flat) {
79
+ const out = [];
80
+ for (let i = 0; i + 2 < flat.length; i += 3) out.push({
81
+ x: flat[i],
82
+ y: flat[i + 1],
83
+ pressure: flat[i + 2]
84
+ });
85
+ return out;
86
+ }
87
+ var fmt = (p) => `${round(p.x)} ${round(p.y)}`;
88
+ /**
89
+ * The outline of a stroke as a filled path: each side offset by half the
90
+ * width at that point, joined by round ends, with curves through the middles
91
+ * of the samples.
92
+ */
93
+ function inkOutline(points, size = 4) {
94
+ if (!points.length) return "";
95
+ const first = points[0];
96
+ if (points.length === 1) {
97
+ const r = widthAt(size, first.pressure) / 2;
98
+ return `M${round(first.x - r)} ${round(first.y)}a${r} ${r} 0 1 0 ${r * 2} 0a${r} ${r} 0 1 0 ${-r * 2} 0Z`;
99
+ }
100
+ const left = [];
101
+ const right = [];
102
+ for (let i = 0; i < points.length; i++) {
103
+ const a = points[Math.max(0, i - 1)];
104
+ const b = points[Math.min(points.length - 1, i + 1)];
105
+ const p = points[i];
106
+ const length = Math.hypot(b.x - a.x, b.y - a.y) || 1;
107
+ const nx = -(b.y - a.y) / length;
108
+ const ny = (b.x - a.x) / length;
109
+ const r = widthAt(size, p.pressure) / 2;
110
+ left.push({
111
+ x: p.x + nx * r,
112
+ y: p.y + ny * r
113
+ });
114
+ right.push({
115
+ x: p.x - nx * r,
116
+ y: p.y - ny * r
117
+ });
118
+ }
119
+ const side = (list) => {
120
+ let d = "";
121
+ for (let i = 1; i < list.length - 1; i++) {
122
+ const p = list[i];
123
+ const n = list[i + 1];
124
+ d += `Q${fmt(p)} ${fmt({
125
+ x: (p.x + n.x) / 2,
126
+ y: (p.y + n.y) / 2
127
+ })}`;
128
+ }
129
+ return d + `L${fmt(list[list.length - 1])}`;
130
+ };
131
+ const last = points[points.length - 1];
132
+ const rEnd = round(widthAt(size, last.pressure) / 2);
133
+ const rStart = round(widthAt(size, first.pressure) / 2);
134
+ right.reverse();
135
+ return `M${fmt(left[0])}${side(left)}A${rEnd} ${rEnd} 0 0 0 ${fmt(right[0])}${side(right)}A${rStart} ${rStart} 0 0 0 ${fmt(left[0])}Z`;
136
+ }
137
+ function segmentDistance(p, a, b) {
138
+ const dx = b.x - a.x;
139
+ const dy = b.y - a.y;
140
+ const length = dx * dx + dy * dy;
141
+ const t = length ? Math.max(0, Math.min(1, ((p.x - a.x) * dx + (p.y - a.y) * dy) / length)) : 0;
142
+ return Math.hypot(p.x - a.x - t * dx, p.y - a.y - t * dy);
143
+ }
144
+ /** Whether a stroke passes within `radius` of a canvas point. */
145
+ function inkHit(node, point, radius) {
146
+ const data = node.data;
147
+ if (!data?.points?.length) return false;
148
+ const width = node.width ?? data.box[0];
149
+ const height = node.height ?? data.box[1];
150
+ const centre = {
151
+ x: node.x + width / 2,
152
+ y: node.y + height / 2
153
+ };
154
+ const local = turn(point, centre, -(data.rotation ?? 0));
155
+ const size = data.size ?? 4;
156
+ const points = fitInk(unpack(data.points), data.box, width, height, size).map((p) => ({
157
+ x: node.x + p.x,
158
+ y: node.y + p.y,
159
+ pressure: p.pressure
160
+ }));
161
+ const reach = radius + size / 2;
162
+ if (points.length === 1) return Math.hypot(local.x - points[0].x, local.y - points[0].y) <= reach;
163
+ for (let i = 1; i < points.length; i++) if (segmentDistance(local, points[i - 1], points[i]) <= reach) return true;
164
+ return false;
165
+ }
166
+ export { fitInk, follow, inkHit, inkOutline, strokeNode, unpack };
167
+
168
+ //# sourceMappingURL=ink.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ink.js","names":[],"sources":["../../src/whiteboard/ink.ts"],"sourcesContent":["import { turn } from '../core/geometry'\nimport type { FlowNode, Point } from '../core/model'\nimport type { WhiteboardInkData } from './model'\n\n/** A sampled point: canvas position and pen pressure from 0 to 1. */\nexport interface InkPoint extends Point {\n pressure: number\n}\n\nexport const INK_SIZE = 4\n\nconst round = (v: number) => Math.round(v * 10) / 10\n\n/** The stroke's width at a pressure; a mouse, always 0.5, gets the plain size. */\nconst widthAt = (size: number, pressure: number) => size * (0.4 + 1.2 * pressure)\n\n/**\n * Follows the pointer with a little lag, which smooths out the shake of a\n * hand; samples closer than `gap` to the last one are dropped.\n */\nexport function follow(points: InkPoint[], next: InkPoint, gap: number) {\n const last = points[points.length - 1]\n if (!last) return void points.push(next)\n const point = {\n x: last.x + (next.x - last.x) * 0.5,\n y: last.y + (next.y - last.y) * 0.5,\n pressure: last.pressure + (next.pressure - last.pressure) * 0.5,\n }\n if (Math.hypot(point.x - last.x, point.y - last.y) >= gap) points.push(point)\n}\n\n/** The node a finished stroke becomes: its box with room for the stroke's width. */\nexport function strokeNode(\n id: string,\n points: InkPoint[],\n size = INK_SIZE,\n): FlowNode<WhiteboardInkData> {\n const pad = widthAt(size, 1) / 2 + 1\n let minX = Infinity\n let minY = Infinity\n let maxX = -Infinity\n let maxY = -Infinity\n for (const p of points) {\n minX = Math.min(minX, p.x)\n minY = Math.min(minY, p.y)\n maxX = Math.max(maxX, p.x)\n maxY = Math.max(maxY, p.y)\n }\n const x = minX - pad\n const y = minY - pad\n const width = round(maxX - minX + pad * 2)\n const height = round(maxY - minY + pad * 2)\n const flat: number[] = []\n for (const p of points) flat.push(round(p.x - x), round(p.y - y), round(p.pressure))\n return {\n id,\n type: 'ink',\n x: round(x),\n y: round(y),\n width,\n height,\n data: { points: flat, box: [width, height] },\n }\n}\n\n/**\n * A stroke's points in a box of `width` by `height`: the line itself\n * stretches, but the room left around it for the pen's width does not, so\n * a stretched stroke keeps its thickness.\n */\nexport function fitInk(\n points: readonly InkPoint[],\n box: readonly [number, number],\n width: number,\n height: number,\n size = INK_SIZE,\n): InkPoint[] {\n const pad = widthAt(size, 1) / 2 + 1\n const axis = (from: number, to: number) => {\n const span = from - pad * 2\n // a flat stroke has no span to stretch: it stays centred\n return span > 0.01\n ? { scale: (to - pad * 2) / span, shift: 0 }\n : { scale: 1, shift: (to - from) / 2 }\n }\n const ax = axis(box[0], width)\n const ay = axis(box[1], height)\n if (ax.scale === 1 && ay.scale === 1 && !ax.shift && !ay.shift) return [...points]\n return points.map((p) => ({\n x: pad + (p.x - pad) * ax.scale + ax.shift,\n y: pad + (p.y - pad) * ay.scale + ay.shift,\n pressure: p.pressure,\n }))\n}\n\n/** The triples of `data.points` as points. */\nexport function unpack(flat: readonly number[]): InkPoint[] {\n const out: InkPoint[] = []\n for (let i = 0; i + 2 < flat.length; i += 3)\n out.push({ x: flat[i]!, y: flat[i + 1]!, pressure: flat[i + 2]! })\n return out\n}\n\nconst fmt = (p: Point) => `${round(p.x)} ${round(p.y)}`\n\n/**\n * The outline of a stroke as a filled path: each side offset by half the\n * width at that point, joined by round ends, with curves through the middles\n * of the samples.\n */\nexport function inkOutline(points: readonly InkPoint[], size = INK_SIZE): string {\n if (!points.length) return ''\n const first = points[0]!\n if (points.length === 1) {\n const r = widthAt(size, first.pressure) / 2\n return `M${round(first.x - r)} ${round(first.y)}a${r} ${r} 0 1 0 ${r * 2} 0a${r} ${r} 0 1 0 ${-r * 2} 0Z`\n }\n const left: Point[] = []\n const right: Point[] = []\n for (let i = 0; i < points.length; i++) {\n const a = points[Math.max(0, i - 1)]!\n const b = points[Math.min(points.length - 1, i + 1)]!\n const p = points[i]!\n const length = Math.hypot(b.x - a.x, b.y - a.y) || 1\n // the normal to the direction through the neighbours\n const nx = -(b.y - a.y) / length\n const ny = (b.x - a.x) / length\n const r = widthAt(size, p.pressure) / 2\n left.push({ x: p.x + nx * r, y: p.y + ny * r })\n right.push({ x: p.x - nx * r, y: p.y - ny * r })\n }\n const side = (list: Point[]) => {\n let d = ''\n for (let i = 1; i < list.length - 1; i++) {\n const p = list[i]!\n const n = list[i + 1]!\n d += `Q${fmt(p)} ${fmt({ x: (p.x + n.x) / 2, y: (p.y + n.y) / 2 })}`\n }\n return d + `L${fmt(list[list.length - 1]!)}`\n }\n const last = points[points.length - 1]!\n const rEnd = round(widthAt(size, last.pressure) / 2)\n const rStart = round(widthAt(size, first.pressure) / 2)\n right.reverse()\n return (\n `M${fmt(left[0]!)}${side(left)}` +\n `A${rEnd} ${rEnd} 0 0 0 ${fmt(right[0]!)}${side(right)}` +\n `A${rStart} ${rStart} 0 0 0 ${fmt(left[0]!)}Z`\n )\n}\n\nfunction segmentDistance(p: Point, a: Point, b: Point) {\n const dx = b.x - a.x\n const dy = b.y - a.y\n const length = dx * dx + dy * dy\n const t = length ? Math.max(0, Math.min(1, ((p.x - a.x) * dx + (p.y - a.y) * dy) / length)) : 0\n return Math.hypot(p.x - a.x - t * dx, p.y - a.y - t * dy)\n}\n\n/** Whether a stroke passes within `radius` of a canvas point. */\nexport function inkHit(node: FlowNode, point: Point, radius: number): boolean {\n const data = node.data as WhiteboardInkData | undefined\n if (!data?.points?.length) return false\n const width = node.width ?? data.box[0]\n const height = node.height ?? data.box[1]\n // into the stroke's own units: undo the turn, then the stretch\n const centre = { x: node.x + width / 2, y: node.y + height / 2 }\n const local = turn(point, centre, -(data.rotation ?? 0))\n const size = data.size ?? INK_SIZE\n const points = fitInk(unpack(data.points), data.box, width, height, size).map((p) => ({\n x: node.x + p.x,\n y: node.y + p.y,\n pressure: p.pressure,\n }))\n const reach = radius + size / 2\n if (points.length === 1)\n return Math.hypot(local.x - points[0]!.x, local.y - points[0]!.y) <= reach\n for (let i = 1; i < points.length; i++)\n if (segmentDistance(local, points[i - 1]!, points[i]!) <= reach) return true\n return false\n}\n"],"mappings":"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"}