Skip to content

Commit c9b7dfa

Browse files
committed
chore: minor perf improvements
1 parent bf37e83 commit c9b7dfa

7 files changed

Lines changed: 122 additions & 49 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.root {
2+
position: relative;
3+
height: 90px;
4+
}

packages/raga-web-app/src/components/audioPlayer/audioPlayer.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { useSelectedTrackFileURL } from "../../hooks";
66
import useSelectedTrackDef from "../../hooks/useSelectedTrackDef";
77
import { appStore } from "../../store/appStore";
88
import EmptyState from "../common/emptyState";
9+
import styles from "./audioPlayer.module.scss";
910
import { TrackBPMOverlay } from "./trackBPMOverlay";
1011

1112
// TODO: reconsider if this lazy-loading is worth it...
@@ -36,7 +37,7 @@ export function AudioPlayer() {
3637
}
3738

3839
return (
39-
<Box pos="relative" h={90}>
40+
<Box className={styles.root}>
4041
<Suspense fallback={<Skeleton width="100%" height="100%" />}>
4142
<AudioWaveform mediaURL={selectedFileURL} />
4243
</Suspense>

packages/raga-web-app/src/components/common/tree.tsx

Lines changed: 91 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ export interface ControlledTreeProps<T> {
5555
* Notable differences from older tree implementations (like Blueprint's Tree component):
5656
* - Node value string represent not just the id of the current node, but the full path (delimited by
5757
* slashes) of all the node ids from the root node to the current node.
58+
*
59+
* Performance notes:
60+
* - Event handlers are memoized to prevent re-renders on hover
61+
* - Component uses custom memo comparison to avoid re-renders when array contents haven't changed
62+
* - If still experiencing performance issues, check parent components for frequently changing props
5863
*/
5964
function ControlledTree<T extends object>({
6065
nodes,
@@ -115,8 +120,61 @@ function ControlledTree<T extends object>({
115120
}
116121
}, [checkedState, nodes, onSelect, selectionMode]);
117122

123+
// Memoized event handlers to prevent re-renders
124+
const handleCheckboxClick = useCallback(
125+
(nodeValue: string, checked: boolean) => {
126+
if (checked) {
127+
tree.uncheckNode(nodeValue);
128+
} else {
129+
tree.checkNode(nodeValue);
130+
}
131+
},
132+
[tree],
133+
);
134+
135+
const handleExpandClick = useCallback(
136+
(nodeValue: string, expanded: boolean) => {
137+
if (expanded) {
138+
tree.collapse(nodeValue);
139+
} else {
140+
tree.expand(nodeValue);
141+
}
142+
},
143+
[tree],
144+
);
145+
146+
const handleLabelClick = useCallback(
147+
(nodeValue: string, selected: boolean) => {
148+
if (selectionMode !== "single") {
149+
return;
150+
}
151+
152+
if (selected) {
153+
tree.deselect(nodeValue);
154+
} else {
155+
tree.select(nodeValue);
156+
const selectedNode = findNodeById(nodes, mantineNodeValueToId(nodeValue));
157+
if (selectedNode) {
158+
onSelect?.([selectedNode]);
159+
}
160+
}
161+
},
162+
[selectionMode, tree, nodes, onSelect],
163+
);
164+
165+
const handleSelectAllClick = useCallback(
166+
(allChecked: boolean) => {
167+
if (allChecked) {
168+
tree.uncheckAllNodes();
169+
} else {
170+
tree.checkAllNodes();
171+
}
172+
},
173+
[tree],
174+
);
175+
118176
const renderTreeNode = useCallback(
119-
({ node, expanded, selected, hasChildren, elementProps, tree }: RenderTreeNodePayload) => {
177+
({ node, expanded, selected, hasChildren, elementProps }: RenderTreeNodePayload) => {
120178
const checked = tree.isNodeChecked(node.value);
121179
const indeterminate = tree.isNodeIndeterminate(node.value);
122180

@@ -135,11 +193,7 @@ function ControlledTree<T extends object>({
135193
checked={checked}
136194
indeterminate={indeterminate}
137195
onClick={() => {
138-
if (checked) {
139-
tree.uncheckNode(node.value);
140-
} else {
141-
tree.checkNode(node.value);
142-
}
196+
handleCheckboxClick(node.value, checked);
143197
}}
144198
/>
145199
)}
@@ -151,11 +205,7 @@ function ControlledTree<T extends object>({
151205
color="gray"
152206
variant="subtle"
153207
onClick={() => {
154-
if (expanded) {
155-
tree.collapse(node.value);
156-
} else {
157-
tree.expand(node.value);
158-
}
208+
handleExpandClick(node.value, expanded);
159209
}}
160210
>
161211
{expanded ? <IoChevronDown /> : <IoChevronForward />}
@@ -165,27 +215,15 @@ function ControlledTree<T extends object>({
165215
<div
166216
className={styles.labelContainer}
167217
onClick={() => {
168-
if (selectionMode !== "single") {
169-
return;
170-
}
171-
172-
if (selected) {
173-
tree.deselect(node.value);
174-
} else {
175-
tree.select(node.value);
176-
const selectedNode = findNodeById(nodes, mantineNodeValueToId(node.value));
177-
if (selectedNode) {
178-
onSelect?.([selectedNode]);
179-
}
180-
}
218+
handleLabelClick(node.value, selected);
181219
}}
182220
>
183221
<span>{node.label}</span>
184222
</div>
185223
</div>
186224
);
187225
},
188-
[nodes, onSelect, selectionMode],
226+
[selectionMode, tree, handleCheckboxClick, handleExpandClick, handleLabelClick],
189227
);
190228

191229
// Update tree state controlled selection changes
@@ -209,11 +247,7 @@ function ControlledTree<T extends object>({
209247
checked={allNodesChecked}
210248
indeterminate={!allNodesChecked && someNodesChecked}
211249
onClick={() => {
212-
if (allNodesChecked) {
213-
tree.uncheckAllNodes();
214-
} else {
215-
tree.checkAllNodes();
216-
}
250+
handleSelectAllClick(allNodesChecked);
217251
}}
218252
/>
219253
<Text pl={5} fw={600}>
@@ -232,7 +266,33 @@ function ControlledTree<T extends object>({
232266
);
233267
}
234268

235-
export default memo(ControlledTree) as typeof ControlledTree;
269+
export default memo(ControlledTree, (prevProps, nextProps) => {
270+
// Custom comparison to prevent re-renders when array contents haven't changed
271+
if (prevProps.selectionMode !== nextProps.selectionMode) {
272+
return false;
273+
}
274+
275+
if (prevProps.nodes !== nextProps.nodes) {
276+
return false;
277+
}
278+
279+
if (prevProps.onSelect !== nextProps.onSelect) {
280+
return false;
281+
}
282+
283+
// Deep comparison for selectedNodeIds array
284+
if (prevProps.selectedNodeIds.length !== nextProps.selectedNodeIds.length) {
285+
return false;
286+
}
287+
288+
for (let i = 0; i < prevProps.selectedNodeIds.length; i++) {
289+
if (prevProps.selectedNodeIds[i] !== nextProps.selectedNodeIds[i]) {
290+
return false;
291+
}
292+
}
293+
294+
return true;
295+
}) as typeof ControlledTree;
236296

237297
// UTILITIES
238298
// -------------------------------------------------------------------------------------------------

packages/raga-web-app/src/views/export/exportView.module.scss

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,11 @@
22
border-bottom-left-radius: 8px;
33
border-bottom-right-radius: 8px;
44
}
5+
6+
.exportColumn {
7+
width: 100%;
8+
height: 100%;
9+
padding: 10px;
10+
min-width: 250px;
11+
max-width: 500px;
12+
}

packages/raga-web-app/src/views/export/index.tsx

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,7 @@ interface ExportColumnProps extends PaperProps {
4040

4141
function ExportColumn({ children, title, ...props }: ExportColumnProps) {
4242
return (
43-
<Paper
44-
shadow="sm"
45-
withBorder
46-
radius="sm"
47-
miw={250}
48-
maw={500}
49-
w="100%"
50-
h="100%"
51-
p={10}
52-
{...props}
53-
>
43+
<Paper shadow="sm" withBorder radius="sm" className={styles.exportColumn} {...props}>
5444
<Stack h="100%">
5545
{title && <Text>{title}</Text>}
5646
{children}

packages/raga-web-app/src/views/libraryView.module.scss

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
.librarySidebar {
2-
justify-content: space-between;
32
position: relative; // creates a stacking context for playlist table layout
43

54
.librarySidebarFooter {
@@ -10,7 +9,18 @@
109
}
1110
}
1211

12+
.sidebarFooterItem {
13+
padding: 5px 7px;
14+
}
15+
1316
.library {
17+
width: 100%;
18+
height: 100%;
1419
border-bottom-left-radius: 8px;
1520
border-bottom-right-radius: 8px;
1621
}
22+
23+
.libraryStack {
24+
width: 100%;
25+
height: 100%;
26+
}

packages/raga-web-app/src/views/libraryView.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ export default function LibraryView() {
3131
);
3232

3333
return (
34-
<Paper w="100%" h="100%" shadow="sm" withBorder={true} radius="sm" className={styles.library}>
35-
<Stack gap={0} w="100%" h="100%">
34+
<Paper shadow="sm" withBorder={true} radius="sm" className={styles.library}>
35+
<Stack gap={0} className={styles.libraryStack}>
3636
<Group justify="space-between" p={5}>
3737
{selectedTrack === undefined ? null : (
3838
<AudioPlayerNowPlaying selectedTrack={selectedTrack} />
@@ -77,15 +77,15 @@ function LibrarySidebarFooter() {
7777
return (
7878
<Box className={styles.librarySidebarFooter}>
7979
<Divider orientation="horizontal" />
80-
<Box py={5} px={7}>
80+
<Box className={styles.sidebarFooterItem}>
8181
<AudioFilesServerControls />
8282
</Box>
8383
<Divider orientation="horizontal" />
84-
<Box py={5} px={7}>
84+
<Box className={styles.sidebarFooterItem}>
8585
<AudioAnalyzerStatus />
8686
</Box>
8787
<Divider orientation="horizontal" />
88-
<Box py={5} px={7}>
88+
<Box className={styles.sidebarFooterItem}>
8989
<Group align="center" justify="space-between">
9090
<Text component="span" truncate={true} size="sm" c="dimmed">
9191
Total # tracks

0 commit comments

Comments
 (0)