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

Implement multi selection of datasets in folder tab #6683

Merged
merged 14 commits into from
Dec 13, 2022
Merged
Show file tree
Hide file tree
Changes from 9 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
1 change: 1 addition & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released
- Added sign in via OIDC. [#6534](https://github.com/scalableminds/webknossos/pull/6534)
- Added a new datasets tab to the dashboard which supports managing datasets in folders. Folders can be organized hierarchically and datasets can be moved into these folders. Selecting a dataset will show dataset details in a sidebar. [#6591](https://github.com/scalableminds/webknossos/pull/6591)
- Added the option to search a specific folder in the new datasets tab. [#6677](https://github.com/scalableminds/webknossos/pull/6677)
- The new datasets tab in the dashboard allows multi-selection of datasets so that multiple datasets can be moved to a folder at once. As in typical file explorers, CTRL + left click adds individual datasets to the current selection. Shift + left click selects a range of datasets. [#6683](https://github.com/scalableminds/webknossos/pull/6683)

### Changed
- webKnossos is now able to recover from a lost webGL context. [#6663](https://github.com/scalableminds/webknossos/pull/6663)
Expand Down
4 changes: 3 additions & 1 deletion docs/dashboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ You can *view* a dataset (read-only) or start new annotations from this screen.
Search for your dataset by using the search bar or sorting any of the table columns.
Learn more about managing datasets in the [Datasets guide](./datasets.md).

The presentation differs corresponding to your user role.
The presentation differs depending on your user role.
Regular users can only start or continue annotations and work on tasks.
[Admins and Team Managers](./users.md#access-rights-roles) also have access to additional administration actions, access-rights management, and advanced dataset properties for each dataset.

Read more about the organization of datasets [here](./datasets.md#dataset-organization).

![Dashboard for Team Managers or Admins with access to dataset settings and additional administration actions.](./images/dashboard_datasets.jpeg)
![Dashboard for Regular Users](./images/dashboard_regular_user.jpeg)

Expand Down
2 changes: 2 additions & 0 deletions docs/datasets.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,8 @@ This is because the access permissions are handled cumulatively.
In addition to the folder organization, datasets can also be tagged.
Use the tags column to do so or select a dataset with a click and use the right sidebar.

To move multiple datasets to a folder at once, you can make use of multi-selection. As in typical file explorers, CTRL + left click adds individual datasets to the current selection. Shift + left click selects a range of datasets.

## Dataset Sharing
Read more in the [Sharing guide](./sharing.md#dataset-sharing)

Expand Down
55 changes: 45 additions & 10 deletions frontend/javascripts/dashboard/advanced_dataset/dataset_table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ type Props = {
reloadDataset: (arg0: APIDatasetId, arg1?: Array<APIMaybeUnimportedDataset>) => Promise<void>;
updateDataset: (arg0: APIDataset) => Promise<void>;
addTagToSearch: (tag: string) => void;
onSelectDataset?: (dataset: APIMaybeUnimportedDataset | null) => void;
selectedDataset?: APIMaybeUnimportedDataset | null | undefined;
onSelectDataset: (dataset: APIMaybeUnimportedDataset | null, multiSelect?: boolean) => void;
selectedDatasets: APIMaybeUnimportedDataset[];
hideDetailsColumns?: boolean;
context: DatasetCacheContextValue | DatasetCollectionContextValue;
};
Expand All @@ -69,6 +69,7 @@ type State = {
sortedInfo: SorterResult<string>;
contextMenuPosition: [number, number] | null | undefined;
datasetForContextMenu: APIMaybeUnimportedDataset | null;
currentDataSource: APIMaybeUnimportedDataset[];
};

type ContextMenuProps = {
Expand Down Expand Up @@ -240,6 +241,7 @@ class DatasetTable extends React.PureComponent<Props, State> {
prevSearchQuery: "",
contextMenuPosition: null,
datasetForContextMenu: null,
currentDataSource: [],
};

static getDerivedStateFromProps(nextProps: Props, prevState: State): Partial<State> {
Expand All @@ -263,11 +265,12 @@ class DatasetTable extends React.PureComponent<Props, State> {
_pagination: TablePaginationConfig,
_filters: Record<string, FilterValue | null>,
sorter: SorterResult<RecordType> | SorterResult<RecordType>[],
_extra: TableCurrentDataSource<RecordType>,
extra: TableCurrentDataSource<RecordType>,
) => {
this.setState({
// @ts-ignore
sortedInfo: sorter,
currentDataSource: extra.currentDataSource as APIMaybeUnimportedDataset[],
});
};

Expand Down Expand Up @@ -424,6 +427,11 @@ class DatasetTable extends React.PureComponent<Props, State> {
emptyText: this.renderEmptyText(),
}}
onRow={(record: APIMaybeUnimportedDataset) => ({
onDragStart: () => {
if (!this.props.selectedDatasets.includes(record)) {
this.props.onSelectDataset(record);
}
},
onClick: (event) => {
// @ts-expect-error
if (event.target?.tagName !== "TD") {
Expand All @@ -432,11 +440,38 @@ class DatasetTable extends React.PureComponent<Props, State> {
// (e.g., the link action and a (de)selection).
return;
}
if (this.props.onSelectDataset) {
if (this.props.selectedDataset === record) {
this.props.onSelectDataset(null);
} else {
this.props.onSelectDataset(record);

if (!event.shiftKey || this.props.selectedDatasets.length === 0) {
this.props.onSelectDataset(record, event.ctrlKey || event.metaKey);
} else {
// Shift was pressed and there's already another selected dataset that was not
// clicked just now.
const renderedDatasets =
this.state.currentDataSource.length === 0
? this.props.datasets
: this.state.currentDataSource;

const clickedDatasetIdx = renderedDatasets.indexOf(record);
const selectedIndices = this.props.selectedDatasets.map((selectedDS) =>
renderedDatasets.indexOf(selectedDS),
);
const closestSelectedDatasetIdx = _.minBy(selectedIndices, (idx) =>
Math.abs(idx - clickedDatasetIdx),
);

if (clickedDatasetIdx == null || closestSelectedDatasetIdx == null) {
return;
}

const [start, end] = [closestSelectedDatasetIdx, clickedDatasetIdx].sort(
(a, b) => a - b,
);

for (let idx = start; idx <= end; idx++) {
// closestSelectedDatasetIdx is already selected (don't deselect it).
if (idx !== closestSelectedDatasetIdx) {
this.props.onSelectDataset(renderedDatasets[idx], true);
}
}
}
},
Expand Down Expand Up @@ -473,8 +508,8 @@ class DatasetTable extends React.PureComponent<Props, State> {
},
})}
rowSelection={{
selectedRowKeys: this.props.selectedDataset ? [this.props.selectedDataset.name] : [],
onSelectNone: () => this.props.onSelectDataset?.(null),
selectedRowKeys: this.props.selectedDatasets.map((ds) => ds.name),
onSelectNone: () => this.props.onSelectDataset(null),
}}
>
<Column
Expand Down
10 changes: 9 additions & 1 deletion frontend/javascripts/dashboard/dashboard_view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,15 @@ class DashboardView extends PureComponent<PropsWithRouter, State> {
}
function DatasetViewWithLegacyContext({ user }: { user: APIUser }) {
const datasetCacheContext = useContext(DatasetCacheContext);
return <DatasetView user={user} hideDetailsColumns={false} context={datasetCacheContext} />;
return (
<DatasetView
user={user}
hideDetailsColumns={false}
context={datasetCacheContext}
selectedDatasets={[]}
onSelectDataset={() => {}}
/>
);
}

const mapStateToProps = (state: OxalisState): StateProps => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ export type DatasetCollectionContextValue = {
setActiveFolderId: (id: string | null) => void;
mostRecentlyUsedActiveFolderId: string | null;
supportsFolders: true;
selectedDatasets: APIMaybeUnimportedDataset[];
setSelectedDatasets: React.Dispatch<React.SetStateAction<APIMaybeUnimportedDataset[]>>;
globalSearchQuery: string | null;
setGlobalSearchQuery: (val: string | null) => void;
searchRecursively: boolean;
Expand Down Expand Up @@ -83,6 +85,7 @@ export default function DatasetCollectionContextProvider({
const isMutating = useIsMutating() > 0;
const { data: folder } = useFolderQuery(activeFolderId);

const [selectedDatasets, setSelectedDatasets] = useState<APIMaybeUnimportedDataset[]>([]);
const [globalSearchQuery, setGlobalSearchQueryInner] = useState<string | null>(null);
const setGlobalSearchQuery = useCallback(
(value: string | null) => {
Expand Down Expand Up @@ -203,6 +206,8 @@ export default function DatasetCollectionContextProvider({

datasetsInFolderQuery.refetch();
},
selectedDatasets,
setSelectedDatasets,
globalSearchQuery,
setGlobalSearchQuery,
searchRecursively,
Expand Down Expand Up @@ -238,6 +243,8 @@ export default function DatasetCollectionContextProvider({
updateFolderMutation,
moveFolderMutation,
updateDatasetMutation,
selectedDatasets,
setSelectedDatasets,
globalSearchQuery,
],
);
Expand Down
Loading