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

Improve performance of Bounding Box Context Menus #8059

Merged
merged 10 commits into from
Sep 19, 2024
2 changes: 1 addition & 1 deletion CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released

### Added
- It is now possible to focus a bounding box in the bounding box tab by clicking its edges in a viewport or via a newly added context menu entry. [#8054](https://github.com/scalableminds/webknossos/pull/8054)
### Added
- Added an assertion to the backend to ensure unique keys in the metadata info of datasets and folders. [#8068](https://github.com/scalableminds/webknossos/issues/8068)

### Changed
- Clicking on a bounding box within the bounding box tab centers it within the viewports and focusses it in the list. [#8049](https://github.com/scalableminds/webknossos/pull/8049)
- For self-hosted versions, the text in the data set upload view was updated to recommend switching to webknossos.org. [#7996](https://github.com/scalableminds/webknossos/pull/7996)
- Updated frontend package management to yarn version 4. [8061](https://github.com/scalableminds/webknossos/pull/8061)
- Updated React to version 18. Updated many peer dependencies inlcuding Redux, React-Router, antd, and FlexLayout. [#8048](https://github.com/scalableminds/webknossos/pull/8048)
- Improved the performance of context menus in the bounding box tab. [#8059](https://github.com/scalableminds/webknossos/pull/8059)

### Fixed
- The JS API v2 has been removed as it was deprecated by v3 in 2018. Please upgrade to v3 in case your scripts still use v2. [#8076](https://github.com/scalableminds/webknossos/pull/8076)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,8 @@ type UserBoundingBoxInputProps = {
isLockedByOwner: boolean;
isOwner: boolean;
visibleSegmentationLayer: APISegmentationLayer | null | undefined;
onOpenContextMenu: (menu: MenuProps, event: React.MouseEvent<HTMLDivElement>) => void;
onHideContextMenu?: () => void;
};
type State = {
isEditing: boolean;
Expand Down Expand Up @@ -487,8 +489,15 @@ class UserBoundingBoxInput extends React.PureComponent<UserBoundingBoxInputProps
const min: Vector3 = [value[0], value[1], value[2]];
const max: Vector3 = [value[0] + value[3], value[1] + value[4], value[2] + value[5]];
api.tracing.registerSegmentsForBoundingBox(min, max, name);
this.maybeCloseContextMenu();
}

maybeCloseContextMenu = () => {
if (this.props.onHideContextMenu) {
this.props.onHideContextMenu();
}
};

render() {
const { name } = this.state;
const {
Expand All @@ -501,6 +510,7 @@ class UserBoundingBoxInput extends React.PureComponent<UserBoundingBoxInputProps
disabled,
isLockedByOwner,
isOwner,
onOpenContextMenu,
} = this.props;
const upscaledColor = color.map((colorPart) => colorPart * 255) as any as Vector3;
const marginRightStyle = {
Expand Down Expand Up @@ -572,17 +582,14 @@ class UserBoundingBoxInput extends React.PureComponent<UserBoundingBoxInputProps
},
];

return (
<div onClick={(e) => e.stopPropagation()}>
<Dropdown menu={{ items }}>
<EllipsisOutlined style={marginLeftStyle} />
</Dropdown>
</div>
);
return { items };
};

return (
<div>
<div
onContextMenu={(evt) => onOpenContextMenu(getContextMenu(), evt)}
onClick={this.props.onHideContextMenu}
>
<Row
style={{
marginTop: 10,
Expand Down Expand Up @@ -622,7 +629,14 @@ class UserBoundingBoxInput extends React.PureComponent<UserBoundingBoxInputProps
</span>
</FastTooltip>
</Col>
<Col span={2}>{getContextMenu()}</Col>
<Col span={2}>
<div
onContextMenu={(evt) => onOpenContextMenu(getContextMenu(), evt)}
onClick={(evt) => onOpenContextMenu(getContextMenu(), evt)}
>
<EllipsisOutlined style={marginLeftStyle} />
</div>
</Col>
</Row>
<Row
style={{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Table, Tooltip, Typography } from "antd";
import { type MenuProps, Table, Tooltip, Typography } from "antd";
import { PlusSquareOutlined } from "@ant-design/icons";
import { useSelector, useDispatch } from "react-redux";
import React, { useEffect, useRef, useState } from "react";
import type React from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import _ from "lodash";
import UserBoundingBoxInput from "oxalis/view/components/setting_input_views";
import {
Expand All @@ -22,10 +23,13 @@ import * as Utils from "libs/utils";
import type { OxalisState, UserBoundingBox } from "oxalis/store";
import DownloadModalView from "../action-bar/download_modal_view";
import { APIJobType } from "types/api_flow_types";
import { ContextMenuContainer } from "./sidebar_context_menu";
import { getContextMenuPositionFromEvent } from "../context_menu";
import AutoSizer from "react-virtualized-auto-sizer";
import { setActiveUserBoundingBoxId } from "oxalis/model/actions/ui_actions";

const ADD_BBOX_BUTTON_HEIGHT = 32;
const CONTEXT_MENU_CLASS = "bbox-list-context-menu-overlay";

export default function BoundingBoxTab() {
const bboxTableRef: Parameters<typeof Table>[0]["ref"] = useRef(null);
Expand All @@ -40,6 +44,8 @@ export default function BoundingBoxTab() {
(state: OxalisState) => state.uiInformation.activeUserBoundingBoxId,
);
const { userBoundingBoxes } = getSomeTracing(tracing);
const [contextMenuPosition, setContextMenuPosition] = useState<[number, number] | null>(null);
const [menu, setMenu] = useState<MenuProps | null>(null);
const dispatch = useDispatch();

const setChangeBoundingBoxBounds = (id: number, boundingBox: BoundingBoxType) =>
Expand All @@ -53,7 +59,10 @@ export default function BoundingBoxTab() {

const setPosition = (position: Vector3) => dispatch(setPositionAction(position));

const deleteBoundingBox = (id: number) => dispatch(deleteUserBoundingBoxAction(id));
const deleteBoundingBox = (id: number) => {
dispatch(deleteUserBoundingBoxAction(id));
hideContextMenu();
};

const setBoundingBoxVisibility = (id: number, isVisible: boolean) =>
dispatch(
Expand All @@ -80,6 +89,11 @@ export default function BoundingBoxTab() {
setChangeBoundingBoxBounds(id, Utils.computeBoundingBoxFromArray(boundingBox));
}

function handleExportBoundingBox(bb: UserBoundingBox) {
_.partial(setSelectedBoundingBoxForExport, bb);
hideContextMenu();
}

function handleGoToBoundingBox(id: number) {
const boundingBoxEntry = userBoundingBoxes.find((bbox) => bbox.id === id);

Expand All @@ -94,6 +108,7 @@ export default function BoundingBoxTab() {
min[2] + (max[2] - min[2]) / 2,
];
setPosition(center);
hideContextMenu();
}

const isViewMode = useSelector(
Expand Down Expand Up @@ -132,14 +147,16 @@ export default function BoundingBoxTab() {
isVisible={bb.isVisible}
onBoundingChange={_.partial(handleBoundingBoxBoundingChange, bb.id)}
onDelete={_.partial(deleteBoundingBox, bb.id)}
onExport={isExportEnabled ? _.partial(setSelectedBoundingBoxForExport, bb) : () => {}}
onExport={isExportEnabled ? () => handleExportBoundingBox(bb) : () => {}}
onGoToBoundingBox={_.partial(handleGoToBoundingBox, bb.id)}
onVisibilityChange={_.partial(setBoundingBoxVisibility, bb.id)}
onNameChange={_.partial(setBoundingBoxName, bb.id)}
onColorChange={_.partial(setBoundingBoxColor, bb.id)}
disabled={!allowUpdate}
isLockedByOwner={isLockedByOwner}
isOwner={isOwner}
onOpenContextMenu={onOpenContextMenu}
onHideContextMenu={hideContextMenu}
/>
),
},
Expand All @@ -159,6 +176,30 @@ export default function BoundingBoxTab() {
</div>
) : null;

const onOpenContextMenu = (menu: MenuProps, event: React.MouseEvent<HTMLDivElement>) => {
event.preventDefault();
event.stopPropagation(); // Prevent that the bounding box gets activated when the context menu is opened.

const [x, y] = getContextMenuPositionFromEvent(event, CONTEXT_MENU_CLASS);
showContextMenuAt(x, y, menu);
};

const showContextMenuAt = useCallback((xPos: number, yPos: number, menu: MenuProps) => {
// On Windows the right click to open the context menu is also triggered for the overlay
// of the context menu. This causes the context menu to instantly close after opening.
// Therefore delay the state update to delay that the context menu is rendered.
// Thus the context overlay does not get the right click as an event and therefore does not close.
setTimeout(() => {
setContextMenuPosition([xPos, yPos]);
setMenu(menu);
}, 0);
}, []);

const hideContextMenu = useCallback(() => {
setContextMenuPosition(null);
setMenu(null);
}, []);

return (
<div
className="padded-tab-content"
Expand All @@ -167,6 +208,12 @@ export default function BoundingBoxTab() {
height: "100%",
}}
>
<ContextMenuContainer
hideContextMenu={hideContextMenu}
contextMenuPosition={contextMenuPosition}
menu={menu}
className={CONTEXT_MENU_CLASS}
/>
{/* Don't render a table in view mode. */}
{isViewMode ? null : userBoundingBoxes.length > 0 ? (
<AutoSizer defaultHeight={500}>
Expand Down Expand Up @@ -195,6 +242,7 @@ export default function BoundingBoxTab() {
// the height of the diff, the AutoSizer will always rerender the table and toggle an additional scrollbar.
onRow={(bb) => ({
onClick: () => {
hideContextMenu();
handleGoToBoundingBox(bb.id);
dispatch(setActiveUserBoundingBoxId(bb.id));
},
Expand Down
6 changes: 5 additions & 1 deletion frontend/stylesheets/trace_view/_tracing_view.less
Original file line number Diff line number Diff line change
Expand Up @@ -602,12 +602,16 @@ img.keyboard-mouse-icon:first-child {
z-index: 110;
}

.segment-list-context-menu-overlay {
.segment-list-context-menu-overlay,.bbox-list-context-menu-overlay {
// The parent has a padding of 10px on all sides which we need to subtract here.
width: calc(100% - 20px) !important;
height: calc(100% - 20px) !important;
}

.tree-list-context-menu-overlay{
Copy link
Member

Choose a reason for hiding this comment

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

This might fix #8058

width: calc(100% - 20px) !important;
height: calc(100% - var(--navbar-height) - var(--footer-height) - 30px) !important;
}

#main-tooltip {
max-width: 250px;
Expand Down