@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.
- package/LICENSE +21 -0
- package/README.md +10 -2
- package/dist/collaboration/index.js +360 -0
- package/dist/collaboration/index.js.map +1 -0
- package/dist/collaboration/peers.js +76 -0
- package/dist/collaboration/peers.js.map +1 -0
- package/dist/components/default-node.js +6 -0
- package/dist/components/default-node.js.map +1 -0
- package/dist/components/default-node.vue_vue_type_script_setup_true_lang.js +87 -0
- package/dist/components/default-node.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/default-node2.js +116 -0
- package/dist/components/default-node2.js.map +1 -0
- package/dist/components/flow-controls.js +6 -0
- package/dist/components/flow-controls.js.map +1 -0
- package/dist/components/flow-controls.vue_vue_type_script_setup_true_lang.js +148 -0
- package/dist/components/flow-controls.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/flow-menu.js +6 -0
- package/dist/components/flow-menu.js.map +1 -0
- package/dist/components/flow-menu.vue_vue_type_script_setup_true_lang.js +52 -0
- package/dist/components/flow-menu.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/flow-minimap.js +6 -0
- package/dist/components/flow-minimap.js.map +1 -0
- package/dist/components/flow-minimap.vue_vue_type_script_setup_true_lang.js +238 -0
- package/dist/components/flow-minimap.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/flow-port.js +6 -0
- package/dist/components/flow-port.js.map +1 -0
- package/dist/components/flow-port.vue_vue_type_script_setup_true_lang.js +57 -0
- package/dist/components/flow-port.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/flow-quick-add.js +6 -0
- package/dist/components/flow-quick-add.js.map +1 -0
- package/dist/components/flow-quick-add.vue_vue_type_script_setup_true_lang.js +126 -0
- package/dist/components/flow-quick-add.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/flow-toolbar.js +6 -0
- package/dist/components/flow-toolbar.js.map +1 -0
- package/dist/components/flow-toolbar.vue_vue_type_script_setup_true_lang.js +161 -0
- package/dist/components/flow-toolbar.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/group-node.js +6 -0
- package/dist/components/group-node.js.map +1 -0
- package/dist/components/group-node.vue_vue_type_script_setup_true_lang.js +84 -0
- package/dist/components/group-node.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/group-node2.js +42 -0
- package/dist/components/group-node2.js.map +1 -0
- package/dist/components/node-view.js +6 -0
- package/dist/components/node-view.js.map +1 -0
- package/dist/components/node-view.vue_vue_type_script_setup_true_lang.js +179 -0
- package/dist/components/node-view.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/context.js +13 -0
- package/dist/context.js.map +1 -0
- package/dist/core/clipboard.js +37 -0
- package/dist/core/clipboard.js.map +1 -0
- package/dist/core/connect.js +74 -0
- package/dist/core/connect.js.map +1 -0
- package/dist/core/edge-types.js +240 -0
- package/dist/core/edge-types.js.map +1 -0
- package/dist/core/extension.js +63 -0
- package/dist/core/extension.js.map +1 -0
- package/dist/core/geometry.js +232 -0
- package/dist/core/geometry.js.map +1 -0
- package/dist/core/groups.js +76 -0
- package/dist/core/groups.js.map +1 -0
- package/dist/core/history.js +79 -0
- package/dist/core/history.js.map +1 -0
- package/dist/core/model.js +21 -0
- package/dist/core/model.js.map +1 -0
- package/dist/core/node-types.js +32 -0
- package/dist/core/node-types.js.map +1 -0
- package/dist/core/order.js +42 -0
- package/dist/core/order.js.map +1 -0
- package/dist/core/transaction.js +288 -0
- package/dist/core/transaction.js.map +1 -0
- package/dist/document.js +220 -0
- package/dist/document.js.map +1 -0
- package/dist/export/color.js +38 -0
- package/dist/export/color.js.map +1 -0
- package/dist/export/context.js +223 -0
- package/dist/export/context.js.map +1 -0
- package/dist/export/draw.js +249 -0
- package/dist/export/draw.js.map +1 -0
- package/dist/flow-editor.js +6 -0
- package/dist/flow-editor.js.map +1 -0
- package/dist/flow-editor.vue_vue_type_script_setup_true_lang.js +3721 -0
- package/dist/flow-editor.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/kit.js +234 -0
- package/dist/kit.js.map +1 -0
- package/dist/layers.js +150 -0
- package/dist/layers.js.map +1 -0
- package/dist/locale.js +426 -0
- package/dist/locale.js.map +1 -0
- package/dist/style.css +1 -0
- package/dist/style.d.ts +1 -0
- package/dist/style.js +8 -0
- package/dist/types/collaboration/index.d.ts +42 -0
- package/dist/types/collaboration/peers.d.ts +27 -0
- package/dist/types/components/default-node.d.ts +18 -0
- package/dist/types/components/default-node.vue.d.ts +6 -0
- package/dist/types/components/flow-controls.vue.d.ts +8 -0
- package/dist/types/components/flow-menu.vue.d.ts +29 -0
- package/dist/types/components/flow-minimap.vue.d.ts +8 -0
- package/dist/types/components/flow-port.vue.d.ts +9 -0
- package/dist/types/components/flow-quick-add.vue.d.ts +31 -0
- package/dist/types/components/flow-toolbar.vue.d.ts +25 -0
- package/dist/types/components/group-node.d.ts +13 -0
- package/dist/types/components/group-node.vue.d.ts +6 -0
- package/dist/types/components/node-view.vue.d.ts +28 -0
- package/dist/types/context.d.ts +362 -0
- package/dist/types/core/clipboard.d.ts +9 -0
- package/dist/types/core/connect.d.ts +63 -0
- package/dist/types/core/edge-types.d.ts +50 -0
- package/dist/types/core/extension.d.ts +141 -0
- package/dist/types/core/geometry.d.ts +63 -0
- package/dist/types/core/groups.d.ts +23 -0
- package/dist/types/core/history.d.ts +24 -0
- package/dist/types/core/menu.d.ts +40 -0
- package/dist/types/core/model.d.ts +87 -0
- package/dist/types/core/node-types.d.ts +103 -0
- package/dist/types/core/order.d.ts +7 -0
- package/dist/types/core/transaction.d.ts +97 -0
- package/dist/types/document.d.ts +57 -0
- package/dist/types/export/color.d.ts +7 -0
- package/dist/types/export/context.d.ts +90 -0
- package/dist/types/export/draw.d.ts +72 -0
- package/dist/types/flow-editor.vue.d.ts +63 -0
- package/dist/types/index.d.ts +202 -0
- package/dist/types/kit.d.ts +22 -0
- package/dist/types/layers.d.ts +24 -0
- package/dist/types/locale.d.ts +172 -0
- package/dist/types/whiteboard/board-draft.vue.d.ts +3 -0
- package/dist/types/whiteboard/board-node.vue.d.ts +19 -0
- package/dist/types/whiteboard/connector.d.ts +12 -0
- package/dist/types/whiteboard/draw.d.ts +5 -0
- package/dist/types/whiteboard/image.d.ts +27 -0
- package/dist/types/whiteboard/index.d.ts +6 -0
- package/dist/types/whiteboard/ink.d.ts +30 -0
- package/dist/types/whiteboard/kit.d.ts +36 -0
- package/dist/types/whiteboard/model.d.ts +79 -0
- package/dist/types/whiteboard/pen.d.ts +7 -0
- package/dist/types/whiteboard/state.d.ts +37 -0
- package/dist/types/whiteboard/transform.d.ts +67 -0
- package/dist/types/whiteboard/whiteboard-toolbar.vue.d.ts +3 -0
- package/dist/whiteboard/board-draft.js +6 -0
- package/dist/whiteboard/board-draft.js.map +1 -0
- package/dist/whiteboard/board-draft.vue_vue_type_script_setup_true_lang.js +58 -0
- package/dist/whiteboard/board-draft.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/whiteboard/board-node.js +6 -0
- package/dist/whiteboard/board-node.js.map +1 -0
- package/dist/whiteboard/board-node.vue_vue_type_script_setup_true_lang.js +332 -0
- package/dist/whiteboard/board-node.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/whiteboard/connector.js +180 -0
- package/dist/whiteboard/connector.js.map +1 -0
- package/dist/whiteboard/draw.js +68 -0
- package/dist/whiteboard/draw.js.map +1 -0
- package/dist/whiteboard/image.js +104 -0
- package/dist/whiteboard/image.js.map +1 -0
- package/dist/whiteboard/index.js +5 -0
- package/dist/whiteboard/ink.js +168 -0
- package/dist/whiteboard/ink.js.map +1 -0
- package/dist/whiteboard/kit.js +406 -0
- package/dist/whiteboard/kit.js.map +1 -0
- package/dist/whiteboard/model.js +52 -0
- package/dist/whiteboard/model.js.map +1 -0
- package/dist/whiteboard/pen.js +88 -0
- package/dist/whiteboard/pen.js.map +1 -0
- package/dist/whiteboard/state.js +26 -0
- package/dist/whiteboard/state.js.map +1 -0
- package/dist/whiteboard/transform.js +256 -0
- package/dist/whiteboard/transform.js.map +1 -0
- package/dist/whiteboard/whiteboard-toolbar.js +6 -0
- package/dist/whiteboard/whiteboard-toolbar.js.map +1 -0
- package/dist/whiteboard/whiteboard-toolbar.vue_vue_type_script_setup_true_lang.js +171 -0
- package/dist/whiteboard/whiteboard-toolbar.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/package.json +74 -4
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import { useFlowEditor } from "../context.js";
|
|
2
|
+
import flow_port_default from "../components/flow-port.js";
|
|
3
|
+
import { fitInk, inkOutline, unpack } from "./ink.js";
|
|
4
|
+
import { anchorSides } from "./model.js";
|
|
5
|
+
import { boardState } from "./state.js";
|
|
6
|
+
import { RESIZE_HANDLES, useTransformHandles } from "./transform.js";
|
|
7
|
+
import { Fragment, Teleport, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, nextTick, normalizeClass, normalizeStyle, openBlock, ref, renderList, toDisplayString, toRef, unref, useTemplateRef, vModelText, watch, withDirectives, withModifiers } from "vue";
|
|
8
|
+
import { ImageOffIcon } from "@dou-ui/icons";
|
|
9
|
+
// src/whiteboard/board-node.vue?vue&type=script&setup=true&lang.ts
|
|
10
|
+
var _hoisted_1 = ["viewBox"];
|
|
11
|
+
var _hoisted_2 = ["width", "height"];
|
|
12
|
+
var _hoisted_3 = [
|
|
13
|
+
"cx",
|
|
14
|
+
"cy",
|
|
15
|
+
"rx",
|
|
16
|
+
"ry"
|
|
17
|
+
];
|
|
18
|
+
var _hoisted_4 = ["points"];
|
|
19
|
+
var _hoisted_5 = ["viewBox"];
|
|
20
|
+
var _hoisted_6 = ["d"];
|
|
21
|
+
var _hoisted_7 = ["d"];
|
|
22
|
+
var _hoisted_8 = ["aria-label"];
|
|
23
|
+
var _hoisted_9 = ["src", "alt"];
|
|
24
|
+
var _hoisted_10 = ["aria-label"];
|
|
25
|
+
var _hoisted_11 = ["textContent"];
|
|
26
|
+
var _hoisted_12 = {
|
|
27
|
+
key: 6,
|
|
28
|
+
class: "dou-whiteboard-node__anchors"
|
|
29
|
+
};
|
|
30
|
+
var _hoisted_13 = ["data-handle", "onPointerdown"];
|
|
31
|
+
var _hoisted_14 = ["data-handle", "onPointerdown"];
|
|
32
|
+
var board_node_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
|
|
33
|
+
__name: "board-node",
|
|
34
|
+
props: {
|
|
35
|
+
node: {},
|
|
36
|
+
data: {},
|
|
37
|
+
ports: {},
|
|
38
|
+
selected: { type: Boolean },
|
|
39
|
+
readonly: { type: Boolean },
|
|
40
|
+
updateData: { type: Function }
|
|
41
|
+
},
|
|
42
|
+
setup(__props) {
|
|
43
|
+
const props = __props;
|
|
44
|
+
const ctx = useFlowEditor();
|
|
45
|
+
const state = boardState(ctx);
|
|
46
|
+
const KINDS = [
|
|
47
|
+
"sticky",
|
|
48
|
+
"text",
|
|
49
|
+
"ink",
|
|
50
|
+
"image"
|
|
51
|
+
];
|
|
52
|
+
/** What the node is: one of the shapes, a note, text, a pen stroke or an image. */
|
|
53
|
+
const kind = computed(() => KINDS.find((k) => k === props.node.type) ?? "shape");
|
|
54
|
+
const shape = computed(() => props.data.shape ?? "rect");
|
|
55
|
+
const tone = computed(() => props.data.tone ?? (kind.value === "sticky" ? "warning" : "neutral"));
|
|
56
|
+
const writable = computed(() => [
|
|
57
|
+
"shape",
|
|
58
|
+
"sticky",
|
|
59
|
+
"text"
|
|
60
|
+
].includes(kind.value));
|
|
61
|
+
const MIN = {
|
|
62
|
+
shape: 24,
|
|
63
|
+
sticky: 64,
|
|
64
|
+
text: 40,
|
|
65
|
+
ink: 8,
|
|
66
|
+
image: 24
|
|
67
|
+
};
|
|
68
|
+
const rotation = computed(() => props.data.rotation ?? 0);
|
|
69
|
+
const { turning, startTurn, startResize } = useTransformHandles(ctx, toRef(props, "node"), rotation, {
|
|
70
|
+
width: MIN[kind.value],
|
|
71
|
+
height: MIN[kind.value]
|
|
72
|
+
});
|
|
73
|
+
const transform = computed(() => rotation.value ? { rotate: `${rotation.value}deg` } : void 0);
|
|
74
|
+
/**
|
|
75
|
+
* The resize handles: an image keeps its proportions, from its corners; text
|
|
76
|
+
* only widens, so it has no top or bottom side; the rest has every corner
|
|
77
|
+
* and side.
|
|
78
|
+
*/
|
|
79
|
+
const resizeHandles = computed(() => kind.value === "image" ? RESIZE_HANDLES.filter((h) => h.x && h.y) : kind.value === "text" ? RESIZE_HANDLES.filter((h) => h.x) : RESIZE_HANDLES);
|
|
80
|
+
const sideHandles = computed(() => resizeHandles.value.filter((h) => !h.x || !h.y));
|
|
81
|
+
const cornerHandles = computed(() => resizeHandles.value.filter((h) => h.x && h.y));
|
|
82
|
+
/** The node's box, turned with it, for the handles drawn above every node. */
|
|
83
|
+
const frameStyle = computed(() => {
|
|
84
|
+
const rect = ctx.getNodeRect(props.node.id) ?? {
|
|
85
|
+
...props.node,
|
|
86
|
+
width: 0,
|
|
87
|
+
height: 0
|
|
88
|
+
};
|
|
89
|
+
return {
|
|
90
|
+
translate: `${rect.x}px ${rect.y}px`,
|
|
91
|
+
width: `${rect.width}px`,
|
|
92
|
+
height: `${rect.height}px`,
|
|
93
|
+
rotate: rotation.value ? `${rotation.value}deg` : void 0
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
const CURSORS = [
|
|
97
|
+
"ew-resize",
|
|
98
|
+
"nwse-resize",
|
|
99
|
+
"ns-resize",
|
|
100
|
+
"nesw-resize"
|
|
101
|
+
];
|
|
102
|
+
/** The resize cursor along a handle's way out, turned with the node. */
|
|
103
|
+
function cursorOf(handle) {
|
|
104
|
+
if (kind.value === "text") handle = {
|
|
105
|
+
x: handle.x,
|
|
106
|
+
y: 0
|
|
107
|
+
};
|
|
108
|
+
const degrees = Math.atan2(handle.y, handle.x) * 180 / Math.PI + rotation.value;
|
|
109
|
+
return CURSORS[(Math.round(degrees / 45) % 4 + 4) % 4];
|
|
110
|
+
}
|
|
111
|
+
function onResize(event, handle) {
|
|
112
|
+
startResize(event, handle, {
|
|
113
|
+
keepRatio: kind.value === "image",
|
|
114
|
+
widthOnly: kind.value === "text"
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
/** Where a handle sits on the box: `-1` is the start, `1` the end. */
|
|
118
|
+
const place = (v) => `${(v + 1) * 50}%`;
|
|
119
|
+
/** Handles show on the one selected node, while the select tool is in hand. */
|
|
120
|
+
const handles = computed(() => props.selected && !props.readonly && !editing.value && state.tool.value === "select" && ctx.selection.value.nodes.length === 1);
|
|
121
|
+
/** The stroke fitted to the box shown, so stretching it keeps its thickness. */
|
|
122
|
+
const ink = computed(() => {
|
|
123
|
+
const { points, box: inkBox } = props.data;
|
|
124
|
+
if (kind.value !== "ink" || !points || !inkBox) return "";
|
|
125
|
+
const size = props.data.size ?? 4;
|
|
126
|
+
return inkOutline(fitInk(unpack(points), inkBox, shown.value.w, shown.value.h, size), size);
|
|
127
|
+
});
|
|
128
|
+
const erasing = computed(() => state.erasing.value.has(props.node.id));
|
|
129
|
+
const shown = computed(() => ({
|
|
130
|
+
w: props.node.width ?? 160,
|
|
131
|
+
h: props.node.height ?? 100
|
|
132
|
+
}));
|
|
133
|
+
const diamond = computed(() => {
|
|
134
|
+
const { w, h } = shown.value;
|
|
135
|
+
const i = 1;
|
|
136
|
+
return `${w / 2},${i} ${w - i},${h / 2} ${w / 2},${h - i} ${i},${h / 2}`;
|
|
137
|
+
});
|
|
138
|
+
const textEl = useTemplateRef("textEl");
|
|
139
|
+
const textMetrics = ref(null);
|
|
140
|
+
watch(textEl, (el) => {
|
|
141
|
+
if (!el) return;
|
|
142
|
+
const s = getComputedStyle(el);
|
|
143
|
+
const line = Number.parseFloat(s.lineHeight);
|
|
144
|
+
if (!line) return;
|
|
145
|
+
const padding = Number.parseFloat(s.paddingTop) + Number.parseFloat(s.paddingBottom);
|
|
146
|
+
textMetrics.value = {
|
|
147
|
+
line,
|
|
148
|
+
padding: padding || 0
|
|
149
|
+
};
|
|
150
|
+
}, { flush: "post" });
|
|
151
|
+
const textStyle = computed(() => {
|
|
152
|
+
const m = textMetrics.value;
|
|
153
|
+
if (!m || kind.value === "text") return void 0;
|
|
154
|
+
const height = shown.value.h;
|
|
155
|
+
return { "-webkit-line-clamp": Math.max(1, Math.floor((height - m.padding + .5) / m.line)) };
|
|
156
|
+
});
|
|
157
|
+
const broken = ref(false);
|
|
158
|
+
watch(() => props.data.src, () => broken.value = false);
|
|
159
|
+
const editing = computed(() => state.editing.value === props.node.id);
|
|
160
|
+
const draft = ref("");
|
|
161
|
+
const field = useTemplateRef("field");
|
|
162
|
+
watch(editing, (on) => {
|
|
163
|
+
if (!on) return;
|
|
164
|
+
draft.value = props.data.text ?? "";
|
|
165
|
+
nextTick(() => {
|
|
166
|
+
fit();
|
|
167
|
+
field.value?.focus();
|
|
168
|
+
field.value?.select();
|
|
169
|
+
});
|
|
170
|
+
}, { immediate: true });
|
|
171
|
+
function fit() {
|
|
172
|
+
const el = field.value;
|
|
173
|
+
if (!el || CSS.supports?.("field-sizing", "content")) return;
|
|
174
|
+
el.style.height = "auto";
|
|
175
|
+
el.style.height = `${el.scrollHeight}px`;
|
|
176
|
+
}
|
|
177
|
+
watch(draft, () => void nextTick(fit));
|
|
178
|
+
function edit() {
|
|
179
|
+
if (props.readonly || !writable.value) return;
|
|
180
|
+
state.editing.value = props.node.id;
|
|
181
|
+
}
|
|
182
|
+
function finish() {
|
|
183
|
+
if (!editing.value) return;
|
|
184
|
+
state.editing.value = null;
|
|
185
|
+
const text = draft.value.replace(/\s+$/, "");
|
|
186
|
+
if (!text && kind.value === "text") ctx.transact((tr) => tr.removeNode(props.node.id));
|
|
187
|
+
else if (text !== (props.data.text ?? "")) props.updateData({ text: text || void 0 });
|
|
188
|
+
ctx.root.value?.focus({ preventScroll: true });
|
|
189
|
+
}
|
|
190
|
+
function onKeydown(event) {
|
|
191
|
+
if (event.isComposing) return;
|
|
192
|
+
if (event.key === "Escape" || event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
|
|
193
|
+
event.preventDefault();
|
|
194
|
+
event.stopPropagation();
|
|
195
|
+
finish();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return (_ctx, _cache) => {
|
|
199
|
+
return openBlock(), createElementBlock("div", {
|
|
200
|
+
class: normalizeClass(["dou-whiteboard-node", [
|
|
201
|
+
`is-${kind.value}`,
|
|
202
|
+
kind.value === "shape" && `is-${shape.value}`,
|
|
203
|
+
`is-${tone.value}`,
|
|
204
|
+
{
|
|
205
|
+
"is-editing": editing.value,
|
|
206
|
+
"is-turned": !!rotation.value,
|
|
207
|
+
"is-turning": unref(turning) !== null,
|
|
208
|
+
"is-selected": __props.selected,
|
|
209
|
+
"is-erasing": erasing.value
|
|
210
|
+
}
|
|
211
|
+
]]),
|
|
212
|
+
style: normalizeStyle(transform.value),
|
|
213
|
+
onDblclick: withModifiers(edit, ["stop"])
|
|
214
|
+
}, [
|
|
215
|
+
kind.value === "shape" ? (openBlock(), createElementBlock("svg", {
|
|
216
|
+
key: 0,
|
|
217
|
+
class: "dou-whiteboard-node__shape",
|
|
218
|
+
viewBox: `0 0 ${shown.value.w} ${shown.value.h}`,
|
|
219
|
+
"aria-hidden": "true"
|
|
220
|
+
}, [shape.value === "rect" ? (openBlock(), createElementBlock("rect", {
|
|
221
|
+
key: 0,
|
|
222
|
+
x: "0.75",
|
|
223
|
+
y: "0.75",
|
|
224
|
+
width: Math.max(0, shown.value.w - 1.5),
|
|
225
|
+
height: Math.max(0, shown.value.h - 1.5),
|
|
226
|
+
rx: "8"
|
|
227
|
+
}, null, 8, _hoisted_2)) : shape.value === "ellipse" ? (openBlock(), createElementBlock("ellipse", {
|
|
228
|
+
key: 1,
|
|
229
|
+
cx: shown.value.w / 2,
|
|
230
|
+
cy: shown.value.h / 2,
|
|
231
|
+
rx: Math.max(0, shown.value.w / 2 - .75),
|
|
232
|
+
ry: Math.max(0, shown.value.h / 2 - .75)
|
|
233
|
+
}, null, 8, _hoisted_3)) : (openBlock(), createElementBlock("polygon", {
|
|
234
|
+
key: 2,
|
|
235
|
+
points: diamond.value
|
|
236
|
+
}, null, 8, _hoisted_4))], 8, _hoisted_1)) : kind.value === "ink" && __props.data.box ? (openBlock(), createElementBlock("svg", {
|
|
237
|
+
key: 1,
|
|
238
|
+
class: "dou-whiteboard-node__ink",
|
|
239
|
+
viewBox: `0 0 ${shown.value.w} ${shown.value.h}`,
|
|
240
|
+
"aria-hidden": "true"
|
|
241
|
+
}, [createElementVNode("path", {
|
|
242
|
+
class: "dou-whiteboard-node__ink-hit",
|
|
243
|
+
d: ink.value
|
|
244
|
+
}, null, 8, _hoisted_6), createElementVNode("path", { d: ink.value }, null, 8, _hoisted_7)], 8, _hoisted_5)) : kind.value === "image" && broken.value ? (openBlock(), createElementBlock("div", {
|
|
245
|
+
key: 2,
|
|
246
|
+
class: "dou-whiteboard-node__image is-broken",
|
|
247
|
+
role: "img",
|
|
248
|
+
"aria-label": __props.data.alt || void 0
|
|
249
|
+
}, [createVNode(unref(ImageOffIcon))], 8, _hoisted_8)) : kind.value === "image" ? (openBlock(), createElementBlock("img", {
|
|
250
|
+
key: 3,
|
|
251
|
+
class: "dou-whiteboard-node__image",
|
|
252
|
+
src: __props.data.src,
|
|
253
|
+
alt: __props.data.alt ?? "",
|
|
254
|
+
draggable: "false",
|
|
255
|
+
onError: _cache[0] || (_cache[0] = ($event) => broken.value = true)
|
|
256
|
+
}, null, 40, _hoisted_9)) : createCommentVNode("", true),
|
|
257
|
+
editing.value ? withDirectives((openBlock(), createElementBlock("textarea", {
|
|
258
|
+
key: 4,
|
|
259
|
+
ref_key: "field",
|
|
260
|
+
ref: field,
|
|
261
|
+
"onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => draft.value = $event),
|
|
262
|
+
class: "dou-whiteboard-node__text dou-whiteboard-node__field",
|
|
263
|
+
"aria-label": unref(ctx).messages.value.board.editText,
|
|
264
|
+
onKeydown,
|
|
265
|
+
onBlur: finish,
|
|
266
|
+
onPointerdown: _cache[2] || (_cache[2] = withModifiers(() => {}, ["stop"]))
|
|
267
|
+
}, null, 40, _hoisted_10)), [[vModelText, draft.value]]) : writable.value ? (openBlock(), createElementBlock("div", {
|
|
268
|
+
key: 5,
|
|
269
|
+
ref_key: "textEl",
|
|
270
|
+
ref: textEl,
|
|
271
|
+
class: "dou-whiteboard-node__text",
|
|
272
|
+
style: normalizeStyle(textStyle.value),
|
|
273
|
+
textContent: toDisplayString(__props.data.text)
|
|
274
|
+
}, null, 12, _hoisted_11)) : createCommentVNode("", true),
|
|
275
|
+
kind.value !== "ink" ? (openBlock(), createElementBlock("div", _hoisted_12, [(openBlock(true), createElementBlock(Fragment, null, renderList(unref(anchorSides), (side) => {
|
|
276
|
+
return openBlock(), createElementBlock(Fragment, { key: side }, [createVNode(flow_port_default, {
|
|
277
|
+
id: `${side}-in`,
|
|
278
|
+
class: "dou-whiteboard-node__anchor"
|
|
279
|
+
}, null, 8, ["id"]), createVNode(flow_port_default, {
|
|
280
|
+
id: side,
|
|
281
|
+
class: "dou-whiteboard-node__anchor"
|
|
282
|
+
}, null, 8, ["id"])], 64);
|
|
283
|
+
}), 128))])) : createCommentVNode("", true),
|
|
284
|
+
handles.value ? (openBlock(), createElementBlock(Fragment, { key: 7 }, [(openBlock(true), createElementBlock(Fragment, null, renderList(sideHandles.value, (handle) => {
|
|
285
|
+
return openBlock(), createElementBlock("span", {
|
|
286
|
+
key: `${handle.x},${handle.y}`,
|
|
287
|
+
class: normalizeClass(["dou-whiteboard-node__side", handle.x ? "is-x" : "is-y"]),
|
|
288
|
+
style: normalizeStyle({
|
|
289
|
+
left: place(handle.x),
|
|
290
|
+
top: place(handle.y),
|
|
291
|
+
cursor: cursorOf(handle)
|
|
292
|
+
}),
|
|
293
|
+
"data-handle": `${handle.x},${handle.y}`,
|
|
294
|
+
"data-dou-nodrag": "",
|
|
295
|
+
"aria-hidden": "true",
|
|
296
|
+
onPointerdown: ($event) => onResize($event, handle)
|
|
297
|
+
}, null, 46, _hoisted_13);
|
|
298
|
+
}), 128)), (openBlock(), createBlock(Teleport, {
|
|
299
|
+
to: unref(state).handles.value,
|
|
300
|
+
disabled: !unref(state).handles.value
|
|
301
|
+
}, [createElementVNode("div", {
|
|
302
|
+
class: "dou-whiteboard-node__frame",
|
|
303
|
+
style: normalizeStyle(frameStyle.value),
|
|
304
|
+
"aria-hidden": "true"
|
|
305
|
+
}, [createElementVNode("span", {
|
|
306
|
+
class: "dou-whiteboard-node__turn",
|
|
307
|
+
"data-dou-nodrag": "",
|
|
308
|
+
onPointerdown: _cache[3] || (_cache[3] = (...args) => unref(startTurn) && unref(startTurn)(...args))
|
|
309
|
+
}, [..._cache[4] || (_cache[4] = [createElementVNode("span", {
|
|
310
|
+
class: "dou-whiteboard-node__target",
|
|
311
|
+
"data-flow-overlay": ""
|
|
312
|
+
}, null, -1)])], 32), (openBlock(true), createElementBlock(Fragment, null, renderList(cornerHandles.value, (handle) => {
|
|
313
|
+
return openBlock(), createElementBlock("span", {
|
|
314
|
+
key: `${handle.x},${handle.y}`,
|
|
315
|
+
class: "dou-whiteboard-node__corner",
|
|
316
|
+
style: normalizeStyle({
|
|
317
|
+
left: place(handle.x),
|
|
318
|
+
top: place(handle.y),
|
|
319
|
+
cursor: cursorOf(handle)
|
|
320
|
+
}),
|
|
321
|
+
"data-handle": `${handle.x},${handle.y}`,
|
|
322
|
+
"data-dou-nodrag": "",
|
|
323
|
+
onPointerdown: ($event) => onResize($event, handle)
|
|
324
|
+
}, null, 44, _hoisted_14);
|
|
325
|
+
}), 128))], 4)], 8, ["to", "disabled"]))], 64)) : createCommentVNode("", true)
|
|
326
|
+
], 38);
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
export { board_node_vue_vue_type_script_setup_true_lang_default as default };
|
|
331
|
+
|
|
332
|
+
//# sourceMappingURL=board-node.vue_vue_type_script_setup_true_lang.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"board-node.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createElementBlock","_normalizeClass","_unref","_normalizeStyle","_withModifiers","_createElementVNode","_createVNode","_createCommentVNode","_withDirectives","_vModelText","_toDisplayString","_Fragment","_renderList","DouFlowPort","_createBlock","_Teleport"],"sources":["../../src/whiteboard/board-node.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ImageOffIcon } from '@dou-ui/icons'\nimport { computed, nextTick, ref, toRef, useTemplateRef, watch } from 'vue'\nimport DouFlowPort from '../components/flow-port.vue'\nimport { useFlowEditor } from '../context'\nimport type { NodeViewProps } from '../core/node-types'\nimport type { Tone } from 'dou-ui'\nimport { fitInk, inkOutline, INK_SIZE, unpack } from './ink'\nimport { anchorSides, type WhiteboardShape } from './model'\nimport { boardState } from './state'\nimport { RESIZE_HANDLES, useTransformHandles, type ResizeHandle } from './transform'\n\ntype BoardData = {\n [key: string]: unknown\n text?: string\n shape?: WhiteboardShape\n tone?: Tone\n rotation?: number\n points?: number[]\n box?: [number, number]\n size?: number\n src?: string\n alt?: string\n}\n\ntype Kind = 'shape' | 'sticky' | 'text' | 'ink' | 'image'\n\nconst props = defineProps<NodeViewProps<BoardData>>()\nconst ctx = useFlowEditor()\nconst state = boardState(ctx)\n\nconst KINDS: readonly Kind[] = ['sticky', 'text', 'ink', 'image']\n/** What the node is: one of the shapes, a note, text, a pen stroke or an image. */\nconst kind = computed<Kind>(() => KINDS.find((k) => k === props.node.type) ?? 'shape')\nconst shape = computed<WhiteboardShape>(() => props.data.shape ?? 'rect')\nconst tone = computed<Tone>(\n () => props.data.tone ?? (kind.value === 'sticky' ? 'warning' : 'neutral'),\n)\nconst writable = computed(() => ['shape', 'sticky', 'text'].includes(kind.value))\n\nconst MIN: Record<Kind, number> = { shape: 24, sticky: 64, text: 40, ink: 8, image: 24 }\nconst rotation = computed(() => props.data.rotation ?? 0)\nconst { turning, startTurn, startResize } = useTransformHandles(\n ctx,\n toRef(props, 'node'),\n rotation,\n { width: MIN[kind.value], height: MIN[kind.value] },\n)\n/*\n * A handle being dragged shows the node changed through the editor, so\n * `node` and `data` already are what it is dragged to: the node's own frame,\n * its ports and its edges follow along with it.\n */\nconst transform = computed(() => (rotation.value ? { rotate: `${rotation.value}deg` } : undefined))\n/**\n * The resize handles: an image keeps its proportions, from its corners; text\n * only widens, so it has no top or bottom side; the rest has every corner\n * and side.\n */\nconst resizeHandles = computed(() =>\n kind.value === 'image'\n ? RESIZE_HANDLES.filter((h) => h.x && h.y)\n : kind.value === 'text'\n ? RESIZE_HANDLES.filter((h) => h.x)\n : RESIZE_HANDLES,\n)\nconst sideHandles = computed(() => resizeHandles.value.filter((h) => !h.x || !h.y))\nconst cornerHandles = computed(() => resizeHandles.value.filter((h) => h.x && h.y))\n/** The node's box, turned with it, for the handles drawn above every node. */\nconst frameStyle = computed(() => {\n const rect = ctx.getNodeRect(props.node.id) ?? { ...props.node, width: 0, height: 0 }\n return {\n // `translate`, not `transform`: it applies before `rotate`, so the box\n // turns about its own centre and its offset is not turned with it\n translate: `${rect.x}px ${rect.y}px`,\n width: `${rect.width}px`,\n height: `${rect.height}px`,\n rotate: rotation.value ? `${rotation.value}deg` : undefined,\n }\n})\nconst CURSORS = ['ew-resize', 'nwse-resize', 'ns-resize', 'nesw-resize'] as const\n/** The resize cursor along a handle's way out, turned with the node. */\nfunction cursorOf(handle: ResizeHandle) {\n // text only widens, from a corner too\n if (kind.value === 'text') handle = { x: handle.x, y: 0 }\n const degrees = (Math.atan2(handle.y, handle.x) * 180) / Math.PI + rotation.value\n return CURSORS[((Math.round(degrees / 45) % 4) + 4) % 4]\n}\nfunction onResize(event: PointerEvent, handle: ResizeHandle) {\n startResize(event, handle, {\n keepRatio: kind.value === 'image',\n widthOnly: kind.value === 'text',\n })\n}\n/** Where a handle sits on the box: `-1` is the start, `1` the end. */\nconst place = (v: -1 | 0 | 1) => `${(v + 1) * 50}%`\n\n/** Handles show on the one selected node, while the select tool is in hand. */\nconst handles = computed(\n () =>\n props.selected &&\n !props.readonly &&\n !editing.value &&\n state.tool.value === 'select' &&\n ctx.selection.value.nodes.length === 1,\n)\n\n/** The stroke fitted to the box shown, so stretching it keeps its thickness. */\nconst ink = computed(() => {\n const { points, box: inkBox } = props.data\n if (kind.value !== 'ink' || !points || !inkBox) return ''\n const size = props.data.size ?? INK_SIZE\n return inkOutline(fitInk(unpack(points), inkBox, shown.value.w, shown.value.h, size), size)\n})\nconst erasing = computed(() => state.erasing.value.has(props.node.id))\n\n// drawn in the node's own units, half the 1.5px stroke in from the edge\nconst shown = computed(() => ({ w: props.node.width ?? 160, h: props.node.height ?? 100 }))\nconst diamond = computed(() => {\n const { w, h } = shown.value\n const i = 1\n return `${w / 2},${i} ${w - i},${h / 2} ${w / 2},${h - i} ${i},${h / 2}`\n})\n\n// Whole lines that fit, the last one ending in an ellipsis, as an export draws them.\nconst textEl = useTemplateRef<HTMLElement>('textEl')\nconst textMetrics = ref<{ line: number; padding: number } | null>(null)\nwatch(\n textEl,\n (el) => {\n if (!el) return\n const s = getComputedStyle(el)\n const line = Number.parseFloat(s.lineHeight)\n if (!line) return\n const padding = Number.parseFloat(s.paddingTop) + Number.parseFloat(s.paddingBottom)\n textMetrics.value = { line, padding: padding || 0 }\n },\n { flush: 'post' },\n)\nconst textStyle = computed(() => {\n const m = textMetrics.value\n if (!m || kind.value === 'text') return undefined\n const height = shown.value.h\n return { '-webkit-line-clamp': Math.max(1, Math.floor((height - m.padding + 0.5) / m.line)) }\n})\n\n// An image that can't be loaded: a plain box with a sign, as an export draws it.\nconst broken = ref(false)\nwatch(\n () => props.data.src,\n () => (broken.value = false),\n)\n\nconst editing = computed(() => state.editing.value === props.node.id)\nconst draft = ref('')\nconst field = useTemplateRef<HTMLTextAreaElement>('field')\n\nwatch(\n editing,\n (on) => {\n if (!on) return\n draft.value = props.data.text ?? ''\n void nextTick(() => {\n fit()\n field.value?.focus()\n field.value?.select()\n })\n },\n { immediate: true },\n)\n\n// browsers without `field-sizing` grow the field by hand\nfunction fit() {\n const el = field.value\n if (!el || CSS.supports?.('field-sizing', 'content')) return\n el.style.height = 'auto'\n el.style.height = `${el.scrollHeight}px`\n}\nwatch(draft, () => void nextTick(fit))\n\nfunction edit() {\n if (props.readonly || !writable.value) return\n state.editing.value = props.node.id\n}\n\nfunction finish() {\n if (!editing.value) return\n state.editing.value = null\n const text = draft.value.replace(/\\s+$/, '')\n // text with nothing written in it is nothing at all\n if (!text && kind.value === 'text') ctx.transact((tr) => tr.removeNode(props.node.id))\n else if (text !== (props.data.text ?? '')) props.updateData({ text: text || undefined })\n // back to the canvas, so the keys go on working\n ctx.root.value?.focus({ preventScroll: true })\n}\n\nfunction onKeydown(event: KeyboardEvent) {\n if (event.isComposing) return\n // Escape, or mod+Enter, puts the text down; Enter is a new line\n if (event.key === 'Escape' || (event.key === 'Enter' && (event.metaKey || event.ctrlKey))) {\n event.preventDefault()\n event.stopPropagation()\n finish()\n }\n}\n</script>\n\n<template>\n <div\n class=\"dou-whiteboard-node\"\n :class=\"[\n `is-${kind}`,\n kind === 'shape' && `is-${shape}`,\n `is-${tone}`,\n {\n 'is-editing': editing,\n 'is-turned': !!rotation,\n 'is-turning': turning !== null,\n 'is-selected': selected,\n 'is-erasing': erasing,\n },\n ]\"\n :style=\"transform\"\n @dblclick.stop=\"edit\"\n >\n <svg\n v-if=\"kind === 'shape'\"\n class=\"dou-whiteboard-node__shape\"\n :viewBox=\"`0 0 ${shown.w} ${shown.h}`\"\n aria-hidden=\"true\"\n >\n <rect\n v-if=\"shape === 'rect'\"\n x=\"0.75\"\n y=\"0.75\"\n :width=\"Math.max(0, shown.w - 1.5)\"\n :height=\"Math.max(0, shown.h - 1.5)\"\n rx=\"8\"\n />\n <ellipse\n v-else-if=\"shape === 'ellipse'\"\n :cx=\"shown.w / 2\"\n :cy=\"shown.h / 2\"\n :rx=\"Math.max(0, shown.w / 2 - 0.75)\"\n :ry=\"Math.max(0, shown.h / 2 - 0.75)\"\n />\n <polygon v-else :points=\"diamond\" />\n </svg>\n <svg\n v-else-if=\"kind === 'ink' && data.box\"\n class=\"dou-whiteboard-node__ink\"\n :viewBox=\"`0 0 ${shown.w} ${shown.h}`\"\n aria-hidden=\"true\"\n >\n <path class=\"dou-whiteboard-node__ink-hit\" :d=\"ink\" />\n <path :d=\"ink\" />\n </svg>\n <div\n v-else-if=\"kind === 'image' && broken\"\n class=\"dou-whiteboard-node__image is-broken\"\n role=\"img\"\n :aria-label=\"data.alt || undefined\"\n >\n <ImageOffIcon />\n </div>\n <img\n v-else-if=\"kind === 'image'\"\n class=\"dou-whiteboard-node__image\"\n :src=\"data.src\"\n :alt=\"data.alt ?? ''\"\n draggable=\"false\"\n @error=\"broken = true\"\n />\n <textarea\n v-if=\"editing\"\n ref=\"field\"\n v-model=\"draft\"\n class=\"dou-whiteboard-node__text dou-whiteboard-node__field\"\n :aria-label=\"ctx.messages.value.board.editText\"\n @keydown=\"onKeydown\"\n @blur=\"finish\"\n @pointerdown.stop\n />\n <div\n v-else-if=\"writable\"\n ref=\"textEl\"\n class=\"dou-whiteboard-node__text\"\n :style=\"textStyle\"\n v-text=\"data.text\"\n />\n <div v-if=\"kind !== 'ink'\" class=\"dou-whiteboard-node__anchors\">\n <template v-for=\"side in anchorSides\" :key=\"side\">\n <DouFlowPort :id=\"`${side}-in`\" class=\"dou-whiteboard-node__anchor\" />\n <DouFlowPort :id=\"side\" class=\"dou-whiteboard-node__anchor\" />\n </template>\n </div>\n <template v-if=\"handles\">\n <!-- the sides stay on the node, under its anchors; the turn and the\n corners go above every node, so a neighbour never covers them -->\n <span\n v-for=\"handle in sideHandles\"\n :key=\"`${handle.x},${handle.y}`\"\n :class=\"['dou-whiteboard-node__side', handle.x ? 'is-x' : 'is-y']\"\n :style=\"{ left: place(handle.x), top: place(handle.y), cursor: cursorOf(handle) }\"\n :data-handle=\"`${handle.x},${handle.y}`\"\n data-dou-nodrag\n aria-hidden=\"true\"\n @pointerdown=\"onResize($event, handle)\"\n />\n <Teleport :to=\"state.handles.value\" :disabled=\"!state.handles.value\">\n <div class=\"dou-whiteboard-node__frame\" :style=\"frameStyle\" aria-hidden=\"true\">\n <!-- data-flow-overlay on its target: the selection's toolbar keeps\n clear of where a finger can take it, when it floats below the\n node or the node is turned upside down -->\n <span class=\"dou-whiteboard-node__turn\" data-dou-nodrag @pointerdown=\"startTurn\">\n <span class=\"dou-whiteboard-node__target\" data-flow-overlay />\n </span>\n <span\n v-for=\"handle in cornerHandles\"\n :key=\"`${handle.x},${handle.y}`\"\n class=\"dou-whiteboard-node__corner\"\n :style=\"{ left: place(handle.x), top: place(handle.y), cursor: cursorOf(handle) }\"\n :data-handle=\"`${handle.x},${handle.y}`\"\n data-dou-nodrag\n @pointerdown=\"onResize($event, handle)\"\n />\n </div>\n </Teleport>\n </template>\n </div>\n</template>\n"],"mappings":"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"}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { bezierEdge, defineEdgeType, stepEdge } from "../core/edge-types.js";
|
|
2
|
+
import { format } from "../locale.js";
|
|
3
|
+
import { ArrowEndsIcon, ArrowLeftRightIcon, ArrowRightIcon, ConnectorCurveIcon, ConnectorElbowIcon, ConnectorStraightIcon, LineDashDotIcon, LineDashedIcon, LineDottedIcon, LineSolidIcon, LineStyleIcon, MinusIcon, RouteIcon } from "@dou-ui/icons";
|
|
4
|
+
// src/whiteboard/connector.ts
|
|
5
|
+
var ARROW = {
|
|
6
|
+
length: 10,
|
|
7
|
+
spread: .45
|
|
8
|
+
};
|
|
9
|
+
var r2 = (v) => Math.round(v * 100) / 100;
|
|
10
|
+
var p = (pt) => `${r2(pt.x)} ${r2(pt.y)}`;
|
|
11
|
+
/** Out of a side, away from the node. */
|
|
12
|
+
var outward = {
|
|
13
|
+
left: {
|
|
14
|
+
x: -1,
|
|
15
|
+
y: 0
|
|
16
|
+
},
|
|
17
|
+
right: {
|
|
18
|
+
x: 1,
|
|
19
|
+
y: 0
|
|
20
|
+
},
|
|
21
|
+
top: {
|
|
22
|
+
x: 0,
|
|
23
|
+
y: -1
|
|
24
|
+
},
|
|
25
|
+
bottom: {
|
|
26
|
+
x: 0,
|
|
27
|
+
y: 1
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
/** An open arrowhead with its tip at `tip`, pointing along `angle`. */
|
|
31
|
+
function arrowhead(tip, angle) {
|
|
32
|
+
const wing = (spread) => ({
|
|
33
|
+
x: tip.x - ARROW.length * Math.cos(angle + spread),
|
|
34
|
+
y: tip.y - ARROW.length * Math.sin(angle + spread)
|
|
35
|
+
});
|
|
36
|
+
return `M${p(wing(ARROW.spread))}L${p(tip)}L${p(wing(-ARROW.spread))}`;
|
|
37
|
+
}
|
|
38
|
+
/** The way a run arrives at `end`: straight from `from`, or into the side it faces. */
|
|
39
|
+
function arrival(end, from, straight) {
|
|
40
|
+
if (straight) return Math.atan2(end.y - from.y, end.x - from.x);
|
|
41
|
+
const out = outward[end.side];
|
|
42
|
+
return Math.atan2(-out.y, -out.x);
|
|
43
|
+
}
|
|
44
|
+
/** Dash patterns, in px of a 1.5px stroke with round ends: a 0 dash is a dot. */
|
|
45
|
+
var DASHES = {
|
|
46
|
+
solid: void 0,
|
|
47
|
+
dashed: "6 5",
|
|
48
|
+
dotted: "0 4",
|
|
49
|
+
"dash-dot": "8 4 0 4"
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* The whiteboard's line: straight, with right-angled turns or curved
|
|
53
|
+
* (`data.route`), solid or dashed (`data.dash`), with arrowheads at the end,
|
|
54
|
+
* both ends or neither (`data.arrow`).
|
|
55
|
+
*/
|
|
56
|
+
var connectorEdge = defineEdgeType({
|
|
57
|
+
type: "connector",
|
|
58
|
+
route(source, target, edge) {
|
|
59
|
+
const data = edge?.data ?? {};
|
|
60
|
+
const route = data.route ?? "straight";
|
|
61
|
+
const straight = route === "straight";
|
|
62
|
+
const base = straight ? {
|
|
63
|
+
path: `M${p(source)}L${p(target)}`,
|
|
64
|
+
label: {
|
|
65
|
+
x: (source.x + target.x) / 2,
|
|
66
|
+
y: (source.y + target.y) / 2
|
|
67
|
+
}
|
|
68
|
+
} : (route === "elbow" ? stepEdge : bezierEdge).route(source, target, edge);
|
|
69
|
+
const arrow = data.arrow ?? "end";
|
|
70
|
+
let marks = "";
|
|
71
|
+
if (arrow !== "none") marks += arrowhead(target, arrival(target, source, straight));
|
|
72
|
+
if (arrow === "both") marks += arrowhead(source, arrival(source, target, straight));
|
|
73
|
+
return {
|
|
74
|
+
...base,
|
|
75
|
+
marks: marks || void 0
|
|
76
|
+
};
|
|
77
|
+
},
|
|
78
|
+
style(edge) {
|
|
79
|
+
const dash = DASHES[edge.data?.dash ?? "solid"];
|
|
80
|
+
return dash ? { strokeDasharray: dash } : void 0;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
var ROUTES = [
|
|
84
|
+
{
|
|
85
|
+
value: "straight",
|
|
86
|
+
icon: ConnectorStraightIcon
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
value: "elbow",
|
|
90
|
+
icon: ConnectorElbowIcon
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
value: "curve",
|
|
94
|
+
icon: ConnectorCurveIcon
|
|
95
|
+
}
|
|
96
|
+
];
|
|
97
|
+
var DASH_CHOICES = [
|
|
98
|
+
{
|
|
99
|
+
value: "solid",
|
|
100
|
+
icon: LineSolidIcon
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
value: "dashed",
|
|
104
|
+
icon: LineDashedIcon
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
value: "dotted",
|
|
108
|
+
icon: LineDottedIcon
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
value: "dash-dot",
|
|
112
|
+
icon: LineDashDotIcon
|
|
113
|
+
}
|
|
114
|
+
];
|
|
115
|
+
var ARROWS = [
|
|
116
|
+
{
|
|
117
|
+
value: "none",
|
|
118
|
+
icon: MinusIcon
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
value: "end",
|
|
122
|
+
icon: ArrowRightIcon
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
value: "both",
|
|
126
|
+
icon: ArrowLeftRightIcon
|
|
127
|
+
}
|
|
128
|
+
];
|
|
129
|
+
var data = (edge) => edge.data ?? {};
|
|
130
|
+
var isConnector = (edge) => (edge.type ?? "connector") === "connector";
|
|
131
|
+
/** Route, dash and arrow choices over the selected connectors. */
|
|
132
|
+
function connectorToolbar(ctx, edges) {
|
|
133
|
+
if (!edges.every(isConnector)) return [];
|
|
134
|
+
const t = ctx.messages.value.board;
|
|
135
|
+
/**
|
|
136
|
+
* One button: its label and icon say the value in use, or that the
|
|
137
|
+
* selected connectors differ; its menu sets one value on all of them.
|
|
138
|
+
*/
|
|
139
|
+
function choice(key, fallback, options, names, text, mixedIcon) {
|
|
140
|
+
const values = new Set(edges.map((edge) => data(edge)[key] ?? fallback));
|
|
141
|
+
const only = values.size === 1 ? [...values][0] : null;
|
|
142
|
+
return {
|
|
143
|
+
key,
|
|
144
|
+
label: format(text.of, { value: only ? names[only] : t.mixed }),
|
|
145
|
+
icon: only ? options.find((item) => item.value === only).icon : mixedIcon,
|
|
146
|
+
children: options.map((item) => ({
|
|
147
|
+
key: `${key}-${item.value}`,
|
|
148
|
+
label: names[item.value],
|
|
149
|
+
icon: item.icon,
|
|
150
|
+
checked: item.value === only,
|
|
151
|
+
run() {
|
|
152
|
+
ctx.transact((tr) => {
|
|
153
|
+
for (const edge of edges) tr.updateEdge(edge.id, { data: {
|
|
154
|
+
...edge.data,
|
|
155
|
+
[key]: item.value
|
|
156
|
+
} });
|
|
157
|
+
});
|
|
158
|
+
ctx.announce(format(text.set, { value: names[item.value] }));
|
|
159
|
+
}
|
|
160
|
+
}))
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
return [
|
|
164
|
+
choice("route", "straight", ROUTES, t.routes, {
|
|
165
|
+
of: t.routeOf,
|
|
166
|
+
set: t.routeSet
|
|
167
|
+
}, RouteIcon),
|
|
168
|
+
choice("dash", "solid", DASH_CHOICES, t.dashes, {
|
|
169
|
+
of: t.dashOf,
|
|
170
|
+
set: t.dashSet
|
|
171
|
+
}, LineStyleIcon),
|
|
172
|
+
choice("arrow", "end", ARROWS, t.arrows, {
|
|
173
|
+
of: t.arrowOf,
|
|
174
|
+
set: t.arrowSet
|
|
175
|
+
}, ArrowEndsIcon)
|
|
176
|
+
];
|
|
177
|
+
}
|
|
178
|
+
export { connectorEdge, connectorToolbar };
|
|
179
|
+
|
|
180
|
+
//# sourceMappingURL=connector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connector.js","names":[],"sources":["../../src/whiteboard/connector.ts"],"sourcesContent":["import {\n ArrowEndsIcon,\n ArrowLeftRightIcon,\n ArrowRightIcon,\n ConnectorCurveIcon,\n ConnectorElbowIcon,\n ConnectorStraightIcon,\n LineDashDotIcon,\n LineDashedIcon,\n LineDottedIcon,\n LineSolidIcon,\n LineStyleIcon,\n MinusIcon,\n RouteIcon,\n} from '@dou-ui/icons'\nimport type { Component } from 'vue'\nimport type { FlowEditorContext } from '../context'\nimport { bezierEdge, defineEdgeType, stepEdge, type EdgeAnchor } from '../core/edge-types'\nimport type { FlowToolbarItem } from '../core/menu'\nimport type { FlowEdge, Point, PortSide } from '../core/model'\nimport { format } from '../locale'\nimport type {\n WhiteboardArrow,\n WhiteboardConnectorData,\n WhiteboardDash,\n WhiteboardRoute,\n} from './model'\n\nconst ARROW = { length: 10, spread: 0.45 }\nconst r2 = (v: number) => Math.round(v * 100) / 100\nconst p = (pt: Point) => `${r2(pt.x)} ${r2(pt.y)}`\n\n/** Out of a side, away from the node. */\nconst outward: Record<PortSide, Point> = {\n left: { x: -1, y: 0 },\n right: { x: 1, y: 0 },\n top: { x: 0, y: -1 },\n bottom: { x: 0, y: 1 },\n}\n\n/** An open arrowhead with its tip at `tip`, pointing along `angle`. */\nfunction arrowhead(tip: Point, angle: number) {\n const wing = (spread: number) => ({\n x: tip.x - ARROW.length * Math.cos(angle + spread),\n y: tip.y - ARROW.length * Math.sin(angle + spread),\n })\n return `M${p(wing(ARROW.spread))}L${p(tip)}L${p(wing(-ARROW.spread))}`\n}\n\n/** The way a run arrives at `end`: straight from `from`, or into the side it faces. */\nfunction arrival(end: EdgeAnchor, from: Point, straight: boolean) {\n if (straight) return Math.atan2(end.y - from.y, end.x - from.x)\n const out = outward[end.side]\n return Math.atan2(-out.y, -out.x)\n}\n\n/** Dash patterns, in px of a 1.5px stroke with round ends: a 0 dash is a dot. */\nconst DASHES: Record<WhiteboardDash, string | undefined> = {\n solid: undefined,\n dashed: '6 5',\n dotted: '0 4',\n 'dash-dot': '8 4 0 4',\n}\n\n/**\n * The whiteboard's line: straight, with right-angled turns or curved\n * (`data.route`), solid or dashed (`data.dash`), with arrowheads at the end,\n * both ends or neither (`data.arrow`).\n */\nexport const connectorEdge = defineEdgeType<WhiteboardConnectorData>({\n type: 'connector',\n route(source, target, edge) {\n const data = edge?.data ?? {}\n const route = data.route ?? 'straight'\n const straight = route === 'straight'\n const base = straight\n ? {\n path: `M${p(source)}L${p(target)}`,\n label: { x: (source.x + target.x) / 2, y: (source.y + target.y) / 2 },\n }\n : (route === 'elbow' ? stepEdge : bezierEdge).route(source, target, edge as FlowEdge)\n const arrow = data.arrow ?? 'end'\n let marks = ''\n if (arrow !== 'none') marks += arrowhead(target, arrival(target, source, straight))\n if (arrow === 'both') marks += arrowhead(source, arrival(source, target, straight))\n return { ...base, marks: marks || undefined }\n },\n style(edge) {\n const dash = DASHES[edge.data?.dash ?? 'solid']\n return dash ? { strokeDasharray: dash } : undefined\n },\n})\n\nconst ROUTES: { value: WhiteboardRoute; icon: Component }[] = [\n { value: 'straight', icon: ConnectorStraightIcon },\n { value: 'elbow', icon: ConnectorElbowIcon },\n { value: 'curve', icon: ConnectorCurveIcon },\n]\nconst DASH_CHOICES: { value: WhiteboardDash; icon: Component }[] = [\n { value: 'solid', icon: LineSolidIcon },\n { value: 'dashed', icon: LineDashedIcon },\n { value: 'dotted', icon: LineDottedIcon },\n { value: 'dash-dot', icon: LineDashDotIcon },\n]\nconst ARROWS: { value: WhiteboardArrow; icon: Component }[] = [\n { value: 'none', icon: MinusIcon },\n { value: 'end', icon: ArrowRightIcon },\n { value: 'both', icon: ArrowLeftRightIcon },\n]\n\nconst data = (edge: FlowEdge) => (edge.data ?? {}) as WhiteboardConnectorData\nconst isConnector = (edge: FlowEdge) => (edge.type ?? 'connector') === 'connector'\n\n/** Route, dash and arrow choices over the selected connectors. */\nexport function connectorToolbar(ctx: FlowEditorContext, edges: FlowEdge[]): FlowToolbarItem[] {\n if (!edges.every(isConnector)) return []\n const t = ctx.messages.value.board\n\n /**\n * One button: its label and icon say the value in use, or that the\n * selected connectors differ; its menu sets one value on all of them.\n */\n function choice<V extends string>(\n key: 'route' | 'dash' | 'arrow',\n fallback: V,\n options: { value: V; icon: Component }[],\n names: Record<V, string>,\n text: { of: string; set: string },\n mixedIcon: Component,\n ): FlowToolbarItem {\n const values = new Set(edges.map((edge) => (data(edge)[key] as V | undefined) ?? fallback))\n const only = values.size === 1 ? [...values][0]! : null\n return {\n key,\n label: format(text.of, { value: only ? names[only] : t.mixed }),\n icon: only ? options.find((item) => item.value === only)!.icon : mixedIcon,\n children: options.map((item) => ({\n key: `${key}-${item.value}`,\n label: names[item.value],\n icon: item.icon,\n checked: item.value === only,\n run() {\n ctx.transact((tr) => {\n for (const edge of edges)\n tr.updateEdge(edge.id, { data: { ...edge.data, [key]: item.value } })\n })\n ctx.announce(format(text.set, { value: names[item.value] }))\n },\n })),\n }\n }\n\n return [\n choice('route', 'straight', ROUTES, t.routes, { of: t.routeOf, set: t.routeSet }, RouteIcon),\n choice(\n 'dash',\n 'solid',\n DASH_CHOICES,\n t.dashes,\n { of: t.dashOf, set: t.dashSet },\n LineStyleIcon,\n ),\n choice('arrow', 'end', ARROWS, t.arrows, { of: t.arrowOf, set: t.arrowSet }, ArrowEndsIcon),\n ]\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { fitInk, inkOutline, unpack } from "./ink.js";
|
|
2
|
+
// src/whiteboard/draw.ts
|
|
3
|
+
var px = (value, fallback) => {
|
|
4
|
+
const n = Number.parseFloat(value);
|
|
5
|
+
return Number.isFinite(n) ? n : fallback;
|
|
6
|
+
};
|
|
7
|
+
var r = (value) => Math.round(value * 100) / 100;
|
|
8
|
+
/** A board node as SVG, as `board-node.vue` draws it. */
|
|
9
|
+
function drawBoardNode(node, draw) {
|
|
10
|
+
const data = node.data ?? {};
|
|
11
|
+
const kind = node.type === "shape" || !node.type ? "shape" : node.type;
|
|
12
|
+
const tone = data.tone ?? (kind === "sticky" ? "warning" : "neutral");
|
|
13
|
+
const shape = data.shape ?? "rect";
|
|
14
|
+
const nodeClass = `dou-whiteboard-node is-${kind}${kind === "shape" ? ` is-${shape}` : ""} is-${tone}`;
|
|
15
|
+
const { width: w, height: h } = draw;
|
|
16
|
+
const parts = [];
|
|
17
|
+
if (kind === "shape") {
|
|
18
|
+
const s = draw.style(nodeClass, "dou-whiteboard-node__shape");
|
|
19
|
+
const stroke = px(s.strokeWidth, 1.5);
|
|
20
|
+
const i = stroke / 2;
|
|
21
|
+
const paint = `fill="${draw.color(s.fill)}" stroke="${draw.color(s.stroke)}" stroke-width="${stroke}" stroke-linejoin="round"`;
|
|
22
|
+
if (shape === "ellipse") parts.push(`<ellipse cx="${r(w / 2)}" cy="${r(h / 2)}" rx="${r(Math.max(0, w / 2 - i))}" ry="${r(Math.max(0, h / 2 - i))}" ${paint}/>`);
|
|
23
|
+
else if (shape === "diamond") parts.push(`<polygon points="${r(w / 2)},1 ${r(w - 1)},${r(h / 2)} ${r(w / 2)},${r(h - 1)} 1,${r(h / 2)}" ${paint}/>`);
|
|
24
|
+
else parts.push(`<rect x="${i}" y="${i}" width="${r(Math.max(0, w - stroke))}" height="${r(Math.max(0, h - stroke))}" rx="8" ${paint}/>`);
|
|
25
|
+
} else if (kind === "sticky") {
|
|
26
|
+
const s = draw.style(nodeClass);
|
|
27
|
+
parts.push(`<rect width="${r(w)}" height="${r(h)}" rx="${px(s.borderTopLeftRadius, 4)}" fill="${draw.color(s.backgroundColor)}"/>`);
|
|
28
|
+
} else if (kind === "ink" && data.points && data.box) {
|
|
29
|
+
const s = draw.style(nodeClass, "dou-whiteboard-node__ink");
|
|
30
|
+
const size = data.size ?? 4;
|
|
31
|
+
const d = inkOutline(fitInk(unpack(data.points), data.box, w, h, size), size);
|
|
32
|
+
parts.push(`<path d="${d}" fill="${draw.color(s.fill)}"/>`);
|
|
33
|
+
} else if (kind === "image" && data.src) {
|
|
34
|
+
const s = draw.style(nodeClass, "dou-whiteboard-node__image");
|
|
35
|
+
parts.push(draw.image(data.src, {
|
|
36
|
+
x: 0,
|
|
37
|
+
y: 0,
|
|
38
|
+
width: w,
|
|
39
|
+
height: h
|
|
40
|
+
}, { radius: px(s.borderTopLeftRadius, 4) }));
|
|
41
|
+
}
|
|
42
|
+
if (data.text && (kind === "shape" || kind === "sticky" || kind === "text")) {
|
|
43
|
+
const s = draw.style(nodeClass, "dou-whiteboard-node__text");
|
|
44
|
+
const size = px(s.fontSize, 14);
|
|
45
|
+
const top = px(s.paddingTop, 12);
|
|
46
|
+
const side = px(s.paddingLeft, 16);
|
|
47
|
+
const lineHeight = px(s.lineHeight, size * 1.5);
|
|
48
|
+
const centred = kind !== "text";
|
|
49
|
+
parts.push(draw.text(data.text, {
|
|
50
|
+
x: side,
|
|
51
|
+
y: top,
|
|
52
|
+
width: Math.max(0, w - side * 2),
|
|
53
|
+
height: Math.max(0, h - top * 2)
|
|
54
|
+
}, {
|
|
55
|
+
size,
|
|
56
|
+
lineHeight,
|
|
57
|
+
color: draw.color(draw.style(nodeClass).color),
|
|
58
|
+
align: centred ? "center" : "start",
|
|
59
|
+
valign: centred ? "middle" : "top"
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
const body = parts.join("");
|
|
63
|
+
const rotation = data.rotation;
|
|
64
|
+
return rotation ? `<g transform="rotate(${rotation} ${r(w / 2)} ${r(h / 2)})">${body}</g>` : body;
|
|
65
|
+
}
|
|
66
|
+
export { drawBoardNode };
|
|
67
|
+
|
|
68
|
+
//# sourceMappingURL=draw.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"draw.js","names":[],"sources":["../../src/whiteboard/draw.ts"],"sourcesContent":["import type { FlowNode } from '../core/model'\nimport type { FlowDrawContext } from '../export/context'\nimport { fitInk, inkOutline, INK_SIZE, unpack } from './ink'\nimport type { WhiteboardNodeData, WhiteboardShape } from './model'\n\nconst px = (value: string, fallback: number) => {\n const n = Number.parseFloat(value)\n return Number.isFinite(n) ? n : fallback\n}\nconst r = (value: number) => Math.round(value * 100) / 100\n\n/** A board node as SVG, as `board-node.vue` draws it. */\nexport function drawBoardNode(node: FlowNode<WhiteboardNodeData>, draw: FlowDrawContext): string {\n const data = (node.data ?? {}) as WhiteboardNodeData & {\n shape?: WhiteboardShape\n text?: string\n tone?: string\n points?: number[]\n box?: [number, number]\n size?: number\n src?: string\n }\n const kind = node.type === 'shape' || !node.type ? 'shape' : node.type\n const tone = data.tone ?? (kind === 'sticky' ? 'warning' : 'neutral')\n const shape = data.shape ?? 'rect'\n const nodeClass = `dou-whiteboard-node is-${kind}${kind === 'shape' ? ` is-${shape}` : ''} is-${tone}`\n const { width: w, height: h } = draw\n const parts: string[] = []\n\n if (kind === 'shape') {\n const s = draw.style(nodeClass, 'dou-whiteboard-node__shape')\n const stroke = px(s.strokeWidth, 1.5)\n const i = stroke / 2\n const paint = `fill=\"${draw.color(s.fill)}\" stroke=\"${draw.color(s.stroke)}\" stroke-width=\"${stroke}\" stroke-linejoin=\"round\"`\n if (shape === 'ellipse')\n parts.push(\n `<ellipse cx=\"${r(w / 2)}\" cy=\"${r(h / 2)}\" rx=\"${r(Math.max(0, w / 2 - i))}\" ry=\"${r(Math.max(0, h / 2 - i))}\" ${paint}/>`,\n )\n else if (shape === 'diamond')\n parts.push(\n `<polygon points=\"${r(w / 2)},${1} ${r(w - 1)},${r(h / 2)} ${r(w / 2)},${r(h - 1)} 1,${r(h / 2)}\" ${paint}/>`,\n )\n else\n parts.push(\n `<rect x=\"${i}\" y=\"${i}\" width=\"${r(Math.max(0, w - stroke))}\" height=\"${r(Math.max(0, h - stroke))}\" rx=\"8\" ${paint}/>`,\n )\n } else if (kind === 'sticky') {\n const s = draw.style(nodeClass)\n parts.push(\n `<rect width=\"${r(w)}\" height=\"${r(h)}\" rx=\"${px(s.borderTopLeftRadius, 4)}\" fill=\"${draw.color(s.backgroundColor)}\"/>`,\n )\n } else if (kind === 'ink' && data.points && data.box) {\n const s = draw.style(nodeClass, 'dou-whiteboard-node__ink')\n const size = data.size ?? INK_SIZE\n const d = inkOutline(fitInk(unpack(data.points), data.box, w, h, size), size)\n parts.push(`<path d=\"${d}\" fill=\"${draw.color(s.fill)}\"/>`)\n } else if (kind === 'image' && data.src) {\n const s = draw.style(nodeClass, 'dou-whiteboard-node__image')\n parts.push(\n draw.image(\n data.src,\n { x: 0, y: 0, width: w, height: h },\n { radius: px(s.borderTopLeftRadius, 4) },\n ),\n )\n }\n\n if (data.text && (kind === 'shape' || kind === 'sticky' || kind === 'text')) {\n const s = draw.style(nodeClass, 'dou-whiteboard-node__text')\n const size = px(s.fontSize, 14)\n const top = px(s.paddingTop, 12)\n const side = px(s.paddingLeft, 16)\n const lineHeight = px(s.lineHeight, size * 1.5)\n const centred = kind !== 'text'\n parts.push(\n draw.text(\n data.text,\n { x: side, y: top, width: Math.max(0, w - side * 2), height: Math.max(0, h - top * 2) },\n {\n size,\n lineHeight,\n color: draw.color(draw.style(nodeClass).color),\n align: centred ? 'center' : 'start',\n valign: centred ? 'middle' : 'top',\n },\n ),\n )\n }\n\n const body = parts.join('')\n const rotation = data.rotation\n return rotation ? `<g transform=\"rotate(${rotation} ${r(w / 2)} ${r(h / 2)})\">${body}</g>` : body\n}\n"],"mappings":"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"}
|