Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ describe("<DirectoryTree />", () => {
annotationHierarchy: [fooAnnotation.name, barAnnotation.name],
columns: [...baseDisplayAnnotations, fooAnnotation, barAnnotation].map((a) => ({
name: a.name,
width: 0.1,
width: 100,
})),
shouldShowNullGroups: false,
},
Expand Down Expand Up @@ -368,7 +368,7 @@ describe("<DirectoryTree />", () => {
annotationHierarchy: [fooAnnotation.name],
columns: [...baseDisplayAnnotations, fooAnnotation, barAnnotation].map((a) => ({
name: a.name,
width: 0.1,
width: 100,
})),
},
});
Expand Down
2 changes: 1 addition & 1 deletion packages/core/components/FileList/ColumnPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default function ColumnPicker() {
if (!columnNames.includes(selectedColumn)) {
adjustedColumns.push({
name: selectedColumn,
width: 0.25, // Default width of 25%
width: 150, // Default width in px
});
}
});
Expand Down
12 changes: 10 additions & 2 deletions packages/core/components/FileList/Header.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,20 @@
}

.header-click-target {
display: flex;
flex-direction: row;
max-height: 21.5px;
min-width: 0;
overflow: hidden;
}

.header-tooltip-wrapper {
min-width: 0;
overflow: hidden;
}

.header-title {
display: inline-block;
display: block;
max-width: 100%;
text-overflow: ellipsis;
white-space: nowrap;
Expand Down Expand Up @@ -86,7 +94,7 @@
.sort-icon {
font-size: 12px;
font-weight: bold;
margin-left: 0.5em;
margin: auto 0.25em;
}

.list-parent {
Expand Down
100 changes: 50 additions & 50 deletions packages/core/components/FileList/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@ import { map } from "lodash";
import * as React from "react";
import { useSelector, useDispatch } from "react-redux";

import ColumnPicker from "./ColumnPicker";
import useDragAndDropOrder from "./useDragAndDropOrder";
import useVisibleColumns from "./useVisibleCells";
import { ContextMenuItem } from "../ContextMenu";
import Tooltip from "../Tooltip";
import FileRow, { CellConfig } from "../../components/FileRow";
import { SortOrder } from "../../entity/FileSort";
import Tutorial from "../../entity/Tutorial";
import { interaction, metadata, selection } from "../../state";
import { Column } from "../../state/selection/actions";

import styles from "./Header.module.css";

Expand All @@ -31,18 +30,15 @@ function Header(
const annotationNameToAnnotationMap = useSelector(
metadata.selectors.getAnnotationNameToAnnotationMap
);
const columns = useSelector(selection.selectors.getColumns);
const columnNames = useSelector(selection.selectors.getColumnNames);
const { columns: visibleColumns, padding } = useVisibleColumns();
const allColumnNames = useSelector(selection.selectors.getColumnNames);
const sortColumn = useSelector(selection.selectors.getSortColumn);

const onReorder = React.useCallback(
(newOrder: string[]) => {
const reorderedColumns = newOrder.flatMap(
(name) => columns.find((c) => c.name === name) || []
);
dispatch(selection.actions.setColumns(reorderedColumns));
(item: string, moveTo: number) => {
dispatch(selection.actions.reorderColumns([{ name: item, moveTo }]));
},
[columns, dispatch]
[dispatch]
);

const {
Expand All @@ -52,22 +48,47 @@ function Header(
onDragOver,
onDrop,
onDragEnd,
} = useDragAndDropOrder(columnNames, onReorder);
} = useDragAndDropOrder(allColumnNames, onReorder);

const onResize = (name: string, width?: number) => {
// Default to 0.25 if width is undefined
// which resets the column width to the default
dispatch(selection.actions.resizeColumn({ name, width: width || 0.25 }));
dispatch(selection.actions.resizeColumn({ name, width }));
};

const onHeaderColumnClick = (evt: React.MouseEvent, column: Column) => {
const onHeaderNameClick = (evt: React.MouseEvent, columnName: string) => {
// Prevent this click from bubbling up to the header's onClick
// which opens the column picker context menu
evt.stopPropagation();
dispatch(selection.actions.sortColumn(column.name));
dispatch(selection.actions.sortColumn(columnName));
};

const headerCells: CellConfig[] = map(columns, (column) => ({
const onHeaderColumnClick = (evt: React.MouseEvent, columnName: string) => {
Comment on lines +57 to +64
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What do you think of combining these so that sort is an option in the menu? When the columns are more narrow it's hard to click the header without widening the column, and I'm not sure if users would know to distinguish between those click targets. I know they were separate before bc one was acting on the column itself and the other was acting on the header as a whole, but now the menu is also column-specific

Screen.Recording.2026-05-19.at.1.12.37.PM.mov

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think that makes sense! I'll run it by UX and report back if they disagree, otherwise will implement

evt.preventDefault();
const items: ContextMenuItem[] = [
{
key: "Move to start",
text: "Move to start",
title: "Move column to the start",
onClick: () => {
dispatch(selection.actions.reorderColumns([{ name: columnName, moveTo: 0 }]));
},
},
{
key: "Move to end",
text: "Move to end",
title: "Move column to the end",
onClick: () => {
dispatch(
selection.actions.reorderColumns([
{ name: columnName, moveTo: allColumnNames.length - 1 },
])
);
},
},
];
dispatch(interaction.actions.showContextMenu(items, evt.nativeEvent));
};

const headerCells: CellConfig[] = map(visibleColumns, (column) => ({
className: classNames(styles.headerCell, {
[styles.dragOver]: dragOverItem === column.name && draggedItem !== column.name,
[styles.dragging]: draggedItem === column.name,
Expand All @@ -87,60 +108,39 @@ function Header(
onDragOver={(e) => onDragOver(e, column.name)}
onDrop={() => onDrop(column.name)}
onDragEnd={onDragEnd}
onClick={(evt) => onHeaderColumnClick(evt, column.name)}
>
<span
onClick={(evt) => onHeaderColumnClick(evt, column)}
<div
onClick={(evt) => onHeaderNameClick(evt, column.name)}
className={styles.headerClickTarget}
>
<Tooltip content={annotationNameToAnnotationMap[column.name]?.description}>
<span className={styles.headerTitle}>
{annotationNameToAnnotationMap[column.name]?.displayName}
</span>
</Tooltip>
<span className={styles.headerTooltipWrapper}>
<Tooltip content={annotationNameToAnnotationMap[column.name]?.description}>
<span className={styles.headerTitle}>
{annotationNameToAnnotationMap[column.name]?.displayName}
</span>
</Tooltip>
</span>
{sortColumn?.annotationName === column.name &&
(sortColumn?.order === SortOrder.DESC ? (
<Icon className={styles.sortIcon} iconName="ChevronDown" />
) : (
<Icon className={styles.sortIcon} iconName="ChevronUp" />
))}
</span>
</div>
</div>
),
width: column.width,
}));

const onHeaderClick = (evt: React.MouseEvent) => {
evt.preventDefault();
const items: ContextMenuItem[] = [
{
key: "modify-columns",
text: "Modify columns",
title: "Modify columns displayed in the file list",
iconProps: {
iconName: "TripleColumnEdit",
},
items: [
{
key: "available-annotations",
text: "Available annotations",
onRender() {
return <ColumnPicker />;
},
},
],
},
];
dispatch(interaction.actions.showContextMenu(items, evt.nativeEvent));
};

return (
<div ref={ref} {...rest}>
<div className={styles.headerWrapper} id={Tutorial.COLUMN_HEADERS_ID}>
<FileRow
cells={headerCells}
className={styles.header}
onClick={onHeaderClick}
onResize={onResize}
padding={padding}
/>
</div>
<div className={styles.listParent}>{children}</div>
Expand Down
17 changes: 17 additions & 0 deletions packages/core/components/FileList/HorizontalScrollContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import * as React from "react";

interface HorizontalScrollState {
scrollLeft: number;
containerWidth: number;
}

/**
* Context providing horizontal scroll position and visible width of the file list's
* scroll container. Used by FileRow to virtualize cells horizontally.
*/
const HorizontalScrollContext = React.createContext<HorizontalScrollState>({
scrollLeft: 0,
containerWidth: 0,
});

export default HorizontalScrollContext;
13 changes: 7 additions & 6 deletions packages/core/components/FileList/LazilyRenderedRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import { map } from "lodash";
import * as React from "react";
import { useSelector } from "react-redux";

import { OnSelect } from "./useFileSelector";
import useVisibleColumns from "./useVisibleCells";
import FileRow from "../../components/FileRow";
import FileSet from "../../entity/FileSet";
import { metadata, selection } from "../../state";
import { OnSelect } from "./useFileSelector";

import styles from "./LazilyRenderedRow.module.css";

Expand All @@ -27,8 +28,6 @@ interface LazilyRenderedRowProps {
style: React.CSSProperties; // injected by react-window
}

const MARGIN = 1.5; // px; defined in LazilyRenderedRow.module.css

/**
* A single file in the listing of available files FMS.
*/
Expand All @@ -39,7 +38,8 @@ export default function LazilyRenderedRow(props: LazilyRenderedRowProps) {
style,
} = props;

const columns = useSelector(selection.selectors.getColumns);
const { columns: visibleColumns, padding } = useVisibleColumns();
const totalColumnWidth = useSelector(selection.selectors.getTotalColumnWidth);
const isSmallFont = useSelector(selection.selectors.getShouldDisplaySmallFont);
const annotationNameToAnnotationMap = useSelector(
metadata.selectors.getAnnotationNameToAnnotationMap
Expand All @@ -64,13 +64,14 @@ export default function LazilyRenderedRow(props: LazilyRenderedRowProps) {
if (file) {
content = (
<FileRow
cells={map(columns, (column) => ({
cells={map(visibleColumns, (column) => ({
columnKey: column.name,
displayValue: annotationNameToAnnotationMap[column.name]?.extractFromFile(file),
width: column.width,
}))}
rowIdentifier={{ index, id: file.uid }}
onSelect={onSelect}
padding={padding}
/>
);
} else {
Expand All @@ -95,7 +96,7 @@ export default function LazilyRenderedRow(props: LazilyRenderedRowProps) {
})}
style={{
...style,
width: `calc(100% - ${2 * MARGIN}px)`,
width: `${totalColumnWidth}px`,
}}
onContextMenu={onContextMenu}
>
Expand Down
Loading
Loading