Skip to content

Commit 3143cee

Browse files
authored
Merge pull request #37 from openpatch/copilot/replace-editor-drawer-with-panel
Replace editor drawer with React Flow panel with single-click interaction
2 parents f540109 + ed6f01a commit 3143cee

13 files changed

Lines changed: 4847 additions & 3397 deletions
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import React, { useEffect } from "react";
2+
import { X, Trash2 } from "lucide-react";
3+
import { Edge, Panel } from "@xyflow/react";
4+
import { EditorDrawerEdgeContent } from "./EditorDrawerEdgeContent";
5+
import { getTranslations } from "./translations";
6+
import { useEditorStore } from "./editorStore";
7+
8+
interface EdgePanelProps {
9+
defaultLanguage?: string;
10+
}
11+
12+
export const EdgePanel: React.FC<EdgePanelProps> = ({
13+
defaultLanguage = "en",
14+
}) => {
15+
// Get edge and drawer state from store
16+
const selectedEdge = useEditorStore(state => state.selectedEdge);
17+
const edgeDrawerOpen = useEditorStore(state => state.edgeDrawerOpen);
18+
const settings = useEditorStore(state => state.settings);
19+
const edges = useEditorStore(state => state.edges);
20+
21+
// Get actions from store
22+
const setEdgeDrawerOpen = useEditorStore(state => state.setEdgeDrawerOpen);
23+
const setSelectedEdge = useEditorStore(state => state.setSelectedEdge);
24+
const updateEdge = useEditorStore(state => state.updateEdge);
25+
const deleteEdge = useEditorStore(state => state.deleteEdge);
26+
27+
const language = settings?.language || defaultLanguage;
28+
const t = getTranslations(language);
29+
30+
// Close panel when edge gets deselected
31+
useEffect(() => {
32+
if (edgeDrawerOpen && !selectedEdge) {
33+
setEdgeDrawerOpen(false);
34+
}
35+
}, [selectedEdge, edgeDrawerOpen, setEdgeDrawerOpen]);
36+
37+
const closePanel = () => {
38+
setEdgeDrawerOpen(false);
39+
setSelectedEdge(null);
40+
};
41+
42+
const onUpdate = (edge: Edge) => {
43+
updateEdge(edge.id, edge);
44+
};
45+
46+
const onDelete = () => {
47+
if (selectedEdge) {
48+
deleteEdge(selectedEdge.id);
49+
closePanel();
50+
}
51+
};
52+
53+
if (!selectedEdge || !edgeDrawerOpen) return null;
54+
55+
return (
56+
<Panel position="center-right" className="editor-panel">
57+
<div className="panel-inner">
58+
<div className="panel-header">
59+
<h2 className="panel-title">{t.editEdge}</h2>
60+
<button onClick={closePanel} className="close-button">
61+
<X size={20} />
62+
</button>
63+
</div>
64+
<EditorDrawerEdgeContent
65+
localEdge={selectedEdge}
66+
handleFieldChange={(field: string, value: any) => {
67+
let updated = { ...selectedEdge };
68+
if (field === "color") {
69+
updated = {
70+
...updated,
71+
style: { ...updated.style, stroke: value },
72+
};
73+
} else if (field === "animated") {
74+
updated = { ...updated, animated: value };
75+
} else if (field === "type") {
76+
updated = { ...updated, type: value };
77+
}
78+
onUpdate(updated);
79+
}}
80+
language={language}
81+
/>
82+
<div className="panel-footer panel-footer-centered">
83+
<button onClick={onDelete} className="danger-button">
84+
<Trash2 size={16} /> {t.deleteEdge}
85+
</button>
86+
</div>
87+
</div>
88+
</Panel>
89+
);
90+
};

packages/learningmap/src/EditorCanvas.tsx

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import { ImageNode } from "./nodes/ImageNode";
88
import { TextNode } from "./nodes/TextNode";
99
import FloatingEdge from "./FloatingEdge";
1010
import { MultiNodePanel } from "./MultiNodePanel";
11+
import { EditorPanel } from "./EditorPanel";
12+
import { EdgePanel } from "./EdgePanel";
13+
import { SettingsPanel } from "./SettingsPanel";
1114
import { getTranslations } from "./translations";
1215
import { NodeData } from "./types";
1316

@@ -43,6 +46,7 @@ export const EditorCanvas = memo(({ defaultLanguage = "en" }: EditorCanvasProps)
4346
const setSelectedEdge = useEditorStore(state => state.setSelectedEdge);
4447
const setDrawerOpen = useEditorStore(state => state.setDrawerOpen);
4548
const setEdgeDrawerOpen = useEditorStore(state => state.setEdgeDrawerOpen);
49+
const setSettingsDrawerOpen = useEditorStore(state => state.setSettingsDrawerOpen);
4650
const setHelpOpen = useEditorStore(state => state.setHelpOpen);
4751
const setLastMousePosition = useEditorStore(state => state.setLastMousePosition);
4852

@@ -76,21 +80,29 @@ export const EditorCanvas = memo(({ defaultLanguage = "en" }: EditorCanvasProps)
7680
setDrawerOpen(true);
7781
setSelectedEdge(null);
7882
setEdgeDrawerOpen(false);
79-
}, [setSelectedNodeId, setDrawerOpen, setSelectedEdge, setEdgeDrawerOpen]);
83+
setSettingsDrawerOpen(false);
84+
}, [setSelectedNodeId, setDrawerOpen, setSelectedEdge, setEdgeDrawerOpen, setSettingsDrawerOpen]);
8085

8186
const handleEdgeClick = useCallback((_: any, edge: Edge) => {
8287
setSelectedEdge(edge);
8388
setEdgeDrawerOpen(true);
8489
setSelectedNodeId(null);
8590
setDrawerOpen(false);
86-
}, [setSelectedEdge, setEdgeDrawerOpen, setSelectedNodeId, setDrawerOpen]);
91+
setSettingsDrawerOpen(false);
92+
}, [setSelectedEdge, setEdgeDrawerOpen, setSelectedNodeId, setDrawerOpen, setSettingsDrawerOpen]);
8793

8894
const handleSelectionChange: OnSelectionChangeFunc = useCallback(
8995
({ nodes: selectedNodes }) => {
9096
// Only select nodes, not edges (as per requirement #6)
9197
setSelectedNodeIds(selectedNodes.map(n => n.id));
98+
99+
// Close the node panel if no nodes are selected and it's currently open
100+
if (selectedNodes.length === 0) {
101+
setDrawerOpen(false);
102+
setSelectedNodeId(null);
103+
}
92104
},
93-
[setSelectedNodeIds]
105+
[setSelectedNodeIds, setDrawerOpen, setSelectedNodeId]
94106
);
95107

96108
// Track mouse position for keyboard shortcuts
@@ -99,6 +111,15 @@ export const EditorCanvas = memo(({ defaultLanguage = "en" }: EditorCanvasProps)
99111
setLastMousePosition(position);
100112
}, [screenToFlowPosition, setLastMousePosition]);
101113

114+
// Close panels when clicking on empty canvas
115+
const handlePaneClick = useCallback(() => {
116+
setDrawerOpen(false);
117+
setSelectedNodeId(null);
118+
setEdgeDrawerOpen(false);
119+
setSelectedEdge(null);
120+
setSettingsDrawerOpen(false);
121+
}, [setDrawerOpen, setSelectedNodeId, setEdgeDrawerOpen, setSelectedEdge, setSettingsDrawerOpen]);
122+
102123
const defaultEdgeOptions = {
103124
animated: false,
104125
style: {
@@ -121,11 +142,12 @@ export const EditorCanvas = memo(({ defaultLanguage = "en" }: EditorCanvasProps)
121142
nodes={nodes}
122143
edges={edges}
123144
onEdgesChange={onEdgesChange}
124-
onNodeDoubleClick={handleNodeClick}
125-
onEdgeDoubleClick={handleEdgeClick}
145+
onNodeClick={handleNodeClick}
146+
onEdgeClick={handleEdgeClick}
126147
onNodesChange={onNodesChange}
127148
onConnect={onConnect}
128149
onSelectionChange={handleSelectionChange}
150+
onPaneClick={handlePaneClick}
129151
nodeTypes={nodeTypes}
130152
selectionOnDrag={false}
131153
edgeTypes={edgeTypes}
@@ -150,6 +172,9 @@ export const EditorCanvas = memo(({ defaultLanguage = "en" }: EditorCanvasProps)
150172
</ControlButton>
151173
</Controls>
152174
{selectedNodeIds.length > 1 && <MultiNodePanel />}
175+
<EditorPanel defaultLanguage={defaultLanguage} />
176+
<EdgePanel defaultLanguage={defaultLanguage} />
177+
<SettingsPanel defaultLanguage={defaultLanguage} />
153178
</ReactFlow>
154179
</div>
155180
);

packages/learningmap/src/EditorDrawerEdgeContent.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export function EditorDrawerEdgeContent({
1616
const t = getTranslations(language);
1717

1818
return (
19-
<div className="drawer-content">
19+
<div className="panel-content">
2020
<div className="form-group">
2121
<ColorSelector
2222
label={t.edgeColor}

packages/learningmap/src/EditorDrawerImageContent.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export function EditorDrawerImageContent({ localNode, handleFieldChange, languag
2525
};
2626

2727
return (
28-
<div className="drawer-content">
28+
<div className="panel-content">
2929
<div className="form-group">
3030
<label>{t.image} (JPG, PNG, SVG)</label>
3131
<input

packages/learningmap/src/EditorDrawerTaskContent.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export function EditorDrawerTaskContent({
6060
if (localNode.type === "topic") defaultColor = "yellow";
6161
const selectedColor = localNode.data?.color || defaultColor;
6262
return (
63-
<div className="drawer-content">
63+
<div className="panel-content">
6464
<div className="form-group">
6565
<label>{t.nodeColor}</label>
6666
<div style={{ display: "flex", gap: 12, marginTop: 8 }}>

packages/learningmap/src/EditorDrawerTextContent.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export function EditorDrawerTextContent({ localNode, handleFieldChange, language
1414
const t = getTranslations(language);
1515

1616
return (
17-
<div className="drawer-content">
17+
<div className="panel-content">
1818
<div className="form-group">
1919
<label>{t.text}</label>
2020
<input

0 commit comments

Comments
 (0)