Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add conditional shadow to sticky experiments column #2062

Merged
merged 12 commits into from
Jul 21, 2022
14 changes: 12 additions & 2 deletions webview/src/experiments/components/table/MergeHeaderGroups.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
OnDragStart,
OnDrop
} from '../../../shared/components/dragDrop/DragDropWorkbench'
import { isFirstInArr } from '../../util/isFirstInArr'

export const MergedHeaderGroups: React.FC<{
headerGroup: HeaderGroup<Experiment>
Expand All @@ -20,6 +21,9 @@ export const MergedHeaderGroups: React.FC<{
onDragUpdate: OnDragOver
onDragStart: OnDragStart
onDragEnd: OnDrop
isFirst: boolean
setExpColumnNeedsShadow: (needsShadow: boolean) => void
root: HTMLElement | null
}> = ({
headerGroup,
sorts,
Expand All @@ -28,16 +32,21 @@ export const MergedHeaderGroups: React.FC<{
orderedColumns,
onDragUpdate,
onDragEnd,
onDragStart
onDragStart,
root,
isFirst,
setExpColumnNeedsShadow
}) => {
return (
<div
{...headerGroup.getHeaderGroupProps({
className: cx(styles.tr, styles.headRow)
})}
>
{headerGroup.headers.map((column: HeaderGroup<Experiment>) => (
{headerGroup.headers.map((column: HeaderGroup<Experiment>, ind) => (
<TableHeader
isFirst={isFirst && isFirstInArr(ind)}
setExpColumnNeedsShadow={setExpColumnNeedsShadow}
key={column.id}
orderedColumns={orderedColumns}
column={column}
Expand All @@ -47,6 +56,7 @@ export const MergedHeaderGroups: React.FC<{
onDragOver={onDragUpdate}
onDragStart={onDragStart}
onDrop={onDragEnd}
root={root}
/>
))}
</div>
Expand Down
11 changes: 9 additions & 2 deletions webview/src/experiments/components/table/Table.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useRef } from 'react'
import React, { useRef, useState } from 'react'
import cx from 'classnames'
import styles from './styles.module.scss'
import { TableHead } from './TableHead'
Expand Down Expand Up @@ -137,6 +137,7 @@ export const Table: React.FC<TableProps & WithChanges> = ({

const { clearSelectedRows, batchSelection, lastSelectedRow } =
React.useContext(RowSelectionContext)
const [expColumnNeedsShadow, setExpColumnNeedsShadow] = useState(false)

const tableRef = useRef<HTMLDivElement>(null)

Expand Down Expand Up @@ -178,7 +179,12 @@ export const Table: React.FC<TableProps & WithChanges> = ({
return (
<div className={styles.tableContainer}>
<div
{...getTableProps({ className: styles.table })}
{...getTableProps({
className: cx(
styles.table,
expColumnNeedsShadow && styles.withExpColumnShadow
)
})}
ref={tableRef}
tabIndex={0}
role="tree"
Expand All @@ -195,6 +201,7 @@ export const Table: React.FC<TableProps & WithChanges> = ({
filters={filters}
columns={columns}
root={tableRef.current}
setExpColumnNeedsShadow={setExpColumnNeedsShadow}
/>
{rows.map(row => (
<TableBody
Expand Down
10 changes: 8 additions & 2 deletions webview/src/experiments/components/table/TableHead.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
OnDragStart
} from '../../../shared/components/dragDrop/DragDropWorkbench'
import { getSelectedForPlotsCount } from '../../util/rows'
import { isFirstInArr } from '../../util/isFirstInArr'

interface TableHeadProps {
instance: TableInstance<Experiment>
Expand All @@ -25,6 +26,7 @@ interface TableHeadProps {
filteredCounts: FilteredCounts
filters: string[]
root: HTMLElement | null
setExpColumnNeedsShadow: (needsShadow: boolean) => void
}

export const TableHead = ({
Expand All @@ -39,7 +41,8 @@ export const TableHead = ({
filteredCounts,
filters,
columns,
sorts
sorts,
setExpColumnNeedsShadow
}: TableHeadProps) => {
const orderedColumns = useColumnOrder(columns, columnOrder)
const allHeaders: HeaderGroup<Experiment>[] = []
Expand Down Expand Up @@ -110,7 +113,7 @@ export const TableHead = ({
filters={filters}
filteredCounts={filteredCounts}
/>
{headerGroups.map(headerGroup => (
{headerGroups.map((headerGroup, ind) => (
// eslint-disable-next-line react/jsx-key
<MergedHeaderGroups
{...headerGroup.getHeaderGroupProps()}
Expand All @@ -122,6 +125,9 @@ export const TableHead = ({
onDragStart={onDragStart}
onDragUpdate={onDragUpdate}
onDragEnd={onDragEnd}
root={root}
isFirst={isFirstInArr(ind)}
setExpColumnNeedsShadow={setExpColumnNeedsShadow}
/>
))}
</div>
Expand Down
143 changes: 117 additions & 26 deletions webview/src/experiments/components/table/TableHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import {
Column,
ColumnType
} from 'dvc/src/experiments/webview/contract'
import React from 'react'
import React, { useEffect } from 'react'
import { HeaderGroup } from 'react-table'
import cx from 'classnames'
import { useInView } from 'react-intersection-observer'
import { MessageFromWebviewType } from 'dvc/src/webview/contract'
import { VSCodeDivider } from '@vscode/webview-ui-toolkit/react'
import styles from './styles.module.scss'
Expand Down Expand Up @@ -129,6 +130,76 @@ const getIconMenuItems = (
}
]

const FirstTableHeaderCellWrapper: React.FC<{
children: React.ReactNode
setExpColumnNeedsShadow: (needsShadow: boolean) => void
root: HTMLElement | null
}> = ({ root, setExpColumnNeedsShadow, children }) => {
const [ref, needsShadow] = useInView({
root,
rootMargin: '0px 0px 0px -15px',
threshold: 1
})

useEffect(() => {
setExpColumnNeedsShadow(needsShadow)
}, [needsShadow, setExpColumnNeedsShadow])

return <div ref={ref}>{children}</div>
}

const TableHeaderCellContents: React.FC<{
column: HeaderGroup<Experiment>
sortOrder: SortOrder
sortEnabled: boolean
hasFilter: boolean
isDraggable: boolean
menuSuppressed: boolean
onDragOver: OnDragOver
onDragStart: OnDragStart
onDrop: OnDrop
canResize: boolean
setMenuSuppressed: (menuSuppressed: boolean) => void
resizerHeight: string
}> = ({
column,
sortEnabled,
sortOrder,
hasFilter,
isDraggable,
menuSuppressed,
onDragOver,
onDragStart,
onDrop,
canResize,
setMenuSuppressed,
resizerHeight
}) => {
return (
<>
<div className={styles.iconMenu}>
<IconMenu items={getIconMenuItems(sortEnabled, sortOrder, hasFilter)} />
</div>
<ColumnDragHandle
column={column}
disabled={!isDraggable || menuSuppressed}
onDragOver={onDragOver}
onDragStart={onDragStart}
onDrop={onDrop}
/>
{canResize && (
<div
{...column.getResizerProps()}
onMouseEnter={() => setMenuSuppressed(true)}
onMouseLeave={() => setMenuSuppressed(false)}
className={styles.columnResizer}
style={{ height: resizerHeight }}
/>
)}
</>
)
}

const TableHeaderCell: React.FC<{
column: HeaderGroup<Experiment>
columns: HeaderGroup<Experiment>[]
Expand All @@ -141,6 +212,9 @@ const TableHeaderCell: React.FC<{
onDragOver: OnDragOver
onDragStart: OnDragStart
onDrop: OnDrop
isFirst: boolean
setExpColumnNeedsShadow: (needsShadow: boolean) => void
root: HTMLElement | null
}> = ({
column,
columns,
Expand All @@ -152,10 +226,12 @@ const TableHeaderCell: React.FC<{
menuDisabled,
onDragOver,
onDragStart,
onDrop
onDrop,
root,
isFirst,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[C] I think we need a better name for this "FirstCell". We would want to convey with the name that it contains controls/metadata about the rest of the row. I am not sure what the correct name would be.
Perhaps do a bit of research and follow up with a simple renaming PR. Rename everything around the "First" concept e.g <FirstCell/> etc.

setExpColumnNeedsShadow
}) => {
const [menuSuppressed, setMenuSuppressed] = React.useState<boolean>(false)

const isDraggable =
!column.placeholderOf && !['id', 'timestamp'].includes(column.id)

Expand All @@ -168,6 +244,23 @@ const TableHeaderCell: React.FC<{
columns
)

const cellContents = (
<TableHeaderCellContents
column={column}
sortOrder={sortOrder}
sortEnabled={sortEnabled}
hasFilter={hasFilter}
isDraggable={isDraggable}
menuSuppressed={menuSuppressed}
onDragOver={onDragOver}
onDragStart={onDragStart}
onDrop={onDrop}
canResize={canResize}
setMenuSuppressed={setMenuSuppressed}
resizerHeight={resizerHeight}
/>
)

return (
<ContextMenu
content={menuContent}
Expand All @@ -178,31 +271,20 @@ const TableHeaderCell: React.FC<{
{...column.getHeaderProps(
getHeaderPropsArgs(column, sortEnabled, sortOrder)
)}
key={column.id}
data-testid={`header-${column.id}`}
role={'columnheader'}
key={column.id}
role="columnheader"
tabIndex={0}
>
<div className={styles.iconMenu}>
<IconMenu
items={getIconMenuItems(sortEnabled, sortOrder, hasFilter)}
/>
</div>
<ColumnDragHandle
column={column}
disabled={!isDraggable || menuSuppressed}
onDragOver={onDragOver}
onDragStart={onDragStart}
onDrop={onDrop}
/>
{canResize && (
<div
{...column.getResizerProps()}
onMouseEnter={() => setMenuSuppressed(true)}
onMouseLeave={() => setMenuSuppressed(false)}
className={styles.columnResizer}
style={{ height: resizerHeight }}
/>
{isFirst ? (
<FirstTableHeaderCellWrapper
setExpColumnNeedsShadow={setExpColumnNeedsShadow}
root={root}
>
{cellContents}
</FirstTableHeaderCellWrapper>
) : (
cellContents
)}
</div>
</ContextMenu>
Expand All @@ -218,6 +300,9 @@ interface TableHeaderProps {
onDragOver: OnDragOver
onDragStart: OnDragStart
onDrop: OnDrop
isFirst: boolean
setExpColumnNeedsShadow: (needsShadow: boolean) => void
root: HTMLElement | null
}

export const TableHeader: React.FC<TableHeaderProps> = ({
Expand All @@ -228,7 +313,10 @@ export const TableHeader: React.FC<TableHeaderProps> = ({
orderedColumns,
onDragOver,
onDragStart,
onDrop
onDrop,
root,
isFirst,
setExpColumnNeedsShadow
}) => {
const baseColumn = column.placeholderOf || column
const sort = sorts.find(sort => sort.path === baseColumn.id)
Expand Down Expand Up @@ -278,6 +366,9 @@ export const TableHeader: React.FC<TableHeaderProps> = ({
onDragStart={onDragStart}
onDrop={onDrop}
menuDisabled={!isSortable && column.group !== ColumnType.PARAMS}
root={root}
isFirst={isFirst}
setExpColumnNeedsShadow={setExpColumnNeedsShadow}
menuContent={
<div>
<MessagesMenu options={contextMenuOptions} />
Expand Down
21 changes: 20 additions & 1 deletion webview/src/experiments/components/table/styles.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,16 @@ $workspace-row-edge-margin: $edge-padding - $cell-padding;
position: sticky;
left: 0;
z-index: 3;
border-right: 2px solid var(--editor-foreground-transparency-4);

&:after {
content: '';
height: 100%;
position: absolute;
top: 0;
width: 6px;
right: 0;
transition: box-shadow 0.25s;
}
}

&.oddRow > *:first-child {
Expand All @@ -282,6 +291,12 @@ $workspace-row-edge-margin: $edge-padding - $cell-padding;
}
}

.table.withExpColumnShadow {
.tr:not(.rowSelected) > *:first-child:after {
box-shadow: 3px 0px 3px var(--vscode-widget-shadow);
julieg18 marked this conversation as resolved.
Show resolved Hide resolved
}
}

.bodyRow {
&:not(.rowSelected) {
& > *:first-child {
Expand Down Expand Up @@ -311,6 +326,10 @@ $workspace-row-edge-margin: $edge-padding - $cell-padding;
background-color: $header-bg-color;
}

&:not(.rowSelected) > *:first-child:after {
right: -2px;
}

&:last-child,
.firstLevelHeader {
.paramHeaderCell,
Expand Down
1 change: 1 addition & 0 deletions webview/src/experiments/util/isFirstInArr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const isFirstInArr = (ind: number): boolean => ind === 0