Files
dashfront/src/components/vfs/VfsEditorPanel.jsx
2026-10-07 21:28:26 +02:00

210 lines
7.3 KiB
JavaScript

import React, { PureComponent } from 'react';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import IconButton from '@mui/material/IconButton';
import FolderOpenRoundedIcon from '@mui/icons-material/FolderOpenRounded';
import CloseRoundedIcon from '@mui/icons-material/CloseRounded';
import vfsAutomat from '../../automats/vfsAutomat';
import VfsFileExplorer from './VfsFileExplorer';
import VfsTextEditor from './VfsTextEditor';
import VfsFilePreview from './VfsFilePreview';
import VfsNewFileDialog from './VfsNewFileDialog';
export default class VfsEditorPanel extends PureComponent {
constructor(props) {
super(props);
const s = vfsAutomat.state;
this.state = {
files: s.files,
loadingFiles: s.loadingFiles,
loadError: s.loadError,
selectedFileName: s.selectedFileName,
selectedFile: s.selectedFile,
fileContent: s.fileContent,
originalContent: s.originalContent,
isDirty: s.isDirty,
loadingContent: s.loadingContent,
isSaving: s.isSaving,
saveError: s.saveError,
isUploading: s.isUploading,
searchQuery: s.searchQuery,
newFileDialogOpen: s.newFileDialogOpen,
newFileDefaultFolder: s.newFileDefaultFolder,
};
}
componentDidMount() {
this.unsub = vfsAutomat.subscribe(this, (state) => ({
files: state.files,
loadingFiles: state.loadingFiles,
loadError: state.loadError,
selectedFileName: state.selectedFileName,
selectedFile: state.selectedFile,
fileContent: state.fileContent,
originalContent: state.originalContent,
isDirty: state.isDirty,
loadingContent: state.loadingContent,
isSaving: state.isSaving,
saveError: state.saveError,
isUploading: state.isUploading,
searchQuery: state.searchQuery,
newFileDialogOpen: state.newFileDialogOpen,
newFileDefaultFolder: state.newFileDefaultFolder,
}));
if (this.state.files.length === 0 && !this.state.loadingFiles) {
void vfsAutomat.actions.loadFiles();
}
}
componentWillUnmount() {
this.unsub?.();
}
handleMentionInChat = (file, lineInfo = null) => {
if (!file) return;
if (this.props.onMentionReference) {
this.props.onMentionReference({
fileName: file.name,
path: file.name,
startLine: lineInfo?.startLine || null,
endLine: lineInfo?.endLine || null,
selectedText: lineInfo?.selectedText || '',
isImage: file.isImage || false,
});
return;
}
if (this.props.onInsertToChat) {
const isImg = file.isImage;
const tag = lineInfo?.startLine
? `[${file.name}#${lineInfo.startLine === lineInfo.endLine ? `L${lineInfo.startLine}` : `L${lineInfo.startLine}-L${lineInfo.endLine}`}](${file.name}#${lineInfo.startLine === lineInfo.endLine ? `L${lineInfo.startLine}` : `L${lineInfo.startLine}-L${lineInfo.endLine}`})`
: isImg
? `![${file.name}](${file.name})`
: `[${file.name}](${file.name})`;
this.props.onInsertToChat(tag);
}
};
render() {
const {
files,
loadingFiles,
selectedFileName,
selectedFile,
fileContent,
originalContent,
isDirty,
loadingContent,
isSaving,
saveError,
searchQuery,
newFileDialogOpen,
} = this.state;
return (
<Box
sx={{
display: 'flex',
flexDirection: 'column',
height: '100%',
width: '100%',
overflow: 'hidden',
bgcolor: 'background.paper',
borderRight: 1,
borderColor: 'divider',
}}
>
{selectedFileName && selectedFile?.isText ? (
/* Single Column Mode: Text Editor */
<VfsTextEditor
fileName={selectedFileName}
file={selectedFile}
content={fileContent}
originalContent={originalContent}
isDirty={isDirty}
isSaving={isSaving}
saveError={saveError}
loadingContent={loadingContent}
onChange={(c) => vfsAutomat.actions.updateContent(c)}
onSave={() => vfsAutomat.actions.saveCurrentFile()}
onClose={() => vfsAutomat.actions.selectFile(null)}
onDownload={() => vfsAutomat.actions.downloadFile(selectedFileName)}
onMentionInChat={(lineInfo) => this.handleMentionInChat(selectedFile, lineInfo)}
/>
) : selectedFileName && !selectedFile?.isText ? (
/* Single Column Mode: Preview (Image, PDF, Binary) */
<VfsFilePreview
fileName={selectedFileName}
file={selectedFile}
onClose={() => vfsAutomat.actions.selectFile(null)}
onDownload={() => vfsAutomat.actions.downloadFile(selectedFileName)}
onMentionInChat={() => this.handleMentionInChat(selectedFile)}
/>
) : (
/* Single Column Mode: File Explorer */
<>
<Box
sx={{
p: 1.5,
px: 2,
borderBottom: 1,
borderColor: 'divider',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
bgcolor: 'background.paper',
minHeight: 52,
flexShrink: 0,
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<FolderOpenRoundedIcon color="primary" sx={{ fontSize: 22 }} />
<Typography variant="subtitle1" fontWeight={700}>
Dateien
</Typography>
</Box>
<IconButton
size="small"
onClick={() => vfsAutomat.actions.closeVfs()}
aria-label="VFS schließen"
>
<CloseRoundedIcon fontSize="small" />
</IconButton>
</Box>
<Box sx={{ flexGrow: 1, minHeight: 0, overflow: 'hidden' }}>
<VfsFileExplorer
files={files}
loading={loadingFiles}
selectedFileName={selectedFileName}
searchQuery={searchQuery}
onSelectFile={(name) => vfsAutomat.actions.selectFile(name)}
onRefresh={() => vfsAutomat.actions.loadFiles()}
onOpenNewDialog={(parentDir) => vfsAutomat.actions.setNewFileDialogOpen(true, parentDir)}
onSearchChange={(q) => vfsAutomat.actions.setSearchQuery(q)}
onDownload={(name) => vfsAutomat.actions.downloadFile(name)}
onDelete={(name) => vfsAutomat.actions.deletePath(name)}
onRename={(oldPath, newPath) => vfsAutomat.actions.renameFile(oldPath, newPath)}
onMkdir={(dirPath) => vfsAutomat.actions.mkdir(dirPath)}
onUpload={(filesToUpload) => vfsAutomat.actions.uploadFiles(filesToUpload)}
onMentionInChat={this.handleMentionInChat}
/>
</Box>
</>
)}
{/* New File Dialog */}
<VfsNewFileDialog
open={newFileDialogOpen}
defaultFolder={this.state.newFileDefaultFolder}
onClose={() => vfsAutomat.actions.setNewFileDialogOpen(false)}
onCreate={(name) => vfsAutomat.actions.createFile(name)}
isCreating={isSaving}
/>
</Box>
);
}
}