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