Skip to content

Commit 64657ef

Browse files
Copilotmikebarkmin
andcommitted
Optimize performance by moving store hooks into child components
Co-authored-by: mikebarkmin <2592379+mikebarkmin@users.noreply.github.com>
1 parent f210a53 commit 64657ef

8 files changed

Lines changed: 78 additions & 90 deletions

File tree

packages/learningmap/src/EdgeDrawer.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,27 @@ import { X, Trash2, Save } from "lucide-react";
33
import { Edge } from "@xyflow/react";
44
import { EditorDrawerEdgeContent } from "./EditorDrawerEdgeContent";
55
import { getTranslations } from "./translations";
6+
import { useEditorStore } from "./editorStore";
67

78
interface EdgeDrawerProps {
8-
edge: Edge | null;
9-
isOpen: boolean;
109
onClose: () => void;
1110
onUpdate: (edge: Edge) => void;
1211
onDelete: () => void;
1312
language?: string;
1413
}
1514

1615
export const EdgeDrawer: React.FC<EdgeDrawerProps> = ({
17-
edge: selectedEdge,
18-
isOpen: edgeDrawerOpen,
1916
onClose: closeDrawer,
2017
onUpdate: updateEdge,
2118
onDelete: deleteEdge,
2219
language = "en",
2320
}) => {
2421
const t = getTranslations(language);
2522

23+
// Get edge and drawer state from store
24+
const selectedEdge = useEditorStore(state => state.selectedEdge);
25+
const edgeDrawerOpen = useEditorStore(state => state.edgeDrawerOpen);
26+
2627
if (!selectedEdge || !edgeDrawerOpen) return null;
2728
return (
2829
<div>

packages/learningmap/src/EditorDrawer.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import React, { useState, useEffect } from "react";
22
import { X, Trash2, Save } from "lucide-react";
3-
import { Node, useReactFlow } from "@xyflow/react";
3+
import { Node } from "@xyflow/react";
44
import { EditorDrawerTaskContent } from "./EditorDrawerTaskContent";
55
import { EditorDrawerTopicContent } from "./EditorDrawerTopicContent";
66
import { EditorDrawerImageContent } from "./EditorDrawerImageContent";
77
import { EditorDrawerTextContent } from "./EditorDrawerTextContent";
88
import { Completion, NodeData } from "./types";
99
import { getTranslations } from "./translations";
10+
import { useEditorStore } from "./editorStore";
1011

1112
interface EditorDrawerProps {
12-
node: Node<NodeData> | null;
1313
isOpen: boolean;
1414
onClose: () => void;
1515
onUpdate: (node: Node<NodeData>) => void;
@@ -18,17 +18,20 @@ interface EditorDrawerProps {
1818
}
1919

2020
export const EditorDrawer: React.FC<EditorDrawerProps> = ({
21-
node,
2221
isOpen,
2322
onClose,
2423
onUpdate,
2524
onDelete,
2625
language = "en",
2726
}) => {
2827
const t = getTranslations(language);
28+
29+
// Get node and all nodes from store
30+
const selectedNodeId = useEditorStore(state => state.selectedNodeId);
31+
const nodes = useEditorStore(state => state.nodes);
32+
const node = nodes.find(n => n.id === selectedNodeId) || null;
33+
2934
const [localNode, setLocalNode] = useState<Node<NodeData> | null>(node);
30-
const { getNodes } = useReactFlow();
31-
const allNodes = getNodes();
3235

3336
useEffect(() => {
3437
setLocalNode(node);
@@ -37,7 +40,7 @@ export const EditorDrawer: React.FC<EditorDrawerProps> = ({
3740
if (!isOpen || !node || !localNode) return null;
3841

3942
// Filter out the current node from selectable options
40-
const nodeOptions = allNodes.filter(n => n.id !== node.id && n.type === "task" || n.type === "topic");
43+
const nodeOptions = nodes.filter(n => n.id !== node.id && (n.type === "task" || n.type === "topic"));
4144

4245
// Helper for dropdowns
4346
const renderNodeSelect = (value: string, onChange: (id: string) => void) => (

packages/learningmap/src/EditorToolbar.tsx

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,9 @@ import "@szhsin/react-menu/dist/index.css";
44
import '@szhsin/react-menu/dist/transitions/zoom.css';
55
import { Plus, Bug, Settings, Eye, Menu as MenuI, FolderOpen, Download, ImageDown, ExternalLink, Share2, RotateCcw } from "lucide-react";
66
import { getTranslations } from "./translations";
7+
import { useEditorStore } from "./editorStore";
78

89
interface EditorToolbarProps {
9-
debugMode: boolean;
10-
previewMode: boolean;
11-
showCompletionNeeds: boolean;
12-
showCompletionOptional: boolean;
13-
showUnlockAfter: boolean;
14-
onToggleDebugMode: () => void;
15-
onTogglePreviewMode: () => void;
16-
onSetShowCompletionNeeds: (checked: boolean) => void;
17-
onSetShowCompletionOptional: (checked: boolean) => void;
18-
onSetShowUnlockAfter: (checked: boolean) => void;
1910
onAddNewNode: (type: "task" | "topic" | "image" | "text") => void;
2011
onOpenSettingsDrawer: () => void;
2112
onDownlad: () => void;
@@ -26,16 +17,6 @@ interface EditorToolbarProps {
2617
}
2718

2819
export const EditorToolbar: React.FC<EditorToolbarProps> = ({
29-
debugMode,
30-
previewMode,
31-
showCompletionNeeds,
32-
showCompletionOptional,
33-
showUnlockAfter,
34-
onTogglePreviewMode,
35-
onToggleDebugMode,
36-
onSetShowCompletionNeeds,
37-
onSetShowCompletionOptional,
38-
onSetShowUnlockAfter,
3920
onAddNewNode,
4021
onOpenSettingsDrawer,
4122
onDownlad,
@@ -45,6 +26,26 @@ export const EditorToolbar: React.FC<EditorToolbarProps> = ({
4526
language = "en",
4627
}) => {
4728
const t = getTranslations(language);
29+
30+
// Get state directly from store
31+
const debugMode = useEditorStore(state => state.debugMode);
32+
const previewMode = useEditorStore(state => state.previewMode);
33+
const showCompletionNeeds = useEditorStore(state => state.showCompletionNeeds);
34+
const showCompletionOptional = useEditorStore(state => state.showCompletionOptional);
35+
const showUnlockAfter = useEditorStore(state => state.showUnlockAfter);
36+
37+
// Get actions directly from store
38+
const setDebugMode = useEditorStore(state => state.setDebugMode);
39+
const setPreviewMode = useEditorStore(state => state.setPreviewMode);
40+
const setShowCompletionNeeds = useEditorStore(state => state.setShowCompletionNeeds);
41+
const setShowCompletionOptional = useEditorStore(state => state.setShowCompletionOptional);
42+
const setShowUnlockAfter = useEditorStore(state => state.setShowUnlockAfter);
43+
44+
const onToggleDebugMode = () => setDebugMode(!debugMode);
45+
const onTogglePreviewMode = () => setPreviewMode(!previewMode);
46+
const onSetShowCompletionNeeds = (checked: boolean) => setShowCompletionNeeds(checked);
47+
const onSetShowCompletionOptional = (checked: boolean) => setShowCompletionOptional(checked);
48+
const onSetShowUnlockAfter = (checked: boolean) => setShowUnlockAfter(checked);
4849

4950
return (
5051
<div className="editor-toolbar">

packages/learningmap/src/LearningMapEditor.tsx

Lines changed: 19 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export function LearningMapEditor({
6969
const parsedRoadmap = parseRoadmapData(roadmapData || "");
7070
const { screenToFlowPosition, zoomIn, zoomOut, setCenter, fitView } = useReactFlow();
7171

72-
// Use Zustand store
72+
// Only get minimal state needed in this component
7373
const nodes = useEditorStore(state => state.nodes);
7474
const edges = useEditorStore(state => state.edges);
7575
const settings = useEditorStore(state => state.settings);
@@ -78,48 +78,39 @@ export function LearningMapEditor({
7878
const debugMode = useEditorStore(state => state.debugMode);
7979
const showGrid = useEditorStore(state => state.showGrid);
8080
const helpOpen = useEditorStore(state => state.helpOpen);
81-
const drawerOpen = useEditorStore(state => state.drawerOpen);
82-
const settingsDrawerOpen = useEditorStore(state => state.settingsDrawerOpen);
83-
const edgeDrawerOpen = useEditorStore(state => state.edgeDrawerOpen);
84-
const shareDialogOpen = useEditorStore(state => state.shareDialogOpen);
85-
const loadExternalDialogOpen = useEditorStore(state => state.loadExternalDialogOpen);
8681
const selectedNodeId = useEditorStore(state => state.selectedNodeId);
8782
const selectedNodeIds = useEditorStore(state => state.selectedNodeIds);
8883
const selectedEdge = useEditorStore(state => state.selectedEdge);
89-
const nextNodeId = useEditorStore(state => state.nextNodeId);
90-
const clipboard = useEditorStore(state => state.clipboard);
91-
const lastMousePosition = useEditorStore(state => state.lastMousePosition);
92-
const shareLink = useEditorStore(state => state.shareLink);
93-
const pendingExternalId = useEditorStore(state => state.pendingExternalId);
9484
const showCompletionNeeds = useEditorStore(state => state.showCompletionNeeds);
9585
const showCompletionOptional = useEditorStore(state => state.showCompletionOptional);
9686
const showUnlockAfter = useEditorStore(state => state.showUnlockAfter);
87+
const lastMousePosition = useEditorStore(state => state.lastMousePosition);
88+
const nextNodeId = useEditorStore(state => state.nextNodeId);
89+
const clipboard = useEditorStore(state => state.clipboard);
90+
const pendingExternalId = useEditorStore(state => state.pendingExternalId);
9791

9892
// Store actions
9993
const onNodesChange = useEditorStore(state => state.onNodesChange);
10094
const onEdgesChange = useEditorStore(state => state.onEdgesChange);
10195
const onConnect = useEditorStore(state => state.onConnect);
10296
const setSaved = useEditorStore(state => state.setSaved);
103-
const setPreviewMode = useEditorStore(state => state.setPreviewMode);
104-
const setDebugMode = useEditorStore(state => state.setDebugMode);
105-
const setShowGrid = useEditorStore(state => state.setShowGrid);
10697
const setHelpOpen = useEditorStore(state => state.setHelpOpen);
98+
const setShowGrid = useEditorStore(state => state.setShowGrid);
99+
const setSelectedNodeId = useEditorStore(state => state.setSelectedNodeId);
100+
const setSelectedNodeIds = useEditorStore(state => state.setSelectedNodeIds);
107101
const setDrawerOpen = useEditorStore(state => state.setDrawerOpen);
108102
const setSettingsDrawerOpen = useEditorStore(state => state.setSettingsDrawerOpen);
109103
const setEdgeDrawerOpen = useEditorStore(state => state.setEdgeDrawerOpen);
110-
const setShareDialogOpen = useEditorStore(state => state.setShareDialogOpen);
111-
const setLoadExternalDialogOpen = useEditorStore(state => state.setLoadExternalDialogOpen);
112-
const setSelectedNodeId = useEditorStore(state => state.setSelectedNodeId);
113-
const setSelectedNodeIds = useEditorStore(state => state.setSelectedNodeIds);
114104
const setSelectedEdge = useEditorStore(state => state.setSelectedEdge);
115105
const setNextNodeId = useEditorStore(state => state.setNextNodeId);
116106
const setClipboard = useEditorStore(state => state.setClipboard);
117107
const setLastMousePosition = useEditorStore(state => state.setLastMousePosition);
118-
const setShareLink = useEditorStore(state => state.setShareLink);
108+
const setShareDialogOpen = useEditorStore(state => state.setShareDialogOpen);
109+
const setLoadExternalDialogOpen = useEditorStore(state => state.setLoadExternalDialogOpen);
119110
const setPendingExternalId = useEditorStore(state => state.setPendingExternalId);
120-
const setShowCompletionNeeds = useEditorStore(state => state.setShowCompletionNeeds);
121-
const setShowCompletionOptional = useEditorStore(state => state.setShowCompletionOptional);
122-
const setShowUnlockAfter = useEditorStore(state => state.setShowUnlockAfter);
111+
const setShareLink = useEditorStore(state => state.setShareLink);
112+
const setDebugMode = useEditorStore(state => state.setDebugMode);
113+
const setPreviewMode = useEditorStore(state => state.setPreviewMode);
123114
const updateNode = useEditorStore(state => state.updateNode);
124115
const updateNodes = useEditorStore(state => state.updateNodes);
125116
const updateEdge = useEditorStore(state => state.updateEdge);
@@ -235,10 +226,6 @@ export function LearningMapEditor({
235226
setEdgeDrawerOpen(true);
236227
}, [setSelectedEdge, setEdgeDrawerOpen]);
237228

238-
const toggleDebugMode = useCallback(() => {
239-
setDebugMode(!debugMode);
240-
}, [debugMode, setDebugMode]);
241-
242229
const closeDrawer = useCallback(() => {
243230
closeAllDrawers();
244231
}, [closeAllDrawers]);
@@ -376,6 +363,10 @@ export function LearningMapEditor({
376363
}
377364
}, [previewMode, setPreviewMode, setDebugMode, handleSave, closeDrawer]);
378365

366+
const toggleDebugMode = useCallback(() => {
367+
setDebugMode(!debugMode);
368+
}, [debugMode, setDebugMode]);
369+
379370
const handleDownload = useCallback(() => {
380371
const roadmapData = getRoadmapData();
381372
const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(roadmapData, null, 2));
@@ -488,9 +479,6 @@ export function LearningMapEditor({
488479

489480
// Toolbar handler wrappers for EditorToolbar props
490481
const handleOpenSettingsDrawer = useCallback(() => setSettingsDrawerOpen(true), [setSettingsDrawerOpen]);
491-
const handleSetShowCompletionNeeds = useCallback((checked: boolean) => setShowCompletionNeeds(checked), [setShowCompletionNeeds]);
492-
const handleSetShowCompletionOptional = useCallback((checked: boolean) => setShowCompletionOptional(checked), [setShowCompletionOptional]);
493-
const handleSetShowUnlockAfter = useCallback((checked: boolean) => setShowUnlockAfter(checked), [setShowUnlockAfter]);
494482

495483
const handleNodesChange: OnNodesChange = useCallback(
496484
(changes) => {
@@ -761,16 +749,6 @@ export function LearningMapEditor({
761749
return (
762750
<>
763751
<EditorToolbar
764-
debugMode={debugMode}
765-
previewMode={previewMode}
766-
showCompletionNeeds={showCompletionNeeds}
767-
showCompletionOptional={showCompletionOptional}
768-
showUnlockAfter={showUnlockAfter}
769-
onToggleDebugMode={toggleDebugMode}
770-
onTogglePreviewMode={togglePreviewMode}
771-
onSetShowCompletionNeeds={handleSetShowCompletionNeeds}
772-
onSetShowCompletionOptional={handleSetShowCompletionOptional}
773-
onSetShowUnlockAfter={handleSetShowUnlockAfter}
774752
onAddNewNode={addNewNode}
775753
onOpenSettingsDrawer={handleOpenSettingsDrawer}
776754
onDownlad={handleDownload}
@@ -831,29 +809,24 @@ export function LearningMapEditor({
831809
{!saved && <Panel position="bottom-right" title={t.unsavedChanges} onClick={() => { handleSave(); }}>
832810
<ShieldAlert size={32} color="var(--learningmap-color-coal)" />
833811
</Panel>}
834-
{selectedNodeIds.length > 1 && <MultiNodePanel nodes={nodes.filter(n => selectedNodeIds.includes(n.id))} onUpdate={handleUpdateNodes} />}
812+
{selectedNodeIds.length > 1 && <MultiNodePanel onUpdate={handleUpdateNodes} />}
835813
</ReactFlow>
836814
</div>
837815
<EditorDrawer
838-
node={nodes.find(n => n.id === selectedNodeId)}
839-
isOpen={drawerOpen}
816+
isOpen={true}
840817
onClose={closeDrawer}
841818
onUpdate={handleUpdateNode}
842819
onDelete={handleDeleteNode}
843820
language={effectiveLanguage}
844821
/>
845822
<EdgeDrawer
846-
edge={selectedEdge}
847-
isOpen={edgeDrawerOpen}
848823
onClose={closeDrawer}
849824
onUpdate={handleUpdateEdge}
850825
onDelete={handleDeleteEdge}
851826
language={effectiveLanguage}
852827
/>
853828
<SettingsDrawer
854-
isOpen={settingsDrawerOpen}
855829
onClose={closeDrawer}
856-
settings={settings}
857830
onUpdate={setSettings}
858831
language={effectiveLanguage}
859832
/>
@@ -891,13 +864,10 @@ export function LearningMapEditor({
891864
</div>
892865
</dialog>
893866
<ShareDialog
894-
open={shareDialogOpen}
895867
onClose={() => setShareDialogOpen(false)}
896-
shareLink={shareLink}
897868
language={effectiveLanguage}
898869
/>
899870
<LoadExternalDialog
900-
open={loadExternalDialogOpen}
901871
onClose={() => {
902872
setLoadExternalDialogOpen(false);
903873
setPendingExternalId(null);

packages/learningmap/src/LoadExternalDialog.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,25 @@
11
import React from "react";
22
import { X, Download, AlertTriangle } from "lucide-react";
33
import { getTranslations } from "./translations";
4+
import { useEditorStore } from "./editorStore";
45

56
interface LoadExternalDialogProps {
6-
open: boolean;
77
onClose: () => void;
88
onDownloadCurrent: () => void;
99
onReplace: () => void;
1010
language?: string;
1111
}
1212

1313
export function LoadExternalDialog({
14-
open,
1514
onClose,
1615
onDownloadCurrent,
1716
onReplace,
1817
language = "en",
1918
}: LoadExternalDialogProps) {
2019
const t = getTranslations(language);
20+
21+
// Get state from store
22+
const open = useEditorStore(state => state.loadExternalDialogOpen);
2123

2224
if (!open) return null;
2325

packages/learningmap/src/MultiNodePanel.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,19 @@ import { Node, Panel } from "@xyflow/react";
22
import { NodeData } from "./types";
33
import { FC } from "react";
44
import { AlignCenterVertical, AlignCenterHorizontal, AlignEndHorizontal, AlignEndVertical, AlignStartVertical, AlignStartHorizontal, RulerDimensionLine, AlignVerticalDistributeCenter, AlignHorizontalDistributeCenter } from "lucide-react";
5+
import { useEditorStore } from "./editorStore";
56

67
interface Props {
7-
nodes: Node<NodeData>[];
88
onUpdate: (nodes: Node<NodeData>[]) => void;
99
}
1010

11-
export const MultiNodePanel: FC<Props> = ({ nodes, onUpdate }) => {
11+
export const MultiNodePanel: FC<Props> = ({ onUpdate }) => {
12+
// Get selected nodes from store
13+
const selectedNodeIds = useEditorStore(state => state.selectedNodeIds);
14+
const allNodes = useEditorStore(state => state.nodes);
15+
const nodes = allNodes.filter(n => selectedNodeIds.includes(n.id));
16+
17+
if (nodes.length < 2) return null;
1218

1319
const alignLeftVertical = () => {
1420
if (nodes.length < 2) return;

packages/learningmap/src/SettingsDrawer.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,25 @@ import { Settings } from "./types";
44
import { ColorSelector } from "./ColorSelector";
55
import { getTranslations } from "./translations";
66
import { useReactFlow } from "@xyflow/react";
7+
import { useEditorStore } from "./editorStore";
78

89
interface SettingsDrawerProps {
9-
isOpen: boolean;
1010
onClose: () => void;
11-
settings: Settings;
1211
onUpdate: (s: Settings) => void;
1312
language?: string;
1413
}
1514

1615
export const SettingsDrawer: React.FC<SettingsDrawerProps> = ({
17-
isOpen,
1816
onClose,
19-
settings,
2017
onUpdate,
2118
language = "en",
2219
}) => {
2320
const t = getTranslations(language);
21+
22+
// Get state from store
23+
const isOpen = useEditorStore(state => state.settingsDrawerOpen);
24+
const settings = useEditorStore(state => state.settings);
25+
2426
const [localSettings, setLocalSettings] = useState<Settings>(settings);
2527
const { getViewport } = useReactFlow();
2628

0 commit comments

Comments
 (0)