dsh-figmark 1.1.3 → 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 +965 -316
- package/lib/client.pdf.js +452 -65
- package/lib/index.js +4 -3
- package/market/index.json +2 -2
- package/package.json +2 -2
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",
|
|
@@ -3222,6 +3265,182 @@ window.__ModuleLoader__.load({
|
|
|
3222
3265
|
}));
|
|
3223
3266
|
} catch {}
|
|
3224
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
|
+
}
|
|
3225
3444
|
//#endregion
|
|
3226
3445
|
//#region src/shared/http.ts
|
|
3227
3446
|
/**
|
|
@@ -3279,7 +3498,8 @@ window.__ModuleLoader__.load({
|
|
|
3279
3498
|
path: body.figure.path,
|
|
3280
3499
|
mediaType: body.figure.mediaType,
|
|
3281
3500
|
sha256: body.figure.sha256,
|
|
3282
|
-
annotation: body.annotation ?? null
|
|
3501
|
+
annotation: body.annotation ?? null,
|
|
3502
|
+
workspace: body.figure.workspace ?? null
|
|
3283
3503
|
};
|
|
3284
3504
|
}
|
|
3285
3505
|
/**
|
|
@@ -3388,8 +3608,14 @@ body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,1
|
|
|
3388
3608
|
/* Every small picture in the panel is one stroke set, declared once: without this
|
|
3389
3609
|
an svg with no fill of its own falls back to a solid black shape, which is what
|
|
3390
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. */
|
|
3391
3616
|
.da-ico{width:16px;height:16px;flex:0 0 auto;fill:none;stroke:currentColor;
|
|
3392
|
-
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}
|
|
3393
3619
|
|
|
3394
3620
|
/* --- the floating tool palette ------------------------------------------- */
|
|
3395
3621
|
/* The tools float over the figure in a dark rounded pill so they can be parked
|
|
@@ -3423,7 +3649,7 @@ body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,1
|
|
|
3423
3649
|
.da-palette .da-tool:hover{background:rgba(255,255,255,.14)}
|
|
3424
3650
|
.da-palette .da-tool[aria-pressed="true"]{background:#176df2;color:#fff}
|
|
3425
3651
|
.da-palette .da-tool .da-tool-icon{height:auto}
|
|
3426
|
-
.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}
|
|
3427
3653
|
|
|
3428
3654
|
/* --- the size rail ------------------------------------------------------- */
|
|
3429
3655
|
/* A vertical rail along the left edge of the figure: how heavy the pen is, and how
|
|
@@ -3491,6 +3717,9 @@ body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,1
|
|
|
3491
3717
|
/* Parked: placed from the top by the reader's drag, so the bottom anchor that keeps
|
|
3492
3718
|
it centred while untouched must not also apply — with both set it stretches. */
|
|
3493
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}
|
|
3494
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)}
|
|
3495
3724
|
.da-bar[data-dragging="true"] *{cursor:move !important}
|
|
3496
3725
|
/* The bar's buttons are white glyphs on the dark strip. The one filled control is
|
|
@@ -3499,8 +3728,6 @@ body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,1
|
|
|
3499
3728
|
.da-bar .da-dock-btn:hover{background:rgba(255,255,255,.14)}
|
|
3500
3729
|
.da-bar .da-dock-btn[aria-pressed="true"]{background:#176df2;color:#fff}
|
|
3501
3730
|
.da-bar .da-dock-sep{background:rgba(255,255,255,.22)}
|
|
3502
|
-
.da-bar .da-dock-btn-primary{background:#176df2;color:#fff;border-color:#176df2}
|
|
3503
|
-
.da-bar .da-dock-btn-primary:hover{background:#176df2;opacity:.92}
|
|
3504
3731
|
.da-bar .da-dock-dot{background:#4c8dff}
|
|
3505
3732
|
.da-bar .da-dock-count{background:#176df2;color:#fff}
|
|
3506
3733
|
.da-bar .da-style{width:30px;height:28px;padding:0;border:0;border-radius:50%;background:transparent;color:#fff}
|
|
@@ -3519,7 +3746,7 @@ body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,1
|
|
|
3519
3746
|
.da-btn:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.1))}
|
|
3520
3747
|
.da-btn:disabled{opacity:.4;cursor:default}
|
|
3521
3748
|
.da-btn:disabled:hover{background:var(--dsw-alias-bg-base,transparent)}
|
|
3522
|
-
.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}
|
|
3523
3750
|
/* A quiet button has no frame at all until the pointer is on it: it is an action,
|
|
3524
3751
|
not a thing, and the row it sits in is already framed. */
|
|
3525
3752
|
.da-btn-quiet{border-color:transparent;background:transparent;height:24px;padding:0 7px;font-size:11px;color:var(--dsw-alias-label-secondary,inherit)}
|
|
@@ -3529,13 +3756,13 @@ body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,1
|
|
|
3529
3756
|
picture is what a reader who has used the tool once recognizes without reading. */
|
|
3530
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}
|
|
3531
3758
|
.da-tool .da-tool-icon{display:flex;align-items:center;justify-content:center;height:14px}
|
|
3532
|
-
.da-tool .da-tool-icon svg{width:
|
|
3759
|
+
.da-tool .da-tool-icon svg{width:14px;height:14px;stroke-width:2.05}
|
|
3533
3760
|
.da-tool .da-tool-label{font-size:10px;line-height:1;white-space:nowrap;color:var(--dsw-alias-label-secondary,inherit)}
|
|
3534
3761
|
/* The armed tool is the one state in this row the reader must never hunt for: it is
|
|
3535
3762
|
the only tile with a fill, and the only label in the brand colour. */
|
|
3536
3763
|
.da-tool[aria-pressed="true"]{border-color:transparent;background:var(--da-accent-soft,rgba(23,109,242,.1));color:var(--da-accent,currentColor)}
|
|
3537
3764
|
.da-tool[aria-pressed="true"] .da-tool-label{color:var(--da-accent,currentColor);font-weight:600}
|
|
3538
|
-
.da-tool[aria-pressed="true"] svg{stroke-width:2.
|
|
3765
|
+
.da-tool[aria-pressed="true"] svg{stroke-width:2.4}
|
|
3539
3766
|
.da-tool:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.08))}
|
|
3540
3767
|
.da-btn-icon{width:30px;padding:0}
|
|
3541
3768
|
|
|
@@ -3598,13 +3825,73 @@ body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,1
|
|
|
3598
3825
|
.da-page{font-size:12px;min-width:44px;text-align:center;color:var(--dsw-alias-label-secondary,inherit)}
|
|
3599
3826
|
/* The document's toolbar sits above the page body, so the pane is one column of
|
|
3600
3827
|
definite height and the body takes what is left of it. */
|
|
3601
|
-
.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}
|
|
3602
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}
|
|
3603
3886
|
/* The page slot before the first raster. It takes the column the body's stage
|
|
3604
3887
|
takes, so the frame around it — the toolbar above all — is in place from the
|
|
3605
3888
|
first paint and does not move when the page arrives. */
|
|
3606
3889
|
.da-placeholder{flex:1 1 auto;min-height:0;background:var(--dsw-alias-bg-document-preview,transparent)}
|
|
3607
|
-
|
|
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}
|
|
3608
3895
|
/* --- the two cards under the figure ------------------------------------- */
|
|
3609
3896
|
/* --- the dock ------------------------------------------------------------ */
|
|
3610
3897
|
/* The dock's buttons are quiet circles in the bar: they are the actions that are
|
|
@@ -3618,8 +3905,6 @@ body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,1
|
|
|
3618
3905
|
.da-dock-btn:disabled{opacity:.4;cursor:default}
|
|
3619
3906
|
.da-dock-btn:disabled:hover{background:transparent}
|
|
3620
3907
|
/* The one filled control in the panel is the way out of it. */
|
|
3621
|
-
.da-dock-btn-primary{border-color:var(--da-accent,#176df2);background:var(--da-accent,#176df2);color:#fff;box-shadow:0 2px 8px rgba(23,109,242,.32)}
|
|
3622
|
-
.da-dock-btn-primary:hover{opacity:.92;background:var(--da-accent,#176df2)}
|
|
3623
3908
|
/* A dot, not a count: "there is a goal written" is all the bar needs to say about
|
|
3624
3909
|
a field it is not a list of. */
|
|
3625
3910
|
.da-dock-dot{position:absolute;top:3px;right:4px;width:5px;height:5px;border-radius:50%;
|
|
@@ -3706,14 +3991,23 @@ body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,1
|
|
|
3706
3991
|
sentence as the thing you press, and the sentence's own shape and anchor under it
|
|
3707
3992
|
in smaller type. A borderless run of text would read as one paragraph of marks. */
|
|
3708
3993
|
.da-marks{display:flex;flex-direction:column;gap:3px;min-height:0}
|
|
3709
|
-
.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;
|
|
3710
3995
|
border:0.5px solid transparent;min-width:0}
|
|
3711
3996
|
.da-mark:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.08))}
|
|
3712
3997
|
.da-mark[data-selected="true"]{border-color:var(--da-accent,#176df2);
|
|
3713
|
-
background:var(--dsw-alias-interactive-bg-hover,rgba(23,109,242,.
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
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}
|
|
3717
4011
|
.da-mark-text{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;gap:1px;
|
|
3718
4012
|
padding:0;border:0;background:transparent;color:inherit;font:inherit;text-align:left;cursor:pointer}
|
|
3719
4013
|
.da-mark-title{display:block;font-size:12px;line-height:1.35;overflow:hidden;
|
|
@@ -3721,9 +4015,25 @@ body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,1
|
|
|
3721
4015
|
.da-mark-meta{display:block;font-size:10px;line-height:1.35;
|
|
3722
4016
|
color:var(--dsw-alias-label-tertiary,currentColor);overflow:hidden;
|
|
3723
4017
|
text-overflow:ellipsis;white-space:nowrap}
|
|
3724
|
-
|
|
3725
|
-
|
|
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}
|
|
3726
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}
|
|
3727
4037
|
|
|
3728
4038
|
/* The empty list is a picture and two lines: a card of grey text reads as broken,
|
|
3729
4039
|
and "nothing yet" is a state, not an error. */
|
|
@@ -3731,6 +4041,16 @@ body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,1
|
|
|
3731
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}
|
|
3732
4042
|
.da-empty-title{font-size:12px;font-weight:650;color:var(--dsw-alias-label-secondary,inherit)}
|
|
3733
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
|
+
|
|
3734
4054
|
.da-status{flex:0 0 auto;padding:6px 10px;font-size:12px;color:var(--dsw-alias-label-secondary,inherit)}
|
|
3735
4055
|
.da-status[data-tone="error"]{color:var(--dsw-alias-label-error,var(--dsw-alias-state-error-primary,currentColor))}
|
|
3736
4056
|
.da-status[data-tone="ok"]{color:var(--dsw-alias-state-success-primary,currentColor)}
|
|
@@ -4127,30 +4447,41 @@ body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,1
|
|
|
4127
4447
|
/**
|
|
4128
4448
|
* The stroke each marking tool draws, so the row says it in pictures as well as words.
|
|
4129
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
|
+
*
|
|
4130
4455
|
* `select` is not a marking tool and never comes here: it draws nothing, so it
|
|
4131
|
-
* wears the pointer instead
|
|
4456
|
+
* wears the pointer instead.
|
|
4132
4457
|
*/
|
|
4133
4458
|
function ToolIcon(tool) {
|
|
4134
4459
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
4135
|
-
viewBox: "0 0
|
|
4460
|
+
viewBox: "0 0 24 24",
|
|
4136
4461
|
"aria-hidden": "true",
|
|
4137
4462
|
focusable: "false",
|
|
4138
|
-
children: tool === "arrow" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M
|
|
4139
|
-
x: "
|
|
4140
|
-
y: "
|
|
4141
|
-
width: "
|
|
4142
|
-
height: "
|
|
4143
|
-
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"
|
|
4144
4469
|
}) : tool === "ellipse" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
4145
|
-
cx: "
|
|
4146
|
-
cy: "
|
|
4147
|
-
r: "
|
|
4148
|
-
}) : 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", {
|
|
4149
4474
|
fillRule: "evenodd",
|
|
4150
|
-
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",
|
|
4151
4476
|
fill: "currentColor",
|
|
4152
4477
|
stroke: "none"
|
|
4153
|
-
})
|
|
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
|
+
})] })
|
|
4154
4485
|
});
|
|
4155
4486
|
}
|
|
4156
4487
|
/**
|
|
@@ -4164,22 +4495,22 @@ body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,1
|
|
|
4164
4495
|
function GoalIcon() {
|
|
4165
4496
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
4166
4497
|
className: "da-ico",
|
|
4167
|
-
viewBox: "0 0
|
|
4498
|
+
viewBox: "0 0 24 24",
|
|
4168
4499
|
"aria-hidden": "true",
|
|
4169
4500
|
focusable: "false",
|
|
4170
4501
|
children: [
|
|
4171
4502
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
4172
|
-
cx: "
|
|
4173
|
-
cy: "
|
|
4174
|
-
r: "
|
|
4503
|
+
cx: "10.5",
|
|
4504
|
+
cy: "13.5",
|
|
4505
|
+
r: "7.8"
|
|
4175
4506
|
}),
|
|
4176
4507
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
4177
|
-
cx: "
|
|
4178
|
-
cy: "
|
|
4179
|
-
r: "
|
|
4508
|
+
cx: "10.5",
|
|
4509
|
+
cy: "13.5",
|
|
4510
|
+
r: "3.45"
|
|
4180
4511
|
}),
|
|
4181
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M
|
|
4182
|
-
/* @__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" })
|
|
4183
4514
|
]
|
|
4184
4515
|
});
|
|
4185
4516
|
}
|
|
@@ -4187,29 +4518,29 @@ body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,1
|
|
|
4187
4518
|
function ListIcon() {
|
|
4188
4519
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
4189
4520
|
className: "da-ico",
|
|
4190
|
-
viewBox: "0 0
|
|
4521
|
+
viewBox: "0 0 24 24",
|
|
4191
4522
|
"aria-hidden": "true",
|
|
4192
4523
|
focusable: "false",
|
|
4193
4524
|
children: [
|
|
4194
|
-
/* @__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" }),
|
|
4195
4526
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
4196
|
-
cx: "
|
|
4197
|
-
cy: "
|
|
4198
|
-
r: "1",
|
|
4527
|
+
cx: "4.8",
|
|
4528
|
+
cy: "6.75",
|
|
4529
|
+
r: "1.5",
|
|
4199
4530
|
fill: "currentColor",
|
|
4200
4531
|
stroke: "none"
|
|
4201
4532
|
}),
|
|
4202
4533
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
4203
|
-
cx: "
|
|
4204
|
-
cy: "
|
|
4205
|
-
r: "1",
|
|
4534
|
+
cx: "4.8",
|
|
4535
|
+
cy: "12",
|
|
4536
|
+
r: "1.5",
|
|
4206
4537
|
fill: "currentColor",
|
|
4207
4538
|
stroke: "none"
|
|
4208
4539
|
}),
|
|
4209
4540
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
4210
|
-
cx: "
|
|
4211
|
-
cy: "
|
|
4212
|
-
r: "1",
|
|
4541
|
+
cx: "4.8",
|
|
4542
|
+
cy: "17.25",
|
|
4543
|
+
r: "1.5",
|
|
4213
4544
|
fill: "currentColor",
|
|
4214
4545
|
stroke: "none"
|
|
4215
4546
|
})
|
|
@@ -4235,40 +4566,40 @@ body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,1
|
|
|
4235
4566
|
function UndoIcon() {
|
|
4236
4567
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
4237
4568
|
className: "da-ico",
|
|
4238
|
-
viewBox: "0 0
|
|
4569
|
+
viewBox: "0 0 24 24",
|
|
4239
4570
|
"aria-hidden": "true",
|
|
4240
4571
|
focusable: "false",
|
|
4241
|
-
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" })]
|
|
4242
4573
|
});
|
|
4243
4574
|
}
|
|
4244
4575
|
/** The same arrow the other way. */
|
|
4245
4576
|
function RedoIcon() {
|
|
4246
4577
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
4247
4578
|
className: "da-ico",
|
|
4248
|
-
viewBox: "0 0
|
|
4579
|
+
viewBox: "0 0 24 24",
|
|
4249
4580
|
"aria-hidden": "true",
|
|
4250
4581
|
focusable: "false",
|
|
4251
|
-
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" })]
|
|
4252
4583
|
});
|
|
4253
4584
|
}
|
|
4254
4585
|
/** The primary action's mark: a paper plane, the usual "this leaves here". */
|
|
4255
4586
|
function SendIcon() {
|
|
4256
4587
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
4257
4588
|
className: "da-ico",
|
|
4258
|
-
viewBox: "0 0
|
|
4589
|
+
viewBox: "0 0 24 24",
|
|
4259
4590
|
"aria-hidden": "true",
|
|
4260
4591
|
focusable: "false",
|
|
4261
|
-
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" })]
|
|
4262
4593
|
});
|
|
4263
4594
|
}
|
|
4264
4595
|
/** The secondary action's mark: a speech bubble, "this goes to the chat box". */
|
|
4265
4596
|
function ChatIcon() {
|
|
4266
4597
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
4267
4598
|
className: "da-ico",
|
|
4268
|
-
viewBox: "0 0
|
|
4599
|
+
viewBox: "0 0 24 24",
|
|
4269
4600
|
"aria-hidden": "true",
|
|
4270
4601
|
focusable: "false",
|
|
4271
|
-
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" })
|
|
4272
4603
|
});
|
|
4273
4604
|
}
|
|
4274
4605
|
/**
|
|
@@ -4298,7 +4629,7 @@ body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,1
|
|
|
4298
4629
|
const parsedAddress = parseFileAddress(address);
|
|
4299
4630
|
const mediaType = figureMediaType(parsedAddress?.path ?? "");
|
|
4300
4631
|
const isVector = mediaType === "image/svg+xml";
|
|
4301
|
-
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]);
|
|
4302
4633
|
const sessionId = props.sessionId ?? parsedAddress?.sessionId;
|
|
4303
4634
|
/**
|
|
4304
4635
|
* A private prefix for the rail's gradient and clip ids.
|
|
@@ -4370,6 +4701,7 @@ body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,1
|
|
|
4370
4701
|
const surface = props.page;
|
|
4371
4702
|
/** Whether this body annotates one page of a document its owner persists. */
|
|
4372
4703
|
const isPage = surface !== void 0;
|
|
4704
|
+
const chrome = props.chrome !== false;
|
|
4373
4705
|
const figure = surface === void 0 ? fileFigure : {
|
|
4374
4706
|
kind: "raster",
|
|
4375
4707
|
objectUrl: surface.dataUrl,
|
|
@@ -4409,12 +4741,46 @@ body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,1
|
|
|
4409
4741
|
const [color, setColor] = (0, react.useState)(remembered.color ?? COLORS[0]);
|
|
4410
4742
|
const [weight, setWeight] = (0, react.useState)(remembered.weight ?? 1);
|
|
4411
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;
|
|
4412
4753
|
const [past, setPast] = (0, react.useState)([]);
|
|
4413
4754
|
const [future, setFuture] = (0, react.useState)([]);
|
|
4414
|
-
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]);
|
|
4415
4769
|
/** The mark whose detail box should take the caret once it is on screen. */
|
|
4416
4770
|
const [focusId, setFocusId] = (0, react.useState)(null);
|
|
4417
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
|
+
/**
|
|
4418
4784
|
* What the figure shows, as context for the marks below it.
|
|
4419
4785
|
*
|
|
4420
4786
|
* One per figure rather than one per mark: it is never drawn, and its only job
|
|
@@ -4443,7 +4809,14 @@ body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,1
|
|
|
4443
4809
|
* second one's own close button. Opening any popover closes whatever was open;
|
|
4444
4810
|
* opening the one already showing closes it, so the button toggles.
|
|
4445
4811
|
*/
|
|
4446
|
-
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]);
|
|
4447
4820
|
const styleOpen = openPanel === "style";
|
|
4448
4821
|
const goalOpen = openPanel === "goal";
|
|
4449
4822
|
const listOpen = openPanel === "list";
|
|
@@ -4481,7 +4854,19 @@ body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,1
|
|
|
4481
4854
|
* the reader picks a colour is the moment it has to be safe. Storage failures are
|
|
4482
4855
|
* swallowed inside `rememberPreferences` — losing a colour is not worth an error.
|
|
4483
4856
|
*/
|
|
4484
|
-
|
|
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]);
|
|
4485
4870
|
/**
|
|
4486
4871
|
* Whether a drag is currently moving the view.
|
|
4487
4872
|
*
|
|
@@ -4493,6 +4878,11 @@ body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,1
|
|
|
4493
4878
|
const [fitWidth, setFitWidth] = (0, react.useState)(0);
|
|
4494
4879
|
const [busy, setBusy] = (0, react.useState)(null);
|
|
4495
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);
|
|
4496
4886
|
/** The panel's own box, for placing the cards that float over it. */
|
|
4497
4887
|
const rootRef = (0, react.useRef)(null);
|
|
4498
4888
|
/**
|
|
@@ -4505,6 +4895,7 @@ body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,1
|
|
|
4505
4895
|
* off the edge when it narrows.
|
|
4506
4896
|
*/
|
|
4507
4897
|
const [palette, setPalette] = (0, react.useState)(remembered.palette);
|
|
4898
|
+
const paletteRef = (0, react.useRef)(null);
|
|
4508
4899
|
const [dragging, setDragging] = (0, react.useState)(false);
|
|
4509
4900
|
const dragFrom = (0, react.useRef)(null);
|
|
4510
4901
|
/**
|
|
@@ -4550,24 +4941,30 @@ body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,1
|
|
|
4550
4941
|
* @param from - which strip's press is in progress.
|
|
4551
4942
|
* @param write - where its new spot goes.
|
|
4552
4943
|
*/
|
|
4553
|
-
const followPointer = (0, react.useCallback)((event, from, write) => {
|
|
4944
|
+
const followPointer = (0, react.useCallback)((event, from, write, strip) => {
|
|
4554
4945
|
const held = from.current;
|
|
4555
|
-
const root = rootRef.current;
|
|
4946
|
+
const root = strip?.offsetParent ?? rootRef.current;
|
|
4556
4947
|
/* v8 ignore next */
|
|
4557
|
-
if (held === null || root === null) return;
|
|
4948
|
+
if (held === null || root === null || !(root instanceof Element)) return;
|
|
4558
4949
|
const bounds = root.getBoundingClientRect();
|
|
4559
4950
|
const x = held.origin.x + (event.clientX - held.box.left) / Math.max(1, bounds.width);
|
|
4560
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);
|
|
4561
4958
|
write({
|
|
4562
|
-
x: Math.min(
|
|
4563
|
-
y: Math.min(
|
|
4959
|
+
x: Math.min(roomX, Math.max(0, x)),
|
|
4960
|
+
y: Math.min(roomY, Math.max(0, y))
|
|
4564
4961
|
});
|
|
4565
4962
|
}, []);
|
|
4566
4963
|
const dragPalette = (0, react.useCallback)((event) => {
|
|
4567
|
-
followPointer(event, dragFrom, setPalette);
|
|
4964
|
+
followPointer(event, dragFrom, setPalette, paletteRef.current);
|
|
4568
4965
|
}, [followPointer]);
|
|
4569
4966
|
const dragBar = (0, react.useCallback)((event) => {
|
|
4570
|
-
followPointer(event, barFrom, setBarSpot);
|
|
4967
|
+
followPointer(event, barFrom, setBarSpot, barRef.current);
|
|
4571
4968
|
}, [followPointer]);
|
|
4572
4969
|
(0, react.useEffect)(() => {
|
|
4573
4970
|
rememberPreferences({
|
|
@@ -4819,12 +5216,14 @@ body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,1
|
|
|
4819
5216
|
const scale = (0, react.useMemo)(() => {
|
|
4820
5217
|
if (size === null) return 1;
|
|
4821
5218
|
if (zoom !== "fit") return zoom;
|
|
4822
|
-
|
|
4823
|
-
|
|
5219
|
+
const target = props.fitWidth ?? fitWidth;
|
|
5220
|
+
if (target <= 0) return 1;
|
|
5221
|
+
return Math.min(1.5, Math.max(.05, target / size.width));
|
|
4824
5222
|
}, [
|
|
4825
5223
|
zoom,
|
|
4826
5224
|
fitWidth,
|
|
4827
|
-
size
|
|
5225
|
+
size,
|
|
5226
|
+
props.fitWidth
|
|
4828
5227
|
]);
|
|
4829
5228
|
/**
|
|
4830
5229
|
* Where the records hang: above the bar, centred on it.
|
|
@@ -5013,13 +5412,20 @@ body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,1
|
|
|
5013
5412
|
const stage = stageRef.current;
|
|
5014
5413
|
if (stage === null) return;
|
|
5015
5414
|
/**
|
|
5016
|
-
*
|
|
5017
|
-
*
|
|
5018
|
-
*
|
|
5019
|
-
*
|
|
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.
|
|
5020
5427
|
*/
|
|
5021
5428
|
const onWheel = (event) => {
|
|
5022
|
-
if (!event.ctrlKey) return;
|
|
5023
5429
|
event.preventDefault();
|
|
5024
5430
|
zoomAt(liveScale.current * wheelZoomFactor(event.deltaY, event.deltaMode, stage.clientHeight), event.clientX, event.clientY);
|
|
5025
5431
|
};
|
|
@@ -5037,9 +5443,17 @@ body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,1
|
|
|
5037
5443
|
commit([...marks, mark]);
|
|
5038
5444
|
}, [commit, marks]);
|
|
5039
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
|
+
}
|
|
5040
5450
|
commit(marks.filter((mark) => mark.id !== id));
|
|
5041
5451
|
setSelectedId((current) => current === id ? null : current);
|
|
5042
|
-
}, [
|
|
5452
|
+
}, [
|
|
5453
|
+
commit,
|
|
5454
|
+
marks,
|
|
5455
|
+
props.onDocumentRemove
|
|
5456
|
+
]);
|
|
5043
5457
|
/**
|
|
5044
5458
|
* Write one mark's detail.
|
|
5045
5459
|
*
|
|
@@ -5464,6 +5878,178 @@ body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,1
|
|
|
5464
5878
|
size,
|
|
5465
5879
|
t
|
|
5466
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
|
+
]);
|
|
5467
6053
|
/** Whether this body holds a rendered document rather than a figure file. */
|
|
5468
6054
|
const isHtml = mediaType === "text/html";
|
|
5469
6055
|
/** Name what one figure point landed on, for a surface that holds its own elements. */
|
|
@@ -5481,6 +6067,18 @@ body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,1
|
|
|
5481
6067
|
role: "alert",
|
|
5482
6068
|
children: figureError
|
|
5483
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
|
+
};
|
|
5484
6082
|
const selected = marks.find((mark) => mark.id === selectedId) ?? null;
|
|
5485
6083
|
/**
|
|
5486
6084
|
* What the colour control currently means.
|
|
@@ -5563,6 +6161,240 @@ body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,1
|
|
|
5563
6161
|
width: size?.width,
|
|
5564
6162
|
height: size?.height
|
|
5565
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
|
+
})] });
|
|
5566
6398
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5567
6399
|
className: "da-root",
|
|
5568
6400
|
ref: rootRef,
|
|
@@ -5585,6 +6417,7 @@ body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,1
|
|
|
5585
6417
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5586
6418
|
className: "da-stage",
|
|
5587
6419
|
ref: attachStage,
|
|
6420
|
+
onContextMenu: openMenu,
|
|
5588
6421
|
"data-hand": tool === "select" ? "true" : void 0,
|
|
5589
6422
|
"data-panning": panning ? "true" : void 0,
|
|
5590
6423
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -5658,6 +6491,25 @@ body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,1
|
|
|
5658
6491
|
})
|
|
5659
6492
|
})
|
|
5660
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
|
+
}),
|
|
5661
6513
|
goalOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5662
6514
|
className: "da-float da-float-record da-float-goal",
|
|
5663
6515
|
role: "group",
|
|
@@ -5715,14 +6567,14 @@ body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,1
|
|
|
5715
6567
|
t("marks"),
|
|
5716
6568
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5717
6569
|
className: "da-card-count",
|
|
5718
|
-
children:
|
|
6570
|
+
children: listedMarks.length
|
|
5719
6571
|
})
|
|
5720
6572
|
]
|
|
5721
6573
|
}),
|
|
5722
6574
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5723
6575
|
type: "button",
|
|
5724
6576
|
className: "da-btn da-btn-quiet",
|
|
5725
|
-
disabled:
|
|
6577
|
+
disabled: listedMarks.length === 0,
|
|
5726
6578
|
onClick: () => {
|
|
5727
6579
|
discard();
|
|
5728
6580
|
},
|
|
@@ -5741,7 +6593,7 @@ body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,1
|
|
|
5741
6593
|
]
|
|
5742
6594
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5743
6595
|
className: "da-marks",
|
|
5744
|
-
children:
|
|
6596
|
+
children: listedMarks.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5745
6597
|
className: "da-empty",
|
|
5746
6598
|
children: [
|
|
5747
6599
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(BoxIcon, {}),
|
|
@@ -5754,41 +6606,56 @@ body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,1
|
|
|
5754
6606
|
children: t("noMarksHint")
|
|
5755
6607
|
})
|
|
5756
6608
|
]
|
|
5757
|
-
}) :
|
|
6609
|
+
}) : listedMarks.map((mark, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5758
6610
|
className: "da-mark",
|
|
5759
6611
|
"data-selected": mark.id === selectedId,
|
|
5760
6612
|
onClick: () => {
|
|
5761
|
-
|
|
6613
|
+
reveal(mark);
|
|
5762
6614
|
},
|
|
5763
6615
|
children: [
|
|
5764
6616
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5765
|
-
className: "da-mark-
|
|
5766
|
-
style: { background: mark.color },
|
|
6617
|
+
className: "da-mark-n",
|
|
5767
6618
|
children: index + 1
|
|
5768
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
|
+
}),
|
|
5769
6626
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
5770
6627
|
type: "button",
|
|
5771
6628
|
className: "da-mark-text",
|
|
5772
6629
|
onClick: (event) => {
|
|
5773
6630
|
event.stopPropagation();
|
|
5774
|
-
|
|
6631
|
+
reveal(mark);
|
|
5775
6632
|
},
|
|
5776
6633
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5777
6634
|
className: "da-mark-title",
|
|
5778
6635
|
children: mark.detail !== void 0 && mark.detail.trim() !== "" ? mark.detail : t("emptyDetail")
|
|
5779
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6636
|
+
}), anchorName(mark) === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5780
6637
|
className: "da-mark-meta",
|
|
5781
|
-
children:
|
|
6638
|
+
children: anchorName(mark)
|
|
5782
6639
|
})]
|
|
5783
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
|
+
}),
|
|
5784
6645
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5785
6646
|
type: "button",
|
|
5786
6647
|
className: "da-mark-del",
|
|
6648
|
+
"aria-label": t("delete"),
|
|
6649
|
+
title: t("delete"),
|
|
5787
6650
|
onClick: (event) => {
|
|
5788
6651
|
event.stopPropagation();
|
|
5789
6652
|
removeMark(mark.id);
|
|
5790
6653
|
},
|
|
5791
|
-
children:
|
|
6654
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6655
|
+
className: "da-mark-del-x",
|
|
6656
|
+
"aria-hidden": "true",
|
|
6657
|
+
children: "✕"
|
|
6658
|
+
})
|
|
5792
6659
|
})
|
|
5793
6660
|
]
|
|
5794
6661
|
}, mark.id))
|
|
@@ -5837,7 +6704,7 @@ body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,1
|
|
|
5837
6704
|
})]
|
|
5838
6705
|
})]
|
|
5839
6706
|
}),
|
|
5840
|
-
shownWeight === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
6707
|
+
!chrome || shownWeight === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
5841
6708
|
className: "da-weight",
|
|
5842
6709
|
title: t(selected?.kind === "point" || tool === "point" ? "pinSize" : "weight"),
|
|
5843
6710
|
children: [
|
|
@@ -5895,227 +6762,7 @@ body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,1
|
|
|
5895
6762
|
})
|
|
5896
6763
|
]
|
|
5897
6764
|
}),
|
|
5898
|
-
|
|
5899
|
-
className: "da-palette",
|
|
5900
|
-
"data-dragging": dragging ? "true" : void 0,
|
|
5901
|
-
style: paletteStyle,
|
|
5902
|
-
role: "toolbar",
|
|
5903
|
-
"aria-label": t("tools"),
|
|
5904
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5905
|
-
className: "da-palette-grip",
|
|
5906
|
-
title: t("moveTools"),
|
|
5907
|
-
onPointerDown: (event) => {
|
|
5908
|
-
/* v8 ignore next */
|
|
5909
|
-
if (rootRef.current === null) return;
|
|
5910
|
-
/* v8 ignore next -- jsdom has no pointer capture; a browser always does. */
|
|
5911
|
-
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
5912
|
-
dragFrom.current = {
|
|
5913
|
-
pointerId: event.pointerId,
|
|
5914
|
-
origin: palette ?? DEFAULT_PALETTE,
|
|
5915
|
-
box: {
|
|
5916
|
-
left: event.clientX,
|
|
5917
|
-
top: event.clientY
|
|
5918
|
-
}
|
|
5919
|
-
};
|
|
5920
|
-
setDragging(true);
|
|
5921
|
-
},
|
|
5922
|
-
onPointerMove: (event) => {
|
|
5923
|
-
dragPalette(event);
|
|
5924
|
-
},
|
|
5925
|
-
onPointerUp: (event) => {
|
|
5926
|
-
event.currentTarget.releasePointerCapture?.(event.pointerId);
|
|
5927
|
-
dragFrom.current = null;
|
|
5928
|
-
setDragging(false);
|
|
5929
|
-
},
|
|
5930
|
-
onPointerCancel: () => {
|
|
5931
|
-
dragFrom.current = null;
|
|
5932
|
-
setDragging(false);
|
|
5933
|
-
},
|
|
5934
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GripDots, { wide: true })
|
|
5935
|
-
}), TOOLS.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5936
|
-
type: "button",
|
|
5937
|
-
className: "da-btn da-tool",
|
|
5938
|
-
"aria-pressed": tool === entry.tool,
|
|
5939
|
-
"aria-label": t(entry.label),
|
|
5940
|
-
title: t(entry.label),
|
|
5941
|
-
onClick: () => {
|
|
5942
|
-
setTool(entry.tool);
|
|
5943
|
-
},
|
|
5944
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5945
|
-
className: "da-tool-icon",
|
|
5946
|
-
"aria-hidden": true,
|
|
5947
|
-
children: entry.tool === "select" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PointerIcon, {}) : ToolIcon(entry.tool)
|
|
5948
|
-
})
|
|
5949
|
-
}, entry.tool))]
|
|
5950
|
-
}),
|
|
5951
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5952
|
-
className: "da-bar",
|
|
5953
|
-
ref: barRef,
|
|
5954
|
-
"data-dragging": barDragging ? "true" : void 0,
|
|
5955
|
-
"data-parked": barSpot === void 0 ? void 0 : "true",
|
|
5956
|
-
style: barStyle,
|
|
5957
|
-
children: [
|
|
5958
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5959
|
-
className: "da-palette-grip",
|
|
5960
|
-
title: t("moveBar"),
|
|
5961
|
-
onPointerDown: (event) => {
|
|
5962
|
-
/* v8 ignore next */
|
|
5963
|
-
if (rootRef.current === null) return;
|
|
5964
|
-
/* v8 ignore next -- jsdom has no pointer capture; a browser always does. */
|
|
5965
|
-
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
5966
|
-
barFrom.current = {
|
|
5967
|
-
pointerId: event.pointerId,
|
|
5968
|
-
origin: barSpot ?? DEFAULT_PALETTE,
|
|
5969
|
-
box: {
|
|
5970
|
-
left: event.clientX,
|
|
5971
|
-
top: event.clientY
|
|
5972
|
-
}
|
|
5973
|
-
};
|
|
5974
|
-
setBarDragging(true);
|
|
5975
|
-
},
|
|
5976
|
-
onPointerMove: (event) => {
|
|
5977
|
-
dragBar(event);
|
|
5978
|
-
},
|
|
5979
|
-
onPointerUp: (event) => {
|
|
5980
|
-
event.currentTarget.releasePointerCapture?.(event.pointerId);
|
|
5981
|
-
barFrom.current = null;
|
|
5982
|
-
setBarDragging(false);
|
|
5983
|
-
},
|
|
5984
|
-
onPointerCancel: () => {
|
|
5985
|
-
barFrom.current = null;
|
|
5986
|
-
setBarDragging(false);
|
|
5987
|
-
},
|
|
5988
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GripDots, { wide: false })
|
|
5989
|
-
}),
|
|
5990
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5991
|
-
className: "da-bar-tools",
|
|
5992
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
5993
|
-
type: "button",
|
|
5994
|
-
className: "da-btn da-style",
|
|
5995
|
-
"aria-expanded": styleOpen,
|
|
5996
|
-
"aria-label": t("style"),
|
|
5997
|
-
title: t("style"),
|
|
5998
|
-
onClick: () => {
|
|
5999
|
-
setOpenPanel((open) => open === "style" ? null : "style");
|
|
6000
|
-
},
|
|
6001
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6002
|
-
className: "da-swatch-dot",
|
|
6003
|
-
style: { background: shownColor }
|
|
6004
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6005
|
-
className: "da-style-caret",
|
|
6006
|
-
"aria-hidden": true,
|
|
6007
|
-
children: styleOpen ? "▴" : "▾"
|
|
6008
|
-
})]
|
|
6009
|
-
})
|
|
6010
|
-
}),
|
|
6011
|
-
styleOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6012
|
-
className: "da-style-row",
|
|
6013
|
-
role: "group",
|
|
6014
|
-
"aria-label": t("style"),
|
|
6015
|
-
"data-side": foldOpensDown ? "below" : "above",
|
|
6016
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6017
|
-
className: "da-style-label",
|
|
6018
|
-
children: t("styleColor")
|
|
6019
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6020
|
-
className: "da-style-colors",
|
|
6021
|
-
children: COLORS.map((value) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6022
|
-
type: "button",
|
|
6023
|
-
className: "da-swatch",
|
|
6024
|
-
"aria-pressed": shownColor === value,
|
|
6025
|
-
style: { background: value },
|
|
6026
|
-
"aria-label": value,
|
|
6027
|
-
title: value,
|
|
6028
|
-
onClick: () => {
|
|
6029
|
-
chooseColor(value);
|
|
6030
|
-
}
|
|
6031
|
-
}, value))
|
|
6032
|
-
})]
|
|
6033
|
-
}) : null,
|
|
6034
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6035
|
-
className: "da-sep",
|
|
6036
|
-
"aria-hidden": true
|
|
6037
|
-
}),
|
|
6038
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "da-spacer" }),
|
|
6039
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
6040
|
-
type: "button",
|
|
6041
|
-
className: "da-dock-btn",
|
|
6042
|
-
"aria-pressed": goalOpen,
|
|
6043
|
-
"aria-label": t("summary"),
|
|
6044
|
-
title: t("summary"),
|
|
6045
|
-
onClick: () => {
|
|
6046
|
-
setOpenPanel((open) => open === "goal" ? null : "goal");
|
|
6047
|
-
},
|
|
6048
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(GoalIcon, {}), summary.trim() === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6049
|
-
className: "da-dock-dot",
|
|
6050
|
-
"aria-hidden": true
|
|
6051
|
-
})]
|
|
6052
|
-
}),
|
|
6053
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
6054
|
-
type: "button",
|
|
6055
|
-
className: "da-dock-btn",
|
|
6056
|
-
"aria-pressed": listOpen,
|
|
6057
|
-
"aria-label": t("marks"),
|
|
6058
|
-
title: t("marks"),
|
|
6059
|
-
onClick: () => {
|
|
6060
|
-
setOpenPanel((open) => open === "list" ? null : "list");
|
|
6061
|
-
},
|
|
6062
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ListIcon, {}), marks.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6063
|
-
className: "da-dock-count",
|
|
6064
|
-
children: marks.length
|
|
6065
|
-
})]
|
|
6066
|
-
}),
|
|
6067
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6068
|
-
className: "da-dock-sep",
|
|
6069
|
-
"aria-hidden": true
|
|
6070
|
-
}),
|
|
6071
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6072
|
-
type: "button",
|
|
6073
|
-
className: "da-dock-btn",
|
|
6074
|
-
"aria-label": t("undo"),
|
|
6075
|
-
title: t("undo"),
|
|
6076
|
-
disabled: past.length === 0,
|
|
6077
|
-
onClick: undo,
|
|
6078
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UndoIcon, {})
|
|
6079
|
-
}),
|
|
6080
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6081
|
-
type: "button",
|
|
6082
|
-
className: "da-dock-btn",
|
|
6083
|
-
"aria-label": t("redo"),
|
|
6084
|
-
title: t("redo"),
|
|
6085
|
-
disabled: future.length === 0,
|
|
6086
|
-
onClick: redo,
|
|
6087
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RedoIcon, {})
|
|
6088
|
-
}),
|
|
6089
|
-
isPage ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
6090
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6091
|
-
className: "da-dock-sep",
|
|
6092
|
-
"aria-hidden": true
|
|
6093
|
-
}),
|
|
6094
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6095
|
-
type: "button",
|
|
6096
|
-
className: "da-dock-btn da-dock-btn-primary",
|
|
6097
|
-
disabled: busy !== null,
|
|
6098
|
-
"aria-label": t("sendToAgent"),
|
|
6099
|
-
title: t("sendToAgent"),
|
|
6100
|
-
onClick: () => {
|
|
6101
|
-
run("agent");
|
|
6102
|
-
},
|
|
6103
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SendIcon, {})
|
|
6104
|
-
}),
|
|
6105
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6106
|
-
type: "button",
|
|
6107
|
-
className: "da-dock-btn",
|
|
6108
|
-
disabled: busy !== null,
|
|
6109
|
-
"aria-label": t("sendToComposer"),
|
|
6110
|
-
title: t("sendToComposer"),
|
|
6111
|
-
onClick: () => {
|
|
6112
|
-
run("composer");
|
|
6113
|
-
},
|
|
6114
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChatIcon, {})
|
|
6115
|
-
})
|
|
6116
|
-
] })
|
|
6117
|
-
]
|
|
6118
|
-
})
|
|
6765
|
+
!chrome ? null : props.chromeHost === void 0 || props.chromeHost === null ? chromeStrips : (0, react_dom.createPortal)(chromeStrips, props.chromeHost)
|
|
6119
6766
|
]
|
|
6120
6767
|
});
|
|
6121
6768
|
}
|
|
@@ -6739,7 +7386,8 @@ body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,1
|
|
|
6739
7386
|
sessions: ctx.get("sessions"),
|
|
6740
7387
|
conversation: ctx.get("conversation"),
|
|
6741
7388
|
sessionId: String(sessionId),
|
|
6742
|
-
localeId: locale.getLocale().locale ?? "zh"
|
|
7389
|
+
localeId: locale.getLocale().locale ?? "zh",
|
|
7390
|
+
remote: ctx.get("remote")
|
|
6743
7391
|
})
|
|
6744
7392
|
}, AnnotatorBody)), "dsh-figmark: document body");
|
|
6745
7393
|
ctx.effect(() => previews.register({
|
|
@@ -6805,7 +7453,8 @@ body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,1
|
|
|
6805
7453
|
sessions: ctx.get("sessions"),
|
|
6806
7454
|
conversation: ctx.get("conversation"),
|
|
6807
7455
|
sessionId: String(sessionId),
|
|
6808
|
-
localeId: locale.getLocale().locale ?? "zh"
|
|
7456
|
+
localeId: locale.getLocale().locale ?? "zh",
|
|
7457
|
+
remote: ctx.get("remote")
|
|
6809
7458
|
})
|
|
6810
7459
|
}, AnnotatorBody)), "dsh-figmark: html document body");
|
|
6811
7460
|
}
|