Skip to content

Commit e5f665b

Browse files
committed
feat(toolbar): hidden-nodes dropdown with unhide + show all (#154)
1 parent 143dbc7 commit e5f665b

1 file changed

Lines changed: 128 additions & 0 deletions

File tree

src/routes/+page.svelte

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,50 @@
508508
509509
// App state
510510
let nodeCount = $state(0);
511+
let hiddenNodes = $state<import('$lib/nodes/types').NodeInstance[]>([]);
512+
let hiddenMenuOpen = $state(false);
513+
let hiddenOpenTimer: ReturnType<typeof setTimeout> | null = null;
514+
let hiddenCloseTimer: ReturnType<typeof setTimeout> | null = null;
515+
516+
function clearHiddenTimers() {
517+
if (hiddenOpenTimer) {
518+
clearTimeout(hiddenOpenTimer);
519+
hiddenOpenTimer = null;
520+
}
521+
if (hiddenCloseTimer) {
522+
clearTimeout(hiddenCloseTimer);
523+
hiddenCloseTimer = null;
524+
}
525+
}
526+
527+
function handleHiddenGroupEnter() {
528+
clearHiddenTimers();
529+
hiddenOpenTimer = setTimeout(() => {
530+
hiddenMenuOpen = true;
531+
hiddenOpenTimer = null;
532+
}, 250);
533+
}
534+
535+
function handleHiddenGroupLeave() {
536+
clearHiddenTimers();
537+
hiddenCloseTimer = setTimeout(() => {
538+
hiddenMenuOpen = false;
539+
hiddenCloseTimer = null;
540+
}, 180);
541+
}
542+
543+
function handleUnhide(nodeId: string) {
544+
historyStore.mutate(() => graphStore.updateNodeParams(nodeId, { _hidden: false }));
545+
}
546+
547+
function handleShowAll() {
548+
clearHiddenTimers();
549+
hiddenMenuOpen = false;
550+
const ids = hiddenNodes.map((n) => n.id);
551+
historyStore.mutate(() => {
552+
for (const id of ids) graphStore.updateNodeParams(id, { _hidden: false });
553+
});
554+
}
511555
let pyodideReady = $state(false);
512556
let pyodideLoading = $state(false);
513557
let simRunning = $state(false);
@@ -575,6 +619,7 @@
575619
576620
const unsubNodeCount = graphStore.nodesArray.subscribe((nodes) => {
577621
nodeCount = nodes.length;
622+
hiddenNodes = nodes.filter((n) => n.params?.['_hidden']);
578623
});
579624
580625
const unsubPyodide = pyodideState.subscribe((s) => {
@@ -1355,6 +1400,46 @@
13551400
</button>
13561401
</div>
13571402

1403+
<!-- Hidden nodes -->
1404+
{#if hiddenNodes.length > 0}
1405+
<!-- svelte-ignore a11y_no_static_element_interactions -->
1406+
<div
1407+
class="toolbar-group hidden-group"
1408+
onmouseenter={handleHiddenGroupEnter}
1409+
onmouseleave={handleHiddenGroupLeave}
1410+
>
1411+
<button
1412+
class="toolbar-btn hidden-btn"
1413+
use:tooltip={`${hiddenNodes.length} hidden node${hiddenNodes.length === 1 ? '' : 's'}`}
1414+
aria-label="Hidden nodes"
1415+
>
1416+
<Icon name="eye-off" size={16} />
1417+
<span class="hidden-badge">{hiddenNodes.length}</span>
1418+
</button>
1419+
{#if hiddenMenuOpen}
1420+
<div class="recent-menu" role="menu">
1421+
<div class="recent-menu-header">Hidden nodes</div>
1422+
{#each hiddenNodes as node (node.id)}
1423+
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions -->
1424+
<div class="recent-item" role="menuitem" tabindex="0" onclick={() => handleUnhide(node.id)}>
1425+
<Icon name="eye" size={14} />
1426+
<span class="recent-name" title={node.name}>{node.name}</span>
1427+
<span class="hidden-type">{node.type}</span>
1428+
</div>
1429+
{/each}
1430+
{#if hiddenNodes.length > 1}
1431+
<div class="recent-divider"></div>
1432+
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions -->
1433+
<div class="recent-item show-all" role="menuitem" tabindex="0" onclick={handleShowAll}>
1434+
<Icon name="eye" size={14} />
1435+
<span class="recent-name">Show all</span>
1436+
</div>
1437+
{/if}
1438+
</div>
1439+
{/if}
1440+
</div>
1441+
{/if}
1442+
13581443
<!-- Help -->
13591444
<div class="toolbar-group">
13601445
<button
@@ -1937,6 +2022,49 @@
19372022
background: color-mix(in srgb, var(--error) 15%, transparent);
19382023
}
19392024
2025+
/* Hidden-nodes group reuses .open-group/.recent-menu layout */
2026+
.hidden-group {
2027+
position: relative;
2028+
}
2029+
2030+
.hidden-btn {
2031+
position: relative;
2032+
}
2033+
2034+
.hidden-badge {
2035+
position: absolute;
2036+
top: -4px;
2037+
right: -4px;
2038+
min-width: 16px;
2039+
height: 16px;
2040+
padding: 0 4px;
2041+
border-radius: 8px;
2042+
background: var(--accent);
2043+
color: var(--surface);
2044+
font-size: 10px;
2045+
font-weight: 600;
2046+
display: flex;
2047+
align-items: center;
2048+
justify-content: center;
2049+
box-shadow: 0 0 0 2px var(--surface);
2050+
}
2051+
2052+
.hidden-type {
2053+
font-size: 10px;
2054+
color: var(--text-disabled);
2055+
font-family: var(--font-mono);
2056+
}
2057+
2058+
.recent-divider {
2059+
height: 1px;
2060+
background: var(--border);
2061+
margin: 4px 0;
2062+
}
2063+
2064+
.recent-item.show-all {
2065+
color: var(--accent);
2066+
}
2067+
19402068
.mutation-badge {
19412069
position: absolute;
19422070
top: -4px;

0 commit comments

Comments
 (0)