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

Update to React 18 #8048

Merged
merged 28 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
8d38615
update libs to react 18.3
hotzenklotz Sep 2, 2024
9bbc863
update reactDom to createRoot
hotzenklotz Sep 2, 2024
c556627
remove antd resolutions in package.json
hotzenklotz Sep 2, 2024
0c4b434
update antd to version 5.17.4
hotzenklotz Sep 2, 2024
a00bcba
updated redux to v4.0.5
hotzenklotz Sep 3, 2024
724ea0e
updated react.router-dom to v5.3.4
hotzenklotz Sep 3, 2024
e660e7e
replace react-virtualized with react-virtualized-auto-sizer lib
hotzenklotz Sep 4, 2024
a5bae3f
replace react-sortable-hoc with dnd-kit 1/2
hotzenklotz Sep 4, 2024
6d48ecf
replace react-sortable-hoc with dnd-kit 2/2
hotzenklotz Sep 4, 2024
bf51f6e
fix typescript errors
hotzenklotz Sep 4, 2024
aab53be
updated yarn lock
hotzenklotz Sep 4, 2024
bab7594
updated tanstack/query
hotzenklotz Sep 4, 2024
c409b3e
updated tanstack/query
hotzenklotz Sep 4, 2024
71f4ba0
update flex layout
hotzenklotz Sep 4, 2024
d4d1017
update react-json-tree
hotzenklotz Sep 4, 2024
2592486
fix remaining typescript errors
hotzenklotz Sep 4, 2024
d103ffe
merge
hotzenklotz Sep 4, 2024
8d6bc45
mock renderIndenpently in unit tests
hotzenklotz Sep 5, 2024
f3ef1a5
formatting
hotzenklotz Sep 5, 2024
f4441d4
fix unit tests
hotzenklotz Sep 5, 2024
f26323a
Merge branch 'master' of github.com:scalableminds/webknossos into rea…
hotzenklotz Sep 6, 2024
4222dc8
fix dark mode overwrite for flex layout
hotzenklotz Sep 6, 2024
12b2ce0
changelog
hotzenklotz Sep 6, 2024
bfa5042
Merge branch 'master' of github.com:scalableminds/webknossos into rea…
hotzenklotz Sep 10, 2024
ad7db4a
apply PR feedback for TS typing
hotzenklotz Sep 10, 2024
e673f0e
fix dark mode layer handles
hotzenklotz Sep 10, 2024
848edf1
format
hotzenklotz Sep 10, 2024
554897e
Merge branch 'master' of github.com:scalableminds/webknossos into rea…
hotzenklotz Sep 11, 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 @@ -14,6 +14,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released

### Changed
- 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 React to version 18. Updated many peer dependencies inlcuding Redux, React-Router, antd, and FlexLayout. [#8048](https://github.com/scalableminds/webknossos/pull/8048)

### Fixed

Expand Down
4 changes: 3 additions & 1 deletion frontend/javascripts/admin/team/team_list_view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ export function filterTeamMembersOf(team: APITeam, user: APIUser): boolean {
export function renderUsersForTeam(
team: APITeam,
allUsers: APIUser[] | null,
renderAdditionalContent = (_teamMember: APIUser, _team: APITeam) => {},
renderAdditionalContent = (_teamMember: APIUser, _team: APITeam): React.ReactNode => {
return null;
},
) {
if (allUsers === null) return;
const teamMembers = allUsers.filter((user) => filterTeamMembersOf(team, user));
Expand Down
3 changes: 2 additions & 1 deletion frontend/javascripts/admin/voxelytics/ai_model_list_view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { JobState } from "admin/job/job_list_view";
import { Link } from "react-router-dom";
import { useGuardedFetch } from "libs/react_helpers";
import { PageNotAvailableToNormalUser } from "components/permission_enforcer";
import type { Key } from "react";

export default function AiModelListView() {
const activeUser = useSelector((state: OxalisState) => state.activeUser);
Expand Down Expand Up @@ -63,7 +64,7 @@ export default function AiModelListView() {
value: username,
}),
),
onFilter: (value: string | number | boolean, model: AiModel) =>
onFilter: (value: Key | boolean, model: AiModel) =>
formatUserName(null, model.user).startsWith(String(value)),
filterSearch: true,
},
Expand Down
17 changes: 9 additions & 8 deletions frontend/javascripts/admin/voxelytics/task_view.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react";
import { JSONTree } from "react-json-tree";
import { JSONTree, type ShouldExpandNodeInitially, type LabelRenderer } from "react-json-tree";
import { Progress, Tabs, type TabsProps, Tooltip } from "antd";
import Markdown from "libs/markdown_adapter";
import {
Expand All @@ -14,11 +14,11 @@ import LogTab from "./log_tab";
import StatisticsTab from "./statistics_tab";
import { runStateToStatus, useTheme } from "./utils";
import { formatNumber } from "libs/format_utils";
function labelRenderer(_keyPath: Array<string | number>) {

const labelRenderer: LabelRenderer = function (_keyPath) {
const keyPath = _keyPath.slice().reverse();
const divWithId = <div id={`label-${keyPath.join(".")}`}>{keyPath.slice(-1)[0]}</div>;
return divWithId;
}
return <div id={`label-${keyPath.join(".")}`}>{keyPath.slice(-1)[0]}</div>;
};

function TaskView({
taskName,
Expand All @@ -39,9 +39,10 @@ function TaskView({
taskInfo: VoxelyticsTaskInfo;
onSelectTask: (id: string) => void;
}) {
const shouldExpandNode = (_keyPath: Array<string | number>, data: any) =>
const shouldExpandNode: ShouldExpandNodeInitially = function (_keyPath, data) {
// Expand all with at most 10 keys
(data.length || 0) <= 10;
return ((data as any[]).length || 0) <= 10;
};

const ingoingEdges = dag.edges.filter((edge) => edge.target === taskName);
const [theme, invertTheme] = useTheme();
Expand All @@ -54,7 +55,7 @@ function TaskView({
<JSONTree
data={task.config}
hideRoot
shouldExpandNode={shouldExpandNode}
shouldExpandNodeInitially={shouldExpandNode}
labelRenderer={labelRenderer}
theme={theme}
invertTheme={invertTheme}
Expand Down
6 changes: 3 additions & 3 deletions frontend/javascripts/admin/voxelytics/workflow_list_view.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type React from "react";
import { useEffect, useMemo, useState } from "react";
import { type Key, useEffect, useMemo, useState } from "react";
import { SyncOutlined } from "@ant-design/icons";
import { Table, Progress, Tooltip, Button, Input } from "antd";
import { Link } from "react-router-dom";
Expand Down Expand Up @@ -211,7 +211,7 @@ export default function WorkflowListView() {
text: username || "",
value: username || "",
})),
onFilter: (value: string | number | boolean, run: RenderRunInfo) =>
onFilter: (value: Key | boolean, run: RenderRunInfo) =>
run.userDisplayName?.startsWith(String(value)) || false,
filterSearch: true,
},
Expand All @@ -223,7 +223,7 @@ export default function WorkflowListView() {
text: hostname,
value: hostname,
})),
onFilter: (value: string | number | boolean, run: RenderRunInfo) =>
onFilter: (value: Key | boolean, run: RenderRunInfo) =>
run.hostName.startsWith(String(value)),
filterSearch: true,
},
Expand Down
26 changes: 14 additions & 12 deletions frontend/javascripts/components/pricing_enforcers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import UpgradePricingPlanModal from "admin/organization/upgrade_plan_modal";
import type { APIOrganization, APIUser } from "types/api_flow_types";
import type { TooltipPlacement } from "antd/lib/tooltip";
import { SwitchSetting } from "oxalis/view/components/setting_input_views";
import type { PopoverProps } from "antd/lib";

const PRIMARY_COLOR_HEX = rgbToHex(PRIMARY_COLOR);

Expand Down Expand Up @@ -50,20 +51,21 @@ const useActiveUserAndOrganization = (): [APIUser | null | undefined, APIOrganiz
return [activeUser, activeOrganization];
};

type PopoverEnforcedProps = RequiredPricingProps & {
activeUser: APIUser | null | undefined;
activeOrganization: APIOrganization | null;
placement?: TooltipPlacement;
zIndex?: number;
};
const PricingEnforcedPopover: React.FunctionComponent<PopoverEnforcedProps> = ({
type PopoverEnforcedProps = RequiredPricingProps &
PopoverProps & {
activeUser: APIUser | null | undefined;
activeOrganization: APIOrganization | null;
placement?: TooltipPlacement;
zIndex?: number;
};
const PricingEnforcedPopover = function ({
philippotto marked this conversation as resolved.
Show resolved Hide resolved
children,
requiredPricingPlan,
activeUser,
activeOrganization,
placement,
zIndex,
}) => {
}: PopoverEnforcedProps) {
return (
<Popover
color={PRIMARY_COLOR_HEX}
Expand All @@ -82,10 +84,10 @@ const PricingEnforcedPopover: React.FunctionComponent<PopoverEnforcedProps> = ({
);
};

export const PricingEnforcedSpan: React.FunctionComponent<RequiredPricingProps> = ({
export const PricingEnforcedSpan = function ({
children,
requiredPricingPlan,
}) => {
}: RequiredPricingProps & { children: React.ReactNode }) {
const [activeUser, activeOrganization] = useActiveUserAndOrganization();
const isFeatureAllowed = isFeatureAllowedByPricingPlan(activeOrganization, requiredPricingPlan);

Expand Down Expand Up @@ -173,11 +175,11 @@ export const PricingEnforcedSwitchSetting: React.FunctionComponent<
);
};

export const PricingEnforcedBlur: React.FunctionComponent<RequiredPricingProps> = ({
export const PricingEnforcedBlur = function ({
children,
requiredPricingPlan,
...restProps
}) => {
}: RequiredPricingProps & { children: React.ReactNode }) {
const [activeUser, activeOrganization] = useActiveUserAndOrganization();
const isFeatureAllowed = isFeatureAllowedByPricingPlan(activeOrganization, requiredPricingPlan);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,42 +1,59 @@
import { MenuOutlined, InfoCircleOutlined } from "@ant-design/icons";
import { List, Collapse, Tooltip, type CollapseProps } from "antd";
import React from "react";
import type { SortEnd } from "react-sortable-hoc";
import { SortableContainer, SortableElement, SortableHandle } from "react-sortable-hoc";
import { settings, settingsTooltips } from "messages";
import { DndContext, type DragEndEvent } from "@dnd-kit/core";
import { CSS } from "@dnd-kit/utilities";
import { SortableContext, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";

// Example taken and modified from https://4x.ant.design/components/table/#components-table-demo-drag-sorting-handler.
// Example taken and modified from https://ant.design/components/table/#components-table-demo-drag-sorting-handler.

const DragHandle = SortableHandle(() => <MenuOutlined style={{ cursor: "grab", color: "#999" }} />);
function SortableListItem({ colorLayerName }: { colorLayerName: string }) {
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
id: colorLayerName,
});

const SortableItem = SortableElement(({ name }: { name: string }) => (
<List.Item key={name}>
<DragHandle /> {name}
</List.Item>
));
const style = {
transform: CSS.Transform.toString(transform),
transition,
zIndex: isDragging ? "100" : "auto",
opacity: isDragging ? 0.3 : 1,
};

const SortableLayerSettingsContainer = SortableContainer(({ children }: { children: any }) => {
return <div style={{ paddingTop: -16, paddingBottom: -16 }}>{children}</div>;
});
return (
<List.Item id={colorLayerName} ref={setNodeRef} style={style}>
<MenuOutlined style={{ cursor: "grab", color: "#999" }} {...listeners} {...attributes} />{" "}
{colorLayerName}
</List.Item>
);
}

export default function ColorLayerOrderingTable({
colorLayerNames,
onChange,
}: {
colorLayerNames?: string[];
onChange?: (newColorLayerNames: string[]) => void;
}): JSX.Element {
const onSortEnd = ({ oldIndex, newIndex }: SortEnd) => {
document.body.classList.remove("is-dragging");
if (oldIndex !== newIndex && onChange && colorLayerNames) {
const movedElement = colorLayerNames[oldIndex];
const newColorLayerNames = colorLayerNames.filter((_, index) => index !== oldIndex);
newColorLayerNames.splice(newIndex, 0, movedElement);
onChange(newColorLayerNames);
}) {
const onSortEnd = (event: DragEndEvent) => {
const { active, over } = event;

if (active && over && colorLayerNames) {
const oldIndex = colorLayerNames.indexOf(active.id as string);
const newIndex = colorLayerNames.indexOf(over.id as string);

document.body.classList.remove("is-dragging");

if (oldIndex !== newIndex && onChange) {
const movedElement = colorLayerNames[oldIndex];
const newColorLayerNames = colorLayerNames.filter((_, index) => index !== oldIndex);
newColorLayerNames.splice(newIndex, 0, movedElement);
onChange(newColorLayerNames);
}
}
};

const isSettingEnabled = colorLayerNames && colorLayerNames.length > 1;
const sortingItems = isSettingEnabled ? colorLayerNames.map((name) => name) : [];
const collapsibleDisabledExplanation =
"The order of layers can only be configured when the dataset has multiple color layers.";

Expand All @@ -55,29 +72,25 @@ export default function ColorLayerOrderingTable({
{
label: panelTitle,
key: "1",
children: (
<SortableLayerSettingsContainer
onSortEnd={onSortEnd}
onSortStart={() =>
colorLayerNames &&
colorLayerNames.length > 1 &&
document.body.classList.add("is-dragging")
}
useDragHandle
>
{colorLayerNames?.map((name, index) => (
<SortableItem key={name} index={index} name={name} />
))}
</SortableLayerSettingsContainer>
),
children: sortingItems.map((name) => <SortableListItem key={name} colorLayerName={name} />),
},
];

return (
<Collapse
defaultActiveKey={[]}
collapsible={isSettingEnabled ? "header" : "disabled"}
items={collapseItems}
/>
<DndContext
autoScroll={false}
onDragStart={() => {
colorLayerNames && colorLayerNames.length > 1 && document.body.classList.add("is-dragging");
}}
onDragEnd={onSortEnd}
>
<SortableContext items={sortingItems} strategy={verticalListSortingStrategy}>
<Collapse
defaultActiveKey={[]}
collapsible={isSettingEnabled ? "header" : "disabled"}
items={collapseItems}
/>
</SortableContext>
</DndContext>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -679,7 +679,7 @@ class ExplorativeAnnotationsView extends React.PureComponent<Props, State> {
width: 300,
filters: ownerAndTeamsFilters,
filterMode: "tree",
onFilter: (value: string | number | boolean, tracing: APIAnnotationInfo) =>
onFilter: (value: React.Key | boolean, tracing: APIAnnotationInfo) =>
(tracing.owner != null && tracing.owner.id === value.toString()) ||
tracing.teams.some((team) => team.id === value),
sorter: Utils.localeCompareBy((annotation) => annotation.owner?.firstName || ""),
Expand Down
15 changes: 9 additions & 6 deletions frontend/javascripts/dashboard/folders/folder_tree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ import {
import { DeleteOutlined, EditOutlined, PlusOutlined } from "@ant-design/icons";
import { Dropdown, Modal, type MenuProps, Tree } from "antd";
import Toast from "libs/toast";
import type { DataNode, DirectoryTreeProps } from "antd/lib/tree";
import type { AntTreeNodeSelectedEvent, DataNode, DirectoryTreeProps } from "antd/lib/tree";
import memoizeOne from "memoize-one";
import classNames from "classnames";
import type { FolderItem } from "types/api_flow_types";
import { PricingEnforcedSpan } from "components/pricing_enforcers";
import { PricingPlanEnum } from "admin/organization/pricing_plan_utils";
import { AntTreeNodeBaseEvent } from "antd/es/tree/Tree";

const { DirectoryTree } = Tree;

Expand Down Expand Up @@ -78,18 +79,20 @@ export function FolderTreeSidebar({
});

const onSelect: DirectoryTreeProps["onSelect"] = useCallback(
(keys, event) => {
(keys: React.Key[], { nativeEvent }: { nativeEvent: MouseEvent }) => {
// Without the following check, the onSelect callback would also be called by antd
// when the user clicks on a menu entry in the context menu (e.g., deleting a folder
// would directly select it afterwards).
// Since the context menu is inserted at the root of the DOM, it's not a child node of
// the ant-tree container. Therefore, we can use this property to filter out those
// click events.
// The classic preventDefault() didn't work as an alternative workaround.
const doesEventReferToTreeUi = event.nativeEvent.target.closest(".ant-tree") != null;
if (keys.length > 0 && doesEventReferToTreeUi) {
context.setActiveFolderId(keys[0] as string);
context.setSelectedDatasets([]);
if (nativeEvent.target && nativeEvent.target instanceof HTMLElement) {
const doesEventReferToTreeUi = nativeEvent.target.closest(".ant-tree") != null;
if (keys.length > 0 && doesEventReferToTreeUi) {
context.setActiveFolderId(keys[0] as string);
context.setSelectedDatasets([]);
}
}
},
[context],
Expand Down
4 changes: 2 additions & 2 deletions frontend/javascripts/libs/react_helpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@ export function useGuardedFetch<T>(
updates.
*/
export function usePolledState(callback: (arg0: OxalisState) => void, interval: number = 1000) {
const store = useStore();
const oldState = useRef(null);
const store = useStore<OxalisState>();
const oldState = useRef<OxalisState | null>(null);
useInterval(() => {
const state = store.getState();

Expand Down
9 changes: 5 additions & 4 deletions frontend/javascripts/libs/render_independently.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import ReactDOM from "react-dom";
import { document } from "libs/window";
import { Provider } from "react-redux";
import GlobalThemeProvider from "theme";
import { createRoot } from "react-dom/client";

type DestroyFunction = () => void; // The returned promise gets resolved once the element is destroyed.

Expand All @@ -14,6 +15,7 @@ export default function renderIndependently(
import("oxalis/throttled_store").then((_Store) => {
const Store = _Store.default;
const div = document.createElement("div");
const react_root = createRoot(div);

if (!document.body) {
resolve();
Expand All @@ -23,21 +25,20 @@ export default function renderIndependently(
document.body.appendChild(div);

function destroy() {
const unmountResult = ReactDOM.unmountComponentAtNode(div);
react_root.unmount();

if (unmountResult && div.parentNode) {
if (div.parentNode) {
div.parentNode.removeChild(div);
}

resolve();
}

ReactDOM.render(
react_root.render(
// @ts-ignore
<Provider store={Store}>
<GlobalThemeProvider isMainProvider={false}>{getComponent(destroy)}</GlobalThemeProvider>
</Provider>,
div,
);
});
});
Expand Down
Loading