dsh-codex-subscription 2.1.0-beta.1 → 2.1.0-beta.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/README.en.md +16 -11
- package/README.md +16 -11
- package/lib/client.js +968 -160
- package/lib/index.js +160 -24
- package/package.json +2 -2
package/lib/client.js
CHANGED
|
@@ -386,6 +386,7 @@ window.__ModuleLoader__.load({
|
|
|
386
386
|
"image/original/chunk",
|
|
387
387
|
"sketch/connect",
|
|
388
388
|
"sketch/poll",
|
|
389
|
+
"sketch/claim",
|
|
389
390
|
"sketch/result",
|
|
390
391
|
"sketch/disconnect"
|
|
391
392
|
]);
|
|
@@ -697,7 +698,34 @@ window.__ModuleLoader__.load({
|
|
|
697
698
|
context.lineWidth = stroke.width * (stroke.brush === "pencil" ? .55 : 1) * (stroke.pressure ?? 1);
|
|
698
699
|
context.beginPath();
|
|
699
700
|
const last = stroke.points.at(-1);
|
|
700
|
-
if (stroke.shape === "
|
|
701
|
+
if (stroke.shape === "text") {
|
|
702
|
+
const x = Math.min(first.x, last.x) * size, y = Math.min(first.y, last.y) * height, w = Math.abs(last.x - first.x) * size, h = Math.abs(last.y - first.y) * height;
|
|
703
|
+
const lines = stroke.text.split("\n"), fontSize = Math.min(stroke.width, h / Math.max(1, lines.length) / 1.2);
|
|
704
|
+
context.font = `${fontSize}px system-ui, sans-serif`;
|
|
705
|
+
context.textBaseline = "top";
|
|
706
|
+
lines.forEach((line, i) => context.fillText(line, x, y + i * fontSize * 1.2, w));
|
|
707
|
+
} else if (stroke.shape === "arrow") {
|
|
708
|
+
const x = last.x * size, y = last.y * height, a = Math.atan2(y - first.y * height, x - first.x * size), head = Math.min(Math.hypot(x - first.x * size, y - first.y * height) * .4, Math.max(12, stroke.width * 3));
|
|
709
|
+
context.moveTo(first.x * size, first.y * height);
|
|
710
|
+
context.lineTo(x, y);
|
|
711
|
+
context.stroke();
|
|
712
|
+
context.beginPath();
|
|
713
|
+
context.moveTo(x, y);
|
|
714
|
+
context.lineTo(x - head * Math.cos(a - .5), y - head * Math.sin(a - .5));
|
|
715
|
+
context.lineTo(x - head * Math.cos(a + .5), y - head * Math.sin(a + .5));
|
|
716
|
+
context.closePath();
|
|
717
|
+
context.fill();
|
|
718
|
+
} else if (stroke.shape === "bezier") {
|
|
719
|
+
context.moveTo(first.x * size, first.y * height);
|
|
720
|
+
for (let i = 1; i < stroke.points.length; i += 3) {
|
|
721
|
+
const [a, b, c] = stroke.points.slice(i, i + 3);
|
|
722
|
+
context.bezierCurveTo(a.x * size, a.y * height, b.x * size, b.y * height, c.x * size, c.y * height);
|
|
723
|
+
}
|
|
724
|
+
if (stroke.fill) {
|
|
725
|
+
context.closePath();
|
|
726
|
+
context.fill();
|
|
727
|
+
} else context.stroke();
|
|
728
|
+
} else if (stroke.shape === "line") {
|
|
701
729
|
context.moveTo(first.x * size, first.y * height);
|
|
702
730
|
context.lineTo(last.x * size, last.y * height);
|
|
703
731
|
context.stroke();
|
|
@@ -732,6 +760,46 @@ window.__ModuleLoader__.load({
|
|
|
732
760
|
context.globalCompositeOperation = "source-over";
|
|
733
761
|
context.globalAlpha = 1;
|
|
734
762
|
}
|
|
763
|
+
//#endregion
|
|
764
|
+
//#region src/sketch-curves.js
|
|
765
|
+
const cached = /* @__PURE__ */ new WeakMap();
|
|
766
|
+
const midpoint = (a, b) => ({
|
|
767
|
+
x: (a.x + b.x) / 2,
|
|
768
|
+
y: (a.y + b.y) / 2
|
|
769
|
+
});
|
|
770
|
+
function flattenSketchCurve(stroke, width, height) {
|
|
771
|
+
const previous = cached.get(stroke);
|
|
772
|
+
if (previous?.width === width && previous.height === height) return previous.points;
|
|
773
|
+
const controls = stroke.points.map((p) => ({
|
|
774
|
+
x: p.x * width,
|
|
775
|
+
y: p.y * height
|
|
776
|
+
})), points = [controls[0]];
|
|
777
|
+
const distance = (p, a, b) => {
|
|
778
|
+
const dx = b.x - a.x, dy = b.y - a.y, d = dx * dx + dy * dy;
|
|
779
|
+
const t = d ? Math.max(0, Math.min(1, ((p.x - a.x) * dx + (p.y - a.y) * dy) / d)) : 0;
|
|
780
|
+
return Math.hypot(p.x - a.x - t * dx, p.y - a.y - t * dy);
|
|
781
|
+
};
|
|
782
|
+
const split = (a, b, c, d, depth) => {
|
|
783
|
+
if (depth === 10 || Math.max(distance(b, a, d), distance(c, a, d)) <= .5) {
|
|
784
|
+
points.push(d);
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
const ab = midpoint(a, b), bc = midpoint(b, c), cd = midpoint(c, d), abc = midpoint(ab, bc), bcd = midpoint(bc, cd), m = midpoint(abc, bcd);
|
|
788
|
+
split(a, ab, abc, m, depth + 1);
|
|
789
|
+
split(m, bcd, cd, d, depth + 1);
|
|
790
|
+
};
|
|
791
|
+
for (let i = 1; i < controls.length; i += 3) split(controls[i - 1], controls[i], controls[i + 1], controls[i + 2], 0);
|
|
792
|
+
const normalized = points.map((p) => ({
|
|
793
|
+
x: p.x / width,
|
|
794
|
+
y: p.y / height
|
|
795
|
+
}));
|
|
796
|
+
cached.set(stroke, {
|
|
797
|
+
width,
|
|
798
|
+
height,
|
|
799
|
+
points: normalized
|
|
800
|
+
});
|
|
801
|
+
return normalized;
|
|
802
|
+
}
|
|
735
803
|
const createSketchLayers = () => ({
|
|
736
804
|
active: 1,
|
|
737
805
|
nextId: 2,
|
|
@@ -809,15 +877,15 @@ window.__ModuleLoader__.load({
|
|
|
809
877
|
return Math.hypot(p.x - a.x - k * dx, p.y - a.y - k * dy);
|
|
810
878
|
};
|
|
811
879
|
function strokeHit(stroke, point, radius, width = SKETCH_SIZE, height = width) {
|
|
812
|
-
let points = stroke.points;
|
|
880
|
+
let points = stroke.shape === "bezier" ? flattenSketchCurve(stroke, width, height) : stroke.points;
|
|
813
881
|
if (!points.length) return false;
|
|
814
882
|
const a = points[0], b = points.at(-1);
|
|
815
|
-
if (stroke.fill && stroke.shape === "rectangle" && point.x >= Math.min(a.x, b.x) && point.x <= Math.max(a.x, b.x) && point.y >= Math.min(a.y, b.y) && point.y <= Math.max(a.y, b.y)) return true;
|
|
883
|
+
if ((stroke.shape === "text" || stroke.fill && stroke.shape === "rectangle") && point.x >= Math.min(a.x, b.x) && point.x <= Math.max(a.x, b.x) && point.y >= Math.min(a.y, b.y) && point.y <= Math.max(a.y, b.y)) return true;
|
|
816
884
|
if (stroke.fill && stroke.shape === "circle") {
|
|
817
885
|
const rx = Math.abs(b.x - a.x) / 2, ry = Math.abs(b.y - a.y) / 2;
|
|
818
886
|
if (rx && ry && ((point.x - (a.x + b.x) / 2) / rx) ** 2 + ((point.y - (a.y + b.y) / 2) / ry) ** 2 <= 1) return true;
|
|
819
887
|
}
|
|
820
|
-
if (stroke.shape === "polygon") {
|
|
888
|
+
if (stroke.shape === "polygon" || stroke.shape === "bezier" && stroke.fill) {
|
|
821
889
|
if (stroke.fill) {
|
|
822
890
|
let inside = false;
|
|
823
891
|
for (let i = 0, j = points.length - 1; i < points.length; j = i++) {
|
|
@@ -1345,12 +1413,63 @@ window.__ModuleLoader__.load({
|
|
|
1345
1413
|
]
|
|
1346
1414
|
});
|
|
1347
1415
|
}
|
|
1416
|
+
//#endregion
|
|
1417
|
+
//#region src/sketch-objects.js
|
|
1418
|
+
const objectId = (stroke, index) => stroke.id ?? `legacy-${index}`;
|
|
1419
|
+
const identifyObjects = (doc) => ({
|
|
1420
|
+
...doc,
|
|
1421
|
+
layers: doc.layers.map((layer) => ({
|
|
1422
|
+
...layer,
|
|
1423
|
+
strokes: layer.strokes.map((s, i) => s.id ? s : {
|
|
1424
|
+
...s,
|
|
1425
|
+
id: objectId(s, i)
|
|
1426
|
+
})
|
|
1427
|
+
}))
|
|
1428
|
+
});
|
|
1429
|
+
function objectBounds(stroke) {
|
|
1430
|
+
const xs = stroke.points.map((p) => p.x), ys = stroke.points.map((p) => p.y);
|
|
1431
|
+
return {
|
|
1432
|
+
x: Math.min(...xs),
|
|
1433
|
+
y: Math.min(...ys),
|
|
1434
|
+
width: Math.max(...xs) - Math.min(...xs),
|
|
1435
|
+
height: Math.max(...ys) - Math.min(...ys)
|
|
1436
|
+
};
|
|
1437
|
+
}
|
|
1438
|
+
function transformObject(stroke, { dx = 0, dy = 0, scaleX = 1, scaleY = 1 }) {
|
|
1439
|
+
if (![
|
|
1440
|
+
dx,
|
|
1441
|
+
dy,
|
|
1442
|
+
scaleX,
|
|
1443
|
+
scaleY
|
|
1444
|
+
].every(Number.isFinite) || scaleX <= 0 || scaleY <= 0) throw Error("Invalid object transform");
|
|
1445
|
+
const box = objectBounds(stroke);
|
|
1446
|
+
const points = stroke.points.map((p) => ({
|
|
1447
|
+
x: box.x + (p.x - box.x) * scaleX + dx,
|
|
1448
|
+
y: box.y + (p.y - box.y) * scaleY + dy
|
|
1449
|
+
}));
|
|
1450
|
+
if (points.some((p) => p.x < 0 || p.x > 1 || p.y < 0 || p.y > 1)) throw Error("Object would leave the canvas");
|
|
1451
|
+
return {
|
|
1452
|
+
...stroke,
|
|
1453
|
+
points
|
|
1454
|
+
};
|
|
1455
|
+
}
|
|
1456
|
+
function sketchObjectSummary(doc) {
|
|
1457
|
+
return doc.layers.flatMap((layer) => layer.strokes.map((stroke, i) => ({
|
|
1458
|
+
layer: layer.id,
|
|
1459
|
+
id: objectId(stroke, i),
|
|
1460
|
+
shape: stroke.shape,
|
|
1461
|
+
color: stroke.color,
|
|
1462
|
+
bounds: objectBounds(stroke),
|
|
1463
|
+
...stroke.text ? { text: stroke.text } : {}
|
|
1464
|
+
})));
|
|
1465
|
+
}
|
|
1348
1466
|
const SKETCH_COMMAND_HELP = {
|
|
1349
|
-
coordinates: "Normalized x/y in [0,1]; width is canvas pixels. Read documentId and revision before editing.",
|
|
1350
|
-
shapes: "line: exactly two endpoints; rectangle/circle: exactly two opposite bounding-box corners (circle draws an ellipse within that box); polygon: three or more vertices, closed automatically; pen: ordered path points. fill:true fills rectangle/circle/polygon. Layers and strokes paint in list order, later ones on top. All commands needed for drawing are described here; no source-code search is required.",
|
|
1467
|
+
coordinates: "Assign short meaningful stroke id values for later edits. Text uses two opposite box corners and text content; width is font size in pixels, automatic fitting within the box. Arrow uses two endpoints. Normalized x/y in [0,1]; width is canvas pixels. Read documentId and revision before editing.",
|
|
1468
|
+
shapes: "line: exactly two endpoints; rectangle/circle: exactly two opposite bounding-box corners (circle draws an ellipse within that box); polygon: three or more vertices, closed automatically; pen: ordered path points. bezier: start point, then groups of control1/control2/end; use 4 points for one cubic curve, max 64 segments. Prefer bezier for smooth designed curves instead of many pen samples. fill:true closes and fills the curve. fill:true fills rectangle/circle/polygon. Layers and strokes paint in list order, later ones on top. All commands needed for drawing are described here; no source-code search is required.",
|
|
1351
1469
|
commands: {
|
|
1352
|
-
stroke: "{op:\"stroke\",layer:1,shape:\"pen|line|rectangle|circle|polygon\",color:\"#rrggbb\",width:2,opacity:1,fill:false,points:[{x:0.1,y:0.1},...]}",
|
|
1470
|
+
stroke: "{op:\"stroke\",layer:1,shape:\"pen|line|arrow|text|rectangle|circle|polygon|bezier\",color:\"#rrggbb\",width:2,opacity:1,fill:false,points:[{x:0.1,y:0.1},...]}",
|
|
1353
1471
|
layer: "{op:\"layer\",action:\"add|select|rename|visible|duplicate|up|down|delete|clear\",id:1,value:\"name\"}",
|
|
1472
|
+
object: "{op:\"object\",layer:1,id:\"title\",action:\"update|duplicate|delete\",patch:{color:\"#0088ff\",text:\"Title\"},transform:{dx:0.05,dy:0,scaleX:1,scaleY:1}}. All patch and transform fields optional. Inspect returns object IDs and bounds. Prefer targeted edits over redrawing layers.",
|
|
1354
1473
|
resize: "{op:\"resize\",ratio:\"1:1|4:3|3:4|16:9|9:16\"}"
|
|
1355
1474
|
},
|
|
1356
1475
|
limits: {
|
|
@@ -1363,7 +1482,7 @@ window.__ModuleLoader__.load({
|
|
|
1363
1482
|
const finite = (value, min, max) => typeof value === "number" && Number.isFinite(value) && value >= min && value <= max;
|
|
1364
1483
|
function applySketchCommands(source, commands) {
|
|
1365
1484
|
if (!Array.isArray(commands) || !commands.length || commands.length > 256) throw Error("Expected 1–256 commands");
|
|
1366
|
-
let doc = source;
|
|
1485
|
+
let doc = identifyObjects(source);
|
|
1367
1486
|
for (const command of commands) {
|
|
1368
1487
|
if (!command || typeof command !== "object") throw Error("Invalid command");
|
|
1369
1488
|
if (command.op === "resize") {
|
|
@@ -1387,29 +1506,93 @@ window.__ModuleLoader__.load({
|
|
|
1387
1506
|
doc = next;
|
|
1388
1507
|
continue;
|
|
1389
1508
|
}
|
|
1509
|
+
if (command.op === "object") {
|
|
1510
|
+
const layer = doc.layers.find((l) => l.id === (command.layer ?? doc.active)), index = layer?.strokes.findIndex((s) => s.id === command.id);
|
|
1511
|
+
if (!layer?.visible || index < 0 || index === void 0) throw Error("Object missing or hidden; inspect again");
|
|
1512
|
+
const strokes = layer.strokes.slice(), original = strokes[index];
|
|
1513
|
+
if (command.action === "delete") strokes.splice(index, 1);
|
|
1514
|
+
else if (command.action === "duplicate") strokes.splice(index + 1, 0, {
|
|
1515
|
+
...original,
|
|
1516
|
+
id: crypto.randomUUID(),
|
|
1517
|
+
points: original.points.map((p) => ({ ...p }))
|
|
1518
|
+
});
|
|
1519
|
+
else if (command.action === "update") {
|
|
1520
|
+
const patch = command.patch ?? {};
|
|
1521
|
+
if (Object.keys(patch).some((k) => ![
|
|
1522
|
+
"color",
|
|
1523
|
+
"width",
|
|
1524
|
+
"opacity",
|
|
1525
|
+
"fill",
|
|
1526
|
+
"text",
|
|
1527
|
+
"points"
|
|
1528
|
+
].includes(k))) throw Error("Unsupported object property");
|
|
1529
|
+
const changed = command.transform ? transformObject({
|
|
1530
|
+
...original,
|
|
1531
|
+
...patch
|
|
1532
|
+
}, command.transform) : {
|
|
1533
|
+
...original,
|
|
1534
|
+
...patch
|
|
1535
|
+
};
|
|
1536
|
+
strokes[index] = {
|
|
1537
|
+
...applySketchCommands({
|
|
1538
|
+
...doc,
|
|
1539
|
+
layers: [{
|
|
1540
|
+
...layer,
|
|
1541
|
+
strokes: []
|
|
1542
|
+
}]
|
|
1543
|
+
}, [{
|
|
1544
|
+
...changed,
|
|
1545
|
+
op: "stroke",
|
|
1546
|
+
layer: layer.id
|
|
1547
|
+
}]).layers[0].strokes[0],
|
|
1548
|
+
brush: original.brush ?? "pen",
|
|
1549
|
+
...original.pressure !== void 0 ? { pressure: original.pressure } : {}
|
|
1550
|
+
};
|
|
1551
|
+
} else throw Error("Unknown object action");
|
|
1552
|
+
doc = {
|
|
1553
|
+
...doc,
|
|
1554
|
+
layers: doc.layers.map((l) => l === layer ? {
|
|
1555
|
+
...l,
|
|
1556
|
+
strokes
|
|
1557
|
+
} : l)
|
|
1558
|
+
};
|
|
1559
|
+
continue;
|
|
1560
|
+
}
|
|
1390
1561
|
if (command.op !== "stroke") throw Error("Unknown command");
|
|
1391
|
-
const { points, color, shape = "pen", width = 2, opacity = 1, fill = false } = command;
|
|
1562
|
+
const { points, color, shape = "pen", width = shape === "text" ? 24 : 2, opacity = 1, fill = false } = command;
|
|
1392
1563
|
if (![
|
|
1393
1564
|
"pen",
|
|
1394
1565
|
"line",
|
|
1395
1566
|
"rectangle",
|
|
1396
1567
|
"circle",
|
|
1397
1568
|
"polygon",
|
|
1569
|
+
"bezier",
|
|
1570
|
+
"arrow",
|
|
1571
|
+
"text",
|
|
1398
1572
|
"eraser"
|
|
1399
1573
|
].includes(shape) || !/^#[0-9a-f]{6}$/i.test(color ?? "") || !finite(width, 1, 256) || !finite(opacity, 0, 1) || typeof fill !== "boolean" || !Array.isArray(points) || !points.length || points.length > 2e3 || points.some((p) => !p || !finite(p.x, 0, 1) || !finite(p.y, 0, 1))) throw Error("Invalid stroke");
|
|
1400
1574
|
if ([
|
|
1401
1575
|
"line",
|
|
1576
|
+
"arrow",
|
|
1577
|
+
"text",
|
|
1402
1578
|
"rectangle",
|
|
1403
1579
|
"circle"
|
|
1404
1580
|
].includes(shape) && points.length !== 2 || shape === "polygon" && points.length < 3) throw Error("Invalid shape points");
|
|
1581
|
+
if (shape === "bezier" && (points.length < 4 || points.length > 193 || (points.length - 1) % 3 !== 0)) throw Error("Bezier needs a start point followed by groups of two controls and an endpoint (max 64 segments)");
|
|
1405
1582
|
if (fill && ![
|
|
1406
1583
|
"rectangle",
|
|
1407
1584
|
"circle",
|
|
1408
|
-
"polygon"
|
|
1585
|
+
"polygon",
|
|
1586
|
+
"bezier"
|
|
1409
1587
|
].includes(shape)) throw Error("Fill requires a closed shape");
|
|
1410
1588
|
const layer = doc.layers.find((layer) => layer.id === (command.layer ?? doc.active));
|
|
1411
1589
|
if (!layer?.visible) throw Error("Target layer is missing or hidden");
|
|
1590
|
+
if (shape === "text" && (typeof command.text !== "string" || !command.text.trim() || command.text.length > 500 || points[0].x === points[1].x || points[0].y === points[1].y)) throw Error("Text requires 1–500 characters and a non-empty bounding box");
|
|
1591
|
+
const id = command.id ?? crypto.randomUUID();
|
|
1592
|
+
if (typeof id !== "string" || !id.length || id.length > 100 || layer.strokes.some((s) => s.id === id)) throw Error("Invalid or duplicate object id");
|
|
1412
1593
|
const stroke = {
|
|
1594
|
+
id,
|
|
1595
|
+
...shape === "text" ? { text: command.text } : {},
|
|
1413
1596
|
shape,
|
|
1414
1597
|
color,
|
|
1415
1598
|
width,
|
|
@@ -1434,15 +1617,24 @@ window.__ModuleLoader__.load({
|
|
|
1434
1617
|
}
|
|
1435
1618
|
function createSketchCommandSession(adapter) {
|
|
1436
1619
|
const completed = /* @__PURE__ */ new Map();
|
|
1437
|
-
let pending = false;
|
|
1620
|
+
let pending = false, cachedCharacters = 0;
|
|
1438
1621
|
return async (request) => {
|
|
1439
1622
|
if (!request || typeof request !== "object") throw Error("Invalid sketch request");
|
|
1440
1623
|
if (!adapter.available()) throw Error("Open the sketch board for this session first");
|
|
1441
1624
|
const current = adapter.snapshot();
|
|
1442
|
-
if (request.action === "inspect")
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1625
|
+
if (request.action === "inspect") {
|
|
1626
|
+
const offset = request.offset ?? 0, objects = adapter.objects?.() ?? [];
|
|
1627
|
+
if (!Number.isInteger(offset) || offset < 0) throw Error("offset must be a non-negative integer");
|
|
1628
|
+
return {
|
|
1629
|
+
...current,
|
|
1630
|
+
objects: objects.slice(offset, offset + 50),
|
|
1631
|
+
objectCount: objects.length,
|
|
1632
|
+
...offset + 50 < objects.length ? { nextOffset: offset + 50 } : {},
|
|
1633
|
+
...request.objectId ? { object: adapter.object?.(request.objectId, request.layer) } : {},
|
|
1634
|
+
recentRequests: [...completed.values()].slice(-8).map((entry) => entry.receipt),
|
|
1635
|
+
help: SKETCH_COMMAND_HELP
|
|
1636
|
+
};
|
|
1637
|
+
}
|
|
1446
1638
|
if (request.documentId !== current.documentId) throw Error("Document changed; inspect again");
|
|
1447
1639
|
if (pending || adapter.busy()) throw Error("Sketch is being edited; retry after it settles");
|
|
1448
1640
|
if (request.action === "preview") return {
|
|
@@ -1451,16 +1643,25 @@ window.__ModuleLoader__.load({
|
|
|
1451
1643
|
};
|
|
1452
1644
|
if (!["apply", "save"].includes(request.action)) throw Error("Unknown sketch action");
|
|
1453
1645
|
if (typeof request.requestId !== "string" || !request.requestId.length || request.requestId.length > 100) throw Error("A unique requestId is required");
|
|
1454
|
-
const key = `${current.documentId}:${request.requestId}`, fingerprint = JSON.stringify(
|
|
1646
|
+
const key = `${current.documentId}:${request.requestId}`, fingerprint = JSON.stringify({
|
|
1647
|
+
...request,
|
|
1648
|
+
runId: void 0
|
|
1649
|
+
});
|
|
1455
1650
|
const cached = completed.get(key);
|
|
1456
1651
|
if (cached) {
|
|
1457
1652
|
if (cached.fingerprint !== fingerprint) throw Error("requestId reused with different content");
|
|
1458
1653
|
return cached.result;
|
|
1459
1654
|
}
|
|
1460
1655
|
if (request.revision !== current.revision || adapter.busy()) throw Error("Sketch changed or is being edited; inspect again");
|
|
1656
|
+
let changedObjects;
|
|
1461
1657
|
if (request.action === "apply") {
|
|
1462
|
-
const
|
|
1658
|
+
const before = adapter.document(), next = applySketchCommands(before, request.commands);
|
|
1463
1659
|
adapter.commit(next);
|
|
1660
|
+
const previous = new Map(before.layers.flatMap((l) => l.strokes.map((s) => [`${l.id}:${s.id}`, s])));
|
|
1661
|
+
changedObjects = next.layers.flatMap((l) => l.strokes.filter((s) => previous.get(`${l.id}:${s.id}`) !== s).map((s) => ({
|
|
1662
|
+
layer: l.id,
|
|
1663
|
+
id: s.id
|
|
1664
|
+
})));
|
|
1464
1665
|
} else {
|
|
1465
1666
|
if (request.name !== void 0 && (typeof request.name !== "string" || request.name.length > 60)) throw Error("Invalid draft name");
|
|
1466
1667
|
pending = true;
|
|
@@ -1470,12 +1671,28 @@ window.__ModuleLoader__.load({
|
|
|
1470
1671
|
pending = false;
|
|
1471
1672
|
}
|
|
1472
1673
|
}
|
|
1473
|
-
const result =
|
|
1674
|
+
const result = {
|
|
1675
|
+
...adapter.snapshot(),
|
|
1676
|
+
...changedObjects ? {
|
|
1677
|
+
changedObjects: changedObjects.slice(0, 100),
|
|
1678
|
+
changedObjectCount: changedObjects.length
|
|
1679
|
+
} : {}
|
|
1680
|
+
};
|
|
1474
1681
|
completed.set(key, {
|
|
1475
1682
|
fingerprint,
|
|
1476
|
-
result
|
|
1683
|
+
result,
|
|
1684
|
+
receipt: {
|
|
1685
|
+
requestId: request.requestId,
|
|
1686
|
+
action: request.action,
|
|
1687
|
+
revision: result.revision
|
|
1688
|
+
}
|
|
1477
1689
|
});
|
|
1478
|
-
|
|
1690
|
+
cachedCharacters += fingerprint.length;
|
|
1691
|
+
while (completed.size > 1 && (completed.size > 128 || cachedCharacters > 4e6)) {
|
|
1692
|
+
const oldest = completed.keys().next().value;
|
|
1693
|
+
cachedCharacters -= completed.get(oldest).fingerprint.length;
|
|
1694
|
+
completed.delete(oldest);
|
|
1695
|
+
}
|
|
1479
1696
|
return result;
|
|
1480
1697
|
};
|
|
1481
1698
|
}
|
|
@@ -1669,6 +1886,7 @@ window.__ModuleLoader__.load({
|
|
|
1669
1886
|
}, [open, working]);
|
|
1670
1887
|
useSketchDismiss(open, setOpen, host, [".codexSketchFiles"]);
|
|
1671
1888
|
const run = async (operation) => {
|
|
1889
|
+
if (disabled || working) return;
|
|
1672
1890
|
setWorking(true);
|
|
1673
1891
|
onWorking(true);
|
|
1674
1892
|
try {
|
|
@@ -1724,7 +1942,7 @@ window.__ModuleLoader__.load({
|
|
|
1724
1942
|
className: "codexSketchFileActions",
|
|
1725
1943
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1726
1944
|
type: "button",
|
|
1727
|
-
disabled: working,
|
|
1945
|
+
disabled: disabled || working,
|
|
1728
1946
|
onClick: () => void run(async () => {
|
|
1729
1947
|
await fresh();
|
|
1730
1948
|
setName("");
|
|
@@ -1733,7 +1951,7 @@ window.__ModuleLoader__.load({
|
|
|
1733
1951
|
children: t("sketchNew")
|
|
1734
1952
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1735
1953
|
type: "button",
|
|
1736
|
-
disabled: working,
|
|
1954
|
+
disabled: disabled || working,
|
|
1737
1955
|
onClick: () => input.current.click(),
|
|
1738
1956
|
children: t("sketchImport")
|
|
1739
1957
|
})]
|
|
@@ -1747,7 +1965,7 @@ window.__ModuleLoader__.load({
|
|
|
1747
1965
|
}),
|
|
1748
1966
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1749
1967
|
type: "button",
|
|
1750
|
-
disabled: working,
|
|
1968
|
+
disabled: disabled || working,
|
|
1751
1969
|
onClick: () => void run(async () => {
|
|
1752
1970
|
await save(name);
|
|
1753
1971
|
await refresh();
|
|
@@ -1765,14 +1983,14 @@ window.__ModuleLoader__.load({
|
|
|
1765
1983
|
].map(([value, label]) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1766
1984
|
type: "button",
|
|
1767
1985
|
"aria-pressed": format === value,
|
|
1768
|
-
disabled: working,
|
|
1986
|
+
disabled: disabled || working,
|
|
1769
1987
|
onClick: () => setFormat(value),
|
|
1770
1988
|
children: label
|
|
1771
1989
|
}, value))
|
|
1772
1990
|
}),
|
|
1773
1991
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1774
1992
|
type: "button",
|
|
1775
|
-
disabled: working || !hasContent,
|
|
1993
|
+
disabled: disabled || working || !hasContent,
|
|
1776
1994
|
onClick: () => void run(async () => {
|
|
1777
1995
|
await download(format);
|
|
1778
1996
|
setOpen(false);
|
|
@@ -1785,7 +2003,7 @@ window.__ModuleLoader__.load({
|
|
|
1785
2003
|
className: "codexSketchDraftList",
|
|
1786
2004
|
children: rows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1787
2005
|
type: "button",
|
|
1788
|
-
disabled: working,
|
|
2006
|
+
disabled: disabled || working,
|
|
1789
2007
|
onClick: () => void run(async () => {
|
|
1790
2008
|
await load(row);
|
|
1791
2009
|
setName(row.name);
|
|
@@ -1794,7 +2012,7 @@ window.__ModuleLoader__.load({
|
|
|
1794
2012
|
children: row.name
|
|
1795
2013
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1796
2014
|
type: "button",
|
|
1797
|
-
disabled: working,
|
|
2015
|
+
disabled: disabled || working,
|
|
1798
2016
|
"aria-label": `${t("sketchDeleteDraft")} ${row.name}`,
|
|
1799
2017
|
onClick: () => {
|
|
1800
2018
|
if (remove !== row.id) {
|
|
@@ -1824,6 +2042,9 @@ window.__ModuleLoader__.load({
|
|
|
1824
2042
|
//#region src/workspace-icons.jsx
|
|
1825
2043
|
function WorkspaceIcon({ name, size = 24 }) {
|
|
1826
2044
|
const paths = {
|
|
2045
|
+
select: "M5 3l14 9-7 2-3 7-4-18z",
|
|
2046
|
+
text: "M4 5h16M12 5v15M8 20h8M4 5v3M20 5v3",
|
|
2047
|
+
arrow: "M4 20L20 4M10 4h10v10",
|
|
1827
2048
|
line: "M4 20L20 4",
|
|
1828
2049
|
layers: "M12 3L2 8l10 5 10-5-10-5zM2 12l10 5 10-5M2 16l10 5 10-5",
|
|
1829
2050
|
eye: "M2 12s4-7 10-7 10 7 10 7-4 7-10 7S2 12 2 12zM15 12a3 3 0 11-6 0 3 3 0 016 0",
|
|
@@ -1858,9 +2079,158 @@ window.__ModuleLoader__.load({
|
|
|
1858
2079
|
});
|
|
1859
2080
|
}
|
|
1860
2081
|
//#endregion
|
|
2082
|
+
//#region src/sketch-size-control.jsx
|
|
2083
|
+
function SketchSizeControl({ value, onChange, onStart, onEnd, label, disabled, min = 2, max = 128, mode, modes, onModeChange, suffix = "" }) {
|
|
2084
|
+
const active = (0, react.useRef)(false);
|
|
2085
|
+
const start = () => {
|
|
2086
|
+
if (!active.current) {
|
|
2087
|
+
active.current = true;
|
|
2088
|
+
onStart?.();
|
|
2089
|
+
}
|
|
2090
|
+
};
|
|
2091
|
+
const end = () => {
|
|
2092
|
+
if (active.current) {
|
|
2093
|
+
active.current = false;
|
|
2094
|
+
onEnd?.();
|
|
2095
|
+
}
|
|
2096
|
+
};
|
|
2097
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2098
|
+
className: "codexSketchSizeControl",
|
|
2099
|
+
title: label,
|
|
2100
|
+
children: [
|
|
2101
|
+
modes ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2102
|
+
className: "codexSketchSizeModes",
|
|
2103
|
+
children: modes.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2104
|
+
type: "button",
|
|
2105
|
+
"aria-pressed": mode === item.value,
|
|
2106
|
+
disabled,
|
|
2107
|
+
onClick: () => {
|
|
2108
|
+
end();
|
|
2109
|
+
onModeChange(item.value);
|
|
2110
|
+
},
|
|
2111
|
+
children: item.label
|
|
2112
|
+
}, item.value))
|
|
2113
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label }),
|
|
2114
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2115
|
+
type: "range",
|
|
2116
|
+
"aria-label": label,
|
|
2117
|
+
"aria-orientation": "vertical",
|
|
2118
|
+
min,
|
|
2119
|
+
max,
|
|
2120
|
+
value,
|
|
2121
|
+
disabled,
|
|
2122
|
+
onPointerDown: start,
|
|
2123
|
+
onPointerUp: end,
|
|
2124
|
+
onPointerCancel: end,
|
|
2125
|
+
onBlur: end,
|
|
2126
|
+
onKeyDown: start,
|
|
2127
|
+
onKeyUp: end,
|
|
2128
|
+
onChange: (event) => {
|
|
2129
|
+
start();
|
|
2130
|
+
onChange(Number(event.target.value));
|
|
2131
|
+
}
|
|
2132
|
+
}),
|
|
2133
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("output", { children: [Math.round(value), suffix] })
|
|
2134
|
+
]
|
|
2135
|
+
});
|
|
2136
|
+
}
|
|
2137
|
+
//#endregion
|
|
2138
|
+
//#region src/sketch-agent-run.js
|
|
2139
|
+
function createSketchAgentRun({ execute, open, changed, busy = () => false, previewEnabled = () => false, idleMs = 18e4 }) {
|
|
2140
|
+
let state = "idle", generation = 0, runNumber = 0, pending = false, runId, timer, completed;
|
|
2141
|
+
const update = (next) => {
|
|
2142
|
+
state = next;
|
|
2143
|
+
changed(next);
|
|
2144
|
+
};
|
|
2145
|
+
const clear = () => {
|
|
2146
|
+
clearTimeout(timer);
|
|
2147
|
+
timer = void 0;
|
|
2148
|
+
};
|
|
2149
|
+
const expire = () => {
|
|
2150
|
+
clear();
|
|
2151
|
+
generation++;
|
|
2152
|
+
if (state !== "stopped") update("failed");
|
|
2153
|
+
};
|
|
2154
|
+
return {
|
|
2155
|
+
get state() {
|
|
2156
|
+
return state;
|
|
2157
|
+
},
|
|
2158
|
+
get locked() {
|
|
2159
|
+
return state === "drawing";
|
|
2160
|
+
},
|
|
2161
|
+
stop() {
|
|
2162
|
+
clear();
|
|
2163
|
+
generation++;
|
|
2164
|
+
update("stopped");
|
|
2165
|
+
},
|
|
2166
|
+
resume() {
|
|
2167
|
+
clear();
|
|
2168
|
+
generation++;
|
|
2169
|
+
update("idle");
|
|
2170
|
+
},
|
|
2171
|
+
fail: expire,
|
|
2172
|
+
dispose() {
|
|
2173
|
+
clear();
|
|
2174
|
+
generation++;
|
|
2175
|
+
state = "idle";
|
|
2176
|
+
},
|
|
2177
|
+
async execute(request) {
|
|
2178
|
+
if (!request || typeof request !== "object") throw Error("Invalid sketch request");
|
|
2179
|
+
if (state === "stopped") throw Error("Drawing stopped by the user. Do not retry until they enable drawing again.");
|
|
2180
|
+
if (pending || busy()) throw Error("Sketch is being edited; retry after it settles");
|
|
2181
|
+
if (request.action !== "inspect" && request.runId !== runId) throw Error("Drawing run changed; inspect again");
|
|
2182
|
+
if (state === "finished" && completed?.key === JSON.stringify(request)) return completed.value;
|
|
2183
|
+
if (state !== "drawing" && request.action !== "inspect") throw Error("Start drawing with inspect");
|
|
2184
|
+
clear();
|
|
2185
|
+
const version = generation;
|
|
2186
|
+
pending = true;
|
|
2187
|
+
try {
|
|
2188
|
+
if (state !== "drawing") {
|
|
2189
|
+
runId = `run-${++runNumber}`;
|
|
2190
|
+
completed = void 0;
|
|
2191
|
+
update("drawing");
|
|
2192
|
+
open();
|
|
2193
|
+
}
|
|
2194
|
+
const value = await execute(request.action === "finish" ? {
|
|
2195
|
+
...request,
|
|
2196
|
+
action: "save"
|
|
2197
|
+
} : request);
|
|
2198
|
+
if (version !== generation) throw Error("Drawing interrupted");
|
|
2199
|
+
if (request.action !== "finish") return {
|
|
2200
|
+
...value,
|
|
2201
|
+
runId
|
|
2202
|
+
};
|
|
2203
|
+
const result = previewEnabled() ? await execute({
|
|
2204
|
+
action: "preview",
|
|
2205
|
+
documentId: value.documentId
|
|
2206
|
+
}) : value;
|
|
2207
|
+
if (version !== generation) throw Error("Drawing interrupted");
|
|
2208
|
+
completed = {
|
|
2209
|
+
key: JSON.stringify(request),
|
|
2210
|
+
value: {
|
|
2211
|
+
...result,
|
|
2212
|
+
runId
|
|
2213
|
+
}
|
|
2214
|
+
};
|
|
2215
|
+
update("finished");
|
|
2216
|
+
return completed.value;
|
|
2217
|
+
} catch (error) {
|
|
2218
|
+
if (version === generation) update("failed");
|
|
2219
|
+
throw error;
|
|
2220
|
+
} finally {
|
|
2221
|
+
pending = false;
|
|
2222
|
+
if (state === "drawing") {
|
|
2223
|
+
timer = setTimeout(expire, idleMs);
|
|
2224
|
+
timer.unref?.();
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
};
|
|
2229
|
+
}
|
|
2230
|
+
//#endregion
|
|
1861
2231
|
//#region src/sketch-agent-client.js
|
|
1862
2232
|
function connectSketchAgent(rpc, sessionId, execute, report, pollDelay = () => 350) {
|
|
1863
|
-
let stopped = false, token, timer;
|
|
2233
|
+
let stopped = false, token, timer, attempts = 0;
|
|
1864
2234
|
const call = (endpoint, payload) => rpc.call(CHANNEL, `sketch/${endpoint}`, {
|
|
1865
2235
|
sessionId,
|
|
1866
2236
|
token,
|
|
@@ -1871,9 +2241,14 @@ window.__ModuleLoader__.load({
|
|
|
1871
2241
|
const tasks = await call("poll");
|
|
1872
2242
|
for (const task of tasks) {
|
|
1873
2243
|
if (stopped) break;
|
|
2244
|
+
if (task.cancelled) {
|
|
2245
|
+
report("Sketch operation interrupted; completed strokes are preserved.");
|
|
2246
|
+
continue;
|
|
2247
|
+
}
|
|
1874
2248
|
let value, error;
|
|
1875
2249
|
try {
|
|
1876
|
-
if (task.expiresAt < Date.now()) throw Error("Sketch command expired; inspect before retrying");
|
|
2250
|
+
if (task.expiresAt < Date.now() || !await call("claim", { id: task.id })) throw Error("Sketch command expired or cancelled; inspect before retrying");
|
|
2251
|
+
if (stopped) break;
|
|
1877
2252
|
value = await execute(task.request);
|
|
1878
2253
|
} catch (cause) {
|
|
1879
2254
|
error = cause.message;
|
|
@@ -1890,29 +2265,23 @@ window.__ModuleLoader__.load({
|
|
|
1890
2265
|
}
|
|
1891
2266
|
if (!stopped) timer = setTimeout(poll, pollDelay());
|
|
1892
2267
|
};
|
|
1893
|
-
call("connect").then((value) => {
|
|
2268
|
+
const connect = () => void call("connect").then((value) => {
|
|
1894
2269
|
token = value.token;
|
|
1895
2270
|
if (stopped) call("disconnect").catch(() => {});
|
|
1896
2271
|
else poll();
|
|
1897
2272
|
}, (error) => {
|
|
1898
|
-
if (
|
|
2273
|
+
if (stopped) return;
|
|
2274
|
+
const leaseConflict = /Another board is connected/.test(error.message);
|
|
2275
|
+
if (++attempts < (leaseConflict ? 8 : 3)) timer = setTimeout(connect, Math.min(3e3, 500 * attempts));
|
|
2276
|
+
else report(error.message);
|
|
1899
2277
|
});
|
|
2278
|
+
connect();
|
|
1900
2279
|
return () => {
|
|
1901
2280
|
stopped = true;
|
|
1902
2281
|
clearTimeout(timer);
|
|
1903
2282
|
if (token) call("disconnect").catch(() => {});
|
|
1904
2283
|
};
|
|
1905
2284
|
}
|
|
1906
|
-
async function executeSketchFromAgent(request, { available, open, execute, live = () => true, wait = () => new Promise((resolve) => setTimeout(resolve, 20)) }) {
|
|
1907
|
-
if (!available()) {
|
|
1908
|
-
if (request.action !== "inspect") throw Error("Sketch board closed; inspect before editing");
|
|
1909
|
-
if (!live()) throw Error("Sketch session disconnected");
|
|
1910
|
-
open();
|
|
1911
|
-
for (let i = 0; i < 100 && live() && !available(); i++) await wait();
|
|
1912
|
-
}
|
|
1913
|
-
if (!live() || !available()) throw Error("Sketch board could not open in the current session");
|
|
1914
|
-
return execute(request);
|
|
1915
|
-
}
|
|
1916
2285
|
//#endregion
|
|
1917
2286
|
//#region src/sketch-studio.jsx
|
|
1918
2287
|
const PALETTE = [
|
|
@@ -1924,20 +2293,51 @@ window.__ModuleLoader__.load({
|
|
|
1924
2293
|
"#34c759",
|
|
1925
2294
|
"#0088ff"
|
|
1926
2295
|
];
|
|
1927
|
-
function SketchStudio({ open, agentEnabled, onOpen, onClose, attachSketch, enabled, t, incoming, sessionId, rpc }) {
|
|
2296
|
+
function SketchStudio({ open, agentEnabled, agentPreview, onOpen, onClose, attachSketch, enabled, t, incoming, sessionId, rpc }) {
|
|
1928
2297
|
const dialog = (0, react.useRef)(null), canvas = (0, react.useRef)(null), doc = (0, react.useRef)(createSketchLayers()), cache = (0, react.useRef)(/* @__PURE__ */ new Map());
|
|
1929
2298
|
const undo = (0, react.useRef)([]), redo = (0, react.useRef)([]), active = (0, react.useRef)(null), frame = (0, react.useRef)(null);
|
|
1930
2299
|
const images = (0, react.useRef)(/* @__PURE__ */ new Map()), saved = (0, react.useRef)(null), dirty = (0, react.useRef)(false), updateUi = (0, react.useRef)(false);
|
|
1931
2300
|
const documentId = (0, react.useRef)(crypto.randomUUID()), documentRevision = (0, react.useRef)(0), agentAdapter = (0, react.useRef)({}), agentSession = (0, react.useRef)(null);
|
|
2301
|
+
const [agentState, setAgentState] = (0, react.useState)("idle"), agentRun = (0, react.useRef)(null);
|
|
2302
|
+
const agentLocked = agentState === "drawing";
|
|
2303
|
+
const [noticeHidden, setNoticeHidden] = (0, react.useState)(false);
|
|
1932
2304
|
const [stability, setStability] = (0, react.useState)(0), [flow, setFlow] = (0, react.useState)(100), [picturesOpen, setPicturesOpen] = (0, react.useState)(false);
|
|
1933
2305
|
const pictureInput = (0, react.useRef)(null), received = (0, react.useRef)(null);
|
|
1934
2306
|
const navigation = useSketchView(canvas, open);
|
|
1935
2307
|
const [revision, redraw] = (0, react.useState)(0), [tool, setTool] = (0, react.useState)("pen"), [brush, setBrush] = (0, react.useState)("pen");
|
|
1936
2308
|
const [eraser, setEraser] = (0, react.useState)("pixel"), [color, setColor] = (0, react.useState)("#0088ff"), [width, setWidth] = (0, react.useState)(12);
|
|
2309
|
+
const [selection, setSelection] = (0, react.useState)(null), [textEdit, setTextEdit] = (0, react.useState)(null), [shapesOpen, setShapesOpen] = (0, react.useState)(false);
|
|
2310
|
+
const sizeGesture = (0, react.useRef)(false);
|
|
2311
|
+
const [sizeMode, setSizeMode] = (0, react.useState)("size");
|
|
2312
|
+
const showOpacity = tool !== "eraser";
|
|
2313
|
+
const opacityMode = showOpacity && sizeMode === "opacity";
|
|
2314
|
+
const selected = doc.current.layers.find((l) => l.id === selection?.layer)?.strokes.find((s, i) => objectId(s, i) === selection?.id);
|
|
2315
|
+
const editObject = (patch, action = "update") => {
|
|
2316
|
+
if (agentRun.current?.locked || busy || !selected) return;
|
|
2317
|
+
try {
|
|
2318
|
+
const next = applySketchCommands(doc.current, [{
|
|
2319
|
+
op: "object",
|
|
2320
|
+
...selection,
|
|
2321
|
+
action,
|
|
2322
|
+
patch
|
|
2323
|
+
}]);
|
|
2324
|
+
if (!sizeGesture.current) checkpoint();
|
|
2325
|
+
doc.current = next;
|
|
2326
|
+
schedule();
|
|
2327
|
+
if (action === "delete") setSelection(null);
|
|
2328
|
+
} catch (e) {
|
|
2329
|
+
setError(e.message);
|
|
2330
|
+
}
|
|
2331
|
+
};
|
|
2332
|
+
const pickColor = (value) => {
|
|
2333
|
+
setColor(value);
|
|
2334
|
+
if (selected) editObject({ color: value });
|
|
2335
|
+
};
|
|
1937
2336
|
const [fillShape, setFillShape] = (0, react.useState)(false);
|
|
1938
2337
|
const [layersOpen, setLayersOpen] = (0, react.useState)(false), [busy, setBusy] = (0, react.useState)(false), [error, setError] = (0, react.useState)("");
|
|
1939
2338
|
const cursorRing = (0, react.useRef)(null);
|
|
1940
|
-
const cursor = useSketchCursor(canvas, cursorRing, width, tool === "pen" ? brush : "pen", navigation.view.scale, !open || navigation.space || busy);
|
|
2339
|
+
const cursor = useSketchCursor(canvas, cursorRing, width, tool === "pen" ? brush : "pen", navigation.view.scale, !open || navigation.space || busy || agentLocked || tool === "select" || tool === "text");
|
|
2340
|
+
useSketchDismiss(shapesOpen, setShapesOpen, dialog, [".codexSketchShapeMenu", ".codexSketchShapeToggle"]);
|
|
1941
2341
|
useSketchDismiss(layersOpen, setLayersOpen, dialog, [".codexSketchLayers", ".codexSketchLayersToggle"]);
|
|
1942
2342
|
useSketchDismiss(picturesOpen, setPicturesOpen, dialog, [".codexSketchPictures", ".codexSketchPicturesToggle"]);
|
|
1943
2343
|
const paint = () => {
|
|
@@ -1967,7 +2367,7 @@ window.__ModuleLoader__.load({
|
|
|
1967
2367
|
redo.current = [];
|
|
1968
2368
|
};
|
|
1969
2369
|
const change = (action, id, value) => {
|
|
1970
|
-
if (busy || active.current) return;
|
|
2370
|
+
if (busy || agentRun.current?.locked || active.current) return;
|
|
1971
2371
|
const next = changeSketchLayer(doc.current, action, id, value);
|
|
1972
2372
|
if (next === doc.current) return;
|
|
1973
2373
|
if (action !== "select") checkpoint();
|
|
@@ -1979,12 +2379,14 @@ window.__ModuleLoader__.load({
|
|
|
1979
2379
|
(0, react.useEffect)(() => {
|
|
1980
2380
|
if (open) {
|
|
1981
2381
|
dialog.current.showModal();
|
|
2382
|
+
canvas.current.width = doc.current.width ?? 1024;
|
|
1982
2383
|
paint();
|
|
1983
2384
|
} else dialog.current?.close();
|
|
1984
2385
|
}, [open]);
|
|
1985
2386
|
(0, react.useEffect)(() => () => {
|
|
1986
2387
|
cancelAnimationFrame(frame.current);
|
|
1987
2388
|
cache.current.clear();
|
|
2389
|
+
agentRun.current?.dispose();
|
|
1988
2390
|
}, []);
|
|
1989
2391
|
const hasContent = () => doc.current.layers.some((layer) => layer.image || layer.strokes.length);
|
|
1990
2392
|
const save = async (name) => {
|
|
@@ -2007,7 +2409,9 @@ window.__ModuleLoader__.load({
|
|
|
2007
2409
|
const replace = (next, decoded, identity) => {
|
|
2008
2410
|
documentId.current = crypto.randomUUID();
|
|
2009
2411
|
documentRevision.current++;
|
|
2010
|
-
doc.current = structuredClone(next);
|
|
2412
|
+
doc.current = identifyObjects(structuredClone(next));
|
|
2413
|
+
setSelection(null);
|
|
2414
|
+
setTextEdit(null);
|
|
2011
2415
|
images.current = decoded;
|
|
2012
2416
|
cache.current.clear();
|
|
2013
2417
|
undo.current = [];
|
|
@@ -2068,6 +2472,10 @@ window.__ModuleLoader__.load({
|
|
|
2068
2472
|
schedule();
|
|
2069
2473
|
};
|
|
2070
2474
|
const close = async () => {
|
|
2475
|
+
if (agentRun.current?.locked) {
|
|
2476
|
+
onClose();
|
|
2477
|
+
return;
|
|
2478
|
+
}
|
|
2071
2479
|
if (busy || active.current) return;
|
|
2072
2480
|
setBusy(true);
|
|
2073
2481
|
try {
|
|
@@ -2080,7 +2488,7 @@ window.__ModuleLoader__.load({
|
|
|
2080
2488
|
}
|
|
2081
2489
|
};
|
|
2082
2490
|
const runFile = async (operation) => {
|
|
2083
|
-
if (busy || active.current) return;
|
|
2491
|
+
if (busy || agentRun.current?.locked || active.current) return;
|
|
2084
2492
|
setBusy(true);
|
|
2085
2493
|
setError("");
|
|
2086
2494
|
try {
|
|
@@ -2092,15 +2500,33 @@ window.__ModuleLoader__.load({
|
|
|
2092
2500
|
}
|
|
2093
2501
|
};
|
|
2094
2502
|
(0, react.useEffect)(() => {
|
|
2095
|
-
if (open && incoming && incoming !== received.current) {
|
|
2503
|
+
if (open && !agentLocked && incoming && incoming !== received.current) {
|
|
2096
2504
|
received.current = incoming;
|
|
2097
2505
|
runFile(() => importImage(incoming.file));
|
|
2098
2506
|
}
|
|
2099
|
-
}, [
|
|
2507
|
+
}, [
|
|
2508
|
+
open,
|
|
2509
|
+
incoming,
|
|
2510
|
+
agentLocked
|
|
2511
|
+
]);
|
|
2100
2512
|
const keyDown = (event) => {
|
|
2101
2513
|
if (event.target.closest("input,textarea,select,[contenteditable=true]") || event.isComposing || busy || active.current) return;
|
|
2102
2514
|
if (!navigation.shortcuts) return;
|
|
2103
2515
|
if (navigation.keyDown(event)) return;
|
|
2516
|
+
if (agentRun.current?.locked) return;
|
|
2517
|
+
if (selection && ["Delete", "Backspace"].includes(event.key)) {
|
|
2518
|
+
event.preventDefault();
|
|
2519
|
+
editObject({}, "delete");
|
|
2520
|
+
return;
|
|
2521
|
+
}
|
|
2522
|
+
if (event.key.toLowerCase() === "v") {
|
|
2523
|
+
setTool("select");
|
|
2524
|
+
return;
|
|
2525
|
+
}
|
|
2526
|
+
if (event.key.toLowerCase() === "t") {
|
|
2527
|
+
setTool("text");
|
|
2528
|
+
return;
|
|
2529
|
+
}
|
|
2104
2530
|
const key = event.key.toLowerCase(), command = event.ctrlKey || event.metaKey;
|
|
2105
2531
|
if (command && [
|
|
2106
2532
|
"z",
|
|
@@ -2137,6 +2563,32 @@ window.__ModuleLoader__.load({
|
|
|
2137
2563
|
const gesture = active.current;
|
|
2138
2564
|
if (!gesture || gesture.id !== event.pointerId || busy) return;
|
|
2139
2565
|
const rect = bounds ?? canvas.current.getBoundingClientRect();
|
|
2566
|
+
if (gesture.object) {
|
|
2567
|
+
const point = sketchPoint(event.clientX, event.clientY, rect);
|
|
2568
|
+
if (!point) return;
|
|
2569
|
+
try {
|
|
2570
|
+
const box = objectBounds(gesture.object);
|
|
2571
|
+
const stroke = gesture.handle === "end" ? {
|
|
2572
|
+
...gesture.object,
|
|
2573
|
+
points: [gesture.object.points[0], point]
|
|
2574
|
+
} : gesture.handle === "size" ? transformObject(gesture.object, {
|
|
2575
|
+
scaleX: Math.max(.001, point.x - box.x) / Math.max(.001, box.width),
|
|
2576
|
+
scaleY: Math.max(.001, point.y - box.y) / Math.max(.001, box.height)
|
|
2577
|
+
}) : transformObject(gesture.object, {
|
|
2578
|
+
dx: point.x - gesture.start.x,
|
|
2579
|
+
dy: point.y - gesture.start.y
|
|
2580
|
+
});
|
|
2581
|
+
doc.current = {
|
|
2582
|
+
...doc.current,
|
|
2583
|
+
layers: doc.current.layers.map((l) => l.id === gesture.layer ? {
|
|
2584
|
+
...l,
|
|
2585
|
+
strokes: l.strokes.map((s) => s.id === gesture.object.id ? stroke : s)
|
|
2586
|
+
} : l)
|
|
2587
|
+
};
|
|
2588
|
+
schedule();
|
|
2589
|
+
} catch {}
|
|
2590
|
+
return;
|
|
2591
|
+
}
|
|
2140
2592
|
const native = event.nativeEvent ?? event;
|
|
2141
2593
|
const events = native.getCoalescedEvents?.() ?? [];
|
|
2142
2594
|
for (const sample of events.length ? [...events, native] : [native]) {
|
|
@@ -2157,6 +2609,7 @@ window.__ModuleLoader__.load({
|
|
|
2157
2609
|
const stroke = layer.strokes.at(-1);
|
|
2158
2610
|
if ([
|
|
2159
2611
|
"line",
|
|
2612
|
+
"arrow",
|
|
2160
2613
|
"rectangle",
|
|
2161
2614
|
"circle"
|
|
2162
2615
|
].includes(stroke.shape)) {
|
|
@@ -2194,13 +2647,29 @@ window.__ModuleLoader__.load({
|
|
|
2194
2647
|
const gesture = active.current, stroke = doc.current.layers.find((layer) => layer.id === gesture.layer)?.strokes.at(-1);
|
|
2195
2648
|
if (gesture.smoothing && stroke) stroke.points = smoothStrokePoints(stroke.points, gesture.smoothing);
|
|
2196
2649
|
}
|
|
2650
|
+
const drawn = active.current;
|
|
2651
|
+
if (!cancel && !drawn.object && [
|
|
2652
|
+
"line",
|
|
2653
|
+
"arrow",
|
|
2654
|
+
"rectangle",
|
|
2655
|
+
"circle"
|
|
2656
|
+
].includes(tool)) {
|
|
2657
|
+
const stroke = doc.current.layers.find((l) => l.id === drawn.layer)?.strokes.at(-1);
|
|
2658
|
+
if (stroke) {
|
|
2659
|
+
setSelection({
|
|
2660
|
+
layer: drawn.layer,
|
|
2661
|
+
id: stroke.id
|
|
2662
|
+
});
|
|
2663
|
+
setTool("select");
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2197
2666
|
active.current = null;
|
|
2198
2667
|
if (canvas.current.hasPointerCapture(event.pointerId)) canvas.current.releasePointerCapture(event.pointerId);
|
|
2199
2668
|
if (cancel) doc.current = undo.current.pop() ?? doc.current;
|
|
2200
2669
|
schedule();
|
|
2201
2670
|
};
|
|
2202
2671
|
const history = (direction) => {
|
|
2203
|
-
if (busy || active.current) return;
|
|
2672
|
+
if (busy || agentRun.current?.locked || active.current) return;
|
|
2204
2673
|
const source = direction === "undo" ? undo : redo, target = direction === "undo" ? redo : undo;
|
|
2205
2674
|
if (!source.current.length) return;
|
|
2206
2675
|
documentRevision.current++;
|
|
@@ -2210,9 +2679,10 @@ window.__ModuleLoader__.load({
|
|
|
2210
2679
|
schedule();
|
|
2211
2680
|
};
|
|
2212
2681
|
Object.assign(agentAdapter.current, {
|
|
2213
|
-
available: () =>
|
|
2682
|
+
available: () => enabled && agentEnabled,
|
|
2683
|
+
previewEnabled: () => agentPreview,
|
|
2214
2684
|
open: () => onOpen(),
|
|
2215
|
-
busy: () => busy || Boolean(active.current),
|
|
2685
|
+
busy: () => busy || Boolean(active.current) || Boolean(textEdit) || sizeGesture.current,
|
|
2216
2686
|
document: () => doc.current,
|
|
2217
2687
|
snapshot: () => ({
|
|
2218
2688
|
documentId: documentId.current,
|
|
@@ -2229,6 +2699,15 @@ window.__ModuleLoader__.load({
|
|
|
2229
2699
|
})),
|
|
2230
2700
|
strokeCount: strokeCount(doc.current)
|
|
2231
2701
|
}),
|
|
2702
|
+
objects: () => sketchObjectSummary(doc.current),
|
|
2703
|
+
object: (id, layer = doc.current.active) => {
|
|
2704
|
+
const target = doc.current.layers.find((l) => l.id === layer)?.strokes.find((s, i) => objectId(s, i) === id);
|
|
2705
|
+
if (!target) throw Error("Object not found");
|
|
2706
|
+
return {
|
|
2707
|
+
...target,
|
|
2708
|
+
id
|
|
2709
|
+
};
|
|
2710
|
+
},
|
|
2232
2711
|
commit: (next) => {
|
|
2233
2712
|
checkpoint();
|
|
2234
2713
|
doc.current = next;
|
|
@@ -2239,24 +2718,27 @@ window.__ModuleLoader__.load({
|
|
|
2239
2718
|
paint();
|
|
2240
2719
|
return canvas.current.toDataURL("image/png");
|
|
2241
2720
|
},
|
|
2242
|
-
save
|
|
2243
|
-
setBusy(true);
|
|
2244
|
-
try {
|
|
2245
|
-
await save(name);
|
|
2246
|
-
} finally {
|
|
2247
|
-
setBusy(false);
|
|
2248
|
-
}
|
|
2249
|
-
}
|
|
2721
|
+
save
|
|
2250
2722
|
});
|
|
2251
2723
|
agentSession.current ??= createSketchCommandSession(agentAdapter.current);
|
|
2724
|
+
agentRun.current ??= createSketchAgentRun({
|
|
2725
|
+
execute: (request) => agentSession.current(request),
|
|
2726
|
+
open: () => agentAdapter.current.open(),
|
|
2727
|
+
changed: (state) => {
|
|
2728
|
+
setAgentState(state);
|
|
2729
|
+
setNoticeHidden(false);
|
|
2730
|
+
},
|
|
2731
|
+
busy: () => agentAdapter.current.busy(),
|
|
2732
|
+
previewEnabled: () => agentAdapter.current.previewEnabled()
|
|
2733
|
+
});
|
|
2252
2734
|
(0, react.useEffect)(() => {
|
|
2253
|
-
if (!
|
|
2735
|
+
if (!enabled || !agentEnabled) return;
|
|
2254
2736
|
const api = Object.freeze({
|
|
2255
2737
|
version: 1,
|
|
2256
2738
|
sessionId,
|
|
2257
|
-
execute: (request) =>
|
|
2739
|
+
execute: (request) => agentRun.current.execute(request),
|
|
2258
2740
|
export: async (format) => {
|
|
2259
|
-
if (agentAdapter.current.busy()) throw Error("Sketch is being edited");
|
|
2741
|
+
if (agentAdapter.current.busy() || agentRun.current.locked) throw Error("Sketch is being edited");
|
|
2260
2742
|
const { blob, extension } = await agentAdapter.current.export(format);
|
|
2261
2743
|
const data = new Uint8Array(await blob.arrayBuffer());
|
|
2262
2744
|
let raw = "";
|
|
@@ -2273,7 +2755,6 @@ window.__ModuleLoader__.load({
|
|
|
2273
2755
|
if (window.dshSketchAgent === api) delete window.dshSketchAgent;
|
|
2274
2756
|
};
|
|
2275
2757
|
}, [
|
|
2276
|
-
open,
|
|
2277
2758
|
enabled,
|
|
2278
2759
|
agentEnabled,
|
|
2279
2760
|
rpc,
|
|
@@ -2282,14 +2763,17 @@ window.__ModuleLoader__.load({
|
|
|
2282
2763
|
(0, react.useEffect)(() => {
|
|
2283
2764
|
if (!enabled || !agentEnabled || !rpc || !sessionId) return;
|
|
2284
2765
|
let live = true;
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2766
|
+
agentRun.current.resume();
|
|
2767
|
+
const disconnect = connectSketchAgent(rpc, sessionId, (request) => {
|
|
2768
|
+
if (!live) throw Error("Sketch session disconnected");
|
|
2769
|
+
return agentRun.current.execute(request);
|
|
2770
|
+
}, (message) => {
|
|
2771
|
+
agentRun.current.fail();
|
|
2772
|
+
setError(message);
|
|
2773
|
+
}, () => 350);
|
|
2291
2774
|
return () => {
|
|
2292
2775
|
live = false;
|
|
2776
|
+
agentRun.current.stop();
|
|
2293
2777
|
disconnect();
|
|
2294
2778
|
};
|
|
2295
2779
|
}, [
|
|
@@ -2299,7 +2783,7 @@ window.__ModuleLoader__.load({
|
|
|
2299
2783
|
sessionId
|
|
2300
2784
|
]);
|
|
2301
2785
|
const attach = async () => {
|
|
2302
|
-
if (!enabled || busy) return;
|
|
2786
|
+
if (!enabled || busy || agentRun.current?.locked) return;
|
|
2303
2787
|
setBusy(true);
|
|
2304
2788
|
setError("");
|
|
2305
2789
|
try {
|
|
@@ -2342,13 +2826,36 @@ window.__ModuleLoader__.load({
|
|
|
2342
2826
|
link.remove();
|
|
2343
2827
|
setTimeout(() => URL.revokeObjectURL(url), 1e4);
|
|
2344
2828
|
};
|
|
2345
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("
|
|
2829
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [enabled && agentEnabled && !open && !noticeHidden && agentState !== "idle" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2830
|
+
className: "codexSketchBackgroundStatus",
|
|
2831
|
+
role: "status",
|
|
2832
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2833
|
+
type: "button",
|
|
2834
|
+
onClick: onOpen,
|
|
2835
|
+
children: t(`sketchRun_${agentState}`)
|
|
2836
|
+
}), agentLocked ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2837
|
+
type: "button",
|
|
2838
|
+
"aria-label": t("sketchRunStop"),
|
|
2839
|
+
onClick: () => agentRun.current.stop(),
|
|
2840
|
+
children: "×"
|
|
2841
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2842
|
+
type: "button",
|
|
2843
|
+
"aria-label": t("sketchCancel"),
|
|
2844
|
+
onClick: () => setNoticeHidden(true),
|
|
2845
|
+
children: "×"
|
|
2846
|
+
})]
|
|
2847
|
+
}) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dialog", {
|
|
2346
2848
|
ref: dialog,
|
|
2347
2849
|
className: "codexSketchDialog codexSketchStudio codexLayerStudio",
|
|
2348
2850
|
"aria-label": t("sketchTitle"),
|
|
2349
2851
|
onKeyDown: keyDown,
|
|
2350
2852
|
onKeyUp: navigation.keyUp,
|
|
2351
2853
|
onPaste: (event) => {
|
|
2854
|
+
if (agentRun.current?.locked) {
|
|
2855
|
+
event.preventDefault();
|
|
2856
|
+
event.stopPropagation();
|
|
2857
|
+
return;
|
|
2858
|
+
}
|
|
2352
2859
|
if (event.target.closest("input,textarea")) return;
|
|
2353
2860
|
const file = Array.from(event.clipboardData.items).find((item) => item.type.startsWith("image/"))?.getAsFile();
|
|
2354
2861
|
if (file) {
|
|
@@ -2383,7 +2890,7 @@ window.__ModuleLoader__.load({
|
|
|
2383
2890
|
type: "button",
|
|
2384
2891
|
"aria-label": t("sketchCancel"),
|
|
2385
2892
|
title: t("sketchCancel"),
|
|
2386
|
-
disabled: busy,
|
|
2893
|
+
disabled: busy && !agentLocked,
|
|
2387
2894
|
onClick: close,
|
|
2388
2895
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspaceIcon, { name: "close" })
|
|
2389
2896
|
}),
|
|
@@ -2394,7 +2901,7 @@ window.__ModuleLoader__.load({
|
|
|
2394
2901
|
importImage,
|
|
2395
2902
|
download,
|
|
2396
2903
|
hasContent: doc.current.layers.some((l) => l.visible && (l.image || l.strokes.length)),
|
|
2397
|
-
disabled: busy,
|
|
2904
|
+
disabled: agentLocked || busy,
|
|
2398
2905
|
t,
|
|
2399
2906
|
report: setError,
|
|
2400
2907
|
onWorking: setBusy
|
|
@@ -2413,7 +2920,7 @@ window.__ModuleLoader__.load({
|
|
|
2413
2920
|
className: "codexSketchRound",
|
|
2414
2921
|
"aria-label": t(name === "undo" ? "sketchUndo" : "sketchRedo"),
|
|
2415
2922
|
title: t(name === "undo" ? "sketchUndo" : "sketchRedo"),
|
|
2416
|
-
disabled: busy || !(name === "undo" ? undo : redo).current.length,
|
|
2923
|
+
disabled: agentLocked || busy || !(name === "undo" ? undo : redo).current.length,
|
|
2417
2924
|
onClick: () => history(name),
|
|
2418
2925
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspaceIcon, {
|
|
2419
2926
|
name,
|
|
@@ -2426,9 +2933,9 @@ window.__ModuleLoader__.load({
|
|
|
2426
2933
|
"aria-label": t("sketchRatio"),
|
|
2427
2934
|
title: t("sketchRatioHint"),
|
|
2428
2935
|
value: doc.current.ratio ?? "1:1",
|
|
2429
|
-
disabled: busy,
|
|
2936
|
+
disabled: agentLocked || busy,
|
|
2430
2937
|
onChange: (event) => {
|
|
2431
|
-
if (active.current) return;
|
|
2938
|
+
if (active.current || agentRun.current?.locked) return;
|
|
2432
2939
|
const next = resizeSketch(doc.current, event.target.value);
|
|
2433
2940
|
if (next === doc.current) return;
|
|
2434
2941
|
checkpoint();
|
|
@@ -2469,7 +2976,7 @@ window.__ModuleLoader__.load({
|
|
|
2469
2976
|
type: "button",
|
|
2470
2977
|
className: "codexSketchConfirm",
|
|
2471
2978
|
"aria-label": t("sketchAttach"),
|
|
2472
|
-
disabled: busy || !enabled || !doc.current.layers.some((l) => l.visible && (l.strokes.length || l.image)),
|
|
2979
|
+
disabled: agentLocked || busy || !enabled || !doc.current.layers.some((l) => l.visible && (l.strokes.length || l.image)),
|
|
2473
2980
|
onClick: () => void attach(),
|
|
2474
2981
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspaceIcon, {
|
|
2475
2982
|
name: "check",
|
|
@@ -2478,6 +2985,25 @@ window.__ModuleLoader__.load({
|
|
|
2478
2985
|
})
|
|
2479
2986
|
]
|
|
2480
2987
|
}),
|
|
2988
|
+
enabled && agentEnabled && agentState !== "idle" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2989
|
+
className: "codexSketchAgentStatus",
|
|
2990
|
+
role: "status",
|
|
2991
|
+
"aria-live": "polite",
|
|
2992
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t(`sketchRun_${agentState}`) }), agentLocked ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2993
|
+
type: "button",
|
|
2994
|
+
"aria-label": t("sketchRunStop"),
|
|
2995
|
+
title: t("sketchRunStop"),
|
|
2996
|
+
onClick: () => agentRun.current.stop(),
|
|
2997
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspaceIcon, {
|
|
2998
|
+
name: "close",
|
|
2999
|
+
size: 16
|
|
3000
|
+
})
|
|
3001
|
+
}) : agentState === "stopped" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3002
|
+
type: "button",
|
|
3003
|
+
onClick: () => agentRun.current.resume(),
|
|
3004
|
+
children: t("sketchRunResume")
|
|
3005
|
+
}) : null]
|
|
3006
|
+
}) : null,
|
|
2481
3007
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2482
3008
|
className: `codexLayerBody ${layersOpen ? "withLayers" : ""}`,
|
|
2483
3009
|
children: [
|
|
@@ -2485,7 +3011,7 @@ window.__ModuleLoader__.load({
|
|
|
2485
3011
|
tabIndex: 0,
|
|
2486
3012
|
style: {
|
|
2487
3013
|
transform: `translate(${navigation.view.x}px,${navigation.view.y}px) scale(${navigation.view.scale})`,
|
|
2488
|
-
cursor: navigation.space ? "grab" : "none",
|
|
3014
|
+
cursor: navigation.space ? "grab" : agentLocked ? "default" : tool === "select" ? "default" : tool === "text" ? "text" : "none",
|
|
2489
3015
|
"--sketch-ratio": (doc.current.width ?? 1024) / (doc.current.height ?? 1024)
|
|
2490
3016
|
},
|
|
2491
3017
|
ref: canvas,
|
|
@@ -2495,7 +3021,69 @@ window.__ModuleLoader__.load({
|
|
|
2495
3021
|
onPointerDown: (event) => {
|
|
2496
3022
|
if (busy || !enabled || active.current) return;
|
|
2497
3023
|
if (navigation.down(event)) return;
|
|
3024
|
+
if (agentRun.current?.locked) return;
|
|
2498
3025
|
if (event.button !== 0) return;
|
|
3026
|
+
const point = sketchPoint(event.clientX, event.clientY, canvas.current.getBoundingClientRect());
|
|
3027
|
+
if (!point) return;
|
|
3028
|
+
if (tool === "text") {
|
|
3029
|
+
setTextEdit({
|
|
3030
|
+
point,
|
|
3031
|
+
value: ""
|
|
3032
|
+
});
|
|
3033
|
+
return;
|
|
3034
|
+
}
|
|
3035
|
+
if (tool === "select") {
|
|
3036
|
+
doc.current = identifyObjects(doc.current);
|
|
3037
|
+
if (selected) {
|
|
3038
|
+
const b = objectBounds(selected), end = ["line", "arrow"].includes(selected.shape) ? selected.points.at(-1) : {
|
|
3039
|
+
x: b.x + b.width,
|
|
3040
|
+
y: b.y + b.height
|
|
3041
|
+
}, rect = canvas.current.getBoundingClientRect();
|
|
3042
|
+
if (Math.hypot((end.x - point.x) * rect.width, (end.y - point.y) * rect.height) < 12) {
|
|
3043
|
+
checkpoint();
|
|
3044
|
+
active.current = {
|
|
3045
|
+
id: event.pointerId,
|
|
3046
|
+
layer: selection.layer,
|
|
3047
|
+
object: {
|
|
3048
|
+
...selected,
|
|
3049
|
+
id: selection.id
|
|
3050
|
+
},
|
|
3051
|
+
start: point,
|
|
3052
|
+
handle: ["line", "arrow"].includes(selected.shape) ? "end" : "size"
|
|
3053
|
+
};
|
|
3054
|
+
canvas.current.setPointerCapture(event.pointerId);
|
|
3055
|
+
return;
|
|
3056
|
+
}
|
|
3057
|
+
}
|
|
3058
|
+
let hit;
|
|
3059
|
+
for (const l of doc.current.layers.slice().reverse()) {
|
|
3060
|
+
if (!l.visible) continue;
|
|
3061
|
+
const stroke = l.strokes.slice().reverse().find((s) => s.shape !== "eraser" && strokeHit(s, point, 6, doc.current.width, doc.current.height));
|
|
3062
|
+
if (stroke) {
|
|
3063
|
+
hit = {
|
|
3064
|
+
layer: l.id,
|
|
3065
|
+
stroke
|
|
3066
|
+
};
|
|
3067
|
+
break;
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
setSelection(hit ? {
|
|
3071
|
+
layer: hit.layer,
|
|
3072
|
+
id: hit.stroke.id
|
|
3073
|
+
} : null);
|
|
3074
|
+
if (hit) {
|
|
3075
|
+
checkpoint();
|
|
3076
|
+
active.current = {
|
|
3077
|
+
id: event.pointerId,
|
|
3078
|
+
layer: hit.layer,
|
|
3079
|
+
object: hit.stroke,
|
|
3080
|
+
start: point
|
|
3081
|
+
};
|
|
3082
|
+
canvas.current.setPointerCapture(event.pointerId);
|
|
3083
|
+
}
|
|
3084
|
+
return;
|
|
3085
|
+
}
|
|
3086
|
+
setSelection(null);
|
|
2499
3087
|
cursor.down(event);
|
|
2500
3088
|
if (!current.visible) {
|
|
2501
3089
|
setError(t("sketchHiddenLayer"));
|
|
@@ -2523,6 +3111,7 @@ window.__ModuleLoader__.load({
|
|
|
2523
3111
|
const layer = layers.find((layer) => layer.id === doc.current.active);
|
|
2524
3112
|
const eraseStroke = tool === "eraser" && eraser === "stroke";
|
|
2525
3113
|
if (!eraseStroke) layer.strokes.push({
|
|
3114
|
+
id: crypto.randomUUID(),
|
|
2526
3115
|
color,
|
|
2527
3116
|
opacity: flow / 100,
|
|
2528
3117
|
shape: tool,
|
|
@@ -2554,11 +3143,131 @@ window.__ModuleLoader__.load({
|
|
|
2554
3143
|
onPointerUp: (event) => end(event),
|
|
2555
3144
|
onPointerCancel: (event) => end(event, true)
|
|
2556
3145
|
}),
|
|
3146
|
+
selected && tool === "select" && !agentLocked ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
3147
|
+
className: "codexSketchSelection",
|
|
3148
|
+
viewBox: "0 0 1 1",
|
|
3149
|
+
preserveAspectRatio: "none",
|
|
3150
|
+
style: {
|
|
3151
|
+
"--sketch-ratio": (doc.current.width ?? 1024) / (doc.current.height ?? 1024),
|
|
3152
|
+
transform: `translate(${navigation.view.x}px,${navigation.view.y}px) scale(${navigation.view.scale})`
|
|
3153
|
+
},
|
|
3154
|
+
"aria-hidden": "true",
|
|
3155
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
3156
|
+
...objectBounds(selected),
|
|
3157
|
+
fill: "none",
|
|
3158
|
+
stroke: "#0088ff",
|
|
3159
|
+
strokeWidth: ".002",
|
|
3160
|
+
strokeDasharray: ".008 .005"
|
|
3161
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
3162
|
+
cx: ["line", "arrow"].includes(selected.shape) ? selected.points.at(-1).x : objectBounds(selected).x + objectBounds(selected).width,
|
|
3163
|
+
cy: ["line", "arrow"].includes(selected.shape) ? selected.points.at(-1).y : objectBounds(selected).y + objectBounds(selected).height,
|
|
3164
|
+
r: ".007",
|
|
3165
|
+
fill: "white",
|
|
3166
|
+
stroke: "#0088ff",
|
|
3167
|
+
strokeWidth: ".002"
|
|
3168
|
+
})]
|
|
3169
|
+
}) : null,
|
|
3170
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SketchSizeControl, {
|
|
3171
|
+
label: t(opacityMode ? "sketchFlow" : selected?.shape === "text" || tool === "text" ? "sketchTextSize" : "sketchWidth"),
|
|
3172
|
+
mode: opacityMode ? "opacity" : "size",
|
|
3173
|
+
modes: showOpacity ? [{
|
|
3174
|
+
value: "size",
|
|
3175
|
+
label: t(selected?.shape === "text" || tool === "text" ? "sketchTextSize" : "sketchSizeShort")
|
|
3176
|
+
}, {
|
|
3177
|
+
value: "opacity",
|
|
3178
|
+
label: t("sketchFlow")
|
|
3179
|
+
}] : void 0,
|
|
3180
|
+
onModeChange: setSizeMode,
|
|
3181
|
+
min: opacityMode ? 5 : 1,
|
|
3182
|
+
max: opacityMode ? 100 : 256,
|
|
3183
|
+
suffix: opacityMode ? "%" : "",
|
|
3184
|
+
value: opacityMode ? (selected?.opacity ?? flow / 100) * 100 : selected?.width ?? width,
|
|
3185
|
+
disabled: agentLocked || busy,
|
|
3186
|
+
onStart: () => {
|
|
3187
|
+
if (selected) {
|
|
3188
|
+
checkpoint();
|
|
3189
|
+
sizeGesture.current = true;
|
|
3190
|
+
}
|
|
3191
|
+
},
|
|
3192
|
+
onEnd: () => {
|
|
3193
|
+
sizeGesture.current = false;
|
|
3194
|
+
},
|
|
3195
|
+
onChange: (value) => {
|
|
3196
|
+
if (opacityMode) {
|
|
3197
|
+
setFlow(value);
|
|
3198
|
+
if (selected) editObject({ opacity: value / 100 });
|
|
3199
|
+
} else {
|
|
3200
|
+
setWidth(value);
|
|
3201
|
+
if (selected) editObject({ width: value });
|
|
3202
|
+
}
|
|
3203
|
+
}
|
|
3204
|
+
}),
|
|
3205
|
+
textEdit ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
|
|
3206
|
+
className: "codexSketchTextEditor",
|
|
3207
|
+
onSubmit: (event) => {
|
|
3208
|
+
event.preventDefault();
|
|
3209
|
+
if (!textEdit.value.trim()) {
|
|
3210
|
+
setTextEdit(null);
|
|
3211
|
+
return;
|
|
3212
|
+
}
|
|
3213
|
+
try {
|
|
3214
|
+
if (textEdit.selection) editObject({ text: textEdit.value });
|
|
3215
|
+
else {
|
|
3216
|
+
const a = textEdit.point, b = {
|
|
3217
|
+
x: Math.min(1, a.x + .35),
|
|
3218
|
+
y: Math.min(1, a.y + .15)
|
|
3219
|
+
}, id = crypto.randomUUID();
|
|
3220
|
+
const next = applySketchCommands(doc.current, [{
|
|
3221
|
+
op: "stroke",
|
|
3222
|
+
id,
|
|
3223
|
+
shape: "text",
|
|
3224
|
+
text: textEdit.value,
|
|
3225
|
+
color,
|
|
3226
|
+
width: Math.max(24, width),
|
|
3227
|
+
points: [a, b]
|
|
3228
|
+
}]);
|
|
3229
|
+
checkpoint();
|
|
3230
|
+
doc.current = next;
|
|
3231
|
+
setSelection({
|
|
3232
|
+
layer: doc.current.active,
|
|
3233
|
+
id
|
|
3234
|
+
});
|
|
3235
|
+
schedule();
|
|
3236
|
+
}
|
|
3237
|
+
setTextEdit(null);
|
|
3238
|
+
setTool("select");
|
|
3239
|
+
} catch (e) {
|
|
3240
|
+
setError(e.message);
|
|
3241
|
+
}
|
|
3242
|
+
},
|
|
3243
|
+
children: [
|
|
3244
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
3245
|
+
autoFocus: true,
|
|
3246
|
+
"aria-label": t("sketchText"),
|
|
3247
|
+
maxLength: 500,
|
|
3248
|
+
value: textEdit.value,
|
|
3249
|
+
onChange: (e) => setTextEdit({
|
|
3250
|
+
...textEdit,
|
|
3251
|
+
value: e.target.value
|
|
3252
|
+
})
|
|
3253
|
+
}),
|
|
3254
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3255
|
+
type: "submit",
|
|
3256
|
+
children: t("sketchTextDone")
|
|
3257
|
+
}),
|
|
3258
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3259
|
+
type: "button",
|
|
3260
|
+
onClick: () => setTextEdit(null),
|
|
3261
|
+
children: t("sketchCancel")
|
|
3262
|
+
})
|
|
3263
|
+
]
|
|
3264
|
+
}) : null,
|
|
2557
3265
|
picturesOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("aside", {
|
|
2558
3266
|
className: "codexSketchPictures",
|
|
2559
3267
|
children: [
|
|
2560
3268
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("sketchPictures") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2561
3269
|
type: "button",
|
|
3270
|
+
disabled: agentLocked || busy,
|
|
2562
3271
|
onClick: () => pictureInput.current.click(),
|
|
2563
3272
|
children: t("sketchPictureAdd")
|
|
2564
3273
|
})] }),
|
|
@@ -2577,6 +3286,7 @@ window.__ModuleLoader__.load({
|
|
|
2577
3286
|
"data-active": layer.id === doc.current.active,
|
|
2578
3287
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2579
3288
|
type: "button",
|
|
3289
|
+
disabled: agentLocked || busy,
|
|
2580
3290
|
"aria-label": `${t("sketchPictureSelect")} ${layer.name}`,
|
|
2581
3291
|
onClick: () => change("select", layer.id),
|
|
2582
3292
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
@@ -2585,6 +3295,7 @@ window.__ModuleLoader__.load({
|
|
|
2585
3295
|
})
|
|
2586
3296
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2587
3297
|
type: "button",
|
|
3298
|
+
disabled: agentLocked || busy,
|
|
2588
3299
|
"aria-label": `${t("sketchDeleteDraft")} ${layer.name}`,
|
|
2589
3300
|
onClick: () => change(doc.current.layers.length === 1 ? "clear" : "delete", layer.id),
|
|
2590
3301
|
children: "×"
|
|
@@ -2601,7 +3312,7 @@ window.__ModuleLoader__.load({
|
|
|
2601
3312
|
type: "button",
|
|
2602
3313
|
title: t("sketchLayerAdd"),
|
|
2603
3314
|
"aria-label": t("sketchLayerAdd"),
|
|
2604
|
-
disabled: busy || doc.current.layers.length >= 8,
|
|
3315
|
+
disabled: agentLocked || busy || doc.current.layers.length >= 8,
|
|
2605
3316
|
onClick: () => change("add"),
|
|
2606
3317
|
children: "+"
|
|
2607
3318
|
})] }),
|
|
@@ -2612,6 +3323,7 @@ window.__ModuleLoader__.load({
|
|
|
2612
3323
|
"data-active": layer.id === doc.current.active,
|
|
2613
3324
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2614
3325
|
type: "button",
|
|
3326
|
+
disabled: agentLocked || busy,
|
|
2615
3327
|
"aria-label": `${t("sketchLayerVisible")} ${layer.id}`,
|
|
2616
3328
|
"aria-pressed": layer.visible,
|
|
2617
3329
|
onClick: () => change("visible", layer.id),
|
|
@@ -2621,6 +3333,7 @@ window.__ModuleLoader__.load({
|
|
|
2621
3333
|
})
|
|
2622
3334
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2623
3335
|
type: "button",
|
|
3336
|
+
disabled: agentLocked || busy,
|
|
2624
3337
|
"aria-pressed": layer.id === doc.current.active,
|
|
2625
3338
|
onClick: () => change("select", layer.id),
|
|
2626
3339
|
children: layer.name || `${t("sketchLayer")} ${layer.id}`
|
|
@@ -2632,6 +3345,7 @@ window.__ModuleLoader__.load({
|
|
|
2632
3345
|
children: t("sketchLayerName")
|
|
2633
3346
|
}),
|
|
2634
3347
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3348
|
+
disabled: agentLocked || busy,
|
|
2635
3349
|
"aria-label": t("sketchLayerName"),
|
|
2636
3350
|
defaultValue: current.name,
|
|
2637
3351
|
placeholder: `${t("sketchLayer")} ${current.id}`,
|
|
@@ -2654,7 +3368,7 @@ window.__ModuleLoader__.load({
|
|
|
2654
3368
|
type: "button",
|
|
2655
3369
|
title: t(`sketchLayer_${action}`),
|
|
2656
3370
|
"aria-label": t(`sketchLayer_${action}`),
|
|
2657
|
-
disabled: busy || action === "delete" && doc.current.layers.length === 1 || action === "duplicate" && (doc.current.layers.length >= 8 || strokeCount(doc.current) + current.strokes.length > 2e3) || action === "up" && current === doc.current.layers.at(-1) || action === "down" && current === doc.current.layers[0],
|
|
3371
|
+
disabled: agentLocked || busy || action === "delete" && doc.current.layers.length === 1 || action === "duplicate" && (doc.current.layers.length >= 8 || strokeCount(doc.current) + current.strokes.length > 2e3) || action === "up" && current === doc.current.layers.at(-1) || action === "down" && current === doc.current.layers[0],
|
|
2658
3372
|
onClick: () => change(action),
|
|
2659
3373
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspaceIcon, {
|
|
2660
3374
|
name: action === "delete" ? "clear" : action,
|
|
@@ -2665,7 +3379,7 @@ window.__ModuleLoader__.load({
|
|
|
2665
3379
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2666
3380
|
type: "button",
|
|
2667
3381
|
className: "codexSketchClearLayer",
|
|
2668
|
-
disabled: busy || !current.strokes.length && !current.image,
|
|
3382
|
+
disabled: agentLocked || busy || !current.strokes.length && !current.image,
|
|
2669
3383
|
onClick: () => change("clear"),
|
|
2670
3384
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspaceIcon, {
|
|
2671
3385
|
name: "clear",
|
|
@@ -2690,57 +3404,123 @@ window.__ModuleLoader__.load({
|
|
|
2690
3404
|
navigation,
|
|
2691
3405
|
t
|
|
2692
3406
|
}),
|
|
2693
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
3407
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2694
3408
|
className: "codexSketchPill",
|
|
2695
3409
|
role: "toolbar",
|
|
2696
3410
|
"aria-label": t("sketchTitle"),
|
|
2697
3411
|
title: t("sketchShortcuts"),
|
|
2698
3412
|
children: [
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
"marker",
|
|
2702
|
-
"eraser",
|
|
2703
|
-
"line",
|
|
2704
|
-
"rectangle",
|
|
2705
|
-
"circle"
|
|
2706
|
-
].map((name) => {
|
|
2707
|
-
const drawing = [
|
|
3413
|
+
[
|
|
3414
|
+
"select",
|
|
2708
3415
|
"pen",
|
|
2709
|
-
"
|
|
2710
|
-
"
|
|
2711
|
-
].
|
|
2712
|
-
|
|
2713
|
-
|
|
3416
|
+
"text",
|
|
3417
|
+
"eraser"
|
|
3418
|
+
].map((name) => {
|
|
3419
|
+
const drawing = [
|
|
3420
|
+
"pen",
|
|
3421
|
+
"pencil",
|
|
3422
|
+
"marker"
|
|
3423
|
+
].includes(name);
|
|
3424
|
+
const label = t(drawing ? `sketchBrush_${name}` : `sketchTool_${name}`);
|
|
3425
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3426
|
+
type: "button",
|
|
3427
|
+
"aria-label": label,
|
|
3428
|
+
title: label,
|
|
3429
|
+
"aria-pressed": drawing ? tool === "pen" && brush === name : tool === name,
|
|
3430
|
+
disabled: agentLocked || busy,
|
|
3431
|
+
onClick: () => {
|
|
3432
|
+
setTool(drawing ? "pen" : name);
|
|
3433
|
+
if (name !== "select") setSelection(null);
|
|
3434
|
+
if (drawing) setBrush(name);
|
|
3435
|
+
},
|
|
3436
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspaceIcon, {
|
|
3437
|
+
name,
|
|
3438
|
+
size: 23
|
|
3439
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label })]
|
|
3440
|
+
}, name);
|
|
3441
|
+
}),
|
|
3442
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3443
|
+
className: "codexSketchShapeToggle",
|
|
2714
3444
|
type: "button",
|
|
2715
|
-
"aria-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
disabled: busy,
|
|
2719
|
-
onClick: () => {
|
|
2720
|
-
setTool(drawing ? "pen" : name);
|
|
2721
|
-
if (drawing) setBrush(name);
|
|
2722
|
-
},
|
|
3445
|
+
"aria-expanded": shapesOpen,
|
|
3446
|
+
disabled: agentLocked || busy,
|
|
3447
|
+
onClick: () => setShapesOpen((v) => !v),
|
|
2723
3448
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspaceIcon, {
|
|
2724
|
-
name,
|
|
3449
|
+
name: "rectangle",
|
|
2725
3450
|
size: 23
|
|
2726
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children:
|
|
2727
|
-
},
|
|
2728
|
-
|
|
3451
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("sketchShapes") })]
|
|
3452
|
+
}),
|
|
3453
|
+
shapesOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3454
|
+
className: "codexSketchShapeMenu",
|
|
3455
|
+
children: [
|
|
3456
|
+
"line",
|
|
3457
|
+
"arrow",
|
|
3458
|
+
"rectangle",
|
|
3459
|
+
"circle"
|
|
3460
|
+
].map((name) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3461
|
+
type: "button",
|
|
3462
|
+
onClick: () => {
|
|
3463
|
+
setTool(name);
|
|
3464
|
+
setSelection(null);
|
|
3465
|
+
setShapesOpen(false);
|
|
3466
|
+
},
|
|
3467
|
+
children: t(`sketchTool_${name}`)
|
|
3468
|
+
}, name))
|
|
3469
|
+
}) : null
|
|
3470
|
+
]
|
|
2729
3471
|
}),
|
|
2730
3472
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2731
3473
|
className: "codexLayerBrush",
|
|
2732
3474
|
children: [
|
|
2733
3475
|
["rectangle", "circle"].includes(tool) ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2734
3476
|
type: "checkbox",
|
|
3477
|
+
disabled: agentLocked || busy,
|
|
2735
3478
|
checked: fillShape,
|
|
2736
3479
|
onChange: (e) => setFillShape(e.target.checked)
|
|
2737
3480
|
}), t("sketchFill")] }) : null,
|
|
3481
|
+
tool === "pen" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
3482
|
+
"aria-label": t("sketchBrush"),
|
|
3483
|
+
value: brush,
|
|
3484
|
+
onChange: (e) => setBrush(e.target.value),
|
|
3485
|
+
disabled: agentLocked || busy,
|
|
3486
|
+
children: [
|
|
3487
|
+
"pen",
|
|
3488
|
+
"pencil",
|
|
3489
|
+
"marker"
|
|
3490
|
+
].map((b) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
3491
|
+
value: b,
|
|
3492
|
+
children: t(`sketchBrush_${b}`)
|
|
3493
|
+
}, b))
|
|
3494
|
+
}) : null,
|
|
3495
|
+
selected ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3496
|
+
className: "codexSketchObjectActions",
|
|
3497
|
+
children: [
|
|
3498
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3499
|
+
disabled: agentLocked || busy,
|
|
3500
|
+
onClick: () => editObject({}, "duplicate"),
|
|
3501
|
+
children: t("sketchObjectDuplicate")
|
|
3502
|
+
}),
|
|
3503
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3504
|
+
disabled: agentLocked || busy,
|
|
3505
|
+
onClick: () => editObject({}, "delete"),
|
|
3506
|
+
children: t("sketchObjectDelete")
|
|
3507
|
+
}),
|
|
3508
|
+
selected.shape === "text" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3509
|
+
disabled: agentLocked || busy,
|
|
3510
|
+
onClick: () => setTextEdit({
|
|
3511
|
+
selection,
|
|
3512
|
+
value: selected.text
|
|
3513
|
+
}),
|
|
3514
|
+
children: t("sketchText")
|
|
3515
|
+
}) : null
|
|
3516
|
+
]
|
|
3517
|
+
}) : null,
|
|
2738
3518
|
tool === "pen" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2739
3519
|
className: "codexSketchStability",
|
|
2740
3520
|
children: [t("sketchStability"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
2741
3521
|
"aria-label": t("sketchStability"),
|
|
2742
3522
|
value: stability,
|
|
2743
|
-
disabled: busy,
|
|
3523
|
+
disabled: agentLocked || busy,
|
|
2744
3524
|
onChange: (e) => setStability(Number(e.target.value)),
|
|
2745
3525
|
children: [
|
|
2746
3526
|
0,
|
|
@@ -2760,43 +3540,11 @@ window.__ModuleLoader__.load({
|
|
|
2760
3540
|
children: ["pixel", "stroke"].map((value) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2761
3541
|
type: "button",
|
|
2762
3542
|
"aria-pressed": eraser === value,
|
|
2763
|
-
disabled: busy,
|
|
3543
|
+
disabled: agentLocked || busy,
|
|
2764
3544
|
onClick: () => setEraser(value),
|
|
2765
3545
|
children: t(`sketchErase_${value}`)
|
|
2766
3546
|
}, value))
|
|
2767
|
-
}) : null
|
|
2768
|
-
tool !== "eraser" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2769
|
-
className: "codexSketchWidth",
|
|
2770
|
-
children: [
|
|
2771
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("sketchFlow") }),
|
|
2772
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2773
|
-
type: "range",
|
|
2774
|
-
min: 5,
|
|
2775
|
-
max: 100,
|
|
2776
|
-
step: 5,
|
|
2777
|
-
value: flow,
|
|
2778
|
-
"aria-label": t("sketchFlow"),
|
|
2779
|
-
onChange: (e) => setFlow(Number(e.target.value))
|
|
2780
|
-
}),
|
|
2781
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("output", { children: flow })
|
|
2782
|
-
]
|
|
2783
|
-
}) : null,
|
|
2784
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2785
|
-
className: "codexSketchWidth",
|
|
2786
|
-
children: [
|
|
2787
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("sketchWidth") }),
|
|
2788
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2789
|
-
type: "range",
|
|
2790
|
-
min: 2,
|
|
2791
|
-
max: 64,
|
|
2792
|
-
value: width,
|
|
2793
|
-
"aria-label": t("sketchWidth"),
|
|
2794
|
-
onChange: (e) => setWidth(Number(e.target.value)),
|
|
2795
|
-
disabled: busy
|
|
2796
|
-
}),
|
|
2797
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("output", { children: width })
|
|
2798
|
-
]
|
|
2799
|
-
})
|
|
3547
|
+
}) : null
|
|
2800
3548
|
]
|
|
2801
3549
|
}),
|
|
2802
3550
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -2808,9 +3556,9 @@ window.__ModuleLoader__.load({
|
|
|
2808
3556
|
className: "codexSketchSwatch",
|
|
2809
3557
|
style: { "--swatch": value },
|
|
2810
3558
|
"aria-label": `${t("sketchColor")} ${value}`,
|
|
2811
|
-
"aria-pressed": color === value,
|
|
2812
|
-
disabled: busy,
|
|
2813
|
-
onClick: () =>
|
|
3559
|
+
"aria-pressed": (selected?.color ?? color) === value,
|
|
3560
|
+
disabled: agentLocked || busy,
|
|
3561
|
+
onClick: () => pickColor(value)
|
|
2814
3562
|
}, value)), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2815
3563
|
className: "codexSketchCustom",
|
|
2816
3564
|
title: t("sketchColor"),
|
|
@@ -2818,8 +3566,8 @@ window.__ModuleLoader__.load({
|
|
|
2818
3566
|
type: "color",
|
|
2819
3567
|
"aria-label": t("sketchColor"),
|
|
2820
3568
|
value: color,
|
|
2821
|
-
disabled: busy,
|
|
2822
|
-
onChange: (e) =>
|
|
3569
|
+
disabled: agentLocked || busy,
|
|
3570
|
+
onChange: (e) => pickColor(e.target.value)
|
|
2823
3571
|
})]
|
|
2824
3572
|
})]
|
|
2825
3573
|
})
|
|
@@ -2831,11 +3579,26 @@ window.__ModuleLoader__.load({
|
|
|
2831
3579
|
children: error
|
|
2832
3580
|
}) : null
|
|
2833
3581
|
]
|
|
2834
|
-
});
|
|
3582
|
+
})] });
|
|
2835
3583
|
}
|
|
2836
3584
|
//#endregion
|
|
2837
3585
|
//#region src/sketch-styles.js
|
|
2838
3586
|
const SKETCH_CSS = `
|
|
3587
|
+
.codexSketchSizeModes{display:flex;flex-direction:column;gap:3px}.codexSketchSizeModes button{font-size:11px;padding:4px 6px;border-radius:10px;color:var(--sketch-muted)}.codexSketchSizeModes button[aria-pressed=true]{background:var(--sketch-line);color:var(--sketch-fg)}
|
|
3588
|
+
|
|
3589
|
+
.codexSketchSizeControl{position:absolute;left:10px;top:50%;transform:translateY(-50%);z-index:2;display:flex;flex-direction:column;align-items:center;gap:10px;padding:12px 6px;border:1px solid var(--sketch-line);border-radius:24px;background:var(--sketch-glass);backdrop-filter:blur(18px);box-shadow:0 3px 14px #0001}
|
|
3590
|
+
.codexSketchSizeControl>span{font-size:10px;color:var(--sketch-muted);max-width:42px;text-align:center}.codexSketchSizeControl output{font-size:11px;font-variant-numeric:tabular-nums;color:var(--sketch-muted)}
|
|
3591
|
+
.codexSketchSizeControl input{writing-mode:vertical-lr;direction:rtl;width:28px;height:150px;appearance:none;background:transparent;cursor:ns-resize;touch-action:none}
|
|
3592
|
+
.codexSketchSizeControl input::-webkit-slider-runnable-track{width:4px;border-radius:4px;background:var(--sketch-line)}
|
|
3593
|
+
.codexSketchSizeControl input::-webkit-slider-thumb{appearance:none;width:20px;height:20px;margin-left:-8px;border-radius:50%;background:var(--sketch-fg);border:2px solid var(--sketch-bg);box-shadow:0 1px 5px #0004}
|
|
3594
|
+
.codexSketchSelection{position:absolute;width:min(100cqw,calc(100cqh * var(--sketch-ratio,1)));height:auto;aspect-ratio:var(--sketch-ratio);pointer-events:none;overflow:visible}
|
|
3595
|
+
.codexSketchShapeMenu{position:absolute;bottom:65px;display:grid;grid-template-columns:1fr 1fr;padding:8px;border:1px solid var(--sketch-line);border-radius:14px;background:var(--sketch-bg);box-shadow:0 8px 24px #0003;z-index:5}.codexSketchShapeMenu button{min-height:40px;padding:8px 14px}
|
|
3596
|
+
.codexSketchTextEditor{position:absolute;z-index:4;left:50%;top:50%;transform:translate(-50%,-50%);display:flex;flex-wrap:wrap;gap:8px;width:min(320px,80%);padding:12px;background:var(--sketch-bg);border:1px solid var(--sketch-line);border-radius:12px;box-shadow:0 6px 24px #0003}.codexSketchTextEditor textarea{width:100%;min-height:72px;resize:vertical;background:transparent;color:inherit;border:1px solid var(--sketch-line);border-radius:8px;padding:8px;font:inherit}.codexSketchTextEditor button{padding:6px 10px;border-radius:8px;background:var(--sketch-line)}
|
|
3597
|
+
.codexSketchObjectActions{display:flex;gap:8px}.codexSketchObjectActions button{padding:6px 8px;border-radius:8px;background:var(--sketch-line)}
|
|
3598
|
+
.codexLayerBrush>select{background:var(--sketch-bg);color:inherit;border:1px solid var(--sketch-line);border-radius:8px;padding:6px}
|
|
3599
|
+
|
|
3600
|
+
.codexSketchBackgroundStatus{position:fixed;top:16px;left:50%;transform:translateX(-50%);z-index:1000;display:flex;gap:8px;align-items:center;padding:6px 10px;border-radius:12px;background:var(--dsw-alias-bg-layer-1,#f5f5f7);color:var(--dsw-alias-label-primary,#202124);border:1px solid #8883;box-shadow:0 4px 16px #0002;font:12px system-ui}.codexSketchBackgroundStatus button{color:inherit;background:none;border:0;cursor:pointer}
|
|
3601
|
+
.codexSketchAgentStatus{display:flex;align-items:center;justify-content:center;gap:8px;min-height:24px;color:var(--sketch-muted);font-size:12px}.codexSketchAgentStatus button{padding:3px 6px;border-radius:6px;background:var(--sketch-line)}
|
|
2839
3602
|
.codexSketchDialog{--sketch-bg:var(--dsw-alias-bg-layer-1,#f5f5f7);--sketch-fg:var(--dsw-alias-label-primary,#202124);--sketch-muted:var(--dsw-alias-label-secondary,#727279);--sketch-line:var(--dsw-alias-border-l2,#8883);--sketch-glass:color-mix(in srgb,var(--sketch-bg) 90%,transparent);box-sizing:border-box;width:min(1280px,calc(100vw - 24px));height:94dvh;max-height:94dvh;margin:auto;padding:10px;border:1px solid var(--sketch-line);border-radius:18px;background:var(--sketch-bg);color:var(--sketch-fg);box-shadow:0 24px 90px #0004;overflow:hidden;font:13px/1.4 system-ui}
|
|
2840
3603
|
.codexSketchDialog[open]{display:flex;flex-direction:column;gap:8px}
|
|
2841
3604
|
.codexSketchDialog::backdrop{background:#0005;backdrop-filter:blur(12px)}
|
|
@@ -2906,6 +3669,7 @@ window.__ModuleLoader__.load({
|
|
|
2906
3669
|
}
|
|
2907
3670
|
}), [registerOpen]);
|
|
2908
3671
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SketchStudio, {
|
|
3672
|
+
agentPreview: settings.imageSketchAgentPreview,
|
|
2909
3673
|
agentEnabled: settings.imageSketchAgent,
|
|
2910
3674
|
onOpen: () => {
|
|
2911
3675
|
opener.current = document.activeElement;
|
|
@@ -2953,7 +3717,7 @@ window.__ModuleLoader__.load({
|
|
|
2953
3717
|
}) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SketchWorkspace, {
|
|
2954
3718
|
...props,
|
|
2955
3719
|
registerOpen: registerWorkspace
|
|
2956
|
-
})] });
|
|
3720
|
+
}, props.sessionId)] });
|
|
2957
3721
|
}
|
|
2958
3722
|
//#endregion
|
|
2959
3723
|
//#region src/image-composer.js
|
|
@@ -3000,9 +3764,10 @@ window.__ModuleLoader__.load({
|
|
|
3000
3764
|
}
|
|
3001
3765
|
const createSketchTrigger = (options) => createWorkspaceTrigger({
|
|
3002
3766
|
...options,
|
|
3767
|
+
open: () => {},
|
|
3003
3768
|
name: "Sketch · Beta",
|
|
3004
3769
|
aliases: ["sketch", "草图"],
|
|
3005
|
-
description: "
|
|
3770
|
+
description: "Ask the Agent to draw or edit a sketch"
|
|
3006
3771
|
});
|
|
3007
3772
|
const createImageTrigger = (options) => createWorkspaceTrigger({
|
|
3008
3773
|
...options,
|
|
@@ -3013,6 +3778,17 @@ window.__ModuleLoader__.load({
|
|
|
3013
3778
|
//#endregion
|
|
3014
3779
|
//#region src/client-locales.js
|
|
3015
3780
|
const zh = {
|
|
3781
|
+
sketchSizeShort: "粗细",
|
|
3782
|
+
sketchObjectDuplicate: "复制对象",
|
|
3783
|
+
sketchObjectDelete: "删除对象",
|
|
3784
|
+
sketchTool_select: "选择",
|
|
3785
|
+
sketchTool_text: "文字",
|
|
3786
|
+
sketchTool_arrow: "箭头",
|
|
3787
|
+
sketchShapes: "图形",
|
|
3788
|
+
sketchText: "编辑文字",
|
|
3789
|
+
sketchTextDone: "完成",
|
|
3790
|
+
sketchTextSize: "字号",
|
|
3791
|
+
sketchBrush: "笔型",
|
|
3016
3792
|
sketchFill: "填色",
|
|
3017
3793
|
sketchDownload: "下载",
|
|
3018
3794
|
sketchExportFormat: "导出格式",
|
|
@@ -3122,7 +3898,17 @@ window.__ModuleLoader__.load({
|
|
|
3122
3898
|
sketchCanvas_on: "开启",
|
|
3123
3899
|
sketchCanvas_off: "关闭",
|
|
3124
3900
|
sketchAgent: "Agent 绘图 · Beta",
|
|
3125
|
-
sketchAgentHint: "
|
|
3901
|
+
sketchAgentHint: "默认关闭;开启后可用 @sketch 请求 Agent 绘图,需要同时开启草图画板。",
|
|
3902
|
+
sketchRun_drawing: "Agent 正在绘制…",
|
|
3903
|
+
sketchRun_finished: "绘制完成",
|
|
3904
|
+
sketchRun_stopped: "绘制已停止",
|
|
3905
|
+
sketchRun_failed: "绘制失败",
|
|
3906
|
+
sketchRunStop: "停止绘制",
|
|
3907
|
+
sketchRunResume: "允许继续绘制",
|
|
3908
|
+
sketchAgentPreview: "完成后返回预览 · Beta",
|
|
3909
|
+
sketchAgentPreviewHint: "默认关闭;完成绘制后向模型返回画布图片,会增加图片输入用量。",
|
|
3910
|
+
sketchAgentPreview_on: "开启",
|
|
3911
|
+
sketchAgentPreview_off: "关闭",
|
|
3126
3912
|
sketchAgent_on: "开启",
|
|
3127
3913
|
sketchAgent_off: "关闭",
|
|
3128
3914
|
imageSettings: "图片",
|
|
@@ -3398,6 +4184,17 @@ window.__ModuleLoader__.load({
|
|
|
3398
4184
|
imageRemoveAnnotation: "删除标注"
|
|
3399
4185
|
};
|
|
3400
4186
|
const en = {
|
|
4187
|
+
sketchSizeShort: "Size",
|
|
4188
|
+
sketchObjectDuplicate: "Duplicate object",
|
|
4189
|
+
sketchObjectDelete: "Delete object",
|
|
4190
|
+
sketchTool_select: "Select",
|
|
4191
|
+
sketchTool_text: "Text",
|
|
4192
|
+
sketchTool_arrow: "Arrow",
|
|
4193
|
+
sketchShapes: "Shapes",
|
|
4194
|
+
sketchText: "Edit text",
|
|
4195
|
+
sketchTextDone: "Done",
|
|
4196
|
+
sketchTextSize: "Text size",
|
|
4197
|
+
sketchBrush: "Brush",
|
|
3401
4198
|
sketchFill: "Fill",
|
|
3402
4199
|
sketchDownload: "Download",
|
|
3403
4200
|
sketchExportFormat: "Export format",
|
|
@@ -3507,7 +4304,17 @@ window.__ModuleLoader__.load({
|
|
|
3507
4304
|
sketchCanvas_on: "On",
|
|
3508
4305
|
sketchCanvas_off: "Off",
|
|
3509
4306
|
sketchAgent: "Agent drawing · Beta",
|
|
3510
|
-
sketchAgentHint: "Off by default.
|
|
4307
|
+
sketchAgentHint: "Off by default. Use @sketch to ask the Agent to draw; requires the sketch canvas.",
|
|
4308
|
+
sketchRun_drawing: "Agent is drawing…",
|
|
4309
|
+
sketchRun_finished: "Drawing complete",
|
|
4310
|
+
sketchRun_stopped: "Drawing stopped",
|
|
4311
|
+
sketchRun_failed: "Drawing failed",
|
|
4312
|
+
sketchRunStop: "Stop drawing",
|
|
4313
|
+
sketchRunResume: "Allow drawing again",
|
|
4314
|
+
sketchAgentPreview: "Preview on completion · Beta",
|
|
4315
|
+
sketchAgentPreviewHint: "Off by default. Returns the canvas to the model on completion, adding image input usage.",
|
|
4316
|
+
sketchAgentPreview_on: "On",
|
|
4317
|
+
sketchAgentPreview_off: "Off",
|
|
3511
4318
|
sketchAgent_on: "On",
|
|
3512
4319
|
sketchAgent_off: "Off",
|
|
3513
4320
|
imageSettings: "Images",
|
|
@@ -4964,7 +5771,8 @@ window.__ModuleLoader__.load({
|
|
|
4964
5771
|
imageViewer: true,
|
|
4965
5772
|
imageAnnotations: true,
|
|
4966
5773
|
imageSketch: false,
|
|
4967
|
-
imageSketchAgent: false
|
|
5774
|
+
imageSketchAgent: false,
|
|
5775
|
+
imageSketchAgentPreview: false
|
|
4968
5776
|
});
|
|
4969
5777
|
function readImageFeatures(value = {}) {
|
|
4970
5778
|
return Object.fromEntries(Object.entries(IMAGE_FEATURE_DEFAULTS).map(([key, fallback]) => [key, typeof value?.[key] === "boolean" ? value[key] : fallback]));
|
|
@@ -5602,7 +6410,7 @@ window.__ModuleLoader__.load({
|
|
|
5602
6410
|
}
|
|
5603
6411
|
//#endregion
|
|
5604
6412
|
//#region src/version.js
|
|
5605
|
-
const PACKAGE_VERSION = "2.1.0-beta.
|
|
6413
|
+
const PACKAGE_VERSION = "2.1.0-beta.3";
|
|
5606
6414
|
//#endregion
|
|
5607
6415
|
//#region src/client-recovery.js
|
|
5608
6416
|
async function recoveryCall(rpc, endpoint, payload = {}, timeoutMs = 1e4) {
|
|
@@ -6172,6 +6980,7 @@ window.__ModuleLoader__.load({
|
|
|
6172
6980
|
imageEntryPoints: ["imageShortcut"],
|
|
6173
6981
|
sketchCanvas: ["imageSketch"],
|
|
6174
6982
|
sketchAgent: ["imageSketchAgent"],
|
|
6983
|
+
sketchAgentPreview: ["imageSketchAgentPreview"],
|
|
6175
6984
|
imageBrowsing: ["imageViewer", "imageAnnotations"]
|
|
6176
6985
|
});
|
|
6177
6986
|
function imageGroupValue(snapshot, group) {
|
|
@@ -7921,15 +8730,14 @@ window.__ModuleLoader__.load({
|
|
|
7921
8730
|
ctx.inject(["inputTriggers"], (triggerContext) => triggerContext.effect(() => triggerContext.get("inputTriggers").registerSource(createSketchTrigger({
|
|
7922
8731
|
enabled: () => {
|
|
7923
8732
|
const value = preference.getSnapshot();
|
|
7924
|
-
return value.imageSketch && value.imageEditing;
|
|
8733
|
+
return value.imageSketchAgent && value.imageSketch && value.imageEditing;
|
|
7925
8734
|
},
|
|
7926
|
-
open: (sessionId) => sketchOpeners.get(sessionId)?.(),
|
|
7927
8735
|
consume: (sessionId, span) => {
|
|
7928
8736
|
const actx = sessions.scope(sessionId);
|
|
7929
|
-
return sketchOpeners.has(sessionId) && actx?.bail(actx, "slash/input-
|
|
7930
|
-
|
|
8737
|
+
return sketchOpeners.has(sessionId) && actx?.bail(actx, "slash/input-insert-text", {
|
|
8738
|
+
text: "@sketch ",
|
|
7931
8739
|
span
|
|
7932
|
-
}
|
|
8740
|
+
}) === true;
|
|
7933
8741
|
}
|
|
7934
8742
|
})), "codex-subscription: Sketch trigger"));
|
|
7935
8743
|
ctx.inject(["inputTriggers"], (triggerContext) => triggerContext.effect(() => triggerContext.get("inputTriggers").registerSource(createImageTrigger({
|