@@ -3,6 +3,7 @@ import { scrollAndSelectNodeById } from './graph.js';
33
44const sidebar = document . getElementById ( 'sidebar' ) ;
55export let sidebarSticky = false ;
6+ let lastSidebarTab = null ;
67
78export function showSidebar ( ) {
89 sidebar . style . transform = 'translateX(0)' ;
@@ -24,7 +25,6 @@ export function showSidebarContent(d, fromHover = false) {
2425 if ( archiveProgramIds && archiveProgramIds . includes ( d . id ) ) {
2526 starHtml = '<span style="position:relative;top:0.05em;left:0.15em;font-size:1.6em;color:#FFD600;z-index:10;" title="MAP-elites member" aria-label="MAP-elites member">★</span>' ;
2627 }
27- // Locator icon button (left of close X)
2828 let locatorBtn = '<button id="sidebar-locator-btn" title="Locate selected node" aria-label="Locate selected node" style="position:absolute;top:0.05em;right:2.5em;font-size:1.5em;background:none;border:none;color:#FFD600;cursor:pointer;z-index:10;line-height:1;filter:drop-shadow(0 0 2px #FFD600);">⦿</button>' ;
2929 let closeBtn = '<button id="sidebar-close-btn" style="position:absolute;top:0.05em;right:0.15em;font-size:1.6em;background:none;border:none;color:#888;cursor:pointer;z-index:10;line-height:1;">×</button>' ;
3030 let openLink = '<div style="text-align:center;margin:-1em 0 1.2em 0;"><a href="/program/' + d . id + '" target="_blank" class="open-in-new" style="font-size:0.95em;">[open in new window]</a></div>' ;
@@ -33,17 +33,45 @@ export function showSidebarContent(d, fromHover = false) {
3333 let tabNames = [ ] ;
3434 if ( d . code && typeof d . code === 'string' && d . code . trim ( ) !== '' ) tabNames . push ( 'Code' ) ;
3535 if ( d . prompts && typeof d . prompts === 'object' && Object . keys ( d . prompts ) . length > 0 ) tabNames . push ( 'Prompts' ) ;
36- if ( tabNames . length > 0 ) {
37- tabHtml = '<div id="sidebar-tab-bar" style="display:flex;gap:0.7em;margin-bottom:0.7em;">' +
38- tabNames . map ( ( name , i ) => `<span class="sidebar-tab${ i === 0 ?' active' :'' } " data-tab="${ name } ">${ name } </span>` ) . join ( '' ) + '</div>' ;
39- tabContentHtml = '<div id="sidebar-tab-content">' ;
40- if ( tabNames [ 0 ] === 'Code' ) tabContentHtml += `<pre class="sidebar-code-pre">${ d . code } </pre>` ;
41- if ( tabNames [ 0 ] === 'Prompts' ) {
36+ const children = allNodeData . filter ( n => n . parent_id === d . id ) ;
37+ if ( children . length > 0 ) tabNames . push ( 'Children' ) ;
38+ let activeTab = lastSidebarTab && tabNames . includes ( lastSidebarTab ) ? lastSidebarTab : tabNames [ 0 ] ;
39+
40+ // Helper to render tab content
41+ function renderSidebarTabContent ( tabName , d , children ) {
42+ if ( tabName === 'Code' ) {
43+ return `<pre class="sidebar-code-pre">${ d . code } </pre>` ;
44+ }
45+ if ( tabName === 'Prompts' ) {
46+ let html = '' ;
4247 for ( const [ k , v ] of Object . entries ( d . prompts ) ) {
43- tabContentHtml += `<div style="margin-bottom:0.7em;"><b>${ k } :</b><pre class="sidebar-pre">${ v } </pre></div>` ;
48+ html += `<div style="margin-bottom:0.7em;"><b>${ k } :</b><pre class="sidebar-pre">${ v } </pre></div>` ;
4449 }
50+ return html ;
4551 }
46- tabContentHtml += '</div>' ;
52+ if ( tabName === 'Children' ) {
53+ const metric = ( document . getElementById ( 'metric-select' ) && document . getElementById ( 'metric-select' ) . value ) || 'combined_score' ;
54+ let min = 0 , max = 1 ;
55+ const vals = children . map ( child => ( child . metrics && typeof child . metrics [ metric ] === 'number' ) ? child . metrics [ metric ] : null ) . filter ( x => x !== null ) ;
56+ if ( vals . length > 0 ) {
57+ min = Math . min ( ...vals ) ;
58+ max = Math . max ( ...vals ) ;
59+ }
60+ return `<div><ul style='margin:0.5em 0 0 1em;padding:0;'>` +
61+ children . map ( child => {
62+ let val = ( child . metrics && typeof child . metrics [ metric ] === 'number' ) ? child . metrics [ metric ] . toFixed ( 4 ) : '(no value)' ;
63+ let bar = ( child . metrics && typeof child . metrics [ metric ] === 'number' ) ? renderMetricBar ( child . metrics [ metric ] , min , max ) : '' ;
64+ return `<li style='margin-bottom:0.3em;'><a href="#" class="child-link" data-child="${ child . id } ">${ child . id } </a><br /><br /> <span style='margin-left:0.5em;'>${ val } </span> ${ bar } </li>` ;
65+ } ) . join ( '' ) +
66+ `</ul></div>` ;
67+ }
68+ return '' ;
69+ }
70+
71+ if ( tabNames . length > 0 ) {
72+ tabHtml = '<div id="sidebar-tab-bar" style="display:flex;gap:0.7em;margin-bottom:0.7em;">' +
73+ tabNames . map ( ( name ) => `<span class="sidebar-tab${ name === activeTab ?' active' :'' } " data-tab="${ name } ">${ name } </span>` ) . join ( '' ) + '</div>' ;
74+ tabContentHtml = `<div id="sidebar-tab-content">${ renderSidebarTabContent ( activeTab , d , children ) } </div>` ;
4775 }
4876 let parentIslandHtml = '' ;
4977 if ( d . parent_id && d . parent_id !== 'None' ) {
@@ -72,18 +100,55 @@ export function showSidebarContent(d, fromHover = false) {
72100 Array . from ( tabBar . children ) . forEach ( e => e . classList . remove ( 'active' ) ) ;
73101 tabEl . classList . add ( 'active' ) ;
74102 const tabName = tabEl . dataset . tab ;
103+ lastSidebarTab = tabName ;
75104 const tabContent = document . getElementById ( 'sidebar-tab-content' ) ;
76- if ( tabName === 'Code' ) tabContent . innerHTML = `<pre class="sidebar-code-pre">${ d . code } </pre>` ;
77- if ( tabName === 'Prompts' ) {
78- let html = '' ;
79- for ( const [ k , v ] of Object . entries ( d . prompts ) ) {
80- html += `<div style="margin-bottom:0.7em;"><b>${ k } :</b><pre class="sidebar-pre">${ v } </pre></div>` ;
105+ tabContent . innerHTML = renderSidebarTabContent ( tabName , d , children ) ;
106+ setTimeout ( ( ) => {
107+ document . querySelectorAll ( '.child-link' ) . forEach ( link => {
108+ link . onclick = function ( e ) {
109+ e . preventDefault ( ) ;
110+ const childNode = allNodeData . find ( n => n . id == link . dataset . child ) ;
111+ if ( childNode ) {
112+ window . _lastSelectedNodeData = childNode ;
113+ const perfTabBtn = document . getElementById ( 'tab-performance' ) ;
114+ const perfTabView = document . getElementById ( 'view-performance' ) ;
115+ if ( ( perfTabBtn && perfTabBtn . classList . contains ( 'active' ) ) || ( perfTabView && perfTabView . classList . contains ( 'active' ) ) ) {
116+ import ( './performance.js' ) . then ( mod => {
117+ mod . selectPerformanceNodeById ( childNode . id ) ;
118+ showSidebar ( ) ;
119+ } ) ;
120+ } else {
121+ scrollAndSelectNodeById ( childNode . id ) ;
122+ }
123+ }
124+ } ;
125+ } ) ;
126+ } , 0 ) ;
127+ } ;
128+ } ) ;
129+ }
130+ setTimeout ( ( ) => {
131+ document . querySelectorAll ( '.child-link' ) . forEach ( link => {
132+ link . onclick = function ( e ) {
133+ e . preventDefault ( ) ;
134+ const childNode = allNodeData . find ( n => n . id == link . dataset . child ) ;
135+ if ( childNode ) {
136+ window . _lastSelectedNodeData = childNode ;
137+ // Check if performance tab is active
138+ const perfTabBtn = document . getElementById ( 'tab-performance' ) ;
139+ const perfTabView = document . getElementById ( 'view-performance' ) ;
140+ if ( ( perfTabBtn && perfTabBtn . classList . contains ( 'active' ) ) || ( perfTabView && perfTabView . classList . contains ( 'active' ) ) ) {
141+ import ( './performance.js' ) . then ( mod => {
142+ mod . selectPerformanceNodeById ( childNode . id ) ;
143+ showSidebar ( ) ;
144+ } ) ;
145+ } else {
146+ scrollAndSelectNodeById ( childNode . id ) ;
81147 }
82- tabContent . innerHTML = html ;
83148 }
84149 } ;
85150 } ) ;
86- }
151+ } , 0 ) ;
87152 const closeBtnEl = document . getElementById ( 'sidebar-close-btn' ) ;
88153 if ( closeBtnEl ) closeBtnEl . onclick = function ( ) {
89154 setSelectedProgramId ( null ) ;
0 commit comments