mirror of
https://github.com/lordmathis/lemma.git
synced 2025-11-06 07:54:22 +00:00
Implement state contexts
This commit is contained in:
@@ -1,48 +1,17 @@
|
||||
// App.js
|
||||
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 MainContent from './components/MainContent';
|
||||
import { useFileManagement } from './hooks/useFileManagement';
|
||||
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';
|
||||
|
||||
function AppContent() {
|
||||
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) {
|
||||
return <div>Loading...</div>;
|
||||
@@ -54,21 +23,7 @@ function AppContent() {
|
||||
<Page>
|
||||
<Header />
|
||||
<Page.Content className="page-content">
|
||||
<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}
|
||||
/>
|
||||
<MainContent />
|
||||
</Page.Content>
|
||||
</Page>
|
||||
</GeistProvider>
|
||||
@@ -78,7 +33,15 @@ function AppContent() {
|
||||
function App() {
|
||||
return (
|
||||
<SettingsProvider>
|
||||
<AppContent />
|
||||
<FileListProvider>
|
||||
<FileContentProvider>
|
||||
<GitOperationsProvider>
|
||||
<UIStateProvider>
|
||||
<AppContent />
|
||||
</UIStateProvider>
|
||||
</GitOperationsProvider>
|
||||
</FileContentProvider>
|
||||
</FileListProvider>
|
||||
</SettingsProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,17 +3,17 @@ import Editor from './Editor';
|
||||
import MarkdownPreview from './MarkdownPreview';
|
||||
import { getFileUrl } from '../services/api';
|
||||
import { isImageFile } from '../utils/fileHelpers';
|
||||
import { useFileContentContext } from '../contexts/FileContentContext';
|
||||
|
||||
const ContentView = ({
|
||||
activeTab,
|
||||
content,
|
||||
selectedFile,
|
||||
onContentChange,
|
||||
onSave,
|
||||
themeType,
|
||||
onLinkClick,
|
||||
lookupFileByName,
|
||||
}) => {
|
||||
const { content, selectedFile, handleContentChange, handleSave } =
|
||||
useFileContentContext();
|
||||
|
||||
if (isImageFile(selectedFile)) {
|
||||
return (
|
||||
<div className="image-preview">
|
||||
@@ -33,8 +33,8 @@ const ContentView = ({
|
||||
return activeTab === 'source' ? (
|
||||
<Editor
|
||||
content={content}
|
||||
onChange={onContentChange}
|
||||
onSave={onSave}
|
||||
onChange={handleContentChange}
|
||||
onSave={handleSave}
|
||||
filePath={selectedFile}
|
||||
themeType={themeType}
|
||||
/>
|
||||
|
||||
@@ -1,16 +1,25 @@
|
||||
import React from 'react';
|
||||
import { Button, Tooltip, ButtonGroup, Spacer } from '@geist-ui/core';
|
||||
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 (
|
||||
<ButtonGroup className="file-actions">
|
||||
<Tooltip text="Create new file" type="dark">
|
||||
@@ -18,7 +27,7 @@ const FileActions = ({
|
||||
icon={<Plus />}
|
||||
auto
|
||||
scale={2 / 3}
|
||||
onClick={onCreateFile}
|
||||
onClick={handleCreateFile}
|
||||
px={0.6}
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -31,7 +40,7 @@ const FileActions = ({
|
||||
icon={<Trash />}
|
||||
auto
|
||||
scale={2 / 3}
|
||||
onClick={onDeleteFile}
|
||||
onClick={handleDeleteFile}
|
||||
disabled={!selectedFile}
|
||||
type="error"
|
||||
px={0.6}
|
||||
@@ -39,24 +48,28 @@ const FileActions = ({
|
||||
</Tooltip>
|
||||
<Spacer w={0.5} />
|
||||
<Tooltip
|
||||
text={gitEnabled ? 'Pull changes from remote' : 'Git is not enabled'}
|
||||
text={
|
||||
settings.gitEnabled
|
||||
? 'Pull changes from remote'
|
||||
: 'Git is not enabled'
|
||||
}
|
||||
type="dark"
|
||||
>
|
||||
<Button
|
||||
icon={<GitPullRequest />}
|
||||
auto
|
||||
scale={2 / 3}
|
||||
onClick={onPull}
|
||||
disabled={!gitEnabled}
|
||||
onClick={pullLatestChanges}
|
||||
disabled={!settings.gitEnabled}
|
||||
px={0.6}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Spacer w={0.5} />
|
||||
<Tooltip
|
||||
text={
|
||||
!gitEnabled
|
||||
!settings.gitEnabled
|
||||
? 'Git is not enabled'
|
||||
: gitAutoCommit
|
||||
: settings.gitAutoCommit
|
||||
? 'Auto-commit is enabled'
|
||||
: 'Commit and push changes'
|
||||
}
|
||||
@@ -66,8 +79,8 @@ const FileActions = ({
|
||||
icon={<GitCommit />}
|
||||
auto
|
||||
scale={2 / 3}
|
||||
onClick={onCommitAndPush}
|
||||
disabled={!gitEnabled || gitAutoCommit}
|
||||
onClick={handleCommitAndPush}
|
||||
disabled={!settings.gitEnabled || settings.gitAutoCommit}
|
||||
px={0.6}
|
||||
/>
|
||||
</Tooltip>
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
import React from 'react';
|
||||
import { Tree } from '@geist-ui/core';
|
||||
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) {
|
||||
return <div>No files to display</div>;
|
||||
}
|
||||
|
||||
const handleSelect = (filePath) => {
|
||||
onFileSelect(filePath);
|
||||
};
|
||||
|
||||
const renderLabel = (node) => {
|
||||
const path = node.extra;
|
||||
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 }) => {
|
||||
if (type === 'directory') return <Folder />;
|
||||
return isImageFile(name) ? <Image /> : <File />;
|
||||
@@ -37,7 +30,7 @@ const FileTree = ({
|
||||
return (
|
||||
<Tree
|
||||
value={files}
|
||||
onClick={handleSelect}
|
||||
onClick={(filePath) => handleFileSelect(filePath)}
|
||||
renderIcon={renderIcon}
|
||||
renderLabel={renderLabel}
|
||||
/>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
// components/MainContent.js
|
||||
import React from 'react';
|
||||
import {
|
||||
Grid,
|
||||
Breadcrumbs,
|
||||
@@ -10,46 +11,36 @@ import {
|
||||
import { Code, Eye } from '@geist-ui/icons';
|
||||
import FileTree from './FileTree';
|
||||
import FileActions from './FileActions';
|
||||
import ContentView from './ContentView';
|
||||
import CreateFileModal from './modals/CreateFileModal';
|
||||
import DeleteFileModal from './modals/DeleteFileModal';
|
||||
import CommitMessageModal from './modals/CommitMessageModal';
|
||||
import ContentView from './ContentView';
|
||||
import { commitAndPush, saveFileContent, deleteFile } from '../services/api';
|
||||
import { isImageFile } from '../utils/fileHelpers';
|
||||
import { useSettings } from '../contexts/SettingsContext';
|
||||
import { useFileListContext } from '../contexts/FileListContext';
|
||||
import { useFileContentContext } from '../contexts/FileContentContext';
|
||||
import { useGitOperationsContext } from '../contexts/GitOperationsContext';
|
||||
import { useUIStateContext } from '../contexts/UIStateContext';
|
||||
import { useFileNavigation } from '../hooks/useFileNavigation';
|
||||
|
||||
const MainContent = ({
|
||||
content,
|
||||
files,
|
||||
selectedFile,
|
||||
hasUnsavedChanges,
|
||||
onFileSelect,
|
||||
onContentChange,
|
||||
onSave,
|
||||
pullLatestChanges,
|
||||
onLinkClick,
|
||||
lookupFileByName,
|
||||
}) => {
|
||||
const [activeTab, setActiveTab] = useState('source');
|
||||
const MainContent = () => {
|
||||
const { files } = useFileListContext();
|
||||
const { selectedFile, hasUnsavedChanges } = useFileContentContext();
|
||||
const { pullLatestChanges } = useGitOperationsContext();
|
||||
const {
|
||||
activeTab,
|
||||
setActiveTab,
|
||||
newFileModalVisible,
|
||||
setNewFileModalVisible,
|
||||
deleteFileModalVisible,
|
||||
setDeleteFileModalVisible,
|
||||
commitMessageModalVisible,
|
||||
setCommitMessageModalVisible,
|
||||
} = useUIStateContext();
|
||||
const { handleLinkClick } = useFileNavigation();
|
||||
const { type: themeType } = useTheme();
|
||||
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) => {
|
||||
if (!isImageFile(selectedFile) || value === 'preview') {
|
||||
setActiveTab(value);
|
||||
}
|
||||
setActiveTab(value);
|
||||
};
|
||||
|
||||
const handlePull = async () => {
|
||||
@@ -68,64 +59,14 @@ const MainContent = ({
|
||||
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 = () => {
|
||||
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 = () => {
|
||||
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 = () => {
|
||||
if (!selectedFile) return null;
|
||||
const pathParts = selectedFile.split('/');
|
||||
@@ -149,19 +90,12 @@ const MainContent = ({
|
||||
<Grid xs={24} sm={6} md={5} lg={4} height="100%" className="sidebar">
|
||||
<div className="file-tree-container">
|
||||
<FileActions
|
||||
selectedFile={selectedFile}
|
||||
gitEnabled={settings.gitEnabled}
|
||||
gitAutoCommit={settings.gitAutoCommit}
|
||||
onPull={handlePull}
|
||||
onCommitAndPush={handleCommitAndPush}
|
||||
onCreateFile={handleCreateFile}
|
||||
onDeleteFile={handleDeleteFile}
|
||||
/>
|
||||
<FileTree
|
||||
files={files}
|
||||
onFileSelect={onFileSelect}
|
||||
selectedFile={selectedFile}
|
||||
/>
|
||||
<FileTree files={files} />
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid
|
||||
@@ -175,46 +109,22 @@ const MainContent = ({
|
||||
<div className="content-header">
|
||||
{renderBreadcrumbs()}
|
||||
<Tabs value={activeTab} onChange={handleTabChange}>
|
||||
<Tabs.Item
|
||||
label={<Code />}
|
||||
value="source"
|
||||
disabled={isImageFile(selectedFile)}
|
||||
/>
|
||||
<Tabs.Item label={<Code />} value="source" />
|
||||
<Tabs.Item label={<Eye />} value="preview" />
|
||||
</Tabs>
|
||||
</div>
|
||||
<div className="content-body">
|
||||
<ContentView
|
||||
activeTab={activeTab}
|
||||
content={content}
|
||||
selectedFile={selectedFile}
|
||||
onContentChange={onContentChange}
|
||||
onSave={onSave}
|
||||
themeType={themeType}
|
||||
onLinkClick={onLinkClick}
|
||||
lookupFileByName={lookupFileByName}
|
||||
onLinkClick={handleLinkClick}
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid.Container>
|
||||
<CreateFileModal
|
||||
visible={newFileModalVisible}
|
||||
onClose={() => setNewFileModalVisible(false)}
|
||||
onSubmit={handleNewFileSubmit}
|
||||
fileName={newFileName}
|
||||
setFileName={setNewFileName}
|
||||
/>
|
||||
<DeleteFileModal
|
||||
visible={deleteFileModalVisible}
|
||||
onClose={() => setDeleteFileModalVisible(false)}
|
||||
onConfirm={confirmDeleteFile}
|
||||
fileName={selectedFile}
|
||||
/>
|
||||
<CommitMessageModal
|
||||
visible={commitMessageModalVisible}
|
||||
onClose={() => setCommitMessageModalVisible(false)}
|
||||
onSubmit={confirmCommitAndPush}
|
||||
/>
|
||||
<CreateFileModal visible={newFileModalVisible} />
|
||||
<DeleteFileModal visible={deleteFileModalVisible} />
|
||||
<CommitMessageModal visible={commitMessageModalVisible} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,16 +1,27 @@
|
||||
import React, { useState } from 'react';
|
||||
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 { handleCommitAndPush } = useGitOperationsContext();
|
||||
const { commitMessageModalVisible, setCommitMessageModalVisible } =
|
||||
useUIStateContext();
|
||||
|
||||
const handleSubmit = () => {
|
||||
onSubmit(message);
|
||||
setMessage('');
|
||||
const handleSubmit = async () => {
|
||||
if (message) {
|
||||
await handleCommitAndPush(message);
|
||||
setMessage('');
|
||||
setCommitMessageModalVisible(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal visible={visible} onClose={onClose}>
|
||||
<Modal
|
||||
visible={commitMessageModalVisible}
|
||||
onClose={() => setCommitMessageModalVisible(false)}
|
||||
>
|
||||
<Modal.Title>Enter Commit Message</Modal.Title>
|
||||
<Modal.Content>
|
||||
<Input
|
||||
@@ -20,7 +31,7 @@ const CommitMessageModal = ({ visible, onClose, onSubmit }) => {
|
||||
onChange={(e) => setMessage(e.target.value)}
|
||||
/>
|
||||
</Modal.Content>
|
||||
<Modal.Action passive onClick={onClose}>
|
||||
<Modal.Action passive onClick={() => setCommitMessageModalVisible(false)}>
|
||||
Cancel
|
||||
</Modal.Action>
|
||||
<Modal.Action onClick={handleSubmit}>Commit</Modal.Action>
|
||||
|
||||
@@ -1,15 +1,26 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
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 (
|
||||
<Modal visible={visible} onClose={onClose}>
|
||||
<Modal
|
||||
visible={newFileModalVisible}
|
||||
onClose={() => setNewFileModalVisible(false)}
|
||||
>
|
||||
<Modal.Title>Create New File</Modal.Title>
|
||||
<Modal.Content>
|
||||
<Input
|
||||
@@ -19,10 +30,10 @@ const CreateFileModal = ({
|
||||
onChange={(e) => setFileName(e.target.value)}
|
||||
/>
|
||||
</Modal.Content>
|
||||
<Modal.Action passive onClick={onClose}>
|
||||
<Modal.Action passive onClick={() => setNewFileModalVisible(false)}>
|
||||
Cancel
|
||||
</Modal.Action>
|
||||
<Modal.Action onClick={onSubmit}>Create</Modal.Action>
|
||||
<Modal.Action onClick={handleSubmit}>Create</Modal.Action>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,17 +1,31 @@
|
||||
import React from 'react';
|
||||
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 (
|
||||
<Modal visible={visible} onClose={onClose}>
|
||||
<Modal
|
||||
visible={deleteFileModalVisible}
|
||||
onClose={() => setDeleteFileModalVisible(false)}
|
||||
>
|
||||
<Modal.Title>Delete File</Modal.Title>
|
||||
<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.Action passive onClick={onClose}>
|
||||
<Modal.Action passive onClick={() => setDeleteFileModalVisible(false)}>
|
||||
Cancel
|
||||
</Modal.Action>
|
||||
<Modal.Action onClick={onConfirm}>Delete</Modal.Action>
|
||||
<Modal.Action onClick={handleConfirm}>Delete</Modal.Action>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
24
frontend/src/contexts/FileContentContext.js
Normal file
24
frontend/src/contexts/FileContentContext.js
Normal 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;
|
||||
};
|
||||
24
frontend/src/contexts/FileListContext.js
Normal file
24
frontend/src/contexts/FileListContext.js
Normal 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;
|
||||
};
|
||||
24
frontend/src/contexts/GitOperationsContext.js
Normal file
24
frontend/src/contexts/GitOperationsContext.js
Normal 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;
|
||||
};
|
||||
34
frontend/src/contexts/UIStateContext.js
Normal file
34
frontend/src/contexts/UIStateContext.js
Normal 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;
|
||||
};
|
||||
@@ -1,6 +1,8 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { fetchFileContent, saveFileContent } from '../services/api';
|
||||
import { fetchFileContent, saveFileContent, deleteFile } from '../services/api';
|
||||
import { isImageFile } from '../utils/fileHelpers';
|
||||
import { useToasts } from '@geist-ui/core';
|
||||
import { useFileListContext } from '../contexts/FileListContext';
|
||||
|
||||
const DEFAULT_FILE = {
|
||||
name: 'New File.md',
|
||||
@@ -14,16 +16,12 @@ export const useFileContent = () => {
|
||||
const [isNewFile, setIsNewFile] = useState(true);
|
||||
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
const { setToast } = useToasts();
|
||||
const { loadFileList } = useFileListContext();
|
||||
|
||||
const handleFileSelect = useCallback(
|
||||
async (filePath) => {
|
||||
if (hasUnsavedChanges) {
|
||||
const confirmSwitch = window.confirm(
|
||||
'You have unsaved changes. Are you sure you want to switch files?'
|
||||
);
|
||||
if (!confirmSwitch) return;
|
||||
}
|
||||
|
||||
console.log('handleFileSelect', filePath);
|
||||
try {
|
||||
if (filePath === DEFAULT_FILE.path) {
|
||||
setContent(DEFAULT_FILE.content);
|
||||
@@ -67,13 +65,38 @@ export const useFileContent = () => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const createNewFile = useCallback(() => {
|
||||
setContent(DEFAULT_FILE.content);
|
||||
setSelectedFile(DEFAULT_FILE.path);
|
||||
setIsNewFile(true);
|
||||
setHasUnsavedChanges(false);
|
||||
setError(null);
|
||||
}, []);
|
||||
const handleCreateNewFile = useCallback(
|
||||
async (fileName, initialContent = '') => {
|
||||
try {
|
||||
await saveFileContent(fileName, initialContent);
|
||||
setToast({ text: 'New file created successfully', type: 'success' });
|
||||
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 {
|
||||
content,
|
||||
@@ -84,7 +107,8 @@ export const useFileContent = () => {
|
||||
handleFileSelect,
|
||||
handleContentChange,
|
||||
handleSave,
|
||||
createNewFile,
|
||||
handleCreateNewFile,
|
||||
handleDeleteFile,
|
||||
DEFAULT_FILE,
|
||||
};
|
||||
};
|
||||
|
||||
39
frontend/src/hooks/useFileNavigation.js
Normal file
39
frontend/src/hooks/useFileNavigation.js
Normal 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 };
|
||||
};
|
||||
Reference in New Issue
Block a user