/ src / App.js
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;