diff --git a/tensorbored/webapp/metrics/actions/index.ts b/tensorbored/webapp/metrics/actions/index.ts index baeb2b3bf9..b8114f68f9 100644 --- a/tensorbored/webapp/metrics/actions/index.ts +++ b/tensorbored/webapp/metrics/actions/index.ts @@ -206,6 +206,10 @@ export const metricsTagGroupExpansionStateLoaded = createAction( props<{expandedGroups: Array<[string, boolean]>}>() ); +export const metricsSuperimposedSectionExpansionChanged = createAction( + '[Metrics] Superimposed Section Expansion Changed' +); + export const cardFullWidthStateLoaded = createAction( '[Metrics] Card Full Width State Loaded From Storage', props<{fullWidthCardIds: string[]; fullWidthSuperimposedCardIds: string[]}>() diff --git a/tensorbored/webapp/metrics/effects/index.ts b/tensorbored/webapp/metrics/effects/index.ts index acd6eac2d7..7a7bb87d0f 100644 --- a/tensorbored/webapp/metrics/effects/index.ts +++ b/tensorbored/webapp/metrics/effects/index.ts @@ -108,6 +108,7 @@ import { getTagSymlogLinearThresholds, getSuperimposedCardsWithMetadata, getMetricsTagGroupExpansionMap, + getMetricsSuperimposedSectionExpanded, getCardStateMap, getFullWidthSuperimposedCards, } from '../store'; @@ -875,22 +876,23 @@ export class MetricsEffects implements OnInitEffects { this.persistTagGroupExpansion$ = this.actions$.pipe( ofType( actions.metricsTagGroupExpansionChanged, - actions.metricsTagMetadataLoaded + actions.metricsTagMetadataLoaded, + actions.metricsSuperimposedSectionExpansionChanged ), debounceTime(200), - withLatestFrom(this.store.select(getMetricsTagGroupExpansionMap)), - tap(([, expansionMap]) => { + withLatestFrom( + this.store.select(getMetricsTagGroupExpansionMap), + this.store.select(getMetricsSuperimposedSectionExpanded) + ), + tap(([, expansionMap, superimposedExpanded]) => { const entries: Array<[string, boolean]> = Array.from( expansionMap.entries() ); - if (entries.length > 0) { - window.localStorage.setItem( - TAG_GROUP_EXPANSION_STORAGE_KEY, - JSON.stringify({version: 1, groups: entries}) - ); - } else { - window.localStorage.removeItem(TAG_GROUP_EXPANSION_STORAGE_KEY); - } + entries.push(['__superimposed__', superimposedExpanded]); + window.localStorage.setItem( + TAG_GROUP_EXPANSION_STORAGE_KEY, + JSON.stringify({version: 1, groups: entries}) + ); window.dispatchEvent(new CustomEvent('tb-tag-group-expansion-changed')); }) ); diff --git a/tensorbored/webapp/metrics/store/metrics_reducers.ts b/tensorbored/webapp/metrics/store/metrics_reducers.ts index 6d1cd47a5f..37fda4e4cb 100644 --- a/tensorbored/webapp/metrics/store/metrics_reducers.ts +++ b/tensorbored/webapp/metrics/store/metrics_reducers.ts @@ -502,6 +502,7 @@ const {initialState, reducers: namespaceContextedReducer} = superimposedCardMetadataMap: {}, superimposedCardList: [], fullWidthSuperimposedCards: new Set(), + superimposedSectionExpanded: true, }, { isSettingsPaneOpen: true, @@ -1280,8 +1281,21 @@ const reducer = createReducer( return {...state, tagGroupExpanded}; }), on(actions.metricsTagGroupExpansionStateLoaded, (state, {expandedGroups}) => { - const tagGroupExpanded = new Map(expandedGroups); - return {...state, tagGroupExpanded}; + const superimposedEntry = expandedGroups.find( + ([key]) => key === '__superimposed__' + ); + const tagGroupExpanded = new Map( + expandedGroups.filter(([key]) => key !== '__superimposed__') + ); + const superimposedSectionExpanded = + superimposedEntry !== undefined ? superimposedEntry[1] : true; + return {...state, tagGroupExpanded, superimposedSectionExpanded}; + }), + on(actions.metricsSuperimposedSectionExpansionChanged, (state) => { + return { + ...state, + superimposedSectionExpanded: !state.superimposedSectionExpanded, + }; }), on( actions.cardFullWidthStateLoaded, diff --git a/tensorbored/webapp/metrics/store/metrics_selectors.ts b/tensorbored/webapp/metrics/store/metrics_selectors.ts index 9627e7e7b8..281ab88143 100644 --- a/tensorbored/webapp/metrics/store/metrics_selectors.ts +++ b/tensorbored/webapp/metrics/store/metrics_selectors.ts @@ -473,6 +473,11 @@ export const getMetricsTagGroupExpansionMap = createSelector( } ); +export const getMetricsSuperimposedSectionExpanded = createSelector( + selectMetricsState, + (state: MetricsState): boolean => state.superimposedSectionExpanded +); + export const getMetricsLinkedTimeEnabled = createSelector( selectMetricsState, (state: MetricsState): boolean => { diff --git a/tensorbored/webapp/metrics/store/metrics_types.ts b/tensorbored/webapp/metrics/store/metrics_types.ts index 3c45b6bcab..e4a0cdae62 100644 --- a/tensorbored/webapp/metrics/store/metrics_types.ts +++ b/tensorbored/webapp/metrics/store/metrics_types.ts @@ -242,6 +242,13 @@ export interface MetricsNamespacedState { * Set of superimposed card IDs that are displayed at full width. */ fullWidthSuperimposedCards: Set; + + /** + * Whether the Superimposed section header is expanded (cards visible). + * Defaults to true. Persisted in the same localStorage key as tagGroupExpanded, + * using the reserved key '__superimposed__'. + */ + superimposedSectionExpanded: boolean; } export interface MetricsSettings { diff --git a/tensorbored/webapp/metrics/testing.ts b/tensorbored/webapp/metrics/testing.ts index 01637e3078..249132633f 100644 --- a/tensorbored/webapp/metrics/testing.ts +++ b/tensorbored/webapp/metrics/testing.ts @@ -161,6 +161,7 @@ function buildBlankState(): MetricsState { superimposedCardMetadataMap: {}, superimposedCardList: [], fullWidthSuperimposedCards: new Set(), + superimposedSectionExpanded: true, }; } diff --git a/tensorbored/webapp/metrics/views/main_view/superimposed_cards_view_component.scss b/tensorbored/webapp/metrics/views/main_view/superimposed_cards_view_component.scss index 9919737b34..744dc115a2 100644 --- a/tensorbored/webapp/metrics/views/main_view/superimposed_cards_view_component.scss +++ b/tensorbored/webapp/metrics/views/main_view/superimposed_cards_view_component.scss @@ -21,16 +21,21 @@ limitations under the License. .group-toolbar { @include tb-theme-background-prop(background-color, background); - @include tb-theme-foreground-prop(border-bottom, border, 1px solid); + @include tb-theme-foreground-prop(color, text); align-items: center; background-color: #fff; + border: 0; + @include tb-theme-foreground-prop(border-bottom, border, 1px solid); + cursor: pointer; display: flex; flex: none; + font: inherit; height: 42px; margin-bottom: -1px; padding: 0 16px; position: sticky; top: 0; + width: 100%; z-index: 1; box-shadow: 0px 2px 4px 0px rgba(#000, 15%); @include tb-dark-theme { @@ -40,8 +45,10 @@ limitations under the License. .left-items { align-items: center; display: flex; + flex-grow: 1; gap: 10px; overflow: hidden; + text-align: left; mat-icon { color: #673ab7; @@ -70,6 +77,10 @@ limitations under the License. } } +.expand-group-icon { + @include tb-theme-foreground-prop(color, secondary-text); +} + .superimposed-cards-grid { display: grid; grid-template-columns: repeat( diff --git a/tensorbored/webapp/metrics/views/main_view/superimposed_cards_view_component.ts b/tensorbored/webapp/metrics/views/main_view/superimposed_cards_view_component.ts index a97108e6e1..88fd976c2b 100644 --- a/tensorbored/webapp/metrics/views/main_view/superimposed_cards_view_component.ts +++ b/tensorbored/webapp/metrics/views/main_view/superimposed_cards_view_component.ts @@ -15,8 +15,10 @@ limitations under the License. import { ChangeDetectionStrategy, Component, + EventEmitter, Input, OnChanges, + Output, SimpleChanges, } from '@angular/core'; import {Store} from '@ngrx/store'; @@ -35,7 +37,14 @@ const MAX_CARD_MIN_WIDTH_IN_PX = 735; selector: 'superimposed-cards-view-component', template: ` -
+
+ + + + + + +
@@ -76,6 +95,8 @@ export class SuperimposedCardsViewComponent implements OnChanges { @Input() cardObserver!: CardObserver; @Input() superimposedCards: SuperimposedCardMetadata[] = []; @Input() cardMinWidth: number | null = null; + @Input() isExpanded: boolean = true; + @Output() expansionToggled = new EventEmitter(); gridTemplateColumn = ''; diff --git a/tensorbored/webapp/metrics/views/main_view/superimposed_cards_view_container.ts b/tensorbored/webapp/metrics/views/main_view/superimposed_cards_view_container.ts index 1ccd19084e..0c8b57910b 100644 --- a/tensorbored/webapp/metrics/views/main_view/superimposed_cards_view_container.ts +++ b/tensorbored/webapp/metrics/views/main_view/superimposed_cards_view_container.ts @@ -18,7 +18,11 @@ import {Observable} from 'rxjs'; import {startWith} from 'rxjs/operators'; import {State} from '../../../app_state'; import {getMetricsCardMinWidth} from '../../../selectors'; -import {getSuperimposedCardsWithMetadata} from '../../store'; +import { + getSuperimposedCardsWithMetadata, + getMetricsSuperimposedSectionExpanded, +} from '../../store'; +import {metricsSuperimposedSectionExpansionChanged} from '../../actions'; import {SuperimposedCardMetadata} from '../../types'; import {CardObserver} from '../card_renderer/card_lazy_loader'; @@ -30,6 +34,8 @@ import {CardObserver} from '../card_renderer/card_lazy_loader'; [superimposedCards]="superimposedCards$ | async" [cardObserver]="cardObserver" [cardMinWidth]="cardMinWidth$ | async" + [isExpanded]="isExpanded$ | async" + (expansionToggled)="onExpansionToggled()" > `, changeDetection: ChangeDetectionStrategy.OnPush, @@ -39,11 +45,17 @@ export class SuperimposedCardsViewContainer { readonly superimposedCards$: Observable; readonly cardMinWidth$: Observable; + readonly isExpanded$: Observable; constructor(private readonly store: Store) { this.superimposedCards$ = this.store .select(getSuperimposedCardsWithMetadata) .pipe(startWith([])); this.cardMinWidth$ = this.store.select(getMetricsCardMinWidth); + this.isExpanded$ = this.store.select(getMetricsSuperimposedSectionExpanded); + } + + onExpansionToggled() { + this.store.dispatch(metricsSuperimposedSectionExpansionChanged()); } }