Skip to content

Commit 531a578

Browse files
authored
Refactor components to use functional components and hooks (#8751)
Converted several more React class-based components to functional components. Mostly from `/frontend/javascripts/viewer/view/components`. - ButtonComponent, - CheckboxComponent - EditableTextIcon - EditableTextLabel - InputComponent - SettingsInputView ### Issues: - Contributes to #8747 ------ (Please delete unneeded items, merge only when none are left open) - [ ] Added changelog entry (create a `$PR_NUMBER.md` file in `unreleased_changes` or use `./tools/create-changelog-entry.py`) - [ ] Added migration guide entry if applicable (edit the same file as for the changelog) - [ ] Updated [documentation](../blob/master/docs) if applicable - [ ] Adapted [wk-libs python client](https://github.com/scalableminds/webknossos-libs/tree/master/webknossos/webknossos/client) if relevant API parts change - [ ] Removed dev-only changes like prints and application.conf edits - [ ] Considered [common edge cases](../blob/master/.github/common_edge_cases.md) - [ ] Needs datastore update after deployment
1 parent b7a8d56 commit 531a578

File tree

9 files changed

+761
-898
lines changed

9 files changed

+761
-898
lines changed

frontend/javascripts/components/hover_icon_button.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { ButtonProps } from "antd";
2-
import * as React from "react";
3-
const { useState } = React;
2+
import type React from "react";
3+
import { cloneElement, useState } from "react";
44

55
export type HoverButtonProps = Omit<ButtonProps, "icon"> & {
66
icon: React.ReactElement<any>;
@@ -23,7 +23,7 @@ export function HoverIconButton(props: HoverButtonProps) {
2323
}
2424
};
2525
const { hoveredIcon, ...restProps } = props;
26-
return React.cloneElement(isMouseOver ? hoveredIcon : props.icon, {
26+
return cloneElement(isMouseOver ? hoveredIcon : props.icon, {
2727
...restProps,
2828
onMouseEnter,
2929
onMouseLeave,

frontend/javascripts/viewer/view/components/border_toggle_button.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Button } from "antd";
22
import FastTooltip from "components/fast_tooltip";
33
import { V2 } from "libs/mjs";
4-
import * as React from "react";
4+
import { useState } from "react";
55
import { connect } from "react-redux";
66
import type { BorderOpenStatus, WebknossosState } from "viewer/store";
77
type OwnProps = {
@@ -30,7 +30,7 @@ function BorderToggleButton(props: Props) {
3030
const imageClass = `center-item-using-flex icon-sidebar-toggle icon-sidebar-${iconKind}-${side}-${
3131
inFooter ? "dark" : "bright"
3232
}`;
33-
const [lastTouchPosition, setLastTouchPosition] = React.useState([0, 0]);
33+
const [lastTouchPosition, setLastTouchPosition] = useState([0, 0]);
3434
return (
3535
<FastTooltip title={tooltipTitle} placement={placement} style={TOOLTIP_STYLE}>
3636
<Button

frontend/javascripts/viewer/view/components/button_component.tsx

Lines changed: 35 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Button, type ButtonProps } from "antd";
22
import FastTooltip, { type FastTooltipPlacement } from "components/fast_tooltip";
33
import _ from "lodash";
4-
import * as React from "react";
4+
import type React from "react";
55

66
type ButtonComponentProps = ButtonProps & {
77
faIcon?: string;
@@ -12,53 +12,50 @@ type ButtonComponentProps = ButtonProps & {
1212
* after it was clicked.
1313
*/
1414

15-
class ButtonComponent extends React.PureComponent<ButtonComponentProps> {
16-
static defaultProps: ButtonComponentProps = {
17-
onClick: _.noop,
18-
};
15+
function ButtonComponent(props: ButtonComponentProps) {
16+
const {
17+
children,
18+
faIcon,
19+
title,
20+
tooltipPlacement,
21+
onClick = _.noop,
22+
onTouchEnd,
23+
...restProps
24+
} = props;
1925

20-
handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
21-
// For antd buttons e.target seems to be the span with the button description, whereas
22-
// e.currentTarget is the actual button
26+
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
2327
e.currentTarget.blur();
24-
25-
if (this.props.onClick) {
26-
this.props.onClick(e);
27-
}
28+
onClick(e);
2829
};
2930

30-
handleTouchEnd = (e: React.TouchEvent<HTMLButtonElement>) => {
31+
const handleTouchEnd = (e: React.TouchEvent<HTMLButtonElement>) => {
3132
e.currentTarget.blur();
32-
33-
if (this.props.onTouchEnd) {
34-
this.props.onTouchEnd(e);
33+
if (onTouchEnd) {
34+
onTouchEnd(e);
3535
}
3636
};
3737

38-
render() {
39-
const { children, faIcon, title, tooltipPlacement, ...restProps } = this.props;
40-
const iconEl = faIcon != null && !this.props.loading ? <i className={faIcon} /> : null;
41-
const button =
42-
// Differentiate via children != null, since antd uses a different styling for buttons
43-
// with a single icon child (.ant-btn-icon-only will be assigned)
44-
children != null ? (
45-
<Button {...restProps} onClick={this.handleClick} onTouchEnd={this.handleTouchEnd}>
46-
{iconEl}
47-
{children}
48-
</Button>
49-
) : (
50-
<Button {...restProps} onClick={this.handleClick} onTouchEnd={this.handleTouchEnd}>
51-
{iconEl}
52-
</Button>
53-
);
54-
return title != null ? (
55-
<FastTooltip title={title} placement={tooltipPlacement}>
56-
{button}
57-
</FastTooltip>
38+
const iconEl = faIcon != null && !props.loading ? <i className={faIcon} /> : null;
39+
const button =
40+
// Differentiate via children != null, since antd uses a different styling for buttons
41+
// with a single icon child (.ant-btn-icon-only will be assigned)
42+
children != null ? (
43+
<Button {...restProps} onClick={handleClick} onTouchEnd={handleTouchEnd}>
44+
{iconEl}
45+
{children}
46+
</Button>
5847
) : (
59-
button
48+
<Button {...restProps} onClick={handleClick} onTouchEnd={handleTouchEnd}>
49+
{iconEl}
50+
</Button>
6051
);
61-
}
52+
return title != null ? (
53+
<FastTooltip title={title} placement={tooltipPlacement}>
54+
{button}
55+
</FastTooltip>
56+
) : (
57+
button
58+
);
6259
}
6360

6461
export function ToggleButton(props: { active: boolean } & ButtonComponentProps) {
Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Checkbox } from "antd";
22
import _ from "lodash";
3-
import * as React from "react";
3+
import type React from "react";
44
type CheckboxComponentProp = {
55
onClick?: (...args: Array<any>) => any;
66
};
@@ -9,25 +9,17 @@ type CheckboxComponentProp = {
99
* after it was clicked.
1010
*/
1111

12-
class CheckboxComponent extends React.PureComponent<CheckboxComponentProp> {
13-
static defaultProps: CheckboxComponentProp = {
14-
onClick: _.noop,
15-
};
12+
function CheckboxComponent(props: CheckboxComponentProp) {
13+
const { onClick = _.noop, ...restProps } = props;
1614

17-
handleClick = (e: React.SyntheticEvent<HTMLInputElement>) => {
15+
const handleClick: React.MouseEventHandler<HTMLElement> = (e) => {
1816
// For antd checkboxs e.target seems to be the span with the checkbox description, whereas
1917
// e.currentTarget is the actual checkbox
2018
e.currentTarget.blur();
21-
22-
if (this.props.onClick) {
23-
this.props.onClick(e);
24-
}
19+
onClick(e);
2520
};
2621

27-
render() {
28-
// @ts-expect-error ts-migrate(2322) FIXME: Type '(e: React.SyntheticEvent<HTMLInputElement>) ... Remove this comment to see the full error message
29-
return <Checkbox {...this.props} onClick={this.handleClick} />;
30-
}
22+
return <Checkbox {...restProps} onClick={handleClick} />;
3123
}
3224

3325
export default CheckboxComponent;

frontend/javascripts/viewer/view/components/dom_visibility_observer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import * as React from "react";
1+
import React from "react";
22
// This component uses an IntersectionObserver to find out if the element with the id targetId
33
// is visible in the current viewport or not. It then calls its children render function with that value.
44
// This allows to not render performance-heavy components or to disable shortcuts if their flex layout tab is not visible.
Lines changed: 37 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,61 @@
11
import { Button, Input } from "antd";
2-
import React from "react";
2+
import type React from "react";
3+
import { useState } from "react";
34

45
type Props = {
56
icon: React.ReactElement;
67
label?: string;
78
onChange: (value: string, event: React.SyntheticEvent<HTMLInputElement>) => void;
89
};
910

10-
type State = {
11-
isEditing: boolean;
12-
value: string;
13-
};
14-
15-
class EditableTextIcon extends React.PureComponent<Props, State> {
16-
state = {
17-
isEditing: false,
18-
value: "",
19-
};
11+
function EditableTextIcon(props: Props) {
12+
const [isEditing, setIsEditing] = useState(false);
13+
const [value, setValue] = useState("");
2014

21-
handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
22-
this.setState({
23-
value: event.target.value,
24-
});
15+
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
16+
setValue(event.target.value);
2517
};
2618

27-
handleInputSubmit = (event: React.SyntheticEvent<HTMLInputElement>) => {
28-
const { value } = this.state;
29-
19+
const handleInputSubmit = (event: React.FormEvent<HTMLInputElement>) => {
3020
if (value !== "") {
31-
this.props.onChange(this.state.value, event);
21+
props.onChange(value, event);
3222
}
3323

34-
this.setState({
35-
isEditing: false,
36-
value: "",
37-
});
24+
setIsEditing(false);
25+
setValue("");
3826
};
3927

40-
render() {
41-
if (this.state.isEditing) {
42-
return (
43-
<Input
44-
value={this.state.value}
45-
onChange={this.handleInputChange}
46-
onPressEnter={this.handleInputSubmit}
47-
onBlur={this.handleInputSubmit}
48-
style={{
49-
width: 75,
50-
}}
51-
size="small"
52-
autoFocus
53-
/>
54-
);
55-
}
56-
28+
if (isEditing) {
5729
return (
58-
<Button
59-
size="small"
60-
icon={this.props.icon}
30+
<Input
31+
value={value}
32+
onChange={handleInputChange}
33+
onPressEnter={handleInputSubmit}
34+
onBlur={handleInputSubmit}
6135
style={{
62-
height: 22,
63-
width: this.props.label ? "initial" : 22,
64-
fontSize: "12px",
65-
color: "#7c7c7c",
36+
width: 75,
6637
}}
67-
onClick={() =>
68-
this.setState({
69-
isEditing: true,
70-
})
71-
}
72-
>
73-
{this.props.label ? <span style={{ marginLeft: 0 }}>{this.props.label}</span> : null}
74-
</Button>
38+
size="small"
39+
autoFocus
40+
/>
7541
);
7642
}
43+
44+
return (
45+
<Button
46+
size="small"
47+
icon={props.icon}
48+
style={{
49+
height: 22,
50+
width: props.label ? "initial" : 22,
51+
fontSize: "12px",
52+
color: "#7c7c7c",
53+
}}
54+
onClick={() => setIsEditing(true)}
55+
>
56+
{props.label ? <span style={{ marginLeft: 0 }}>{props.label}</span> : null}
57+
</Button>
58+
);
7759
}
7860

7961
export default EditableTextIcon;

0 commit comments

Comments
 (0)