Skip to content

Commit

Permalink
Wrap antd tree to enable scrolling while dragging (#8162)
Browse files Browse the repository at this point in the history
* Wrap antd tree to enable scrolling while dragging

* add explanation to biome ignore comment

* add changelog entry

* fix typing

* extract magic numbers into constants

* apply feedback
- send datasource id in correct format to backend
- fix dataset renaming in dataset settings
- fix typo in filename

---------

Co-authored-by: Michael Büßemeyer <[email protected]>
  • Loading branch information
2 people authored and knollengewaechs committed Nov 11, 2024
1 parent 53cad36 commit e0fd7b6
Show file tree
Hide file tree
Showing 10 changed files with 93 additions and 24 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released
- Fixed a bug during dataset upload in case the configured `datastore.baseFolder` is an absolute path. [#8098](https://github.com/scalableminds/webknossos/pull/8098) [#8103](https://github.com/scalableminds/webknossos/pull/8103)
- Fixed bbox export menu item [#8152](https://github.com/scalableminds/webknossos/pull/8152)
- When trying to save an annotation opened via a link including a sharing token, the token is automatically discarded in case it is insufficient for update actions but the users token is. [#8139](https://github.com/scalableminds/webknossos/pull/8139)
- Fix that scrolling in the trees and segments tab did not work while dragging. [#8162](https://github.com/scalableminds/webknossos/pull/8162)
- Fixed that uploading a dataset which needs a conversion failed when the angstrom unit was configured for the conversion. [#8173](https://github.com/scalableminds/webknossos/pull/8173)
- Fixed that the skeleton search did not automatically expand groups that contained the selected tree [#8129](https://github.com/scalableminds/webknossos/pull/8129)
- Fixed a bug that zarr streaming version 3 returned the shape of mag (1, 1, 1) / the finest mag for all mags. [#8116](https://github.com/scalableminds/webknossos/pull/8116)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import InputCatcher from "oxalis/view/input_catcher";
import LayerSettingsTab from "oxalis/view/left-border-tabs/layer_settings_tab";
import RecordingSwitch from "oxalis/view/recording_switch";
import SegmentsView from "oxalis/view/right-border-tabs/segments_tab/segments_view";
import SkeletonTabView from "oxalis/view/right-border-tabs/skeleton_tab_view";
import SkeletonTabView from "oxalis/view/right-border-tabs/trees_tab/skeleton_tab_view";
import Statusbar from "oxalis/view/statusbar";
import type { OxalisState, BusyBlockingInfo, BorderOpenStatus } from "oxalis/store";
import Store from "oxalis/store";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { RenderToPortal } from "oxalis/view/layouting/portal_utils";
import NmlUploadZoneContainer from "oxalis/view/nml_upload_zone_container";
import PresentModernControls from "oxalis/view/novel_user_experiences/01-present-modern-controls";
import WelcomeToast from "oxalis/view/novel_user_experiences/welcome_toast";
import { importTracingFiles } from "oxalis/view/right-border-tabs/skeleton_tab_view";
import { importTracingFiles } from "oxalis/view/right-border-tabs/trees_tab/skeleton_tab_view";
import TracingView from "oxalis/view/tracing_view";
import VersionView from "oxalis/view/version_view";
import * as React from "react";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Tree as AntdTree, type TreeProps } from "antd";
import type { BasicDataNode } from "antd/es/tree";
import { throttle } from "lodash";
import { useCallback, useRef } from "react";
import type RcTree from "rc-tree";

const MIN_SCROLL_SPEED = 30;
const MAX_SCROLL_SPEED = 200;
const MIN_SCROLL_AREA_HEIGHT = 60;
const SCROLL_AREA_RATIO = 10; // 1/10th of the container height
const THROTTLE_TIME = 25;

function ScrollableVirtualizedTree<T extends BasicDataNode>(
props: TreeProps<T> & { ref: React.RefObject<RcTree> },
) {
const wrapperRef = useRef<HTMLDivElement>(null);
// biome-ignore lint/correctness/useExhaustiveDependencies: biome is not smart enough to notice that the function needs to be re-created when wrapperRef changes.
const onDragOver = useCallback(
throttle((info: { event: React.DragEvent<HTMLDivElement> }) => {
const target = info.event.target as HTMLElement;
if (!target || !wrapperRef.current) {
return;
}
const { bottom: currentBottom, top: currentTop } = target.getBoundingClientRect();
const { bottom: boxBottom, top: boxTop } = wrapperRef.current.getBoundingClientRect();
const scrollableList = wrapperRef.current.getElementsByClassName("ant-tree-list-holder")[0];
if (!scrollableList) {
return;
}
const scrollAreaHeight = Math.max(
MIN_SCROLL_AREA_HEIGHT,
Math.round((boxBottom - boxTop) / SCROLL_AREA_RATIO),
);

if (currentTop > boxBottom - scrollAreaHeight && scrollableList) {
const ratioWithinScrollingArea =
(currentTop - (boxBottom - scrollAreaHeight)) / scrollAreaHeight;
const scrollingValue = Math.max(
Math.round(ratioWithinScrollingArea * MAX_SCROLL_SPEED),
MIN_SCROLL_SPEED,
);
scrollableList.scrollTop += scrollingValue;
}
if (boxTop + scrollAreaHeight > currentBottom && scrollableList) {
const ratioWithinScrollingArea =
(boxTop + scrollAreaHeight - currentBottom) / scrollAreaHeight;
const scrollingValue = Math.max(
Math.round(ratioWithinScrollingArea * MAX_SCROLL_SPEED),
MIN_SCROLL_SPEED,
);
scrollableList.scrollTop -= scrollingValue;
}
}, THROTTLE_TIME),
[wrapperRef],
);

return (
<div ref={wrapperRef}>
<AntdTree {...props} onDragOver={onDragOver} />
</div>
);
}

export default ScrollableVirtualizedTree;
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import {
Modal,
Popover,
Select,
Tree,
type MenuProps,
} from "antd";
import type { DataNode } from "antd/lib/tree";
Expand Down Expand Up @@ -136,6 +135,7 @@ import { MetadataEntryTableRows } from "../metadata_table";
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";
import ScrollableVirtualizedTree from "../scrollable_virtualized_tree";

const SCROLL_DELAY_MS = 50;

Expand Down Expand Up @@ -1904,7 +1904,7 @@ class SegmentsView extends React.Component<Props, State> {
overflow: "hidden",
}}
>
<Tree
<ScrollableVirtualizedTree<SegmentHierarchyNode>
allowDrop={this.allowDrop}
onDrop={this.onDrop}
onSelect={this.onSelectTreeItem}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ import {
} from "oxalis/model/accessors/volumetracing_accessor";
import type { MenuClickEventHandler } from "rc-menu/lib/interface";
import { hasSegmentIndexInDataStore } from "admin/admin_rest_api";
import type { BasicDataNode } from "antd/es/tree";

const { confirm } = Modal;

export type SegmentHierarchyGroup = {
export type SegmentHierarchyGroup = BasicDataNode & {
title: string;
type: "group";
name: string | null | undefined;
Expand All @@ -25,11 +26,12 @@ export type SegmentHierarchyGroup = {
children: Array<SegmentHierarchyNode>;
};

export type SegmentHierarchyLeaf = Segment & {
type: "segment";
key: string;
title: string;
};
export type SegmentHierarchyLeaf = BasicDataNode &
Segment & {
type: "segment";
key: string;
title: string;
};

export type SegmentHierarchyNode = SegmentHierarchyLeaf | SegmentHierarchyGroup;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,12 @@ import type {
} from "oxalis/store";
import Store from "oxalis/store";
import Toast from "libs/toast";
import TreeHierarchyView from "oxalis/view/right-border-tabs/tree_hierarchy_view";
import TreeHierarchyView from "oxalis/view/right-border-tabs/trees_tab/tree_hierarchy_view";
import * as Utils from "libs/utils";
import { api } from "oxalis/singletons";
import messages from "messages";
import AdvancedSearchPopover from "./advanced_search_popover";
import DeleteGroupModalView from "./delete_group_modal_view";
import AdvancedSearchPopover from "../advanced_search_popover";
import DeleteGroupModalView from "../delete_group_modal_view";
import { isAnnotationOwner } from "oxalis/model/accessors/annotation_accessor";
import { LongUnitToShortUnitMap } from "oxalis/constants";

Expand Down Expand Up @@ -365,7 +365,7 @@ class SkeletonTabView extends React.PureComponent<Props, State> {
_groups: Array<TreeGroup>,
_groupToTreesMap: Record<number, Array<Tree>>,
_sortBy: string,
): Generator<TreeOrTreeGroup, void, void> {
): Generator<TreeOrTreeGroup, void, undefined> {
for (const group of _groups) {
yield makeGroup(group);

Expand All @@ -380,7 +380,6 @@ class SkeletonTabView extends React.PureComponent<Props, State> {
// Trees are sorted by the sortBy property
const sortedTrees = _.orderBy(_groupToTreesMap[group.groupId], [_sortBy], ["asc"]);

// @ts-expect-error ts-migrate(2766) FIXME: Cannot delegate iteration to value because the 'ne... Remove this comment to see the full error message
yield* sortedTrees.map(makeTree);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ import {
MISSING_GROUP_ID,
type TreeNode,
} from "oxalis/view/right-border-tabs/tree_hierarchy_view_helpers";
import { HideTreeEdgesIcon } from "./hide_tree_eges_icon";
import { ColoredDotIcon } from "./segments_tab/segment_list_item";
import { HideTreeEdgesIcon } from "./hide_tree_edges_icon";
import { ColoredDotIcon } from "../segments_tab/segment_list_item";

export type Props = {
activeTreeId: number | null | undefined;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { DownOutlined } from "@ant-design/icons";
import { Tree as AntdTree, type GetRef, type MenuProps, Modal, type TreeProps } from "antd";
import { type Tree as AntdTree, type GetRef, type MenuProps, Modal, type TreeProps } from "antd";
import React, { memo, useCallback, useEffect, useRef, useState } from "react";
import AutoSizer from "react-virtualized-auto-sizer";
import { mapGroups } from "oxalis/model/accessors/skeletontracing_accessor";
Expand All @@ -25,8 +25,8 @@ import {
moveGroupsHelper,
type TreeNode,
} from "oxalis/view/right-border-tabs/tree_hierarchy_view_helpers";
import { getContextMenuPositionFromEvent } from "../context_menu";
import { ContextMenuContainer } from "./sidebar_context_menu";
import { getContextMenuPositionFromEvent } from "../../context_menu";
import { ContextMenuContainer } from "../sidebar_context_menu";
import {
onBatchActions,
type Props,
Expand All @@ -36,10 +36,11 @@ import {
setExpandedGroups,
setUpdateTreeGroups,
} from "./tree_hierarchy_renderers";
import { ResizableSplitPane } from "./resizable_split_pane";
import { MetadataEntryTableRows } from "./metadata_table";
import { ResizableSplitPane } from "../resizable_split_pane";
import { MetadataEntryTableRows } from "../metadata_table";
import type { MetadataEntryProto } from "types/api_flow_types";
import { InputWithUpdateOnBlur } from "../components/input_with_update_on_blur";
import { InputWithUpdateOnBlur } from "../../components/input_with_update_on_blur";
import ScrollableVirtualizedTree from "../scrollable_virtualized_tree";

const onCheck: TreeProps<TreeNode>["onCheck"] = (_checkedKeysValue, info) => {
const { id, type } = info.node;
Expand All @@ -61,6 +62,7 @@ function TreeHierarchyView(props: Props) {
const [menu, setMenu] = useState<MenuProps | null>(null);

const treeRef = useRef<GetRef<typeof AntdTree>>(null);
const wrapperRef = useRef<HTMLDivElement>(null);

useEffect(() => {
// equivalent of LifeCycle hook "getDerivedStateFromProps"
Expand Down Expand Up @@ -275,12 +277,13 @@ function TreeHierarchyView(props: Props) {
<AutoSizer>
{({ height, width }) => (
<div
ref={wrapperRef}
style={{
height,
width,
}}
>
<AntdTree
<ScrollableVirtualizedTree
treeData={UITreeData}
height={height}
ref={treeRef}
Expand Down

0 comments on commit e0fd7b6

Please sign in to comment.