@cr8rcho/alkahest 0.1.11 → 0.1.13
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/assets/dashboard.html +62 -7
- package/package.json +1 -1
|
@@ -64,6 +64,8 @@
|
|
|
64
64
|
#theme-toggle svg, #fit-btn svg { width: 16px; height: 16px; }
|
|
65
65
|
[data-theme="dark"] #theme-toggle .i-sun { display: block; } [data-theme="dark"] #theme-toggle .i-moon { display: none; }
|
|
66
66
|
[data-theme="light"] #theme-toggle .i-sun { display: none; } [data-theme="light"] #theme-toggle .i-moon { display: block; }
|
|
67
|
+
/* Fit + toggle are a tight pair (8px), set apart from the rest of the toolbar by its 16px gap. */
|
|
68
|
+
.toolbar-actions { display: inline-flex; align-items: center; gap: 8px; }
|
|
67
69
|
.brand { font-weight: 700; letter-spacing: 0.3px; white-space: nowrap; }
|
|
68
70
|
.brand .sub { color: var(--muted); font-weight: 400; margin-left: 6px; }
|
|
69
71
|
.counts { color: var(--muted); white-space: nowrap; }
|
|
@@ -165,8 +167,10 @@
|
|
|
165
167
|
<span><span class="edge" style="border-top-style: dotted; border-color: var(--edge)"></span>Contains</span>
|
|
166
168
|
<span><span class="edge" style="border-top-style: dashed; border-color: var(--edge)"></span>Call</span>
|
|
167
169
|
</div>
|
|
168
|
-
<
|
|
169
|
-
|
|
170
|
+
<div class="toolbar-actions">
|
|
171
|
+
<button class="theme-toggle" id="fit-btn" aria-label="Fit to view" title="Fit to view"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3M16 3h3a2 2 0 0 1 2 2v3M8 21H5a2 2 0 0 1-2-2v-3M16 21h3a2 2 0 0 0 2-2v-3"/></svg></button>
|
|
172
|
+
<button class="theme-toggle" id="theme-toggle" aria-label="Toggle theme" title="Toggle theme"><svg class="i-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg><svg class="i-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg></button>
|
|
173
|
+
</div>
|
|
170
174
|
</header>
|
|
171
175
|
<main>
|
|
172
176
|
<svg id="graph">
|
|
@@ -602,18 +606,54 @@
|
|
|
602
606
|
const DRAG_THRESHOLD = 4;
|
|
603
607
|
let down = null; // {node|null, x, y, moved, pointerId}
|
|
604
608
|
|
|
609
|
+
// Active pointers for multi-touch. Two pointers down → pinch-to-zoom.
|
|
610
|
+
const pointers = new Map(); // pointerId -> {x, y}
|
|
611
|
+
let pinch = null; // { dist, midX, midY } for the running gesture
|
|
612
|
+
|
|
613
|
+
function startPinch() {
|
|
614
|
+
const pts = [...pointers.values()];
|
|
615
|
+
const dx = pts[0].x - pts[1].x, dy = pts[0].y - pts[1].y;
|
|
616
|
+
const rect = svg.getBoundingClientRect();
|
|
617
|
+
pinch = {
|
|
618
|
+
dist: Math.hypot(dx, dy) || 1,
|
|
619
|
+
midX: (pts[0].x + pts[1].x) / 2 - rect.left,
|
|
620
|
+
midY: (pts[0].y + pts[1].y) / 2 - rect.top,
|
|
621
|
+
};
|
|
622
|
+
// a pinch is purely a zoom — abandon any in-progress tap / pan / node drag
|
|
623
|
+
dragging = null; panStart = null; down = null;
|
|
624
|
+
}
|
|
625
|
+
|
|
605
626
|
function onNodeDown(ev, n) {
|
|
606
627
|
ev.stopPropagation();
|
|
607
|
-
|
|
628
|
+
pointers.set(ev.pointerId, { x: ev.clientX, y: ev.clientY });
|
|
608
629
|
svg.setPointerCapture(ev.pointerId);
|
|
630
|
+
if (pointers.size === 2) { startPinch(); return; }
|
|
631
|
+
down = { node: n, x: ev.clientX, y: ev.clientY, moved: false, pointerId: ev.pointerId };
|
|
609
632
|
}
|
|
610
633
|
svg.addEventListener("pointerdown", (ev) => {
|
|
634
|
+
pointers.set(ev.pointerId, { x: ev.clientX, y: ev.clientY });
|
|
635
|
+
svg.setPointerCapture(ev.pointerId);
|
|
636
|
+
if (pointers.size === 2) { startPinch(); return; }
|
|
611
637
|
if (down) return; // already started on a node
|
|
612
638
|
down = { node: null, x: ev.clientX, y: ev.clientY, moved: false, pointerId: ev.pointerId };
|
|
613
639
|
panStart = { x: ev.clientX - tx, y: ev.clientY - ty };
|
|
614
|
-
svg.setPointerCapture(ev.pointerId);
|
|
615
640
|
});
|
|
616
641
|
svg.addEventListener("pointermove", (ev) => {
|
|
642
|
+
if (pointers.has(ev.pointerId)) pointers.set(ev.pointerId, { x: ev.clientX, y: ev.clientY });
|
|
643
|
+
if (pinch && pointers.size >= 2) {
|
|
644
|
+
const pts = [...pointers.values()];
|
|
645
|
+
const dx = pts[0].x - pts[1].x, dy = pts[0].y - pts[1].y;
|
|
646
|
+
const dist = Math.hypot(dx, dy) || 1;
|
|
647
|
+
const rect = svg.getBoundingClientRect();
|
|
648
|
+
const mx = (pts[0].x + pts[1].x) / 2 - rect.left, my = (pts[0].y + pts[1].y) / 2 - rect.top;
|
|
649
|
+
// pan by how far the two-finger midpoint moved, then zoom around that midpoint
|
|
650
|
+
tx += mx - pinch.midX; ty += my - pinch.midY;
|
|
651
|
+
const nk = Math.min(4, Math.max(0.2, k * (dist / pinch.dist)));
|
|
652
|
+
tx = mx - (mx - tx) * (nk / k); ty = my - (my - ty) * (nk / k); k = nk;
|
|
653
|
+
pinch.dist = dist; pinch.midX = mx; pinch.midY = my;
|
|
654
|
+
applyTransform();
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
617
657
|
if (!down) return;
|
|
618
658
|
if (!down.moved && Math.hypot(ev.clientX - down.x, ev.clientY - down.y) > DRAG_THRESHOLD) {
|
|
619
659
|
down.moved = true;
|
|
@@ -623,8 +663,21 @@
|
|
|
623
663
|
if (dragging) { const w = toWorld(ev.clientX, ev.clientY); dragging.x = w.x; dragging.y = w.y; dragging.vx = 0; dragging.vy = 0; } // free 2D since it's force-based
|
|
624
664
|
else if (panStart) { tx = ev.clientX - panStart.x; ty = ev.clientY - panStart.y; applyTransform(); }
|
|
625
665
|
});
|
|
626
|
-
|
|
627
|
-
|
|
666
|
+
function onPointerUp(ev) {
|
|
667
|
+
pointers.delete(ev.pointerId);
|
|
668
|
+
if (pinch) {
|
|
669
|
+
if (pointers.size < 2) {
|
|
670
|
+
pinch = null;
|
|
671
|
+
if (pointers.size === 1) {
|
|
672
|
+
// one finger left after a pinch → resume panning from there (no jump)
|
|
673
|
+
const [id, p] = [...pointers.entries()][0];
|
|
674
|
+
down = { node: null, x: p.x, y: p.y, moved: true, pointerId: id };
|
|
675
|
+
panStart = { x: p.x - tx, y: p.y - ty };
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
return; // a pinch gesture never counts as a tap
|
|
679
|
+
}
|
|
680
|
+
if (ev.type !== "pointercancel" && down && !down.moved) { // didn't move = tap
|
|
628
681
|
if (down.node) select(down.node);
|
|
629
682
|
else clearSelection();
|
|
630
683
|
} else if (dragging) {
|
|
@@ -632,7 +685,9 @@
|
|
|
632
685
|
alpha = 0.06;
|
|
633
686
|
}
|
|
634
687
|
dragging = null; panStart = null; down = null;
|
|
635
|
-
}
|
|
688
|
+
}
|
|
689
|
+
svg.addEventListener("pointerup", onPointerUp);
|
|
690
|
+
svg.addEventListener("pointercancel", onPointerUp);
|
|
636
691
|
svg.addEventListener("wheel", (ev) => {
|
|
637
692
|
ev.preventDefault();
|
|
638
693
|
const rect = svg.getBoundingClientRect();
|