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 option to expand and collapse all children of segment group #7911

Merged
merged 17 commits into from
Jul 23, 2024
Merged
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
3 changes: 2 additions & 1 deletion CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released
- WEBKNOSSOS now automatically searches in subfolder / sub-collection identifiers for valid datasets in case a provided link to a remote dataset does not directly point to a dataset. [#7912](https://github.com/scalableminds/webknossos/pull/7912)
- Added the option to move a bounding box via dragging while pressing ctrl / meta. [#7892](https://github.com/scalableminds/webknossos/pull/7892)
- Added route `/import?url=<url_to_datasource>` to automatically import and view remote datasets. [#7844](https://github.com/scalableminds/webknossos/pull/7844)
- The context menu that is opened upon right-clicking a segment in the dataview port now contains the segment's name. [#7920](https://github.com/scalableminds/webknossos/pull/7920)
- Added option to expand or collapse all subgroups of a segment group in the segments tab. [#7911](https://github.com/scalableminds/webknossos/pull/7911)
- The context menu that is opened upon right-clicking a segment in the dataview port now contains the segment's name. [#7920](https://github.com/scalableminds/webknossos/pull/7920)
- Upgraded backend dependencies for improved performance and stability. [#7922](https://github.com/scalableminds/webknossos/pull/7922)

### Changed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
EyeInvisibleOutlined,
EyeOutlined,
CloseOutlined,
ShrinkOutlined,
ExpandAltOutlined,
} from "@ant-design/icons";
import type RcTree from "rc-tree";
import { getJobs, startComputeMeshFileJob } from "admin/admin_rest_api";
Expand Down Expand Up @@ -323,6 +325,7 @@ type State = {
[groupId: number]: { areSomeSegmentsVisible: boolean; areSomeSegmentsInvisible: boolean };
};
activeStatisticsModalGroupId: number | null;
expandedGroupKeys: Key[];
};

const formatMagWithLabel = (mag: Vector3, index: number) => {
Expand Down Expand Up @@ -402,6 +405,7 @@ class SegmentsView extends React.Component<Props, State> {
groupToDelete: null,
groupsSegmentsVisibilityStateMap: {},
activeStatisticsModalGroupId: null,
expandedGroupKeys: [],
};
tree: React.RefObject<RcTree>;

Expand All @@ -425,6 +429,10 @@ class SegmentsView extends React.Component<Props, State> {
}

Store.dispatch(ensureSegmentIndexIsLoadedAction(this.props.visibleSegmentationLayer?.name));

const allGroups = this.getSubGroupsAsTreeNodes(MISSING_GROUP_ID);
allGroups.push(this.getKeyForGroupId(MISSING_GROUP_ID));
this.setState({ expandedGroupKeys: allGroups });
}

componentDidUpdate(prevProps: Props) {
Expand Down Expand Up @@ -461,6 +469,38 @@ class SegmentsView extends React.Component<Props, State> {
}
}

onExpandTree = (expandedKeys: Key[]) => {
this.setState({ expandedGroupKeys: expandedKeys });
};

getKeyForGroupId = (groupId: number) => `group-${groupId}`;

getSubGroupsAsTreeNodes = (groupId: number) => {
if (groupId !== MISSING_GROUP_ID) {
return getGroupByIdWithSubgroups(this.props.segmentGroups, groupId)
.filter((group) => group !== groupId)
.map((group) => this.getKeyForGroupId(group));
}
const allSegmentGroups = this.props.segmentGroups.flatMap((group) =>
getGroupByIdWithSubgroups(this.props.segmentGroups, group.groupId),
);
return allSegmentGroups.map((group) => this.getKeyForGroupId(group));
};

expandGroups = (groupsToExpand: Key[]) => {
this.setState({
expandedGroupKeys: this.state.expandedGroupKeys?.concat(groupsToExpand),
});
};

collapseGroups = (groupsToCollapse: Key[]) => {
this.setState({
expandedGroupKeys: this.state.expandedGroupKeys?.filter(
(key) => groupsToCollapse.indexOf(key.toString()) === -1,
),
});
};

onSelectTreeItem = (
keys: Key[],
event: {
Expand Down Expand Up @@ -1405,7 +1445,7 @@ class SegmentsView extends React.Component<Props, State> {
(segmentId) => `segment-${segmentId}`,
);
if (this.props.selectedIds.group != null) {
return mappedIdsToKeys.concat(`group-${this.props.selectedIds.group}`);
return mappedIdsToKeys.concat(this.getKeyForGroupId(this.props.selectedIds.group));
}
return mappedIdsToKeys;
};
Expand Down Expand Up @@ -1686,6 +1726,8 @@ class SegmentsView extends React.Component<Props, State> {
icon: <DeleteOutlined />,
label: "Delete group",
},
this.getExpandSubgroupsItem(id),
this.getCollapseSubgroupsItem(id),
this.getMoveSegmentsHereMenuItem(id),
{
key: "groupAndMeshActionDivider",
Expand Down Expand Up @@ -1792,7 +1834,6 @@ class SegmentsView extends React.Component<Props, State> {
allowDrop={this.allowDrop}
onDrop={this.onDrop}
onSelect={this.onSelectTreeItem}
defaultExpandAll
className="segments-tree"
blockNode
// Passing an explicit height here, makes the tree virtualized
Expand All @@ -1817,6 +1858,8 @@ class SegmentsView extends React.Component<Props, State> {
overflow: "auto", // use hidden when not using virtualization
}}
ref={this.tree}
onExpand={this.onExpandTree}
expandedKeys={this.state.expandedGroupKeys}
/>
</div>
)}
Expand All @@ -1842,6 +1885,48 @@ class SegmentsView extends React.Component<Props, State> {
);
}

getExpandSubgroupsItem(groupId: number) {
const children = this.getSubGroupsAsTreeNodes(groupId);
const expandedKeySet = new Set(this.state.expandedGroupKeys);
const areAllChildrenExpanded = children.every((childNode) => expandedKeySet.has(childNode));
const isGroupItselfExpanded = expandedKeySet.has(this.getKeyForGroupId(groupId));
if (areAllChildrenExpanded && isGroupItselfExpanded) {
return null;
}
const groupsToExpand = children;
// It doesn't make sense to expand subgroups if the group itself is collapsed, so also expand the group.
if (!isGroupItselfExpanded) groupsToExpand.push(this.getKeyForGroupId(groupId));
return {
key: "expandAll",
onClick: () => {
this.expandGroups(groupsToExpand);
this.closeSegmentOrGroupDropdown();
},
icon: <ExpandAltOutlined />,
label: "Expand all subgroups",
};
}

getCollapseSubgroupsItem(groupId: number) {
const children = this.getSubGroupsAsTreeNodes(groupId);
const expandedKeySet = new Set(this.state.expandedGroupKeys);
const areAllChildrenCollapsed =
children.filter((childNode) => expandedKeySet.has(childNode)).length === 0;
const isGroupItselfCollapsed = !expandedKeySet.has(this.getKeyForGroupId(groupId));
if (areAllChildrenCollapsed || isGroupItselfCollapsed) {
return null;
}
return {
key: "collapseAll",
onClick: () => {
this.collapseGroups(children);
this.closeSegmentOrGroupDropdown();
},
icon: <ShrinkOutlined />,
label: "Collapse all subgroups",
};
}

createGroup(parentGroupId: number): void {
if (!this.props.visibleSegmentationLayer) {
return;
Expand Down