Skip to content

Commit 54129b3

Browse files
authored
HParams: Header Button Styling and Context Menu updates (#6471)
## Motivation for features / changes While testing the new hparams in time series feature we got a lot of feedback about the styling and placement of the header buttons. The main takeaways are: - Remove the "Delete Column" button - Ensure the icons are the same height and color - The 3dot button ripple was weird We also noticed that the sort button was missing from the context menu and discovered that right clicking a header cell logged an error to the console ## Screenshots of UI changes (or N/A) Header Buttons (Dark Mode) ![image](https://github.com/tensorflow/tensorboard/assets/78179109/cd26c82d-12ad-4915-b476-8bf72a2c34bb) Header Buttons (Light Mode) ![image](https://github.com/tensorflow/tensorboard/assets/78179109/e33e6dd8-ff0e-4ae8-9726-dd931353ffcc) Context Menu (Dark Mode) ![image](https://github.com/tensorflow/tensorboard/assets/78179109/5fcb762a-539b-43f0-b044-b73f0e55aa79) Context Menu (Light Mode) ![image](https://github.com/tensorflow/tensorboard/assets/78179109/4a050bc9-6465-48db-8c7b-fb2348c85e74) No context menu in scalar card ![image](https://github.com/tensorflow/tensorboard/assets/78179109/6f0220a9-5213-4cca-aab7-832be9f4f5a6)
1 parent 7c365cb commit 54129b3

File tree

10 files changed

+191
-142
lines changed

10 files changed

+191
-142
lines changed

tensorboard/webapp/metrics/views/card_renderer/scalar_card_data_table.ng.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
[header]="header"
3131
[sortingInfo]="sortingInfo"
3232
[hparamsEnabled]="hparamsEnabled"
33+
disableContextMenu="true"
3334
></tb-data-table-header-cell> </ng-container
3435
></ng-container>
3536

tensorboard/webapp/runs/views/runs_table/runs_data_table.ng.html

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
[header]="header"
4141
[sortingInfo]="sortingInfo"
4242
[hparamsEnabled]="true"
43-
(contextmenu)="openContextMenu($event, header)"
4443
>
4544
<ng-container [ngSwitch]="header.name">
4645
<div *ngSwitchCase="'selected'">

tensorboard/webapp/widgets/data_table/data_table_component.ng.html

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,37 @@
1515
<custom-modal #contextMenu>
1616
<div class="context-menu">
1717
<div
18-
*ngIf="!canContextMenuRemoveColumn() && !(selectableColumns && selectableColumns.length)"
18+
*ngIf="!contextMenuHeader?.removable && !contextMenuHeader?.sortable && !canContextMenuInsert()"
1919
class="no-actions-message"
2020
>
2121
No Actions Available
2222
</div>
2323
<button
24-
*ngIf="canContextMenuRemoveColumn()"
24+
*ngIf="contextMenuHeader?.removable"
2525
class="context-menu-button"
2626
mat-button
2727
(click)="contextMenuRemoveColumn()"
2828
>
2929
<mat-icon svgIcon="close_24px"></mat-icon>Remove
3030
</button>
31+
<button
32+
*ngIf="contextMenuHeader?.sortable"
33+
class="context-menu-button sort-button"
34+
mat-button
35+
(click)="sortByHeader(contextMenuHeader?.name)"
36+
>
37+
<ng-container
38+
*ngIf="
39+
sortingInfo.order === SortingOrder.ASCENDING &&
40+
sortingInfo.name === contextMenuHeader?.name;
41+
else descending"
42+
>
43+
<mat-icon svgIcon="arrow_downward_24px"></mat-icon>Sort Descending
44+
</ng-container>
45+
<ng-template #descending>
46+
<mat-icon svgIcon="arrow_upward_24px"></mat-icon>Sort Ascending
47+
</ng-template>
48+
</button>
3149
<button
3250
mat-button
3351
*ngIf="canContextMenuInsert()"

tensorboard/webapp/widgets/data_table/data_table_component.scss

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,10 @@ $_accent: map-get(mat.get-color-config($tb-theme), accent);
7373
padding: 8px;
7474
text-wrap: nowrap;
7575
}
76+
77+
.sort-button {
78+
::ng-deep path {
79+
fill: unset;
80+
}
81+
}
7682
}

tensorboard/webapp/widgets/data_table/data_table_component.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,7 @@ export class DataTableComponent implements OnDestroy, AfterContentInit {
120120
headerCell.dragStart.subscribe(this.dragStart.bind(this)),
121121
headerCell.dragEnter.subscribe(this.dragEnter.bind(this)),
122122
headerCell.dragEnd.subscribe(this.dragEnd.bind(this)),
123-
headerCell.headerClicked.subscribe(this.headerClicked.bind(this)),
124-
headerCell.deleteButtonClicked.subscribe(
125-
this.deleteButtonClicked.bind(this)
126-
),
123+
headerCell.headerClicked.subscribe(this.sortByHeader.bind(this)),
127124
headerCell.contextMenuOpened.subscribe(
128125
this.openContextMenu.bind(this, headerCell.header)
129126
)
@@ -144,7 +141,7 @@ export class DataTableComponent implements OnDestroy, AfterContentInit {
144141
.flat();
145142
}
146143

147-
headerClicked(name: string) {
144+
sortByHeader(name: string) {
148145
if (
149146
this.sortingInfo.name === name &&
150147
this.sortingInfo.order === SortingOrder.ASCENDING
@@ -236,10 +233,6 @@ export class DataTableComponent implements OnDestroy, AfterContentInit {
236233
});
237234
}
238235

239-
deleteButtonClicked(header: ColumnHeader) {
240-
this.removeColumn.emit(header);
241-
}
242-
243236
focusColumnSelector() {
244237
this.columnSelector.focus();
245238
}

tensorboard/webapp/widgets/data_table/data_table_test.ts

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,7 @@ describe('data table', () => {
552552
displayName: 'Run',
553553
enabled: true,
554554
movable: true,
555+
sortable: true,
555556
},
556557
{
557558
name: 'disabled_header',
@@ -577,6 +578,12 @@ describe('data table', () => {
577578
removable: true,
578579
movable: false,
579580
},
581+
{
582+
name: 'some static column',
583+
type: ColumnHeaderType.HPARAM,
584+
displayName: 'cant touch this',
585+
enabled: true,
586+
},
580587
];
581588
mockTableData = [
582589
{
@@ -697,7 +704,7 @@ describe('data table', () => {
697704
By.directive(ContentCellComponent)
698705
);
699706

700-
expect(cells.length).toBe(3);
707+
expect(cells.length).toBe(4);
701708

702709
cells.forEach((cell) => {
703710
cell.nativeElement.dispatchEvent(new MouseEvent('contextmenu'));
@@ -772,7 +779,7 @@ describe('data table', () => {
772779
By.directive(ContentCellComponent)
773780
);
774781

775-
expect(cells.length).toBe(3);
782+
expect(cells.length).toBe(4);
776783

777784
cells.forEach((cell) => {
778785
cell.nativeElement.dispatchEvent(new MouseEvent('contextmenu'));
@@ -796,5 +803,69 @@ describe('data table', () => {
796803
}
797804
});
798805
});
806+
807+
it('renders an upwards arrow when the sort direction is ascending', () => {
808+
const fixture = createComponent({
809+
headers: mockHeaders,
810+
data: mockTableData,
811+
potentialColumns: mockPotentialColumns,
812+
sortingInfo: {
813+
name: 'run',
814+
order: SortingOrder.ASCENDING,
815+
},
816+
});
817+
const header = fixture.debugElement.query(
818+
By.directive(HeaderCellComponent)
819+
);
820+
header.nativeElement.dispatchEvent(new MouseEvent('contextmenu'));
821+
fixture.detectChanges();
822+
823+
expect(
824+
fixture.debugElement
825+
.query(By.css('.context-menu-button.sort-button mat-icon'))
826+
.nativeElement.getAttribute('svgIcon')
827+
).toBe('arrow_downward_24px');
828+
});
829+
830+
it('renders a downwards arrow when the sort direction is descending', () => {
831+
const fixture = createComponent({
832+
headers: mockHeaders,
833+
data: mockTableData,
834+
potentialColumns: mockPotentialColumns,
835+
sortingInfo: {
836+
name: 'run',
837+
order: SortingOrder.DESCENDING,
838+
},
839+
});
840+
const header = fixture.debugElement.query(
841+
By.directive(HeaderCellComponent)
842+
);
843+
header.nativeElement.dispatchEvent(new MouseEvent('contextmenu'));
844+
fixture.detectChanges();
845+
846+
expect(
847+
fixture.debugElement
848+
.query(By.css('.context-menu-button.sort-button mat-icon'))
849+
.nativeElement.getAttribute('svgIcon')
850+
).toBe('arrow_upward_24px');
851+
});
852+
853+
it('displays a message when empty', () => {
854+
const fixture = createComponent({
855+
headers: mockHeaders,
856+
data: mockTableData,
857+
potentialColumns: mockPotentialColumns,
858+
});
859+
const fixedHeader = fixture.debugElement.queryAll(
860+
By.directive(HeaderCellComponent)
861+
)[4];
862+
fixedHeader.nativeElement.dispatchEvent(new MouseEvent('contextmenu'));
863+
fixture.detectChanges();
864+
865+
const contextMenu = fixture.debugElement.query(By.css('.context-menu'));
866+
expect(
867+
contextMenu.nativeElement.innerHTML.includes('No Actions Available')
868+
).toBeTrue();
869+
});
799870
});
800871
});

tensorboard/webapp/widgets/data_table/header_cell_component.ng.html

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,6 @@
2222
[ngClass]="highlightStyle$ | async"
2323
>
2424
<ng-content></ng-content>
25-
<div
26-
*ngIf="hparamsEnabled && header.removable"
27-
class="delete-button-container"
28-
>
29-
<button
30-
class="delete-button"
31-
mat-icon-button
32-
i18n-aria-label="A button to delete a data table column."
33-
aria-label="Delete column"
34-
(click)="deleteButtonClicked.emit(header)"
35-
>
36-
<mat-icon svgIcon="close_24px"> </mat-icon>
37-
</button>
38-
</div>
3925
<tb-data-table-header [header]="header"></tb-data-table-header>
4026
<div *ngIf="header.sortable" class="sorting-icon-container">
4127
<mat-icon
@@ -60,15 +46,12 @@
6046
></mat-icon>
6147
</div>
6248
<div
63-
*ngIf="header.removable || header.sortable"
64-
class="context-menu-container"
49+
*ngIf="(header.removable || header.sortable) && !disableContextMenu"
50+
class="context-menu-container show-on-hover"
6551
>
66-
<button
67-
mat-icon-button
68-
class="context-menu-trigger"
52+
<mat-icon
6953
(click)="onContextMenuOpened($event)"
70-
>
71-
<mat-icon svgIcon="more_vert_24px"></mat-icon>
72-
</button>
54+
svgIcon="more_vert_24px"
55+
></mat-icon>
7356
</div>
7457
</div>

tensorboard/webapp/widgets/data_table/header_cell_component.scss

Lines changed: 8 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,20 @@ $_icon_size: 12px;
2525
vertical-align: bottom;
2626

2727
&:hover {
28-
.context-menu-container {
29-
opacity: 0.8;
30-
}
31-
3228
.show-on-hover {
3329
opacity: 0.3;
3430
}
3531
}
32+
33+
.show-on-hover {
34+
&:hover {
35+
opacity: 1;
36+
}
37+
}
3638
}
3739

38-
.sorting-icon-container {
40+
.sorting-icon-container,
41+
.context-menu-container {
3942
width: $_icon_size;
4043
height: $_icon_size;
4144
border-radius: 5px;
@@ -45,12 +48,6 @@ $_icon_size: 12px;
4548
align-items: center;
4649
display: flex;
4750

48-
&:hover {
49-
.delete-button-container {
50-
opacity: 1;
51-
}
52-
}
53-
5451
mat-icon {
5552
height: $_icon_size;
5653
width: $_icon_size;
@@ -63,30 +60,6 @@ $_icon_size: 12px;
6360
}
6461
}
6562

66-
.delete-button {
67-
display: flex;
68-
align-items: center;
69-
color: #fff;
70-
font-size: $_icon_size;
71-
height: $_icon_size;
72-
width: $_icon_size;
73-
line-height: $_icon_size;
74-
cursor: pointer;
75-
76-
.mat-icon {
77-
line-height: $_icon_size;
78-
}
79-
}
80-
81-
.delete-button-container {
82-
border-radius: 50%;
83-
background-color: mat.get-color-from-palette(mat.$gray-palette, 400);
84-
margin-left: -$_icon_size;
85-
height: $_icon_size;
86-
opacity: 0;
87-
position: absolute;
88-
}
89-
9063
.show {
9164
opacity: 1;
9265
}
@@ -106,15 +79,3 @@ $_icon_size: 12px;
10679
.highlight-border-left {
10780
border-left: 2px solid mat.get-color-from-palette($_accent);
10881
}
109-
110-
.context-menu-container {
111-
opacity: 0;
112-
113-
.context-menu-trigger {
114-
width: 12px;
115-
116-
mat-icon {
117-
height: unset;
118-
}
119-
}
120-
}

tensorboard/webapp/widgets/data_table/header_cell_component.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,7 @@ import {
2121
Input,
2222
Output,
2323
} from '@angular/core';
24-
import {
25-
ColumnHeader,
26-
ColumnHeaderType,
27-
SortingInfo,
28-
SortingOrder,
29-
} from './types';
24+
import {ColumnHeader, SortingInfo, SortingOrder} from './types';
3025
import {BehaviorSubject} from 'rxjs';
3126

3227
@Component({
@@ -39,14 +34,12 @@ export class HeaderCellComponent {
3934
@Input() header!: ColumnHeader;
4035
@Input() sortingInfo!: SortingInfo;
4136
@Input() hparamsEnabled?: boolean = false;
37+
@Input() disableContextMenu?: boolean = false;
4238

4339
@Output() dragStart = new EventEmitter<ColumnHeader>();
4440
@Output() dragEnd = new EventEmitter<void>();
4541
@Output() dragEnter = new EventEmitter<ColumnHeader>();
4642
@Output() headerClicked = new EventEmitter<string>();
47-
@Output() deleteButtonClicked = new EventEmitter<{
48-
headerType: ColumnHeaderType;
49-
}>();
5043
@Output() contextMenuOpened = new EventEmitter<MouseEvent>();
5144

5245
highlightStyle$: BehaviorSubject<Object> = new BehaviorSubject<Object>({});
@@ -55,6 +48,9 @@ export class HeaderCellComponent {
5548

5649
@HostListener('contextmenu', ['$event'])
5750
onContextMenuOpened(event: MouseEvent) {
51+
if (this.disableContextMenu) {
52+
return;
53+
}
5854
this.contextMenuOpened.emit(event);
5955
}
6056

0 commit comments

Comments
 (0)