mirror of
https://github.com/PR0M3TH3AN/Artstr-Studio.git
synced 2026-11-13 00:50:09 +00:00
feat(book): J3.c — show-thread overlay on port hover
Hovering any in / out port of a threaded frame highlights every frame in the same chain. The head of the thread (first frame, no incoming link) gets a blue outline; the rest get green. Hover off clears the highlight. _bookCollectThreadChain walks backwards along incoming nextFrame refs to the head, then forward along outgoing refs to the tail. Cycle-safe and depth-capped. Makes long threads (e.g., a chapter flowing across many designed pages) immediately legible — useful in J5+ when threads start spanning more than two frames. Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
1 parent
4afe726704
commit
a419160b3a
1 file changed
+70
@@ -2088,6 +2088,20 @@
|
||||
0%,100% { box-shadow: 0 0 0 3px rgba(59,130,246,.9); }
|
||||
50% { box-shadow: 0 0 0 6px rgba(59,130,246,.35); }
|
||||
}
|
||||
|
||||
/* J3.c: thread-hover highlight. When the user hovers any port on
|
||||
a threaded frame, every frame in the same chain glows so the
|
||||
flow path is visible at a glance. Implemented by tagging
|
||||
chain-member freeLayers with `.threadHighlight` from a JS
|
||||
hover handler. */
|
||||
.freeLayer.threadHighlight {
|
||||
outline: 2px solid rgba(34,197,94,.85);
|
||||
outline-offset: 2px;
|
||||
transition: outline-color .12s;
|
||||
}
|
||||
.freeLayer.threadHighlight.threadHighlightHead {
|
||||
outline-color: rgba(59,130,246,.85);
|
||||
}
|
||||
.bookFrameIdBadge {
|
||||
position: absolute; bottom: 4px; left: 4px;
|
||||
font-size: .58rem; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
@@ -22662,6 +22676,54 @@
|
||||
layer.nextFrame = null;
|
||||
render();
|
||||
}
|
||||
// J3.c — walk forward + backward along nextFrame refs from
|
||||
// `layer` and tag every chain member's freeLayer wrapper. The
|
||||
// head of the thread (first frame, no incoming link) gets a
|
||||
// distinct blue outline so direction is readable.
|
||||
function _bookHighlightThread(layer) {
|
||||
_bookClearThreadHighlight();
|
||||
const chain = _bookCollectThreadChain(layer);
|
||||
if (!chain.length) return;
|
||||
chain.forEach((l, i) => {
|
||||
const el = document.querySelector(`.freeLayer[data-layer-id="${CSS.escape(l.id)}"]`);
|
||||
if (!el) return;
|
||||
el.classList.add('threadHighlight');
|
||||
if (i === 0) el.classList.add('threadHighlightHead');
|
||||
});
|
||||
}
|
||||
function _bookClearThreadHighlight() {
|
||||
document.querySelectorAll('.freeLayer.threadHighlight').forEach(el => {
|
||||
el.classList.remove('threadHighlight', 'threadHighlightHead');
|
||||
});
|
||||
}
|
||||
function _bookCollectThreadChain(layer) {
|
||||
// Walk backwards to the head, then forward to the tail.
|
||||
// Returns layers in flow order (head → ... → tail).
|
||||
const seen = new Set();
|
||||
// Backwards: find the layer whose nextFrame.frameId equals
|
||||
// current.frameId; loop until none exists.
|
||||
let head = layer;
|
||||
let guard = 0;
|
||||
while (guard++ < 1000) {
|
||||
const prev = _findBookFrameLayer(o => o !== head && o.nextFrame?.frameId === head.frameId);
|
||||
if (!prev || seen.has(prev.id)) break;
|
||||
seen.add(prev.id);
|
||||
head = prev;
|
||||
}
|
||||
const chain = [head];
|
||||
seen.add(head.id);
|
||||
let cursor = head;
|
||||
guard = 0;
|
||||
while (cursor.nextFrame?.frameId && guard++ < 1000) {
|
||||
const nxt = _findBookFrameLayer(o => o.frameId === cursor.nextFrame.frameId);
|
||||
if (!nxt || seen.has(nxt.id)) break;
|
||||
seen.add(nxt.id);
|
||||
chain.push(nxt);
|
||||
cursor = nxt;
|
||||
}
|
||||
return chain;
|
||||
}
|
||||
|
||||
function _bookPortOnClick(layer, portKind, ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
@@ -22713,6 +22775,10 @@
|
||||
inPort.dataset.framePort = 'in';
|
||||
inPort.dataset.frameId = myFrameId;
|
||||
inPort.addEventListener('click', (ev) => _bookPortOnClick(layer, 'in', ev));
|
||||
if (linkedIn || linkedOut) {
|
||||
inPort.addEventListener('mouseenter', () => _bookHighlightThread(layer));
|
||||
inPort.addEventListener('mouseleave', _bookClearThreadHighlight);
|
||||
}
|
||||
el.appendChild(inPort);
|
||||
|
||||
const outPort = document.createElement('div');
|
||||
@@ -22727,6 +22793,10 @@
|
||||
outPort.dataset.framePort = 'out';
|
||||
outPort.dataset.frameId = myFrameId;
|
||||
outPort.addEventListener('click', (ev) => _bookPortOnClick(layer, 'out', ev));
|
||||
if (linkedIn || linkedOut) {
|
||||
outPort.addEventListener('mouseenter', () => _bookHighlightThread(layer));
|
||||
outPort.addEventListener('mouseleave', _bookClearThreadHighlight);
|
||||
}
|
||||
el.appendChild(outPort);
|
||||
|
||||
const badge = document.createElement('div');
|
||||
|
||||
Reference in new issue
Block a user