diff --git a/tensorboard/webapp/core/views/layout_container.ts b/tensorboard/webapp/core/views/layout_container.ts index e714ed9b19..e90276da5b 100644 --- a/tensorboard/webapp/core/views/layout_container.ts +++ b/tensorboard/webapp/core/views/layout_container.ts @@ -20,11 +20,14 @@ import { } from '@angular/core'; import {Store} from '@ngrx/store'; import {fromEvent, Observable, Subject} from 'rxjs'; -import {filter, takeUntil} from 'rxjs/operators'; +import {combineLatestWith, filter, map, takeUntil} from 'rxjs/operators'; import {MouseEventButtons} from '../../util/dom'; import {sideBarWidthChanged} from '../actions'; import {State} from '../state'; -import {getSideBarWidthInPercent} from '../store/core_selectors'; +import { + getRunsTableFullScreen, + getSideBarWidthInPercent, +} from '../store/core_selectors'; @Component({ selector: 'tb-dashboard-layout', @@ -41,6 +44,7 @@ import {getSideBarWidthInPercent} from '../store/core_selectors'; class="sidebar" [style.width.%]="width$ | async" [style.minWidth.px]="MINIMUM_SIDEBAR_WIDTH_IN_PX" + [style.maxWidth.%]="(runsTableFullScreen$ | async) ? 100 : ''" > @@ -55,9 +59,15 @@ import {getSideBarWidthInPercent} from '../store/core_selectors'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class LayoutContainer implements OnDestroy { - readonly width$: Observable = this.store.select( - getSideBarWidthInPercent - ); + readonly runsTableFullScreen$ = this.store.select(getRunsTableFullScreen); + readonly width$: Observable = this.store + .select(getSideBarWidthInPercent) + .pipe( + combineLatestWith(this.runsTableFullScreen$), + map(([percentageWidth, fullScreen]) => { + return fullScreen ? 100 : percentageWidth; + }) + ); private readonly ngUnsubscribe = new Subject(); private resizing: boolean = false; diff --git a/tensorboard/webapp/core/views/layout_test.ts b/tensorboard/webapp/core/views/layout_test.ts index 2a6b09454e..c4beca2435 100644 --- a/tensorboard/webapp/core/views/layout_test.ts +++ b/tensorboard/webapp/core/views/layout_test.ts @@ -23,7 +23,10 @@ import {provideMockTbStore} from '../../testing/utils'; import {MouseEventButtons} from '../../util/dom'; import {sideBarWidthChanged} from '../actions'; import {State} from '../state'; -import {getSideBarWidthInPercent} from '../store/core_selectors'; +import { + getRunsTableFullScreen, + getSideBarWidthInPercent, +} from '../store/core_selectors'; import {LayoutContainer} from './layout_container'; @Component({ @@ -137,6 +140,15 @@ describe('layout test', () => { expect(navEl.styles['width']).toBe('70%'); }); + it('overrides max width when the runs table full screen is true', () => { + store.overrideSelector(getRunsTableFullScreen, true); + const fixture = TestBed.createComponent(TestableComponent); + fixture.detectChanges(); + + const navEl = fixture.debugElement.query(byCss.SIDEBAR_CONTAINER); + expect(navEl.styles['width']).toBe('100%'); + }); + describe('interactions', () => { function triggerMouseMove( fixture: ComponentFixture, diff --git a/tensorboard/webapp/metrics/views/BUILD b/tensorboard/webapp/metrics/views/BUILD index 77405137b5..66fbb2d274 100644 --- a/tensorboard/webapp/metrics/views/BUILD +++ b/tensorboard/webapp/metrics/views/BUILD @@ -33,6 +33,7 @@ tf_ng_module( "//tensorboard/webapp:selectors", "//tensorboard/webapp/angular:expect_angular_material_icon", "//tensorboard/webapp/core", + "//tensorboard/webapp/core/store", "//tensorboard/webapp/customization", "//tensorboard/webapp/feature_flag/store", "//tensorboard/webapp/feature_flag/store:types", @@ -80,9 +81,11 @@ tf_ts_library( "//tensorboard/webapp/angular:expect_angular_core_testing", "//tensorboard/webapp/angular:expect_angular_platform_browser_animations", "//tensorboard/webapp/angular:expect_ngrx_store_testing", + "//tensorboard/webapp/core/store", "//tensorboard/webapp/feature_flag/store", "//tensorboard/webapp/metrics/actions", "//tensorboard/webapp/metrics/data_source", + "//tensorboard/webapp/metrics/views/main_view", "//tensorboard/webapp/runs/views/runs_selector", "//tensorboard/webapp/testing:utils", "@npm//@angular/core", diff --git a/tensorboard/webapp/metrics/views/metrics_container.ts b/tensorboard/webapp/metrics/views/metrics_container.ts index 7328ab47c8..3059e52ff6 100644 --- a/tensorboard/webapp/metrics/views/metrics_container.ts +++ b/tensorboard/webapp/metrics/views/metrics_container.ts @@ -15,7 +15,8 @@ limitations under the License. import {ChangeDetectionStrategy, Component} from '@angular/core'; import {Store} from '@ngrx/store'; import {getEnableHparamsInTimeSeries} from '../../feature_flag/store/feature_flag_selectors'; -import {State} from '../../feature_flag/store/feature_flag_types'; +import {State} from '../../app_state'; +import {getRunsTableFullScreen} from '../../core/store/core_selectors'; @Component({ selector: 'metrics-dashboard', @@ -25,7 +26,10 @@ import {State} from '../../feature_flag/store/feature_flag_types'; [showHparamsAndMetrics]="showHparamsAndMetrics$ | async" sidebar > - + `, styleUrls: ['metrics_container.css'], @@ -33,6 +37,7 @@ import {State} from '../../feature_flag/store/feature_flag_types'; }) export class MetricsDashboardContainer { showHparamsAndMetrics$ = this.store.select(getEnableHparamsInTimeSeries); + runsTableFullScreen$ = this.store.select(getRunsTableFullScreen); constructor(readonly store: Store) {} } diff --git a/tensorboard/webapp/metrics/views/metrics_container_test.ts b/tensorboard/webapp/metrics/views/metrics_container_test.ts index 5814d45bfc..19748134b8 100644 --- a/tensorboard/webapp/metrics/views/metrics_container_test.ts +++ b/tensorboard/webapp/metrics/views/metrics_container_test.ts @@ -22,6 +22,8 @@ import {getEnableHparamsInTimeSeries} from '../../feature_flag/store/feature_fla import {RunsSelectorContainer} from '../../runs/views/runs_selector/runs_selector_container'; import {provideMockTbStore} from '../../testing/utils'; import {MetricsDashboardContainer} from './metrics_container'; +import {getRunsTableFullScreen} from '../../core/store/core_selectors'; +import {MainViewContainer} from './main_view/main_view_container'; describe('metrics view', () => { let store: MockStore; @@ -64,4 +66,14 @@ describe('metrics view', () => { .showHparamsAndMetrics ).toBeFalse(); }); + + it('hides main view when the runs table is full screen is true', () => { + store.overrideSelector(getRunsTableFullScreen, true); + const fixture = TestBed.createComponent(MetricsDashboardContainer); + fixture.detectChanges(); + + expect( + fixture.debugElement.query(By.directive(MainViewContainer)) + ).toBeNull(); + }); }); diff --git a/tensorboard/webapp/runs/views/runs_table/BUILD b/tensorboard/webapp/runs/views/runs_table/BUILD index 79be955ddb..4d936d8af1 100644 --- a/tensorboard/webapp/runs/views/runs_table/BUILD +++ b/tensorboard/webapp/runs/views/runs_table/BUILD @@ -94,6 +94,7 @@ tf_ng_module( "//tensorboard/webapp/angular:expect_angular_material_table", "//tensorboard/webapp/app_routing", "//tensorboard/webapp/app_routing:types", + "//tensorboard/webapp/core/actions", "//tensorboard/webapp/experiments:types", "//tensorboard/webapp/feature_flag/store", "//tensorboard/webapp/hparams", diff --git a/tensorboard/webapp/runs/views/runs_table/runs_data_table.ng.html b/tensorboard/webapp/runs/views/runs_table/runs_data_table.ng.html index 8763af9b5b..702428d747 100644 --- a/tensorboard/webapp/runs/views/runs_table/runs_data_table.ng.html +++ b/tensorboard/webapp/runs/views/runs_table/runs_data_table.ng.html @@ -91,3 +91,16 @@ +
+ +
diff --git a/tensorboard/webapp/runs/views/runs_table/runs_data_table.scss b/tensorboard/webapp/runs/views/runs_table/runs_data_table.scss index 6da2f0f472..31cb544a62 100644 --- a/tensorboard/webapp/runs/views/runs_table/runs_data_table.scss +++ b/tensorboard/webapp/runs/views/runs_table/runs_data_table.scss @@ -15,6 +15,11 @@ limitations under the License. @use '@angular/material' as mat; @import 'tensorboard/webapp/theme/tb_theme'; $_circle-size: 20px; +$_arrow_size: 16px; + +:host { + width: 100%; +} .color-container { display: flex; @@ -30,10 +35,6 @@ $_circle-size: 20px; outline: none; } -:host { - width: 100%; -} - tb-data-table-content-row, tb-data-table-header-cell { height: 44px; @@ -52,3 +53,44 @@ tb-data-table-header-cell { padding-right: 16px; } } + +.full-screen-toggle { + opacity: 0; + position: absolute; + height: 100%; + // Ensure the button is on the right side then add 2px for the drag target. + left: calc(100% + 2px); + top: 0; + z-index: 1; + display: flex; + align-items: center; + + &:hover { + opacity: 0.8; + } + + &.full-screen { + left: unset; + right: 0; + } + + .full-screen-btn { + background-color: gray; + padding: 0; + min-width: $_arrow_size; + width: $_arrow_size; + + &.expand { + border-radius: 0 $_arrow_size $_arrow_size 0; + } + + &.collapse { + border-radius: $_arrow_size 0 0 $_arrow_size; + } + + .expand-collapse-icon { + font-size: $_arrow_size; + width: $_arrow_size; + } + } +} diff --git a/tensorboard/webapp/runs/views/runs_table/runs_data_table.ts b/tensorboard/webapp/runs/views/runs_table/runs_data_table.ts index fa6b3a2ff1..4a731900c9 100644 --- a/tensorboard/webapp/runs/views/runs_table/runs_data_table.ts +++ b/tensorboard/webapp/runs/views/runs_table/runs_data_table.ts @@ -38,6 +38,7 @@ export class RunsDataTable { @Input() sortingInfo!: SortingInfo; @Input() experimentIds!: string[]; @Input() regexFilter!: string; + @Input() isFullScreen!: boolean; ColumnHeaderType = ColumnHeaderType; @@ -46,6 +47,7 @@ export class RunsDataTable { @Output() onSelectionToggle = new EventEmitter(); @Output() onAllSelectionToggle = new EventEmitter(); @Output() onRegexFilterChange = new EventEmitter(); + @Output() toggleFullScreen = new EventEmitter(); @Output() onRunColorChange = new EventEmitter<{ runId: string; newColor: string; diff --git a/tensorboard/webapp/runs/views/runs_table/runs_table_container.ts b/tensorboard/webapp/runs/views/runs_table/runs_table_container.ts index a68319a4f3..2ef43506d1 100644 --- a/tensorboard/webapp/runs/views/runs_table/runs_table_container.ts +++ b/tensorboard/webapp/runs/views/runs_table/runs_table_container.ts @@ -57,6 +57,7 @@ import { getRunSelectorRegexFilter, getRunSelectorSort, getRunsLoadState, + getRunsTableFullScreen, getRunsTableHeaders, getRunsTableSortingInfo, } from '../../../selectors'; @@ -90,6 +91,7 @@ import { import {RunsTableColumn, RunTableItem} from './types'; import {getFilteredRenderableRunsFromRoute} from '../../../metrics/views/main_view/common_selectors'; import {RunToHParamValues} from '../../data_source/runs_data_source_types'; +import {runsTableFullScreenToggled} from '../../../core/actions'; const getRunsLoading = createSelector< State, @@ -239,12 +241,14 @@ function matchFilter( [sortingInfo]="sortingInfo$ | async" [experimentIds]="experimentIds" [regexFilter]="regexFilter$ | async" + [isFullScreen]="runsTableFullScreen$ | async" (sortDataBy)="sortDataBy($event)" (orderColumns)="orderColumns($event)" (onSelectionToggle)="onRunSelectionToggle($event)" (onAllSelectionToggle)="onAllSelectionToggle($event)" (onRunColorChange)="onRunColorChange($event)" (onRegexFilterChange)="onRegexFilterChange($event)" + (toggleFullScreen)="toggleFullScreen()" > `, host: { @@ -252,6 +256,10 @@ function matchFilter( }, styles: [ ` + :host { + position: relative; + } + :host.flex-layout { display: flex; } @@ -310,6 +318,7 @@ export class RunsTableContainer implements OnInit, OnDestroy { regexFilter$ = this.store.select(getRunSelectorRegexFilter); HParamsEnabled = new BehaviorSubject(false); runsColumns$ = this.store.select(getRunsTableHeaders); + runsTableFullScreen$ = this.store.select(getRunsTableFullScreen); runToHParamValues$ = this.store .select(getFilteredRenderableRunsFromRoute) @@ -777,6 +786,10 @@ export class RunsTableContainer implements OnInit, OnDestroy { }) ); } + + toggleFullScreen() { + this.store.dispatch(runsTableFullScreenToggled()); + } } export const TEST_ONLY = {