|
508 | 508 |
|
509 | 509 | // App state |
510 | 510 | 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 | + } |
511 | 555 | let pyodideReady = $state(false); |
512 | 556 | let pyodideLoading = $state(false); |
513 | 557 | let simRunning = $state(false); |
|
575 | 619 |
|
576 | 620 | const unsubNodeCount = graphStore.nodesArray.subscribe((nodes) => { |
577 | 621 | nodeCount = nodes.length; |
| 622 | + hiddenNodes = nodes.filter((n) => n.params?.['_hidden']); |
578 | 623 | }); |
579 | 624 |
|
580 | 625 | const unsubPyodide = pyodideState.subscribe((s) => { |
|
1355 | 1400 | </button> |
1356 | 1401 | </div> |
1357 | 1402 |
|
| 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 | + |
1358 | 1443 | <!-- Help --> |
1359 | 1444 | <div class="toolbar-group"> |
1360 | 1445 | <button |
|
1937 | 2022 | background: color-mix(in srgb, var(--error) 15%, transparent); |
1938 | 2023 | } |
1939 | 2024 |
|
| 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 | +
|
1940 | 2068 | .mutation-badge { |
1941 | 2069 | position: absolute; |
1942 | 2070 | top: -4px; |
|
0 commit comments