@@ -24,6 +24,90 @@ import {runsColorScale} from '../tf_color_scale/colorScale';
2424import '../tf_dashboard_common/tf-multi-checkbox' ;
2525import '../tf_wbr_string/tf-wbr-string' ;
2626
27+ const RUN_SELECTION_KEY = '_tb_run_selection.v1' ;
28+
29+ /**
30+ * Read the NgRx run-selection localStorage entry and return it as a
31+ * bare-run-name → boolean map suitable for tf-multi-checkbox.
32+ */
33+ function readSelectionFromLocalStorage ( ) : Record < string , boolean > {
34+ const raw = window . localStorage . getItem ( RUN_SELECTION_KEY ) ;
35+ if ( ! raw ) return { } ;
36+ try {
37+ const parsed = JSON . parse ( raw ) as {
38+ version ?: number ;
39+ runSelection ?: Array < [ string , boolean ] > ;
40+ } ;
41+ if ( parsed . version !== 1 || ! Array . isArray ( parsed . runSelection ) ) return { } ;
42+ const out : Record < string , boolean > = { } ;
43+ for ( const [ runId , selected ] of parsed . runSelection ) {
44+ const slashIdx = runId . indexOf ( '/' ) ;
45+ const name = slashIdx >= 0 ? runId . substring ( slashIdx + 1 ) : runId ;
46+ out [ name ] = selected ;
47+ }
48+ return out ;
49+ } catch {
50+ return { } ;
51+ }
52+ }
53+
54+ /**
55+ * Merge a bare-run-name selection map back into the NgRx localStorage
56+ * entry, preserving any run-IDs that we don't know about.
57+ */
58+ function writeSelectionToLocalStorage ( state : Record < string , boolean > ) : void {
59+ const raw = window . localStorage . getItem ( RUN_SELECTION_KEY ) ;
60+ let existing : Array < [ string , boolean ] > = [ ] ;
61+ if ( raw ) {
62+ try {
63+ const parsed = JSON . parse ( raw ) as {
64+ version ?: number ;
65+ runSelection ?: Array < [ string , boolean ] > ;
66+ } ;
67+ if ( parsed . version === 1 && Array . isArray ( parsed . runSelection ) ) {
68+ existing = parsed . runSelection ;
69+ }
70+ } catch {
71+ // ignore
72+ }
73+ }
74+
75+ // Build a set of bare names we're about to write so we can detect
76+ // which existing entries to update vs. keep as-is.
77+ const updatedIds = new Set < string > ( ) ;
78+ const result : Array < [ string , boolean ] > = [ ] ;
79+
80+ for ( const [ runId , _ ] of existing ) {
81+ const slashIdx = runId . indexOf ( '/' ) ;
82+ const name = slashIdx >= 0 ? runId . substring ( slashIdx + 1 ) : runId ;
83+ if ( name in state ) {
84+ result . push ( [ runId , state [ name ] ] ) ;
85+ updatedIds . add ( runId ) ;
86+ } else {
87+ result . push ( [ runId , _ ] ) ;
88+ updatedIds . add ( runId ) ;
89+ }
90+ }
91+
92+ // Add entries from `state` that weren't in `existing` (bare names).
93+ for ( const [ name , selected ] of Object . entries ( state ) ) {
94+ const alreadyCovered = existing . some ( ( [ runId ] ) => {
95+ const slashIdx = runId . indexOf ( '/' ) ;
96+ const n = slashIdx >= 0 ? runId . substring ( slashIdx + 1 ) : runId ;
97+ return n === name ;
98+ } ) ;
99+ if ( ! alreadyCovered ) {
100+ result . push ( [ name , selected ] ) ;
101+ }
102+ }
103+
104+ window . localStorage . setItem (
105+ RUN_SELECTION_KEY ,
106+ JSON . stringify ( { version : 1 , runSelection : result } )
107+ ) ;
108+ window . dispatchEvent ( new CustomEvent ( 'tb-run-selection-changed' ) ) ;
109+ }
110+
27111@customElement ( 'tf-runs-selector' )
28112class TfRunsSelector extends LegacyElementMixin ( PolymerElement ) {
29113 static readonly template = html `
@@ -115,8 +199,9 @@ class TfRunsSelector extends LegacyElementMixin(PolymerElement) {
115199
116200 @property ( {
117201 type : Object ,
202+ observer : '_storeRunSelectionState' ,
118203 } )
119- runSelectionState : object = { } ;
204+ runSelectionState : object = readSelectionFromLocalStorage ( ) ;
120205
121206 @property ( {
122207 type : String ,
@@ -159,20 +244,44 @@ class TfRunsSelector extends LegacyElementMixin(PolymerElement) {
159244
160245 _envStoreListener : baseStore . ListenKey ;
161246
247+ private _selectionChangedListener : ( ( ) => void ) | null = null ;
248+ private _syncingFromStorage = false ;
249+
162250 override attached ( ) {
251+ this . _syncFromStorage ( ) ;
252+
163253 this . _runStoreListener = runsStore . addListener ( ( ) => {
164- this . set ( 'runs' , runsStore . getRuns ( ) ) ;
254+ this . set ( 'runs' , runsStore . getRuns ( ) . slice ( ) . sort ( ) ) ;
165255 } ) ;
166- this . set ( 'runs' , runsStore . getRuns ( ) ) ;
256+ this . set ( 'runs' , runsStore . getRuns ( ) . slice ( ) . sort ( ) ) ;
167257 this . _envStoreListener = environmentStore . addListener ( ( ) => {
168258 this . set ( 'dataLocation' , environmentStore . getDataLocation ( ) ) ;
169259 } ) ;
170260 this . set ( 'dataLocation' , environmentStore . getDataLocation ( ) ) ;
261+
262+ this . _selectionChangedListener = ( ) => this . _syncFromStorage ( ) ;
263+ window . addEventListener (
264+ 'tb-run-selection-changed' ,
265+ this . _selectionChangedListener
266+ ) ;
267+ }
268+
269+ private _syncFromStorage ( ) {
270+ this . _syncingFromStorage = true ;
271+ this . set ( 'runSelectionState' , readSelectionFromLocalStorage ( ) ) ;
272+ this . _syncingFromStorage = false ;
171273 }
172274
173275 override detached ( ) {
174276 runsStore . removeListenerByKey ( this . _runStoreListener ) ;
175277 environmentStore . removeListenerByKey ( this . _envStoreListener ) ;
278+ if ( this . _selectionChangedListener ) {
279+ window . removeEventListener (
280+ 'tb-run-selection-changed' ,
281+ this . _selectionChangedListener
282+ ) ;
283+ this . _selectionChangedListener = null ;
284+ }
176285 }
177286
178287 _toggleAll ( ) {
@@ -205,7 +314,10 @@ class TfRunsSelector extends LegacyElementMixin(PolymerElement) {
205314 return dataLocation && dataLocation . length > _dataLocationClipLength ;
206315 }
207316
208- // Run selection state and regex are no longer persisted to the URL hash.
209- // Run selection is managed via localStorage (runs_effects.ts).
210- // Regex filter is managed via Angular query params ('runFilter').
317+ _storeRunSelectionState ( ) {
318+ if ( this . _syncingFromStorage ) return ;
319+ writeSelectionToLocalStorage (
320+ this . runSelectionState as Record < string , boolean >
321+ ) ;
322+ }
211323}
0 commit comments