sketchmark 2.1.9 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -226
- package/bin/editor-ui.cjs +211 -84
- package/bin/preview-ui.d.ts +1 -0
- package/dist/src/edit.d.ts +1 -0
- package/dist/src/edit.js +9 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +25 -1
- package/dist/src/render/embed.js +26 -16
- package/dist/src/render/index.d.ts +6 -0
- package/dist/src/render/index.js +27 -0
- package/dist/tests/run.js +3 -1
- package/package.json +12 -1
- package/skills/sketchmark/SKILL.md +16 -0
- package/skills/sketchmark/animation.md +67 -0
- package/skills/sketchmark/design.md +59 -0
- package/skills/sketchmark/kernel.md +354 -0
- package/skills/sketchmark/render.md +166 -0
package/bin/editor-ui.cjs
CHANGED
|
@@ -699,6 +699,7 @@ async function deleteTreeElement(id) {
|
|
|
699
699
|
}
|
|
700
700
|
|
|
701
701
|
function renderTree() {
|
|
702
|
+
const elementsScroll = captureElementsTreeScroll();
|
|
702
703
|
const canvas = doc && doc.canvas ? doc.canvas : {};
|
|
703
704
|
const canvasSummary = Math.round(valueOr(canvas.width, 1)) + "x" + Math.round(valueOr(canvas.height, 1));
|
|
704
705
|
const canvasBody =
|
|
@@ -785,6 +786,7 @@ function renderTree() {
|
|
|
785
786
|
treeRoot.appendChild(row);
|
|
786
787
|
}
|
|
787
788
|
resizeElementsTree();
|
|
789
|
+
restoreElementsTreeScroll(elementsScroll);
|
|
788
790
|
}
|
|
789
791
|
|
|
790
792
|
function resizeElementsTree() {
|
|
@@ -802,8 +804,28 @@ function resizeElementsTree() {
|
|
|
802
804
|
const available = treeRect.bottom - bodyRect.top - fixedRect.height - paddingBottom - 6;
|
|
803
805
|
treeRoot.style.maxHeight = Math.max(72, Math.floor(available)) + "px";
|
|
804
806
|
}
|
|
805
|
-
|
|
806
|
-
function
|
|
807
|
+
|
|
808
|
+
function captureElementsTreeScroll() {
|
|
809
|
+
const treeRoot = document.getElementById("elementsTree");
|
|
810
|
+
return treeRoot ? { top: treeRoot.scrollTop, left: treeRoot.scrollLeft } : { top: 0, left: 0 };
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
function restoreElementsTreeScroll(scroll) {
|
|
814
|
+
const treeRoot = document.getElementById("elementsTree");
|
|
815
|
+
if (!treeRoot || !scroll) return;
|
|
816
|
+
const top = Math.max(0, Number(scroll.top) || 0);
|
|
817
|
+
const left = Math.max(0, Number(scroll.left) || 0);
|
|
818
|
+
treeRoot.scrollTop = top;
|
|
819
|
+
treeRoot.scrollLeft = left;
|
|
820
|
+
requestAnimationFrame(() => {
|
|
821
|
+
const nextRoot = document.getElementById("elementsTree");
|
|
822
|
+
if (!nextRoot) return;
|
|
823
|
+
nextRoot.scrollTop = top;
|
|
824
|
+
nextRoot.scrollLeft = left;
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
function bindCanvasInputs() {
|
|
807
829
|
const bind = (id, callback) => {
|
|
808
830
|
const input = document.getElementById(id);
|
|
809
831
|
if (!input) return;
|
|
@@ -2191,12 +2213,14 @@ function scheduleSidebarKeyframe(property, valueReader) {
|
|
|
2191
2213
|
}, 120);
|
|
2192
2214
|
}
|
|
2193
2215
|
|
|
2194
|
-
function readNumberInput(id) {
|
|
2195
|
-
const input = document.getElementById(id);
|
|
2196
|
-
if (!input) return NaN;
|
|
2197
|
-
const
|
|
2198
|
-
|
|
2199
|
-
|
|
2216
|
+
function readNumberInput(id) {
|
|
2217
|
+
const input = document.getElementById(id);
|
|
2218
|
+
if (!input) return NaN;
|
|
2219
|
+
const raw = String(input.value ?? "").trim();
|
|
2220
|
+
if (!raw) return NaN;
|
|
2221
|
+
const value = Number(raw);
|
|
2222
|
+
return Number.isFinite(value) ? value : NaN;
|
|
2223
|
+
}
|
|
2200
2224
|
|
|
2201
2225
|
function readTextInput(id) {
|
|
2202
2226
|
const input = document.getElementById(id);
|
|
@@ -2224,11 +2248,11 @@ async function mutate(path, body, options) {
|
|
|
2224
2248
|
if (currentTime > duration) currentTime = duration;
|
|
2225
2249
|
rebuildElementIndex();
|
|
2226
2250
|
if (selectedId && !findElement(selectedId)) selectedId = "";
|
|
2227
|
-
if (refreshTree) renderTree();
|
|
2228
|
-
if (refreshInspector) renderInspector();
|
|
2229
|
-
if (refreshTimeline) renderTimeline();
|
|
2230
|
-
requestDraw();
|
|
2231
|
-
}
|
|
2251
|
+
if (refreshTree) renderTree();
|
|
2252
|
+
if (refreshInspector) renderInspector();
|
|
2253
|
+
if (refreshTimeline) renderTimeline();
|
|
2254
|
+
if (!options || options.requestDraw !== false) requestDraw();
|
|
2255
|
+
}
|
|
2232
2256
|
|
|
2233
2257
|
stageWrap.addEventListener("wheel", (event) => {
|
|
2234
2258
|
if (!stage.contains(event.target)) return;
|
|
@@ -2349,9 +2373,11 @@ stage.addEventListener("pointermove", (event) => {
|
|
|
2349
2373
|
drag.changed = drag.changed || Math.abs(dx) > 0.25 || Math.abs(dy) > 0.25;
|
|
2350
2374
|
let x = drag.x;
|
|
2351
2375
|
let y = drag.y;
|
|
2352
|
-
let rotation = drag.rotation;
|
|
2353
|
-
let scale = drag.scale;
|
|
2354
|
-
|
|
2376
|
+
let rotation = drag.rotation;
|
|
2377
|
+
let scale = drag.scale;
|
|
2378
|
+
let scaleX = drag.scaleX;
|
|
2379
|
+
let scaleY = drag.scaleY;
|
|
2380
|
+
if (drag.mode === "move") {
|
|
2355
2381
|
x = Math.round(drag.x + dx);
|
|
2356
2382
|
y = Math.round(drag.y + dy);
|
|
2357
2383
|
setInput("propX", x);
|
|
@@ -2363,18 +2389,25 @@ stage.addEventListener("pointermove", (event) => {
|
|
|
2363
2389
|
rotation = Math.round((drag.rotation + delta) * 100) / 100;
|
|
2364
2390
|
setInput("propRotation", rotation);
|
|
2365
2391
|
previewDraggedTransform("rotate(" + delta + " " + drag.center.x + " " + drag.center.y + ")");
|
|
2366
|
-
} else {
|
|
2367
|
-
const startDistance = Math.max(distance(drag.center, drag.start), 1);
|
|
2368
|
-
const nextDistance = Math.max(distance(drag.center, point), 1);
|
|
2369
|
-
const ratio = Math.max(0.05, nextDistance / startDistance);
|
|
2370
|
-
drag.changed = drag.changed || Math.abs(ratio - 1) > 0.005;
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2392
|
+
} else {
|
|
2393
|
+
const startDistance = Math.max(distance(drag.center, drag.start), 1);
|
|
2394
|
+
const nextDistance = Math.max(distance(drag.center, point), 1);
|
|
2395
|
+
const ratio = Math.max(0.05, nextDistance / startDistance);
|
|
2396
|
+
drag.changed = drag.changed || Math.abs(ratio - 1) > 0.005;
|
|
2397
|
+
if (drag.usesAxisScale) {
|
|
2398
|
+
scaleX = scaleComponentValue(drag.scaleX, ratio);
|
|
2399
|
+
scaleY = scaleComponentValue(drag.scaleY, ratio);
|
|
2400
|
+
setInput("propScaleX", scaleX);
|
|
2401
|
+
setInput("propScaleY", scaleY);
|
|
2402
|
+
} else {
|
|
2403
|
+
scale = scaleComponentValue(drag.scale, ratio);
|
|
2404
|
+
setInput("propScale", scale);
|
|
2405
|
+
}
|
|
2406
|
+
previewDraggedTransform("translate(" + drag.center.x + " " + drag.center.y + ") scale(" + ratio + ") translate(" + (-drag.center.x) + " " + (-drag.center.y) + ")");
|
|
2407
|
+
}
|
|
2408
|
+
drag.value = { x, y, rotation, scale, scaleX, scaleY };
|
|
2409
|
+
refreshHandles();
|
|
2410
|
+
});
|
|
2378
2411
|
|
|
2379
2412
|
stage.addEventListener("pointerup", finishDrag);
|
|
2380
2413
|
stage.addEventListener("pointercancel", finishDrag);
|
|
@@ -2400,21 +2433,25 @@ async function finishDrag() {
|
|
|
2400
2433
|
|
|
2401
2434
|
function startDrag(event, target, mode) {
|
|
2402
2435
|
if (!target || isElementLocked(target.id) || isElementHidden(target.id)) return;
|
|
2403
|
-
const element = findElement(target.id);
|
|
2404
|
-
if (!element) return;
|
|
2405
|
-
const resolved = findResolvedElement(target.id) || element;
|
|
2406
|
-
|
|
2436
|
+
const element = findElement(target.id);
|
|
2437
|
+
if (!element) return;
|
|
2438
|
+
const resolved = findResolvedElement(target.id) || element;
|
|
2439
|
+
const scaleState = resolvedScaleState(resolved, element);
|
|
2440
|
+
drag = {
|
|
2407
2441
|
id: target.id,
|
|
2408
2442
|
target,
|
|
2409
2443
|
mode,
|
|
2410
2444
|
start: parentPoint(event, target),
|
|
2411
2445
|
center: targetCenterInParent(target),
|
|
2412
|
-
x: Number(resolved.x ?? element.x ?? 0),
|
|
2413
|
-
y: Number(resolved.y ?? element.y ?? 0),
|
|
2414
|
-
rotation: Number(resolved.rotation ?? element.rotation ?? 0),
|
|
2415
|
-
scale:
|
|
2416
|
-
|
|
2417
|
-
|
|
2446
|
+
x: Number(resolved.x ?? element.x ?? 0),
|
|
2447
|
+
y: Number(resolved.y ?? element.y ?? 0),
|
|
2448
|
+
rotation: Number(resolved.rotation ?? element.rotation ?? 0),
|
|
2449
|
+
scale: scaleState.scale,
|
|
2450
|
+
scaleX: scaleState.scaleX,
|
|
2451
|
+
scaleY: scaleState.scaleY,
|
|
2452
|
+
usesAxisScale: scaleState.usesAxisScale,
|
|
2453
|
+
transform: target.getAttribute("transform") || "",
|
|
2454
|
+
changed: false,
|
|
2418
2455
|
value: null
|
|
2419
2456
|
};
|
|
2420
2457
|
event.preventDefault();
|
|
@@ -2427,17 +2464,22 @@ async function commitDrag(snapshot) {
|
|
|
2427
2464
|
if (!element || !snapshot.value) return;
|
|
2428
2465
|
if (snapshot.mode === "move") {
|
|
2429
2466
|
await commitEditedProperty(element, "position", [snapshot.value.x, snapshot.value.y]);
|
|
2430
|
-
} else if (snapshot.mode === "rotate") {
|
|
2431
|
-
await commitEditedProperty(element, "rotation", snapshot.value.rotation);
|
|
2432
|
-
} else if (snapshot.mode === "scale") {
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
}
|
|
2467
|
+
} else if (snapshot.mode === "rotate") {
|
|
2468
|
+
await commitEditedProperty(element, "rotation", snapshot.value.rotation);
|
|
2469
|
+
} else if (snapshot.mode === "scale") {
|
|
2470
|
+
if (snapshot.usesAxisScale) {
|
|
2471
|
+
await commitEditedProperty(element, "scaleX", snapshot.value.scaleX, { refreshTree: false, refreshInspector: false, refreshTimeline: false, requestDraw: false });
|
|
2472
|
+
await commitEditedProperty(element, "scaleY", snapshot.value.scaleY);
|
|
2473
|
+
} else {
|
|
2474
|
+
await commitEditedProperty(element, "scale", snapshot.value.scale);
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
async function commitEditedProperty(element, property, value, options) {
|
|
2480
|
+
if (!ensureElementEditable(element.id)) return;
|
|
2481
|
+
await mutate(apiPath("/keyframe"), { id: element.id, property, value, time: currentTime, curvePreset: "linear" }, options);
|
|
2482
|
+
}
|
|
2441
2483
|
|
|
2442
2484
|
function ensureElementEditable(id) {
|
|
2443
2485
|
if (!id) return false;
|
|
@@ -2540,13 +2582,13 @@ function drawHandles(target) {
|
|
|
2540
2582
|
const old = svg.querySelector("#__sketchmark_handles");
|
|
2541
2583
|
if (old) old.remove();
|
|
2542
2584
|
let box;
|
|
2543
|
-
let matrix;
|
|
2544
|
-
try {
|
|
2545
|
-
box = target.getBBox();
|
|
2546
|
-
matrix = elementMatrixInSvg(target, svg);
|
|
2547
|
-
} catch {
|
|
2548
|
-
return;
|
|
2549
|
-
}
|
|
2585
|
+
let matrix;
|
|
2586
|
+
try {
|
|
2587
|
+
box = editorHandleBox(target.getBBox());
|
|
2588
|
+
matrix = elementMatrixInSvg(target, svg);
|
|
2589
|
+
} catch {
|
|
2590
|
+
return;
|
|
2591
|
+
}
|
|
2550
2592
|
if (!matrix) return;
|
|
2551
2593
|
const topLeft = matrixPoint(svg, matrix, box.x, box.y);
|
|
2552
2594
|
const topRight = matrixPoint(svg, matrix, box.x + box.width, box.y);
|
|
@@ -2611,17 +2653,34 @@ function elementMatrixInSvg(target, svg) {
|
|
|
2611
2653
|
function targetCenterInParent(target) {
|
|
2612
2654
|
const svg = target.ownerSVGElement || stage.querySelector("svg");
|
|
2613
2655
|
if (!svg || !target.getBBox || !target.getCTM) return { x: 0, y: 0 };
|
|
2614
|
-
try {
|
|
2615
|
-
const box = target.getBBox();
|
|
2616
|
-
const targetMatrix = target.getCTM();
|
|
2617
|
-
const parent = target.parentNode && target.parentNode.getCTM ? target.parentNode : svg;
|
|
2618
|
-
const parentMatrix = parent.getCTM ? parent.getCTM() : null;
|
|
2656
|
+
try {
|
|
2657
|
+
const box = editorHandleBox(target.getBBox());
|
|
2658
|
+
const targetMatrix = target.getCTM();
|
|
2659
|
+
const parent = target.parentNode && target.parentNode.getCTM ? target.parentNode : svg;
|
|
2660
|
+
const parentMatrix = parent.getCTM ? parent.getCTM() : null;
|
|
2619
2661
|
if (!targetMatrix || !parentMatrix) return { x: box.x + box.width / 2, y: box.y + box.height / 2 };
|
|
2620
2662
|
return matrixPoint(svg, parentMatrix.inverse().multiply(targetMatrix), box.x + box.width / 2, box.y + box.height / 2);
|
|
2621
2663
|
} catch {
|
|
2622
|
-
return { x: 0, y: 0 };
|
|
2623
|
-
}
|
|
2624
|
-
}
|
|
2664
|
+
return { x: 0, y: 0 };
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
function editorHandleBox(box) {
|
|
2668
|
+
const minSize = 18;
|
|
2669
|
+
const x = Number(box && box.x || 0);
|
|
2670
|
+
const y = Number(box && box.y || 0);
|
|
2671
|
+
const width = Math.max(0, Number(box && box.width || 0));
|
|
2672
|
+
const height = Math.max(0, Number(box && box.height || 0));
|
|
2673
|
+
const centerX = x + width / 2;
|
|
2674
|
+
const centerY = y + height / 2;
|
|
2675
|
+
const stableWidth = width < minSize ? minSize : width;
|
|
2676
|
+
const stableHeight = height < minSize ? minSize : height;
|
|
2677
|
+
return {
|
|
2678
|
+
x: centerX - stableWidth / 2,
|
|
2679
|
+
y: centerY - stableHeight / 2,
|
|
2680
|
+
width: stableWidth,
|
|
2681
|
+
height: stableHeight
|
|
2682
|
+
};
|
|
2683
|
+
}
|
|
2625
2684
|
function matrixPoint(svg, matrix, x, y) {
|
|
2626
2685
|
const point = svg.createSVGPoint();
|
|
2627
2686
|
point.x = x;
|
|
@@ -2644,14 +2703,33 @@ function svgNode(name) {
|
|
|
2644
2703
|
function angleAround(center, point) {
|
|
2645
2704
|
return Math.atan2(point.y - center.y, point.x - center.x) * 180 / Math.PI;
|
|
2646
2705
|
}
|
|
2647
|
-
function distance(a, b) {
|
|
2648
|
-
const dx = a.x - b.x;
|
|
2649
|
-
const dy = a.y - b.y;
|
|
2650
|
-
return Math.sqrt(dx * dx + dy * dy);
|
|
2651
|
-
}
|
|
2652
|
-
function
|
|
2653
|
-
|
|
2654
|
-
|
|
2706
|
+
function distance(a, b) {
|
|
2707
|
+
const dx = a.x - b.x;
|
|
2708
|
+
const dy = a.y - b.y;
|
|
2709
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
2710
|
+
}
|
|
2711
|
+
function scaleComponentValue(value, ratio) {
|
|
2712
|
+
const sign = Number(value) < 0 ? -1 : 1;
|
|
2713
|
+
const next = Number(value) * Number(ratio);
|
|
2714
|
+
const clamped = Math.max(0.05, Math.abs(Number.isFinite(next) ? next : sign * 0.05));
|
|
2715
|
+
return Math.round(sign * clamped * 100) / 100;
|
|
2716
|
+
}
|
|
2717
|
+
function resolvedScaleState(resolved, element) {
|
|
2718
|
+
const tracks = element && element.timeline && element.timeline.tracks ? element.timeline.tracks : {};
|
|
2719
|
+
const usesAxisScale = resolved.scaleX !== undefined || resolved.scaleY !== undefined || element.scaleX !== undefined || element.scaleY !== undefined || tracks.scaleX || tracks.scaleY;
|
|
2720
|
+
const baseScale = finiteNumber(resolved.scale ?? element.scale, 1);
|
|
2721
|
+
const scaleX = finiteNumber(resolved.scaleX ?? element.scaleX, baseScale);
|
|
2722
|
+
const scaleY = finiteNumber(resolved.scaleY ?? element.scaleY, baseScale);
|
|
2723
|
+
return {
|
|
2724
|
+
scale: baseScale,
|
|
2725
|
+
scaleX,
|
|
2726
|
+
scaleY,
|
|
2727
|
+
usesAxisScale: Boolean(usesAxisScale)
|
|
2728
|
+
};
|
|
2729
|
+
}
|
|
2730
|
+
function supportsPosition(element) {
|
|
2731
|
+
return element && ["path","point","text","image","group"].includes(element.type);
|
|
2732
|
+
}
|
|
2655
2733
|
function setInput(id, value) {
|
|
2656
2734
|
const input = document.getElementById(id);
|
|
2657
2735
|
if (input) input.value = value;
|
|
@@ -2752,18 +2830,67 @@ function setPointInputs(prefix, point) {
|
|
|
2752
2830
|
setInput(prefix + "X", valueOr(value[0], 0));
|
|
2753
2831
|
setInput(prefix + "Y", valueOr(value[1], 0));
|
|
2754
2832
|
}
|
|
2755
|
-
function originPointValue(element) {
|
|
2756
|
-
if (Array.isArray(element && element.origin) && element.origin.length === 2) {
|
|
2757
|
-
const x = Number(element.origin[0]);
|
|
2758
|
-
const y = Number(element.origin[1]);
|
|
2759
|
-
if (Number.isFinite(x) && Number.isFinite(y)) return [x, y];
|
|
2760
|
-
}
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2833
|
+
function originPointValue(element) {
|
|
2834
|
+
if (Array.isArray(element && element.origin) && element.origin.length === 2) {
|
|
2835
|
+
const x = Number(element.origin[0]);
|
|
2836
|
+
const y = Number(element.origin[1]);
|
|
2837
|
+
if (Number.isFinite(x) && Number.isFinite(y)) return [x, y];
|
|
2838
|
+
}
|
|
2839
|
+
if (element && element.type === "group") {
|
|
2840
|
+
return [
|
|
2841
|
+
finiteNumber(element.x, 0) + finiteNumber(element.width, 0) / 2,
|
|
2842
|
+
finiteNumber(element.y, 0) + finiteNumber(element.height, 0) / 2
|
|
2843
|
+
];
|
|
2844
|
+
}
|
|
2845
|
+
const x = Number(element && element.x);
|
|
2846
|
+
const y = Number(element && element.y);
|
|
2847
|
+
if (element && (element.type === "text" || element.type === "point") && Number.isFinite(x) && Number.isFinite(y)) return [x, y];
|
|
2848
|
+
const box = editorElementBox(element);
|
|
2849
|
+
if (box) return [box.x + box.width / 2, box.y + box.height / 2];
|
|
2850
|
+
if (Number.isFinite(x) && Number.isFinite(y)) return [x, y];
|
|
2851
|
+
return [0, 0];
|
|
2852
|
+
}
|
|
2853
|
+
function valueOr(value, fallback) { return value === undefined ? fallback : value; }
|
|
2854
|
+
function editorElementBox(element) {
|
|
2855
|
+
if (!element) return null;
|
|
2856
|
+
const target = element.id ? stage.querySelector("#" + cssId(element.id)) : null;
|
|
2857
|
+
if (target && target.getBBox) {
|
|
2858
|
+
try {
|
|
2859
|
+
const box = target.getBBox();
|
|
2860
|
+
if (Number.isFinite(box.x) && Number.isFinite(box.y) && Number.isFinite(box.width) && Number.isFinite(box.height)) {
|
|
2861
|
+
return { x: box.x, y: box.y, width: box.width, height: box.height };
|
|
2862
|
+
}
|
|
2863
|
+
} catch {}
|
|
2864
|
+
}
|
|
2865
|
+
if (element.type === "image" || element.type === "group") {
|
|
2866
|
+
const x = Number(element.x);
|
|
2867
|
+
const y = Number(element.y);
|
|
2868
|
+
const width = Number(element.width);
|
|
2869
|
+
const height = Number(element.height);
|
|
2870
|
+
if ([x, y, width, height].every(Number.isFinite)) return { x, y, width, height };
|
|
2871
|
+
}
|
|
2872
|
+
if (element.type === "path" && typeof element.d === "string") return pathDataBox(element.d);
|
|
2873
|
+
return null;
|
|
2874
|
+
}
|
|
2875
|
+
function pathDataBox(data) {
|
|
2876
|
+
const numbers = String(data || "").match(/-?\\d+(?:\\.\\d+)?(?:e[+-]?\\d+)?/gi);
|
|
2877
|
+
if (!numbers || numbers.length < 2) return null;
|
|
2878
|
+
let minX = Infinity;
|
|
2879
|
+
let minY = Infinity;
|
|
2880
|
+
let maxX = -Infinity;
|
|
2881
|
+
let maxY = -Infinity;
|
|
2882
|
+
for (let index = 0; index < numbers.length - 1; index += 2) {
|
|
2883
|
+
const x = Number(numbers[index]);
|
|
2884
|
+
const y = Number(numbers[index + 1]);
|
|
2885
|
+
if (!Number.isFinite(x) || !Number.isFinite(y)) continue;
|
|
2886
|
+
minX = Math.min(minX, x);
|
|
2887
|
+
minY = Math.min(minY, y);
|
|
2888
|
+
maxX = Math.max(maxX, x);
|
|
2889
|
+
maxY = Math.max(maxY, y);
|
|
2890
|
+
}
|
|
2891
|
+
if (![minX, minY, maxX, maxY].every(Number.isFinite)) return null;
|
|
2892
|
+
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
|
|
2893
|
+
}
|
|
2767
2894
|
function clipRadiusValue(element) {
|
|
2768
2895
|
if (!element || !element.clip || typeof element.clip.d !== "string") return 0;
|
|
2769
2896
|
const numbers = element.clip.d.match(/-?\\d+(?:\\.\\d+)?(?:e[+-]?\\d+)?/gi);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function previewHtml(title: string): string;
|
package/dist/src/edit.d.ts
CHANGED
|
@@ -58,3 +58,4 @@ export declare function listTimelineTracks(document: VisualDocument, id: string)
|
|
|
58
58
|
}>;
|
|
59
59
|
export declare function roundedRectClipPath(x: number, y: number, width: number, height: number, radius?: number): string;
|
|
60
60
|
export declare function imageRoundedClip(element: Pick<ImageElement, "x" | "y" | "width" | "height">, radius?: number): ClipShape;
|
|
61
|
+
export { deleteElement as remove, findElementById as find, insertElementPreset as insertPreset, listElementReferences as listReferences, listTimelineTracks as listTracks, removeTimelineKeyframe as removeKeyframe, reorderElement as reorder, setElementProperty as setProperty, setTimelineKeyframe as setKeyframe };
|
package/dist/src/edit.js
CHANGED
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.listElementReferences = listElementReferences;
|
|
4
|
+
exports.listReferences = listElementReferences;
|
|
4
5
|
exports.findElementById = findElementById;
|
|
6
|
+
exports.find = findElementById;
|
|
5
7
|
exports.insertElementPreset = insertElementPreset;
|
|
8
|
+
exports.insertPreset = insertElementPreset;
|
|
6
9
|
exports.reorderElement = reorderElement;
|
|
10
|
+
exports.reorder = reorderElement;
|
|
7
11
|
exports.deleteElement = deleteElement;
|
|
12
|
+
exports.remove = deleteElement;
|
|
8
13
|
exports.setElementProperty = setElementProperty;
|
|
14
|
+
exports.setProperty = setElementProperty;
|
|
9
15
|
exports.setTimelineKeyframe = setTimelineKeyframe;
|
|
16
|
+
exports.setKeyframe = setTimelineKeyframe;
|
|
10
17
|
exports.removeTimelineKeyframe = removeTimelineKeyframe;
|
|
18
|
+
exports.removeKeyframe = removeTimelineKeyframe;
|
|
11
19
|
exports.listTimelineTracks = listTimelineTracks;
|
|
20
|
+
exports.listTracks = listTimelineTracks;
|
|
12
21
|
exports.roundedRectClipPath = roundedRectClipPath;
|
|
13
22
|
exports.imageRoundedClip = imageRoundedClip;
|
|
14
23
|
const animatable_1 = require("./animatable");
|
package/dist/src/index.d.ts
CHANGED
|
@@ -5,8 +5,10 @@ export * from "./diagnostics";
|
|
|
5
5
|
export * from "./schema";
|
|
6
6
|
export * from "./keyframes";
|
|
7
7
|
export * from "./edit";
|
|
8
|
+
export * as edit from "./edit";
|
|
8
9
|
export { deleteElement, insertElementPreset, reorderElement } from "./edit";
|
|
9
10
|
export * from "./animatable";
|
|
11
|
+
export * as render from "./render";
|
|
10
12
|
export * from "./render/svg";
|
|
11
13
|
export * from "./render/html";
|
|
12
14
|
export * from "./render/embed";
|
package/dist/src/index.js
CHANGED
|
@@ -10,11 +10,33 @@ var __createBinding = (this && this.__createBinding) || (Object.create ? (functi
|
|
|
10
10
|
if (k2 === undefined) k2 = k;
|
|
11
11
|
o[k2] = m[k];
|
|
12
12
|
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
13
18
|
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
19
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
20
|
};
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
16
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.reorderElement = exports.insertElementPreset = exports.deleteElement = void 0;
|
|
39
|
+
exports.render = exports.reorderElement = exports.insertElementPreset = exports.deleteElement = exports.edit = void 0;
|
|
18
40
|
__exportStar(require("./types"), exports);
|
|
19
41
|
__exportStar(require("./validate"), exports);
|
|
20
42
|
__exportStar(require("./normalize"), exports);
|
|
@@ -22,11 +44,13 @@ __exportStar(require("./diagnostics"), exports);
|
|
|
22
44
|
__exportStar(require("./schema"), exports);
|
|
23
45
|
__exportStar(require("./keyframes"), exports);
|
|
24
46
|
__exportStar(require("./edit"), exports);
|
|
47
|
+
exports.edit = __importStar(require("./edit"));
|
|
25
48
|
var edit_1 = require("./edit");
|
|
26
49
|
Object.defineProperty(exports, "deleteElement", { enumerable: true, get: function () { return edit_1.deleteElement; } });
|
|
27
50
|
Object.defineProperty(exports, "insertElementPreset", { enumerable: true, get: function () { return edit_1.insertElementPreset; } });
|
|
28
51
|
Object.defineProperty(exports, "reorderElement", { enumerable: true, get: function () { return edit_1.reorderElement; } });
|
|
29
52
|
__exportStar(require("./animatable"), exports);
|
|
53
|
+
exports.render = __importStar(require("./render"));
|
|
30
54
|
__exportStar(require("./render/svg"), exports);
|
|
31
55
|
__exportStar(require("./render/html"), exports);
|
|
32
56
|
__exportStar(require("./render/embed"), exports);
|
package/dist/src/render/embed.js
CHANGED
|
@@ -19,6 +19,7 @@ function renderToEmbedHtml(document, options = {}) {
|
|
|
19
19
|
const initialFrameIndex = frameIndexForTime(initialTime, frameTimes);
|
|
20
20
|
const initialFrame = frames[initialFrameIndex] ?? frames[0] ?? (0, svg_1.renderToSvg)(document, options);
|
|
21
21
|
const chromeBackground = escapeHtml(String(options.chromeBackground ?? "transparent"));
|
|
22
|
+
const mp4MuxerRuntimeSource = inlineMp4MuxerRuntime(mp4_muxer_source_1.MP4_MUXER_SOURCE);
|
|
22
23
|
const statusLabel = escapeHtml(title || "Sketchmark embed");
|
|
23
24
|
const payload = {
|
|
24
25
|
title,
|
|
@@ -225,9 +226,11 @@ function renderToEmbedHtml(document, options = {}) {
|
|
|
225
226
|
</details>` : ""}
|
|
226
227
|
<div id="meta">${statusLabel}</div>
|
|
227
228
|
</div>
|
|
229
|
+
<script>
|
|
230
|
+
${mp4MuxerRuntimeSource}
|
|
231
|
+
</script>
|
|
228
232
|
<script>
|
|
229
233
|
const payload = ${serializeForScript(payload)};
|
|
230
|
-
const mp4MuxerSource = ${serializeForScript(mp4_muxer_source_1.MP4_MUXER_SOURCE)};
|
|
231
234
|
const stage = document.getElementById("stage");
|
|
232
235
|
const playButton = document.getElementById("play");
|
|
233
236
|
const slider = document.getElementById("time");
|
|
@@ -244,7 +247,6 @@ function renderToEmbedHtml(document, options = {}) {
|
|
|
244
247
|
};
|
|
245
248
|
let metaTimer = 0;
|
|
246
249
|
let exportBusy = false;
|
|
247
|
-
let mp4MuxerModulePromise = null;
|
|
248
250
|
|
|
249
251
|
function clampTime(value) {
|
|
250
252
|
if (!payload.duration) return 0;
|
|
@@ -372,9 +374,7 @@ function renderToEmbedHtml(document, options = {}) {
|
|
|
372
374
|
}
|
|
373
375
|
|
|
374
376
|
function loadSvgImage(svg) {
|
|
375
|
-
|
|
376
|
-
const url = URL.createObjectURL(blob);
|
|
377
|
-
return loadImage(url).finally(() => URL.revokeObjectURL(url));
|
|
377
|
+
return loadImage(svgDataUrl(svg));
|
|
378
378
|
}
|
|
379
379
|
|
|
380
380
|
function loadImage(url) {
|
|
@@ -386,6 +386,10 @@ function renderToEmbedHtml(document, options = {}) {
|
|
|
386
386
|
});
|
|
387
387
|
}
|
|
388
388
|
|
|
389
|
+
function svgDataUrl(svg) {
|
|
390
|
+
return "data:image/svg+xml;charset=utf-8," + encodeURIComponent(svg);
|
|
391
|
+
}
|
|
392
|
+
|
|
389
393
|
function canvasToBlob(canvas, type, quality) {
|
|
390
394
|
return new Promise((resolve, reject) => {
|
|
391
395
|
canvas.toBlob((blob) => {
|
|
@@ -431,17 +435,9 @@ function renderToEmbedHtml(document, options = {}) {
|
|
|
431
435
|
}
|
|
432
436
|
|
|
433
437
|
async function loadMp4Muxer() {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
mp4MuxerModulePromise = import(url)
|
|
438
|
-
.catch((error) => {
|
|
439
|
-
mp4MuxerModulePromise = null;
|
|
440
|
-
throw error;
|
|
441
|
-
})
|
|
442
|
-
.finally(() => URL.revokeObjectURL(url));
|
|
443
|
-
}
|
|
444
|
-
return mp4MuxerModulePromise;
|
|
438
|
+
const module = globalThis.__SKETCHMARK_MP4_MUXER__;
|
|
439
|
+
if (module && module.Muxer && module.ArrayBufferTarget) return module;
|
|
440
|
+
throw new Error("MP4 runtime could not be initialized in this host.");
|
|
445
441
|
}
|
|
446
442
|
|
|
447
443
|
async function rasterBlob(format) {
|
|
@@ -686,3 +682,17 @@ function serializeForScript(value) {
|
|
|
686
682
|
.replace(/\u2028/g, "\\u2028")
|
|
687
683
|
.replace(/\u2029/g, "\\u2029");
|
|
688
684
|
}
|
|
685
|
+
function inlineMp4MuxerRuntime(source) {
|
|
686
|
+
const withoutExportBlock = source.replace(/\s*export\s*\{[\s\S]*?\}\s*;?\s*$/, "");
|
|
687
|
+
const runtime = `${withoutExportBlock}
|
|
688
|
+
globalThis.__SKETCHMARK_MP4_MUXER__ = {
|
|
689
|
+
ArrayBufferTarget,
|
|
690
|
+
FileSystemWritableFileStreamTarget,
|
|
691
|
+
Muxer,
|
|
692
|
+
StreamTarget
|
|
693
|
+
};`;
|
|
694
|
+
return escapeInlineScript(runtime);
|
|
695
|
+
}
|
|
696
|
+
function escapeInlineScript(value) {
|
|
697
|
+
return value.replace(/<\/script/gi, "<\\/script").replace(/<!--/g, "<\\!--");
|
|
698
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.embedHtml = exports.html = exports.resolvedSvg = exports.svg = void 0;
|
|
18
|
+
__exportStar(require("./svg"), exports);
|
|
19
|
+
__exportStar(require("./html"), exports);
|
|
20
|
+
__exportStar(require("./embed"), exports);
|
|
21
|
+
var svg_1 = require("./svg");
|
|
22
|
+
Object.defineProperty(exports, "svg", { enumerable: true, get: function () { return svg_1.renderToSvg; } });
|
|
23
|
+
Object.defineProperty(exports, "resolvedSvg", { enumerable: true, get: function () { return svg_1.renderResolvedSvg; } });
|
|
24
|
+
var html_1 = require("./html");
|
|
25
|
+
Object.defineProperty(exports, "html", { enumerable: true, get: function () { return html_1.renderToHtml; } });
|
|
26
|
+
var embed_1 = require("./embed");
|
|
27
|
+
Object.defineProperty(exports, "embedHtml", { enumerable: true, get: function () { return embed_1.renderToEmbedHtml; } });
|
package/dist/tests/run.js
CHANGED
|
@@ -137,7 +137,9 @@ test("renders a self-contained interactive embed HTML shell", () => {
|
|
|
137
137
|
assert(html.includes('data-export-format="svg"'), "should include export controls");
|
|
138
138
|
assert(html.includes('data-export-format="mp4"'), "should include mp4 export");
|
|
139
139
|
assert(html.includes("__SKETCHMARK_EMBED__"), "should expose a runtime controller");
|
|
140
|
-
assert(html.includes("
|
|
140
|
+
assert(html.includes("__SKETCHMARK_MP4_MUXER__"), "should inline the mp4 muxer runtime");
|
|
141
|
+
assert(!html.includes("import(url)"), "should avoid blob-based dynamic module imports");
|
|
142
|
+
assert(html.includes("data:image/svg+xml;charset=utf-8,"), "should use data URLs for SVG rasterization inside embed hosts");
|
|
141
143
|
assert(html.includes("sketchmark-rendered"), "should notify host frames when ready");
|
|
142
144
|
});
|
|
143
145
|
test("resolves element-local timeline tracks", () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sketchmark",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Render kernel for Sketchmark visual documents.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -30,11 +30,21 @@
|
|
|
30
30
|
"require": "./dist/src/edit.js",
|
|
31
31
|
"default": "./dist/src/edit.js"
|
|
32
32
|
},
|
|
33
|
+
"./render": {
|
|
34
|
+
"types": "./dist/src/render/index.d.ts",
|
|
35
|
+
"require": "./dist/src/render/index.js",
|
|
36
|
+
"default": "./dist/src/render/index.js"
|
|
37
|
+
},
|
|
33
38
|
"./editor": {
|
|
34
39
|
"types": "./bin/editor-ui.d.ts",
|
|
35
40
|
"require": "./bin/editor-ui.cjs",
|
|
36
41
|
"default": "./bin/editor-ui.cjs"
|
|
37
42
|
},
|
|
43
|
+
"./preview": {
|
|
44
|
+
"types": "./bin/preview-ui.d.ts",
|
|
45
|
+
"require": "./bin/preview-ui.cjs",
|
|
46
|
+
"default": "./bin/preview-ui.cjs"
|
|
47
|
+
},
|
|
38
48
|
"./schema": {
|
|
39
49
|
"default": "./schema/visual.schema.json"
|
|
40
50
|
}
|
|
@@ -52,6 +62,7 @@
|
|
|
52
62
|
"files": [
|
|
53
63
|
"bin",
|
|
54
64
|
"dist",
|
|
65
|
+
"skills",
|
|
55
66
|
"schema",
|
|
56
67
|
"README.md"
|
|
57
68
|
],
|