@markdy/renderer-dom 0.7.10 → 0.7.12
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/dist/index.js +328 -2
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -367,6 +367,66 @@ function createActorEl(name, def, assetDefs, assetOverrides) {
|
|
|
367
367
|
el = createFigureEl(def);
|
|
368
368
|
break;
|
|
369
369
|
}
|
|
370
|
+
case "service":
|
|
371
|
+
case "client":
|
|
372
|
+
case "db":
|
|
373
|
+
case "queue": {
|
|
374
|
+
const card = document.createElement("div");
|
|
375
|
+
const label = document.createElement("div");
|
|
376
|
+
label.textContent = def.args[0] ?? "";
|
|
377
|
+
Object.assign(label.style, {
|
|
378
|
+
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
379
|
+
fontSize: `${def.size ?? 16}px`,
|
|
380
|
+
fontWeight: "600",
|
|
381
|
+
color: "#e2e8f0",
|
|
382
|
+
textAlign: "center",
|
|
383
|
+
lineHeight: "1.2",
|
|
384
|
+
pointerEvents: "none",
|
|
385
|
+
whiteSpace: "nowrap",
|
|
386
|
+
overflow: "hidden",
|
|
387
|
+
textOverflow: "ellipsis",
|
|
388
|
+
maxWidth: "100%"
|
|
389
|
+
});
|
|
390
|
+
Object.assign(card.style, {
|
|
391
|
+
width: "180px",
|
|
392
|
+
height: "84px",
|
|
393
|
+
boxSizing: "border-box",
|
|
394
|
+
border: "1px solid #475569",
|
|
395
|
+
background: "#0f172a",
|
|
396
|
+
display: "grid",
|
|
397
|
+
placeItems: "center",
|
|
398
|
+
padding: "10px 12px"
|
|
399
|
+
});
|
|
400
|
+
if (def.type === "service") {
|
|
401
|
+
card.style.borderRadius = "12px";
|
|
402
|
+
card.style.background = "linear-gradient(180deg, #1e293b 0%, #0f172a 100%)";
|
|
403
|
+
} else if (def.type === "client") {
|
|
404
|
+
const bar = document.createElement("div");
|
|
405
|
+
Object.assign(bar.style, {
|
|
406
|
+
position: "absolute",
|
|
407
|
+
top: "8px",
|
|
408
|
+
left: "8px",
|
|
409
|
+
right: "8px",
|
|
410
|
+
height: "8px",
|
|
411
|
+
borderRadius: "6px",
|
|
412
|
+
background: "#334155"
|
|
413
|
+
});
|
|
414
|
+
card.style.position = "relative";
|
|
415
|
+
card.style.borderRadius = "10px";
|
|
416
|
+
card.style.paddingTop = "20px";
|
|
417
|
+
card.appendChild(bar);
|
|
418
|
+
} else if (def.type === "db") {
|
|
419
|
+
card.style.borderRadius = "50% / 14%";
|
|
420
|
+
card.style.background = "linear-gradient(180deg, #334155 0%, #0f172a 70%)";
|
|
421
|
+
card.style.boxShadow = "inset 0 10px 0 rgba(226, 232, 240, 0.12)";
|
|
422
|
+
} else if (def.type === "queue") {
|
|
423
|
+
card.style.borderRadius = "8px";
|
|
424
|
+
card.style.boxShadow = "-6px -6px 0 rgba(30, 41, 59, 0.9), -12px -12px 0 rgba(15, 23, 42, 0.9)";
|
|
425
|
+
}
|
|
426
|
+
card.appendChild(label);
|
|
427
|
+
el = card;
|
|
428
|
+
break;
|
|
429
|
+
}
|
|
370
430
|
default: {
|
|
371
431
|
const div = document.createElement("div");
|
|
372
432
|
div.style.width = "100px";
|
|
@@ -437,7 +497,22 @@ function buildAnimations(ast, actorEls, scene, assetOverrides, faceSwaps) {
|
|
|
437
497
|
const el = actorEls.get(ev.actor);
|
|
438
498
|
const s = states.get(ev.actor);
|
|
439
499
|
if (!el || !s) continue;
|
|
440
|
-
buildAction(
|
|
500
|
+
buildAction(
|
|
501
|
+
ev,
|
|
502
|
+
el,
|
|
503
|
+
s,
|
|
504
|
+
baseOpts,
|
|
505
|
+
delayMs,
|
|
506
|
+
durMs,
|
|
507
|
+
ast,
|
|
508
|
+
states,
|
|
509
|
+
actorEls,
|
|
510
|
+
scene,
|
|
511
|
+
assetOverrides,
|
|
512
|
+
faceSwaps,
|
|
513
|
+
anims,
|
|
514
|
+
txFor(ev.actor)
|
|
515
|
+
);
|
|
441
516
|
}
|
|
442
517
|
return anims;
|
|
443
518
|
}
|
|
@@ -541,8 +616,259 @@ function buildCameraAction(ev, scene, ast, baseOpts, anims, cameraState) {
|
|
|
541
616
|
break;
|
|
542
617
|
}
|
|
543
618
|
}
|
|
544
|
-
|
|
619
|
+
var FLOW_STROKE_BY_ACTION = {
|
|
620
|
+
request: "#38bdf8",
|
|
621
|
+
response: "#a78bfa",
|
|
622
|
+
emit: "#f59e0b"
|
|
623
|
+
};
|
|
624
|
+
function actorSizeByType(type) {
|
|
625
|
+
switch (type) {
|
|
626
|
+
case "service":
|
|
627
|
+
case "client":
|
|
628
|
+
case "db":
|
|
629
|
+
case "queue":
|
|
630
|
+
return { width: 180, height: 84 };
|
|
631
|
+
case "box":
|
|
632
|
+
return { width: 100, height: 100 };
|
|
633
|
+
case "caption":
|
|
634
|
+
return { width: 260, height: 56 };
|
|
635
|
+
case "figure":
|
|
636
|
+
return { width: 120, height: 170 };
|
|
637
|
+
default:
|
|
638
|
+
return { width: 140, height: 42 };
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
function actorCenter(state, actorType) {
|
|
642
|
+
const { width, height } = actorSizeByType(actorType);
|
|
643
|
+
return {
|
|
644
|
+
x: state.x + width / 2,
|
|
645
|
+
y: state.y + height / 2
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
function actorRect(state, actorType) {
|
|
649
|
+
const { width, height } = actorSizeByType(actorType);
|
|
650
|
+
return {
|
|
651
|
+
x1: state.x,
|
|
652
|
+
y1: state.y,
|
|
653
|
+
x2: state.x + width,
|
|
654
|
+
y2: state.y + height
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
function inflateRect(rect, pad) {
|
|
658
|
+
return {
|
|
659
|
+
x1: rect.x1 - pad,
|
|
660
|
+
y1: rect.y1 - pad,
|
|
661
|
+
x2: rect.x2 + pad,
|
|
662
|
+
y2: rect.y2 + pad
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
function segmentIntersectsRect(a, b, rect) {
|
|
666
|
+
const minX = Math.min(a.x, b.x);
|
|
667
|
+
const maxX = Math.max(a.x, b.x);
|
|
668
|
+
const minY = Math.min(a.y, b.y);
|
|
669
|
+
const maxY = Math.max(a.y, b.y);
|
|
670
|
+
const horizontal = Math.abs(a.y - b.y) < 1e-3;
|
|
671
|
+
const vertical = Math.abs(a.x - b.x) < 1e-3;
|
|
672
|
+
if (horizontal) {
|
|
673
|
+
const yHit = a.y > rect.y1 && a.y < rect.y2;
|
|
674
|
+
const xOverlap = maxX > rect.x1 && minX < rect.x2;
|
|
675
|
+
return yHit && xOverlap;
|
|
676
|
+
}
|
|
677
|
+
if (vertical) {
|
|
678
|
+
const xHit = a.x > rect.x1 && a.x < rect.x2;
|
|
679
|
+
const yOverlap = maxY > rect.y1 && minY < rect.y2;
|
|
680
|
+
return xHit && yOverlap;
|
|
681
|
+
}
|
|
682
|
+
return false;
|
|
683
|
+
}
|
|
684
|
+
function countPathIntersections(points, obstacles) {
|
|
685
|
+
let hits = 0;
|
|
686
|
+
for (let i = 0; i < points.length - 1; i++) {
|
|
687
|
+
for (const obstacle of obstacles) {
|
|
688
|
+
if (segmentIntersectsRect(points[i], points[i + 1], obstacle)) hits++;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
return hits;
|
|
692
|
+
}
|
|
693
|
+
function toPathD(points) {
|
|
694
|
+
return points.map((p, i) => `${i === 0 ? "M" : "L"} ${round1(p.x)} ${round1(p.y)}`).join(" ");
|
|
695
|
+
}
|
|
696
|
+
function round1(n) {
|
|
697
|
+
return Math.round(n * 10) / 10;
|
|
698
|
+
}
|
|
699
|
+
function polylineLength(points) {
|
|
700
|
+
let total = 0;
|
|
701
|
+
for (let i = 0; i < points.length - 1; i++) {
|
|
702
|
+
total += Math.hypot(points[i + 1].x - points[i].x, points[i + 1].y - points[i].y);
|
|
703
|
+
}
|
|
704
|
+
return Math.max(1, total);
|
|
705
|
+
}
|
|
706
|
+
function pointAtDistance(points, dist) {
|
|
707
|
+
let remain = dist;
|
|
708
|
+
for (let i = 0; i < points.length - 1; i++) {
|
|
709
|
+
const a = points[i];
|
|
710
|
+
const b = points[i + 1];
|
|
711
|
+
const seg = Math.hypot(b.x - a.x, b.y - a.y);
|
|
712
|
+
if (remain <= seg || i === points.length - 2) {
|
|
713
|
+
const t = seg <= 0 ? 0 : remain / seg;
|
|
714
|
+
return { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t };
|
|
715
|
+
}
|
|
716
|
+
remain -= seg;
|
|
717
|
+
}
|
|
718
|
+
return points[0];
|
|
719
|
+
}
|
|
720
|
+
function routeFlowPath(sourceName, targetName, sourceState, targetState, states, ast, lane) {
|
|
721
|
+
const sourceRect = actorRect(sourceState, ast.actors[sourceName]?.type ?? "box");
|
|
722
|
+
const targetRect = actorRect(targetState, ast.actors[targetName]?.type ?? "box");
|
|
723
|
+
const laneShift = lane * 18;
|
|
724
|
+
const sourceCenter = actorCenter(sourceState, ast.actors[sourceName]?.type ?? "box");
|
|
725
|
+
const targetCenter = actorCenter(targetState, ast.actors[targetName]?.type ?? "box");
|
|
726
|
+
const horizontalPrimary = Math.abs(targetCenter.x - sourceCenter.x) >= Math.abs(targetCenter.y - sourceCenter.y);
|
|
727
|
+
const source = horizontalPrimary ? { x: targetCenter.x >= sourceCenter.x ? sourceRect.x2 : sourceRect.x1, y: sourceCenter.y } : { x: sourceCenter.x, y: targetCenter.y >= sourceCenter.y ? sourceRect.y2 : sourceRect.y1 };
|
|
728
|
+
const target = horizontalPrimary ? { x: targetCenter.x >= sourceCenter.x ? targetRect.x1 : targetRect.x2, y: targetCenter.y } : { x: targetCenter.x, y: targetCenter.y >= sourceCenter.y ? targetRect.y1 : targetRect.y2 };
|
|
729
|
+
const obstacles = [];
|
|
730
|
+
for (const [name, state] of states.entries()) {
|
|
731
|
+
if (name === sourceName || name === targetName) continue;
|
|
732
|
+
const type = ast.actors[name]?.type ?? "box";
|
|
733
|
+
obstacles.push(inflateRect(actorRect(state, type), 8));
|
|
734
|
+
}
|
|
735
|
+
const candidates = [];
|
|
736
|
+
if (Math.abs(source.y - target.y) < 1e-3 || Math.abs(source.x - target.x) < 1e-3) {
|
|
737
|
+
candidates.push([source, target]);
|
|
738
|
+
}
|
|
739
|
+
const midX = round1((source.x + target.x) / 2 + laneShift);
|
|
740
|
+
const midY = round1((source.y + target.y) / 2 + laneShift);
|
|
741
|
+
candidates.push(
|
|
742
|
+
[source, { x: midX, y: source.y }, { x: midX, y: target.y }, target],
|
|
743
|
+
[source, { x: source.x, y: midY }, { x: target.x, y: midY }, target]
|
|
744
|
+
);
|
|
745
|
+
const minObstacleY = obstacles.length ? Math.min(...obstacles.map((o) => o.y1)) : Math.min(source.y, target.y);
|
|
746
|
+
const maxObstacleY = obstacles.length ? Math.max(...obstacles.map((o) => o.y2)) : Math.max(source.y, target.y);
|
|
747
|
+
const topLane = Math.max(16, minObstacleY - 24 - lane * 12);
|
|
748
|
+
const bottomLane = Math.min(ast.meta.height - 16, maxObstacleY + 24 + lane * 12);
|
|
749
|
+
candidates.push(
|
|
750
|
+
[source, { x: source.x, y: topLane }, { x: target.x, y: topLane }, target],
|
|
751
|
+
[source, { x: source.x, y: bottomLane }, { x: target.x, y: bottomLane }, target]
|
|
752
|
+
);
|
|
753
|
+
let best = candidates[0];
|
|
754
|
+
let bestHits = Number.POSITIVE_INFINITY;
|
|
755
|
+
for (const candidate of candidates) {
|
|
756
|
+
const hits = countPathIntersections(candidate, obstacles);
|
|
757
|
+
if (hits < bestHits) {
|
|
758
|
+
best = candidate;
|
|
759
|
+
bestHits = hits;
|
|
760
|
+
if (hits === 0) break;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
return best;
|
|
764
|
+
}
|
|
765
|
+
function ensureEdgeLayer(scene) {
|
|
766
|
+
const existing = scene.querySelector("svg[data-markdy-edge-layer='1']");
|
|
767
|
+
if (existing) return existing;
|
|
768
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
769
|
+
svg.setAttribute("data-markdy-edge-layer", "1");
|
|
770
|
+
Object.assign(svg.style, {
|
|
771
|
+
position: "absolute",
|
|
772
|
+
inset: "0",
|
|
773
|
+
width: "100%",
|
|
774
|
+
height: "100%",
|
|
775
|
+
overflow: "visible",
|
|
776
|
+
pointerEvents: "none",
|
|
777
|
+
zIndex: "95"
|
|
778
|
+
});
|
|
779
|
+
scene.appendChild(svg);
|
|
780
|
+
return svg;
|
|
781
|
+
}
|
|
782
|
+
function renderFlowEdge(ev, sourceName, targetName, sourceState, targetState, states, ast, lane, scene, baseOpts, anims) {
|
|
783
|
+
const styleToken = String(ev.params.style ?? "");
|
|
784
|
+
const isDashed = styleToken === "dashed" || styleToken === "fire_and_forget" || ev.action === "response";
|
|
785
|
+
const stroke = FLOW_STROKE_BY_ACTION[ev.action] ?? "#38bdf8";
|
|
786
|
+
const points = routeFlowPath(
|
|
787
|
+
sourceName,
|
|
788
|
+
targetName,
|
|
789
|
+
sourceState,
|
|
790
|
+
targetState,
|
|
791
|
+
states,
|
|
792
|
+
ast,
|
|
793
|
+
lane
|
|
794
|
+
);
|
|
795
|
+
const length = polylineLength(points);
|
|
796
|
+
const midPoint = pointAtDistance(points, length * 0.5);
|
|
797
|
+
const pathD = toPathD(points);
|
|
798
|
+
const svg = ensureEdgeLayer(scene);
|
|
799
|
+
const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
800
|
+
group.setAttribute("data-markdy-flow-edge", "1");
|
|
801
|
+
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
802
|
+
path.setAttribute("d", pathD);
|
|
803
|
+
path.setAttribute("fill", "none");
|
|
804
|
+
path.setAttribute("stroke", stroke);
|
|
805
|
+
path.setAttribute("stroke-width", "2.5");
|
|
806
|
+
path.setAttribute("data-markdy-flow-action", ev.action);
|
|
807
|
+
path.style.strokeDasharray = isDashed ? "8 6" : `${length}`;
|
|
808
|
+
path.style.strokeDashoffset = `${length}`;
|
|
809
|
+
group.appendChild(path);
|
|
810
|
+
const marker = document.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
811
|
+
marker.setAttribute("r", "3");
|
|
812
|
+
marker.setAttribute("fill", stroke);
|
|
813
|
+
marker.style.offsetPath = `path('${pathD}')`;
|
|
814
|
+
marker.style.offsetDistance = "0%";
|
|
815
|
+
marker.style.opacity = "0";
|
|
816
|
+
group.appendChild(marker);
|
|
817
|
+
const labelRaw = String(ev.params.label ?? "");
|
|
818
|
+
if (labelRaw) {
|
|
819
|
+
const label = labelRaw.length > 28 ? `${labelRaw.slice(0, 27)}\u2026` : labelRaw;
|
|
820
|
+
const labelEl = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
|
821
|
+
labelEl.setAttribute("x", `${round1(midPoint.x)}`);
|
|
822
|
+
labelEl.setAttribute("y", `${round1(midPoint.y - 8)}`);
|
|
823
|
+
labelEl.setAttribute("text-anchor", "middle");
|
|
824
|
+
labelEl.setAttribute("font-size", "12");
|
|
825
|
+
labelEl.setAttribute("fill", "#cbd5e1");
|
|
826
|
+
labelEl.textContent = label;
|
|
827
|
+
labelEl.setAttribute("data-full-label", labelRaw);
|
|
828
|
+
group.appendChild(labelEl);
|
|
829
|
+
}
|
|
830
|
+
svg.appendChild(group);
|
|
831
|
+
anims.push(path.animate([{ strokeDashoffset: length }, { strokeDashoffset: 0 }], baseOpts));
|
|
832
|
+
anims.push(
|
|
833
|
+
marker.animate(
|
|
834
|
+
[{ offsetDistance: "0%", opacity: 1 }, { offsetDistance: "100%", opacity: 1 }],
|
|
835
|
+
baseOpts
|
|
836
|
+
)
|
|
837
|
+
);
|
|
838
|
+
const fadeOutDelay = Number(baseOpts.delay ?? 0) + Number(baseOpts.duration ?? 0);
|
|
839
|
+
anims.push(
|
|
840
|
+
group.animate([{ opacity: 1 }, { opacity: 0 }], {
|
|
841
|
+
delay: fadeOutDelay,
|
|
842
|
+
duration: 140,
|
|
843
|
+
fill: "forwards"
|
|
844
|
+
})
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
function buildAction(ev, el, s, baseOpts, delayMs, durMs, ast, states, _actorEls, scene, assetOverrides, faceSwaps, anims, txFn) {
|
|
545
848
|
switch (ev.action) {
|
|
849
|
+
case "request":
|
|
850
|
+
case "response":
|
|
851
|
+
case "emit": {
|
|
852
|
+
const targetActorName = String(ev.params.to ?? "");
|
|
853
|
+
if (!targetActorName) break;
|
|
854
|
+
const targetState = states.get(targetActorName);
|
|
855
|
+
if (!targetState) break;
|
|
856
|
+
const lane = ev.line % 5 - 2;
|
|
857
|
+
renderFlowEdge(
|
|
858
|
+
ev,
|
|
859
|
+
ev.actor,
|
|
860
|
+
targetActorName,
|
|
861
|
+
s,
|
|
862
|
+
targetState,
|
|
863
|
+
states,
|
|
864
|
+
ast,
|
|
865
|
+
lane,
|
|
866
|
+
scene,
|
|
867
|
+
baseOpts,
|
|
868
|
+
anims
|
|
869
|
+
);
|
|
870
|
+
break;
|
|
871
|
+
}
|
|
546
872
|
// ── move ────────────────────────────────────────────────────────────────
|
|
547
873
|
case "move": {
|
|
548
874
|
const toArr = ev.params.to;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markdy/renderer-dom",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.12",
|
|
4
4
|
"description": "Web Animations API renderer for MarkdyScript scenes.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"access": "public"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@markdy/core": "0.7.
|
|
46
|
+
"@markdy/core": "0.7.12"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"jsdom": "^29.1.1",
|