App.js
1 import React, { useState, useEffect, useCallback, useRef } from 'react'; 2 import DreamSpace from './components/DreamSpace'; 3 import SettingsPanel from './components/SettingsPanel'; 4 import MetadataPanel from './components/MetadataPanel'; 5 import DreamGraph from './components/DreamGraph'; 6 import ContextMenu from './components/ContextMenu'; 7 import FileContextMenu from './components/FileContextMenu'; 8 import RenamePanel from './components/RenamePanel'; 9 import NodeCreationPanel from './components/NodeCreationPanel'; 10 import SearchPanel from './components/SearchPanel'; 11 import { openInGitFox, processFile } from './services/electronService'; 12 13 function App() { 14 const [isSettingsOpen, setIsSettingsOpen] = useState(false); 15 const [isMetadataPanelOpen, setIsMetadataPanelOpen] = useState(false); 16 const [isRenamePanelOpen, setIsRenamePanelOpen] = useState(false); 17 const [isNodeCreationPanelOpen, setIsNodeCreationPanelOpen] = useState(false); 18 const [isSearchPanelOpen, setIsSearchPanelOpen] = useState(false); 19 const [selectedRepoName, setSelectedRepoName] = useState(''); 20 const [contextMenu, setContextMenu] = useState(null); 21 const [fileContextMenu, setFileContextMenu] = useState(null); 22 const dreamGraphRef = useRef(null); 23 const [nodeNames, setNodeNames] = useState([]); 24 25 const handleNodesChange = useCallback((newNodeNames) => { 26 setNodeNames(newNodeNames); 27 }, []); 28 29 const handleDragOver = useCallback((event) => { 30 event.preventDefault(); 31 }, []); 32 33 const handleDrop = useCallback(async (event, targetNodeName = null) => { 34 event.preventDefault(); 35 const item = event.dataTransfer.items[0]; 36 if (item.kind === 'file') { 37 const entry = item.webkitGetAsEntry(); 38 const file = event.dataTransfer.files[0]; 39 40 if (targetNodeName) { 41 // Dropped on a specific node 42 try { 43 const fileReader = new FileReader(); 44 fileReader.onload = async (e) => { 45 const arrayBuffer = e.target.result; 46 const fileData = { 47 name: file.name, 48 type: file.type, 49 size: file.size, 50 lastModified: file.lastModified, 51 data: arrayBuffer 52 }; 53 const fileAdded = await window.electron.fileSystem.addFileToNode(targetNodeName, fileData); 54 if (fileAdded) { 55 console.log(`File ${file.name} added to node ${targetNodeName}`); 56 // You might want to refresh the DreamSpace or update the state here 57 } else { 58 console.error(`Failed to add file ${file.name} to node ${targetNodeName}`); 59 } 60 }; 61 fileReader.onerror = (error) => { 62 console.error('Error reading file:', error); 63 }; 64 fileReader.readAsArrayBuffer(file); 65 } catch (error) { 66 console.error('Error in drag and drop process:', error); 67 } 68 } else { 69 // Dropped on empty space 70 if (entry.isDirectory) { 71 // Directory dropped 72 try { 73 const result = await window.electron.fileSystem.copyRepositoryToDreamVault(file.path, entry.name); 74 if (result.success) { 75 console.log(`Repository ${entry.name} successfully copied to DreamVault`); 76 // You might want to refresh the DreamSpace or update the state here 77 } else { 78 console.error(`Failed to copy repository: ${result.error}`); 79 } 80 } catch (error) { 81 console.error('Error in drag and drop process:', error); 82 } 83 } else if (entry.name.endsWith('.bundle')) { 84 // Git bundle dropped 85 try { 86 const result = await window.electron.fileSystem.unbundleRepositoryToDreamVault(file.path, file.name.replace('.bundle', '')); 87 if (result.success) { 88 console.log(`Repository bundle ${file.name} successfully unbundled to DreamVault`); 89 // You might want to refresh the DreamSpace or update the state here 90 } else { 91 console.error(`Failed to unbundle repository: ${result.error}`); 92 } 93 } catch (error) { 94 console.error('Error in drag and drop process:', error); 95 } 96 } else if (entry.name.endsWith('.zip')) { 97 // Zip archive dropped 98 try { 99 const result = await window.electron.fileSystem.handleZipArchive(file.path); 100 if (result.success) { 101 console.log(`Zip archive ${file.name} successfully processed`); 102 // You might want to refresh the DreamSpace or update the state here 103 } else { 104 console.error(`Failed to process zip archive: ${result.error}`); 105 } 106 } catch (error) { 107 console.error('Error in drag and drop process:', error); 108 } 109 } else { 110 // File dropped 111 try { 112 const nodeName = file.name.split('.')[0]; // Use the filename without extension as the node name 113 const newNode = await window.electron.fileSystem.createNewNode(nodeName); 114 // New node created 115 116 if (newNode) { 117 // Read the file as an ArrayBuffer 118 const fileReader = new FileReader(); 119 fileReader.onload = async (e) => { 120 const arrayBuffer = e.target.result; 121 const fileData = { 122 name: file.name, 123 type: file.type, 124 size: file.size, 125 lastModified: file.lastModified, 126 data: arrayBuffer 127 }; 128 const fileAdded = await window.electron.fileSystem.addFileToNode(newNode, fileData); 129 if (fileAdded) { 130 // File added to node 131 // You might want to refresh the DreamSpace or update the state here 132 } else { 133 console.error(`Failed to add file ${file.name} to node ${newNode}`); 134 } 135 }; 136 fileReader.onerror = (error) => { 137 console.error('Error reading file:', error); 138 }; 139 fileReader.readAsArrayBuffer(file); 140 } 141 } catch (error) { 142 console.error('Error in drag and drop process:', error); 143 } 144 } 145 } 146 } 147 }, []); 148 149 useEffect(() => { 150 const handleKeyDown = (event) => { 151 if (event.metaKey && event.key === ',') { 152 setIsSettingsOpen(prev => !prev); 153 } 154 if (event.metaKey && event.key === 'n') { 155 event.preventDefault(); 156 setIsNodeCreationPanelOpen(true); 157 } 158 if (event.metaKey && event.key === 'z') { 159 event.preventDefault(); 160 if (dreamGraphRef.current) { 161 dreamGraphRef.current.handleUndo(); 162 } 163 } 164 if (event.metaKey && event.key === 'y') { 165 event.preventDefault(); 166 if (dreamGraphRef.current) { 167 dreamGraphRef.current.handleRedo(); 168 } 169 } 170 if (event.metaKey && event.key === 'f') { 171 event.preventDefault(); 172 setIsSearchPanelOpen(true); 173 } 174 if (event.key === 'Escape') { 175 if (isSearchPanelOpen) { 176 setIsSearchPanelOpen(false); 177 } else { 178 // Only reset the graph layout if search panel is not open 179 if (dreamGraphRef.current && dreamGraphRef.current.resetLayout) { 180 dreamGraphRef.current.resetLayout(); 181 } 182 } 183 } 184 }; 185 186 window.addEventListener('keydown', handleKeyDown); 187 window.addEventListener('dragover', handleDragOver); 188 window.addEventListener('drop', handleDrop); 189 190 return () => { 191 window.removeEventListener('keydown', handleKeyDown); 192 window.removeEventListener('dragover', handleDragOver); 193 window.removeEventListener('drop', handleDrop); 194 }; 195 }, [handleDragOver, handleDrop, isSearchPanelOpen]); 196 197 const handleOpenMetadataPanel = (repoName) => { 198 setSelectedRepoName(repoName); 199 setIsMetadataPanelOpen(true); 200 setContextMenu(null); // Close context menu when opening metadata panel 201 }; 202 203 const handleOpenRenamePanel = (repoName) => { 204 setSelectedRepoName(repoName); 205 setIsRenamePanelOpen(true); 206 setContextMenu(null); // Close context menu when opening rename panel 207 }; 208 209 const handleNodeRightClick = (event, repoName) => { 210 if (event && event.preventDefault) { 211 event.preventDefault(); // Prevent default context menu 212 } 213 setContextMenu({ 214 repoName, 215 position: { x: event.clientX, y: event.clientY } 216 }); 217 }; 218 219 const handleCloseContextMenu = () => { 220 setContextMenu(null); 221 setFileContextMenu(null); 222 }; 223 224 const handleFileRightClick = useCallback((event, file) => { 225 event.preventDefault(); 226 event.stopPropagation(); // Prevent the event from bubbling up 227 console.log('Right-click detected on file:', file); 228 setFileContextMenu({ 229 file, 230 position: { x: event.clientX, y: event.clientY } 231 }); 232 setContextMenu(null); // Close the regular context menu if it's open 233 }, []); 234 235 const handleProcessFile = useCallback((repoName, file) => { 236 console.log(`Processing file: ${file} in repo: ${repoName}`); 237 processFile(repoName, file); 238 }, []); 239 240 const handleSearchComplete = (searchResults) => { 241 console.log('Search results received in App:', searchResults); 242 if (dreamGraphRef.current) { 243 dreamGraphRef.current.displaySearchResults(searchResults); 244 } 245 }; 246 247 return ( 248 <> 249 <div className="App" onClick={handleCloseContextMenu}> 250 <DreamSpace 251 onNodeRightClick={handleNodeRightClick} 252 onFileRightClick={handleFileRightClick} 253 dreamGraphRef={dreamGraphRef} 254 onDrop={handleDrop} 255 onHover={(repoName) => console.log('Hovered node:', repoName)} 256 onNodesChange={handleNodesChange} 257 /> 258 </div> 259 {isSettingsOpen && ( 260 <SettingsPanel 261 isOpen={isSettingsOpen} 262 onClose={() => setIsSettingsOpen(false)} 263 /> 264 )} 265 {isMetadataPanelOpen && ( 266 <MetadataPanel 267 isOpen={isMetadataPanelOpen} 268 onClose={() => setIsMetadataPanelOpen(false)} 269 repoName={selectedRepoName} 270 /> 271 )} 272 {isRenamePanelOpen && ( 273 <RenamePanel 274 isOpen={isRenamePanelOpen} 275 onClose={() => setIsRenamePanelOpen(false)} 276 repoName={selectedRepoName} 277 /> 278 )} 279 {isNodeCreationPanelOpen && ( 280 <NodeCreationPanel 281 isOpen={isNodeCreationPanelOpen} 282 onClose={() => setIsNodeCreationPanelOpen(false)} 283 /> 284 )} 285 <SearchPanel 286 isOpen={isSearchPanelOpen} 287 onSearch={handleSearchComplete} 288 onClose={() => setIsSearchPanelOpen(false)} 289 repoNames={nodeNames} 290 /> 291 {contextMenu && ( 292 <ContextMenu 293 repoName={contextMenu.repoName} 294 position={contextMenu.position} 295 onClose={handleCloseContextMenu} 296 onEditMetadata={handleOpenMetadataPanel} 297 onRename={handleOpenRenamePanel} 298 onOpenInGitFox={() => openInGitFox(contextMenu.repoName)} 299 /> 300 )} 301 {fileContextMenu && ( 302 <FileContextMenu 303 x={fileContextMenu.position.x} 304 y={fileContextMenu.position.y} 305 file={fileContextMenu.file} 306 repoName={selectedRepoName} 307 onClose={handleCloseContextMenu} 308 onProcessFile={handleProcessFile} 309 /> 310 )} 311 </> 312 ); 313 } 314 315 export default App;