dsh-figmark 1.1.1 → 1.1.3
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 +485 -181
- package/lib/index.js +3 -3
- package/market/index.json +2 -2
- package/package.json +2 -2
package/lib/client.js
CHANGED
|
@@ -268,7 +268,7 @@ window.__ModuleLoader__.load({
|
|
|
268
268
|
* @returns the value, pulled to the nearest end of the range.
|
|
269
269
|
*/
|
|
270
270
|
function clampWeight(value) {
|
|
271
|
-
return Math.min(
|
|
271
|
+
return Math.min(8, Math.max(WEIGHT_MIN, value));
|
|
272
272
|
}
|
|
273
273
|
/**
|
|
274
274
|
* Whether a value is a stroke weight this plugin would write.
|
|
@@ -277,7 +277,7 @@ window.__ModuleLoader__.load({
|
|
|
277
277
|
* @returns true when it is one.
|
|
278
278
|
*/
|
|
279
279
|
function isWeight(value) {
|
|
280
|
-
return typeof value === "number" && Number.isFinite(value) && value >= .5 && value <=
|
|
280
|
+
return typeof value === "number" && Number.isFinite(value) && value >= .5 && value <= 8;
|
|
281
281
|
}
|
|
282
282
|
/**
|
|
283
283
|
* Suffix naming one page's files inside a paged document's name.
|
|
@@ -326,6 +326,86 @@ window.__ModuleLoader__.load({
|
|
|
326
326
|
function round(value) {
|
|
327
327
|
return Math.round(value);
|
|
328
328
|
}
|
|
329
|
+
/**
|
|
330
|
+
* Write a freehand stroke's extent as the box it was drawn in.
|
|
331
|
+
*
|
|
332
|
+
* The stroke itself is dozens of coordinates, most of which say nothing an agent
|
|
333
|
+
* can act on; the box says where on the figure to look, which is what the reading
|
|
334
|
+
* of the mark needs. `strokeExtents` names the area, and the phrase above it names
|
|
335
|
+
* the mark, so the pair still reads as one line.
|
|
336
|
+
* @param points - the stroke's sampled points.
|
|
337
|
+
* @returns the box as ` (x,y)-(x,y)`, or nothing for an empty stroke.
|
|
338
|
+
*/
|
|
339
|
+
function strokeExtents(points) {
|
|
340
|
+
let left = Number.POSITIVE_INFINITY;
|
|
341
|
+
let top = Number.POSITIVE_INFINITY;
|
|
342
|
+
let right = Number.NEGATIVE_INFINITY;
|
|
343
|
+
let bottom = Number.NEGATIVE_INFINITY;
|
|
344
|
+
for (const [x, y] of points) {
|
|
345
|
+
left = Math.min(left, x);
|
|
346
|
+
top = Math.min(top, y);
|
|
347
|
+
right = Math.max(right, x);
|
|
348
|
+
bottom = Math.max(bottom, y);
|
|
349
|
+
}
|
|
350
|
+
if (!Number.isFinite(left)) return "";
|
|
351
|
+
return ` (${round(left)},${round(top)})-(${round(right)},${round(bottom)})`;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Write two dragged corners as the rectangle they make.
|
|
355
|
+
*
|
|
356
|
+
* A box dragged up and to the left arrives as (bottom-right, top-left), and the
|
|
357
|
+
* agent reading `(187,138)-(42,88)` has to take a min and a max before it knows
|
|
358
|
+
* which area is meant. Both corners are therefore written left-to-right and
|
|
359
|
+
* top-to-bottom, so the same box always reads the same way however it was drawn.
|
|
360
|
+
* @param first - where the drag started.
|
|
361
|
+
* @param second - where it ended.
|
|
362
|
+
* @returns the corner pair as `(x,y)-(x,y)`, or nothing when the mark is unplaced.
|
|
363
|
+
*/
|
|
364
|
+
function extents(first, second) {
|
|
365
|
+
if (first === void 0 || second === void 0) return "";
|
|
366
|
+
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]))})`;
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Say how far a mark reaches outside the figure, when it does.
|
|
370
|
+
*
|
|
371
|
+
* A negative coordinate is an accurate description — the origin is the figure's
|
|
372
|
+
* top-left corner, so -57 is 57 pixels above it — but a reader has to notice the
|
|
373
|
+
* sign and then reason about what it implies. Naming the overhang outright costs
|
|
374
|
+
* about a dozen characters and removes that step: the agent is told the mark is
|
|
375
|
+
* off the picture and by how much, rather than being left to infer it. A mark that
|
|
376
|
+
* sits inside the figure says nothing at all, which is the common case.
|
|
377
|
+
* @param mark - the mark to measure.
|
|
378
|
+
* @param figure - the figure's size, when the document knows it.
|
|
379
|
+
* @param locale - language of the surrounding message.
|
|
380
|
+
* @returns the sentence fragment to append, or nothing when the mark is inside.
|
|
381
|
+
*/
|
|
382
|
+
function overhang(mark, figure, locale) {
|
|
383
|
+
const { width, height } = figure;
|
|
384
|
+
if (width === void 0 || height === void 0) return "";
|
|
385
|
+
let left = Number.POSITIVE_INFINITY;
|
|
386
|
+
let top = Number.POSITIVE_INFINITY;
|
|
387
|
+
let right = Number.NEGATIVE_INFINITY;
|
|
388
|
+
let bottom = Number.NEGATIVE_INFINITY;
|
|
389
|
+
for (const [x, y] of mark.points) {
|
|
390
|
+
left = Math.min(left, x);
|
|
391
|
+
top = Math.min(top, y);
|
|
392
|
+
right = Math.max(right, x);
|
|
393
|
+
bottom = Math.max(bottom, y);
|
|
394
|
+
}
|
|
395
|
+
const escape = (amount, zhWord, enWord) => {
|
|
396
|
+
const pixels = round(amount);
|
|
397
|
+
if (pixels <= 0) return "";
|
|
398
|
+
return locale === "zh" ? `${zhWord} ${pixels}` : `${enWord} ${pixels}`;
|
|
399
|
+
};
|
|
400
|
+
const escapes = [
|
|
401
|
+
escape(-left, "左", "left"),
|
|
402
|
+
escape(-top, "上", "up"),
|
|
403
|
+
escape(right - width, "右", "right"),
|
|
404
|
+
escape(bottom - height, "下", "down")
|
|
405
|
+
].filter((part) => part !== "");
|
|
406
|
+
if (escapes.length === 0) return "";
|
|
407
|
+
return locale === "zh" ? `(超出图面:${escapes.join("、")} 像素)` : ` (off the figure: ${escapes.join(", ")} px)`;
|
|
408
|
+
}
|
|
329
409
|
/** Describe one mark's shape in words, for the model-facing summary. */
|
|
330
410
|
function describeShape(mark, locale) {
|
|
331
411
|
const [first, second] = mark.points;
|
|
@@ -333,9 +413,12 @@ window.__ModuleLoader__.load({
|
|
|
333
413
|
const turned = mark.rotation === void 0 || mark.rotation === 0 ? "" : zh ? `(旋转 ${Math.round(mark.rotation)}°)` : ` (rotated ${Math.round(mark.rotation)}°)`;
|
|
334
414
|
switch (mark.kind) {
|
|
335
415
|
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":
|
|
416
|
+
case "rect": return (zh ? "框选" : "box") + extents(first, second) + turned;
|
|
417
|
+
case "ellipse": return (zh ? "圈选" : "ellipse") + extents(first, second) + turned;
|
|
418
|
+
case "pen": {
|
|
419
|
+
const box = strokeExtents(mark.points);
|
|
420
|
+
return `${zh ? "手绘线" : "freehand stroke"}${box}${turned}`;
|
|
421
|
+
}
|
|
339
422
|
case "point": return first !== void 0 ? zh ? `单点位于 (${round(first[0])},${round(first[1])})` : `at (${round(first[0])},${round(first[1])})` : zh ? "单点" : "a point";
|
|
340
423
|
}
|
|
341
424
|
}
|
|
@@ -350,7 +433,7 @@ window.__ModuleLoader__.load({
|
|
|
350
433
|
if (anchor.text !== void 0 && anchor.text !== "") parts.push(zh ? `元素文字「${anchor.text}」` : `element text "${anchor.text}"`);
|
|
351
434
|
if (anchor.selector !== void 0 && anchor.selector !== "") parts.push(zh ? `选择器 ${anchor.selector}` : `selector ${anchor.selector}`);
|
|
352
435
|
if (parts.length === 0) parts.push(`<${anchor.tag}>`);
|
|
353
|
-
return `(${parts.join(",")}
|
|
436
|
+
return zh ? `(${parts.join(",")})` : ` (${parts.join(", ")})`;
|
|
354
437
|
}
|
|
355
438
|
/**
|
|
356
439
|
* Render one mark as a numbered line the model reads.
|
|
@@ -362,9 +445,11 @@ window.__ModuleLoader__.load({
|
|
|
362
445
|
* @param mark - the mark to describe.
|
|
363
446
|
* @param index - zero-based position in the document, rendered as a circled number.
|
|
364
447
|
* @param locale - language of the surrounding message.
|
|
448
|
+
* @param figure - the figure these marks belong to, when its size is known; a mark
|
|
449
|
+
* that reaches past it then says so rather than leaving the sign to be noticed.
|
|
365
450
|
* @returns one line of the annotation summary.
|
|
366
451
|
*/
|
|
367
|
-
function describeMark(mark, index, locale) {
|
|
452
|
+
function describeMark(mark, index, locale, figure) {
|
|
368
453
|
const zh = locale === "zh";
|
|
369
454
|
const marker = [
|
|
370
455
|
"①",
|
|
@@ -380,8 +465,9 @@ window.__ModuleLoader__.load({
|
|
|
380
465
|
][index] ?? `${index + 1}.`;
|
|
381
466
|
const page = mark.page === void 0 ? "" : zh ? `第 ${mark.page} 页 ` : `page ${mark.page} `;
|
|
382
467
|
const detail = mark.detail !== void 0 && mark.detail.trim() !== "" ? mark.detail.trim() : "";
|
|
383
|
-
const
|
|
384
|
-
|
|
468
|
+
const escape = figure === void 0 ? "" : overhang(mark, figure, locale);
|
|
469
|
+
const parts = [`${marker} ${page}${describeShape(mark, locale)}${describeAnchor(mark, locale)}${escape}`];
|
|
470
|
+
if (detail !== "") parts.push(zh ? `批注:${detail}` : `note: ${detail}`);
|
|
385
471
|
if (detail === "") parts.push(zh ? "(未写说明)" : "(no detail)");
|
|
386
472
|
return parts.join(zh ? "," : ", ");
|
|
387
473
|
}
|
|
@@ -392,7 +478,7 @@ window.__ModuleLoader__.load({
|
|
|
392
478
|
* @returns one line per mark.
|
|
393
479
|
*/
|
|
394
480
|
function describeMarks(document, locale) {
|
|
395
|
-
return document.marks.map((mark, index) => describeMark(mark, index, locale));
|
|
481
|
+
return document.marks.map((mark, index) => describeMark(mark, index, locale, document.figure));
|
|
396
482
|
}
|
|
397
483
|
//#endregion
|
|
398
484
|
//#region src/shared/figure-kind.ts
|
|
@@ -867,7 +953,7 @@ window.__ModuleLoader__.load({
|
|
|
867
953
|
* @param rotation - the mark's rotation in degrees.
|
|
868
954
|
* @returns the handles.
|
|
869
955
|
*/
|
|
870
|
-
function handlesFor(box, rotation) {
|
|
956
|
+
function handlesFor(box, rotation, size = 7) {
|
|
871
957
|
const centre = boxCentre(box);
|
|
872
958
|
const radians = rotation * Math.PI / 180;
|
|
873
959
|
const at = (x, y) => rotateAround([x, y], centre, radians);
|
|
@@ -884,11 +970,23 @@ window.__ModuleLoader__.load({
|
|
|
884
970
|
});
|
|
885
971
|
placed.push({
|
|
886
972
|
id: "rotate",
|
|
887
|
-
point: at(centre[0], box.y -
|
|
973
|
+
point: at(centre[0], box.y - armFor(size))
|
|
888
974
|
});
|
|
889
975
|
return placed;
|
|
890
976
|
}
|
|
891
977
|
/**
|
|
978
|
+
* How far the turn handle stands off the top edge, for a handle of a given size.
|
|
979
|
+
*
|
|
980
|
+
* Proportional to the handle: a bigger grip on a short arm would have the circle
|
|
981
|
+
* overlapping the corner squares, and a smaller one on a long arm would look
|
|
982
|
+
* detached from the frame it turns.
|
|
983
|
+
* @param size - the handle's side, in figure pixels.
|
|
984
|
+
* @returns the distance from the top edge, in figure pixels.
|
|
985
|
+
*/
|
|
986
|
+
function armFor(size) {
|
|
987
|
+
return size * (22 / 7);
|
|
988
|
+
}
|
|
989
|
+
/**
|
|
892
990
|
* The line that ties the rotation handle to the box it turns.
|
|
893
991
|
*
|
|
894
992
|
* It reads as attached to the mark only because it is anchored on the top edge
|
|
@@ -897,11 +995,11 @@ window.__ModuleLoader__.load({
|
|
|
897
995
|
* @param rotation - the mark's rotation in degrees.
|
|
898
996
|
* @returns the two ends of the tether, from the turn handle to the top edge.
|
|
899
997
|
*/
|
|
900
|
-
function tetherFor(box, rotation) {
|
|
998
|
+
function tetherFor(box, rotation, size = 7) {
|
|
901
999
|
const centre = boxCentre(box);
|
|
902
1000
|
const radians = rotation * Math.PI / 180;
|
|
903
1001
|
return {
|
|
904
|
-
from: rotateAround([centre[0], box.y -
|
|
1002
|
+
from: rotateAround([centre[0], box.y - armFor(size)], centre, radians),
|
|
905
1003
|
to: rotateAround([centre[0], box.y], centre, radians)
|
|
906
1004
|
};
|
|
907
1005
|
}
|
|
@@ -913,10 +1011,10 @@ window.__ModuleLoader__.load({
|
|
|
913
1011
|
* @param reach - how far from a handle's centre still counts, in figure pixels.
|
|
914
1012
|
* @returns the handle grabbed, or undefined for a press on the mark itself.
|
|
915
1013
|
*/
|
|
916
|
-
function handleAt(box, rotation, at, reach = 7) {
|
|
1014
|
+
function handleAt(box, rotation, at, reach = 7, size = 7) {
|
|
917
1015
|
let best;
|
|
918
1016
|
let bestDistance = reach;
|
|
919
|
-
for (const handle of handlesFor(box, rotation)) {
|
|
1017
|
+
for (const handle of handlesFor(box, rotation, size)) {
|
|
920
1018
|
const distance = Math.hypot(handle.point[0] - at[0], handle.point[1] - at[1]);
|
|
921
1019
|
if (distance <= bestDistance) {
|
|
922
1020
|
best = handle.id;
|
|
@@ -993,7 +1091,6 @@ window.__ModuleLoader__.load({
|
|
|
993
1091
|
const degrees = normalizeDegrees((Math.atan2(at[1] - centre[1], at[0] - centre[0]) + Math.PI / 2) * 180 / Math.PI);
|
|
994
1092
|
return snap ? normalizeDegrees(Math.round(degrees / 15) * 15) : degrees;
|
|
995
1093
|
}
|
|
996
|
-
/** The cursor for one handle: what dragging it will do. */
|
|
997
1094
|
const CURSORS = {
|
|
998
1095
|
nw: "nwse-resize",
|
|
999
1096
|
n: "ns-resize",
|
|
@@ -1003,7 +1100,7 @@ window.__ModuleLoader__.load({
|
|
|
1003
1100
|
s: "ns-resize",
|
|
1004
1101
|
sw: "nesw-resize",
|
|
1005
1102
|
w: "ew-resize",
|
|
1006
|
-
rotate: "
|
|
1103
|
+
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
1104
|
};
|
|
1008
1105
|
/**
|
|
1009
1106
|
* The mouse cursor for one handle.
|
|
@@ -1083,6 +1180,20 @@ window.__ModuleLoader__.load({
|
|
|
1083
1180
|
* every colour the palette offers.
|
|
1084
1181
|
*/
|
|
1085
1182
|
const PIN_NUMBER_COLOR = "#ffffff";
|
|
1183
|
+
/**
|
|
1184
|
+
* Font size of the number inside a pin, in figure pixels.
|
|
1185
|
+
*
|
|
1186
|
+
* Scaled by the mark's weight as well as the figure: the rail that makes a pin bigger
|
|
1187
|
+
* makes its head bigger, and a head that grows around a fixed-size numeral reads as a
|
|
1188
|
+
* circle with a speck in it. The number is the pin's whole payload, so it keeps the
|
|
1189
|
+
* same share of the head at every size.
|
|
1190
|
+
* @param mark - the point mark whose pin is being drawn.
|
|
1191
|
+
* @param unit - the figure's size multiple.
|
|
1192
|
+
* @returns the font size to draw the number at.
|
|
1193
|
+
*/
|
|
1194
|
+
function pinFontSizeOf(mark, unit = 1) {
|
|
1195
|
+
return 11 * (mark.weight ?? 1) * unit;
|
|
1196
|
+
}
|
|
1086
1197
|
/** Normalize two opposite corners into a top-left origin and positive extents. */
|
|
1087
1198
|
function bounds(first, second) {
|
|
1088
1199
|
return {
|
|
@@ -1344,27 +1455,15 @@ window.__ModuleLoader__.load({
|
|
|
1344
1455
|
const cy = box.y + box.height / 2;
|
|
1345
1456
|
return `rotate(${n(radians * 180 / Math.PI)} ${n(cx)} ${n(cy)})`;
|
|
1346
1457
|
}
|
|
1347
|
-
//#endregion
|
|
1348
|
-
//#region src/client/scale.ts
|
|
1349
1458
|
/**
|
|
1350
|
-
*
|
|
1351
|
-
*
|
|
1352
|
-
* A stroke is not a physical thing: 2.5 pixels is a bold line on a 300px thumbnail
|
|
1353
|
-
* and a hairline on a 4000px scan. Marks are comparisons the reader makes against
|
|
1354
|
-
* the line work underneath them, so they have to be sized in the same currency as
|
|
1355
|
-
* the figure — a fraction of it — rather than in the figure's pixels.
|
|
1459
|
+
* Smallest multiple of the base sizes, for figures far below the reference.
|
|
1356
1460
|
*
|
|
1357
|
-
*
|
|
1358
|
-
*
|
|
1359
|
-
*
|
|
1360
|
-
*
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
*/
|
|
1364
|
-
/** The figure edge, in pixels, that gets the unit it always had. */
|
|
1365
|
-
const REFERENCE_EDGE = 1200;
|
|
1366
|
-
/** Smallest multiple of the base sizes, for figures far below the reference. */
|
|
1367
|
-
const MIN_UNIT = .75;
|
|
1461
|
+
* Low enough that a matplotlib SVG — which measures itself in points, ~283 wide for
|
|
1462
|
+
* the same figure a PNG exports at 1179px — is still scaled down rather than propped
|
|
1463
|
+
* up to the old floor. The floor is a guard against division, not a design choice:
|
|
1464
|
+
* a figure below ~240px is one nothing sensible is drawn on.
|
|
1465
|
+
*/
|
|
1466
|
+
const MIN_UNIT = .2;
|
|
1368
1467
|
/**
|
|
1369
1468
|
* The size everything drawn on a figure is multiplied by.
|
|
1370
1469
|
*
|
|
@@ -1378,7 +1477,7 @@ window.__ModuleLoader__.load({
|
|
|
1378
1477
|
function figureUnit(width, height) {
|
|
1379
1478
|
const edge = Math.max(width, height);
|
|
1380
1479
|
if (!Number.isFinite(edge) || edge <= 0) return 1;
|
|
1381
|
-
return Math.min(
|
|
1480
|
+
return Math.min(10, Math.max(MIN_UNIT, edge / 800));
|
|
1382
1481
|
}
|
|
1383
1482
|
//#endregion
|
|
1384
1483
|
//#region src/client/Canvas.tsx
|
|
@@ -1409,7 +1508,8 @@ window.__ModuleLoader__.load({
|
|
|
1409
1508
|
/** Minimum distance between recorded freehand samples, in figure pixels. */
|
|
1410
1509
|
const PEN_SAMPLE_STEP = 3;
|
|
1411
1510
|
/** Reach of a handle's hit area, in figure pixels: generous enough to grab. */
|
|
1412
|
-
|
|
1511
|
+
/** How far from a handle's centre a press still counts, as a multiple of its side. */
|
|
1512
|
+
const HANDLE_REACH = 2.2;
|
|
1413
1513
|
/** Monotonic id source for marks created in this session. */
|
|
1414
1514
|
let markSeq = 0;
|
|
1415
1515
|
/**
|
|
@@ -1566,10 +1666,20 @@ window.__ModuleLoader__.load({
|
|
|
1566
1666
|
* @param event - the pointer event.
|
|
1567
1667
|
* @returns true when a handle took the press.
|
|
1568
1668
|
*/
|
|
1669
|
+
/**
|
|
1670
|
+
* The control frame's handle size in figure pixels.
|
|
1671
|
+
*
|
|
1672
|
+
* The frame is drawn in the same svg as the marks, which is in figure pixels, and
|
|
1673
|
+
* the whole paper is then scaled by CSS. A handle has to be a fixed number of screen
|
|
1674
|
+
* pixels to be hittable, so its figure size is that target divided by the scale.
|
|
1675
|
+
*/
|
|
1676
|
+
const grip = 7 / Math.max(.05, props.scale ?? 1);
|
|
1677
|
+
/** A hairline that stays a hairline on screen, whatever the paper is scaled to. */
|
|
1678
|
+
const hairline = 1 / Math.max(.05, props.scale ?? 1);
|
|
1569
1679
|
const beginHandle = (mark, event) => {
|
|
1570
1680
|
if (props.selectedId !== mark.id) return false;
|
|
1571
1681
|
if (mark.kind === "point") return false;
|
|
1572
|
-
const grabbed = handleAt(controlBox(mark), mark.rotation ?? 0, pressedAt.current.value, HANDLE_REACH);
|
|
1682
|
+
const grabbed = handleAt(controlBox(mark), mark.rotation ?? 0, pressedAt.current.value, HANDLE_REACH * grip, grip);
|
|
1573
1683
|
if (grabbed === void 0) return false;
|
|
1574
1684
|
beginGesture(mark, event, grabbed);
|
|
1575
1685
|
return true;
|
|
@@ -1800,26 +1910,26 @@ window.__ModuleLoader__.load({
|
|
|
1800
1910
|
/** The resize and rotation handles of the selected mark. */
|
|
1801
1911
|
const renderHandles = (mark) => {
|
|
1802
1912
|
const frame = controlBox(mark);
|
|
1803
|
-
const half =
|
|
1804
|
-
const handles = handlesFor(frame, mark.rotation ?? 0);
|
|
1805
|
-
const tether = tetherFor(frame, mark.rotation ?? 0);
|
|
1913
|
+
const half = grip / 2;
|
|
1914
|
+
const handles = handlesFor(frame, mark.rotation ?? 0, grip);
|
|
1915
|
+
const tether = tetherFor(frame, mark.rotation ?? 0, grip);
|
|
1806
1916
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
|
|
1807
1917
|
x1: tether.from[0],
|
|
1808
1918
|
y1: tether.from[1],
|
|
1809
1919
|
x2: tether.to[0],
|
|
1810
1920
|
y2: tether.to[1],
|
|
1811
1921
|
stroke: "#4c8dff",
|
|
1812
|
-
strokeWidth:
|
|
1922
|
+
strokeWidth: hairline
|
|
1813
1923
|
}), handles.map((handle) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("g", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
1814
1924
|
"data-da-handle": handle.id,
|
|
1815
1925
|
x: handle.point[0] - half,
|
|
1816
1926
|
y: handle.point[1] - half,
|
|
1817
|
-
width:
|
|
1818
|
-
height:
|
|
1819
|
-
rx: handle.id === "rotate" ?
|
|
1927
|
+
width: grip,
|
|
1928
|
+
height: grip,
|
|
1929
|
+
rx: handle.id === "rotate" ? grip / 2 : grip / 7,
|
|
1820
1930
|
fill: "#ffffff",
|
|
1821
1931
|
stroke: "#4c8dff",
|
|
1822
|
-
strokeWidth: 1.5,
|
|
1932
|
+
strokeWidth: 1.5 * hairline,
|
|
1823
1933
|
style: {
|
|
1824
1934
|
pointerEvents: "all",
|
|
1825
1935
|
cursor: cursorFor(handle.id)
|
|
@@ -1867,7 +1977,7 @@ window.__ModuleLoader__.load({
|
|
|
1867
1977
|
x: centre[0],
|
|
1868
1978
|
y: centre[1],
|
|
1869
1979
|
fontFamily: PIN_FONT_FAMILY,
|
|
1870
|
-
fontSize:
|
|
1980
|
+
fontSize: pinFontSizeOf(mark, unit),
|
|
1871
1981
|
fontWeight: 700,
|
|
1872
1982
|
fill: PIN_NUMBER_COLOR,
|
|
1873
1983
|
textAnchor: "middle",
|
|
@@ -1888,16 +1998,17 @@ window.__ModuleLoader__.load({
|
|
|
1888
1998
|
const box = pointMarkBox(shown, unit);
|
|
1889
1999
|
/* v8 ignore next -- a point mark carries exactly the one coordinate it was placed at. */
|
|
1890
2000
|
if (box === void 0) return null;
|
|
2001
|
+
const inset = 3 * hairline;
|
|
1891
2002
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
1892
|
-
x: box.x -
|
|
1893
|
-
y: box.y -
|
|
1894
|
-
width: box.width +
|
|
1895
|
-
height: box.height +
|
|
1896
|
-
rx:
|
|
2003
|
+
x: box.x - inset,
|
|
2004
|
+
y: box.y - inset,
|
|
2005
|
+
width: box.width + 2 * inset,
|
|
2006
|
+
height: box.height + 2 * inset,
|
|
2007
|
+
rx: inset,
|
|
1897
2008
|
fill: "none",
|
|
1898
2009
|
stroke: "#4c8dff",
|
|
1899
|
-
strokeWidth:
|
|
1900
|
-
strokeDasharray: `${3 *
|
|
2010
|
+
strokeWidth: hairline,
|
|
2011
|
+
strokeDasharray: `${3 * hairline} ${2 * hairline}`,
|
|
1901
2012
|
style: { pointerEvents: "none" }
|
|
1902
2013
|
});
|
|
1903
2014
|
};
|
|
@@ -1984,10 +2095,6 @@ window.__ModuleLoader__.load({
|
|
|
1984
2095
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
1985
2096
|
className: "da-overlay",
|
|
1986
2097
|
"data-da-overlay": "",
|
|
1987
|
-
style: {
|
|
1988
|
-
left: originX,
|
|
1989
|
-
top: originY
|
|
1990
|
-
},
|
|
1991
2098
|
width,
|
|
1992
2099
|
height,
|
|
1993
2100
|
viewBox: `0 0 ${width} ${height}`,
|
|
@@ -2267,14 +2374,13 @@ window.__ModuleLoader__.load({
|
|
|
2267
2374
|
const pages = annotatedPages(document);
|
|
2268
2375
|
const marked = pages.length === 0 ? "" : zh ? `其中 ${pages.length} 页有标注,` : `${pages.length} pages marked, `;
|
|
2269
2376
|
const kind = rendered ? zh ? "【HTML 标注】" : "[HTML annotations]" : zh ? "【附图标注】" : "[figure annotations]";
|
|
2270
|
-
const
|
|
2271
|
-
const header = zh ? `${kind}${figure.path}(${scope === "" ? "" : `${scope},`}${size}${marked}标注 ${marks.length} 处,${hash})` : `${kind} ${figure.path} (${scope === "" ? "" : `${scope}, `}${size}${marked}${marks.length} marks, ${hash})`;
|
|
2377
|
+
const header = zh ? `${kind}${figure.path}(${scope === "" ? "" : `${scope},`}${size}${marked}标注 ${marks.length} 处)` : `${kind} ${figure.path} (${scope === "" ? "" : `${scope}, `}${size}${marked}${marks.length} marks)`;
|
|
2272
2378
|
const paged = figure.pageCount !== void 0;
|
|
2273
2379
|
const pictured = paths.reviewPath !== null;
|
|
2274
2380
|
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."];
|
|
2275
2381
|
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."] : [];
|
|
2276
2382
|
const summary = document.summary !== void 0 && document.summary.trim() !== "" ? [zh ? `这张图是:${document.summary.trim()}` : `the figure shows: ${document.summary.trim()}`, ""] : [];
|
|
2277
|
-
const
|
|
2383
|
+
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.";
|
|
2278
2384
|
return [
|
|
2279
2385
|
header,
|
|
2280
2386
|
...attachment,
|
|
@@ -2283,7 +2389,7 @@ window.__ModuleLoader__.load({
|
|
|
2283
2389
|
...summary,
|
|
2284
2390
|
...describeMarks(document, locale),
|
|
2285
2391
|
"",
|
|
2286
|
-
|
|
2392
|
+
closer
|
|
2287
2393
|
].join("\n");
|
|
2288
2394
|
}
|
|
2289
2395
|
/**
|
|
@@ -2631,7 +2737,7 @@ window.__ModuleLoader__.load({
|
|
|
2631
2737
|
const centre = mark.kind === "point" ? pinNumberCentre(mark, unit) : void 0;
|
|
2632
2738
|
if (pin !== void 0 && centre !== void 0) {
|
|
2633
2739
|
parts.push(`<path d="${pin}" fill="${mark.color}" stroke="#ffffff" stroke-width="${2 * unit}" stroke-linejoin="round"/>`);
|
|
2634
|
-
if (number !== void 0) parts.push(`<text x="${centre[0]}" y="${centre[1]}" font-family="${PIN_FONT_FAMILY}" font-size="${
|
|
2740
|
+
if (number !== void 0) parts.push(`<text x="${centre[0]}" y="${centre[1]}" font-family="${PIN_FONT_FAMILY}" font-size="${pinFontSizeOf(mark, unit)}" font-weight="700" fill="${PIN_NUMBER_COLOR}" text-anchor="middle" dominant-baseline="central">${number}</text>`);
|
|
2635
2741
|
}
|
|
2636
2742
|
return parts.join("");
|
|
2637
2743
|
}
|
|
@@ -2670,7 +2776,25 @@ window.__ModuleLoader__.load({
|
|
|
2670
2776
|
const unit = figureUnit(layer.width, layer.height);
|
|
2671
2777
|
const paper = plotBounds(layer.width, layer.height, marks, unit);
|
|
2672
2778
|
const shift = `translate(${-paper.x} ${-paper.y})`;
|
|
2673
|
-
|
|
2779
|
+
const edge = paper.x !== 0 || paper.y !== 0 || paper.width !== layer.width || paper.height !== layer.height ? figureEdge(layer.width, layer.height, unit) : "";
|
|
2780
|
+
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>";
|
|
2781
|
+
}
|
|
2782
|
+
/** Round a stroke measurement to a tenth, as the path data does. */
|
|
2783
|
+
function hair9(value) {
|
|
2784
|
+
return (Math.round(value * 10) / 10).toString();
|
|
2785
|
+
}
|
|
2786
|
+
/**
|
|
2787
|
+
* The rule that marks where the figure ends and the grown paper begins.
|
|
2788
|
+
*
|
|
2789
|
+
* Drawn only when the paper grew, and drawn grey and dashed: it is a note about the
|
|
2790
|
+
* picture, not part of it, so it must not read as a mark the reader made.
|
|
2791
|
+
* @param width - the figure's width, in figure pixels.
|
|
2792
|
+
* @param height - the figure's height, in figure pixels.
|
|
2793
|
+
* @param unit - the figure's size multiple, so the rule is sized like the marks.
|
|
2794
|
+
* @returns an SVG rect, or an empty string when there is nothing to outline.
|
|
2795
|
+
*/
|
|
2796
|
+
function figureEdge(width, height, unit) {
|
|
2797
|
+
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)}"/>`;
|
|
2674
2798
|
}
|
|
2675
2799
|
/**
|
|
2676
2800
|
* The size of the picture {@link composeReviewSvg} builds.
|
|
@@ -3239,10 +3363,27 @@ window.__ModuleLoader__.load({
|
|
|
3239
3363
|
const STYLE_MARKER = "data-dsh-figmark";
|
|
3240
3364
|
/** The stylesheet text. */
|
|
3241
3365
|
const STYLES = `
|
|
3366
|
+
/* An accent of the plugin's own.
|
|
3367
|
+
The shell's --dsw-alias-brand-primary is not a brand colour in this theme — it
|
|
3368
|
+
resolves to the *ink* (near-black on light, near-white on dark), because the shell
|
|
3369
|
+
uses it for filled buttons in monochrome. Reading it as "the blue" turned this
|
|
3370
|
+
panel's sliders, filled buttons and selection rings into ink: a black knob on a
|
|
3371
|
+
white rail, and a wedge that goes solid black at the heaviest setting. The alias
|
|
3372
|
+
stays as the fallback so a shell that really does define a brand colour still wins,
|
|
3373
|
+
but the accent is named here so every rule reads one variable instead of quietly
|
|
3374
|
+
inheriting whatever "primary" means that week.
|
|
3375
|
+
The dark block re-states it, because a blue tuned for white paper is too dark on a
|
|
3376
|
+
near-black panel. */
|
|
3242
3377
|
/* The panel follows the shell's palette rather than carrying one of its own: every
|
|
3243
3378
|
colour below is an alias the theme owner resolves, with the inherited value as the
|
|
3244
3379
|
fallback so a shell that defines nothing still renders something legible. */
|
|
3245
|
-
.da-root{
|
|
3380
|
+
.da-root{--da-accent:#176df2;--da-accent-soft:rgba(23,109,242,.14);
|
|
3381
|
+
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)}
|
|
3382
|
+
/* The shell marks its dark palette on <body> rather than through prefers-color-scheme:
|
|
3383
|
+
a reader can pick either palette whatever the OS is set to, so following the shell's
|
|
3384
|
+
own switch is what keeps the accent readable in both. A blue tuned for white paper
|
|
3385
|
+
is too dark on a near-black panel. */
|
|
3386
|
+
body[data-ds-dark-theme] .da-root{--da-accent:#4c8dff;--da-accent-soft:rgba(76,141,255,.22)}
|
|
3246
3387
|
|
|
3247
3388
|
/* Every small picture in the panel is one stroke set, declared once: without this
|
|
3248
3389
|
an svg with no fill of its own falls back to a solid black shape, which is what
|
|
@@ -3260,12 +3401,20 @@ window.__ModuleLoader__.load({
|
|
|
3260
3401
|
box-shadow:0 8px 26px rgba(10,14,24,.3),0 0 0 1px rgba(255,255,255,.08)}
|
|
3261
3402
|
/* While it is being dragged the pointer is the grip, whatever it passes over. */
|
|
3262
3403
|
.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)}
|
|
3263
|
-
.da-palette[data-dragging="true"] *{cursor:
|
|
3264
|
-
/* The handle: a
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
.
|
|
3268
|
-
.da-palette-grip
|
|
3404
|
+
.da-palette[data-dragging="true"] *{cursor:move !important}
|
|
3405
|
+
/* The handle: a block of six dots, the one part of a strip that only drags. The block
|
|
3406
|
+
is turned to suit the strip — wider than tall on the bar, taller than wide on the
|
|
3407
|
+
palette — because a grip whose proportions fight its strip looks borrowed from the
|
|
3408
|
+
other one. */
|
|
3409
|
+
.da-palette-grip{display:flex;align-items:center;justify-content:center;width:26px;height:17px;
|
|
3410
|
+
border-radius:5px;cursor:move;color:rgba(255,255,255,.55);touch-action:none;flex:0 0 auto}
|
|
3411
|
+
.da-palette-grip:hover{color:rgba(255,255,255,.92);background:rgba(255,255,255,.1)}
|
|
3412
|
+
/* The dots' box follows the block: three across by two down needs width, two across
|
|
3413
|
+
by three down needs height. Sized per block rather than once, so the svg is not
|
|
3414
|
+
letterboxed inside its own handle. */
|
|
3415
|
+
.da-grip-dots{width:12px;height:8px;fill:currentColor;display:block}
|
|
3416
|
+
.da-bar .da-palette-grip{width:16px;height:18px}
|
|
3417
|
+
.da-bar .da-grip-dots{width:8px;height:12px}
|
|
3269
3418
|
/* A tile is a round button holding one white glyph. No word under it: six glyphs in
|
|
3270
3419
|
a column are read at a glance, and the word is still there as the tooltip and as
|
|
3271
3420
|
the accessible name. */
|
|
@@ -3287,38 +3436,42 @@ window.__ModuleLoader__.load({
|
|
|
3287
3436
|
drawing. The drawn parts never take the pointer, or grabbing the knob would grab
|
|
3288
3437
|
the knob instead of the slider. */
|
|
3289
3438
|
.da-weight{position:absolute;z-index:7;left:6px;top:50%;transform:translateY(-50%);
|
|
3439
|
+
--da-track-inset:10px;--da-knob-size:11px;
|
|
3290
3440
|
display:flex;align-items:center;justify-content:center;width:7px;height:200px;
|
|
3291
3441
|
border-radius:999px;background:var(--dsw-alias-border-l2,rgba(127,127,127,.22));
|
|
3292
3442
|
cursor:ns-resize;transition:width .16s ease,background .16s ease,box-shadow .16s ease}
|
|
3293
3443
|
.da-weight:hover,.da-weight:focus-within{width:30px;background:var(--dsw-alias-bg-layer-2,#fff);
|
|
3294
3444
|
box-shadow:0 5px 18px rgba(20,40,80,.16),0 0 0 0.5px var(--dsw-alias-border-l2,currentColor)}
|
|
3295
|
-
/* The
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3445
|
+
/* The track is an svg in the body: a hairline at the bottom that opens into a rounded
|
|
3446
|
+
shoulder at the top. It is not a clip-path polygon because a polygon has no rounded
|
|
3447
|
+
ends — the corners were what made the wedge read as a cut-out blade rather than a
|
|
3448
|
+
part of the panel. preserveAspectRatio="none" stretches one 18x100 shape over the
|
|
3449
|
+
rail's height, so the shoulders stay smooth at any length.
|
|
3450
|
+
The height is written out rather than expressed as top+bottom: an svg is a replaced
|
|
3451
|
+
element: with a viewBox it takes its intrinsic height for its width and ignores the
|
|
3452
|
+
bottom offset entirely. The track was therefore a fixed 100px tall while the knob
|
|
3453
|
+
travelled the whole 200px rail, which is why the two could never line up. */
|
|
3454
|
+
.da-weight-svg{position:absolute;left:50%;top:var(--da-track-inset);height:calc(100% - 2 * var(--da-track-inset));
|
|
3455
|
+
width:18px;transform:translateX(-50%);opacity:0;pointer-events:none;
|
|
3303
3456
|
transition:opacity .16s ease}
|
|
3304
|
-
.da-weight:hover
|
|
3305
|
-
/* The part already chosen, drawn from the bottom up in the brand colour. */
|
|
3306
|
-
.da-weight-fill{position:absolute;left:50%;bottom:14px;width:11px;transform:translateX(-50%);
|
|
3307
|
-
opacity:0;pointer-events:none;transition:opacity .16s ease;
|
|
3308
|
-
background:var(--dsw-alias-brand-primary,#176df2);
|
|
3309
|
-
clip-path:polygon(calc(50% - 0.5px) 100%,calc(50% + 0.5px) 100%,
|
|
3310
|
-
calc(50% + 5.5px) 0,calc(50% - 5.5px) 0)}
|
|
3311
|
-
.da-weight:hover .da-weight-fill,.da-weight:focus-within .da-weight-fill{opacity:1}
|
|
3457
|
+
.da-weight:hover .da-weight-svg,.da-weight:focus-within .da-weight-svg{opacity:1}
|
|
3312
3458
|
/* The knob sits on the value: the only part that shows at rest, so the rail reads as
|
|
3313
3459
|
a slider waiting to be touched rather than a bare rule. */
|
|
3314
|
-
|
|
3315
|
-
|
|
3460
|
+
/* The blue: a bar of the same width as the groove, its height the one number the
|
|
3461
|
+
knob also uses, so its top edge is the knob's centre at every weight. */
|
|
3462
|
+
.da-weight-fill{position:absolute;left:50%;bottom:var(--da-track-inset);width:5px;
|
|
3463
|
+
transform:translateX(-50%);border-radius:999px;background:var(--da-accent,#176df2);
|
|
3464
|
+
opacity:0;pointer-events:none;transition:opacity .16s ease}
|
|
3465
|
+
.da-weight:hover .da-weight-fill,.da-weight:focus-within .da-weight-fill{opacity:1}
|
|
3466
|
+
.da-weight-knob{position:absolute;left:50%;width:var(--da-knob-size,11px);height:var(--da-knob-size,11px);
|
|
3467
|
+
border-radius:50%;transform:translateX(-50%);pointer-events:none;
|
|
3316
3468
|
background:var(--dsw-alias-bg-layer-2,#fff);
|
|
3317
|
-
box-shadow:0 1px 4px rgba(20,40,80,.
|
|
3469
|
+
box-shadow:0 1px 4px rgba(20,40,80,.28),0 0 0 1px var(--dsw-alias-border-l3,currentColor);
|
|
3318
3470
|
transition:width .16s ease,height .16s ease,background .16s ease}
|
|
3471
|
+
.da-weight:hover,.da-weight:focus-within{--da-knob-size:15px}
|
|
3319
3472
|
.da-weight:hover .da-weight-knob,.da-weight:focus-within .da-weight-knob{
|
|
3320
|
-
|
|
3321
|
-
box-shadow:0 2px 7px rgba(20,40,80,.
|
|
3473
|
+
background:var(--da-accent,#176df2);
|
|
3474
|
+
box-shadow:0 2px 7px rgba(20,40,80,.3)}
|
|
3322
3475
|
/* The browser's own range, rotated upright and made transparent. It spans the whole
|
|
3323
3476
|
rail so a press anywhere on it lands on the slider — including on the knob, which
|
|
3324
3477
|
is why the knob is pointer-transparent. */
|
|
@@ -3339,7 +3492,7 @@ window.__ModuleLoader__.load({
|
|
|
3339
3492
|
it centred while untouched must not also apply — with both set it stretches. */
|
|
3340
3493
|
.da-bar[data-parked="true"]{bottom:auto}
|
|
3341
3494
|
.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)}
|
|
3342
|
-
.da-bar[data-dragging="true"] *{cursor:
|
|
3495
|
+
.da-bar[data-dragging="true"] *{cursor:move !important}
|
|
3343
3496
|
/* The bar's buttons are white glyphs on the dark strip. The one filled control is
|
|
3344
3497
|
still the way out. */
|
|
3345
3498
|
.da-bar .da-dock-btn{color:#fff}
|
|
@@ -3380,8 +3533,8 @@ window.__ModuleLoader__.load({
|
|
|
3380
3533
|
.da-tool .da-tool-label{font-size:10px;line-height:1;white-space:nowrap;color:var(--dsw-alias-label-secondary,inherit)}
|
|
3381
3534
|
/* The armed tool is the one state in this row the reader must never hunt for: it is
|
|
3382
3535
|
the only tile with a fill, and the only label in the brand colour. */
|
|
3383
|
-
.da-tool[aria-pressed="true"]{border-color:transparent;background:var(--
|
|
3384
|
-
.da-tool[aria-pressed="true"] .da-tool-label{color:var(--
|
|
3536
|
+
.da-tool[aria-pressed="true"]{border-color:transparent;background:var(--da-accent-soft,rgba(23,109,242,.1));color:var(--da-accent,currentColor)}
|
|
3537
|
+
.da-tool[aria-pressed="true"] .da-tool-label{color:var(--da-accent,currentColor);font-weight:600}
|
|
3385
3538
|
.da-tool[aria-pressed="true"] svg{stroke-width:2.1}
|
|
3386
3539
|
.da-tool:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.08))}
|
|
3387
3540
|
.da-btn-icon{width:30px;padding:0}
|
|
@@ -3392,6 +3545,10 @@ window.__ModuleLoader__.load({
|
|
|
3392
3545
|
.da-stylewrap{position:relative;flex:0 0 auto}
|
|
3393
3546
|
/* The style fold floats above the bar rather than taking a row of it: opened over
|
|
3394
3547
|
the figure, it costs the picture nothing and lands where the eye already is. */
|
|
3548
|
+
/* Opens upwards by default, because the bar lives at the bottom. Parked at the top
|
|
3549
|
+
there is nothing above it to open into, so the fold turns over and hangs below its
|
|
3550
|
+
own button instead — decided by the same measurement the records use. */
|
|
3551
|
+
.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)}
|
|
3395
3552
|
.da-style-row{position:absolute;left:8px;bottom:calc(100% + 6px);z-index:9;
|
|
3396
3553
|
display:flex;align-items:center;gap:8px;flex-wrap:wrap;max-width:calc(100% - 16px);
|
|
3397
3554
|
padding:8px 10px;border:0.5px solid var(--dsw-alias-border-l2,currentColor);border-radius:11px;
|
|
@@ -3406,17 +3563,24 @@ window.__ModuleLoader__.load({
|
|
|
3406
3563
|
/* Anywhere else is "done here": the panel closes on the click that lands outside it
|
|
3407
3564
|
rather than trapping the reader in a popup with one way out. */
|
|
3408
3565
|
.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}
|
|
3409
|
-
.da-swatch[aria-pressed="true"]{box-shadow:0 0 0 2px var(--
|
|
3566
|
+
.da-swatch[aria-pressed="true"]{box-shadow:0 0 0 2px var(--da-accent,currentColor)}
|
|
3410
3567
|
/* --- the figure on its table -------------------------------------------- */
|
|
3411
3568
|
/* A figure is a sheet on a table, not a picture floating in a void: the white paper
|
|
3412
3569
|
is what gives the panel a middle, and the gap around it is the margin the eye
|
|
3413
3570
|
needs to see where the drawing stops. */
|
|
3414
3571
|
.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)}
|
|
3415
|
-
.da-surface{position:relative;flex:0 0 auto;margin:0;transform-origin:top left
|
|
3416
|
-
/* The paper: the figure plus the margin a mark may have needed. It is
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
.
|
|
3572
|
+
.da-surface{position:relative;flex:0 0 auto;margin:0;transform-origin:top left}
|
|
3573
|
+
/* The paper: the figure plus the margin a mark may have needed. It is deliberately
|
|
3574
|
+
left transparent — the white sheet belongs to the figure, so a margin that only
|
|
3575
|
+
exists because a mark reached past the edge reads as board round a card rather than
|
|
3576
|
+
as more of the figure. The exported picture still gets its white fill: that one is
|
|
3577
|
+
looked at by someone who has no board behind it. */
|
|
3578
|
+
.da-paper{background:transparent}
|
|
3579
|
+
/* The figure is the sheet: white, rounded, lifted off the panel. This is the element
|
|
3580
|
+
that carries the paper look, which is why the margin can be see-through without the
|
|
3581
|
+
drawing losing its edge. */
|
|
3582
|
+
.da-figure-frame{background:var(--dsw-alias-bg-base,#fff);border-radius:4px;overflow:hidden;
|
|
3583
|
+
box-shadow:var(--dsw-elevation-prominent,0 1px 3px rgba(0,0,0,.14),0 8px 24px rgba(0,0,0,.08))}
|
|
3420
3584
|
.da-figure{display:block;user-select:none;-webkit-user-drag:none}
|
|
3421
3585
|
/* The table is a thing you can push. The hand only shows under the tools that
|
|
3422
3586
|
leave a drag free to move the view: under a drawing tool the same drag draws. */
|
|
@@ -3449,22 +3613,22 @@ window.__ModuleLoader__.load({
|
|
|
3449
3613
|
width:28px;height:28px;padding:0;border:0;border-radius:50%;background:transparent;
|
|
3450
3614
|
color:inherit;cursor:pointer;flex:0 0 auto}
|
|
3451
3615
|
.da-dock-btn:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12))}
|
|
3452
|
-
.da-dock-btn[aria-pressed="true"]{background:var(--
|
|
3616
|
+
.da-dock-btn[aria-pressed="true"]{background:var(--da-accent-soft,rgba(23,109,242,.12));color:var(--da-accent,#176df2)}
|
|
3453
3617
|
.da-dock-btn svg{width:15px;height:15px}
|
|
3454
3618
|
.da-dock-btn:disabled{opacity:.4;cursor:default}
|
|
3455
3619
|
.da-dock-btn:disabled:hover{background:transparent}
|
|
3456
3620
|
/* The one filled control in the panel is the way out of it. */
|
|
3457
|
-
.da-dock-btn-primary{border-color:var(--
|
|
3458
|
-
.da-dock-btn-primary:hover{opacity:.92;background:var(--
|
|
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)}
|
|
3459
3623
|
/* A dot, not a count: "there is a goal written" is all the bar needs to say about
|
|
3460
3624
|
a field it is not a list of. */
|
|
3461
3625
|
.da-dock-dot{position:absolute;top:3px;right:4px;width:5px;height:5px;border-radius:50%;
|
|
3462
|
-
background:var(--
|
|
3626
|
+
background:var(--da-accent,#176df2)}
|
|
3463
3627
|
.da-dock-sep{width:1px;height:16px;margin:0 3px;background:var(--dsw-alias-border-l3,currentColor);opacity:.6;flex:0 0 auto}
|
|
3464
3628
|
/* The count rides on the list's own button: it is the one thing about the list a
|
|
3465
3629
|
reader wants without opening it. */
|
|
3466
3630
|
.da-dock-count{position:absolute;top:-1px;right:-2px;min-width:14px;height:14px;padding:0 3px;
|
|
3467
|
-
border-radius:7px;background:var(--
|
|
3631
|
+
border-radius:7px;background:var(--da-accent,#176df2);color:#fff;
|
|
3468
3632
|
font-size:9px;font-weight:700;line-height:14px;text-align:center;font-variant-numeric:tabular-nums}
|
|
3469
3633
|
|
|
3470
3634
|
/* --- the floating records ------------------------------------------------ */
|
|
@@ -3476,7 +3640,7 @@ window.__ModuleLoader__.load({
|
|
|
3476
3640
|
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)}
|
|
3477
3641
|
.da-card-title{display:flex;align-items:center;gap:5px;min-width:0;font-size:12px;font-weight:650;
|
|
3478
3642
|
color:var(--dsw-alias-label-primary,inherit)}
|
|
3479
|
-
.da-card-title svg{width:14px;height:14px;flex:0 0 auto;color:var(--
|
|
3643
|
+
.da-card-title svg{width:14px;height:14px;flex:0 0 auto;color:var(--da-accent,#176df2)}
|
|
3480
3644
|
|
|
3481
3645
|
.da-float-head{display:flex;align-items:center;gap:7px}
|
|
3482
3646
|
.da-float-head .da-card-title{flex:1 1 auto;min-width:0}
|
|
@@ -3491,7 +3655,7 @@ window.__ModuleLoader__.load({
|
|
|
3491
3655
|
come from the mark it belongs to. It is a pill rather than a card — no head, no
|
|
3492
3656
|
border between the field and the panel — so writing about a mark costs one line
|
|
3493
3657
|
over the figure instead of a box that covers it. */
|
|
3494
|
-
.da-float-detail{width:300px;gap:0;padding:4px;border-radius:22px;
|
|
3658
|
+
.da-float-detail{width:300px;max-width:calc(100% - 16px);gap:0;padding:4px;border-radius:22px;
|
|
3495
3659
|
flex-direction:row;align-items:flex-end;
|
|
3496
3660
|
box-shadow:0 8px 26px rgba(20,40,80,.2),0 0 0 1px rgba(20,40,80,.04)}
|
|
3497
3661
|
.da-float .da-comment{flex:1 1 auto;min-width:0;box-sizing:border-box;border:0;background:transparent;
|
|
@@ -3515,10 +3679,10 @@ window.__ModuleLoader__.load({
|
|
|
3515
3679
|
color:var(--dsw-alias-label-primary,currentColor)}
|
|
3516
3680
|
.da-comment-send-btn:disabled{opacity:.45;cursor:default}
|
|
3517
3681
|
.da-comment-send-btn svg{width:15px;height:15px}
|
|
3518
|
-
.da-comment-send-primary{background:var(--
|
|
3519
|
-
.da-comment-send-primary:hover{background:var(--
|
|
3682
|
+
.da-comment-send-primary{background:var(--da-accent,#176df2);color:#fff}
|
|
3683
|
+
.da-comment-send-primary:hover{background:var(--da-accent,#176df2);color:#fff;filter:brightness(1.08)}
|
|
3520
3684
|
.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}
|
|
3521
|
-
.da-float textarea:focus{outline:none;border-color:var(--
|
|
3685
|
+
.da-float textarea:focus{outline:none;border-color:var(--da-accent,#176df2)}
|
|
3522
3686
|
.da-btn-count{display:inline-flex;align-items:center;justify-content:center;min-width:16px;height:16px;
|
|
3523
3687
|
padding:0 4px;border-radius:8px;background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.14));
|
|
3524
3688
|
font-size:10px;font-weight:600;font-variant-numeric:tabular-nums}
|
|
@@ -3531,8 +3695,8 @@ window.__ModuleLoader__.load({
|
|
|
3531
3695
|
/* What the selected mark is, in one word: the detail field is disabled until there
|
|
3532
3696
|
is something to describe, and this says what it would describe. */
|
|
3533
3697
|
.da-chip{display:inline-flex;align-items:center;height:16px;padding:0 6px;border-radius:5px;
|
|
3534
|
-
background:var(--
|
|
3535
|
-
color:var(--
|
|
3698
|
+
background:var(--da-accent-soft,rgba(23,109,242,.12));
|
|
3699
|
+
color:var(--da-accent,#176df2);font-size:10px;font-weight:600}
|
|
3536
3700
|
/* The "?" beside a card's name: the long form of what that field is for. */
|
|
3537
3701
|
.da-hintdot{display:inline-flex;align-items:center;justify-content:center;width:13px;height:13px;
|
|
3538
3702
|
border-radius:50%;border:0.5px solid var(--dsw-alias-border-l3,currentColor);
|
|
@@ -3545,7 +3709,7 @@ window.__ModuleLoader__.load({
|
|
|
3545
3709
|
.da-mark{display:flex;align-items:center;gap:7px;padding:5px 6px;border-radius:8px;
|
|
3546
3710
|
border:0.5px solid transparent;min-width:0}
|
|
3547
3711
|
.da-mark:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.08))}
|
|
3548
|
-
.da-mark[data-selected="true"]{border-color:var(--
|
|
3712
|
+
.da-mark[data-selected="true"]{border-color:var(--da-accent,#176df2);
|
|
3549
3713
|
background:var(--dsw-alias-interactive-bg-hover,rgba(23,109,242,.08))}
|
|
3550
3714
|
.da-mark-index{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;
|
|
3551
3715
|
width:20px;height:20px;border-radius:6px;color:#fff;font-size:11px;font-weight:700;
|
|
@@ -3814,6 +3978,39 @@ window.__ModuleLoader__.load({
|
|
|
3814
3978
|
"#2f9e44"
|
|
3815
3979
|
];
|
|
3816
3980
|
/** Tools in toolbar order. */
|
|
3981
|
+
/**
|
|
3982
|
+
* The rail's track, drawn on an 18x100 grid and stretched to whatever height it gets.
|
|
3983
|
+
*
|
|
3984
|
+
* The control's subject is thickness, so the track says which way is heavier: a little
|
|
3985
|
+
* narrower at the bottom, about one part in six. "A little" is the whole note — a
|
|
3986
|
+
* water-drop taper reads as decoration rather than as a gauge, and the reader said so
|
|
3987
|
+
* twice. One cubic per side and no shoulders, so each flank is a single continuous
|
|
3988
|
+
* curve from end to end with nothing for the eye to catch on; the two ends close with
|
|
3989
|
+
* faint arcs so the track looks drawn rather than cut off.
|
|
3990
|
+
*
|
|
3991
|
+
* Stretching it vertically (preserveAspectRatio="none") is what makes the caps need
|
|
3992
|
+
* care: the svg is 18x100 over a box about 180 tall, so one y unit is 1.8 of an x unit.
|
|
3993
|
+
* An `r` that reads as a circle in the viewBox would be a tall ellipse on screen — the
|
|
3994
|
+
* cap's `ry` is therefore set to `rx / 1.8`, which is what puts a round end on it.
|
|
3995
|
+
*/
|
|
3996
|
+
const WEIGHT_TRACK = [
|
|
3997
|
+
"M 6.9 98.9",
|
|
3998
|
+
"A 2.1 1.15 0 0 0 11.1 98.9",
|
|
3999
|
+
"C 11.4 66 11.9 33 12.2 1.1",
|
|
4000
|
+
"A 3.2 1.15 0 0 0 5.8 1.1",
|
|
4001
|
+
"C 6.1 33 6.6 66 6.9 98.9",
|
|
4002
|
+
"Z"
|
|
4003
|
+
].join(" ");
|
|
4004
|
+
/**
|
|
4005
|
+
* How far the blue reaches, as a `height` in the rail.
|
|
4006
|
+
*
|
|
4007
|
+
* The same expression as the knob's `bottom`, because the blue has to end exactly where
|
|
4008
|
+
* the knob's centre is: written once as a function so a change to the travel cannot
|
|
4009
|
+
* leave the fill behind. `inset` keeps both clear of the rail's rounded caps; the extra
|
|
4010
|
+
* half knob at each end keeps the knob's whole circle between the two ends of the bar
|
|
4011
|
+
* rather than hanging half of itself past the top when the pen is at its heaviest.
|
|
4012
|
+
*/
|
|
4013
|
+
const travelHeight = (fraction) => `calc(var(--da-track-inset) + var(--da-knob-size) / 2 + (100% - 2 * var(--da-track-inset) - var(--da-knob-size)) * ${fraction})`;
|
|
3817
4014
|
const TOOLS = [
|
|
3818
4015
|
{
|
|
3819
4016
|
tool: "select",
|
|
@@ -3870,21 +4067,61 @@ window.__ModuleLoader__.load({
|
|
|
3870
4067
|
* with the mouse rather than a mark you leave behind. Drawn rather than written so
|
|
3871
4068
|
* the toolbar stays one row of controls at any locale or font size.
|
|
3872
4069
|
*/
|
|
4070
|
+
/**
|
|
4071
|
+
* The handle's dots: six of them, three by two, turned as the reader asked.
|
|
4072
|
+
*
|
|
4073
|
+
* The palette stands up and takes a grip three columns wide by two rows tall; the bar
|
|
4074
|
+
* lies down and takes one two columns wide by three rows tall. That is the pair the
|
|
4075
|
+
* reader called for, and it is the opposite of what the strips' own proportions would
|
|
4076
|
+
* suggest — noted here so it is not "corrected" back by the next pair of eyes.
|
|
4077
|
+
*
|
|
4078
|
+
* Drawn instead of typed — the braille character this replaces was one glyph at one
|
|
4079
|
+
* size, so the dots could not be sized or spaced, and at 9px they came out a smudge.
|
|
4080
|
+
*/
|
|
4081
|
+
function GripDots({ wide }) {
|
|
4082
|
+
const xs = wide ? [
|
|
4083
|
+
2,
|
|
4084
|
+
6,
|
|
4085
|
+
10
|
|
4086
|
+
] : [2, 6];
|
|
4087
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
4088
|
+
className: "da-grip-dots",
|
|
4089
|
+
viewBox: wide ? "0 0 12 8" : "0 0 8 12",
|
|
4090
|
+
"aria-hidden": "true",
|
|
4091
|
+
focusable: "false",
|
|
4092
|
+
children: (wide ? [2, 6] : [
|
|
4093
|
+
2,
|
|
4094
|
+
6,
|
|
4095
|
+
10
|
|
4096
|
+
]).map((y) => xs.map((x) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
4097
|
+
cx: x,
|
|
4098
|
+
cy: y,
|
|
4099
|
+
r: 1.4
|
|
4100
|
+
}, `${x}-${y}`)))
|
|
4101
|
+
});
|
|
4102
|
+
}
|
|
4103
|
+
/**
|
|
4104
|
+
* The select tool: a cursor, outlined rather than filled.
|
|
4105
|
+
*
|
|
4106
|
+
* Filled, it read as a black blob against the dark chip — the silhouette was there but
|
|
4107
|
+
* the direction it points was not, which is the only thing a cursor has to say. As an
|
|
4108
|
+
* outline it belongs to the same stroke set as the other five glyphs.
|
|
4109
|
+
*
|
|
4110
|
+
* The shape is Tabler's `cursor-text`-sibling `pointer` glyph, taken whole at its own
|
|
4111
|
+
* 24 grid: its body is one closed curve with round joins rather than the seven hard
|
|
4112
|
+
* corners a hand-traced outline has, and at 16px that softness is the whole difference
|
|
4113
|
+
* between a cursor and a shard. Scaled by hand it lost exactly that.
|
|
4114
|
+
*/
|
|
3873
4115
|
function PointerIcon() {
|
|
3874
4116
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
viewBox: "0 0 13 15",
|
|
4117
|
+
className: "da-ico",
|
|
4118
|
+
viewBox: "0 0 24 24",
|
|
3878
4119
|
"aria-hidden": "true",
|
|
3879
4120
|
focusable: "false",
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
stroke: "currentColor",
|
|
3885
|
-
strokeWidth: "0.9",
|
|
3886
|
-
strokeLinejoin: "round"
|
|
3887
|
-
})
|
|
4121
|
+
strokeWidth: "2",
|
|
4122
|
+
strokeLinecap: "round",
|
|
4123
|
+
strokeLinejoin: "round",
|
|
4124
|
+
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" })
|
|
3888
4125
|
});
|
|
3889
4126
|
}
|
|
3890
4127
|
/**
|
|
@@ -4063,12 +4300,31 @@ window.__ModuleLoader__.load({
|
|
|
4063
4300
|
const isVector = mediaType === "image/svg+xml";
|
|
4064
4301
|
const t = (0, react.useCallback)((key) => resolveTranslate(props, key), [props.t, props.localeId]);
|
|
4065
4302
|
const sessionId = props.sessionId ?? parsedAddress?.sessionId;
|
|
4303
|
+
/**
|
|
4304
|
+
* A private prefix for the rail's gradient and clip ids.
|
|
4305
|
+
*
|
|
4306
|
+
* Two bodies can be mounted at once (a PDF lays out one per page), and an id is
|
|
4307
|
+
* document-wide: sharing one would make every rail's fill follow the first rail's
|
|
4308
|
+
* selection. Declared here, with the other hooks, because the ids are read far below
|
|
4309
|
+
* the early returns for a figure that failed to load.
|
|
4310
|
+
*/
|
|
4311
|
+
const trackId = `da-weight-${(0, react.useId)().replaceAll(":", "")}`;
|
|
4066
4312
|
const stageRef = (0, react.useRef)(null);
|
|
4067
4313
|
/** The box a double clicked mark's detail is written in. */
|
|
4068
4314
|
const detailRef = (0, react.useRef)(null);
|
|
4315
|
+
/**
|
|
4316
|
+
* The comment pill around that box.
|
|
4317
|
+
*
|
|
4318
|
+
* Measured rather than the textarea inside it: the field gives up width to the two
|
|
4319
|
+
* hand-off buttons as soon as there is something to send, so a placement computed
|
|
4320
|
+
* from the field's box would put the buttons past the edge of the panel — which is
|
|
4321
|
+
* exactly what a comment pill against the right edge used to do.
|
|
4322
|
+
*/
|
|
4323
|
+
const pillRef = (0, react.useRef)(null);
|
|
4069
4324
|
const barRef = (0, react.useRef)(null);
|
|
4070
4325
|
const figureHostRef = (0, react.useRef)(null);
|
|
4071
4326
|
const surfaceRef = (0, react.useRef)(null);
|
|
4327
|
+
const paperRef = (0, react.useRef)(null);
|
|
4072
4328
|
const figureRef = (0, react.useRef)(null);
|
|
4073
4329
|
/**
|
|
4074
4330
|
* Hand the stage to the owner as its scrollport, and keep it here too.
|
|
@@ -4610,6 +4866,17 @@ window.__ModuleLoader__.load({
|
|
|
4610
4866
|
htmlHeight
|
|
4611
4867
|
]);
|
|
4612
4868
|
/**
|
|
4869
|
+
* Whether an upward fold would come out of the top of the panel.
|
|
4870
|
+
*
|
|
4871
|
+
* The style fold is anchored to its own button, so it climbs — which is right while
|
|
4872
|
+
* the bar sits at the bottom and wrong the moment the reader parks the bar along the
|
|
4873
|
+
* top, where climbing puts the swatches out of sight above the panel. The record
|
|
4874
|
+
* placement already answers this question for the records; the fold reads the same
|
|
4875
|
+
* answer rather than measuring a second time, so the two can never disagree about
|
|
4876
|
+
* where there is room.
|
|
4877
|
+
*/
|
|
4878
|
+
const foldOpensDown = floatsAt !== null && floatsAt.bottom === void 0;
|
|
4879
|
+
/**
|
|
4613
4880
|
* Put a record away when the reader clicks anywhere else.
|
|
4614
4881
|
*
|
|
4615
4882
|
* A record is a fold, not a mode: it has done its job the moment the reader looks
|
|
@@ -4653,7 +4920,7 @@ window.__ModuleLoader__.load({
|
|
|
4653
4920
|
}
|
|
4654
4921
|
const panel = root.getBoundingClientRect();
|
|
4655
4922
|
const measured = host.getBoundingClientRect();
|
|
4656
|
-
const pillSize = measuredSize(
|
|
4923
|
+
const pillSize = measuredSize(pillRef.current, {
|
|
4657
4924
|
width: 300,
|
|
4658
4925
|
height: 48
|
|
4659
4926
|
});
|
|
@@ -4707,9 +4974,9 @@ window.__ModuleLoader__.load({
|
|
|
4707
4974
|
const zoomAt = (0, react.useCallback)((next, clientX, clientY) => {
|
|
4708
4975
|
const stage = stageRef.current;
|
|
4709
4976
|
const surface = surfaceRef.current;
|
|
4710
|
-
const
|
|
4977
|
+
const sheet = paperRef.current;
|
|
4711
4978
|
/* v8 ignore next -- a wheel event only reaches a handler on a mounted stage, and size is what allows one. */
|
|
4712
|
-
if (stage === null || surface === null ||
|
|
4979
|
+
if (stage === null || surface === null || sheet === null || paper === null) return;
|
|
4713
4980
|
const before = liveScale.current;
|
|
4714
4981
|
const reached = clampZoom(next);
|
|
4715
4982
|
if (reached === before) return;
|
|
@@ -4723,19 +4990,22 @@ window.__ModuleLoader__.load({
|
|
|
4723
4990
|
}, {
|
|
4724
4991
|
clientX,
|
|
4725
4992
|
clientY
|
|
4726
|
-
}, before, reached,
|
|
4993
|
+
}, before, reached, paper);
|
|
4727
4994
|
liveScale.current = reached;
|
|
4728
|
-
surface.style.width = `${
|
|
4729
|
-
surface.style.height = `${
|
|
4730
|
-
|
|
4995
|
+
surface.style.width = `${paper.width * reached}px`;
|
|
4996
|
+
surface.style.height = `${paper.height * reached}px`;
|
|
4997
|
+
sheet.style.transform = `scale(${reached})`;
|
|
4731
4998
|
stage.scrollLeft = target.scrollLeft;
|
|
4732
4999
|
stage.scrollTop = target.scrollTop;
|
|
4733
5000
|
clearTimeout(settleTimer.current);
|
|
4734
5001
|
settleTimer.current = setTimeout(() => {
|
|
4735
5002
|
settleTimer.current = void 0;
|
|
5003
|
+
const settled = paperRef.current;
|
|
5004
|
+
/* v8 ignore next -- the sheet is the element the pinch was measured on. */
|
|
5005
|
+
if (settled !== null) settled.style.transform = "";
|
|
4736
5006
|
setZoom(reached);
|
|
4737
5007
|
}, 120);
|
|
4738
|
-
}, [
|
|
5008
|
+
}, [paper]);
|
|
4739
5009
|
(0, react.useEffect)(() => () => {
|
|
4740
5010
|
clearTimeout(settleTimer.current);
|
|
4741
5011
|
}, []);
|
|
@@ -5231,6 +5501,13 @@ window.__ModuleLoader__.load({
|
|
|
5231
5501
|
*/
|
|
5232
5502
|
const shownWeight = selected === null ? WEIGHT_TOOLS.has(tool) ? weight : null : selected.weight ?? 1;
|
|
5233
5503
|
/**
|
|
5504
|
+
* How far up the rail that value sits, from 0 at the lightest to 1 at the heaviest.
|
|
5505
|
+
*
|
|
5506
|
+
* One number for the fill, the knob and the input: computed three times the three
|
|
5507
|
+
* could round differently and the knob would sit a pixel off the fill it belongs to.
|
|
5508
|
+
*/
|
|
5509
|
+
const weightFraction = shownWeight === null ? 0 : (shownWeight - WEIGHT_MIN) / (8 - WEIGHT_MIN);
|
|
5510
|
+
/**
|
|
5234
5511
|
* Change one attribute of the selected mark, or of the next stroke when nothing
|
|
5235
5512
|
* is selected.
|
|
5236
5513
|
*
|
|
@@ -5314,13 +5591,15 @@ window.__ModuleLoader__.load({
|
|
|
5314
5591
|
className: "da-surface",
|
|
5315
5592
|
ref: surfaceRef,
|
|
5316
5593
|
style: surfaceStyle,
|
|
5317
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.
|
|
5594
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5318
5595
|
className: "da-paper",
|
|
5596
|
+
ref: paperRef,
|
|
5319
5597
|
style: paperStyle,
|
|
5320
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.
|
|
5598
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5599
|
+
className: "da-figure-frame",
|
|
5321
5600
|
ref: figureRef,
|
|
5322
5601
|
style: figureStyle,
|
|
5323
|
-
children:
|
|
5602
|
+
children: figure?.kind === "svg" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5324
5603
|
ref: figureHostRef,
|
|
5325
5604
|
dangerouslySetInnerHTML: { __html: figure.markup }
|
|
5326
5605
|
}) : figure?.kind === "raster" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
@@ -5345,36 +5624,37 @@ window.__ModuleLoader__.load({
|
|
|
5345
5624
|
width: figure.width,
|
|
5346
5625
|
height: htmlHeight
|
|
5347
5626
|
}
|
|
5348
|
-
}) : null
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5627
|
+
}) : null
|
|
5628
|
+
}), paper === null || size === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Canvas, {
|
|
5629
|
+
width: paper.width,
|
|
5630
|
+
height: paper.height,
|
|
5631
|
+
origin: [-paper.x, -paper.y],
|
|
5632
|
+
marks,
|
|
5633
|
+
tool,
|
|
5634
|
+
color,
|
|
5635
|
+
weight,
|
|
5636
|
+
scale,
|
|
5637
|
+
containerRef: figureHostRef,
|
|
5638
|
+
anchoring: isVector,
|
|
5639
|
+
anchorAtFigurePoint: figure?.kind === "html" ? anchorAtFigurePoint : void 0,
|
|
5640
|
+
selectedId,
|
|
5641
|
+
onSelect: setSelectedId,
|
|
5642
|
+
onAdd: addMark,
|
|
5643
|
+
onMove: moveMark,
|
|
5644
|
+
onUpdate: updateMark,
|
|
5645
|
+
onRemove: removeMark,
|
|
5646
|
+
onPan: panBy,
|
|
5647
|
+
onPanStart: () => {
|
|
5648
|
+
setPanning(true);
|
|
5649
|
+
},
|
|
5650
|
+
onPanEnd: () => {
|
|
5651
|
+
setPanning(false);
|
|
5652
|
+
},
|
|
5653
|
+
onEdit: editMark,
|
|
5654
|
+
onDrawn: finishDrawing,
|
|
5655
|
+
onLiveEdit: setLiveMark,
|
|
5656
|
+
numbering: props.numbering
|
|
5657
|
+
})]
|
|
5378
5658
|
})
|
|
5379
5659
|
})
|
|
5380
5660
|
}),
|
|
@@ -5518,6 +5798,7 @@ window.__ModuleLoader__.load({
|
|
|
5518
5798
|
className: "da-float da-float-detail",
|
|
5519
5799
|
role: "group",
|
|
5520
5800
|
"aria-label": t("detail"),
|
|
5801
|
+
ref: pillRef,
|
|
5521
5802
|
style: {
|
|
5522
5803
|
left: detailAt.left,
|
|
5523
5804
|
top: detailAt.top
|
|
@@ -5565,22 +5846,52 @@ window.__ModuleLoader__.load({
|
|
|
5565
5846
|
className: "da-weight-input",
|
|
5566
5847
|
"aria-label": t(selected?.kind === "point" || tool === "point" ? "pinSize" : "weight"),
|
|
5567
5848
|
min: WEIGHT_MIN,
|
|
5568
|
-
max:
|
|
5849
|
+
max: 8,
|
|
5569
5850
|
step: .1,
|
|
5570
5851
|
value: shownWeight,
|
|
5571
5852
|
onChange: (event) => {
|
|
5572
5853
|
chooseWeight(Number(event.target.value));
|
|
5573
5854
|
}
|
|
5574
5855
|
}),
|
|
5856
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
5857
|
+
className: "da-weight-svg",
|
|
5858
|
+
viewBox: "0 0 18 100",
|
|
5859
|
+
preserveAspectRatio: "none",
|
|
5860
|
+
"aria-hidden": "true",
|
|
5861
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("defs", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("linearGradient", {
|
|
5862
|
+
id: `${trackId}-shade`,
|
|
5863
|
+
x1: "0",
|
|
5864
|
+
y1: "1",
|
|
5865
|
+
x2: "0",
|
|
5866
|
+
y2: "0",
|
|
5867
|
+
children: [
|
|
5868
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("stop", {
|
|
5869
|
+
offset: "0",
|
|
5870
|
+
stopColor: "rgba(20,40,80,.06)"
|
|
5871
|
+
}),
|
|
5872
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("stop", {
|
|
5873
|
+
offset: ".55",
|
|
5874
|
+
stopColor: "rgba(20,40,80,.13)"
|
|
5875
|
+
}),
|
|
5876
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("stop", {
|
|
5877
|
+
offset: "1",
|
|
5878
|
+
stopColor: "rgba(20,40,80,.24)"
|
|
5879
|
+
})
|
|
5880
|
+
]
|
|
5881
|
+
}) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
5882
|
+
d: WEIGHT_TRACK,
|
|
5883
|
+
fill: `url(#${trackId}-shade)`
|
|
5884
|
+
})]
|
|
5885
|
+
}),
|
|
5575
5886
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5576
5887
|
className: "da-weight-fill",
|
|
5577
5888
|
"aria-hidden": true,
|
|
5578
|
-
style: { height:
|
|
5889
|
+
style: { height: travelHeight(weightFraction) }
|
|
5579
5890
|
}),
|
|
5580
5891
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5581
5892
|
className: "da-weight-knob",
|
|
5582
5893
|
"aria-hidden": true,
|
|
5583
|
-
style: { bottom: `calc(${(
|
|
5894
|
+
style: { bottom: `calc(${travelHeight(weightFraction)} - var(--da-knob-size) / 2)` }
|
|
5584
5895
|
})
|
|
5585
5896
|
]
|
|
5586
5897
|
}),
|
|
@@ -5620,11 +5931,7 @@ window.__ModuleLoader__.load({
|
|
|
5620
5931
|
dragFrom.current = null;
|
|
5621
5932
|
setDragging(false);
|
|
5622
5933
|
},
|
|
5623
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
5624
|
-
className: "da-palette-grip-dots",
|
|
5625
|
-
"aria-hidden": true,
|
|
5626
|
-
children: "⠿"
|
|
5627
|
-
})
|
|
5934
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GripDots, { wide: true })
|
|
5628
5935
|
}), TOOLS.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5629
5936
|
type: "button",
|
|
5630
5937
|
className: "da-btn da-tool",
|
|
@@ -5678,11 +5985,7 @@ window.__ModuleLoader__.load({
|
|
|
5678
5985
|
barFrom.current = null;
|
|
5679
5986
|
setBarDragging(false);
|
|
5680
5987
|
},
|
|
5681
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
5682
|
-
className: "da-palette-grip-dots",
|
|
5683
|
-
"aria-hidden": true,
|
|
5684
|
-
children: "⠿"
|
|
5685
|
-
})
|
|
5988
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GripDots, { wide: false })
|
|
5686
5989
|
}),
|
|
5687
5990
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5688
5991
|
className: "da-bar-tools",
|
|
@@ -5709,6 +6012,7 @@ window.__ModuleLoader__.load({
|
|
|
5709
6012
|
className: "da-style-row",
|
|
5710
6013
|
role: "group",
|
|
5711
6014
|
"aria-label": t("style"),
|
|
6015
|
+
"data-side": foldOpensDown ? "below" : "above",
|
|
5712
6016
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5713
6017
|
className: "da-style-label",
|
|
5714
6018
|
children: t("styleColor")
|
package/lib/index.js
CHANGED
|
@@ -21,9 +21,9 @@ const GUIDANCE = [
|
|
|
21
21
|
"",
|
|
22
22
|
"用户可能在文档预览里直接标注(箭头/框选/椭圆/手绘/单点),发送给你的消息以【附图标注】开头,正文就是**逐条标注清单**,不需要再去读任何附加文件(标注文件是投递用的临时件,发出即删)。收到时按下列纪律处理:",
|
|
23
23
|
"",
|
|
24
|
-
"1. 逐条读消息正文的清单:每条形如 `① 箭头 (10,10) → (40,30)
|
|
24
|
+
"1. 逐条读消息正文的清单:每条形如 `① 箭头 (10,10) → (40,30),批注:标号指错了`。括号里是图面像素坐标,原点在左上角;框选与圈选给的是**左上角-右下角**,手绘线给的是它画在哪个框里。`批注:` 之后是用户写的**详细说明**——那是这条标注唯一的内容,按它判断意图。有的条目在形状后面紧跟一个圆括号,写着该处落在哪个元素上(SVG 给元素标题/`id`/元素文字,HTML 给 `selector`/元素文字),定位时优先用它。",
|
|
25
25
|
"2. **PDF 是整份文档一份标注**:每条标注都带 `第 N 页`,坐标是那一页自己的(原点在该页左上角)。看每条标注前先确认它的页码。",
|
|
26
|
-
"3.
|
|
26
|
+
"3. 若标注超出原图边界,标注图会把越界的那圈纸也画出来,并用**灰色虚线**框住原图真正的边缘:虚线**里面**是原图,**外面**是你画的标注伸出去的部分——那块不属于图,不要把它当成新增的画布内容。有形状的标注(箭头/框选/椭圆/手绘)会附一张标注图(`<图名>.annotated.png`,PDF 则是一份**带批注的 PDF**,其批注是原生 PDF 注释对象:`/Ink` 手绘、`/Square` 框选、`/Circle` 圈选、`/Line` 箭头),可直接打开核对图面位置。**若消息说「本条标注只有位置点,没有标注图」,那就是全部内容:只有坐标加一句说明,直接按原图定位处理,不要去找不存在的附件。**",
|
|
27
27
|
"4. 逐条处理标注:每条标注都要落到具体的图面改动,并在答复里逐条回应(编号与标注一致)。无法实现的标注要说明原因,不要静默跳过。",
|
|
28
28
|
"5. 修改附图以**标注所指的生成源**为准(绘制脚本、SVG 源或 `generate_patent_figure` 的入参),不要直接涂改导出的位图;PDF 同理,改的是生成该 PDF 的源头,不是那份带批注的副本。未标注的部分保持不动。",
|
|
29
29
|
"6. 改完图后重新渲染并核对:图面尺寸/坐标若变化,旧标注的坐标会失准,此时以 `anchor` 与标注文字为准复核一遍,再回报结果。",
|
|
@@ -270,7 +270,7 @@ const WEIGHT_MIN = .5;
|
|
|
270
270
|
* @returns the value, pulled to the nearest end of the range.
|
|
271
271
|
*/
|
|
272
272
|
function clampWeight(value) {
|
|
273
|
-
return Math.min(
|
|
273
|
+
return Math.min(8, Math.max(WEIGHT_MIN, value));
|
|
274
274
|
}
|
|
275
275
|
//#endregion
|
|
276
276
|
//#region src/host/store.ts
|
package/market/index.json
CHANGED
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-figmark",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"private": false,
|
|
5
|
-
"description": "DSH
|
|
5
|
+
"description": "DSH \u63d2\u4ef6\uff1a\u5728\u53f3\u4fa7\u680f\u9884\u89c8\u91cc\u5bf9\u79d1\u7814\u914d\u56fe\u3001SVG\u3001PDF\u3001HTML \u76f4\u63a5\u5708\u753b\u6279\u6ce8\uff0c\u6807\u6ce8\u5199\u8fdb\u4fa7\u8f66\u6587\u4ef6\u5e76\u53ef\u4e00\u952e\u4ea4\u7ed9\u667a\u80fd\u4f53\u6539\u56fe\u6e90",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"icon": "./icon.svg",
|
|
8
8
|
"keywords": [
|