dsh-figmark 1.1.2 → 1.2.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/lib/client.js +1382 -516
- package/lib/client.pdf.js +452 -65
- package/lib/index.js +7 -6
- package/market/index.json +2 -2
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -4,6 +4,7 @@ window.__ModuleLoader__.load({
|
|
|
4
4
|
var module = { exports: {} };
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
let react_dom = require("react-dom");
|
|
7
8
|
let react = require("react");
|
|
8
9
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
10
|
//#region src/client/locales.ts
|
|
@@ -32,11 +33,28 @@ window.__ModuleLoader__.load({
|
|
|
32
33
|
summary: "总体目标",
|
|
33
34
|
summaryPlaceholder: "可选:这张图整体要往哪个方向改(智能体会连同下面逐条说明一起读)",
|
|
34
35
|
marks: "标注清单",
|
|
36
|
+
markPage: "{page} 页",
|
|
37
|
+
markKindLabel: "图形:{kind}",
|
|
38
|
+
revealMark: "跳到这一处",
|
|
35
39
|
noMarks: "还没有标注",
|
|
36
40
|
noMarksHint: "选一个工具,在图上点一下或拖一下就开始。",
|
|
37
41
|
delete: "删除",
|
|
38
42
|
close: "关闭",
|
|
39
43
|
dock: "批注操作",
|
|
44
|
+
menuLabel: "图片操作",
|
|
45
|
+
menuSendToAgent: "发送给智能体",
|
|
46
|
+
menuSendToComposer: "发送到对话框",
|
|
47
|
+
menuOpen: "用默认应用打开",
|
|
48
|
+
menuReveal: "在文件管理器中显示",
|
|
49
|
+
menuCopyImage: "复制图片",
|
|
50
|
+
menuCopyRelative: "复制相对地址",
|
|
51
|
+
menuCopyAbsolute: "复制绝对地址",
|
|
52
|
+
menuOpened: "已请求用默认应用打开",
|
|
53
|
+
menuOpenFailed: "无法打开,请重试",
|
|
54
|
+
menuRevealed: "已请求在文件管理器中显示",
|
|
55
|
+
menuRevealFailed: "无法在文件管理器中显示,请重试",
|
|
56
|
+
menuCopied: "已复制路径",
|
|
57
|
+
menuCopyFailed: "复制失败,请重试",
|
|
40
58
|
sendToAgent: "发送给智能体",
|
|
41
59
|
sendToComposer: "发送到对话框",
|
|
42
60
|
sending: "正在发送…",
|
|
@@ -55,6 +73,10 @@ window.__ModuleLoader__.load({
|
|
|
55
73
|
pdfTitle: "PDF 批注",
|
|
56
74
|
previousPage: "上一页",
|
|
57
75
|
nextPage: "下一页",
|
|
76
|
+
pageJump: "跳转页码",
|
|
77
|
+
pageOf: "{page} / {pages}",
|
|
78
|
+
pagerLabel: "翻页",
|
|
79
|
+
sendNone: "还没有标注可发送",
|
|
58
80
|
pdfRendering: "正在渲染这一页…",
|
|
59
81
|
pdfOpenFailed: "打开 PDF 失败:",
|
|
60
82
|
documentScope: "共 {pages} 页 · {marks} 处批注",
|
|
@@ -120,11 +142,28 @@ window.__ModuleLoader__.load({
|
|
|
120
142
|
summary: "Overall goal",
|
|
121
143
|
summaryPlaceholder: "Optional: what this figure should become overall (the agent reads it with the marks below)",
|
|
122
144
|
marks: "Marks",
|
|
145
|
+
markPage: "p. {page}",
|
|
146
|
+
markKindLabel: "Shape: {kind}",
|
|
147
|
+
revealMark: "Go to this mark",
|
|
123
148
|
noMarks: "No marks yet",
|
|
124
149
|
noMarksHint: "Pick a tool, then click or drag on the figure to start.",
|
|
125
150
|
delete: "Delete",
|
|
126
151
|
close: "Close",
|
|
127
152
|
dock: "Annotation actions",
|
|
153
|
+
menuLabel: "Figure actions",
|
|
154
|
+
menuSendToAgent: "Send to agent",
|
|
155
|
+
menuSendToComposer: "Send to composer",
|
|
156
|
+
menuOpen: "Open with default app",
|
|
157
|
+
menuReveal: "Reveal in File Manager",
|
|
158
|
+
menuCopyImage: "Copy image",
|
|
159
|
+
menuCopyRelative: "Copy relative path",
|
|
160
|
+
menuCopyAbsolute: "Copy absolute path",
|
|
161
|
+
menuOpened: "Asked the system to open it",
|
|
162
|
+
menuOpenFailed: "Could not open it; please try again",
|
|
163
|
+
menuRevealed: "Asked the file manager to show it",
|
|
164
|
+
menuRevealFailed: "Could not reveal it; please try again",
|
|
165
|
+
menuCopied: "Path copied",
|
|
166
|
+
menuCopyFailed: "Could not copy; please try again",
|
|
128
167
|
sendToAgent: "Send to agent",
|
|
129
168
|
sendToComposer: "Send to composer",
|
|
130
169
|
sending: "Sending…",
|
|
@@ -143,6 +182,10 @@ window.__ModuleLoader__.load({
|
|
|
143
182
|
pdfTitle: "PDF annotations",
|
|
144
183
|
previousPage: "Previous page",
|
|
145
184
|
nextPage: "Next page",
|
|
185
|
+
pageJump: "Go to page",
|
|
186
|
+
pageOf: "{page} / {pages}",
|
|
187
|
+
pagerLabel: "Paging",
|
|
188
|
+
sendNone: "Nothing to send yet",
|
|
146
189
|
pdfRendering: "Rendering this page…",
|
|
147
190
|
pdfOpenFailed: "Opening the PDF failed: ",
|
|
148
191
|
documentScope: "{pages} pages · {marks} marks",
|
|
@@ -268,7 +311,7 @@ window.__ModuleLoader__.load({
|
|
|
268
311
|
* @returns the value, pulled to the nearest end of the range.
|
|
269
312
|
*/
|
|
270
313
|
function clampWeight(value) {
|
|
271
|
-
return Math.min(
|
|
314
|
+
return Math.min(8, Math.max(WEIGHT_MIN, value));
|
|
272
315
|
}
|
|
273
316
|
/**
|
|
274
317
|
* Whether a value is a stroke weight this plugin would write.
|
|
@@ -277,7 +320,7 @@ window.__ModuleLoader__.load({
|
|
|
277
320
|
* @returns true when it is one.
|
|
278
321
|
*/
|
|
279
322
|
function isWeight(value) {
|
|
280
|
-
return typeof value === "number" && Number.isFinite(value) && value >= .5 && value <=
|
|
323
|
+
return typeof value === "number" && Number.isFinite(value) && value >= .5 && value <= 8;
|
|
281
324
|
}
|
|
282
325
|
/**
|
|
283
326
|
* Suffix naming one page's files inside a paged document's name.
|
|
@@ -326,6 +369,86 @@ window.__ModuleLoader__.load({
|
|
|
326
369
|
function round(value) {
|
|
327
370
|
return Math.round(value);
|
|
328
371
|
}
|
|
372
|
+
/**
|
|
373
|
+
* Write a freehand stroke's extent as the box it was drawn in.
|
|
374
|
+
*
|
|
375
|
+
* The stroke itself is dozens of coordinates, most of which say nothing an agent
|
|
376
|
+
* can act on; the box says where on the figure to look, which is what the reading
|
|
377
|
+
* of the mark needs. `strokeExtents` names the area, and the phrase above it names
|
|
378
|
+
* the mark, so the pair still reads as one line.
|
|
379
|
+
* @param points - the stroke's sampled points.
|
|
380
|
+
* @returns the box as ` (x,y)-(x,y)`, or nothing for an empty stroke.
|
|
381
|
+
*/
|
|
382
|
+
function strokeExtents(points) {
|
|
383
|
+
let left = Number.POSITIVE_INFINITY;
|
|
384
|
+
let top = Number.POSITIVE_INFINITY;
|
|
385
|
+
let right = Number.NEGATIVE_INFINITY;
|
|
386
|
+
let bottom = Number.NEGATIVE_INFINITY;
|
|
387
|
+
for (const [x, y] of points) {
|
|
388
|
+
left = Math.min(left, x);
|
|
389
|
+
top = Math.min(top, y);
|
|
390
|
+
right = Math.max(right, x);
|
|
391
|
+
bottom = Math.max(bottom, y);
|
|
392
|
+
}
|
|
393
|
+
if (!Number.isFinite(left)) return "";
|
|
394
|
+
return ` (${round(left)},${round(top)})-(${round(right)},${round(bottom)})`;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Write two dragged corners as the rectangle they make.
|
|
398
|
+
*
|
|
399
|
+
* A box dragged up and to the left arrives as (bottom-right, top-left), and the
|
|
400
|
+
* agent reading `(187,138)-(42,88)` has to take a min and a max before it knows
|
|
401
|
+
* which area is meant. Both corners are therefore written left-to-right and
|
|
402
|
+
* top-to-bottom, so the same box always reads the same way however it was drawn.
|
|
403
|
+
* @param first - where the drag started.
|
|
404
|
+
* @param second - where it ended.
|
|
405
|
+
* @returns the corner pair as `(x,y)-(x,y)`, or nothing when the mark is unplaced.
|
|
406
|
+
*/
|
|
407
|
+
function extents(first, second) {
|
|
408
|
+
if (first === void 0 || second === void 0) return "";
|
|
409
|
+
return ` (${round(Math.min(first[0], second[0]))},${round(Math.min(first[1], second[1]))})-(${round(Math.max(first[0], second[0]))},${round(Math.max(first[1], second[1]))})`;
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Say how far a mark reaches outside the figure, when it does.
|
|
413
|
+
*
|
|
414
|
+
* A negative coordinate is an accurate description — the origin is the figure's
|
|
415
|
+
* top-left corner, so -57 is 57 pixels above it — but a reader has to notice the
|
|
416
|
+
* sign and then reason about what it implies. Naming the overhang outright costs
|
|
417
|
+
* about a dozen characters and removes that step: the agent is told the mark is
|
|
418
|
+
* off the picture and by how much, rather than being left to infer it. A mark that
|
|
419
|
+
* sits inside the figure says nothing at all, which is the common case.
|
|
420
|
+
* @param mark - the mark to measure.
|
|
421
|
+
* @param figure - the figure's size, when the document knows it.
|
|
422
|
+
* @param locale - language of the surrounding message.
|
|
423
|
+
* @returns the sentence fragment to append, or nothing when the mark is inside.
|
|
424
|
+
*/
|
|
425
|
+
function overhang(mark, figure, locale) {
|
|
426
|
+
const { width, height } = figure;
|
|
427
|
+
if (width === void 0 || height === void 0) return "";
|
|
428
|
+
let left = Number.POSITIVE_INFINITY;
|
|
429
|
+
let top = Number.POSITIVE_INFINITY;
|
|
430
|
+
let right = Number.NEGATIVE_INFINITY;
|
|
431
|
+
let bottom = Number.NEGATIVE_INFINITY;
|
|
432
|
+
for (const [x, y] of mark.points) {
|
|
433
|
+
left = Math.min(left, x);
|
|
434
|
+
top = Math.min(top, y);
|
|
435
|
+
right = Math.max(right, x);
|
|
436
|
+
bottom = Math.max(bottom, y);
|
|
437
|
+
}
|
|
438
|
+
const escape = (amount, zhWord, enWord) => {
|
|
439
|
+
const pixels = round(amount);
|
|
440
|
+
if (pixels <= 0) return "";
|
|
441
|
+
return locale === "zh" ? `${zhWord} ${pixels}` : `${enWord} ${pixels}`;
|
|
442
|
+
};
|
|
443
|
+
const escapes = [
|
|
444
|
+
escape(-left, "左", "left"),
|
|
445
|
+
escape(-top, "上", "up"),
|
|
446
|
+
escape(right - width, "右", "right"),
|
|
447
|
+
escape(bottom - height, "下", "down")
|
|
448
|
+
].filter((part) => part !== "");
|
|
449
|
+
if (escapes.length === 0) return "";
|
|
450
|
+
return locale === "zh" ? `(超出图面:${escapes.join("、")} 像素)` : ` (off the figure: ${escapes.join(", ")} px)`;
|
|
451
|
+
}
|
|
329
452
|
/** Describe one mark's shape in words, for the model-facing summary. */
|
|
330
453
|
function describeShape(mark, locale) {
|
|
331
454
|
const [first, second] = mark.points;
|
|
@@ -333,9 +456,12 @@ window.__ModuleLoader__.load({
|
|
|
333
456
|
const turned = mark.rotation === void 0 || mark.rotation === 0 ? "" : zh ? `(旋转 ${Math.round(mark.rotation)}°)` : ` (rotated ${Math.round(mark.rotation)}°)`;
|
|
334
457
|
switch (mark.kind) {
|
|
335
458
|
case "arrow": return first !== void 0 && second !== void 0 ? zh ? `箭头 (${round(first[0])},${round(first[1])}) → (${round(second[0])},${round(second[1])})${turned}` : `arrow (${round(first[0])},${round(first[1])}) → (${round(second[0])},${round(second[1])})${turned}` : zh ? "箭头" : "arrow";
|
|
336
|
-
case "rect": return
|
|
337
|
-
case "ellipse": return
|
|
338
|
-
case "pen":
|
|
459
|
+
case "rect": return (zh ? "框选" : "box") + extents(first, second) + turned;
|
|
460
|
+
case "ellipse": return (zh ? "圈选" : "ellipse") + extents(first, second) + turned;
|
|
461
|
+
case "pen": {
|
|
462
|
+
const box = strokeExtents(mark.points);
|
|
463
|
+
return `${zh ? "手绘线" : "freehand stroke"}${box}${turned}`;
|
|
464
|
+
}
|
|
339
465
|
case "point": return first !== void 0 ? zh ? `单点位于 (${round(first[0])},${round(first[1])})` : `at (${round(first[0])},${round(first[1])})` : zh ? "单点" : "a point";
|
|
340
466
|
}
|
|
341
467
|
}
|
|
@@ -350,7 +476,7 @@ window.__ModuleLoader__.load({
|
|
|
350
476
|
if (anchor.text !== void 0 && anchor.text !== "") parts.push(zh ? `元素文字「${anchor.text}」` : `element text "${anchor.text}"`);
|
|
351
477
|
if (anchor.selector !== void 0 && anchor.selector !== "") parts.push(zh ? `选择器 ${anchor.selector}` : `selector ${anchor.selector}`);
|
|
352
478
|
if (parts.length === 0) parts.push(`<${anchor.tag}>`);
|
|
353
|
-
return `(${parts.join(",")}
|
|
479
|
+
return zh ? `(${parts.join(",")})` : ` (${parts.join(", ")})`;
|
|
354
480
|
}
|
|
355
481
|
/**
|
|
356
482
|
* Render one mark as a numbered line the model reads.
|
|
@@ -362,9 +488,11 @@ window.__ModuleLoader__.load({
|
|
|
362
488
|
* @param mark - the mark to describe.
|
|
363
489
|
* @param index - zero-based position in the document, rendered as a circled number.
|
|
364
490
|
* @param locale - language of the surrounding message.
|
|
491
|
+
* @param figure - the figure these marks belong to, when its size is known; a mark
|
|
492
|
+
* that reaches past it then says so rather than leaving the sign to be noticed.
|
|
365
493
|
* @returns one line of the annotation summary.
|
|
366
494
|
*/
|
|
367
|
-
function describeMark(mark, index, locale) {
|
|
495
|
+
function describeMark(mark, index, locale, figure) {
|
|
368
496
|
const zh = locale === "zh";
|
|
369
497
|
const marker = [
|
|
370
498
|
"①",
|
|
@@ -380,8 +508,9 @@ window.__ModuleLoader__.load({
|
|
|
380
508
|
][index] ?? `${index + 1}.`;
|
|
381
509
|
const page = mark.page === void 0 ? "" : zh ? `第 ${mark.page} 页 ` : `page ${mark.page} `;
|
|
382
510
|
const detail = mark.detail !== void 0 && mark.detail.trim() !== "" ? mark.detail.trim() : "";
|
|
383
|
-
const
|
|
384
|
-
|
|
511
|
+
const escape = figure === void 0 ? "" : overhang(mark, figure, locale);
|
|
512
|
+
const parts = [`${marker} ${page}${describeShape(mark, locale)}${describeAnchor(mark, locale)}${escape}`];
|
|
513
|
+
if (detail !== "") parts.push(zh ? `批注:${detail}` : `note: ${detail}`);
|
|
385
514
|
if (detail === "") parts.push(zh ? "(未写说明)" : "(no detail)");
|
|
386
515
|
return parts.join(zh ? "," : ", ");
|
|
387
516
|
}
|
|
@@ -392,7 +521,7 @@ window.__ModuleLoader__.load({
|
|
|
392
521
|
* @returns one line per mark.
|
|
393
522
|
*/
|
|
394
523
|
function describeMarks(document, locale) {
|
|
395
|
-
return document.marks.map((mark, index) => describeMark(mark, index, locale));
|
|
524
|
+
return document.marks.map((mark, index) => describeMark(mark, index, locale, document.figure));
|
|
396
525
|
}
|
|
397
526
|
//#endregion
|
|
398
527
|
//#region src/shared/figure-kind.ts
|
|
@@ -867,7 +996,7 @@ window.__ModuleLoader__.load({
|
|
|
867
996
|
* @param rotation - the mark's rotation in degrees.
|
|
868
997
|
* @returns the handles.
|
|
869
998
|
*/
|
|
870
|
-
function handlesFor(box, rotation) {
|
|
999
|
+
function handlesFor(box, rotation, size = 7) {
|
|
871
1000
|
const centre = boxCentre(box);
|
|
872
1001
|
const radians = rotation * Math.PI / 180;
|
|
873
1002
|
const at = (x, y) => rotateAround([x, y], centre, radians);
|
|
@@ -884,11 +1013,23 @@ window.__ModuleLoader__.load({
|
|
|
884
1013
|
});
|
|
885
1014
|
placed.push({
|
|
886
1015
|
id: "rotate",
|
|
887
|
-
point: at(centre[0], box.y -
|
|
1016
|
+
point: at(centre[0], box.y - armFor(size))
|
|
888
1017
|
});
|
|
889
1018
|
return placed;
|
|
890
1019
|
}
|
|
891
1020
|
/**
|
|
1021
|
+
* How far the turn handle stands off the top edge, for a handle of a given size.
|
|
1022
|
+
*
|
|
1023
|
+
* Proportional to the handle: a bigger grip on a short arm would have the circle
|
|
1024
|
+
* overlapping the corner squares, and a smaller one on a long arm would look
|
|
1025
|
+
* detached from the frame it turns.
|
|
1026
|
+
* @param size - the handle's side, in figure pixels.
|
|
1027
|
+
* @returns the distance from the top edge, in figure pixels.
|
|
1028
|
+
*/
|
|
1029
|
+
function armFor(size) {
|
|
1030
|
+
return size * (22 / 7);
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
892
1033
|
* The line that ties the rotation handle to the box it turns.
|
|
893
1034
|
*
|
|
894
1035
|
* It reads as attached to the mark only because it is anchored on the top edge
|
|
@@ -897,11 +1038,11 @@ window.__ModuleLoader__.load({
|
|
|
897
1038
|
* @param rotation - the mark's rotation in degrees.
|
|
898
1039
|
* @returns the two ends of the tether, from the turn handle to the top edge.
|
|
899
1040
|
*/
|
|
900
|
-
function tetherFor(box, rotation) {
|
|
1041
|
+
function tetherFor(box, rotation, size = 7) {
|
|
901
1042
|
const centre = boxCentre(box);
|
|
902
1043
|
const radians = rotation * Math.PI / 180;
|
|
903
1044
|
return {
|
|
904
|
-
from: rotateAround([centre[0], box.y -
|
|
1045
|
+
from: rotateAround([centre[0], box.y - armFor(size)], centre, radians),
|
|
905
1046
|
to: rotateAround([centre[0], box.y], centre, radians)
|
|
906
1047
|
};
|
|
907
1048
|
}
|
|
@@ -913,10 +1054,10 @@ window.__ModuleLoader__.load({
|
|
|
913
1054
|
* @param reach - how far from a handle's centre still counts, in figure pixels.
|
|
914
1055
|
* @returns the handle grabbed, or undefined for a press on the mark itself.
|
|
915
1056
|
*/
|
|
916
|
-
function handleAt(box, rotation, at, reach = 7) {
|
|
1057
|
+
function handleAt(box, rotation, at, reach = 7, size = 7) {
|
|
917
1058
|
let best;
|
|
918
1059
|
let bestDistance = reach;
|
|
919
|
-
for (const handle of handlesFor(box, rotation)) {
|
|
1060
|
+
for (const handle of handlesFor(box, rotation, size)) {
|
|
920
1061
|
const distance = Math.hypot(handle.point[0] - at[0], handle.point[1] - at[1]);
|
|
921
1062
|
if (distance <= bestDistance) {
|
|
922
1063
|
best = handle.id;
|
|
@@ -993,7 +1134,6 @@ window.__ModuleLoader__.load({
|
|
|
993
1134
|
const degrees = normalizeDegrees((Math.atan2(at[1] - centre[1], at[0] - centre[0]) + Math.PI / 2) * 180 / Math.PI);
|
|
994
1135
|
return snap ? normalizeDegrees(Math.round(degrees / 15) * 15) : degrees;
|
|
995
1136
|
}
|
|
996
|
-
/** The cursor for one handle: what dragging it will do. */
|
|
997
1137
|
const CURSORS = {
|
|
998
1138
|
nw: "nwse-resize",
|
|
999
1139
|
n: "ns-resize",
|
|
@@ -1003,7 +1143,7 @@ window.__ModuleLoader__.load({
|
|
|
1003
1143
|
s: "ns-resize",
|
|
1004
1144
|
sw: "nesw-resize",
|
|
1005
1145
|
w: "ew-resize",
|
|
1006
|
-
rotate: "
|
|
1146
|
+
rotate: `url("data:image/svg+xml,${encodeURIComponent("<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><g fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M17.5 7.5A7 7 0 1 1 8.6 6.1\" stroke=\"#ffffff\" stroke-width=\"4.4\"/><path d=\"M17.5 7.5A7 7 0 1 1 8.6 6.1\" stroke=\"#2d2d2d\" stroke-width=\"2\"/><path d=\"M15.2 3.1L18.6 8.2L12.9 8.9\" stroke=\"#ffffff\" stroke-width=\"4.4\"/><path d=\"M15.2 3.1L18.6 8.2L12.9 8.9\" stroke=\"#2d2d2d\" stroke-width=\"2\"/></g></svg>")}") 12 12, crosshair`
|
|
1007
1147
|
};
|
|
1008
1148
|
/**
|
|
1009
1149
|
* The mouse cursor for one handle.
|
|
@@ -1358,27 +1498,15 @@ window.__ModuleLoader__.load({
|
|
|
1358
1498
|
const cy = box.y + box.height / 2;
|
|
1359
1499
|
return `rotate(${n(radians * 180 / Math.PI)} ${n(cx)} ${n(cy)})`;
|
|
1360
1500
|
}
|
|
1361
|
-
//#endregion
|
|
1362
|
-
//#region src/client/scale.ts
|
|
1363
1501
|
/**
|
|
1364
|
-
*
|
|
1365
|
-
*
|
|
1366
|
-
* A stroke is not a physical thing: 2.5 pixels is a bold line on a 300px thumbnail
|
|
1367
|
-
* and a hairline on a 4000px scan. Marks are comparisons the reader makes against
|
|
1368
|
-
* the line work underneath them, so they have to be sized in the same currency as
|
|
1369
|
-
* the figure — a fraction of it — rather than in the figure's pixels.
|
|
1502
|
+
* Smallest multiple of the base sizes, for figures far below the reference.
|
|
1370
1503
|
*
|
|
1371
|
-
*
|
|
1372
|
-
*
|
|
1373
|
-
*
|
|
1374
|
-
*
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
*/
|
|
1378
|
-
/** The figure edge, in pixels, that gets the unit it always had. */
|
|
1379
|
-
const REFERENCE_EDGE = 1200;
|
|
1380
|
-
/** Smallest multiple of the base sizes, for figures far below the reference. */
|
|
1381
|
-
const MIN_UNIT = .75;
|
|
1504
|
+
* Low enough that a matplotlib SVG — which measures itself in points, ~283 wide for
|
|
1505
|
+
* the same figure a PNG exports at 1179px — is still scaled down rather than propped
|
|
1506
|
+
* up to the old floor. The floor is a guard against division, not a design choice:
|
|
1507
|
+
* a figure below ~240px is one nothing sensible is drawn on.
|
|
1508
|
+
*/
|
|
1509
|
+
const MIN_UNIT = .2;
|
|
1382
1510
|
/**
|
|
1383
1511
|
* The size everything drawn on a figure is multiplied by.
|
|
1384
1512
|
*
|
|
@@ -1392,7 +1520,7 @@ window.__ModuleLoader__.load({
|
|
|
1392
1520
|
function figureUnit(width, height) {
|
|
1393
1521
|
const edge = Math.max(width, height);
|
|
1394
1522
|
if (!Number.isFinite(edge) || edge <= 0) return 1;
|
|
1395
|
-
return Math.min(
|
|
1523
|
+
return Math.min(10, Math.max(MIN_UNIT, edge / 800));
|
|
1396
1524
|
}
|
|
1397
1525
|
//#endregion
|
|
1398
1526
|
//#region src/client/Canvas.tsx
|
|
@@ -1423,7 +1551,8 @@ window.__ModuleLoader__.load({
|
|
|
1423
1551
|
/** Minimum distance between recorded freehand samples, in figure pixels. */
|
|
1424
1552
|
const PEN_SAMPLE_STEP = 3;
|
|
1425
1553
|
/** Reach of a handle's hit area, in figure pixels: generous enough to grab. */
|
|
1426
|
-
|
|
1554
|
+
/** How far from a handle's centre a press still counts, as a multiple of its side. */
|
|
1555
|
+
const HANDLE_REACH = 2.2;
|
|
1427
1556
|
/** Monotonic id source for marks created in this session. */
|
|
1428
1557
|
let markSeq = 0;
|
|
1429
1558
|
/**
|
|
@@ -1580,10 +1709,20 @@ window.__ModuleLoader__.load({
|
|
|
1580
1709
|
* @param event - the pointer event.
|
|
1581
1710
|
* @returns true when a handle took the press.
|
|
1582
1711
|
*/
|
|
1712
|
+
/**
|
|
1713
|
+
* The control frame's handle size in figure pixels.
|
|
1714
|
+
*
|
|
1715
|
+
* The frame is drawn in the same svg as the marks, which is in figure pixels, and
|
|
1716
|
+
* the whole paper is then scaled by CSS. A handle has to be a fixed number of screen
|
|
1717
|
+
* pixels to be hittable, so its figure size is that target divided by the scale.
|
|
1718
|
+
*/
|
|
1719
|
+
const grip = 7 / Math.max(.05, props.scale ?? 1);
|
|
1720
|
+
/** A hairline that stays a hairline on screen, whatever the paper is scaled to. */
|
|
1721
|
+
const hairline = 1 / Math.max(.05, props.scale ?? 1);
|
|
1583
1722
|
const beginHandle = (mark, event) => {
|
|
1584
1723
|
if (props.selectedId !== mark.id) return false;
|
|
1585
1724
|
if (mark.kind === "point") return false;
|
|
1586
|
-
const grabbed = handleAt(controlBox(mark), mark.rotation ?? 0, pressedAt.current.value, HANDLE_REACH);
|
|
1725
|
+
const grabbed = handleAt(controlBox(mark), mark.rotation ?? 0, pressedAt.current.value, HANDLE_REACH * grip, grip);
|
|
1587
1726
|
if (grabbed === void 0) return false;
|
|
1588
1727
|
beginGesture(mark, event, grabbed);
|
|
1589
1728
|
return true;
|
|
@@ -1814,26 +1953,26 @@ window.__ModuleLoader__.load({
|
|
|
1814
1953
|
/** The resize and rotation handles of the selected mark. */
|
|
1815
1954
|
const renderHandles = (mark) => {
|
|
1816
1955
|
const frame = controlBox(mark);
|
|
1817
|
-
const half =
|
|
1818
|
-
const handles = handlesFor(frame, mark.rotation ?? 0);
|
|
1819
|
-
const tether = tetherFor(frame, mark.rotation ?? 0);
|
|
1956
|
+
const half = grip / 2;
|
|
1957
|
+
const handles = handlesFor(frame, mark.rotation ?? 0, grip);
|
|
1958
|
+
const tether = tetherFor(frame, mark.rotation ?? 0, grip);
|
|
1820
1959
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
|
|
1821
1960
|
x1: tether.from[0],
|
|
1822
1961
|
y1: tether.from[1],
|
|
1823
1962
|
x2: tether.to[0],
|
|
1824
1963
|
y2: tether.to[1],
|
|
1825
1964
|
stroke: "#4c8dff",
|
|
1826
|
-
strokeWidth:
|
|
1965
|
+
strokeWidth: hairline
|
|
1827
1966
|
}), handles.map((handle) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("g", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
1828
1967
|
"data-da-handle": handle.id,
|
|
1829
1968
|
x: handle.point[0] - half,
|
|
1830
1969
|
y: handle.point[1] - half,
|
|
1831
|
-
width:
|
|
1832
|
-
height:
|
|
1833
|
-
rx: handle.id === "rotate" ?
|
|
1970
|
+
width: grip,
|
|
1971
|
+
height: grip,
|
|
1972
|
+
rx: handle.id === "rotate" ? grip / 2 : grip / 7,
|
|
1834
1973
|
fill: "#ffffff",
|
|
1835
1974
|
stroke: "#4c8dff",
|
|
1836
|
-
strokeWidth: 1.5,
|
|
1975
|
+
strokeWidth: 1.5 * hairline,
|
|
1837
1976
|
style: {
|
|
1838
1977
|
pointerEvents: "all",
|
|
1839
1978
|
cursor: cursorFor(handle.id)
|
|
@@ -1902,16 +2041,17 @@ window.__ModuleLoader__.load({
|
|
|
1902
2041
|
const box = pointMarkBox(shown, unit);
|
|
1903
2042
|
/* v8 ignore next -- a point mark carries exactly the one coordinate it was placed at. */
|
|
1904
2043
|
if (box === void 0) return null;
|
|
2044
|
+
const inset = 3 * hairline;
|
|
1905
2045
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
1906
|
-
x: box.x -
|
|
1907
|
-
y: box.y -
|
|
1908
|
-
width: box.width +
|
|
1909
|
-
height: box.height +
|
|
1910
|
-
rx:
|
|
2046
|
+
x: box.x - inset,
|
|
2047
|
+
y: box.y - inset,
|
|
2048
|
+
width: box.width + 2 * inset,
|
|
2049
|
+
height: box.height + 2 * inset,
|
|
2050
|
+
rx: inset,
|
|
1911
2051
|
fill: "none",
|
|
1912
2052
|
stroke: "#4c8dff",
|
|
1913
|
-
strokeWidth:
|
|
1914
|
-
strokeDasharray: `${3 *
|
|
2053
|
+
strokeWidth: hairline,
|
|
2054
|
+
strokeDasharray: `${3 * hairline} ${2 * hairline}`,
|
|
1915
2055
|
style: { pointerEvents: "none" }
|
|
1916
2056
|
});
|
|
1917
2057
|
};
|
|
@@ -1998,10 +2138,6 @@ window.__ModuleLoader__.load({
|
|
|
1998
2138
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
1999
2139
|
className: "da-overlay",
|
|
2000
2140
|
"data-da-overlay": "",
|
|
2001
|
-
style: {
|
|
2002
|
-
left: originX,
|
|
2003
|
-
top: originY
|
|
2004
|
-
},
|
|
2005
2141
|
width,
|
|
2006
2142
|
height,
|
|
2007
2143
|
viewBox: `0 0 ${width} ${height}`,
|
|
@@ -2281,14 +2417,13 @@ window.__ModuleLoader__.load({
|
|
|
2281
2417
|
const pages = annotatedPages(document);
|
|
2282
2418
|
const marked = pages.length === 0 ? "" : zh ? `其中 ${pages.length} 页有标注,` : `${pages.length} pages marked, `;
|
|
2283
2419
|
const kind = rendered ? zh ? "【HTML 标注】" : "[HTML annotations]" : zh ? "【附图标注】" : "[figure annotations]";
|
|
2284
|
-
const
|
|
2285
|
-
const header = zh ? `${kind}${figure.path}(${scope === "" ? "" : `${scope},`}${size}${marked}标注 ${marks.length} 处,${hash})` : `${kind} ${figure.path} (${scope === "" ? "" : `${scope}, `}${size}${marked}${marks.length} marks, ${hash})`;
|
|
2420
|
+
const header = zh ? `${kind}${figure.path}(${scope === "" ? "" : `${scope},`}${size}${marked}标注 ${marks.length} 处)` : `${kind} ${figure.path} (${scope === "" ? "" : `${scope}, `}${size}${marked}${marks.length} marks)`;
|
|
2286
2421
|
const paged = figure.pageCount !== void 0;
|
|
2287
2422
|
const pictured = paths.reviewPath !== null;
|
|
2288
2423
|
const attachment = !pictured ? [] : [paged ? zh ? "带批注 PDF 已作为本条消息的附件发送,可直接在阅读器里核对。" : "The annotated PDF is attached to this message; you can check it in any reader." : zh ? "标注图已作为本条消息的附件发送。" : "The annotated image is attached to this message."];
|
|
2289
2424
|
const noPicture = !pictured && !rendered && marks.length > 0 && !needsReviewImage(document) ? [zh ? "本条标注只有位置点,没有标注图:按上面逐条的坐标与说明处理原图即可。" : "These marks are points only, so no annotated image was made: work from the coordinates and notes above against the original figure."] : [];
|
|
2290
2425
|
const summary = document.summary !== void 0 && document.summary.trim() !== "" ? [zh ? `这张图是:${document.summary.trim()}` : `the figure shows: ${document.summary.trim()}`, ""] : [];
|
|
2291
|
-
const
|
|
2426
|
+
const closer = rendered ? zh ? "逐条改 HTML 生成源。" : "Edit the HTML source, mark by mark." : paged ? zh ? "逐条处理该 PDF。" : "Work through the PDF, mark by mark." : zh ? "逐条改生成源。" : "Edit the generating source, mark by mark.";
|
|
2292
2427
|
return [
|
|
2293
2428
|
header,
|
|
2294
2429
|
...attachment,
|
|
@@ -2297,7 +2432,7 @@ window.__ModuleLoader__.load({
|
|
|
2297
2432
|
...summary,
|
|
2298
2433
|
...describeMarks(document, locale),
|
|
2299
2434
|
"",
|
|
2300
|
-
|
|
2435
|
+
closer
|
|
2301
2436
|
].join("\n");
|
|
2302
2437
|
}
|
|
2303
2438
|
/**
|
|
@@ -2684,7 +2819,25 @@ window.__ModuleLoader__.load({
|
|
|
2684
2819
|
const unit = figureUnit(layer.width, layer.height);
|
|
2685
2820
|
const paper = plotBounds(layer.width, layer.height, marks, unit);
|
|
2686
2821
|
const shift = `translate(${-paper.x} ${-paper.y})`;
|
|
2687
|
-
|
|
2822
|
+
const edge = paper.x !== 0 || paper.y !== 0 || paper.width !== layer.width || paper.height !== layer.height ? figureEdge(layer.width, layer.height, unit) : "";
|
|
2823
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${paper.width}" height="${paper.height}" viewBox="0 0 ${paper.width} ${paper.height}"><rect x="0" y="0" width="${paper.width}" height="${paper.height}" fill="#ffffff"/><g transform="${shift}">` + figureSvg(layer) + edge + marks.map((mark) => markSvg(mark, numbers.get(mark.id), unit)).join("") + "</g></svg>";
|
|
2824
|
+
}
|
|
2825
|
+
/** Round a stroke measurement to a tenth, as the path data does. */
|
|
2826
|
+
function hair9(value) {
|
|
2827
|
+
return (Math.round(value * 10) / 10).toString();
|
|
2828
|
+
}
|
|
2829
|
+
/**
|
|
2830
|
+
* The rule that marks where the figure ends and the grown paper begins.
|
|
2831
|
+
*
|
|
2832
|
+
* Drawn only when the paper grew, and drawn grey and dashed: it is a note about the
|
|
2833
|
+
* picture, not part of it, so it must not read as a mark the reader made.
|
|
2834
|
+
* @param width - the figure's width, in figure pixels.
|
|
2835
|
+
* @param height - the figure's height, in figure pixels.
|
|
2836
|
+
* @param unit - the figure's size multiple, so the rule is sized like the marks.
|
|
2837
|
+
* @returns an SVG rect, or an empty string when there is nothing to outline.
|
|
2838
|
+
*/
|
|
2839
|
+
function figureEdge(width, height, unit) {
|
|
2840
|
+
return `<rect x="0" y="0" width="${width}" height="${height}" fill="none" stroke="#9aa3b2" stroke-width="${hair9(Math.max(1, 1.5 * unit))}" stroke-dasharray="${hair9(6 * unit)} ${hair9(4 * unit)}"/>`;
|
|
2688
2841
|
}
|
|
2689
2842
|
/**
|
|
2690
2843
|
* The size of the picture {@link composeReviewSvg} builds.
|
|
@@ -3112,6 +3265,182 @@ window.__ModuleLoader__.load({
|
|
|
3112
3265
|
}));
|
|
3113
3266
|
} catch {}
|
|
3114
3267
|
}
|
|
3268
|
+
/**
|
|
3269
|
+
* Keep a menu wholly inside the window.
|
|
3270
|
+
*
|
|
3271
|
+
* A right-click near the bottom edge would otherwise open rows below it, where
|
|
3272
|
+
* they cannot be reached; the same on the right. Both axes are clamped
|
|
3273
|
+
* independently, so a click in one corner only moves the menu as far as it has
|
|
3274
|
+
* to move.
|
|
3275
|
+
* @param at - the click, in viewport coordinates.
|
|
3276
|
+
* @param viewport - visible width and height.
|
|
3277
|
+
* @param size - the menu's own width and height.
|
|
3278
|
+
* @returns the viewport position to place the menu's top-left corner at.
|
|
3279
|
+
*/
|
|
3280
|
+
function clampMenu(at, viewport, size) {
|
|
3281
|
+
const rightmost = viewport.width - size.width - 8;
|
|
3282
|
+
const bottommost = viewport.height - size.height - 8;
|
|
3283
|
+
return {
|
|
3284
|
+
left: Math.max(8, Math.min(at.x, rightmost)),
|
|
3285
|
+
top: Math.max(8, Math.min(at.y, bottommost))
|
|
3286
|
+
};
|
|
3287
|
+
}
|
|
3288
|
+
/**
|
|
3289
|
+
* Read a path relative to the directory a Session runs in.
|
|
3290
|
+
*
|
|
3291
|
+
* The figure is usually somewhere under the workspace, which is what makes a
|
|
3292
|
+
* relative path worth copying at all — it survives the project being moved. A
|
|
3293
|
+
* figure outside the workspace, or a Session whose directory is unknown, has no
|
|
3294
|
+
* such path, and the menu leaves the row out rather than offering one that would
|
|
3295
|
+
* not resolve.
|
|
3296
|
+
* @param path - absolute path of the figure.
|
|
3297
|
+
* @param cwd - absolute workspace directory, or null when it is unknown.
|
|
3298
|
+
* @returns the relative path, or null when there is not one.
|
|
3299
|
+
*/
|
|
3300
|
+
function relativeTo(path, cwd) {
|
|
3301
|
+
if (cwd === null) return null;
|
|
3302
|
+
const prefix = `${cwd}${cwd.endsWith("/") || cwd.endsWith("\\") ? "" : "/"}`;
|
|
3303
|
+
if (!path.startsWith(prefix)) return null;
|
|
3304
|
+
const rest = path.slice(prefix.length);
|
|
3305
|
+
return rest === "" || rest.startsWith("..") ? null : rest;
|
|
3306
|
+
}
|
|
3307
|
+
/**
|
|
3308
|
+
* The rows this figure's menu should show.
|
|
3309
|
+
*
|
|
3310
|
+
* Three groups, in the order a reader wants them: the two Host gestures (only
|
|
3311
|
+
* where the Host has a desktop to perform them), then the two copies, then — for
|
|
3312
|
+
* a figure outside the workspace — no relative row at all, because there is no
|
|
3313
|
+
* path from the workspace to it.
|
|
3314
|
+
* @param options - what the Host can do, and whether a relative path exists.
|
|
3315
|
+
* @param labels - the four rows' words.
|
|
3316
|
+
* @returns the rows, in the order they are drawn.
|
|
3317
|
+
*/
|
|
3318
|
+
function figureMenuItems(options, labels) {
|
|
3319
|
+
const items = [];
|
|
3320
|
+
if (options.canSend) items.push({
|
|
3321
|
+
id: "sendToAgent",
|
|
3322
|
+
label: labels.sendToAgent
|
|
3323
|
+
}, {
|
|
3324
|
+
id: "sendToComposer",
|
|
3325
|
+
label: labels.sendToComposer
|
|
3326
|
+
});
|
|
3327
|
+
if (options.canOpen) items.push({
|
|
3328
|
+
id: "open",
|
|
3329
|
+
label: labels.open
|
|
3330
|
+
}, {
|
|
3331
|
+
id: "reveal",
|
|
3332
|
+
label: labels.reveal
|
|
3333
|
+
});
|
|
3334
|
+
if (options.hasImage) items.push({
|
|
3335
|
+
id: "copyImage",
|
|
3336
|
+
label: labels.copyImage
|
|
3337
|
+
});
|
|
3338
|
+
if (options.hasRelative) items.push({
|
|
3339
|
+
id: "copyRelative",
|
|
3340
|
+
label: labels.copyRelative
|
|
3341
|
+
});
|
|
3342
|
+
items.push({
|
|
3343
|
+
id: "copyAbsolute",
|
|
3344
|
+
label: labels.copyAbsolute
|
|
3345
|
+
});
|
|
3346
|
+
return items;
|
|
3347
|
+
}
|
|
3348
|
+
/**
|
|
3349
|
+
* Whether a pointer press landed inside the menu.
|
|
3350
|
+
*
|
|
3351
|
+
* A menu closes when the reader presses anywhere else, and "anywhere else" has to
|
|
3352
|
+
* be answered without knowing what was pressed: the event's composed path is the
|
|
3353
|
+
* one list that names every element between the target and the window, including
|
|
3354
|
+
* ones inside a shadow root.
|
|
3355
|
+
* @param event - the press.
|
|
3356
|
+
* @param panel - the menu element, or null before it has been attached.
|
|
3357
|
+
* @returns true when the press belongs to the menu itself.
|
|
3358
|
+
*/
|
|
3359
|
+
function pressedInsideMenu(event, panel) {
|
|
3360
|
+
return panel !== null && event.composedPath().includes(panel);
|
|
3361
|
+
}
|
|
3362
|
+
/**
|
|
3363
|
+
* The text one copy row puts on the clipboard.
|
|
3364
|
+
* @param action - the row that was picked.
|
|
3365
|
+
* @param path - absolute path of the figure.
|
|
3366
|
+
* @param workspace - absolute workspace directory, or null when it is unknown.
|
|
3367
|
+
* @returns the text to copy, or null when the relative row has nothing to copy.
|
|
3368
|
+
*/
|
|
3369
|
+
function copiedText(action, path, workspace) {
|
|
3370
|
+
return action === "copyRelative" ? relativeTo(path, workspace) : path;
|
|
3371
|
+
}
|
|
3372
|
+
//#endregion
|
|
3373
|
+
//#region src/client/host-actions.ts
|
|
3374
|
+
/**
|
|
3375
|
+
* Whether the Host can open paths at all.
|
|
3376
|
+
*
|
|
3377
|
+
* A remote Session has no desktop, and asking it to open a file would fail on
|
|
3378
|
+
* every row. Answering once at mount keeps the menu from offering gestures the
|
|
3379
|
+
* Session cannot perform; a failed probe reads as "no", which loses only the
|
|
3380
|
+
* rows, never a working one.
|
|
3381
|
+
* @param remote - the injected service, if the composition has one.
|
|
3382
|
+
* @returns whether the menu may offer open and reveal.
|
|
3383
|
+
*/
|
|
3384
|
+
async function desktopAvailable(remote) {
|
|
3385
|
+
if (remote === void 0) return false;
|
|
3386
|
+
try {
|
|
3387
|
+
const result = await remote.session.canOpenWorkspacePath();
|
|
3388
|
+
return result.ok && result.value === true;
|
|
3389
|
+
} catch {
|
|
3390
|
+
return false;
|
|
3391
|
+
}
|
|
3392
|
+
}
|
|
3393
|
+
/**
|
|
3394
|
+
* Open one path in its default application.
|
|
3395
|
+
* @param remote - the injected service.
|
|
3396
|
+
* @param path - absolute path on the Host.
|
|
3397
|
+
* @returns whether the Host accepted it.
|
|
3398
|
+
*/
|
|
3399
|
+
async function openOnDesktop(remote, path) {
|
|
3400
|
+
if (remote === void 0) return false;
|
|
3401
|
+
try {
|
|
3402
|
+
return (await remote.session.openWorkspacePath({ path })).ok;
|
|
3403
|
+
} catch {
|
|
3404
|
+
return false;
|
|
3405
|
+
}
|
|
3406
|
+
}
|
|
3407
|
+
/**
|
|
3408
|
+
* Reveal one path in the Host's file manager.
|
|
3409
|
+
* @param remote - the injected service.
|
|
3410
|
+
* @param path - absolute path on the Host.
|
|
3411
|
+
* @returns whether the Host accepted it.
|
|
3412
|
+
*/
|
|
3413
|
+
async function revealOnDesktop(remote, path) {
|
|
3414
|
+
if (remote === void 0) return false;
|
|
3415
|
+
try {
|
|
3416
|
+
return (await remote.session.openWorkspacePath({
|
|
3417
|
+
path,
|
|
3418
|
+
action: "reveal"
|
|
3419
|
+
})).ok;
|
|
3420
|
+
} catch {
|
|
3421
|
+
return false;
|
|
3422
|
+
}
|
|
3423
|
+
}
|
|
3424
|
+
/**
|
|
3425
|
+
* Put a picture on the clipboard.
|
|
3426
|
+
*
|
|
3427
|
+
* The async Clipboard API only accepts a PNG, and only from a user gesture — both
|
|
3428
|
+
* of which a menu row is. A browser without it (or a document that refuses the
|
|
3429
|
+
* write) is reported as a refusal rather than thrown, because the caller's next
|
|
3430
|
+
* move is the same either way: tell the reader it did not work.
|
|
3431
|
+
* @param bytes - the PNG the menu composed.
|
|
3432
|
+
* @returns whether the clipboard took it.
|
|
3433
|
+
*/
|
|
3434
|
+
async function copyImageToClipboard(bytes) {
|
|
3435
|
+
try {
|
|
3436
|
+
const clipboard = navigator.clipboard;
|
|
3437
|
+
if (clipboard === void 0 || typeof clipboard.write !== "function") return false;
|
|
3438
|
+
await clipboard.write([new ClipboardItem({ "image/png": bytes })]);
|
|
3439
|
+
return true;
|
|
3440
|
+
} catch {
|
|
3441
|
+
return false;
|
|
3442
|
+
}
|
|
3443
|
+
}
|
|
3115
3444
|
//#endregion
|
|
3116
3445
|
//#region src/shared/http.ts
|
|
3117
3446
|
/**
|
|
@@ -3169,7 +3498,8 @@ window.__ModuleLoader__.load({
|
|
|
3169
3498
|
path: body.figure.path,
|
|
3170
3499
|
mediaType: body.figure.mediaType,
|
|
3171
3500
|
sha256: body.figure.sha256,
|
|
3172
|
-
annotation: body.annotation ?? null
|
|
3501
|
+
annotation: body.annotation ?? null,
|
|
3502
|
+
workspace: body.figure.workspace ?? null
|
|
3173
3503
|
};
|
|
3174
3504
|
}
|
|
3175
3505
|
/**
|
|
@@ -3253,16 +3583,39 @@ window.__ModuleLoader__.load({
|
|
|
3253
3583
|
const STYLE_MARKER = "data-dsh-figmark";
|
|
3254
3584
|
/** The stylesheet text. */
|
|
3255
3585
|
const STYLES = `
|
|
3586
|
+
/* An accent of the plugin's own.
|
|
3587
|
+
The shell's --dsw-alias-brand-primary is not a brand colour in this theme — it
|
|
3588
|
+
resolves to the *ink* (near-black on light, near-white on dark), because the shell
|
|
3589
|
+
uses it for filled buttons in monochrome. Reading it as "the blue" turned this
|
|
3590
|
+
panel's sliders, filled buttons and selection rings into ink: a black knob on a
|
|
3591
|
+
white rail, and a wedge that goes solid black at the heaviest setting. The alias
|
|
3592
|
+
stays as the fallback so a shell that really does define a brand colour still wins,
|
|
3593
|
+
but the accent is named here so every rule reads one variable instead of quietly
|
|
3594
|
+
inheriting whatever "primary" means that week.
|
|
3595
|
+
The dark block re-states it, because a blue tuned for white paper is too dark on a
|
|
3596
|
+
near-black panel. */
|
|
3256
3597
|
/* The panel follows the shell's palette rather than carrying one of its own: every
|
|
3257
3598
|
colour below is an alias the theme owner resolves, with the inherited value as the
|
|
3258
3599
|
fallback so a shell that defines nothing still renders something legible. */
|
|
3259
|
-
.da-root{
|
|
3600
|
+
.da-root{--da-accent:#176df2;--da-accent-soft:rgba(23,109,242,.14);
|
|
3601
|
+
position:relative;display:flex;flex-direction:column;height:100%;min-height:0;overflow:hidden;color:var(--dsw-alias-label-primary,inherit);background:var(--dsw-alias-bg-document-preview,transparent)}
|
|
3602
|
+
/* The shell marks its dark palette on <body> rather than through prefers-color-scheme:
|
|
3603
|
+
a reader can pick either palette whatever the OS is set to, so following the shell's
|
|
3604
|
+
own switch is what keeps the accent readable in both. A blue tuned for white paper
|
|
3605
|
+
is too dark on a near-black panel. */
|
|
3606
|
+
body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,141,255,.22)}
|
|
3260
3607
|
|
|
3261
3608
|
/* Every small picture in the panel is one stroke set, declared once: without this
|
|
3262
3609
|
an svg with no fill of its own falls back to a solid black shape, which is what
|
|
3263
3610
|
turned the dock and the cards into blobs of ink. */
|
|
3611
|
+
/* Every icon is drawn on one 24-unit grid, and every one of them is asked to
|
|
3612
|
+
*render* at the same thickness — 1.2px — so the row reads as one set. On a single
|
|
3613
|
+
grid that still means one stroke-width per drawn size, because the number is in
|
|
3614
|
+
the icon's own units: 1.2 x 24 / size. The sizes differ only because the buttons
|
|
3615
|
+
holding them do; the weight does not. */
|
|
3264
3616
|
.da-ico{width:16px;height:16px;flex:0 0 auto;fill:none;stroke:currentColor;
|
|
3265
|
-
stroke-width:1.
|
|
3617
|
+
stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
|
|
3618
|
+
.da-tool-icon svg{fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round}
|
|
3266
3619
|
|
|
3267
3620
|
/* --- the floating tool palette ------------------------------------------- */
|
|
3268
3621
|
/* The tools float over the figure in a dark rounded pill so they can be parked
|
|
@@ -3274,12 +3627,20 @@ window.__ModuleLoader__.load({
|
|
|
3274
3627
|
box-shadow:0 8px 26px rgba(10,14,24,.3),0 0 0 1px rgba(255,255,255,.08)}
|
|
3275
3628
|
/* While it is being dragged the pointer is the grip, whatever it passes over. */
|
|
3276
3629
|
.da-palette[data-dragging="true"]{opacity:.94;box-shadow:0 12px 32px rgba(10,14,24,.4),0 0 0 1px rgba(255,255,255,.14)}
|
|
3277
|
-
.da-palette[data-dragging="true"] *{cursor:
|
|
3278
|
-
/* The handle: a
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
.
|
|
3282
|
-
.da-palette-grip
|
|
3630
|
+
.da-palette[data-dragging="true"] *{cursor:move !important}
|
|
3631
|
+
/* The handle: a block of six dots, the one part of a strip that only drags. The block
|
|
3632
|
+
is turned to suit the strip — wider than tall on the bar, taller than wide on the
|
|
3633
|
+
palette — because a grip whose proportions fight its strip looks borrowed from the
|
|
3634
|
+
other one. */
|
|
3635
|
+
.da-palette-grip{display:flex;align-items:center;justify-content:center;width:26px;height:17px;
|
|
3636
|
+
border-radius:5px;cursor:move;color:rgba(255,255,255,.55);touch-action:none;flex:0 0 auto}
|
|
3637
|
+
.da-palette-grip:hover{color:rgba(255,255,255,.92);background:rgba(255,255,255,.1)}
|
|
3638
|
+
/* The dots' box follows the block: three across by two down needs width, two across
|
|
3639
|
+
by three down needs height. Sized per block rather than once, so the svg is not
|
|
3640
|
+
letterboxed inside its own handle. */
|
|
3641
|
+
.da-grip-dots{width:12px;height:8px;fill:currentColor;display:block}
|
|
3642
|
+
.da-bar .da-palette-grip{width:16px;height:18px}
|
|
3643
|
+
.da-bar .da-grip-dots{width:8px;height:12px}
|
|
3283
3644
|
/* A tile is a round button holding one white glyph. No word under it: six glyphs in
|
|
3284
3645
|
a column are read at a glance, and the word is still there as the tooltip and as
|
|
3285
3646
|
the accessible name. */
|
|
@@ -3288,7 +3649,7 @@ window.__ModuleLoader__.load({
|
|
|
3288
3649
|
.da-palette .da-tool:hover{background:rgba(255,255,255,.14)}
|
|
3289
3650
|
.da-palette .da-tool[aria-pressed="true"]{background:#176df2;color:#fff}
|
|
3290
3651
|
.da-palette .da-tool .da-tool-icon{height:auto}
|
|
3291
|
-
.da-palette .da-tool .da-tool-icon svg{width:
|
|
3652
|
+
.da-palette .da-tool .da-tool-icon svg{width:17px;height:17px;stroke-width:1.7}
|
|
3292
3653
|
|
|
3293
3654
|
/* --- the size rail ------------------------------------------------------- */
|
|
3294
3655
|
/* A vertical rail along the left edge of the figure: how heavy the pen is, and how
|
|
@@ -3301,6 +3662,7 @@ window.__ModuleLoader__.load({
|
|
|
3301
3662
|
drawing. The drawn parts never take the pointer, or grabbing the knob would grab
|
|
3302
3663
|
the knob instead of the slider. */
|
|
3303
3664
|
.da-weight{position:absolute;z-index:7;left:6px;top:50%;transform:translateY(-50%);
|
|
3665
|
+
--da-track-inset:10px;--da-knob-size:11px;
|
|
3304
3666
|
display:flex;align-items:center;justify-content:center;width:7px;height:200px;
|
|
3305
3667
|
border-radius:999px;background:var(--dsw-alias-border-l2,rgba(127,127,127,.22));
|
|
3306
3668
|
cursor:ns-resize;transition:width .16s ease,background .16s ease,box-shadow .16s ease}
|
|
@@ -3309,13 +3671,24 @@ window.__ModuleLoader__.load({
|
|
|
3309
3671
|
/* The track is an svg in the body: a hairline at the bottom that opens into a rounded
|
|
3310
3672
|
shoulder at the top. It is not a clip-path polygon because a polygon has no rounded
|
|
3311
3673
|
ends — the corners were what made the wedge read as a cut-out blade rather than a
|
|
3312
|
-
part of the panel. preserveAspectRatio="none" stretches one
|
|
3313
|
-
rail's height, so the shoulders stay smooth at any length.
|
|
3314
|
-
|
|
3315
|
-
|
|
3674
|
+
part of the panel. preserveAspectRatio="none" stretches one 18x100 shape over the
|
|
3675
|
+
rail's height, so the shoulders stay smooth at any length.
|
|
3676
|
+
The height is written out rather than expressed as top+bottom: an svg is a replaced
|
|
3677
|
+
element: with a viewBox it takes its intrinsic height for its width and ignores the
|
|
3678
|
+
bottom offset entirely. The track was therefore a fixed 100px tall while the knob
|
|
3679
|
+
travelled the whole 200px rail, which is why the two could never line up. */
|
|
3680
|
+
.da-weight-svg{position:absolute;left:50%;top:var(--da-track-inset);height:calc(100% - 2 * var(--da-track-inset));
|
|
3681
|
+
width:18px;transform:translateX(-50%);opacity:0;pointer-events:none;
|
|
3682
|
+
transition:opacity .16s ease}
|
|
3316
3683
|
.da-weight:hover .da-weight-svg,.da-weight:focus-within .da-weight-svg{opacity:1}
|
|
3317
3684
|
/* The knob sits on the value: the only part that shows at rest, so the rail reads as
|
|
3318
3685
|
a slider waiting to be touched rather than a bare rule. */
|
|
3686
|
+
/* The blue: a bar of the same width as the groove, its height the one number the
|
|
3687
|
+
knob also uses, so its top edge is the knob's centre at every weight. */
|
|
3688
|
+
.da-weight-fill{position:absolute;left:50%;bottom:var(--da-track-inset);width:5px;
|
|
3689
|
+
transform:translateX(-50%);border-radius:999px;background:var(--da-accent,#176df2);
|
|
3690
|
+
opacity:0;pointer-events:none;transition:opacity .16s ease}
|
|
3691
|
+
.da-weight:hover .da-weight-fill,.da-weight:focus-within .da-weight-fill{opacity:1}
|
|
3319
3692
|
.da-weight-knob{position:absolute;left:50%;width:var(--da-knob-size,11px);height:var(--da-knob-size,11px);
|
|
3320
3693
|
border-radius:50%;transform:translateX(-50%);pointer-events:none;
|
|
3321
3694
|
background:var(--dsw-alias-bg-layer-2,#fff);
|
|
@@ -3323,7 +3696,7 @@ window.__ModuleLoader__.load({
|
|
|
3323
3696
|
transition:width .16s ease,height .16s ease,background .16s ease}
|
|
3324
3697
|
.da-weight:hover,.da-weight:focus-within{--da-knob-size:15px}
|
|
3325
3698
|
.da-weight:hover .da-weight-knob,.da-weight:focus-within .da-weight-knob{
|
|
3326
|
-
background:var(--
|
|
3699
|
+
background:var(--da-accent,#176df2);
|
|
3327
3700
|
box-shadow:0 2px 7px rgba(20,40,80,.3)}
|
|
3328
3701
|
/* The browser's own range, rotated upright and made transparent. It spans the whole
|
|
3329
3702
|
rail so a press anywhere on it lands on the slider — including on the knob, which
|
|
@@ -3344,16 +3717,17 @@ window.__ModuleLoader__.load({
|
|
|
3344
3717
|
/* Parked: placed from the top by the reader's drag, so the bottom anchor that keeps
|
|
3345
3718
|
it centred while untouched must not also apply — with both set it stretches. */
|
|
3346
3719
|
.da-bar[data-parked="true"]{bottom:auto}
|
|
3720
|
+
/* A bar standing in for the page body's own: it is placed by the pane rather than
|
|
3721
|
+
dragged, because there is no page yet to park it over. */
|
|
3722
|
+
.da-bar[data-static="true"]{bottom:8px}
|
|
3347
3723
|
.da-bar[data-dragging="true"]{opacity:.94;box-shadow:0 12px 32px rgba(10,14,24,.4),0 0 0 1px rgba(255,255,255,.14)}
|
|
3348
|
-
.da-bar[data-dragging="true"] *{cursor:
|
|
3724
|
+
.da-bar[data-dragging="true"] *{cursor:move !important}
|
|
3349
3725
|
/* The bar's buttons are white glyphs on the dark strip. The one filled control is
|
|
3350
3726
|
still the way out. */
|
|
3351
3727
|
.da-bar .da-dock-btn{color:#fff}
|
|
3352
3728
|
.da-bar .da-dock-btn:hover{background:rgba(255,255,255,.14)}
|
|
3353
3729
|
.da-bar .da-dock-btn[aria-pressed="true"]{background:#176df2;color:#fff}
|
|
3354
3730
|
.da-bar .da-dock-sep{background:rgba(255,255,255,.22)}
|
|
3355
|
-
.da-bar .da-dock-btn-primary{background:#176df2;color:#fff;border-color:#176df2}
|
|
3356
|
-
.da-bar .da-dock-btn-primary:hover{background:#176df2;opacity:.92}
|
|
3357
3731
|
.da-bar .da-dock-dot{background:#4c8dff}
|
|
3358
3732
|
.da-bar .da-dock-count{background:#176df2;color:#fff}
|
|
3359
3733
|
.da-bar .da-style{width:30px;height:28px;padding:0;border:0;border-radius:50%;background:transparent;color:#fff}
|
|
@@ -3372,7 +3746,7 @@ window.__ModuleLoader__.load({
|
|
|
3372
3746
|
.da-btn:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.1))}
|
|
3373
3747
|
.da-btn:disabled{opacity:.4;cursor:default}
|
|
3374
3748
|
.da-btn:disabled:hover{background:var(--dsw-alias-bg-base,transparent)}
|
|
3375
|
-
.da-btn svg{width:
|
|
3749
|
+
.da-btn svg{width:15px;height:15px;flex:0 0 auto;fill:none;stroke:currentColor;stroke-width:1.9;stroke-linecap:round;stroke-linejoin:round}
|
|
3376
3750
|
/* A quiet button has no frame at all until the pointer is on it: it is an action,
|
|
3377
3751
|
not a thing, and the row it sits in is already framed. */
|
|
3378
3752
|
.da-btn-quiet{border-color:transparent;background:transparent;height:24px;padding:0 7px;font-size:11px;color:var(--dsw-alias-label-secondary,inherit)}
|
|
@@ -3382,13 +3756,13 @@ window.__ModuleLoader__.load({
|
|
|
3382
3756
|
picture is what a reader who has used the tool once recognizes without reading. */
|
|
3383
3757
|
.da-tool{flex-direction:column;justify-content:center;gap:1px;width:40px;height:34px;padding:0;border-color:transparent;background:transparent;font-size:9px;border-radius:9px}
|
|
3384
3758
|
.da-tool .da-tool-icon{display:flex;align-items:center;justify-content:center;height:14px}
|
|
3385
|
-
.da-tool .da-tool-icon svg{width:
|
|
3759
|
+
.da-tool .da-tool-icon svg{width:14px;height:14px;stroke-width:2.05}
|
|
3386
3760
|
.da-tool .da-tool-label{font-size:10px;line-height:1;white-space:nowrap;color:var(--dsw-alias-label-secondary,inherit)}
|
|
3387
3761
|
/* The armed tool is the one state in this row the reader must never hunt for: it is
|
|
3388
3762
|
the only tile with a fill, and the only label in the brand colour. */
|
|
3389
|
-
.da-tool[aria-pressed="true"]{border-color:transparent;background:var(--
|
|
3390
|
-
.da-tool[aria-pressed="true"] .da-tool-label{color:var(--
|
|
3391
|
-
.da-tool[aria-pressed="true"] svg{stroke-width:2.
|
|
3763
|
+
.da-tool[aria-pressed="true"]{border-color:transparent;background:var(--da-accent-soft,rgba(23,109,242,.1));color:var(--da-accent,currentColor)}
|
|
3764
|
+
.da-tool[aria-pressed="true"] .da-tool-label{color:var(--da-accent,currentColor);font-weight:600}
|
|
3765
|
+
.da-tool[aria-pressed="true"] svg{stroke-width:2.4}
|
|
3392
3766
|
.da-tool:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.08))}
|
|
3393
3767
|
.da-btn-icon{width:30px;padding:0}
|
|
3394
3768
|
|
|
@@ -3398,6 +3772,10 @@ window.__ModuleLoader__.load({
|
|
|
3398
3772
|
.da-stylewrap{position:relative;flex:0 0 auto}
|
|
3399
3773
|
/* The style fold floats above the bar rather than taking a row of it: opened over
|
|
3400
3774
|
the figure, it costs the picture nothing and lands where the eye already is. */
|
|
3775
|
+
/* Opens upwards by default, because the bar lives at the bottom. Parked at the top
|
|
3776
|
+
there is nothing above it to open into, so the fold turns over and hangs below its
|
|
3777
|
+
own button instead — decided by the same measurement the records use. */
|
|
3778
|
+
.da-style-row[data-side="below"]{bottom:auto;top:calc(100% + 6px);box-shadow:0 -6px 22px rgba(20,40,80,.12),0 6px 22px rgba(20,40,80,.16)}
|
|
3401
3779
|
.da-style-row{position:absolute;left:8px;bottom:calc(100% + 6px);z-index:9;
|
|
3402
3780
|
display:flex;align-items:center;gap:8px;flex-wrap:wrap;max-width:calc(100% - 16px);
|
|
3403
3781
|
padding:8px 10px;border:0.5px solid var(--dsw-alias-border-l2,currentColor);border-radius:11px;
|
|
@@ -3412,17 +3790,24 @@ window.__ModuleLoader__.load({
|
|
|
3412
3790
|
/* Anywhere else is "done here": the panel closes on the click that lands outside it
|
|
3413
3791
|
rather than trapping the reader in a popup with one way out. */
|
|
3414
3792
|
.da-swatch{width:20px;height:20px;border-radius:50%;corner-shape:round;border:2px solid var(--dsw-alias-bg-base,transparent);box-shadow:0 0 0 1px var(--dsw-alias-border-l4,rgba(127,127,127,.5));cursor:pointer;padding:0}
|
|
3415
|
-
.da-swatch[aria-pressed="true"]{box-shadow:0 0 0 2px var(--
|
|
3793
|
+
.da-swatch[aria-pressed="true"]{box-shadow:0 0 0 2px var(--da-accent,currentColor)}
|
|
3416
3794
|
/* --- the figure on its table -------------------------------------------- */
|
|
3417
3795
|
/* A figure is a sheet on a table, not a picture floating in a void: the white paper
|
|
3418
3796
|
is what gives the panel a middle, and the gap around it is the margin the eye
|
|
3419
3797
|
needs to see where the drawing stops. */
|
|
3420
3798
|
.da-stage{position:relative;display:flex;align-items:safe center;justify-content:safe center;flex:1 1 auto;overflow:auto;min-height:0;padding:14px;background:var(--dsw-alias-bg-document-preview,transparent)}
|
|
3421
|
-
.da-surface{position:relative;flex:0 0 auto;margin:0;transform-origin:top left
|
|
3422
|
-
/* The paper: the figure plus the margin a mark may have needed. It is
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
.
|
|
3799
|
+
.da-surface{position:relative;flex:0 0 auto;margin:0;transform-origin:top left}
|
|
3800
|
+
/* The paper: the figure plus the margin a mark may have needed. It is deliberately
|
|
3801
|
+
left transparent — the white sheet belongs to the figure, so a margin that only
|
|
3802
|
+
exists because a mark reached past the edge reads as board round a card rather than
|
|
3803
|
+
as more of the figure. The exported picture still gets its white fill: that one is
|
|
3804
|
+
looked at by someone who has no board behind it. */
|
|
3805
|
+
.da-paper{background:transparent}
|
|
3806
|
+
/* The figure is the sheet: white, rounded, lifted off the panel. This is the element
|
|
3807
|
+
that carries the paper look, which is why the margin can be see-through without the
|
|
3808
|
+
drawing losing its edge. */
|
|
3809
|
+
.da-figure-frame{background:var(--dsw-alias-bg-base,#fff);border-radius:4px;overflow:hidden;
|
|
3810
|
+
box-shadow:var(--dsw-elevation-prominent,0 1px 3px rgba(0,0,0,.14),0 8px 24px rgba(0,0,0,.08))}
|
|
3426
3811
|
.da-figure{display:block;user-select:none;-webkit-user-drag:none}
|
|
3427
3812
|
/* The table is a thing you can push. The hand only shows under the tools that
|
|
3428
3813
|
leave a drag free to move the view: under a drawing tool the same drag draws. */
|
|
@@ -3440,13 +3825,73 @@ window.__ModuleLoader__.load({
|
|
|
3440
3825
|
.da-page{font-size:12px;min-width:44px;text-align:center;color:var(--dsw-alias-label-secondary,inherit)}
|
|
3441
3826
|
/* The document's toolbar sits above the page body, so the pane is one column of
|
|
3442
3827
|
definite height and the body takes what is left of it. */
|
|
3443
|
-
.da-pdf{display:flex;flex-direction:column;height:100%;min-height:0}
|
|
3828
|
+
.da-pdf{position:relative;display:flex;flex-direction:column;height:100%;min-height:0}
|
|
3444
3829
|
.da-pdf>.da-root{flex:1 1 auto;height:auto;min-height:0}
|
|
3830
|
+
/* A document's toolbar is a row of decisions above the page: paging on the left,
|
|
3831
|
+
what the document holds and the hand-off on the right. It is fixed height and
|
|
3832
|
+
never scrolls with the page, so the controls keep where the hand left them. */
|
|
3833
|
+
.da-toolbar{display:flex;align-items:center;gap:6px;flex:0 0 auto;
|
|
3834
|
+
padding:6px 10px;border-bottom:0.5px solid var(--dsw-alias-border-l3,currentColor)}
|
|
3835
|
+
|
|
3836
|
+
/* The PDF's own strip. It is the same object as the figure's bottom bar — a dark
|
|
3837
|
+
rounded card floating over the page — because a reader who has marked a figure
|
|
3838
|
+
and then opens a document should not have to learn a second set of controls in
|
|
3839
|
+
a second visual language. Everything the page is read with lives on it: where you
|
|
3840
|
+
are, how you move, how it flows, and the way out. */
|
|
3841
|
+
/* Paging is one decision, so its three controls sit closer together than the bar's
|
|
3842
|
+
own gap and read as a single reader rather than three buttons. It lives on the
|
|
3843
|
+
page body's bar, which the pane owns — not in a strip of its own. */
|
|
3844
|
+
.da-docbar-group{display:flex;align-items:center;gap:1px;flex:0 0 auto}
|
|
3845
|
+
/* The page number is the one control the reader reads rather than presses, so it
|
|
3846
|
+
gets a fixed slot: a number that shifts the buttons as it goes 9 to 100 is a
|
|
3847
|
+
toolbar that moves under the pointer. Not a tinted pill — the inverted strip is
|
|
3848
|
+
already the ground, and a second ground inside it reads as a button. */
|
|
3849
|
+
/* The page number reads as a caption on the dark card, not as a button: no ground of
|
|
3850
|
+
its own, no frame, just the digits. It is still pressable — pressing is how the
|
|
3851
|
+
reader jumps — but a chip inside a chip is two grounds for one control, and the
|
|
3852
|
+
white box read as a hole punched in the bar. */
|
|
3853
|
+
.da-bar button.da-page{display:inline-flex;align-items:center;justify-content:center;
|
|
3854
|
+
min-width:50px;height:28px;padding:0 4px;border:0;border-radius:8px;
|
|
3855
|
+
appearance:none;-webkit-appearance:none;
|
|
3856
|
+
background:transparent;box-shadow:none;color:#fff;cursor:pointer;
|
|
3857
|
+
font:inherit;font-size:12.5px;font-variant-numeric:tabular-nums}
|
|
3858
|
+
.da-bar button.da-page:hover{background:rgba(255,255,255,.14)}
|
|
3859
|
+
.da-bar button.da-page:focus-visible{outline:2px solid var(--da-accent,#176df2);outline-offset:1px}
|
|
3860
|
+
|
|
3861
|
+
.da-bar .da-dock-sep{background:rgba(255,255,255,.22)}
|
|
3862
|
+
.da-bar .da-ico{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:1.9;
|
|
3863
|
+
stroke-linecap:round;stroke-linejoin:round}
|
|
3864
|
+
/* What the document holds, said quietly: the strip's subject is the page, and the
|
|
3865
|
+
count is a caption on it rather than another control. */
|
|
3866
|
+
.da-bar .da-docbar-count{flex:0 0 auto;margin-left:6px;font-size:11.5px;
|
|
3867
|
+
color:rgba(255,255,255,.62);font-variant-numeric:tabular-nums;white-space:nowrap}
|
|
3868
|
+
/* The hand-off's note, only shown when there is nothing to hand over. */
|
|
3869
|
+
.da-bar .da-docbar-note{flex:0 0 auto;margin-right:2px;font-size:11.5px;color:rgba(255,255,255,.5)}
|
|
3870
|
+
/* The page number is the one control the reader reads rather than presses, so it
|
|
3871
|
+
gets a fixed slot: a number that shifts the buttons as it goes 9 → 10 → 100 is a
|
|
3872
|
+
toolbar that moves under the pointer. */
|
|
3873
|
+
.da-toolbar .da-page{min-width:52px;padding:0 2px;font-variant-numeric:tabular-nums}
|
|
3874
|
+
/* The hand-off sits under the page it describes, right-aligned and on its own
|
|
3875
|
+
ground, so it is not reachable by a stray press on the page controls. */
|
|
3876
|
+
.da-send{display:flex;align-items:center;justify-content:flex-end;gap:6px;flex:0 0 auto;
|
|
3877
|
+
padding:6px 10px;border-top:0.5px solid var(--dsw-alias-border-l3,currentColor)}
|
|
3878
|
+
/* The page a reader types into, to jump. Sized to the widest page count a document
|
|
3879
|
+
can plausibly have, so the slot does not resize as the number grows. */
|
|
3880
|
+
.da-page-input{width:52px;height:28px;padding:0 6px;border:0.5px solid rgba(255,255,255,.35);
|
|
3881
|
+
border-radius:8px;background:rgba(255,255,255,.12);color:#fff;
|
|
3882
|
+
font:inherit;font-size:12.5px;line-height:1;text-align:center;font-variant-numeric:tabular-nums}
|
|
3883
|
+
.da-page-input:focus{outline:none;border-color:var(--da-accent,#176df2);background:rgba(255,255,255,.18)}
|
|
3884
|
+
/* The page number's own denominator: smaller and quieter than the number it follows. */
|
|
3885
|
+
.da-page-total{font-size:12px;color:var(--dsw-alias-label-tertiary,inherit);white-space:nowrap}
|
|
3445
3886
|
/* The page slot before the first raster. It takes the column the body's stage
|
|
3446
3887
|
takes, so the frame around it — the toolbar above all — is in place from the
|
|
3447
3888
|
first paint and does not move when the page arrives. */
|
|
3448
3889
|
.da-placeholder{flex:1 1 auto;min-height:0;background:var(--dsw-alias-bg-document-preview,transparent)}
|
|
3449
|
-
|
|
3890
|
+
/* The page-number box and its denominator, held together so the box cannot drift
|
|
3891
|
+
away from the count it is a fraction of. */
|
|
3892
|
+
.da-page-field{display:flex;align-items:center;gap:3px}
|
|
3893
|
+
/* The hand-off's own note, quiet enough to read as a state rather than a control. */
|
|
3894
|
+
.da-send-note{font-size:12px;color:var(--dsw-alias-label-tertiary,inherit);margin-right:2px}
|
|
3450
3895
|
/* --- the two cards under the figure ------------------------------------- */
|
|
3451
3896
|
/* --- the dock ------------------------------------------------------------ */
|
|
3452
3897
|
/* The dock's buttons are quiet circles in the bar: they are the actions that are
|
|
@@ -3455,22 +3900,20 @@ window.__ModuleLoader__.load({
|
|
|
3455
3900
|
width:28px;height:28px;padding:0;border:0;border-radius:50%;background:transparent;
|
|
3456
3901
|
color:inherit;cursor:pointer;flex:0 0 auto}
|
|
3457
3902
|
.da-dock-btn:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12))}
|
|
3458
|
-
.da-dock-btn[aria-pressed="true"]{background:var(--
|
|
3903
|
+
.da-dock-btn[aria-pressed="true"]{background:var(--da-accent-soft,rgba(23,109,242,.12));color:var(--da-accent,#176df2)}
|
|
3459
3904
|
.da-dock-btn svg{width:15px;height:15px}
|
|
3460
3905
|
.da-dock-btn:disabled{opacity:.4;cursor:default}
|
|
3461
3906
|
.da-dock-btn:disabled:hover{background:transparent}
|
|
3462
3907
|
/* The one filled control in the panel is the way out of it. */
|
|
3463
|
-
.da-dock-btn-primary{border-color:var(--dsw-alias-brand-primary,#176df2);background:var(--dsw-alias-brand-primary,#176df2);color:#fff;box-shadow:0 2px 8px rgba(23,109,242,.32)}
|
|
3464
|
-
.da-dock-btn-primary:hover{opacity:.92;background:var(--dsw-alias-brand-primary,#176df2)}
|
|
3465
3908
|
/* A dot, not a count: "there is a goal written" is all the bar needs to say about
|
|
3466
3909
|
a field it is not a list of. */
|
|
3467
3910
|
.da-dock-dot{position:absolute;top:3px;right:4px;width:5px;height:5px;border-radius:50%;
|
|
3468
|
-
background:var(--
|
|
3911
|
+
background:var(--da-accent,#176df2)}
|
|
3469
3912
|
.da-dock-sep{width:1px;height:16px;margin:0 3px;background:var(--dsw-alias-border-l3,currentColor);opacity:.6;flex:0 0 auto}
|
|
3470
3913
|
/* The count rides on the list's own button: it is the one thing about the list a
|
|
3471
3914
|
reader wants without opening it. */
|
|
3472
3915
|
.da-dock-count{position:absolute;top:-1px;right:-2px;min-width:14px;height:14px;padding:0 3px;
|
|
3473
|
-
border-radius:7px;background:var(--
|
|
3916
|
+
border-radius:7px;background:var(--da-accent,#176df2);color:#fff;
|
|
3474
3917
|
font-size:9px;font-weight:700;line-height:14px;text-align:center;font-variant-numeric:tabular-nums}
|
|
3475
3918
|
|
|
3476
3919
|
/* --- the floating records ------------------------------------------------ */
|
|
@@ -3482,7 +3925,7 @@ window.__ModuleLoader__.load({
|
|
|
3482
3925
|
background:var(--dsw-alias-bg-layer-2,#fff);box-shadow:0 6px 22px rgba(20,40,80,.14),0 0 0 1px rgba(20,40,80,.03)}
|
|
3483
3926
|
.da-card-title{display:flex;align-items:center;gap:5px;min-width:0;font-size:12px;font-weight:650;
|
|
3484
3927
|
color:var(--dsw-alias-label-primary,inherit)}
|
|
3485
|
-
.da-card-title svg{width:14px;height:14px;flex:0 0 auto;color:var(--
|
|
3928
|
+
.da-card-title svg{width:14px;height:14px;flex:0 0 auto;color:var(--da-accent,#176df2)}
|
|
3486
3929
|
|
|
3487
3930
|
.da-float-head{display:flex;align-items:center;gap:7px}
|
|
3488
3931
|
.da-float-head .da-card-title{flex:1 1 auto;min-width:0}
|
|
@@ -3521,10 +3964,10 @@ window.__ModuleLoader__.load({
|
|
|
3521
3964
|
color:var(--dsw-alias-label-primary,currentColor)}
|
|
3522
3965
|
.da-comment-send-btn:disabled{opacity:.45;cursor:default}
|
|
3523
3966
|
.da-comment-send-btn svg{width:15px;height:15px}
|
|
3524
|
-
.da-comment-send-primary{background:var(--
|
|
3525
|
-
.da-comment-send-primary:hover{background:var(--
|
|
3967
|
+
.da-comment-send-primary{background:var(--da-accent,#176df2);color:#fff}
|
|
3968
|
+
.da-comment-send-primary:hover{background:var(--da-accent,#176df2);color:#fff;filter:brightness(1.08)}
|
|
3526
3969
|
.da-float textarea{width:100%;box-sizing:border-box;min-height:74px;max-height:200px;resize:vertical;border:0.5px solid var(--dsw-alias-border-l3,currentColor);border-radius:9px;background:var(--dsw-alias-bg-layer-2,transparent);color:inherit;font:inherit;font-size:12px;padding:8px 10px;line-height:1.65}
|
|
3527
|
-
.da-float textarea:focus{outline:none;border-color:var(--
|
|
3970
|
+
.da-float textarea:focus{outline:none;border-color:var(--da-accent,#176df2)}
|
|
3528
3971
|
.da-btn-count{display:inline-flex;align-items:center;justify-content:center;min-width:16px;height:16px;
|
|
3529
3972
|
padding:0 4px;border-radius:8px;background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.14));
|
|
3530
3973
|
font-size:10px;font-weight:600;font-variant-numeric:tabular-nums}
|
|
@@ -3537,8 +3980,8 @@ window.__ModuleLoader__.load({
|
|
|
3537
3980
|
/* What the selected mark is, in one word: the detail field is disabled until there
|
|
3538
3981
|
is something to describe, and this says what it would describe. */
|
|
3539
3982
|
.da-chip{display:inline-flex;align-items:center;height:16px;padding:0 6px;border-radius:5px;
|
|
3540
|
-
background:var(--
|
|
3541
|
-
color:var(--
|
|
3983
|
+
background:var(--da-accent-soft,rgba(23,109,242,.12));
|
|
3984
|
+
color:var(--da-accent,#176df2);font-size:10px;font-weight:600}
|
|
3542
3985
|
/* The "?" beside a card's name: the long form of what that field is for. */
|
|
3543
3986
|
.da-hintdot{display:inline-flex;align-items:center;justify-content:center;width:13px;height:13px;
|
|
3544
3987
|
border-radius:50%;border:0.5px solid var(--dsw-alias-border-l3,currentColor);
|
|
@@ -3548,14 +3991,23 @@ window.__ModuleLoader__.load({
|
|
|
3548
3991
|
sentence as the thing you press, and the sentence's own shape and anchor under it
|
|
3549
3992
|
in smaller type. A borderless run of text would read as one paragraph of marks. */
|
|
3550
3993
|
.da-marks{display:flex;flex-direction:column;gap:3px;min-height:0}
|
|
3551
|
-
.da-mark{display:flex;align-items:center;gap:7px;padding:5px
|
|
3994
|
+
.da-mark{display:flex;align-items:center;gap:7px;padding:5px 4px;border-radius:7px;
|
|
3552
3995
|
border:0.5px solid transparent;min-width:0}
|
|
3553
3996
|
.da-mark:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.08))}
|
|
3554
|
-
.da-mark[data-selected="true"]{border-color:var(--
|
|
3555
|
-
background:var(--dsw-alias-interactive-bg-hover,rgba(23,109,242,.
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3997
|
+
.da-mark[data-selected="true"]{border-color:var(--da-accent,#176df2);
|
|
3998
|
+
background:var(--dsw-alias-interactive-bg-hover,rgba(23,109,242,.06))}
|
|
3999
|
+
/* Two light strokes in the mark's colour, not a block of fill.
|
|
4000
|
+
A filled chip on every row is a wall of colour beside a figure that is mostly
|
|
4001
|
+
paper; the list should read as a list. The number keeps a fixed, right-aligned
|
|
4002
|
+
box so a column of rows lines up whether it holds 1 or 12, and the glyph beside
|
|
4003
|
+
it is drawn at the same 1.5px stroke the tools wear. */
|
|
4004
|
+
.da-mark-n{flex:0 0 auto;width:14px;text-align:right;font-size:11px;font-weight:600;
|
|
4005
|
+
font-variant-numeric:tabular-nums;line-height:1.35;
|
|
4006
|
+
color:var(--dsw-alias-label-secondary,currentColor)}
|
|
4007
|
+
.da-mark-glyph{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;
|
|
4008
|
+
width:14px;height:14px}
|
|
4009
|
+
.da-mark-glyph svg{width:12px;height:12px;fill:none;stroke:currentColor;
|
|
4010
|
+
stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round}
|
|
3559
4011
|
.da-mark-text{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;gap:1px;
|
|
3560
4012
|
padding:0;border:0;background:transparent;color:inherit;font:inherit;text-align:left;cursor:pointer}
|
|
3561
4013
|
.da-mark-title{display:block;font-size:12px;line-height:1.35;overflow:hidden;
|
|
@@ -3563,9 +4015,25 @@ window.__ModuleLoader__.load({
|
|
|
3563
4015
|
.da-mark-meta{display:block;font-size:10px;line-height:1.35;
|
|
3564
4016
|
color:var(--dsw-alias-label-tertiary,currentColor);overflow:hidden;
|
|
3565
4017
|
text-overflow:ellipsis;white-space:nowrap}
|
|
3566
|
-
|
|
3567
|
-
|
|
4018
|
+
/* The page number sits at the row's end rather than under its sentence: it is not
|
|
4019
|
+
part of what the mark says, it is where the mark is, and a list puts that where a
|
|
4020
|
+
table of contents puts it.
|
|
4021
|
+
Its size is the number's, not a step below it. Two sizes in one row — 11px on the
|
|
4022
|
+
left, 10px on the right — read as two different kinds of fact, and the page was
|
|
4023
|
+
the one that looked like it had been shrunk to fit. Weight is what demotes a fact
|
|
4024
|
+
here, not size: 500 against the number's 600, and the tertiary colour. */
|
|
4025
|
+
.da-mark-page{flex:0 0 auto;font-size:10px;font-weight:400;line-height:1.35;
|
|
4026
|
+
white-space:nowrap;color:var(--dsw-alias-label-tertiary,currentColor);
|
|
4027
|
+
font-variant-numeric:tabular-nums}
|
|
4028
|
+
/* A glyph rather than the word 删除: the row is a card, and a word in the corner of
|
|
4029
|
+
a card reads as another thing to press rather than as the way to get rid of it. */
|
|
4030
|
+
.da-mark-del{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;
|
|
4031
|
+
width:20px;height:20px;padding:0;border:0;border-radius:6px;background:transparent;
|
|
4032
|
+
color:var(--dsw-alias-label-tertiary,currentColor);font:inherit;font-size:11px;line-height:1;
|
|
4033
|
+
cursor:pointer;opacity:0}
|
|
4034
|
+
.da-mark:hover .da-mark-del,.da-mark[data-selected="true"] .da-mark-del{opacity:1}
|
|
3568
4035
|
.da-mark-del:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.14));color:inherit}
|
|
4036
|
+
.da-mark-del:focus-visible{opacity:1}
|
|
3569
4037
|
|
|
3570
4038
|
/* The empty list is a picture and two lines: a card of grey text reads as broken,
|
|
3571
4039
|
and "nothing yet" is a state, not an error. */
|
|
@@ -3573,6 +4041,16 @@ window.__ModuleLoader__.load({
|
|
|
3573
4041
|
.da-empty-ico{width:46px;height:46px;fill:none;stroke:var(--dsw-alias-border-l4,currentColor);stroke-width:1.4;stroke-linecap:round;stroke-linejoin:round;opacity:.85}
|
|
3574
4042
|
.da-empty-title{font-size:12px;font-weight:650;color:var(--dsw-alias-label-secondary,inherit)}
|
|
3575
4043
|
.da-empty-hint{font-size:11px;line-height:1.6;color:var(--dsw-alias-label-tertiary,inherit);max-width:190px}
|
|
4044
|
+
/* The figure's context menu. Laid out at a fixed width because the placement
|
|
4045
|
+
maths needs one number; rows are the height that maths assumes. */
|
|
4046
|
+
.da-menu{position:fixed;z-index:40;display:flex;flex-direction:column;gap:1px;width:208px;
|
|
4047
|
+
padding:4px;border-radius:10px;background:var(--dsw-alias-bg-layer-2,#fff);
|
|
4048
|
+
box-shadow:0 8px 24px rgba(15,17,21,.16),0 0 0 1px var(--dsw-alias-border-l2,rgba(0,0,0,.1))}
|
|
4049
|
+
.da-menu-item{display:flex;align-items:center;height:27px;padding:0 8px;border:0;border-radius:6px;
|
|
4050
|
+
background:transparent;color:var(--dsw-alias-label-primary,inherit);font:inherit;font-size:12.5px;
|
|
4051
|
+
text-align:left;cursor:pointer;white-space:nowrap}
|
|
4052
|
+
.da-menu-item:hover,.da-menu-item:focus-visible{background:var(--da-accent-soft,rgba(23,109,242,.14));outline:none}
|
|
4053
|
+
|
|
3576
4054
|
.da-status{flex:0 0 auto;padding:6px 10px;font-size:12px;color:var(--dsw-alias-label-secondary,inherit)}
|
|
3577
4055
|
.da-status[data-tone="error"]{color:var(--dsw-alias-label-error,var(--dsw-alias-state-error-primary,currentColor))}
|
|
3578
4056
|
.da-status[data-tone="ok"]{color:var(--dsw-alias-state-success-primary,currentColor)}
|
|
@@ -3823,24 +4301,36 @@ window.__ModuleLoader__.load({
|
|
|
3823
4301
|
/**
|
|
3824
4302
|
* The rail's track, drawn on an 18x100 grid and stretched to whatever height it gets.
|
|
3825
4303
|
*
|
|
3826
|
-
*
|
|
3827
|
-
*
|
|
3828
|
-
*
|
|
3829
|
-
*
|
|
3830
|
-
*
|
|
3831
|
-
*
|
|
3832
|
-
*
|
|
4304
|
+
* The control's subject is thickness, so the track says which way is heavier: a little
|
|
4305
|
+
* narrower at the bottom, about one part in six. "A little" is the whole note — a
|
|
4306
|
+
* water-drop taper reads as decoration rather than as a gauge, and the reader said so
|
|
4307
|
+
* twice. One cubic per side and no shoulders, so each flank is a single continuous
|
|
4308
|
+
* curve from end to end with nothing for the eye to catch on; the two ends close with
|
|
4309
|
+
* faint arcs so the track looks drawn rather than cut off.
|
|
4310
|
+
*
|
|
4311
|
+
* Stretching it vertically (preserveAspectRatio="none") is what makes the caps need
|
|
4312
|
+
* care: the svg is 18x100 over a box about 180 tall, so one y unit is 1.8 of an x unit.
|
|
4313
|
+
* An `r` that reads as a circle in the viewBox would be a tall ellipse on screen — the
|
|
4314
|
+
* cap's `ry` is therefore set to `rx / 1.8`, which is what puts a round end on it.
|
|
3833
4315
|
*/
|
|
3834
4316
|
const WEIGHT_TRACK = [
|
|
3835
|
-
"M
|
|
3836
|
-
"
|
|
3837
|
-
"C
|
|
3838
|
-
"A
|
|
3839
|
-
"C
|
|
3840
|
-
"C 10.1 58 9.8 78 9.8 100",
|
|
3841
|
-
"A 0.8 0.8 0 0 1 8.2 100",
|
|
4317
|
+
"M 6.9 98.9",
|
|
4318
|
+
"A 2.1 1.15 0 0 0 11.1 98.9",
|
|
4319
|
+
"C 11.4 66 11.9 33 12.2 1.1",
|
|
4320
|
+
"A 3.2 1.15 0 0 0 5.8 1.1",
|
|
4321
|
+
"C 6.1 33 6.6 66 6.9 98.9",
|
|
3842
4322
|
"Z"
|
|
3843
4323
|
].join(" ");
|
|
4324
|
+
/**
|
|
4325
|
+
* How far the blue reaches, as a `height` in the rail.
|
|
4326
|
+
*
|
|
4327
|
+
* The same expression as the knob's `bottom`, because the blue has to end exactly where
|
|
4328
|
+
* the knob's centre is: written once as a function so a change to the travel cannot
|
|
4329
|
+
* leave the fill behind. `inset` keeps both clear of the rail's rounded caps; the extra
|
|
4330
|
+
* half knob at each end keeps the knob's whole circle between the two ends of the bar
|
|
4331
|
+
* rather than hanging half of itself past the top when the pen is at its heaviest.
|
|
4332
|
+
*/
|
|
4333
|
+
const travelHeight = (fraction) => `calc(var(--da-track-inset) + var(--da-knob-size) / 2 + (100% - 2 * var(--da-track-inset) - var(--da-knob-size)) * ${fraction})`;
|
|
3844
4334
|
const TOOLS = [
|
|
3845
4335
|
{
|
|
3846
4336
|
tool: "select",
|
|
@@ -3897,50 +4387,101 @@ window.__ModuleLoader__.load({
|
|
|
3897
4387
|
* with the mouse rather than a mark you leave behind. Drawn rather than written so
|
|
3898
4388
|
* the toolbar stays one row of controls at any locale or font size.
|
|
3899
4389
|
*/
|
|
4390
|
+
/**
|
|
4391
|
+
* The handle's dots: six of them, three by two, turned as the reader asked.
|
|
4392
|
+
*
|
|
4393
|
+
* The palette stands up and takes a grip three columns wide by two rows tall; the bar
|
|
4394
|
+
* lies down and takes one two columns wide by three rows tall. That is the pair the
|
|
4395
|
+
* reader called for, and it is the opposite of what the strips' own proportions would
|
|
4396
|
+
* suggest — noted here so it is not "corrected" back by the next pair of eyes.
|
|
4397
|
+
*
|
|
4398
|
+
* Drawn instead of typed — the braille character this replaces was one glyph at one
|
|
4399
|
+
* size, so the dots could not be sized or spaced, and at 9px they came out a smudge.
|
|
4400
|
+
*/
|
|
4401
|
+
function GripDots({ wide }) {
|
|
4402
|
+
const xs = wide ? [
|
|
4403
|
+
2,
|
|
4404
|
+
6,
|
|
4405
|
+
10
|
|
4406
|
+
] : [2, 6];
|
|
4407
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
4408
|
+
className: "da-grip-dots",
|
|
4409
|
+
viewBox: wide ? "0 0 12 8" : "0 0 8 12",
|
|
4410
|
+
"aria-hidden": "true",
|
|
4411
|
+
focusable: "false",
|
|
4412
|
+
children: (wide ? [2, 6] : [
|
|
4413
|
+
2,
|
|
4414
|
+
6,
|
|
4415
|
+
10
|
|
4416
|
+
]).map((y) => xs.map((x) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
4417
|
+
cx: x,
|
|
4418
|
+
cy: y,
|
|
4419
|
+
r: 1.4
|
|
4420
|
+
}, `${x}-${y}`)))
|
|
4421
|
+
});
|
|
4422
|
+
}
|
|
4423
|
+
/**
|
|
4424
|
+
* The select tool: a cursor, outlined rather than filled.
|
|
4425
|
+
*
|
|
4426
|
+
* Filled, it read as a black blob against the dark chip — the silhouette was there but
|
|
4427
|
+
* the direction it points was not, which is the only thing a cursor has to say. As an
|
|
4428
|
+
* outline it belongs to the same stroke set as the other five glyphs.
|
|
4429
|
+
*
|
|
4430
|
+
* The shape is Tabler's `cursor-text`-sibling `pointer` glyph, taken whole at its own
|
|
4431
|
+
* 24 grid: its body is one closed curve with round joins rather than the seven hard
|
|
4432
|
+
* corners a hand-traced outline has, and at 16px that softness is the whole difference
|
|
4433
|
+
* between a cursor and a shard. Scaled by hand it lost exactly that.
|
|
4434
|
+
*/
|
|
3900
4435
|
function PointerIcon() {
|
|
3901
4436
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
viewBox: "0 0 13 15",
|
|
4437
|
+
className: "da-ico",
|
|
4438
|
+
viewBox: "0 0 24 24",
|
|
3905
4439
|
"aria-hidden": "true",
|
|
3906
4440
|
focusable: "false",
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
stroke: "currentColor",
|
|
3912
|
-
strokeWidth: "0.9",
|
|
3913
|
-
strokeLinejoin: "round"
|
|
3914
|
-
})
|
|
4441
|
+
strokeWidth: "2",
|
|
4442
|
+
strokeLinecap: "round",
|
|
4443
|
+
strokeLinejoin: "round",
|
|
4444
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M14.185 13.14l5.644 -2.202c1.625 -.634 1.538 -2.962 -.13 -3.473l-14.319 -4.382c-1.41 -.431 -2.73 .888 -2.298 2.298l4.382 14.318c.51 1.668 2.84 1.755 3.473 .13l2.202 -5.644a1.84 1.84 0 0 1 1.045 -1.045" })
|
|
3915
4445
|
});
|
|
3916
4446
|
}
|
|
3917
4447
|
/**
|
|
3918
4448
|
* The stroke each marking tool draws, so the row says it in pictures as well as words.
|
|
3919
4449
|
*
|
|
4450
|
+
* Every icon in the row is drawn on the same 24-unit grid as {@link PointerIcon}. That
|
|
4451
|
+
* is not cosmetic: `stroke-width` in an inline SVG is measured in the icon's own units,
|
|
4452
|
+
* so a 16-unit icon and a 24-unit icon given the same number draw at different
|
|
4453
|
+
* thicknesses. On one grid they cannot drift, and the six read as one set.
|
|
4454
|
+
*
|
|
3920
4455
|
* `select` is not a marking tool and never comes here: it draws nothing, so it
|
|
3921
|
-
* wears the pointer instead
|
|
4456
|
+
* wears the pointer instead.
|
|
3922
4457
|
*/
|
|
3923
4458
|
function ToolIcon(tool) {
|
|
3924
4459
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
3925
|
-
viewBox: "0 0
|
|
4460
|
+
viewBox: "0 0 24 24",
|
|
3926
4461
|
"aria-hidden": "true",
|
|
3927
4462
|
focusable: "false",
|
|
3928
|
-
children: tool === "arrow" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M
|
|
3929
|
-
x: "
|
|
3930
|
-
y: "
|
|
3931
|
-
width: "
|
|
3932
|
-
height: "
|
|
3933
|
-
rx: "
|
|
4463
|
+
children: tool === "arrow" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M 4.5 19.5 L 19 5" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M 10 5 h 9 v 9" })] }) : tool === "rect" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
4464
|
+
x: "4",
|
|
4465
|
+
y: "6",
|
|
4466
|
+
width: "16",
|
|
4467
|
+
height: "12",
|
|
4468
|
+
rx: "2"
|
|
3934
4469
|
}) : tool === "ellipse" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
3935
|
-
cx: "
|
|
3936
|
-
cy: "
|
|
3937
|
-
r: "
|
|
3938
|
-
}) : tool === "pen" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M
|
|
4470
|
+
cx: "12",
|
|
4471
|
+
cy: "12",
|
|
4472
|
+
r: "8.2"
|
|
4473
|
+
}) : tool === "pen" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M 4 18 C 7 6 12 20 20 5" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
3939
4474
|
fillRule: "evenodd",
|
|
3940
|
-
d: "M
|
|
4475
|
+
d: "M 12 22.4 Q 4.9 15.1 4.1 10.16 A 7.9 7.9 0 1 1 19.9 10.16 Q 19.1 15.1 12 22.4 Z\n M 12 16.02 A 5.62 5.62 0 1 0 12 4.78 A 5.62 5.62 0 1 0 12 16.02 Z",
|
|
3941
4476
|
fill: "currentColor",
|
|
3942
4477
|
stroke: "none"
|
|
3943
|
-
})
|
|
4478
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
4479
|
+
cx: "12",
|
|
4480
|
+
cy: "10.4",
|
|
4481
|
+
r: "2",
|
|
4482
|
+
fill: "currentColor",
|
|
4483
|
+
stroke: "none"
|
|
4484
|
+
})] })
|
|
3944
4485
|
});
|
|
3945
4486
|
}
|
|
3946
4487
|
/**
|
|
@@ -3954,22 +4495,22 @@ window.__ModuleLoader__.load({
|
|
|
3954
4495
|
function GoalIcon() {
|
|
3955
4496
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
3956
4497
|
className: "da-ico",
|
|
3957
|
-
viewBox: "0 0
|
|
4498
|
+
viewBox: "0 0 24 24",
|
|
3958
4499
|
"aria-hidden": "true",
|
|
3959
4500
|
focusable: "false",
|
|
3960
4501
|
children: [
|
|
3961
4502
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
3962
|
-
cx: "
|
|
3963
|
-
cy: "
|
|
3964
|
-
r: "
|
|
4503
|
+
cx: "10.5",
|
|
4504
|
+
cy: "13.5",
|
|
4505
|
+
r: "7.8"
|
|
3965
4506
|
}),
|
|
3966
4507
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
3967
|
-
cx: "
|
|
3968
|
-
cy: "
|
|
3969
|
-
r: "
|
|
4508
|
+
cx: "10.5",
|
|
4509
|
+
cy: "13.5",
|
|
4510
|
+
r: "3.45"
|
|
3970
4511
|
}),
|
|
3971
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M
|
|
3972
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M
|
|
4512
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M 20.7 3.3 L 12.3 11.7" }),
|
|
4513
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M 14.4 3.9 L 20.7 3.3 L 20.1 9.6" })
|
|
3973
4514
|
]
|
|
3974
4515
|
});
|
|
3975
4516
|
}
|
|
@@ -3977,29 +4518,29 @@ window.__ModuleLoader__.load({
|
|
|
3977
4518
|
function ListIcon() {
|
|
3978
4519
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
3979
4520
|
className: "da-ico",
|
|
3980
|
-
viewBox: "0 0
|
|
4521
|
+
viewBox: "0 0 24 24",
|
|
3981
4522
|
"aria-hidden": "true",
|
|
3982
4523
|
focusable: "false",
|
|
3983
4524
|
children: [
|
|
3984
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M 6
|
|
4525
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M 9 6.75 h 10.5 M 9 12 h 10.5 M 9 17.25 h 10.5" }),
|
|
3985
4526
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
3986
|
-
cx: "
|
|
3987
|
-
cy: "
|
|
3988
|
-
r: "1",
|
|
4527
|
+
cx: "4.8",
|
|
4528
|
+
cy: "6.75",
|
|
4529
|
+
r: "1.5",
|
|
3989
4530
|
fill: "currentColor",
|
|
3990
4531
|
stroke: "none"
|
|
3991
4532
|
}),
|
|
3992
4533
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
3993
|
-
cx: "
|
|
3994
|
-
cy: "
|
|
3995
|
-
r: "1",
|
|
4534
|
+
cx: "4.8",
|
|
4535
|
+
cy: "12",
|
|
4536
|
+
r: "1.5",
|
|
3996
4537
|
fill: "currentColor",
|
|
3997
4538
|
stroke: "none"
|
|
3998
4539
|
}),
|
|
3999
4540
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
4000
|
-
cx: "
|
|
4001
|
-
cy: "
|
|
4002
|
-
r: "1",
|
|
4541
|
+
cx: "4.8",
|
|
4542
|
+
cy: "17.25",
|
|
4543
|
+
r: "1.5",
|
|
4003
4544
|
fill: "currentColor",
|
|
4004
4545
|
stroke: "none"
|
|
4005
4546
|
})
|
|
@@ -4025,40 +4566,40 @@ window.__ModuleLoader__.load({
|
|
|
4025
4566
|
function UndoIcon() {
|
|
4026
4567
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
4027
4568
|
className: "da-ico",
|
|
4028
|
-
viewBox: "0 0
|
|
4569
|
+
viewBox: "0 0 24 24",
|
|
4029
4570
|
"aria-hidden": "true",
|
|
4030
4571
|
focusable: "false",
|
|
4031
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M
|
|
4572
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M 4.8 11.25 H 14.25 a 5.1 5.1 0 0 1 0 10.2 H 9" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M 8.7 6.6 L 4.05 11.25 L 8.7 15.9" })]
|
|
4032
4573
|
});
|
|
4033
4574
|
}
|
|
4034
4575
|
/** The same arrow the other way. */
|
|
4035
4576
|
function RedoIcon() {
|
|
4036
4577
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
4037
4578
|
className: "da-ico",
|
|
4038
|
-
viewBox: "0 0
|
|
4579
|
+
viewBox: "0 0 24 24",
|
|
4039
4580
|
"aria-hidden": "true",
|
|
4040
4581
|
focusable: "false",
|
|
4041
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M
|
|
4582
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M 19.2 11.25 H 9.75 a 5.1 5.1 0 0 0 0 10.2 H 15" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M 15.3 6.6 L 19.95 11.25 L 15.3 15.9" })]
|
|
4042
4583
|
});
|
|
4043
4584
|
}
|
|
4044
4585
|
/** The primary action's mark: a paper plane, the usual "this leaves here". */
|
|
4045
4586
|
function SendIcon() {
|
|
4046
4587
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
4047
4588
|
className: "da-ico",
|
|
4048
|
-
viewBox: "0 0
|
|
4589
|
+
viewBox: "0 0 24 24",
|
|
4049
4590
|
"aria-hidden": "true",
|
|
4050
4591
|
focusable: "false",
|
|
4051
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M
|
|
4592
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M 3 12 L 21 3.9 L 15.9 21 L 12 14.1 Z" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M 12 14.1 L 21 3.9" })]
|
|
4052
4593
|
});
|
|
4053
4594
|
}
|
|
4054
4595
|
/** The secondary action's mark: a speech bubble, "this goes to the chat box". */
|
|
4055
4596
|
function ChatIcon() {
|
|
4056
4597
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
4057
4598
|
className: "da-ico",
|
|
4058
|
-
viewBox: "0 0
|
|
4599
|
+
viewBox: "0 0 24 24",
|
|
4059
4600
|
"aria-hidden": "true",
|
|
4060
4601
|
focusable: "false",
|
|
4061
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M
|
|
4602
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M 3.75 9.75 a 3 3 0 0 1 3 -3 h 10.5 a 3 3 0 0 1 3 3 v 4.5 a 3 3 0 0 1 -3 3 h -6 l -4.5 3.3 v -3.3 a 3 3 0 0 1 -3 -3 z" })
|
|
4062
4603
|
});
|
|
4063
4604
|
}
|
|
4064
4605
|
/**
|
|
@@ -4088,7 +4629,7 @@ window.__ModuleLoader__.load({
|
|
|
4088
4629
|
const parsedAddress = parseFileAddress(address);
|
|
4089
4630
|
const mediaType = figureMediaType(parsedAddress?.path ?? "");
|
|
4090
4631
|
const isVector = mediaType === "image/svg+xml";
|
|
4091
|
-
const t = (0, react.useCallback)((key) => resolveTranslate(props, key), [props.t, props.localeId]);
|
|
4632
|
+
const t = (0, react.useCallback)((key, vars) => resolveTranslate(props, key, vars), [props.t, props.localeId]);
|
|
4092
4633
|
const sessionId = props.sessionId ?? parsedAddress?.sessionId;
|
|
4093
4634
|
/**
|
|
4094
4635
|
* A private prefix for the rail's gradient and clip ids.
|
|
@@ -4114,6 +4655,7 @@ window.__ModuleLoader__.load({
|
|
|
4114
4655
|
const barRef = (0, react.useRef)(null);
|
|
4115
4656
|
const figureHostRef = (0, react.useRef)(null);
|
|
4116
4657
|
const surfaceRef = (0, react.useRef)(null);
|
|
4658
|
+
const paperRef = (0, react.useRef)(null);
|
|
4117
4659
|
const figureRef = (0, react.useRef)(null);
|
|
4118
4660
|
/**
|
|
4119
4661
|
* Hand the stage to the owner as its scrollport, and keep it here too.
|
|
@@ -4159,6 +4701,7 @@ window.__ModuleLoader__.load({
|
|
|
4159
4701
|
const surface = props.page;
|
|
4160
4702
|
/** Whether this body annotates one page of a document its owner persists. */
|
|
4161
4703
|
const isPage = surface !== void 0;
|
|
4704
|
+
const chrome = props.chrome !== false;
|
|
4162
4705
|
const figure = surface === void 0 ? fileFigure : {
|
|
4163
4706
|
kind: "raster",
|
|
4164
4707
|
objectUrl: surface.dataUrl,
|
|
@@ -4198,12 +4741,46 @@ window.__ModuleLoader__.load({
|
|
|
4198
4741
|
const [color, setColor] = (0, react.useState)(remembered.color ?? COLORS[0]);
|
|
4199
4742
|
const [weight, setWeight] = (0, react.useState)(remembered.weight ?? 1);
|
|
4200
4743
|
const [marks, setMarks] = (0, react.useState)(() => [...props.draft?.marks ?? []]);
|
|
4744
|
+
/**
|
|
4745
|
+
* What the list shows.
|
|
4746
|
+
*
|
|
4747
|
+
* One page of a paged document is handed the whole file's marks, because the list
|
|
4748
|
+
* is the map of what is about to be sent and what is sent is the file. A body
|
|
4749
|
+
* mounted on its own has only its own marks to show, which is the same thing said
|
|
4750
|
+
* with nothing extra.
|
|
4751
|
+
*/
|
|
4752
|
+
const listedMarks = props.documentMarks ?? marks;
|
|
4201
4753
|
const [past, setPast] = (0, react.useState)([]);
|
|
4202
4754
|
const [future, setFuture] = (0, react.useState)([]);
|
|
4203
|
-
const [
|
|
4755
|
+
const [ownSelectedId, setOwnSelectedId] = (0, react.useState)(null);
|
|
4756
|
+
/**
|
|
4757
|
+
* The selection, kept by the owner when there is one.
|
|
4758
|
+
*
|
|
4759
|
+
* A column mounts one body per page and moves the chrome between them as the
|
|
4760
|
+
* reader scrolls; a selection held here would be dropped at every page boundary,
|
|
4761
|
+
* and the comment pill would vanish under the sentence it belongs to.
|
|
4762
|
+
*/
|
|
4763
|
+
const selectedId = props.selectedId === void 0 ? ownSelectedId : props.selectedId;
|
|
4764
|
+
const setSelectedId = (0, react.useCallback)((next) => {
|
|
4765
|
+
const value = typeof next === "function" ? next(selectedId) : next;
|
|
4766
|
+
if (props.onSelectedIdChange === void 0) setOwnSelectedId(value);
|
|
4767
|
+
else props.onSelectedIdChange(value);
|
|
4768
|
+
}, [props.onSelectedIdChange, selectedId]);
|
|
4204
4769
|
/** The mark whose detail box should take the caret once it is on screen. */
|
|
4205
4770
|
const [focusId, setFocusId] = (0, react.useState)(null);
|
|
4206
4771
|
/**
|
|
4772
|
+
* Ask the owner to turn to a mark's page, when this body is one page of a document.
|
|
4773
|
+
*
|
|
4774
|
+
* The list is the document's, so a row can name a mark that is not on the page in
|
|
4775
|
+
* view. The owner keeps the page number, so it is the owner that has to turn —
|
|
4776
|
+
* and it must happen before the selection lands, or the pill would be measured
|
|
4777
|
+
* against a page the mark is not on.
|
|
4778
|
+
*/
|
|
4779
|
+
const revealOnPage = (0, react.useCallback)((mark) => {
|
|
4780
|
+
if (props.onRevealMark === void 0 || mark.page === void 0) return;
|
|
4781
|
+
props.onRevealMark(mark);
|
|
4782
|
+
}, [props.onRevealMark]);
|
|
4783
|
+
/**
|
|
4207
4784
|
* What the figure shows, as context for the marks below it.
|
|
4208
4785
|
*
|
|
4209
4786
|
* One per figure rather than one per mark: it is never drawn, and its only job
|
|
@@ -4232,7 +4809,14 @@ window.__ModuleLoader__.load({
|
|
|
4232
4809
|
* second one's own close button. Opening any popover closes whatever was open;
|
|
4233
4810
|
* opening the one already showing closes it, so the button toggles.
|
|
4234
4811
|
*/
|
|
4235
|
-
const [
|
|
4812
|
+
const [ownPanel, setOwnPanel] = (0, react.useState)(null);
|
|
4813
|
+
/** The open card, kept by the owner when there is one, for the reason above. */
|
|
4814
|
+
const openPanel = props.openPanel === void 0 ? ownPanel : props.openPanel;
|
|
4815
|
+
const setOpenPanel = (0, react.useCallback)((next) => {
|
|
4816
|
+
const value = typeof next === "function" ? next(openPanel) : next;
|
|
4817
|
+
if (props.onOpenPanelChange === void 0) setOwnPanel(value);
|
|
4818
|
+
else props.onOpenPanelChange(value);
|
|
4819
|
+
}, [openPanel, props.onOpenPanelChange]);
|
|
4236
4820
|
const styleOpen = openPanel === "style";
|
|
4237
4821
|
const goalOpen = openPanel === "goal";
|
|
4238
4822
|
const listOpen = openPanel === "list";
|
|
@@ -4270,7 +4854,19 @@ window.__ModuleLoader__.load({
|
|
|
4270
4854
|
* the reader picks a colour is the moment it has to be safe. Storage failures are
|
|
4271
4855
|
* swallowed inside `rememberPreferences` — losing a colour is not worth an error.
|
|
4272
4856
|
*/
|
|
4273
|
-
|
|
4857
|
+
/**
|
|
4858
|
+
* The factor this figure is drawn at.
|
|
4859
|
+
*
|
|
4860
|
+
* An owner that magnifies the whole document hands the factor in and takes every
|
|
4861
|
+
* gesture back, so the size follows the reader from page to page instead of
|
|
4862
|
+
* restarting at fit on each sheet. A body mounted alone keeps its own.
|
|
4863
|
+
*/
|
|
4864
|
+
const [ownZoom, setOwnZoom] = (0, react.useState)("fit");
|
|
4865
|
+
const zoom = props.zoom ?? ownZoom;
|
|
4866
|
+
const setZoom = (0, react.useCallback)((next) => {
|
|
4867
|
+
if (props.onZoom === void 0) setOwnZoom(next);
|
|
4868
|
+
else props.onZoom(next);
|
|
4869
|
+
}, [props.onZoom]);
|
|
4274
4870
|
/**
|
|
4275
4871
|
* Whether a drag is currently moving the view.
|
|
4276
4872
|
*
|
|
@@ -4282,6 +4878,11 @@ window.__ModuleLoader__.load({
|
|
|
4282
4878
|
const [fitWidth, setFitWidth] = (0, react.useState)(0);
|
|
4283
4879
|
const [busy, setBusy] = (0, react.useState)(null);
|
|
4284
4880
|
const [status, setStatus] = (0, react.useState)(null);
|
|
4881
|
+
/** Where the figure's context menu was asked for, or null when it is closed. */
|
|
4882
|
+
const [menuAt, setMenuAt] = (0, react.useState)(null);
|
|
4883
|
+
/** Whether this Host can open and reveal paths; read once per mount. */
|
|
4884
|
+
const [canOpen, setCanOpen] = (0, react.useState)(false);
|
|
4885
|
+
const menuRef = (0, react.useRef)(null);
|
|
4285
4886
|
/** The panel's own box, for placing the cards that float over it. */
|
|
4286
4887
|
const rootRef = (0, react.useRef)(null);
|
|
4287
4888
|
/**
|
|
@@ -4294,6 +4895,7 @@ window.__ModuleLoader__.load({
|
|
|
4294
4895
|
* off the edge when it narrows.
|
|
4295
4896
|
*/
|
|
4296
4897
|
const [palette, setPalette] = (0, react.useState)(remembered.palette);
|
|
4898
|
+
const paletteRef = (0, react.useRef)(null);
|
|
4297
4899
|
const [dragging, setDragging] = (0, react.useState)(false);
|
|
4298
4900
|
const dragFrom = (0, react.useRef)(null);
|
|
4299
4901
|
/**
|
|
@@ -4339,24 +4941,30 @@ window.__ModuleLoader__.load({
|
|
|
4339
4941
|
* @param from - which strip's press is in progress.
|
|
4340
4942
|
* @param write - where its new spot goes.
|
|
4341
4943
|
*/
|
|
4342
|
-
const followPointer = (0, react.useCallback)((event, from, write) => {
|
|
4944
|
+
const followPointer = (0, react.useCallback)((event, from, write, strip) => {
|
|
4343
4945
|
const held = from.current;
|
|
4344
|
-
const root = rootRef.current;
|
|
4946
|
+
const root = strip?.offsetParent ?? rootRef.current;
|
|
4345
4947
|
/* v8 ignore next */
|
|
4346
|
-
if (held === null || root === null) return;
|
|
4948
|
+
if (held === null || root === null || !(root instanceof Element)) return;
|
|
4347
4949
|
const bounds = root.getBoundingClientRect();
|
|
4348
4950
|
const x = held.origin.x + (event.clientX - held.box.left) / Math.max(1, bounds.width);
|
|
4349
4951
|
const y = held.origin.y + (event.clientY - held.box.top) / Math.max(1, bounds.height);
|
|
4952
|
+
/* v8 ignore start -- both call sites hold a live ref for the whole drag. */
|
|
4953
|
+
const width = strip?.offsetWidth ?? 0;
|
|
4954
|
+
const height = strip?.offsetHeight ?? 0;
|
|
4955
|
+
/* v8 ignore stop */
|
|
4956
|
+
const roomX = Math.max(0, bounds.width - width) / Math.max(1, bounds.width);
|
|
4957
|
+
const roomY = Math.max(0, bounds.height - height) / Math.max(1, bounds.height);
|
|
4350
4958
|
write({
|
|
4351
|
-
x: Math.min(
|
|
4352
|
-
y: Math.min(
|
|
4959
|
+
x: Math.min(roomX, Math.max(0, x)),
|
|
4960
|
+
y: Math.min(roomY, Math.max(0, y))
|
|
4353
4961
|
});
|
|
4354
4962
|
}, []);
|
|
4355
4963
|
const dragPalette = (0, react.useCallback)((event) => {
|
|
4356
|
-
followPointer(event, dragFrom, setPalette);
|
|
4964
|
+
followPointer(event, dragFrom, setPalette, paletteRef.current);
|
|
4357
4965
|
}, [followPointer]);
|
|
4358
4966
|
const dragBar = (0, react.useCallback)((event) => {
|
|
4359
|
-
followPointer(event, barFrom, setBarSpot);
|
|
4967
|
+
followPointer(event, barFrom, setBarSpot, barRef.current);
|
|
4360
4968
|
}, [followPointer]);
|
|
4361
4969
|
(0, react.useEffect)(() => {
|
|
4362
4970
|
rememberPreferences({
|
|
@@ -4608,12 +5216,14 @@ window.__ModuleLoader__.load({
|
|
|
4608
5216
|
const scale = (0, react.useMemo)(() => {
|
|
4609
5217
|
if (size === null) return 1;
|
|
4610
5218
|
if (zoom !== "fit") return zoom;
|
|
4611
|
-
|
|
4612
|
-
|
|
5219
|
+
const target = props.fitWidth ?? fitWidth;
|
|
5220
|
+
if (target <= 0) return 1;
|
|
5221
|
+
return Math.min(1.5, Math.max(.05, target / size.width));
|
|
4613
5222
|
}, [
|
|
4614
5223
|
zoom,
|
|
4615
5224
|
fitWidth,
|
|
4616
|
-
size
|
|
5225
|
+
size,
|
|
5226
|
+
props.fitWidth
|
|
4617
5227
|
]);
|
|
4618
5228
|
/**
|
|
4619
5229
|
* Where the records hang: above the bar, centred on it.
|
|
@@ -4655,6 +5265,17 @@ window.__ModuleLoader__.load({
|
|
|
4655
5265
|
htmlHeight
|
|
4656
5266
|
]);
|
|
4657
5267
|
/**
|
|
5268
|
+
* Whether an upward fold would come out of the top of the panel.
|
|
5269
|
+
*
|
|
5270
|
+
* The style fold is anchored to its own button, so it climbs — which is right while
|
|
5271
|
+
* the bar sits at the bottom and wrong the moment the reader parks the bar along the
|
|
5272
|
+
* top, where climbing puts the swatches out of sight above the panel. The record
|
|
5273
|
+
* placement already answers this question for the records; the fold reads the same
|
|
5274
|
+
* answer rather than measuring a second time, so the two can never disagree about
|
|
5275
|
+
* where there is room.
|
|
5276
|
+
*/
|
|
5277
|
+
const foldOpensDown = floatsAt !== null && floatsAt.bottom === void 0;
|
|
5278
|
+
/**
|
|
4658
5279
|
* Put a record away when the reader clicks anywhere else.
|
|
4659
5280
|
*
|
|
4660
5281
|
* A record is a fold, not a mode: it has done its job the moment the reader looks
|
|
@@ -4752,9 +5373,9 @@ window.__ModuleLoader__.load({
|
|
|
4752
5373
|
const zoomAt = (0, react.useCallback)((next, clientX, clientY) => {
|
|
4753
5374
|
const stage = stageRef.current;
|
|
4754
5375
|
const surface = surfaceRef.current;
|
|
4755
|
-
const
|
|
5376
|
+
const sheet = paperRef.current;
|
|
4756
5377
|
/* v8 ignore next -- a wheel event only reaches a handler on a mounted stage, and size is what allows one. */
|
|
4757
|
-
if (stage === null || surface === null ||
|
|
5378
|
+
if (stage === null || surface === null || sheet === null || paper === null) return;
|
|
4758
5379
|
const before = liveScale.current;
|
|
4759
5380
|
const reached = clampZoom(next);
|
|
4760
5381
|
if (reached === before) return;
|
|
@@ -4768,19 +5389,22 @@ window.__ModuleLoader__.load({
|
|
|
4768
5389
|
}, {
|
|
4769
5390
|
clientX,
|
|
4770
5391
|
clientY
|
|
4771
|
-
}, before, reached,
|
|
5392
|
+
}, before, reached, paper);
|
|
4772
5393
|
liveScale.current = reached;
|
|
4773
|
-
surface.style.width = `${
|
|
4774
|
-
surface.style.height = `${
|
|
4775
|
-
|
|
5394
|
+
surface.style.width = `${paper.width * reached}px`;
|
|
5395
|
+
surface.style.height = `${paper.height * reached}px`;
|
|
5396
|
+
sheet.style.transform = `scale(${reached})`;
|
|
4776
5397
|
stage.scrollLeft = target.scrollLeft;
|
|
4777
5398
|
stage.scrollTop = target.scrollTop;
|
|
4778
5399
|
clearTimeout(settleTimer.current);
|
|
4779
5400
|
settleTimer.current = setTimeout(() => {
|
|
4780
5401
|
settleTimer.current = void 0;
|
|
5402
|
+
const settled = paperRef.current;
|
|
5403
|
+
/* v8 ignore next -- the sheet is the element the pinch was measured on. */
|
|
5404
|
+
if (settled !== null) settled.style.transform = "";
|
|
4781
5405
|
setZoom(reached);
|
|
4782
5406
|
}, 120);
|
|
4783
|
-
}, [
|
|
5407
|
+
}, [paper]);
|
|
4784
5408
|
(0, react.useEffect)(() => () => {
|
|
4785
5409
|
clearTimeout(settleTimer.current);
|
|
4786
5410
|
}, []);
|
|
@@ -4788,13 +5412,20 @@ window.__ModuleLoader__.load({
|
|
|
4788
5412
|
const stage = stageRef.current;
|
|
4789
5413
|
if (stage === null) return;
|
|
4790
5414
|
/**
|
|
4791
|
-
*
|
|
4792
|
-
*
|
|
4793
|
-
*
|
|
4794
|
-
*
|
|
5415
|
+
* The wheel is the magnification, whatever modifier it carries.
|
|
5416
|
+
*
|
|
5417
|
+
* It used to be two gestures in one: a plain wheel panned the scrollport, and
|
|
5418
|
+
* only a ctrl-modified one — which is how a trackpad reports a pinch — zoomed.
|
|
5419
|
+
* That split is what made the gesture ambiguous: over a page that fits the pane
|
|
5420
|
+
* there is nothing to scroll, so a plain wheel looks like a request for another
|
|
5421
|
+
* page, while the reader who wants a closer look makes exactly the same motion.
|
|
5422
|
+
* One motion, one meaning: up magnifies, down shrinks, and a pinch arrives as the
|
|
5423
|
+
* same thing with a modifier on it.
|
|
5424
|
+
*
|
|
5425
|
+
* Panning is still there — a drag with the select tool moves the view — so
|
|
5426
|
+
* nothing is lost by taking the wheel's second job away from it.
|
|
4795
5427
|
*/
|
|
4796
5428
|
const onWheel = (event) => {
|
|
4797
|
-
if (!event.ctrlKey) return;
|
|
4798
5429
|
event.preventDefault();
|
|
4799
5430
|
zoomAt(liveScale.current * wheelZoomFactor(event.deltaY, event.deltaMode, stage.clientHeight), event.clientX, event.clientY);
|
|
4800
5431
|
};
|
|
@@ -4812,9 +5443,17 @@ window.__ModuleLoader__.load({
|
|
|
4812
5443
|
commit([...marks, mark]);
|
|
4813
5444
|
}, [commit, marks]);
|
|
4814
5445
|
const removeMark = (0, react.useCallback)((id) => {
|
|
5446
|
+
if (props.onDocumentRemove !== void 0 && !marks.some((mark) => mark.id === id)) {
|
|
5447
|
+
props.onDocumentRemove(id);
|
|
5448
|
+
return;
|
|
5449
|
+
}
|
|
4815
5450
|
commit(marks.filter((mark) => mark.id !== id));
|
|
4816
5451
|
setSelectedId((current) => current === id ? null : current);
|
|
4817
|
-
}, [
|
|
5452
|
+
}, [
|
|
5453
|
+
commit,
|
|
5454
|
+
marks,
|
|
5455
|
+
props.onDocumentRemove
|
|
5456
|
+
]);
|
|
4818
5457
|
/**
|
|
4819
5458
|
* Write one mark's detail.
|
|
4820
5459
|
*
|
|
@@ -5239,6 +5878,178 @@ window.__ModuleLoader__.load({
|
|
|
5239
5878
|
size,
|
|
5240
5879
|
t
|
|
5241
5880
|
]);
|
|
5881
|
+
/**
|
|
5882
|
+
* Probe the Host once, so the menu can offer the gestures it can perform.
|
|
5883
|
+
*
|
|
5884
|
+
* `canOpenWorkspacePath` answers for the whole page, and a probe that fails is
|
|
5885
|
+
* the same answer as "no desktop": rows that would fail every time are left out
|
|
5886
|
+
* rather than shown and refused.
|
|
5887
|
+
*/
|
|
5888
|
+
(0, react.useEffect)(() => {
|
|
5889
|
+
let live = true;
|
|
5890
|
+
desktopAvailable(props.remote).then((available) => {
|
|
5891
|
+
if (live) setCanOpen(available);
|
|
5892
|
+
});
|
|
5893
|
+
return () => {
|
|
5894
|
+
live = false;
|
|
5895
|
+
};
|
|
5896
|
+
}, [props.remote]);
|
|
5897
|
+
/**
|
|
5898
|
+
* The rows this figure's menu offers, decided once per figure and Host.
|
|
5899
|
+
*
|
|
5900
|
+
* Empty until a figure has loaded: the rows all name its path, so there is
|
|
5901
|
+
* nothing to offer before there is one.
|
|
5902
|
+
*/
|
|
5903
|
+
const menuItems = (0, react.useMemo)(() => {
|
|
5904
|
+
if (loaded === null) return [];
|
|
5905
|
+
return figureMenuItems({
|
|
5906
|
+
canOpen,
|
|
5907
|
+
canSend: !isPage && marks.length > 0 && busy === null,
|
|
5908
|
+
hasRelative: relativeTo(loaded.path, loaded.workspace) !== null,
|
|
5909
|
+
hasImage: layer !== null
|
|
5910
|
+
}, {
|
|
5911
|
+
sendToAgent: t("menuSendToAgent"),
|
|
5912
|
+
sendToComposer: t("menuSendToComposer"),
|
|
5913
|
+
open: t("menuOpen"),
|
|
5914
|
+
reveal: t("menuReveal"),
|
|
5915
|
+
copyImage: t("menuCopyImage"),
|
|
5916
|
+
copyRelative: t("menuCopyRelative"),
|
|
5917
|
+
copyAbsolute: t("menuCopyAbsolute")
|
|
5918
|
+
});
|
|
5919
|
+
}, [
|
|
5920
|
+
busy,
|
|
5921
|
+
canOpen,
|
|
5922
|
+
isPage,
|
|
5923
|
+
layer,
|
|
5924
|
+
loaded,
|
|
5925
|
+
marks.length,
|
|
5926
|
+
t
|
|
5927
|
+
]);
|
|
5928
|
+
/**
|
|
5929
|
+
* Open the menu at the pointer, kept inside the window.
|
|
5930
|
+
* @param event - the right-click on the figure.
|
|
5931
|
+
*/
|
|
5932
|
+
const openMenu = (0, react.useCallback)((event) => {
|
|
5933
|
+
/* v8 ignore next -- nothing to act on until a figure loaded. */
|
|
5934
|
+
if (loaded === null) return;
|
|
5935
|
+
event.preventDefault();
|
|
5936
|
+
const height = menuItems.length * 27;
|
|
5937
|
+
const placed = clampMenu({
|
|
5938
|
+
x: event.clientX,
|
|
5939
|
+
y: event.clientY
|
|
5940
|
+
}, {
|
|
5941
|
+
width: window.innerWidth,
|
|
5942
|
+
height: window.innerHeight
|
|
5943
|
+
}, {
|
|
5944
|
+
width: 208,
|
|
5945
|
+
height
|
|
5946
|
+
});
|
|
5947
|
+
setMenuAt({
|
|
5948
|
+
...placed,
|
|
5949
|
+
path: loaded.path,
|
|
5950
|
+
workspace: loaded.workspace
|
|
5951
|
+
});
|
|
5952
|
+
}, [loaded, menuItems]);
|
|
5953
|
+
(0, react.useEffect)(() => {
|
|
5954
|
+
if (menuAt === null) return;
|
|
5955
|
+
const onDown = (event) => {
|
|
5956
|
+
if (pressedInsideMenu(event, menuRef.current)) return;
|
|
5957
|
+
setMenuAt(null);
|
|
5958
|
+
};
|
|
5959
|
+
const onKey = (event) => {
|
|
5960
|
+
if (event.key === "Escape") setMenuAt(null);
|
|
5961
|
+
};
|
|
5962
|
+
document.addEventListener("pointerdown", onDown, true);
|
|
5963
|
+
document.addEventListener("keydown", onKey);
|
|
5964
|
+
return () => {
|
|
5965
|
+
document.removeEventListener("pointerdown", onDown, true);
|
|
5966
|
+
document.removeEventListener("keydown", onKey);
|
|
5967
|
+
};
|
|
5968
|
+
}, [menuAt]);
|
|
5969
|
+
/**
|
|
5970
|
+
* Carry out one menu row.
|
|
5971
|
+
*
|
|
5972
|
+
* The two copies are the only rows that touch the clipboard, and they say so in
|
|
5973
|
+
* the status line: a copy with no feedback reads as a menu that did nothing.
|
|
5974
|
+
* @param id - the row the reader picked.
|
|
5975
|
+
* @param path - the figure's own path, as the Host named it.
|
|
5976
|
+
* @param workspace - the session's working directory, when there is one.
|
|
5977
|
+
*/
|
|
5978
|
+
const runMenu = (0, react.useCallback)((id, path, workspace) => {
|
|
5979
|
+
setMenuAt(null);
|
|
5980
|
+
if (id === "sendToAgent") {
|
|
5981
|
+
run("agent");
|
|
5982
|
+
return;
|
|
5983
|
+
}
|
|
5984
|
+
if (id === "sendToComposer") {
|
|
5985
|
+
run("composer");
|
|
5986
|
+
return;
|
|
5987
|
+
}
|
|
5988
|
+
if (id === "open") {
|
|
5989
|
+
openOnDesktop(props.remote, path).then((ok) => {
|
|
5990
|
+
setStatus(ok ? {
|
|
5991
|
+
tone: "ok",
|
|
5992
|
+
text: t("menuOpened")
|
|
5993
|
+
} : {
|
|
5994
|
+
tone: "error",
|
|
5995
|
+
text: t("menuOpenFailed")
|
|
5996
|
+
});
|
|
5997
|
+
});
|
|
5998
|
+
return;
|
|
5999
|
+
}
|
|
6000
|
+
if (id === "reveal") {
|
|
6001
|
+
revealOnDesktop(props.remote, path).then((ok) => {
|
|
6002
|
+
setStatus(ok ? {
|
|
6003
|
+
tone: "ok",
|
|
6004
|
+
text: t("menuRevealed")
|
|
6005
|
+
} : {
|
|
6006
|
+
tone: "error",
|
|
6007
|
+
text: t("menuRevealFailed")
|
|
6008
|
+
});
|
|
6009
|
+
});
|
|
6010
|
+
return;
|
|
6011
|
+
}
|
|
6012
|
+
if (id === "copyImage") {
|
|
6013
|
+
/* v8 ignore next -- the row is only offered for a figure that has a layer. */
|
|
6014
|
+
if (layer === null) return;
|
|
6015
|
+
exportPng(layer).then(async (image) => {
|
|
6016
|
+
const copied = await copyImageToClipboard(image);
|
|
6017
|
+
setStatus(copied ? {
|
|
6018
|
+
tone: "ok",
|
|
6019
|
+
text: t("menuCopied")
|
|
6020
|
+
} : {
|
|
6021
|
+
tone: "error",
|
|
6022
|
+
text: t("menuCopyFailed")
|
|
6023
|
+
});
|
|
6024
|
+
}, () => {
|
|
6025
|
+
setStatus({
|
|
6026
|
+
tone: "error",
|
|
6027
|
+
text: t("menuCopyFailed")
|
|
6028
|
+
});
|
|
6029
|
+
});
|
|
6030
|
+
return;
|
|
6031
|
+
}
|
|
6032
|
+
const text = copiedText(id, path, workspace);
|
|
6033
|
+
/* v8 ignore next */
|
|
6034
|
+
if (text === null) return;
|
|
6035
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
6036
|
+
setStatus({
|
|
6037
|
+
tone: "ok",
|
|
6038
|
+
text: t("menuCopied")
|
|
6039
|
+
});
|
|
6040
|
+
}, () => {
|
|
6041
|
+
setStatus({
|
|
6042
|
+
tone: "error",
|
|
6043
|
+
text: t("menuCopyFailed")
|
|
6044
|
+
});
|
|
6045
|
+
});
|
|
6046
|
+
}, [
|
|
6047
|
+
exportPng,
|
|
6048
|
+
layer,
|
|
6049
|
+
props.remote,
|
|
6050
|
+
run,
|
|
6051
|
+
t
|
|
6052
|
+
]);
|
|
5242
6053
|
/** Whether this body holds a rendered document rather than a figure file. */
|
|
5243
6054
|
const isHtml = mediaType === "text/html";
|
|
5244
6055
|
/** Name what one figure point landed on, for a surface that holds its own elements. */
|
|
@@ -5256,6 +6067,18 @@ window.__ModuleLoader__.load({
|
|
|
5256
6067
|
role: "alert",
|
|
5257
6068
|
children: figureError
|
|
5258
6069
|
});
|
|
6070
|
+
/**
|
|
6071
|
+
* Press a row of the list: show that mark and put the pill beside it.
|
|
6072
|
+
*
|
|
6073
|
+
* The selection goes first and the page turn second, because a turn remounts this
|
|
6074
|
+
* body for another page and the mark has to already be the one selected when the
|
|
6075
|
+
* new page's pill is measured — otherwise the row would turn the page and leave
|
|
6076
|
+
* the reader with nothing to read on it.
|
|
6077
|
+
*/
|
|
6078
|
+
const reveal = (mark) => {
|
|
6079
|
+
setSelectedId(mark.id);
|
|
6080
|
+
revealOnPage(mark);
|
|
6081
|
+
};
|
|
5259
6082
|
const selected = marks.find((mark) => mark.id === selectedId) ?? null;
|
|
5260
6083
|
/**
|
|
5261
6084
|
* What the colour control currently means.
|
|
@@ -5281,7 +6104,7 @@ window.__ModuleLoader__.load({
|
|
|
5281
6104
|
* One number for the fill, the knob and the input: computed three times the three
|
|
5282
6105
|
* could round differently and the knob would sit a pixel off the fill it belongs to.
|
|
5283
6106
|
*/
|
|
5284
|
-
const weightFraction = shownWeight === null ? 0 : (shownWeight - WEIGHT_MIN) / (
|
|
6107
|
+
const weightFraction = shownWeight === null ? 0 : (shownWeight - WEIGHT_MIN) / (8 - WEIGHT_MIN);
|
|
5285
6108
|
/**
|
|
5286
6109
|
* Change one attribute of the selected mark, or of the next stroke when nothing
|
|
5287
6110
|
* is selected.
|
|
@@ -5338,6 +6161,240 @@ window.__ModuleLoader__.load({
|
|
|
5338
6161
|
width: size?.width,
|
|
5339
6162
|
height: size?.height
|
|
5340
6163
|
};
|
|
6164
|
+
/**
|
|
6165
|
+
* The floating chrome: the weight rail, the tool palette and the bar.
|
|
6166
|
+
*
|
|
6167
|
+
* Assembled as a value rather than drawn in place because it may be rendered
|
|
6168
|
+
* somewhere else: a paged document hands this body a node of the pane, and a bar
|
|
6169
|
+
* drawn inside a page is a bar that scrolls with that page.
|
|
6170
|
+
*/
|
|
6171
|
+
const chromeStrips = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6172
|
+
className: "da-palette",
|
|
6173
|
+
ref: paletteRef,
|
|
6174
|
+
"data-dragging": dragging ? "true" : void 0,
|
|
6175
|
+
style: paletteStyle,
|
|
6176
|
+
role: "toolbar",
|
|
6177
|
+
"aria-label": t("tools"),
|
|
6178
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6179
|
+
className: "da-palette-grip",
|
|
6180
|
+
title: t("moveTools"),
|
|
6181
|
+
onPointerDown: (event) => {
|
|
6182
|
+
/* v8 ignore next */
|
|
6183
|
+
if (rootRef.current === null) return;
|
|
6184
|
+
/* v8 ignore next -- jsdom has no pointer capture; a browser always does. */
|
|
6185
|
+
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
6186
|
+
dragFrom.current = {
|
|
6187
|
+
pointerId: event.pointerId,
|
|
6188
|
+
origin: palette ?? DEFAULT_PALETTE,
|
|
6189
|
+
box: {
|
|
6190
|
+
left: event.clientX,
|
|
6191
|
+
top: event.clientY
|
|
6192
|
+
}
|
|
6193
|
+
};
|
|
6194
|
+
setDragging(true);
|
|
6195
|
+
},
|
|
6196
|
+
onPointerMove: (event) => {
|
|
6197
|
+
dragPalette(event);
|
|
6198
|
+
},
|
|
6199
|
+
onPointerUp: (event) => {
|
|
6200
|
+
event.currentTarget.releasePointerCapture?.(event.pointerId);
|
|
6201
|
+
dragFrom.current = null;
|
|
6202
|
+
setDragging(false);
|
|
6203
|
+
},
|
|
6204
|
+
onPointerCancel: () => {
|
|
6205
|
+
dragFrom.current = null;
|
|
6206
|
+
setDragging(false);
|
|
6207
|
+
},
|
|
6208
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GripDots, { wide: true })
|
|
6209
|
+
}), TOOLS.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6210
|
+
type: "button",
|
|
6211
|
+
className: "da-btn da-tool",
|
|
6212
|
+
"aria-pressed": tool === entry.tool,
|
|
6213
|
+
"aria-label": t(entry.label),
|
|
6214
|
+
title: t(entry.label),
|
|
6215
|
+
onClick: () => {
|
|
6216
|
+
setTool(entry.tool);
|
|
6217
|
+
},
|
|
6218
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6219
|
+
className: "da-tool-icon",
|
|
6220
|
+
"aria-hidden": true,
|
|
6221
|
+
children: entry.tool === "select" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PointerIcon, {}) : ToolIcon(entry.tool)
|
|
6222
|
+
})
|
|
6223
|
+
}, entry.tool))]
|
|
6224
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6225
|
+
className: "da-bar",
|
|
6226
|
+
ref: barRef,
|
|
6227
|
+
"data-dragging": barDragging ? "true" : void 0,
|
|
6228
|
+
"data-parked": barSpot === void 0 ? void 0 : "true",
|
|
6229
|
+
style: barStyle,
|
|
6230
|
+
children: [
|
|
6231
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6232
|
+
className: "da-palette-grip",
|
|
6233
|
+
title: t("moveBar"),
|
|
6234
|
+
onPointerDown: (event) => {
|
|
6235
|
+
/* v8 ignore next */
|
|
6236
|
+
if (rootRef.current === null) return;
|
|
6237
|
+
/* v8 ignore next -- jsdom has no pointer capture; a browser always does. */
|
|
6238
|
+
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
6239
|
+
barFrom.current = {
|
|
6240
|
+
pointerId: event.pointerId,
|
|
6241
|
+
origin: barSpot ?? DEFAULT_PALETTE,
|
|
6242
|
+
box: {
|
|
6243
|
+
left: event.clientX,
|
|
6244
|
+
top: event.clientY
|
|
6245
|
+
}
|
|
6246
|
+
};
|
|
6247
|
+
setBarDragging(true);
|
|
6248
|
+
},
|
|
6249
|
+
onPointerMove: (event) => {
|
|
6250
|
+
dragBar(event);
|
|
6251
|
+
},
|
|
6252
|
+
onPointerUp: (event) => {
|
|
6253
|
+
event.currentTarget.releasePointerCapture?.(event.pointerId);
|
|
6254
|
+
barFrom.current = null;
|
|
6255
|
+
setBarDragging(false);
|
|
6256
|
+
},
|
|
6257
|
+
onPointerCancel: () => {
|
|
6258
|
+
barFrom.current = null;
|
|
6259
|
+
setBarDragging(false);
|
|
6260
|
+
},
|
|
6261
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GripDots, { wide: false })
|
|
6262
|
+
}),
|
|
6263
|
+
props.pagingSlot === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [props.pagingSlot, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6264
|
+
className: "da-sep",
|
|
6265
|
+
"aria-hidden": true
|
|
6266
|
+
})] }),
|
|
6267
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6268
|
+
className: "da-bar-tools",
|
|
6269
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
6270
|
+
type: "button",
|
|
6271
|
+
className: "da-btn da-style",
|
|
6272
|
+
"aria-expanded": styleOpen,
|
|
6273
|
+
"aria-label": t("style"),
|
|
6274
|
+
title: t("style"),
|
|
6275
|
+
onClick: () => {
|
|
6276
|
+
setOpenPanel((open) => open === "style" ? null : "style");
|
|
6277
|
+
},
|
|
6278
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6279
|
+
className: "da-swatch-dot",
|
|
6280
|
+
style: { background: shownColor }
|
|
6281
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6282
|
+
className: "da-style-caret",
|
|
6283
|
+
"aria-hidden": true,
|
|
6284
|
+
children: styleOpen ? "▴" : "▾"
|
|
6285
|
+
})]
|
|
6286
|
+
})
|
|
6287
|
+
}),
|
|
6288
|
+
styleOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6289
|
+
className: "da-style-row",
|
|
6290
|
+
role: "group",
|
|
6291
|
+
"aria-label": t("style"),
|
|
6292
|
+
"data-side": foldOpensDown ? "below" : "above",
|
|
6293
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6294
|
+
className: "da-style-label",
|
|
6295
|
+
children: t("styleColor")
|
|
6296
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6297
|
+
className: "da-style-colors",
|
|
6298
|
+
children: COLORS.map((value) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6299
|
+
type: "button",
|
|
6300
|
+
className: "da-swatch",
|
|
6301
|
+
"aria-pressed": shownColor === value,
|
|
6302
|
+
style: { background: value },
|
|
6303
|
+
"aria-label": value,
|
|
6304
|
+
title: value,
|
|
6305
|
+
onClick: () => {
|
|
6306
|
+
chooseColor(value);
|
|
6307
|
+
}
|
|
6308
|
+
}, value))
|
|
6309
|
+
})]
|
|
6310
|
+
}) : null,
|
|
6311
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6312
|
+
className: "da-sep",
|
|
6313
|
+
"aria-hidden": true
|
|
6314
|
+
}),
|
|
6315
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "da-spacer" }),
|
|
6316
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
6317
|
+
type: "button",
|
|
6318
|
+
className: "da-dock-btn",
|
|
6319
|
+
"aria-pressed": goalOpen,
|
|
6320
|
+
"aria-label": t("summary"),
|
|
6321
|
+
title: t("summary"),
|
|
6322
|
+
onClick: () => {
|
|
6323
|
+
setOpenPanel((open) => open === "goal" ? null : "goal");
|
|
6324
|
+
},
|
|
6325
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(GoalIcon, {}), summary.trim() === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6326
|
+
className: "da-dock-dot",
|
|
6327
|
+
"aria-hidden": true
|
|
6328
|
+
})]
|
|
6329
|
+
}),
|
|
6330
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
6331
|
+
type: "button",
|
|
6332
|
+
className: "da-dock-btn",
|
|
6333
|
+
"aria-pressed": listOpen,
|
|
6334
|
+
"aria-label": t("marks"),
|
|
6335
|
+
title: t("marks"),
|
|
6336
|
+
onClick: () => {
|
|
6337
|
+
setOpenPanel((open) => open === "list" ? null : "list");
|
|
6338
|
+
},
|
|
6339
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ListIcon, {}), listedMarks.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6340
|
+
className: "da-dock-count",
|
|
6341
|
+
children: listedMarks.length
|
|
6342
|
+
})]
|
|
6343
|
+
}),
|
|
6344
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6345
|
+
className: "da-dock-sep",
|
|
6346
|
+
"aria-hidden": true
|
|
6347
|
+
}),
|
|
6348
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6349
|
+
type: "button",
|
|
6350
|
+
className: "da-dock-btn",
|
|
6351
|
+
"aria-label": t("undo"),
|
|
6352
|
+
title: t("undo"),
|
|
6353
|
+
disabled: past.length === 0,
|
|
6354
|
+
onClick: undo,
|
|
6355
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UndoIcon, {})
|
|
6356
|
+
}),
|
|
6357
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6358
|
+
type: "button",
|
|
6359
|
+
className: "da-dock-btn",
|
|
6360
|
+
"aria-label": t("redo"),
|
|
6361
|
+
title: t("redo"),
|
|
6362
|
+
disabled: future.length === 0,
|
|
6363
|
+
onClick: redo,
|
|
6364
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RedoIcon, {})
|
|
6365
|
+
}),
|
|
6366
|
+
isPage && props.onSend === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
6367
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6368
|
+
className: "da-dock-sep",
|
|
6369
|
+
"aria-hidden": true
|
|
6370
|
+
}),
|
|
6371
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6372
|
+
type: "button",
|
|
6373
|
+
className: "da-dock-btn",
|
|
6374
|
+
disabled: busy !== null,
|
|
6375
|
+
"aria-label": t("sendToAgent"),
|
|
6376
|
+
title: t("sendToAgent"),
|
|
6377
|
+
onClick: () => {
|
|
6378
|
+
if (props.onSend === void 0) run("agent");
|
|
6379
|
+
else props.onSend("agent");
|
|
6380
|
+
},
|
|
6381
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SendIcon, {})
|
|
6382
|
+
}),
|
|
6383
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6384
|
+
type: "button",
|
|
6385
|
+
className: "da-dock-btn",
|
|
6386
|
+
disabled: busy !== null,
|
|
6387
|
+
"aria-label": t("sendToComposer"),
|
|
6388
|
+
title: t("sendToComposer"),
|
|
6389
|
+
onClick: () => {
|
|
6390
|
+
if (props.onSend === void 0) run("composer");
|
|
6391
|
+
else props.onSend("composer");
|
|
6392
|
+
},
|
|
6393
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChatIcon, {})
|
|
6394
|
+
})
|
|
6395
|
+
] })
|
|
6396
|
+
]
|
|
6397
|
+
})] });
|
|
5341
6398
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5342
6399
|
className: "da-root",
|
|
5343
6400
|
ref: rootRef,
|
|
@@ -5360,19 +6417,22 @@ window.__ModuleLoader__.load({
|
|
|
5360
6417
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5361
6418
|
className: "da-stage",
|
|
5362
6419
|
ref: attachStage,
|
|
6420
|
+
onContextMenu: openMenu,
|
|
5363
6421
|
"data-hand": tool === "select" ? "true" : void 0,
|
|
5364
6422
|
"data-panning": panning ? "true" : void 0,
|
|
5365
6423
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5366
6424
|
className: "da-surface",
|
|
5367
6425
|
ref: surfaceRef,
|
|
5368
6426
|
style: surfaceStyle,
|
|
5369
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.
|
|
6427
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5370
6428
|
className: "da-paper",
|
|
6429
|
+
ref: paperRef,
|
|
5371
6430
|
style: paperStyle,
|
|
5372
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.
|
|
6431
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6432
|
+
className: "da-figure-frame",
|
|
5373
6433
|
ref: figureRef,
|
|
5374
6434
|
style: figureStyle,
|
|
5375
|
-
children:
|
|
6435
|
+
children: figure?.kind === "svg" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5376
6436
|
ref: figureHostRef,
|
|
5377
6437
|
dangerouslySetInnerHTML: { __html: figure.markup }
|
|
5378
6438
|
}) : figure?.kind === "raster" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
@@ -5397,39 +6457,59 @@ window.__ModuleLoader__.load({
|
|
|
5397
6457
|
width: figure.width,
|
|
5398
6458
|
height: htmlHeight
|
|
5399
6459
|
}
|
|
5400
|
-
}) : null
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
6460
|
+
}) : null
|
|
6461
|
+
}), paper === null || size === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Canvas, {
|
|
6462
|
+
width: paper.width,
|
|
6463
|
+
height: paper.height,
|
|
6464
|
+
origin: [-paper.x, -paper.y],
|
|
6465
|
+
marks,
|
|
6466
|
+
tool,
|
|
6467
|
+
color,
|
|
6468
|
+
weight,
|
|
6469
|
+
scale,
|
|
6470
|
+
containerRef: figureHostRef,
|
|
6471
|
+
anchoring: isVector,
|
|
6472
|
+
anchorAtFigurePoint: figure?.kind === "html" ? anchorAtFigurePoint : void 0,
|
|
6473
|
+
selectedId,
|
|
6474
|
+
onSelect: setSelectedId,
|
|
6475
|
+
onAdd: addMark,
|
|
6476
|
+
onMove: moveMark,
|
|
6477
|
+
onUpdate: updateMark,
|
|
6478
|
+
onRemove: removeMark,
|
|
6479
|
+
onPan: panBy,
|
|
6480
|
+
onPanStart: () => {
|
|
6481
|
+
setPanning(true);
|
|
6482
|
+
},
|
|
6483
|
+
onPanEnd: () => {
|
|
6484
|
+
setPanning(false);
|
|
6485
|
+
},
|
|
6486
|
+
onEdit: editMark,
|
|
6487
|
+
onDrawn: finishDrawing,
|
|
6488
|
+
onLiveEdit: setLiveMark,
|
|
6489
|
+
numbering: props.numbering
|
|
6490
|
+
})]
|
|
5430
6491
|
})
|
|
5431
6492
|
})
|
|
5432
6493
|
}),
|
|
6494
|
+
menuAt === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6495
|
+
className: "da-menu",
|
|
6496
|
+
ref: menuRef,
|
|
6497
|
+
role: "menu",
|
|
6498
|
+
"aria-label": t("menuLabel"),
|
|
6499
|
+
style: {
|
|
6500
|
+
left: menuAt.left,
|
|
6501
|
+
top: menuAt.top
|
|
6502
|
+
},
|
|
6503
|
+
children: menuItems.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6504
|
+
type: "button",
|
|
6505
|
+
className: "da-menu-item",
|
|
6506
|
+
role: "menuitem",
|
|
6507
|
+
onClick: () => {
|
|
6508
|
+
runMenu(item.id, menuAt.path, menuAt.workspace);
|
|
6509
|
+
},
|
|
6510
|
+
children: item.label
|
|
6511
|
+
}, item.id))
|
|
6512
|
+
}),
|
|
5433
6513
|
goalOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5434
6514
|
className: "da-float da-float-record da-float-goal",
|
|
5435
6515
|
role: "group",
|
|
@@ -5487,14 +6567,14 @@ window.__ModuleLoader__.load({
|
|
|
5487
6567
|
t("marks"),
|
|
5488
6568
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5489
6569
|
className: "da-card-count",
|
|
5490
|
-
children:
|
|
6570
|
+
children: listedMarks.length
|
|
5491
6571
|
})
|
|
5492
6572
|
]
|
|
5493
6573
|
}),
|
|
5494
6574
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5495
6575
|
type: "button",
|
|
5496
6576
|
className: "da-btn da-btn-quiet",
|
|
5497
|
-
disabled:
|
|
6577
|
+
disabled: listedMarks.length === 0,
|
|
5498
6578
|
onClick: () => {
|
|
5499
6579
|
discard();
|
|
5500
6580
|
},
|
|
@@ -5513,7 +6593,7 @@ window.__ModuleLoader__.load({
|
|
|
5513
6593
|
]
|
|
5514
6594
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5515
6595
|
className: "da-marks",
|
|
5516
|
-
children:
|
|
6596
|
+
children: listedMarks.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5517
6597
|
className: "da-empty",
|
|
5518
6598
|
children: [
|
|
5519
6599
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(BoxIcon, {}),
|
|
@@ -5526,41 +6606,56 @@ window.__ModuleLoader__.load({
|
|
|
5526
6606
|
children: t("noMarksHint")
|
|
5527
6607
|
})
|
|
5528
6608
|
]
|
|
5529
|
-
}) :
|
|
6609
|
+
}) : listedMarks.map((mark, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5530
6610
|
className: "da-mark",
|
|
5531
6611
|
"data-selected": mark.id === selectedId,
|
|
5532
6612
|
onClick: () => {
|
|
5533
|
-
|
|
6613
|
+
reveal(mark);
|
|
5534
6614
|
},
|
|
5535
6615
|
children: [
|
|
5536
6616
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5537
|
-
className: "da-mark-
|
|
5538
|
-
style: { background: mark.color },
|
|
6617
|
+
className: "da-mark-n",
|
|
5539
6618
|
children: index + 1
|
|
5540
6619
|
}),
|
|
6620
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6621
|
+
className: "da-mark-glyph",
|
|
6622
|
+
style: { color: mark.color },
|
|
6623
|
+
title: t("markKindLabel", { kind: t(TOOL_COPY[mark.kind]) }),
|
|
6624
|
+
children: ToolIcon(mark.kind)
|
|
6625
|
+
}),
|
|
5541
6626
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
5542
6627
|
type: "button",
|
|
5543
6628
|
className: "da-mark-text",
|
|
5544
6629
|
onClick: (event) => {
|
|
5545
6630
|
event.stopPropagation();
|
|
5546
|
-
|
|
6631
|
+
reveal(mark);
|
|
5547
6632
|
},
|
|
5548
6633
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5549
6634
|
className: "da-mark-title",
|
|
5550
6635
|
children: mark.detail !== void 0 && mark.detail.trim() !== "" ? mark.detail : t("emptyDetail")
|
|
5551
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6636
|
+
}), anchorName(mark) === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5552
6637
|
className: "da-mark-meta",
|
|
5553
|
-
children:
|
|
6638
|
+
children: anchorName(mark)
|
|
5554
6639
|
})]
|
|
5555
6640
|
}),
|
|
6641
|
+
mark.page === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6642
|
+
className: "da-mark-page",
|
|
6643
|
+
children: t("markPage", { page: mark.page })
|
|
6644
|
+
}),
|
|
5556
6645
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5557
6646
|
type: "button",
|
|
5558
6647
|
className: "da-mark-del",
|
|
6648
|
+
"aria-label": t("delete"),
|
|
6649
|
+
title: t("delete"),
|
|
5559
6650
|
onClick: (event) => {
|
|
5560
6651
|
event.stopPropagation();
|
|
5561
6652
|
removeMark(mark.id);
|
|
5562
6653
|
},
|
|
5563
|
-
children:
|
|
6654
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6655
|
+
className: "da-mark-del-x",
|
|
6656
|
+
"aria-hidden": "true",
|
|
6657
|
+
children: "✕"
|
|
6658
|
+
})
|
|
5564
6659
|
})
|
|
5565
6660
|
]
|
|
5566
6661
|
}, mark.id))
|
|
@@ -5609,7 +6704,7 @@ window.__ModuleLoader__.load({
|
|
|
5609
6704
|
})]
|
|
5610
6705
|
})]
|
|
5611
6706
|
}),
|
|
5612
|
-
shownWeight === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
6707
|
+
!chrome || shownWeight === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
5613
6708
|
className: "da-weight",
|
|
5614
6709
|
title: t(selected?.kind === "point" || tool === "point" ? "pinSize" : "weight"),
|
|
5615
6710
|
children: [
|
|
@@ -5618,7 +6713,7 @@ window.__ModuleLoader__.load({
|
|
|
5618
6713
|
className: "da-weight-input",
|
|
5619
6714
|
"aria-label": t(selected?.kind === "point" || tool === "point" ? "pinSize" : "weight"),
|
|
5620
6715
|
min: WEIGHT_MIN,
|
|
5621
|
-
max:
|
|
6716
|
+
max: 8,
|
|
5622
6717
|
step: .1,
|
|
5623
6718
|
value: shownWeight,
|
|
5624
6719
|
onChange: (event) => {
|
|
@@ -5630,275 +6725,44 @@ window.__ModuleLoader__.load({
|
|
|
5630
6725
|
viewBox: "0 0 18 100",
|
|
5631
6726
|
preserveAspectRatio: "none",
|
|
5632
6727
|
"aria-hidden": "true",
|
|
5633
|
-
children: [
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
6728
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("defs", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("linearGradient", {
|
|
6729
|
+
id: `${trackId}-shade`,
|
|
6730
|
+
x1: "0",
|
|
6731
|
+
y1: "1",
|
|
6732
|
+
x2: "0",
|
|
6733
|
+
y2: "0",
|
|
6734
|
+
children: [
|
|
6735
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("stop", {
|
|
5641
6736
|
offset: "0",
|
|
5642
|
-
stopColor: "rgba(20,40,80,.
|
|
5643
|
-
}),
|
|
6737
|
+
stopColor: "rgba(20,40,80,.06)"
|
|
6738
|
+
}),
|
|
6739
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("stop", {
|
|
6740
|
+
offset: ".55",
|
|
6741
|
+
stopColor: "rgba(20,40,80,.13)"
|
|
6742
|
+
}),
|
|
6743
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("stop", {
|
|
5644
6744
|
offset: "1",
|
|
5645
|
-
stopColor: "rgba(20,40,80,.
|
|
5646
|
-
})]
|
|
5647
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("clipPath", {
|
|
5648
|
-
id: `${trackId}-cut`,
|
|
5649
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
5650
|
-
x: "0",
|
|
5651
|
-
y: 100 - weightFraction * 100,
|
|
5652
|
-
width: "18",
|
|
5653
|
-
height: weightFraction * 100
|
|
6745
|
+
stopColor: "rgba(20,40,80,.24)"
|
|
5654
6746
|
})
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
]
|
|
6747
|
+
]
|
|
6748
|
+
}) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
6749
|
+
d: WEIGHT_TRACK,
|
|
6750
|
+
fill: `url(#${trackId}-shade)`
|
|
6751
|
+
})]
|
|
6752
|
+
}),
|
|
6753
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6754
|
+
className: "da-weight-fill",
|
|
6755
|
+
"aria-hidden": true,
|
|
6756
|
+
style: { height: travelHeight(weightFraction) }
|
|
5666
6757
|
}),
|
|
5667
6758
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5668
6759
|
className: "da-weight-knob",
|
|
5669
6760
|
"aria-hidden": true,
|
|
5670
|
-
style: { bottom: `calc(
|
|
6761
|
+
style: { bottom: `calc(${travelHeight(weightFraction)} - var(--da-knob-size) / 2)` }
|
|
5671
6762
|
})
|
|
5672
6763
|
]
|
|
5673
6764
|
}),
|
|
5674
|
-
|
|
5675
|
-
className: "da-palette",
|
|
5676
|
-
"data-dragging": dragging ? "true" : void 0,
|
|
5677
|
-
style: paletteStyle,
|
|
5678
|
-
role: "toolbar",
|
|
5679
|
-
"aria-label": t("tools"),
|
|
5680
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5681
|
-
className: "da-palette-grip",
|
|
5682
|
-
title: t("moveTools"),
|
|
5683
|
-
onPointerDown: (event) => {
|
|
5684
|
-
/* v8 ignore next */
|
|
5685
|
-
if (rootRef.current === null) return;
|
|
5686
|
-
/* v8 ignore next -- jsdom has no pointer capture; a browser always does. */
|
|
5687
|
-
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
5688
|
-
dragFrom.current = {
|
|
5689
|
-
pointerId: event.pointerId,
|
|
5690
|
-
origin: palette ?? DEFAULT_PALETTE,
|
|
5691
|
-
box: {
|
|
5692
|
-
left: event.clientX,
|
|
5693
|
-
top: event.clientY
|
|
5694
|
-
}
|
|
5695
|
-
};
|
|
5696
|
-
setDragging(true);
|
|
5697
|
-
},
|
|
5698
|
-
onPointerMove: (event) => {
|
|
5699
|
-
dragPalette(event);
|
|
5700
|
-
},
|
|
5701
|
-
onPointerUp: (event) => {
|
|
5702
|
-
event.currentTarget.releasePointerCapture?.(event.pointerId);
|
|
5703
|
-
dragFrom.current = null;
|
|
5704
|
-
setDragging(false);
|
|
5705
|
-
},
|
|
5706
|
-
onPointerCancel: () => {
|
|
5707
|
-
dragFrom.current = null;
|
|
5708
|
-
setDragging(false);
|
|
5709
|
-
},
|
|
5710
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5711
|
-
className: "da-palette-grip-dots",
|
|
5712
|
-
"aria-hidden": true,
|
|
5713
|
-
children: "⠿"
|
|
5714
|
-
})
|
|
5715
|
-
}), TOOLS.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5716
|
-
type: "button",
|
|
5717
|
-
className: "da-btn da-tool",
|
|
5718
|
-
"aria-pressed": tool === entry.tool,
|
|
5719
|
-
"aria-label": t(entry.label),
|
|
5720
|
-
title: t(entry.label),
|
|
5721
|
-
onClick: () => {
|
|
5722
|
-
setTool(entry.tool);
|
|
5723
|
-
},
|
|
5724
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5725
|
-
className: "da-tool-icon",
|
|
5726
|
-
"aria-hidden": true,
|
|
5727
|
-
children: entry.tool === "select" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PointerIcon, {}) : ToolIcon(entry.tool)
|
|
5728
|
-
})
|
|
5729
|
-
}, entry.tool))]
|
|
5730
|
-
}),
|
|
5731
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5732
|
-
className: "da-bar",
|
|
5733
|
-
ref: barRef,
|
|
5734
|
-
"data-dragging": barDragging ? "true" : void 0,
|
|
5735
|
-
"data-parked": barSpot === void 0 ? void 0 : "true",
|
|
5736
|
-
style: barStyle,
|
|
5737
|
-
children: [
|
|
5738
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5739
|
-
className: "da-palette-grip",
|
|
5740
|
-
title: t("moveBar"),
|
|
5741
|
-
onPointerDown: (event) => {
|
|
5742
|
-
/* v8 ignore next */
|
|
5743
|
-
if (rootRef.current === null) return;
|
|
5744
|
-
/* v8 ignore next -- jsdom has no pointer capture; a browser always does. */
|
|
5745
|
-
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
5746
|
-
barFrom.current = {
|
|
5747
|
-
pointerId: event.pointerId,
|
|
5748
|
-
origin: barSpot ?? DEFAULT_PALETTE,
|
|
5749
|
-
box: {
|
|
5750
|
-
left: event.clientX,
|
|
5751
|
-
top: event.clientY
|
|
5752
|
-
}
|
|
5753
|
-
};
|
|
5754
|
-
setBarDragging(true);
|
|
5755
|
-
},
|
|
5756
|
-
onPointerMove: (event) => {
|
|
5757
|
-
dragBar(event);
|
|
5758
|
-
},
|
|
5759
|
-
onPointerUp: (event) => {
|
|
5760
|
-
event.currentTarget.releasePointerCapture?.(event.pointerId);
|
|
5761
|
-
barFrom.current = null;
|
|
5762
|
-
setBarDragging(false);
|
|
5763
|
-
},
|
|
5764
|
-
onPointerCancel: () => {
|
|
5765
|
-
barFrom.current = null;
|
|
5766
|
-
setBarDragging(false);
|
|
5767
|
-
},
|
|
5768
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5769
|
-
className: "da-palette-grip-dots",
|
|
5770
|
-
"aria-hidden": true,
|
|
5771
|
-
children: "⠿"
|
|
5772
|
-
})
|
|
5773
|
-
}),
|
|
5774
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5775
|
-
className: "da-bar-tools",
|
|
5776
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
5777
|
-
type: "button",
|
|
5778
|
-
className: "da-btn da-style",
|
|
5779
|
-
"aria-expanded": styleOpen,
|
|
5780
|
-
"aria-label": t("style"),
|
|
5781
|
-
title: t("style"),
|
|
5782
|
-
onClick: () => {
|
|
5783
|
-
setOpenPanel((open) => open === "style" ? null : "style");
|
|
5784
|
-
},
|
|
5785
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5786
|
-
className: "da-swatch-dot",
|
|
5787
|
-
style: { background: shownColor }
|
|
5788
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5789
|
-
className: "da-style-caret",
|
|
5790
|
-
"aria-hidden": true,
|
|
5791
|
-
children: styleOpen ? "▴" : "▾"
|
|
5792
|
-
})]
|
|
5793
|
-
})
|
|
5794
|
-
}),
|
|
5795
|
-
styleOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5796
|
-
className: "da-style-row",
|
|
5797
|
-
role: "group",
|
|
5798
|
-
"aria-label": t("style"),
|
|
5799
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5800
|
-
className: "da-style-label",
|
|
5801
|
-
children: t("styleColor")
|
|
5802
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5803
|
-
className: "da-style-colors",
|
|
5804
|
-
children: COLORS.map((value) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5805
|
-
type: "button",
|
|
5806
|
-
className: "da-swatch",
|
|
5807
|
-
"aria-pressed": shownColor === value,
|
|
5808
|
-
style: { background: value },
|
|
5809
|
-
"aria-label": value,
|
|
5810
|
-
title: value,
|
|
5811
|
-
onClick: () => {
|
|
5812
|
-
chooseColor(value);
|
|
5813
|
-
}
|
|
5814
|
-
}, value))
|
|
5815
|
-
})]
|
|
5816
|
-
}) : null,
|
|
5817
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5818
|
-
className: "da-sep",
|
|
5819
|
-
"aria-hidden": true
|
|
5820
|
-
}),
|
|
5821
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "da-spacer" }),
|
|
5822
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
5823
|
-
type: "button",
|
|
5824
|
-
className: "da-dock-btn",
|
|
5825
|
-
"aria-pressed": goalOpen,
|
|
5826
|
-
"aria-label": t("summary"),
|
|
5827
|
-
title: t("summary"),
|
|
5828
|
-
onClick: () => {
|
|
5829
|
-
setOpenPanel((open) => open === "goal" ? null : "goal");
|
|
5830
|
-
},
|
|
5831
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(GoalIcon, {}), summary.trim() === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5832
|
-
className: "da-dock-dot",
|
|
5833
|
-
"aria-hidden": true
|
|
5834
|
-
})]
|
|
5835
|
-
}),
|
|
5836
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
5837
|
-
type: "button",
|
|
5838
|
-
className: "da-dock-btn",
|
|
5839
|
-
"aria-pressed": listOpen,
|
|
5840
|
-
"aria-label": t("marks"),
|
|
5841
|
-
title: t("marks"),
|
|
5842
|
-
onClick: () => {
|
|
5843
|
-
setOpenPanel((open) => open === "list" ? null : "list");
|
|
5844
|
-
},
|
|
5845
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ListIcon, {}), marks.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5846
|
-
className: "da-dock-count",
|
|
5847
|
-
children: marks.length
|
|
5848
|
-
})]
|
|
5849
|
-
}),
|
|
5850
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5851
|
-
className: "da-dock-sep",
|
|
5852
|
-
"aria-hidden": true
|
|
5853
|
-
}),
|
|
5854
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5855
|
-
type: "button",
|
|
5856
|
-
className: "da-dock-btn",
|
|
5857
|
-
"aria-label": t("undo"),
|
|
5858
|
-
title: t("undo"),
|
|
5859
|
-
disabled: past.length === 0,
|
|
5860
|
-
onClick: undo,
|
|
5861
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UndoIcon, {})
|
|
5862
|
-
}),
|
|
5863
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5864
|
-
type: "button",
|
|
5865
|
-
className: "da-dock-btn",
|
|
5866
|
-
"aria-label": t("redo"),
|
|
5867
|
-
title: t("redo"),
|
|
5868
|
-
disabled: future.length === 0,
|
|
5869
|
-
onClick: redo,
|
|
5870
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RedoIcon, {})
|
|
5871
|
-
}),
|
|
5872
|
-
isPage ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
5873
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5874
|
-
className: "da-dock-sep",
|
|
5875
|
-
"aria-hidden": true
|
|
5876
|
-
}),
|
|
5877
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5878
|
-
type: "button",
|
|
5879
|
-
className: "da-dock-btn da-dock-btn-primary",
|
|
5880
|
-
disabled: busy !== null,
|
|
5881
|
-
"aria-label": t("sendToAgent"),
|
|
5882
|
-
title: t("sendToAgent"),
|
|
5883
|
-
onClick: () => {
|
|
5884
|
-
run("agent");
|
|
5885
|
-
},
|
|
5886
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SendIcon, {})
|
|
5887
|
-
}),
|
|
5888
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5889
|
-
type: "button",
|
|
5890
|
-
className: "da-dock-btn",
|
|
5891
|
-
disabled: busy !== null,
|
|
5892
|
-
"aria-label": t("sendToComposer"),
|
|
5893
|
-
title: t("sendToComposer"),
|
|
5894
|
-
onClick: () => {
|
|
5895
|
-
run("composer");
|
|
5896
|
-
},
|
|
5897
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChatIcon, {})
|
|
5898
|
-
})
|
|
5899
|
-
] })
|
|
5900
|
-
]
|
|
5901
|
-
})
|
|
6765
|
+
!chrome ? null : props.chromeHost === void 0 || props.chromeHost === null ? chromeStrips : (0, react_dom.createPortal)(chromeStrips, props.chromeHost)
|
|
5902
6766
|
]
|
|
5903
6767
|
});
|
|
5904
6768
|
}
|
|
@@ -6522,7 +7386,8 @@ window.__ModuleLoader__.load({
|
|
|
6522
7386
|
sessions: ctx.get("sessions"),
|
|
6523
7387
|
conversation: ctx.get("conversation"),
|
|
6524
7388
|
sessionId: String(sessionId),
|
|
6525
|
-
localeId: locale.getLocale().locale ?? "zh"
|
|
7389
|
+
localeId: locale.getLocale().locale ?? "zh",
|
|
7390
|
+
remote: ctx.get("remote")
|
|
6526
7391
|
})
|
|
6527
7392
|
}, AnnotatorBody)), "dsh-figmark: document body");
|
|
6528
7393
|
ctx.effect(() => previews.register({
|
|
@@ -6588,7 +7453,8 @@ window.__ModuleLoader__.load({
|
|
|
6588
7453
|
sessions: ctx.get("sessions"),
|
|
6589
7454
|
conversation: ctx.get("conversation"),
|
|
6590
7455
|
sessionId: String(sessionId),
|
|
6591
|
-
localeId: locale.getLocale().locale ?? "zh"
|
|
7456
|
+
localeId: locale.getLocale().locale ?? "zh",
|
|
7457
|
+
remote: ctx.get("remote")
|
|
6592
7458
|
})
|
|
6593
7459
|
}, AnnotatorBody)), "dsh-figmark: html document body");
|
|
6594
7460
|
}
|