Implement state contexts

This commit is contained in:
2024-10-04 21:21:24 +02:00
parent 402d6b1623
commit 5b946269bc
14 changed files with 334 additions and 250 deletions

View File

@@ -1,48 +1,17 @@
// App.js
import React from 'react'; import React from 'react';
import { GeistProvider, CssBaseline, Page, useToasts } from '@geist-ui/core'; import { GeistProvider, CssBaseline, Page } from '@geist-ui/core';
import Header from './components/Header'; import Header from './components/Header';
import MainContent from './components/MainContent'; import MainContent from './components/MainContent';
import { useFileManagement } from './hooks/useFileManagement';
import { SettingsProvider, useSettings } from './contexts/SettingsContext'; import { SettingsProvider, useSettings } from './contexts/SettingsContext';
import { lookupFileByName } from './services/api'; import { FileContentProvider } from './contexts/FileContentContext';
import { FileListProvider } from './contexts/FileListContext';
import { GitOperationsProvider } from './contexts/GitOperationsContext';
import { UIStateProvider } from './contexts/UIStateContext';
import './App.scss'; import './App.scss';
function AppContent() { function AppContent() {
const { settings, loading } = useSettings(); const { settings, loading } = useSettings();
const { setToast } = useToasts();
const {
content,
files,
selectedFile,
isNewFile,
hasUnsavedChanges,
error,
handleFileSelect,
handleContentChange,
handleSave,
pullLatestChanges,
} = useFileManagement(settings.gitEnabled);
const handleLinkClick = async (filename) => {
try {
const filePaths = await lookupFileByName(filename);
if (filePaths.length === 1) {
handleFileSelect(filePaths[0]);
} else if (filePaths.length > 1) {
// Handle multiple file options (you may want to show a modal or dropdown)
console.log('Multiple files found:', filePaths);
} else {
setToast({ text: `File "${filename}" not found`, type: 'error' });
}
} catch (error) {
console.error('Error looking up file:', error);
setToast({
text: 'Failed to lookup file. Please try again.',
type: 'error',
});
}
};
if (loading) { if (loading) {
return <div>Loading...</div>; return <div>Loading...</div>;
@@ -54,21 +23,7 @@ function AppContent() {
<Page> <Page>
<Header /> <Header />
<Page.Content className="page-content"> <Page.Content className="page-content">
<MainContent <MainContent />
content={content}
files={files}
selectedFile={selectedFile}
isNewFile={isNewFile}
hasUnsavedChanges={hasUnsavedChanges}
error={error}
onFileSelect={handleFileSelect}
onContentChange={handleContentChange}
onSave={handleSave}
settings={settings}
pullLatestChanges={pullLatestChanges}
onLinkClick={handleLinkClick}
lookupFileByName={lookupFileByName}
/>
</Page.Content> </Page.Content>
</Page> </Page>
</GeistProvider> </GeistProvider>
@@ -78,7 +33,15 @@ function AppContent() {
function App() { function App() {
return ( return (
<SettingsProvider> <SettingsProvider>
<AppContent /> <FileListProvider>
<FileContentProvider>
<GitOperationsProvider>
<UIStateProvider>
<AppContent />
</UIStateProvider>
</GitOperationsProvider>
</FileContentProvider>
</FileListProvider>
</SettingsProvider> </SettingsProvider>
); );
} }

View File

@@ -3,17 +3,17 @@ import Editor from './Editor';
import MarkdownPreview from './MarkdownPreview'; import MarkdownPreview from './MarkdownPreview';
import { getFileUrl } from '../services/api'; import { getFileUrl } from '../services/api';
import { isImageFile } from '../utils/fileHelpers'; import { isImageFile } from '../utils/fileHelpers';
import { useFileContentContext } from '../contexts/FileContentContext';
const ContentView = ({ const ContentView = ({
activeTab, activeTab,
content,
selectedFile,
onContentChange,
onSave,
themeType, themeType,
onLinkClick, onLinkClick,
lookupFileByName, lookupFileByName,
}) => { }) => {
const { content, selectedFile, handleContentChange, handleSave } =
useFileContentContext();
if (isImageFile(selectedFile)) { if (isImageFile(selectedFile)) {
return ( return (
<div className="image-preview"> <div className="image-preview">
@@ -33,8 +33,8 @@ const ContentView = ({
return activeTab === 'source' ? ( return activeTab === 'source' ? (
<Editor <Editor
content={content} content={content}
onChange={onContentChange} onChange={handleContentChange}
onSave={onSave} onSave={handleSave}
filePath={selectedFile} filePath={selectedFile}
themeType={themeType} themeType={themeType}
/> />

View File

@@ -1,16 +1,25 @@
import React from 'react'; import React from 'react';
import { Button, Tooltip, ButtonGroup, Spacer } from '@geist-ui/core'; import { Button, Tooltip, ButtonGroup, Spacer } from '@geist-ui/core';
import { Plus, Trash, GitPullRequest, GitCommit } from '@geist-ui/icons'; import { Plus, Trash, GitPullRequest, GitCommit } from '@geist-ui/icons';
import { useFileContentContext } from '../contexts/FileContentContext';
import { useGitOperationsContext } from '../contexts/GitOperationsContext';
import { useSettings } from '../contexts/SettingsContext';
import { useUIStateContext } from '../contexts/UIStateContext';
const FileActions = () => {
const { selectedFile } = useFileContentContext();
const { pullLatestChanges } = useGitOperationsContext();
const { settings } = useSettings();
const {
setNewFileModalVisible,
setDeleteFileModalVisible,
setCommitMessageModalVisible,
} = useUIStateContext();
const handleCreateFile = () => setNewFileModalVisible(true);
const handleDeleteFile = () => setDeleteFileModalVisible(true);
const handleCommitAndPush = () => setCommitMessageModalVisible(true);
const FileActions = ({
selectedFile,
gitEnabled,
gitAutoCommit,
onPull,
onCommitAndPush,
onCreateFile,
onDeleteFile,
}) => {
return ( return (
<ButtonGroup className="file-actions"> <ButtonGroup className="file-actions">
<Tooltip text="Create new file" type="dark"> <Tooltip text="Create new file" type="dark">
@@ -18,7 +27,7 @@ const FileActions = ({
icon={<Plus />} icon={<Plus />}
auto auto
scale={2 / 3} scale={2 / 3}
onClick={onCreateFile} onClick={handleCreateFile}
px={0.6} px={0.6}
/> />
</Tooltip> </Tooltip>
@@ -31,7 +40,7 @@ const FileActions = ({
icon={<Trash />} icon={<Trash />}
auto auto
scale={2 / 3} scale={2 / 3}
onClick={onDeleteFile} onClick={handleDeleteFile}
disabled={!selectedFile} disabled={!selectedFile}
type="error" type="error"
px={0.6} px={0.6}
@@ -39,24 +48,28 @@ const FileActions = ({
</Tooltip> </Tooltip>
<Spacer w={0.5} /> <Spacer w={0.5} />
<Tooltip <Tooltip
text={gitEnabled ? 'Pull changes from remote' : 'Git is not enabled'} text={
settings.gitEnabled
? 'Pull changes from remote'
: 'Git is not enabled'
}
type="dark" type="dark"
> >
<Button <Button
icon={<GitPullRequest />} icon={<GitPullRequest />}
auto auto
scale={2 / 3} scale={2 / 3}
onClick={onPull} onClick={pullLatestChanges}
disabled={!gitEnabled} disabled={!settings.gitEnabled}
px={0.6} px={0.6}
/> />
</Tooltip> </Tooltip>
<Spacer w={0.5} /> <Spacer w={0.5} />
<Tooltip <Tooltip
text={ text={
!gitEnabled !settings.gitEnabled
? 'Git is not enabled' ? 'Git is not enabled'
: gitAutoCommit : settings.gitAutoCommit
? 'Auto-commit is enabled' ? 'Auto-commit is enabled'
: 'Commit and push changes' : 'Commit and push changes'
} }
@@ -66,8 +79,8 @@ const FileActions = ({
icon={<GitCommit />} icon={<GitCommit />}
auto auto
scale={2 / 3} scale={2 / 3}
onClick={onCommitAndPush} onClick={handleCommitAndPush}
disabled={!gitEnabled || gitAutoCommit} disabled={!settings.gitEnabled || settings.gitAutoCommit}
px={0.6} px={0.6}
/> />
</Tooltip> </Tooltip>

View File

@@ -1,20 +1,18 @@
import React from 'react'; import React from 'react';
import { Tree } from '@geist-ui/core'; import { Tree } from '@geist-ui/core';
import { File, Folder, Image } from '@geist-ui/icons'; import { File, Folder, Image } from '@geist-ui/icons';
import { useFileContentContext } from '../contexts/FileContentContext';
import { useFileListContext } from '../contexts/FileListContext';
import { isImageFile } from '../utils/fileHelpers';
const FileTree = () => {
const { files } = useFileListContext();
const { selectedFile, handleFileSelect } = useFileContentContext();
const FileTree = ({
files = [],
onFileSelect = () => {},
selectedFile = null,
}) => {
if (files.length === 0) { if (files.length === 0) {
return <div>No files to display</div>; return <div>No files to display</div>;
} }
const handleSelect = (filePath) => {
onFileSelect(filePath);
};
const renderLabel = (node) => { const renderLabel = (node) => {
const path = node.extra; const path = node.extra;
return ( return (
@@ -24,11 +22,6 @@ const FileTree = ({
); );
}; };
const isImageFile = (fileName) => {
const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
return imageExtensions.some((ext) => fileName.toLowerCase().endsWith(ext));
};
const renderIcon = ({ type, name }) => { const renderIcon = ({ type, name }) => {
if (type === 'directory') return <Folder />; if (type === 'directory') return <Folder />;
return isImageFile(name) ? <Image /> : <File />; return isImageFile(name) ? <Image /> : <File />;
@@ -37,7 +30,7 @@ const FileTree = ({
return ( return (
<Tree <Tree
value={files} value={files}
onClick={handleSelect} onClick={(filePath) => handleFileSelect(filePath)}
renderIcon={renderIcon} renderIcon={renderIcon}
renderLabel={renderLabel} renderLabel={renderLabel}
/> />

View File

@@ -1,4 +1,5 @@
import React, { useState, useEffect } from 'react'; // components/MainContent.js
import React from 'react';
import { import {
Grid, Grid,
Breadcrumbs, Breadcrumbs,
@@ -10,46 +11,36 @@ import {
import { Code, Eye } from '@geist-ui/icons'; import { Code, Eye } from '@geist-ui/icons';
import FileTree from './FileTree'; import FileTree from './FileTree';
import FileActions from './FileActions'; import FileActions from './FileActions';
import ContentView from './ContentView';
import CreateFileModal from './modals/CreateFileModal'; import CreateFileModal from './modals/CreateFileModal';
import DeleteFileModal from './modals/DeleteFileModal'; import DeleteFileModal from './modals/DeleteFileModal';
import CommitMessageModal from './modals/CommitMessageModal'; import CommitMessageModal from './modals/CommitMessageModal';
import ContentView from './ContentView'; import { useFileListContext } from '../contexts/FileListContext';
import { commitAndPush, saveFileContent, deleteFile } from '../services/api'; import { useFileContentContext } from '../contexts/FileContentContext';
import { isImageFile } from '../utils/fileHelpers'; import { useGitOperationsContext } from '../contexts/GitOperationsContext';
import { useSettings } from '../contexts/SettingsContext'; import { useUIStateContext } from '../contexts/UIStateContext';
import { useFileNavigation } from '../hooks/useFileNavigation';
const MainContent = ({ const MainContent = () => {
content, const { files } = useFileListContext();
files, const { selectedFile, hasUnsavedChanges } = useFileContentContext();
selectedFile, const { pullLatestChanges } = useGitOperationsContext();
hasUnsavedChanges, const {
onFileSelect, activeTab,
onContentChange, setActiveTab,
onSave, newFileModalVisible,
pullLatestChanges, setNewFileModalVisible,
onLinkClick, deleteFileModalVisible,
lookupFileByName, setDeleteFileModalVisible,
}) => { commitMessageModalVisible,
const [activeTab, setActiveTab] = useState('source'); setCommitMessageModalVisible,
} = useUIStateContext();
const { handleLinkClick } = useFileNavigation();
const { type: themeType } = useTheme(); const { type: themeType } = useTheme();
const { setToast } = useToasts(); const { setToast } = useToasts();
const { settings } = useSettings();
const [newFileModalVisible, setNewFileModalVisible] = useState(false);
const [newFileName, setNewFileName] = useState('');
const [deleteFileModalVisible, setDeleteFileModalVisible] = useState(false);
const [commitMessageModalVisible, setCommitMessageModalVisible] =
useState(false);
useEffect(() => {
if (isImageFile(selectedFile)) {
setActiveTab('preview');
}
}, [selectedFile]);
const handleTabChange = (value) => { const handleTabChange = (value) => {
if (!isImageFile(selectedFile) || value === 'preview') { setActiveTab(value);
setActiveTab(value);
}
}; };
const handlePull = async () => { const handlePull = async () => {
@@ -68,64 +59,14 @@ const MainContent = ({
setNewFileModalVisible(true); setNewFileModalVisible(true);
}; };
const handleNewFileSubmit = async () => {
if (newFileName) {
try {
await saveFileContent(newFileName, '');
setToast({ text: 'New file created successfully', type: 'success' });
await pullLatestChanges();
onFileSelect(newFileName);
} catch (error) {
setToast({
text: 'Failed to create new file: ' + error.message,
type: 'error',
});
}
}
setNewFileModalVisible(false);
setNewFileName('');
};
const handleDeleteFile = () => { const handleDeleteFile = () => {
setDeleteFileModalVisible(true); setDeleteFileModalVisible(true);
}; };
const confirmDeleteFile = async () => {
try {
await deleteFile(selectedFile);
setToast({ text: 'File deleted successfully', type: 'success' });
await pullLatestChanges();
onFileSelect(null);
} catch (error) {
setToast({
text: 'Failed to delete file: ' + error.message,
type: 'error',
});
}
setDeleteFileModalVisible(false);
};
const handleCommitAndPush = () => { const handleCommitAndPush = () => {
setCommitMessageModalVisible(true); setCommitMessageModalVisible(true);
}; };
const confirmCommitAndPush = async (message) => {
try {
await commitAndPush(message);
setToast({
text: 'Changes committed and pushed successfully',
type: 'success',
});
await pullLatestChanges();
} catch (error) {
setToast({
text: 'Failed to commit and push changes: ' + error.message,
type: 'error',
});
}
setCommitMessageModalVisible(false);
};
const renderBreadcrumbs = () => { const renderBreadcrumbs = () => {
if (!selectedFile) return null; if (!selectedFile) return null;
const pathParts = selectedFile.split('/'); const pathParts = selectedFile.split('/');
@@ -149,19 +90,12 @@ const MainContent = ({
<Grid xs={24} sm={6} md={5} lg={4} height="100%" className="sidebar"> <Grid xs={24} sm={6} md={5} lg={4} height="100%" className="sidebar">
<div className="file-tree-container"> <div className="file-tree-container">
<FileActions <FileActions
selectedFile={selectedFile}
gitEnabled={settings.gitEnabled}
gitAutoCommit={settings.gitAutoCommit}
onPull={handlePull} onPull={handlePull}
onCommitAndPush={handleCommitAndPush} onCommitAndPush={handleCommitAndPush}
onCreateFile={handleCreateFile} onCreateFile={handleCreateFile}
onDeleteFile={handleDeleteFile} onDeleteFile={handleDeleteFile}
/> />
<FileTree <FileTree files={files} />
files={files}
onFileSelect={onFileSelect}
selectedFile={selectedFile}
/>
</div> </div>
</Grid> </Grid>
<Grid <Grid
@@ -175,46 +109,22 @@ const MainContent = ({
<div className="content-header"> <div className="content-header">
{renderBreadcrumbs()} {renderBreadcrumbs()}
<Tabs value={activeTab} onChange={handleTabChange}> <Tabs value={activeTab} onChange={handleTabChange}>
<Tabs.Item <Tabs.Item label={<Code />} value="source" />
label={<Code />}
value="source"
disabled={isImageFile(selectedFile)}
/>
<Tabs.Item label={<Eye />} value="preview" /> <Tabs.Item label={<Eye />} value="preview" />
</Tabs> </Tabs>
</div> </div>
<div className="content-body"> <div className="content-body">
<ContentView <ContentView
activeTab={activeTab} activeTab={activeTab}
content={content}
selectedFile={selectedFile}
onContentChange={onContentChange}
onSave={onSave}
themeType={themeType} themeType={themeType}
onLinkClick={onLinkClick} onLinkClick={handleLinkClick}
lookupFileByName={lookupFileByName}
/> />
</div> </div>
</Grid> </Grid>
</Grid.Container> </Grid.Container>
<CreateFileModal <CreateFileModal visible={newFileModalVisible} />
visible={newFileModalVisible} <DeleteFileModal visible={deleteFileModalVisible} />
onClose={() => setNewFileModalVisible(false)} <CommitMessageModal visible={commitMessageModalVisible} />
onSubmit={handleNewFileSubmit}
fileName={newFileName}
setFileName={setNewFileName}
/>
<DeleteFileModal
visible={deleteFileModalVisible}
onClose={() => setDeleteFileModalVisible(false)}
onConfirm={confirmDeleteFile}
fileName={selectedFile}
/>
<CommitMessageModal
visible={commitMessageModalVisible}
onClose={() => setCommitMessageModalVisible(false)}
onSubmit={confirmCommitAndPush}
/>
</> </>
); );
}; };

View File

@@ -1,16 +1,27 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Modal, Input } from '@geist-ui/core'; import { Modal, Input } from '@geist-ui/core';
import { useGitOperationsContext } from '../../contexts/GitOperationsContext';
import { useUIStateContext } from '../../contexts/UIStateContext';
const CommitMessageModal = ({ visible, onClose, onSubmit }) => { const CommitMessageModal = () => {
const [message, setMessage] = useState(''); const [message, setMessage] = useState('');
const { handleCommitAndPush } = useGitOperationsContext();
const { commitMessageModalVisible, setCommitMessageModalVisible } =
useUIStateContext();
const handleSubmit = () => { const handleSubmit = async () => {
onSubmit(message); if (message) {
setMessage(''); await handleCommitAndPush(message);
setMessage('');
setCommitMessageModalVisible(false);
}
}; };
return ( return (
<Modal visible={visible} onClose={onClose}> <Modal
visible={commitMessageModalVisible}
onClose={() => setCommitMessageModalVisible(false)}
>
<Modal.Title>Enter Commit Message</Modal.Title> <Modal.Title>Enter Commit Message</Modal.Title>
<Modal.Content> <Modal.Content>
<Input <Input
@@ -20,7 +31,7 @@ const CommitMessageModal = ({ visible, onClose, onSubmit }) => {
onChange={(e) => setMessage(e.target.value)} onChange={(e) => setMessage(e.target.value)}
/> />
</Modal.Content> </Modal.Content>
<Modal.Action passive onClick={onClose}> <Modal.Action passive onClick={() => setCommitMessageModalVisible(false)}>
Cancel Cancel
</Modal.Action> </Modal.Action>
<Modal.Action onClick={handleSubmit}>Commit</Modal.Action> <Modal.Action onClick={handleSubmit}>Commit</Modal.Action>

View File

@@ -1,15 +1,26 @@
import React from 'react'; import React, { useState } from 'react';
import { Modal, Input } from '@geist-ui/core'; import { Modal, Input } from '@geist-ui/core';
import { useFileContentContext } from '../../contexts/FileContentContext';
import { useUIStateContext } from '../../contexts/UIStateContext';
const CreateFileModal = () => {
const [fileName, setFileName] = useState('');
const { newFileModalVisible, setNewFileModalVisible } = useUIStateContext();
const { handleCreateNewFile } = useFileContentContext();
const handleSubmit = async () => {
if (fileName) {
await handleCreateNewFile(fileName);
setFileName('');
setNewFileModalVisible(false);
}
};
const CreateFileModal = ({
visible,
onClose,
onSubmit,
fileName,
setFileName,
}) => {
return ( return (
<Modal visible={visible} onClose={onClose}> <Modal
visible={newFileModalVisible}
onClose={() => setNewFileModalVisible(false)}
>
<Modal.Title>Create New File</Modal.Title> <Modal.Title>Create New File</Modal.Title>
<Modal.Content> <Modal.Content>
<Input <Input
@@ -19,10 +30,10 @@ const CreateFileModal = ({
onChange={(e) => setFileName(e.target.value)} onChange={(e) => setFileName(e.target.value)}
/> />
</Modal.Content> </Modal.Content>
<Modal.Action passive onClick={onClose}> <Modal.Action passive onClick={() => setNewFileModalVisible(false)}>
Cancel Cancel
</Modal.Action> </Modal.Action>
<Modal.Action onClick={onSubmit}>Create</Modal.Action> <Modal.Action onClick={handleSubmit}>Create</Modal.Action>
</Modal> </Modal>
); );
}; };

View File

@@ -1,17 +1,31 @@
import React from 'react'; import React from 'react';
import { Modal, Text } from '@geist-ui/core'; import { Modal, Text } from '@geist-ui/core';
import { useFileContentContext } from '../../contexts/FileContentContext';
import { useUIStateContext } from '../../contexts/UIStateContext';
const DeleteFileModal = () => {
const { selectedFile, handleDeleteFile } = useFileContentContext();
const { deleteFileModalVisible, setDeleteFileModalVisible } =
useUIStateContext();
const handleConfirm = async () => {
await handleDeleteFile();
setDeleteFileModalVisible(false);
};
const DeleteFileModal = ({ visible, onClose, onConfirm, fileName }) => {
return ( return (
<Modal visible={visible} onClose={onClose}> <Modal
visible={deleteFileModalVisible}
onClose={() => setDeleteFileModalVisible(false)}
>
<Modal.Title>Delete File</Modal.Title> <Modal.Title>Delete File</Modal.Title>
<Modal.Content> <Modal.Content>
<Text>Are you sure you want to delete "{fileName}"?</Text> <Text>Are you sure you want to delete "{selectedFile}"?</Text>
</Modal.Content> </Modal.Content>
<Modal.Action passive onClick={onClose}> <Modal.Action passive onClick={() => setDeleteFileModalVisible(false)}>
Cancel Cancel
</Modal.Action> </Modal.Action>
<Modal.Action onClick={onConfirm}>Delete</Modal.Action> <Modal.Action onClick={handleConfirm}>Delete</Modal.Action>
</Modal> </Modal>
); );
}; };

View File

@@ -0,0 +1,24 @@
import React, { createContext, useContext } from 'react';
import { useFileContent } from '../hooks/useFileContent';
const FileContentContext = createContext();
export const FileContentProvider = ({ children }) => {
const fileContentHook = useFileContent();
return (
<FileContentContext.Provider value={fileContentHook}>
{children}
</FileContentContext.Provider>
);
};
export const useFileContentContext = () => {
const context = useContext(FileContentContext);
if (!context) {
throw new Error(
'useFileContentContext must be used within a FileContentProvider'
);
}
return context;
};

View File

@@ -0,0 +1,24 @@
import React, { createContext, useContext } from 'react';
import { useFileList } from '../hooks/useFileList';
const FileListContext = createContext();
export const FileListProvider = ({ children }) => {
const fileListHook = useFileList();
return (
<FileListContext.Provider value={fileListHook}>
{children}
</FileListContext.Provider>
);
};
export const useFileListContext = () => {
const context = useContext(FileListContext);
if (!context) {
throw new Error(
'useFileListContext must be used within a FileListProvider'
);
}
return context;
};

View File

@@ -0,0 +1,24 @@
import React, { createContext, useContext } from 'react';
import { useGitOperations } from '../hooks/useGitOperations';
const GitOperationsContext = createContext();
export const GitOperationsProvider = ({ children }) => {
const gitOperationsHook = useGitOperations();
return (
<GitOperationsContext.Provider value={gitOperationsHook}>
{children}
</GitOperationsContext.Provider>
);
};
export const useGitOperationsContext = () => {
const context = useContext(GitOperationsContext);
if (!context) {
throw new Error(
'useGitOperationsContext must be used within a GitOperationsProvider'
);
}
return context;
};

View File

@@ -0,0 +1,34 @@
import React, { createContext, useContext, useState } from 'react';
const UIStateContext = createContext();
export const UIStateProvider = ({ children }) => {
const [activeTab, setActiveTab] = useState('source');
const [newFileModalVisible, setNewFileModalVisible] = useState(false);
const [deleteFileModalVisible, setDeleteFileModalVisible] = useState(false);
const [commitMessageModalVisible, setCommitMessageModalVisible] =
useState(false);
const value = {
activeTab,
setActiveTab,
newFileModalVisible,
setNewFileModalVisible,
deleteFileModalVisible,
setDeleteFileModalVisible,
commitMessageModalVisible,
setCommitMessageModalVisible,
};
return (
<UIStateContext.Provider value={value}>{children}</UIStateContext.Provider>
);
};
export const useUIStateContext = () => {
const context = useContext(UIStateContext);
if (!context) {
throw new Error('useUIStateContext must be used within a UIStateProvider');
}
return context;
};

View File

@@ -1,6 +1,8 @@
import { useState, useCallback } from 'react'; import { useState, useCallback } from 'react';
import { fetchFileContent, saveFileContent } from '../services/api'; import { fetchFileContent, saveFileContent, deleteFile } from '../services/api';
import { isImageFile } from '../utils/fileHelpers'; import { isImageFile } from '../utils/fileHelpers';
import { useToasts } from '@geist-ui/core';
import { useFileListContext } from '../contexts/FileListContext';
const DEFAULT_FILE = { const DEFAULT_FILE = {
name: 'New File.md', name: 'New File.md',
@@ -14,16 +16,12 @@ export const useFileContent = () => {
const [isNewFile, setIsNewFile] = useState(true); const [isNewFile, setIsNewFile] = useState(true);
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
const [error, setError] = useState(null); const [error, setError] = useState(null);
const { setToast } = useToasts();
const { loadFileList } = useFileListContext();
const handleFileSelect = useCallback( const handleFileSelect = useCallback(
async (filePath) => { async (filePath) => {
if (hasUnsavedChanges) { console.log('handleFileSelect', filePath);
const confirmSwitch = window.confirm(
'You have unsaved changes. Are you sure you want to switch files?'
);
if (!confirmSwitch) return;
}
try { try {
if (filePath === DEFAULT_FILE.path) { if (filePath === DEFAULT_FILE.path) {
setContent(DEFAULT_FILE.content); setContent(DEFAULT_FILE.content);
@@ -67,13 +65,38 @@ export const useFileContent = () => {
} }
}, []); }, []);
const createNewFile = useCallback(() => { const handleCreateNewFile = useCallback(
setContent(DEFAULT_FILE.content); async (fileName, initialContent = '') => {
setSelectedFile(DEFAULT_FILE.path); try {
setIsNewFile(true); await saveFileContent(fileName, initialContent);
setHasUnsavedChanges(false); setToast({ text: 'New file created successfully', type: 'success' });
setError(null); await loadFileList(); // Refresh the file list
}, []); handleFileSelect(fileName);
} catch (error) {
setToast({
text: 'Failed to create new file: ' + error.message,
type: 'error',
});
}
},
[setToast, loadFileList, handleFileSelect]
);
const handleDeleteFile = useCallback(async () => {
if (!selectedFile) return;
try {
await deleteFile(selectedFile);
setToast({ text: 'File deleted successfully', type: 'success' });
await loadFileList(); // Refresh the file list
setSelectedFile(null);
setContent('');
} catch (error) {
setToast({
text: 'Failed to delete file: ' + error.message,
type: 'error',
});
}
}, [selectedFile, setToast, loadFileList]);
return { return {
content, content,
@@ -84,7 +107,8 @@ export const useFileContent = () => {
handleFileSelect, handleFileSelect,
handleContentChange, handleContentChange,
handleSave, handleSave,
createNewFile, handleCreateNewFile,
handleDeleteFile,
DEFAULT_FILE, DEFAULT_FILE,
}; };
}; };

View File

@@ -0,0 +1,39 @@
// hooks/useFileNavigation.js
import { useCallback } from 'react';
import { useToasts } from '@geist-ui/core';
import { lookupFileByName } from '../services/api';
import { useFileContentContext } from '../contexts/FileContentContext';
export const useFileNavigation = () => {
const { setToast } = useToasts();
const { handleFileSelect } = useFileContentContext();
const handleLinkClick = useCallback(
async (filename) => {
try {
const filePaths = await lookupFileByName(filename);
if (filePaths.length === 1) {
handleFileSelect(filePaths[0]);
} else if (filePaths.length > 1) {
// Handle multiple file options (you may want to show a modal or dropdown)
console.log('Multiple files found:', filePaths);
setToast({
text: 'Multiple files found with the same name. Please specify the full path.',
type: 'warning',
});
} else {
setToast({ text: `File "${filename}" not found`, type: 'error' });
}
} catch (error) {
console.error('Error looking up file:', error);
setToast({
text: 'Failed to lookup file. Please try again.',
type: 'error',
});
}
},
[handleFileSelect, setToast]
);
return { handleLinkClick };
};