Skip to content

Commit 477cc7c

Browse files
Migrate graph-related components from React classes to function components (#1634)
Modified components: FocusBar, FocusTab, GraphDropdown, GraphFallback, Infobox, and Sidebar
1 parent 77b9721 commit 477cc7c

File tree

7 files changed

+157
-188
lines changed

7 files changed

+157
-188
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
- Reverted previous CircleCI configuration change
4848
- Switched CircleCI ImageMagick download to use http
4949
- Modified CI config to take advantage of partial dependency caching and exploit parallelism when resolving/updating dependencies
50+
- Migrate graph-related components (FocusBar, FocusTab, GraphDropdown, GraphFallback, Infobox, and Sidebar) from classes to functions
5051

5152
## [0.7.1] - 2025-06-16
5253

js/components/graph/FocusBar.js

Lines changed: 22 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -16,59 +16,52 @@ const computerScienceFocusLabels = [
1616
]
1717

1818
/**
19-
* React component representing the focus menu bar
20-
*/
21-
export default class FocusBar extends React.Component {
22-
constructor(props) {
23-
super(props)
24-
this.state = {
25-
open: false,
26-
}
27-
}
19+
* React component representing the focus menu bar
20+
*/
21+
export default function FocusBar({focusBarEnabled, highlightFocus, currFocus}) {
22+
const [open, setOpen] = React.useState(false)
2823

2924
/**
3025
* Changes whether the focus bar is open or not
3126
*/
32-
toggleFocusBar = () => {
33-
this.setState({ open: !this.state.open })
27+
const toggleFocusBar = () => {
28+
setOpen(!open)
3429
}
3530

3631
/**
3732
* Creates the menu items of the focus bar using the FocusTab component
3833
* @returns an array of FocusTab components
3934
*/
40-
generateFocusTabs = () => {
35+
const generateFocusTabs = () => {
4136
return computerScienceFocusLabels.map(([focusId, focusTitle]) => {
42-
const selected = this.props.currFocus === focusId
37+
const selected = currFocus === focusId
4338

4439
return (
4540
<FocusTab
4641
key={focusId}
4742
pId={focusId}
4843
focusName={focusTitle}
4944
selected={selected}
50-
highlightFocus={this.props.highlightFocus}
45+
highlightFocus={highlightFocus}
5146
/>
5247
)
5348
})
5449
}
5550

56-
render() {
57-
if (!this.props.focusBarEnabled) {
58-
return null
59-
} else {
60-
return (
61-
<div className="focus-menu-bar">
62-
<button className="focus-menu-toggle" onClick={this.toggleFocusBar}>
63-
{this.state.open ? "⪡ Close" : "Focuses ⪢"}
64-
</button>
65-
<div className="focuses-list">
66-
{this.state.open && this.generateFocusTabs()}
67-
</div>
68-
</div>
69-
)
70-
}
51+
if (!focusBarEnabled) {
52+
return null
7153
}
54+
55+
return (
56+
<div className="focus-menu-bar">
57+
<button className="focus-menu-toggle" onClick={toggleFocusBar}>
58+
{open ? "⪡ Close" : "Focuses ⪢"}
59+
</button>
60+
<div className="focuses-list">
61+
{open && generateFocusTabs()}
62+
</div>
63+
</div>
64+
)
7265
}
7366

7467
FocusBar.propTypes = {

js/components/graph/FocusTab.js

Lines changed: 29 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,51 +5,42 @@ import { FocusModal } from "../common/react_modal.js.jsx"
55
/**
66
* React component representing an item on the focus menu bar
77
*/
8-
export default class FocusTab extends React.Component {
9-
constructor(props) {
10-
super(props)
11-
this.state = {
12-
showFocusModal: false,
13-
}
14-
}
8+
export default function FocusTab({focusName, highlightFocus, selected, pId}) {
9+
const [showFocusModal, setShowFocusModal] = React.useState(false)
1510

1611
/**
1712
* Change whether the modal popup describing this focus is shown
1813
* @param {bool} value
1914
*/
20-
toggleFocusModal = value => {
21-
this.setState({
22-
showFocusModal: value,
23-
})
24-
}
15+
const toggleFocusModal = (value => {
16+
setShowFocusModal(value)
17+
})
2518

26-
render() {
27-
return (
28-
<div className={this.props.selected ? "focus active-focus" : "focus"}>
29-
<button
30-
id={this.props.pId}
31-
onClick={() => this.props.highlightFocus(this.props.pId)}
32-
>
33-
{this.props.focusName}
34-
</button>
35-
<div className="focus-info">
36-
<FocusModal
37-
showFocusModal={this.state.showFocusModal}
38-
focusId={this.props.pId}
39-
onClose={() => this.toggleFocusModal(false)}
40-
/>
41-
{this.props.selected && (
42-
<button
43-
onClick={() => this.toggleFocusModal(true)}
44-
aria-label="Focus Description"
45-
>
46-
i
47-
</button>
48-
)}
49-
</div>
19+
return (
20+
<div className={selected ? "focus active-focus" : "focus"}>
21+
<button
22+
id={pId}
23+
onClick={() => highlightFocus(pId)}
24+
>
25+
{focusName}
26+
</button>
27+
<div className="focus-info">
28+
<FocusModal
29+
showFocusModal={showFocusModal}
30+
focusId={pId}
31+
onClose={() => toggleFocusModal(false)}
32+
/>
33+
{selected && (
34+
<button
35+
onClick={() => toggleFocusModal(true)}
36+
aria-label="Focus Description"
37+
>
38+
i
39+
</button>
40+
)}
5041
</div>
51-
)
52-
}
42+
</div>
43+
)
5344
}
5445

5546
FocusTab.propTypes = {

js/components/graph/GraphDropdown.js

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,37 @@
11
import React from "react"
22
import PropTypes from "prop-types"
33

4-
export default class GraphDropdown extends React.Component {
5-
render() {
4+
export default function GraphDropdown({showGraphDropdown, onMouseMove, onMouseLeave, graphs = [], updateGraph}) {
65
let className = "hidden"
76
let graphTabLeft = 0
8-
if (this.props.graphs.length !== 0 && document.querySelector("#nav-graph")) {
7+
if (graphs.length !== 0 && document.querySelector("#nav-graph")) {
98
const navGraph = document.querySelector("#nav-graph")
10-
if (this.props.graphs.length === 0) {
11-
navGraph.classList.remove("show-dropdown-arrow")
12-
} else {
13-
if (!navGraph.classList.contains("show-dropdown-arrow")) {
14-
navGraph.classList.add("show-dropdown-arrow")
15-
}
16-
if (this.props.showGraphDropdown) {
17-
graphTabLeft = navGraph.getBoundingClientRect().left
18-
className = "graph-dropdown-display"
19-
}
9+
if (graphs.length === 0) {
10+
navGraph.classList.remove("show-dropdown-arrow")
11+
} else {
12+
if (!navGraph.classList.contains("show-dropdown-arrow")) {
13+
navGraph.classList.add("show-dropdown-arrow")
14+
}
15+
if (showGraphDropdown) {
16+
graphTabLeft = navGraph.getBoundingClientRect().left
17+
className = "graph-dropdown-display"
18+
}
2019
}
2120
}
22-
2321
return (
2422
<ul
2523
className={className}
26-
onMouseMove={this.props.onMouseMove}
27-
onMouseLeave={this.props.onMouseLeave}
24+
onMouseMove={onMouseMove}
25+
onMouseLeave={onMouseLeave}
2826
data-testid={"test-graph-dropdown"}
2927
style={{ left: graphTabLeft }}
3028
>
31-
{this.props.graphs.map((graph, i) => {
29+
{graphs.map((graph, i) => {
3230
return (
3331
<li
3432
key={i}
3533
className="graph-dropdown-item"
36-
onClick={() => this.props.updateGraph(graph.title)}
34+
onClick={() => updateGraph(graph.title)}
3735
data-testid={"test-graph-" + i}
3836
>
3937
{graph.title}
@@ -43,7 +41,6 @@ export default class GraphDropdown extends React.Component {
4341
</ul>
4442
)
4543
}
46-
}
4744

4845
GraphDropdown.defaultProps = {
4946
graphs: [],

js/components/graph/GraphFallback.js

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,25 @@
1-
import React from "react"
21
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
32
import { faTriangleExclamation } from "@fortawesome/free-solid-svg-icons"
43
import PropTypes from "prop-types"
54

6-
export default class GraphFallback extends React.Component {
7-
render() {
8-
const { error } = this.props
5+
export default function GraphFallback(props) {
6+
const { error } = props
97

10-
return (
11-
<div className="error-boundary-container">
12-
<div className="error-boundary-box">
13-
<FontAwesomeIcon icon={faTriangleExclamation} className="error-svg" />
14-
<div className="error-boundary-text">
15-
Your graph has failed to render. Please reload this page or report this
16-
issue to David Liu at {""}
17-
<a className="graph-fallback-email" href="mailto:[email protected]">
18-
19-
</a>
20-
<p className="graph-fallback-error">Details: {error.message}</p>
21-
</div>
8+
return (
9+
<div className="error-boundary-container">
10+
<div className="error-boundary-box">
11+
<FontAwesomeIcon icon={faTriangleExclamation} className="error-svg" />
12+
<div className="error-boundary-text">
13+
Your graph has failed to render. Please reload this page or report this
14+
issue to David Liu at {""}
15+
<a className="graph-fallback-email" href="mailto:[email protected]">
16+
17+
</a>
18+
<p className="graph-fallback-error">Details: {error.message}</p>
2219
</div>
2320
</div>
24-
)
25-
}
21+
</div>
22+
)
2623
}
2724

2825
GraphFallback.propTypes = {

js/components/graph/InfoBox.js

Lines changed: 37 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,46 @@
1-
import React from "react"
21
import PropTypes from "prop-types"
32

4-
export default class InfoBox extends React.Component {
5-
render() {
6-
// guard against rendering with no course
7-
if (!this.props.nodeId) {
8-
return null
9-
}
10-
11-
const className = this.props.showInfoBox
12-
? "tooltip-group-display"
13-
: "tooltip-group-hidden"
3+
export default function InfoBox({showInfoBox, nodeId, xPos, yPos, onClick, onMouseEnter, onMouseLeave}) {
4+
// guard against rendering with no course
5+
if (!nodeId) {
6+
return null
7+
}
148

15-
const rectAttrs = {
16-
id: this.props.nodeId + "-tooltip" + "-rect",
17-
x: this.props.xPos,
18-
y: this.props.yPos,
19-
rx: "4",
20-
ry: "4",
21-
fill: "white",
22-
stroke: "black",
23-
strokeWidth: "2",
24-
width: "60",
25-
height: "30",
26-
}
9+
const className = showInfoBox
10+
? "tooltip-group-display"
11+
: "tooltip-group-hidden"
2712

28-
const textAttrs = {
29-
id: this.props.nodeId + "-tooltip" + "-text",
30-
x: this.props.xPos + 60 / 2 - 18,
31-
y: this.props.yPos + 30 / 2 + 6,
32-
}
13+
const rectAttrs = {
14+
id: nodeId + "-tooltip" + "-rect",
15+
x: xPos,
16+
y: yPos,
17+
rx: "4",
18+
ry: "4",
19+
fill: "white",
20+
stroke: "black",
21+
strokeWidth: "2",
22+
width: "60",
23+
height: "30",
24+
}
3325

34-
return (
35-
<g
36-
id="infoBox"
37-
className={className}
38-
onClick={this.props.onClick}
39-
onMouseEnter={this.props.onMouseEnter}
40-
onMouseLeave={this.props.onMouseLeave}
41-
>
42-
<rect {...rectAttrs} />
43-
<text {...textAttrs}>Info</text>
44-
</g>
45-
)
26+
const textAttrs = {
27+
id: nodeId + "-tooltip" + "-text",
28+
x: xPos + 60 / 2 - 18,
29+
y: yPos + 30 / 2 + 6,
4630
}
31+
32+
return (
33+
<g
34+
id="infoBox"
35+
className={className}
36+
onClick={onClick}
37+
onMouseEnter={onMouseEnter}
38+
onMouseLeave={onMouseLeave}
39+
>
40+
<rect {...rectAttrs} />
41+
<text {...textAttrs}>Info</text>
42+
</g>
43+
)
4744
}
4845

4946
InfoBox.propTypes = {

0 commit comments

Comments
 (0)