@@ -2,14 +2,58 @@ import { useEffect } from "react";
22import { useReactFlow } from "@xyflow/react" ;
33import { useEditorStore , useTemporalStore } from "./editorStore" ;
44import { Node } from "@xyflow/react" ;
5- import { NodeData } from "./types" ;
5+ import { NodeData , KeyBindings , KeyBinding } from "./types" ;
66import { getTranslations } from "./translations" ;
77
88interface KeyboardShortcutsProps {
99 jsonStore ?: string ;
10+ keyBindings ?: Partial < KeyBindings > ;
1011}
1112
12- export const KeyboardShortcuts = ( { jsonStore = "https://json.openpatch.org" } : KeyboardShortcutsProps ) => {
13+ // Default keybindings
14+ const defaultKeyBindings : KeyBindings = {
15+ addTaskNode : { key : '1' , ctrl : true } ,
16+ addTopicNode : { key : '2' , ctrl : true } ,
17+ addImageNode : { key : '3' , ctrl : true } ,
18+ addTextNode : { key : '4' , ctrl : true } ,
19+ save : { key : 's' , ctrl : true } ,
20+ undo : { key : 'z' , ctrl : true } ,
21+ redo : { key : 'y' , ctrl : true } ,
22+ help : { key : '?' , ctrl : true } ,
23+ togglePreview : { key : 'p' , ctrl : true } ,
24+ toggleDebug : { key : 'd' , ctrl : true } ,
25+ zoomIn : { key : '+' , ctrl : true } ,
26+ zoomOut : { key : '-' , ctrl : true } ,
27+ resetZoom : { key : '0' , ctrl : true } ,
28+ toggleGrid : { key : "'" , ctrl : true } ,
29+ resetMap : { key : 'Delete' , ctrl : true } ,
30+ cut : { key : 'x' , ctrl : true } ,
31+ copy : { key : 'c' , ctrl : true } ,
32+ paste : { key : 'v' , ctrl : true } ,
33+ selectAll : { key : 'a' , ctrl : true } ,
34+ fitView : { key : '!' , shift : true } ,
35+ zoomToSelection : { key : '@' , shift : true } ,
36+ deleteSelected : { key : 'Delete' } ,
37+ } ;
38+
39+ const matchesKeyBinding = ( e : KeyboardEvent , binding : KeyBinding | undefined ) : boolean => {
40+ if ( ! binding ) return false ;
41+
42+ const keyMatches = e . key . toLowerCase ( ) === binding . key . toLowerCase ( ) ;
43+ const ctrlMatches = binding . ctrl ? ( e . ctrlKey || e . metaKey ) : ! ( e . ctrlKey || e . metaKey ) ;
44+ const shiftMatches = binding . shift ? e . shiftKey : ! e . shiftKey ;
45+ const altMatches = binding . alt ? e . altKey : ! e . altKey ;
46+
47+ return keyMatches && ctrlMatches && shiftMatches && altMatches ;
48+ } ;
49+
50+ export const KeyboardShortcuts = ( {
51+ jsonStore = "https://json.openpatch.org" ,
52+ keyBindings : customKeyBindings = { }
53+ } : KeyboardShortcutsProps ) => {
54+ // Merge custom keybindings with defaults
55+ const keyBindings = { ...defaultKeyBindings , ...customKeyBindings } ;
56+
1357 const { zoomIn, zoomOut, setCenter, fitView, screenToFlowPosition } = useReactFlow ( ) ;
1458
1559 // Get store state
@@ -167,74 +211,72 @@ export const KeyboardShortcuts = ({ jsonStore = "https://json.openpatch.org" }:
167211 if ( drawerOpen || edgeDrawerOpen || settingsDrawerOpen ) {
168212 return ; // Ignore shortcuts when any drawer is open
169213 }
170- if ( e . ctrlKey || e . metaKey ) {
171- if ( e . key === '1' ) {
172- e . preventDefault ( ) ;
173- onAddNode ( "task" ) ;
174- } else if ( e . key === '2' ) {
175- e . preventDefault ( ) ;
176- onAddNode ( "topic" ) ;
177- } else if ( e . key === '3' ) {
178- e . preventDefault ( ) ;
179- onAddNode ( "image" ) ;
180- } else if ( e . key === '4' ) {
181- e . preventDefault ( ) ;
182- onAddNode ( "text" ) ;
183- } else if ( e . key === 's' ) {
184- e . preventDefault ( ) ;
185- onSave ( ) ;
186- } else if ( e . key === 'z' && ! e . shiftKey ) {
187- e . preventDefault ( ) ;
188- undo ( ) ;
189- } else if ( ( e . key === 'y' ) || ( e . key === 'z' && e . shiftKey ) ) {
190- e . preventDefault ( ) ;
191- redo ( ) ;
192- } else if ( ( e . key === '?' || ( e . shiftKey && e . key === '/' ) ) ) {
193- e . preventDefault ( ) ;
194- setHelpOpen ( ! helpOpen ) ;
195- } else if ( e . key . toLowerCase ( ) === 'p' && ! e . shiftKey ) {
196- e . preventDefault ( ) ;
197- onTogglePreview ( ) ;
198- } else if ( e . key . toLowerCase ( ) === 'd' && ! e . shiftKey ) {
199- e . preventDefault ( ) ;
200- onToggleDebug ( ) ;
201- } else if ( e . key === '+' || e . key === '=' ) {
202- e . preventDefault ( ) ;
203- onZoomIn ( ) ;
204- } else if ( e . key === '-' ) {
205- e . preventDefault ( ) ;
206- onZoomOut ( ) ;
207- } else if ( e . key === '0' ) {
208- e . preventDefault ( ) ;
209- onResetZoom ( ) ;
210- } else if ( e . key === "'" ) {
211- e . preventDefault ( ) ;
212- onToggleGrid ( ) ;
213- } else if ( e . key === 'Delete' ) {
214- e . preventDefault ( ) ;
215- onResetMap ( ) ;
216- } else if ( e . key . toLowerCase ( ) === 'x' ) {
217- e . preventDefault ( ) ;
218- onCut ( ) ;
219- } else if ( e . key . toLowerCase ( ) === 'c' ) {
220- e . preventDefault ( ) ;
221- onCopy ( ) ;
222- } else if ( e . key . toLowerCase ( ) === 'v' ) {
223- e . preventDefault ( ) ;
224- onPaste ( ) ;
225- } else if ( e . key . toLowerCase ( ) === 'a' ) {
226- e . preventDefault ( ) ;
227- onSelectAll ( ) ;
228- }
229- } else if ( e . shiftKey ) {
230- if ( e . key === '!' ) {
231- e . preventDefault ( ) ;
232- onFitView ( ) ;
233- } else if ( e . key === '@' ) {
234- e . preventDefault ( ) ;
235- onZoomToSelection ( ) ;
236- }
237- } else if ( e . key === 'Delete' ) {
214+
215+ // Check each keybinding
216+ if ( matchesKeyBinding ( e , keyBindings . addTaskNode ) ) {
217+ e . preventDefault ( ) ;
218+ onAddNode ( "task" ) ;
219+ } else if ( matchesKeyBinding ( e , keyBindings . addTopicNode ) ) {
220+ e . preventDefault ( ) ;
221+ onAddNode ( "topic" ) ;
222+ } else if ( matchesKeyBinding ( e , keyBindings . addImageNode ) ) {
223+ e . preventDefault ( ) ;
224+ onAddNode ( "image" ) ;
225+ } else if ( matchesKeyBinding ( e , keyBindings . addTextNode ) ) {
226+ e . preventDefault ( ) ;
227+ onAddNode ( "text" ) ;
228+ } else if ( matchesKeyBinding ( e , keyBindings . save ) ) {
229+ e . preventDefault ( ) ;
230+ onSave ( ) ;
231+ } else if ( matchesKeyBinding ( e , keyBindings . undo ) ) {
232+ e . preventDefault ( ) ;
233+ undo ( ) ;
234+ } else if ( matchesKeyBinding ( e , keyBindings . redo ) ) {
235+ e . preventDefault ( ) ;
236+ redo ( ) ;
237+ } else if ( matchesKeyBinding ( e , keyBindings . help ) ) {
238+ e . preventDefault ( ) ;
239+ setHelpOpen ( ! helpOpen ) ;
240+ } else if ( matchesKeyBinding ( e , keyBindings . togglePreview ) ) {
241+ e . preventDefault ( ) ;
242+ onTogglePreview ( ) ;
243+ } else if ( matchesKeyBinding ( e , keyBindings . toggleDebug ) ) {
244+ e . preventDefault ( ) ;
245+ onToggleDebug ( ) ;
246+ } else if ( matchesKeyBinding ( e , keyBindings . zoomIn ) ) {
247+ e . preventDefault ( ) ;
248+ onZoomIn ( ) ;
249+ } else if ( matchesKeyBinding ( e , keyBindings . zoomOut ) ) {
250+ e . preventDefault ( ) ;
251+ onZoomOut ( ) ;
252+ } else if ( matchesKeyBinding ( e , keyBindings . resetZoom ) ) {
253+ e . preventDefault ( ) ;
254+ onResetZoom ( ) ;
255+ } else if ( matchesKeyBinding ( e , keyBindings . toggleGrid ) ) {
256+ e . preventDefault ( ) ;
257+ onToggleGrid ( ) ;
258+ } else if ( matchesKeyBinding ( e , keyBindings . resetMap ) ) {
259+ e . preventDefault ( ) ;
260+ onResetMap ( ) ;
261+ } else if ( matchesKeyBinding ( e , keyBindings . cut ) ) {
262+ e . preventDefault ( ) ;
263+ onCut ( ) ;
264+ } else if ( matchesKeyBinding ( e , keyBindings . copy ) ) {
265+ e . preventDefault ( ) ;
266+ onCopy ( ) ;
267+ } else if ( matchesKeyBinding ( e , keyBindings . paste ) ) {
268+ e . preventDefault ( ) ;
269+ onPaste ( ) ;
270+ } else if ( matchesKeyBinding ( e , keyBindings . selectAll ) ) {
271+ e . preventDefault ( ) ;
272+ onSelectAll ( ) ;
273+ } else if ( matchesKeyBinding ( e , keyBindings . fitView ) ) {
274+ e . preventDefault ( ) ;
275+ onFitView ( ) ;
276+ } else if ( matchesKeyBinding ( e , keyBindings . zoomToSelection ) ) {
277+ e . preventDefault ( ) ;
278+ onZoomToSelection ( ) ;
279+ } else if ( matchesKeyBinding ( e , keyBindings . deleteSelected ) ) {
238280 e . preventDefault ( ) ;
239281 onDeleteSelected ( ) ;
240282 }
@@ -246,7 +288,7 @@ export const KeyboardShortcuts = ({ jsonStore = "https://json.openpatch.org" }:
246288 } ;
247289 } , [ onAddNode , onDeleteSelected , onSave , undo , redo , helpOpen , setHelpOpen , onTogglePreview , onToggleDebug ,
248290 onZoomIn , onZoomOut , onResetZoom , onFitView , onZoomToSelection , onToggleGrid ,
249- onResetMap , onCut , onCopy , onPaste , onSelectAll , drawerOpen , edgeDrawerOpen , settingsDrawerOpen ] ) ;
291+ onResetMap , onCut , onCopy , onPaste , onSelectAll , drawerOpen , edgeDrawerOpen , settingsDrawerOpen , keyBindings ] ) ;
250292
251293 return null ;
252294} ;
0 commit comments