@joewinke/jatui 0.1.28 → 0.1.29
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/dist/actions/collapse.js +15 -29
- package/dist/actions/listNav.js +108 -137
- package/dist/actions/railNav.js +72 -89
- package/dist/components/markup.js +59 -62
- package/dist/components/session-nav/transcriptModel.js +49 -66
- package/dist/data/emojis.js +4 -4
- package/dist/stores/confirmDialog.svelte.js +5 -5
- package/dist/stores/inputDialog.svelte.js +5 -5
- package/dist/types/booking.js +4 -4
- package/dist/types/comments.js +2 -2
- package/dist/types/messaging.js +3 -3
- package/dist/types/pipeline.js +2 -2
- package/dist/utils/currency.js +2 -4
- package/dist/utils/daisyuiColors.js +25 -27
- package/dist/utils/dateFormatters.js +44 -44
- package/dist/utils/googleMapsLoader.js +46 -100
- package/dist/utils/mentionParser.js +30 -30
- package/dist/utils/phone.js +21 -27
- package/dist/utils/phoneFormat.js +9 -9
- package/dist/utils/taskUtils.js +1 -1
- package/package.json +6 -4
package/dist/actions/railNav.js
CHANGED
|
@@ -60,17 +60,6 @@
|
|
|
60
60
|
* 3. Handle Left/Right in `onAction` and call `rail.bump(host)` on change:
|
|
61
61
|
* onAction: (id, dir, host) => { formData[id] = cycle(opts[id], …, dir); rail.bump(host); }
|
|
62
62
|
*/
|
|
63
|
-
var __assign = (this && this.__assign) || function () {
|
|
64
|
-
__assign = Object.assign || function(t) {
|
|
65
|
-
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
66
|
-
s = arguments[i];
|
|
67
|
-
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
68
|
-
t[p] = s[p];
|
|
69
|
-
}
|
|
70
|
-
return t;
|
|
71
|
-
};
|
|
72
|
-
return __assign.apply(this, arguments);
|
|
73
|
-
};
|
|
74
63
|
// ─── Exported helpers ──────────────────────────────────────────────────────────
|
|
75
64
|
/**
|
|
76
65
|
* Cycle through `list`, returning the item `dir` steps from `current`.
|
|
@@ -84,40 +73,39 @@ var __assign = (this && this.__assign) || function () {
|
|
|
84
73
|
export function cycle(list, current, dir) {
|
|
85
74
|
if (list.length === 0)
|
|
86
75
|
return current;
|
|
87
|
-
|
|
76
|
+
const i = list.indexOf(current);
|
|
88
77
|
// When `current` is not in the list, treat it as if we're at position -1
|
|
89
78
|
// so that `dir=1` lands at index 0 (the first item) and `dir=-1` lands at
|
|
90
79
|
// the last item — matching the "not found → start from the beginning" idiom.
|
|
91
|
-
|
|
80
|
+
const base = i === -1 ? -1 : i;
|
|
92
81
|
return list[(((base + dir) % list.length) + list.length) % list.length];
|
|
93
82
|
}
|
|
94
83
|
// ─── Internals ─────────────────────────────────────────────────────────────────
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
84
|
+
const BUMP_DURATION_MS = 280;
|
|
85
|
+
const RAIL_FOCUS_CLASS = 'rail-focus';
|
|
86
|
+
const RAIL_CYCLED_CLASS = 'rail-cycled';
|
|
98
87
|
function resolveOrder(opts) {
|
|
99
88
|
return typeof opts.order === 'function' ? opts.order() : opts.order;
|
|
100
89
|
}
|
|
101
90
|
function resolveAttr(opts) {
|
|
102
|
-
|
|
103
|
-
return (_a = opts.attr) !== null && _a !== void 0 ? _a : 'data-rail-id';
|
|
91
|
+
return opts.attr ?? 'data-rail-id';
|
|
104
92
|
}
|
|
105
93
|
/** Return all currently-rendered rung hosts in the rail order. */
|
|
106
94
|
function resolveHosts(optsRef) {
|
|
107
|
-
|
|
95
|
+
const { container } = optsRef.current;
|
|
108
96
|
if (!container)
|
|
109
97
|
return [];
|
|
110
|
-
|
|
111
|
-
|
|
98
|
+
const attr = resolveAttr(optsRef.current);
|
|
99
|
+
const order = resolveOrder(optsRef.current);
|
|
112
100
|
return order
|
|
113
|
-
.map(
|
|
101
|
+
.map((id) => container.querySelector(`[${attr}="${id}"]`))
|
|
114
102
|
// checkVisibility() correctly returns false for elements inside a closed <details>
|
|
115
103
|
// (browsers keep offsetParent non-null for those, so that check alone is insufficient).
|
|
116
104
|
// Where checkVisibility is unavailable (jsdom, older engines) treat the rung as
|
|
117
105
|
// visible — a rendered element in `order` is navigable by default; only the
|
|
118
106
|
// closed-<details> case needs the extra exclusion, and that only exists in a
|
|
119
107
|
// real layout engine.
|
|
120
|
-
.filter(
|
|
108
|
+
.filter((el) => !!el && (el.checkVisibility?.() ?? true));
|
|
121
109
|
}
|
|
122
110
|
/**
|
|
123
111
|
* Focus the most appropriate control inside a rung host:
|
|
@@ -132,17 +120,17 @@ function resolveHosts(optsRef) {
|
|
|
132
120
|
* 4. Last resort: focus the host itself (requires tabindex on the host).
|
|
133
121
|
*/
|
|
134
122
|
function focusRailHost(host) {
|
|
135
|
-
var _a;
|
|
136
123
|
if (host.matches('input,select,textarea,button,[contenteditable="true"],[tabindex]')) {
|
|
137
124
|
host.focus();
|
|
138
125
|
return;
|
|
139
126
|
}
|
|
140
|
-
|
|
141
|
-
|
|
127
|
+
const inner = host.querySelector('[contenteditable="true"],input,select,textarea') ??
|
|
128
|
+
host.querySelector('.sd-trigger,button');
|
|
129
|
+
(inner ?? host).focus();
|
|
142
130
|
}
|
|
143
131
|
function makeController(optsRef) {
|
|
144
|
-
|
|
145
|
-
|
|
132
|
+
let _currentRailId = '';
|
|
133
|
+
let _lastFocusedHost = null;
|
|
146
134
|
function applyFocusClass(host) {
|
|
147
135
|
if (_lastFocusedHost && _lastFocusedHost !== host) {
|
|
148
136
|
_lastFocusedHost.classList.remove(RAIL_FOCUS_CLASS);
|
|
@@ -157,14 +145,14 @@ function makeController(optsRef) {
|
|
|
157
145
|
host.classList.remove(RAIL_CYCLED_CLASS);
|
|
158
146
|
void host.offsetWidth; // reflow so the animation restarts on rapid presses
|
|
159
147
|
host.classList.add(RAIL_CYCLED_CLASS);
|
|
160
|
-
setTimeout(
|
|
148
|
+
setTimeout(() => host.classList.remove(RAIL_CYCLED_CLASS), BUMP_DURATION_MS);
|
|
161
149
|
}
|
|
162
150
|
function focus(id) {
|
|
163
|
-
|
|
151
|
+
const { container } = optsRef.current;
|
|
164
152
|
if (!container)
|
|
165
153
|
return;
|
|
166
|
-
|
|
167
|
-
|
|
154
|
+
const attr = resolveAttr(optsRef.current);
|
|
155
|
+
const host = container.querySelector(`[${attr}="${id}"]`);
|
|
168
156
|
if (host) {
|
|
169
157
|
focusRailHost(host);
|
|
170
158
|
applyFocusClass(host);
|
|
@@ -175,21 +163,20 @@ function makeController(optsRef) {
|
|
|
175
163
|
}
|
|
176
164
|
}
|
|
177
165
|
function moveRung(dir) {
|
|
178
|
-
|
|
179
|
-
var hosts = resolveHosts(optsRef);
|
|
166
|
+
const hosts = resolveHosts(optsRef);
|
|
180
167
|
if (hosts.length === 0)
|
|
181
168
|
return;
|
|
182
|
-
|
|
183
|
-
|
|
169
|
+
const attr = resolveAttr(optsRef.current);
|
|
170
|
+
const ids = hosts.map((h) => h.getAttribute(attr));
|
|
184
171
|
// Anchor on the actually-focused rung first. Fall back to the last tracked
|
|
185
172
|
// rung (covers focus parked on the container after an Escape pop-out).
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
173
|
+
const active = document.activeElement;
|
|
174
|
+
const activeFocused = (active?.closest(`[${attr}]`))?.getAttribute(attr);
|
|
175
|
+
const fromId = activeFocused && ids.includes(activeFocused) ? activeFocused : _currentRailId;
|
|
176
|
+
let pos = fromId ? ids.indexOf(fromId) : -1;
|
|
190
177
|
if (pos === -1)
|
|
191
178
|
pos = dir === 1 ? -1 : hosts.length;
|
|
192
|
-
|
|
179
|
+
const next = (((pos + dir) % hosts.length) + hosts.length) % hosts.length;
|
|
193
180
|
focusRailHost(hosts[next]);
|
|
194
181
|
applyFocusClass(hosts[next]);
|
|
195
182
|
_currentRailId = ids[next];
|
|
@@ -197,10 +184,9 @@ function makeController(optsRef) {
|
|
|
197
184
|
function refresh() {
|
|
198
185
|
// Re-derive hosts; re-apply focus class if the current rung is still rendered
|
|
199
186
|
// (DOM may have been reconstructed by Svelte's reactive updates).
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
for (
|
|
203
|
-
var h = hosts_1[_i];
|
|
187
|
+
const hosts = resolveHosts(optsRef);
|
|
188
|
+
const attr = resolveAttr(optsRef.current);
|
|
189
|
+
for (const h of hosts) {
|
|
204
190
|
if (h.getAttribute(attr) === _currentRailId) {
|
|
205
191
|
applyFocusClass(h);
|
|
206
192
|
return;
|
|
@@ -208,11 +194,10 @@ function makeController(optsRef) {
|
|
|
208
194
|
}
|
|
209
195
|
}
|
|
210
196
|
function handleFocusin(event) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
var id = host === null || host === void 0 ? void 0 : host.getAttribute(attr);
|
|
197
|
+
const attr = resolveAttr(optsRef.current);
|
|
198
|
+
const order = resolveOrder(optsRef.current);
|
|
199
|
+
const host = event.target?.closest(`[${attr}]`);
|
|
200
|
+
const id = host?.getAttribute(attr);
|
|
216
201
|
// Only track rungs that are in the rail order. Tabbing into an off-rail
|
|
217
202
|
// control (e.g. a description textarea) leaves the last real rung intact
|
|
218
203
|
// so Up / Down can resume from it after the user tabs back out.
|
|
@@ -222,18 +207,17 @@ function makeController(optsRef) {
|
|
|
222
207
|
}
|
|
223
208
|
}
|
|
224
209
|
function handleKeydown(event) {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
var isTextField = !!el &&
|
|
210
|
+
const opts = optsRef.current;
|
|
211
|
+
const el = document.activeElement;
|
|
212
|
+
const tag = el?.tagName;
|
|
213
|
+
const isTextField = !!el &&
|
|
230
214
|
(tag === 'INPUT' || tag === 'TEXTAREA' || el.isContentEditable === true);
|
|
231
|
-
|
|
232
|
-
|
|
215
|
+
const isMultiline = !!el && (tag === 'TEXTAREA' || el.isContentEditable === true);
|
|
216
|
+
const hasMod = event.metaKey || event.ctrlKey || event.altKey;
|
|
233
217
|
// Modal trap — stop non-text keystrokes from bubbling to window-level page
|
|
234
218
|
// listeners (e.g., a listNav attached to `window`). Text-field keystrokes
|
|
235
219
|
// propagate normally so typing in the drawer doesn't interfere.
|
|
236
|
-
|
|
220
|
+
const trap = opts.trap !== false;
|
|
237
221
|
if (trap && !isTextField)
|
|
238
222
|
event.stopPropagation();
|
|
239
223
|
// Escape cascade:
|
|
@@ -241,33 +225,33 @@ function makeController(optsRef) {
|
|
|
241
225
|
// 2. focus inside a descendant control → pop back to container
|
|
242
226
|
// 3. focus already on the container (or just popped) → call onEscape
|
|
243
227
|
if (event.key === 'Escape' && !hasMod) {
|
|
244
|
-
|
|
245
|
-
if (
|
|
228
|
+
const popoverOpen = opts.isPopoverOpen?.() ?? false;
|
|
229
|
+
if (popoverOpen) {
|
|
246
230
|
event.preventDefault();
|
|
247
|
-
|
|
231
|
+
opts.onPopoverEscape?.();
|
|
248
232
|
return true;
|
|
249
233
|
}
|
|
250
|
-
|
|
234
|
+
const { container } = opts;
|
|
251
235
|
if (container && el && el !== container && container.contains(el)) {
|
|
252
236
|
event.preventDefault();
|
|
253
237
|
container.focus();
|
|
254
238
|
return true;
|
|
255
239
|
}
|
|
256
240
|
event.preventDefault();
|
|
257
|
-
|
|
241
|
+
opts.onEscape?.();
|
|
258
242
|
return true;
|
|
259
243
|
}
|
|
260
244
|
// Rail motion — suppressed when a popover is open or a modifier is held.
|
|
261
|
-
|
|
245
|
+
const popoverOpen = opts.isPopoverOpen?.() ?? false;
|
|
262
246
|
if (!hasMod && !popoverOpen) {
|
|
263
247
|
// Vertical: ArrowDown / j step down; ArrowUp / k step up.
|
|
264
248
|
// j / k are blocked in ANY text field; arrows are blocked only inside
|
|
265
249
|
// multiline fields so the caret can still move line-by-line.
|
|
266
|
-
|
|
267
|
-
|
|
250
|
+
const goDown = event.key === 'ArrowDown' || event.key === 'j';
|
|
251
|
+
const goUp = event.key === 'ArrowUp' || event.key === 'k';
|
|
268
252
|
if (goDown || goUp) {
|
|
269
|
-
|
|
270
|
-
|
|
253
|
+
const isVimKey = event.key === 'j' || event.key === 'k';
|
|
254
|
+
const blocked = isVimKey ? isTextField : isMultiline;
|
|
271
255
|
if (!blocked) {
|
|
272
256
|
event.preventDefault();
|
|
273
257
|
moveRung(goDown ? 1 : -1);
|
|
@@ -279,14 +263,15 @@ function makeController(optsRef) {
|
|
|
279
263
|
if ((event.key === 'ArrowLeft' || event.key === 'ArrowRight') &&
|
|
280
264
|
!isTextField) {
|
|
281
265
|
event.preventDefault();
|
|
282
|
-
|
|
266
|
+
const attr = resolveAttr(opts);
|
|
283
267
|
// Prefer the element that actually has DOM focus, then fall back to
|
|
284
268
|
// the last tracked rung (focus parked on the container after pop-out).
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
269
|
+
const focusedHost = (el?.closest(`[${attr}]`)) ??
|
|
270
|
+
(opts.container && _currentRailId
|
|
271
|
+
? opts.container.querySelector(`[${attr}="${_currentRailId}"]`)
|
|
272
|
+
: null);
|
|
288
273
|
if (focusedHost && opts.onAction) {
|
|
289
|
-
|
|
274
|
+
const rungId = focusedHost.getAttribute(attr) ?? '';
|
|
290
275
|
opts.onAction(rungId, event.key === 'ArrowRight' ? 1 : -1, focusedHost);
|
|
291
276
|
}
|
|
292
277
|
return true;
|
|
@@ -295,13 +280,13 @@ function makeController(optsRef) {
|
|
|
295
280
|
return false;
|
|
296
281
|
}
|
|
297
282
|
return {
|
|
298
|
-
handleKeydown
|
|
299
|
-
handleFocusin
|
|
300
|
-
moveRung
|
|
301
|
-
focus
|
|
302
|
-
refresh
|
|
303
|
-
currentId:
|
|
304
|
-
bump
|
|
283
|
+
handleKeydown,
|
|
284
|
+
handleFocusin,
|
|
285
|
+
moveRung,
|
|
286
|
+
focus,
|
|
287
|
+
refresh,
|
|
288
|
+
currentId: () => _currentRailId,
|
|
289
|
+
bump,
|
|
305
290
|
};
|
|
306
291
|
}
|
|
307
292
|
// ─── Composable ────────────────────────────────────────────────────────────────
|
|
@@ -316,7 +301,7 @@ function makeController(optsRef) {
|
|
|
316
301
|
* panel.addEventListener('focusin', (e) => rail.handleFocusin(e));
|
|
317
302
|
*/
|
|
318
303
|
export function createRailNav(options) {
|
|
319
|
-
|
|
304
|
+
const ref = { current: options };
|
|
320
305
|
return makeController(ref);
|
|
321
306
|
}
|
|
322
307
|
// ─── Action ────────────────────────────────────────────────────────────────────
|
|
@@ -331,10 +316,9 @@ export function createRailNav(options) {
|
|
|
331
316
|
* <div data-rail-id="priority" tabindex="-1">…</div>
|
|
332
317
|
* </div>
|
|
333
318
|
*/
|
|
334
|
-
export
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
var controller = makeController(ref);
|
|
319
|
+
export const railNav = (node, initial = { order: [] }) => {
|
|
320
|
+
const ref = { current: { ...initial, container: node } };
|
|
321
|
+
const controller = makeController(ref);
|
|
338
322
|
function onKeydown(e) {
|
|
339
323
|
controller.handleKeydown(e);
|
|
340
324
|
}
|
|
@@ -344,12 +328,11 @@ export var railNav = function (node, initial) {
|
|
|
344
328
|
node.addEventListener('keydown', onKeydown);
|
|
345
329
|
node.addEventListener('focusin', onFocusin);
|
|
346
330
|
return {
|
|
347
|
-
update:
|
|
348
|
-
|
|
349
|
-
ref.current = __assign(__assign({}, next), { container: node });
|
|
331
|
+
update(next = { order: [] }) {
|
|
332
|
+
ref.current = { ...next, container: node };
|
|
350
333
|
controller.refresh();
|
|
351
334
|
},
|
|
352
|
-
destroy
|
|
335
|
+
destroy() {
|
|
353
336
|
node.removeEventListener('keydown', onKeydown);
|
|
354
337
|
node.removeEventListener('focusin', onFocusin);
|
|
355
338
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Markup drawing primitives for construction drawing redlining
|
|
2
2
|
// Adapted from JAT feedback annotation engine with cloud bubble tool added
|
|
3
|
-
export
|
|
3
|
+
export const MARKUP_COLORS = [
|
|
4
4
|
"#ef4444",
|
|
5
5
|
"#eab308",
|
|
6
6
|
"#22c55e",
|
|
@@ -8,13 +8,13 @@ export var MARKUP_COLORS = [
|
|
|
8
8
|
"#ffffff",
|
|
9
9
|
"#111827",
|
|
10
10
|
];
|
|
11
|
-
export
|
|
12
|
-
|
|
11
|
+
export const DEFAULT_STROKE_WIDTH = 3;
|
|
12
|
+
let _nextId = 1;
|
|
13
13
|
export function nextShapeId() {
|
|
14
14
|
return _nextId++;
|
|
15
15
|
}
|
|
16
16
|
function drawArrow(ctx, shape) {
|
|
17
|
-
|
|
17
|
+
const { start, end, color, strokeWidth } = shape;
|
|
18
18
|
ctx.strokeStyle = color;
|
|
19
19
|
ctx.lineWidth = strokeWidth;
|
|
20
20
|
ctx.lineCap = "round";
|
|
@@ -24,9 +24,9 @@ function drawArrow(ctx, shape) {
|
|
|
24
24
|
ctx.lineTo(end.x, end.y);
|
|
25
25
|
ctx.stroke();
|
|
26
26
|
// Arrowhead
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
const angle = Math.atan2(end.y - start.y, end.x - start.x);
|
|
28
|
+
const headLen = 14;
|
|
29
|
+
const headAngle = Math.PI / 7;
|
|
30
30
|
ctx.beginPath();
|
|
31
31
|
ctx.moveTo(end.x, end.y);
|
|
32
32
|
ctx.lineTo(end.x - headLen * Math.cos(angle - headAngle), end.y - headLen * Math.sin(angle - headAngle));
|
|
@@ -35,24 +35,24 @@ function drawArrow(ctx, shape) {
|
|
|
35
35
|
ctx.stroke();
|
|
36
36
|
}
|
|
37
37
|
function drawRectangle(ctx, shape) {
|
|
38
|
-
|
|
38
|
+
const { start, end, color, strokeWidth } = shape;
|
|
39
39
|
ctx.strokeStyle = color;
|
|
40
40
|
ctx.lineWidth = strokeWidth;
|
|
41
41
|
ctx.lineJoin = "round";
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
const x = Math.min(start.x, end.x);
|
|
43
|
+
const y = Math.min(start.y, end.y);
|
|
44
|
+
const w = Math.abs(end.x - start.x);
|
|
45
|
+
const h = Math.abs(end.y - start.y);
|
|
46
46
|
ctx.strokeRect(x, y, w, h);
|
|
47
47
|
}
|
|
48
48
|
function drawEllipse(ctx, shape) {
|
|
49
|
-
|
|
49
|
+
const { start, end, color, strokeWidth } = shape;
|
|
50
50
|
ctx.strokeStyle = color;
|
|
51
51
|
ctx.lineWidth = strokeWidth;
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
const cx = (start.x + end.x) / 2;
|
|
53
|
+
const cy = (start.y + end.y) / 2;
|
|
54
|
+
const rx = Math.abs(end.x - start.x) / 2;
|
|
55
|
+
const ry = Math.abs(end.y - start.y) / 2;
|
|
56
56
|
if (rx < 1 || ry < 1)
|
|
57
57
|
return;
|
|
58
58
|
ctx.beginPath();
|
|
@@ -64,53 +64,51 @@ function drawEllipse(ctx, shape) {
|
|
|
64
64
|
* Standard construction markup tool for highlighting areas of change.
|
|
65
65
|
*/
|
|
66
66
|
function drawCloud(ctx, shape) {
|
|
67
|
-
|
|
67
|
+
const { start, end, color, strokeWidth } = shape;
|
|
68
68
|
ctx.strokeStyle = color;
|
|
69
69
|
ctx.lineWidth = strokeWidth;
|
|
70
70
|
ctx.lineCap = "round";
|
|
71
71
|
ctx.lineJoin = "round";
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
const x = Math.min(start.x, end.x);
|
|
73
|
+
const y = Math.min(start.y, end.y);
|
|
74
|
+
const w = Math.abs(end.x - start.x);
|
|
75
|
+
const h = Math.abs(end.y - start.y);
|
|
76
76
|
if (w < 4 || h < 4)
|
|
77
77
|
return;
|
|
78
78
|
// Generate perimeter points, then draw arcs between them
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
const arcSize = Math.min(w, h, 30); // arc diameter adapts to shape size
|
|
80
|
+
const perimeter = 2 * (w + h);
|
|
81
|
+
const numArcs = Math.max(8, Math.round(perimeter / arcSize));
|
|
82
82
|
// Collect points along the rectangle perimeter
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
const points = [];
|
|
84
|
+
const segLengths = [w, h, w, h];
|
|
85
|
+
const segDirs = [
|
|
86
86
|
{ dx: 1, dy: 0 },
|
|
87
87
|
{ dx: 0, dy: 1 },
|
|
88
88
|
{ dx: -1, dy: 0 },
|
|
89
89
|
{ dx: 0, dy: -1 },
|
|
90
90
|
];
|
|
91
|
-
|
|
92
|
-
{ x
|
|
93
|
-
{ x: x + w, y
|
|
91
|
+
const segStarts = [
|
|
92
|
+
{ x, y },
|
|
93
|
+
{ x: x + w, y },
|
|
94
94
|
{ x: x + w, y: y + h },
|
|
95
|
-
{ x
|
|
95
|
+
{ x, y: y + h },
|
|
96
96
|
];
|
|
97
|
-
|
|
98
|
-
for (
|
|
99
|
-
var l = segLengths_1[_i];
|
|
97
|
+
let totalLen = 0;
|
|
98
|
+
for (const l of segLengths)
|
|
100
99
|
totalLen += l;
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
var target = i * step;
|
|
100
|
+
const step = totalLen / numArcs;
|
|
101
|
+
let dist = 0;
|
|
102
|
+
let segIdx = 0;
|
|
103
|
+
let segDist = 0;
|
|
104
|
+
for (let i = 0; i < numArcs; i++) {
|
|
105
|
+
const target = i * step;
|
|
108
106
|
while (dist + segLengths[segIdx] - segDist < target && segIdx < 3) {
|
|
109
107
|
dist += segLengths[segIdx] - segDist;
|
|
110
108
|
segIdx++;
|
|
111
109
|
segDist = 0;
|
|
112
110
|
}
|
|
113
|
-
|
|
111
|
+
const remaining = target - dist;
|
|
114
112
|
points.push({
|
|
115
113
|
x: segStarts[segIdx].x + segDirs[segIdx].dx * (segDist + remaining),
|
|
116
114
|
y: segStarts[segIdx].y + segDirs[segIdx].dy * (segDist + remaining),
|
|
@@ -118,20 +116,20 @@ function drawCloud(ctx, shape) {
|
|
|
118
116
|
}
|
|
119
117
|
// Draw arcs between consecutive points
|
|
120
118
|
ctx.beginPath();
|
|
121
|
-
for (
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
119
|
+
for (let i = 0; i < points.length; i++) {
|
|
120
|
+
const p1 = points[i];
|
|
121
|
+
const p2 = points[(i + 1) % points.length];
|
|
122
|
+
const mx = (p1.x + p2.x) / 2;
|
|
123
|
+
const my = (p1.y + p2.y) / 2;
|
|
126
124
|
// Bulge outward from center of rectangle
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
125
|
+
const cx = x + w / 2;
|
|
126
|
+
const cy = y + h / 2;
|
|
127
|
+
const dx = mx - cx;
|
|
128
|
+
const dy = my - cy;
|
|
129
|
+
const len = Math.sqrt(dx * dx + dy * dy) || 1;
|
|
130
|
+
const bulge = arcSize * 0.35;
|
|
131
|
+
const ctrlX = mx + (dx / len) * bulge;
|
|
132
|
+
const ctrlY = my + (dy / len) * bulge;
|
|
135
133
|
if (i === 0) {
|
|
136
134
|
ctx.moveTo(p1.x, p1.y);
|
|
137
135
|
}
|
|
@@ -141,7 +139,7 @@ function drawCloud(ctx, shape) {
|
|
|
141
139
|
ctx.stroke();
|
|
142
140
|
}
|
|
143
141
|
function drawFreehand(ctx, shape) {
|
|
144
|
-
|
|
142
|
+
const { points, color, strokeWidth } = shape;
|
|
145
143
|
if (points.length < 2)
|
|
146
144
|
return;
|
|
147
145
|
ctx.strokeStyle = color;
|
|
@@ -150,16 +148,16 @@ function drawFreehand(ctx, shape) {
|
|
|
150
148
|
ctx.lineJoin = "round";
|
|
151
149
|
ctx.beginPath();
|
|
152
150
|
ctx.moveTo(points[0].x, points[0].y);
|
|
153
|
-
for (
|
|
151
|
+
for (let i = 1; i < points.length; i++) {
|
|
154
152
|
ctx.lineTo(points[i].x, points[i].y);
|
|
155
153
|
}
|
|
156
154
|
ctx.stroke();
|
|
157
155
|
}
|
|
158
156
|
function drawText(ctx, shape) {
|
|
159
|
-
|
|
157
|
+
const { position, content, color, fontSize } = shape;
|
|
160
158
|
if (!content)
|
|
161
159
|
return;
|
|
162
|
-
ctx.font =
|
|
160
|
+
ctx.font = `bold ${fontSize}px sans-serif`;
|
|
163
161
|
ctx.textBaseline = "top";
|
|
164
162
|
// Black outline for contrast on construction drawings
|
|
165
163
|
ctx.strokeStyle = "#000000";
|
|
@@ -194,8 +192,7 @@ export function renderShape(ctx, shape) {
|
|
|
194
192
|
ctx.restore();
|
|
195
193
|
}
|
|
196
194
|
export function renderAllShapes(ctx, shapes) {
|
|
197
|
-
for (
|
|
198
|
-
var shape = shapes_1[_i];
|
|
195
|
+
for (const shape of shapes) {
|
|
199
196
|
renderShape(ctx, shape);
|
|
200
197
|
}
|
|
201
198
|
}
|