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 button to select all trees and all segments that match a search #8123

Merged
merged 20 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
79b3438
add button to select all trees that match a search
dieknolle3333 Oct 14, 2024
e71449d
add function for segments and improve icon
dieknolle3333 Oct 16, 2024
7fcca34
remove console log
dieknolle3333 Oct 17, 2024
27e841e
Merge branch 'master' into select-all-matching-trees
dieknolle3333 Oct 17, 2024
a9ad926
add ts-expect-error tag again
dieknolle3333 Oct 17, 2024
526fcc9
Merge branch 'master' into select-all-matching-trees
dieknolle3333 Oct 17, 2024
fa56c82
Merge branch 'master' into select-all-matching-trees
dieknolle3333 Oct 18, 2024
84301eb
focus first search result and only allow select all matches for leaves
dieknolle3333 Oct 18, 2024
9d48473
fix select segment group as search result
dieknolle3333 Oct 18, 2024
46c40d9
Merge branch 'master' into select-all-matching-trees
dieknolle3333 Oct 21, 2024
c5c32aa
expand parent groups and fix mixed tree and tree group selection
dieknolle3333 Oct 21, 2024
b7f484a
changelog
dieknolle3333 Oct 21, 2024
304a63a
merge master
dieknolle3333 Oct 21, 2024
3c89ebe
lint
dieknolle3333 Oct 21, 2024
9d829da
address review
dieknolle3333 Oct 24, 2024
0f7c1b7
Merge branch 'master' into select-all-matching-trees
dieknolle3333 Oct 24, 2024
a64028b
add placeholder and disable field if all matches all selected
dieknolle3333 Oct 25, 2024
13b01f4
Merge branch 'master' into select-all-matching-trees
dieknolle3333 Oct 28, 2024
52ef6b4
fix case where group is selected
dieknolle3333 Oct 28, 2024
dacdfc3
Merge branch 'master' into select-all-matching-trees
MichaelBuessemeyer Oct 28, 2024
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 @@ -16,6 +16,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released
- Most sliders have been improved: Wheeling above a slider now changes its value and double-clicking its knob resets it to its default value. [#8095](https://github.com/scalableminds/webknossos/pull/8095)
- It is now possible to search for unnamed segments with the full default name instead of only their id. [#8133](https://github.com/scalableminds/webknossos/pull/8133)
- Increased loading speed for precomputed meshes. [#8110](https://github.com/scalableminds/webknossos/pull/8110)
- Added a button to the search popover in the skeleton and segment tab to select all matching non-group results. [#8123](https://github.com/scalableminds/webknossos/pull/8123)
- Unified wording in UI and code: “Magnification”/“mag” is now used in place of “Resolution“ most of the time, compare [https://docs.webknossos.org/webknossos/terminology.html](terminology document). [#8111](https://github.com/scalableminds/webknossos/pull/8111)
- Added support for adding remote OME-Zarr NGFF version 0.5 datasets. [#8122](https://github.com/scalableminds/webknossos/pull/8122)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import { Input, Tooltip, Popover, Space, type InputRef } from "antd";
import { DownOutlined, UpOutlined } from "@ant-design/icons";
import { CheckSquareOutlined, DownOutlined, UpOutlined } from "@ant-design/icons";
import * as React from "react";
import memoizeOne from "memoize-one";
import ButtonComponent from "oxalis/view/components/button_component";
import Shortcut from "libs/shortcut_component";
import DomVisibilityObserver from "oxalis/view/components/dom_visibility_observer";
import { mod } from "libs/utils";

const PRIMARY_COLOR = "var(--ant-color-primary)";
Copy link
Contributor

Choose a reason for hiding this comment

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

Uh nice 🎉, this made me come up with the following possible refactoring: #8150

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I LOVE THE IDEA, THANK YOU 🙌


type Props<S> = {
data: S[];
searchKey: keyof S | ((item: S) => string);
onSelect: (arg0: S) => void;
onSelectAllMatches?: (arg0: S[]) => void;
children: React.ReactNode;
provideShortcut?: boolean;
targetId: string;
Expand All @@ -20,6 +23,7 @@ type State = {
isVisible: boolean;
searchQuery: string;
currentPosition: number | null | undefined;
areAllMatchesSelected: boolean;
};

export default class AdvancedSearchPopover<
Expand All @@ -29,6 +33,7 @@ export default class AdvancedSearchPopover<
isVisible: false,
searchQuery: "",
currentPosition: null,
areAllMatchesSelected: false,
};

getAvailableOptions = memoizeOne(
Expand Down Expand Up @@ -69,6 +74,7 @@ export default class AdvancedSearchPopover<
currentPosition = mod(currentPosition + offset, numberOfAvailableOptions);
this.setState({
currentPosition,
areAllMatchesSelected: false,
});
this.props.onSelect(availableOptions[currentPosition]);
};
Expand Down Expand Up @@ -101,21 +107,25 @@ export default class AdvancedSearchPopover<

render() {
const { data, searchKey, provideShortcut, children, targetId } = this.props;
const { searchQuery, isVisible } = this.state;
const { searchQuery, isVisible, areAllMatchesSelected } = this.state;
let { currentPosition } = this.state;
const availableOptions = this.getAvailableOptions(data, searchQuery, searchKey);
const numberOfAvailableOptions = availableOptions.length;
// Ensure that currentPosition to not higher than numberOfAvailableOptions.
currentPosition =
currentPosition == null ? -1 : Math.min(currentPosition, numberOfAvailableOptions - 1);
const hasNoResults = numberOfAvailableOptions === 0;
const hasMultipleResults = numberOfAvailableOptions > 1;
const availableOptionsToSelectAllMatches = availableOptions.filter(
(result) => result.type === "Tree" || result.type === "segment",
);
const isSelectAllMatchesDisabled = availableOptionsToSelectAllMatches.length < 2;
dieknolle3333 marked this conversation as resolved.
Show resolved Hide resolved
const additionalInputStyle =
hasNoResults && searchQuery !== ""
? {
color: "red",
}
: {};
const selectAllMatchesButtonColor = areAllMatchesSelected ? PRIMARY_COLOR : undefined;
return (
<React.Fragment>
{provideShortcut ? (
Expand Down Expand Up @@ -171,9 +181,23 @@ export default class AdvancedSearchPopover<
this.setState({
searchQuery: evt.target.value,
currentPosition: null,
areAllMatchesSelected: false,
})
}
addonAfter={`${currentPosition + 1}/${numberOfAvailableOptions}`}
addonAfter={
<div
style={{
minWidth: 25,
color: areAllMatchesSelected
? "var(--ant-color-text-disabled)"
: undefined,
}}
>
{areAllMatchesSelected
? "all"
: `${currentPosition + 1}/${numberOfAvailableOptions}`}
</div>
}
ref={this.autoFocus}
autoFocus
/>
Expand All @@ -183,7 +207,7 @@ export default class AdvancedSearchPopover<
width: 40,
}}
onClick={this.selectPreviousOption}
disabled={!hasMultipleResults}
disabled={hasNoResults}
>
<UpOutlined />
</ButtonComponent>
Expand All @@ -194,11 +218,32 @@ export default class AdvancedSearchPopover<
width: 40,
}}
onClick={this.selectNextOption}
disabled={!hasMultipleResults}
disabled={hasNoResults}
>
<DownOutlined />
</ButtonComponent>
</Tooltip>
<Tooltip title="Select all matches (except groups)">
<ButtonComponent
style={{
width: 40,
color: selectAllMatchesButtonColor,
borderColor: selectAllMatchesButtonColor,
}}
onClick={
this.props.onSelectAllMatches != null
? () => {
this.props.onSelectAllMatches!(availableOptionsToSelectAllMatches);
if (!areAllMatchesSelected)
this.setState({ areAllMatchesSelected: true });
}
: undefined
}
disabled={isSelectAllMatchesDisabled}
>
<CheckSquareOutlined />
</ButtonComponent>
</Tooltip>
dieknolle3333 marked this conversation as resolved.
Show resolved Hide resolved
</Space.Compact>
</React.Fragment>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ import { SegmentStatisticsModal } from "./segment_statistics_modal";
import type { ItemType } from "antd/lib/menu/interface";
import { InputWithUpdateOnBlur } from "oxalis/view/components/input_with_update_on_blur";

const SCROLL_DELAY_MS = 50;

const { confirm } = Modal;
const { Option } = Select;
// Interval in ms to check for running mesh file computation jobs for this dataset
Expand Down Expand Up @@ -1590,7 +1592,7 @@ class SegmentsView extends React.Component<Props, State> {
this.setState(({ renamingCounter }) => ({ renamingCounter: renamingCounter - 1 }));
};

handleSearchSelect = (selectedElement: SegmentHierarchyNode) => {
maybeExpandParentGroup = (selectedElement: SegmentHierarchyNode) => {
if (this.tree?.current == null) {
return;
}
Expand All @@ -1606,16 +1608,47 @@ class SegmentsView extends React.Component<Props, State> {
if (expandedGroups) {
this.setExpandedGroupsFromSet(expandedGroups);
}
};

handleSearchSelect = (selectedElement: SegmentHierarchyNode) => {
this.maybeExpandParentGroup(selectedElement);
// As parent groups might still need to expand, we need to wait for this to finish.
setTimeout(() => {
if (this.tree.current) this.tree.current.scrollTo({ key: selectedElement.key });
}, 50);
}, SCROLL_DELAY_MS);
const isASegment = "color" in selectedElement;
if (isASegment) {
this.onSelectSegment(selectedElement);
} else {
if (this.props.visibleSegmentationLayer == null) return;
Store.dispatch(
setSelectedSegmentsOrGroupAction(
[],
selectedElement.id,
this.props.visibleSegmentationLayer?.name,
),
);
}
};

handleSelectAllMatchingSegments = (allMatches: SegmentHierarchyNode[]) => {
if (this.props.visibleSegmentationLayer == null) return;
const allMatchingSegmentIds = allMatches.map((match) => {
this.maybeExpandParentGroup(match);
return match.id;
});
Store.dispatch(
setSelectedSegmentsOrGroupAction(
allMatchingSegmentIds,
null,
this.props.visibleSegmentationLayer.name,
),
);
setTimeout(() => {
this.tree.current?.scrollTo({ key: allMatches[0].key });
}, SCROLL_DELAY_MS);
};
dieknolle3333 marked this conversation as resolved.
Show resolved Hide resolved

getSegmentStatisticsModal = (groupId: number) => {
const visibleSegmentationLayer = this.props.visibleSegmentationLayer;
if (visibleSegmentationLayer == null) {
Expand Down Expand Up @@ -1833,6 +1866,7 @@ class SegmentsView extends React.Component<Props, State> {
searchKey={(item) => getSegmentName(item)}
provideShortcut
targetId={segmentsTabId}
onSelectAllMatches={this.handleSelectAllMatchingSegments}
>
<ButtonComponent
size="small"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,7 @@ class SkeletonTabView extends React.PureComponent<Props, State> {
});
};

handleSearchSelect = (selectedElement: TreeOrTreeGroup) => {
maybeExpandParentGroups = (selectedElement: TreeOrTreeGroup) => {
const { skeletonTracing } = this.props;
if (!skeletonTracing) {
return;
Expand All @@ -682,13 +682,25 @@ class SkeletonTabView extends React.PureComponent<Props, State> {
if (expandedGroups) {
this.props.onSetExpandedGroups(expandedGroups);
}
};

handleSearchSelect = (selectedElement: TreeOrTreeGroup) => {
this.maybeExpandParentGroups(selectedElement);
if (selectedElement.type === GroupTypeEnum.TREE) {
this.props.onSetActiveTree(selectedElement.id);
} else {
this.props.onSetActiveTreeGroup(selectedElement.id);
}
};

handleSelectAllMatchingTrees = (matchingTrees: TreeOrTreeGroup[]) => {
const treeIds = matchingTrees.map((tree) => {
this.maybeExpandParentGroups(tree);
return tree.id;
});
this.setState({ selectedTreeIds: treeIds });
};

getTreesComponents(sortBy: string) {
if (!this.props.skeletonTracing) {
return null;
Expand Down Expand Up @@ -864,6 +876,7 @@ class SkeletonTabView extends React.PureComponent<Props, State> {
searchKey="name"
provideShortcut
targetId={treeTabId}
onSelectAllMatches={this.handleSelectAllMatchingTrees}
>
<ButtonComponent title="Open the search via CTRL + Shift + F">
<SearchOutlined />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,7 @@ function TreeHierarchyView(props: Props) {
}
}

function onSelectGroupNode(node: TreeNode) {
const groupId = node.id;
function onSelectGroupNode(groupId: number) {
const numberOfSelectedTrees = props.selectedTreeIds.length;

if (numberOfSelectedTrees > 1) {
Expand Down Expand Up @@ -254,11 +253,14 @@ function TreeHierarchyView(props: Props) {
const checkedKeys = deepFlatFilter(UITreeData, (node) => node.isChecked).map((node) => node.key);

// selectedKeys is mainly used for highlighting, i.e. blueish background color
const selectedKeys = props.selectedTreeIds.map((treeId) =>
getNodeKey(GroupTypeEnum.TREE, treeId),
);
const selectedKeys = props.activeGroupId
? [getNodeKey(GroupTypeEnum.GROUP, props.activeGroupId)]
: props.selectedTreeIds.map((treeId) => getNodeKey(GroupTypeEnum.TREE, treeId));

if (props.activeGroupId) selectedKeys.push(getNodeKey(GroupTypeEnum.GROUP, props.activeGroupId));
useEffect(
() => treeRef.current?.scrollTo({ key: selectedKeys[0], align: "auto" }),
[selectedKeys[0]],
);
dieknolle3333 marked this conversation as resolved.
Show resolved Hide resolved

return (
<>
Expand Down Expand Up @@ -297,7 +299,7 @@ function TreeHierarchyView(props: Props) {
onSelect={(_selectedKeys, info: { node: TreeNode; nativeEvent: MouseEvent }) =>
info.node.type === GroupTypeEnum.TREE
? onSelectTreeNode(info.node, info.nativeEvent)
: onSelectGroupNode(info.node)
: onSelectGroupNode(info.node.id)
}
onDrop={onDrop}
onCheck={onCheck}
Expand Down