This commit is contained in:
seb
2026-10-07 13:23:44 +02:00
parent 6c0b1503e8
commit 7045fe5e69
5 changed files with 140 additions and 202 deletions

View File

@@ -2,7 +2,6 @@ import React, { PureComponent } from 'react';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import IconButton from '@mui/material/IconButton';
import Tooltip from '@mui/material/Tooltip';
import FolderOpenRoundedIcon from '@mui/icons-material/FolderOpenRounded';
import CloseRoundedIcon from '@mui/icons-material/CloseRounded';
@@ -61,13 +60,20 @@ export default class VfsEditorPanel extends PureComponent {
this.unsub?.();
}
handleMentionInChat = (file) => {
handleMentionInChat = (file, lineInfo = null) => {
if (!file) return;
if (this.props.onInsertToChat) {
const isImg = file.isImage;
const tag = isImg
? `![${file.name}](${file.name})`
: `[${file.name}](${file.name})`;
let tag = '';
if (lineInfo?.startLine) {
const lineRef =
lineInfo.startLine === lineInfo.endLine
? `L${lineInfo.startLine}`
: `L${lineInfo.startLine}-L${lineInfo.endLine}`;
tag = `[${file.name}#${lineRef}](${file.name}#${lineRef})`;
} else {
tag = isImg ? `![${file.name}](${file.name})` : `[${file.name}](${file.name})`;
}
this.props.onInsertToChat(tag);
}
};
@@ -116,7 +122,7 @@ export default class VfsEditorPanel extends PureComponent {
onSave={() => vfsAutomat.actions.saveCurrentFile()}
onClose={() => vfsAutomat.actions.selectFile(null)}
onDownload={() => vfsAutomat.actions.downloadFile(selectedFileName)}
onMentionInChat={() => this.handleMentionInChat(selectedFile)}
onMentionInChat={(lineInfo) => this.handleMentionInChat(selectedFile, lineInfo)}
/>
) : selectedFileName && !selectedFile?.isText ? (
/* Single Column Mode: Preview (Image, PDF, Binary) */
@@ -147,19 +153,17 @@ export default class VfsEditorPanel extends PureComponent {
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<FolderOpenRoundedIcon color="primary" sx={{ fontSize: 22 }} />
<Typography variant="subtitle1" fontWeight={700}>
VFS Dateisystem
Dateien
</Typography>
</Box>
<Tooltip title="VFS-Dateisystem schließen">
<IconButton
size="small"
onClick={() => vfsAutomat.actions.closeVfs()}
aria-label="VFS schließen"
>
<CloseRoundedIcon fontSize="small" />
</IconButton>
</Tooltip>
<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' }}>

View File

@@ -2,18 +2,14 @@ import React, { PureComponent } from 'react';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import IconButton from '@mui/material/IconButton';
import Button from '@mui/material/Button';
import InputBase from '@mui/material/InputBase';
import Tooltip from '@mui/material/Tooltip';
import CircularProgress from '@mui/material/CircularProgress';
import Chip from '@mui/material/Chip';
import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem';
import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from '@mui/material/ListItemText';
import SearchRoundedIcon from '@mui/icons-material/SearchRounded';
import ClearRoundedIcon from '@mui/icons-material/ClearRounded';
import RefreshRoundedIcon from '@mui/icons-material/RefreshRounded';
import NoteAddRoundedIcon from '@mui/icons-material/NoteAddRounded';
import CloudUploadRoundedIcon from '@mui/icons-material/CloudUploadRounded';
import DownloadRoundedIcon from '@mui/icons-material/DownloadRounded';
@@ -28,7 +24,7 @@ import KeyboardArrowRightRoundedIcon from '@mui/icons-material/KeyboardArrowRigh
import ContentCopyRoundedIcon from '@mui/icons-material/ContentCopyRounded';
import ChatBubbleOutlineRoundedIcon from '@mui/icons-material/ChatBubbleOutlineRounded';
import VfsFileIcon, { getCategoryBadge } from './VfsFileIcon';
import VfsFileIcon from './VfsFileIcon';
import { formatVfsSize, formatVfsDate } from './vfsUtils';
/**
@@ -336,7 +332,6 @@ export default class VfsFileExplorer extends PureComponent {
renderFileNode = (file, depth) => {
const { selectedFileName, onSelectFile } = this.props;
const isSelected = file.path === selectedFileName;
const badge = getCategoryBadge(file.path, file.fileData.mime);
return (
<Box
@@ -375,29 +370,14 @@ export default class VfsFileExplorer extends PureComponent {
/>
<Box sx={{ minWidth: 0, flexGrow: 1, pr: 1 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75 }}>
<Typography
variant="body2"
fontWeight={isSelected ? 600 : 500}
noWrap
sx={{ fontFamily: 'monospace', fontSize: '0.82rem' }}
>
{file.name}
</Typography>
<Chip
label={badge.label}
size="small"
sx={{
height: 17,
fontSize: '0.62rem',
fontWeight: 600,
borderRadius: 1,
bgcolor: (theme) =>
theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.06)',
color: badge.color,
}}
/>
</Box>
<Typography
variant="body2"
fontWeight={isSelected ? 600 : 500}
noWrap
sx={{ fontFamily: 'monospace', fontSize: '0.82rem' }}
>
{file.name}
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.25, mt: 0.15 }}>
<Typography variant="caption" color="text.secondary" sx={{ fontSize: '0.7rem' }}>
@@ -417,24 +397,14 @@ export default class VfsFileExplorer extends PureComponent {
sx={{
display: 'flex',
alignItems: 'center',
gap: 0.2,
opacity: isSelected ? 1 : 0.7,
'&:hover': { opacity: 1 },
}}
>
<Tooltip title="Herunterladen">
<IconButton
size="small"
onClick={() => this.props.onDownload(file.path)}
sx={{ p: 0.5 }}
>
<DownloadRoundedIcon sx={{ fontSize: 16 }} />
</IconButton>
</Tooltip>
<IconButton
size="small"
onClick={(e) => this.handleOpenMenu(e, file.fileData)}
aria-label="Weitere Optionen"
sx={{ p: 0.5 }}
>
<MoreVertRoundedIcon sx={{ fontSize: 16 }} />
@@ -561,62 +531,22 @@ export default class VfsFileExplorer extends PureComponent {
</Box>
)}
{/* Explorer Header / Actions */}
{/* Explorer Header / Actions Row */}
<Box
sx={{
p: 1.5,
p: 1.25,
borderBottom: 1,
borderColor: 'divider',
display: 'flex',
flexDirection: 'column',
gap: 1.25,
alignItems: 'center',
gap: 0.5,
flexShrink: 0,
}}
>
{/* Top Bar with Action Buttons */}
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75 }}>
{loading && <CircularProgress size={16} thickness={4} />}
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, ml: 'auto' }}>
<Tooltip title="Aktualisieren">
<span>
<IconButton size="small" onClick={onRefresh} disabled={loading} sx={{ p: 0.6 }}>
<RefreshRoundedIcon fontSize="small" />
</IconButton>
</span>
</Tooltip>
<Tooltip title="Datei(en) hochladen">
<Button
size="small"
variant="outlined"
startIcon={<CloudUploadRoundedIcon />}
onClick={this.handleTriggerUpload}
sx={{ borderRadius: 2, py: 0.3, px: 1, fontSize: '0.75rem' }}
>
Upload
</Button>
</Tooltip>
<Tooltip title="Neue Datei anlegen">
<Button
size="small"
variant="contained"
startIcon={<NoteAddRoundedIcon />}
onClick={onOpenNewDialog}
sx={{ borderRadius: 2, py: 0.3, px: 1, fontSize: '0.75rem' }}
>
Neu
</Button>
</Tooltip>
</Box>
</Box>
{/* Search Filter Bar */}
<Box
sx={(theme) => ({
flexGrow: 1,
display: 'flex',
alignItems: 'center',
px: 1,
@@ -638,12 +568,33 @@ export default class VfsFileExplorer extends PureComponent {
onChange={(e) => onSearchChange(e.target.value)}
sx={{ fontSize: '0.82rem' }}
/>
{loading && <CircularProgress size={14} thickness={4} sx={{ mr: 0.5 }} />}
{searchQuery && (
<IconButton size="small" onClick={() => onSearchChange('')} sx={{ p: 0.2 }}>
<ClearRoundedIcon sx={{ fontSize: 16 }} />
</IconButton>
)}
</Box>
{/* Upload Icon Button (no word) */}
<IconButton
size="small"
onClick={this.handleTriggerUpload}
aria-label="Datei(en) hochladen"
sx={{ p: 0.75 }}
>
<CloudUploadRoundedIcon fontSize="small" />
</IconButton>
{/* New File Icon Button (no word) */}
<IconButton
size="small"
onClick={onOpenNewDialog}
aria-label="Neue Datei anlegen"
sx={{ p: 0.75 }}
>
<NoteAddRoundedIcon fontSize="small" />
</IconButton>
</Box>
{/* Scrollable File List */}

View File

@@ -3,15 +3,13 @@ import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import Tooltip from '@mui/material/Tooltip';
import Chip from '@mui/material/Chip';
import OpenInNewRoundedIcon from '@mui/icons-material/OpenInNewRounded';
import DownloadRoundedIcon from '@mui/icons-material/DownloadRounded';
import CloseRoundedIcon from '@mui/icons-material/CloseRounded';
import ArrowBackRoundedIcon from '@mui/icons-material/ArrowBackRounded';
import ChatBubbleOutlineRoundedIcon from '@mui/icons-material/ChatBubbleOutlineRounded';
import VfsFileIcon, { getCategoryBadge } from './VfsFileIcon';
import VfsFileIcon from './VfsFileIcon';
import { formatVfsSize, formatVfsDate } from './vfsUtils';
export default class VfsFilePreview extends PureComponent {
@@ -128,7 +126,6 @@ export default class VfsFilePreview extends PureComponent {
render() {
const { fileName, file, onClose, onDownload, onMentionInChat } = this.props;
const badge = getCategoryBadge(fileName, file?.mime);
const vfsUrl = `/api/vfs/${encodeURIComponent(fileName).replace(/%2F/g, '/')}?raw=1`;
return (
@@ -149,11 +146,9 @@ export default class VfsFilePreview extends PureComponent {
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, minWidth: 0, mr: 1 }}>
<Tooltip title="Zurück zur Dateiliste">
<IconButton size="small" onClick={onClose} sx={{ p: 0.5, mr: 0.25 }}>
<ArrowBackRoundedIcon fontSize="small" />
</IconButton>
</Tooltip>
<IconButton size="small" onClick={onClose} aria-label="Zurück zur Dateiliste" sx={{ p: 0.5, mr: 0.25 }}>
<ArrowBackRoundedIcon fontSize="small" />
</IconButton>
<VfsFileIcon fileName={fileName} mime={file?.mime} fontSize="small" />
<Typography
variant="body2"
@@ -163,41 +158,18 @@ export default class VfsFilePreview extends PureComponent {
>
{fileName}
</Typography>
<Chip
label={badge.label}
size="small"
sx={{
height: 20,
fontSize: '0.68rem',
fontWeight: 600,
borderRadius: 1,
bgcolor: (theme) =>
theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.06)',
color: badge.color,
}}
/>
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, flexShrink: 0 }}>
{onMentionInChat && (
<Tooltip title="In Chat erwähnen">
<IconButton size="small" onClick={onMentionInChat} sx={{ p: 0.6 }}>
<ChatBubbleOutlineRoundedIcon fontSize="small" color="primary" />
</IconButton>
</Tooltip>
<IconButton size="small" onClick={onMentionInChat} aria-label="In Chat erwähnen" sx={{ p: 0.6 }}>
<ChatBubbleOutlineRoundedIcon fontSize="small" color="primary" />
</IconButton>
)}
<Tooltip title="Herunterladen">
<IconButton size="small" onClick={onDownload} sx={{ p: 0.6 }}>
<DownloadRoundedIcon fontSize="small" />
</IconButton>
</Tooltip>
<Tooltip title="Vorschau schließen">
<IconButton size="small" onClick={onClose} sx={{ p: 0.6, ml: 0.5 }}>
<CloseRoundedIcon fontSize="small" />
</IconButton>
</Tooltip>
<IconButton size="small" onClick={onClose} aria-label="Vorschau schließen" sx={{ p: 0.6, ml: 0.5 }}>
<CloseRoundedIcon fontSize="small" />
</IconButton>
</Box>
</Box>

View File

@@ -3,16 +3,13 @@ import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import IconButton from '@mui/material/IconButton';
import Button from '@mui/material/Button';
import Tooltip from '@mui/material/Tooltip';
import CircularProgress from '@mui/material/CircularProgress';
import Chip from '@mui/material/Chip';
import SaveRoundedIcon from '@mui/icons-material/SaveRounded';
import UndoRoundedIcon from '@mui/icons-material/UndoRounded';
import DownloadRoundedIcon from '@mui/icons-material/DownloadRounded';
import CloseRoundedIcon from '@mui/icons-material/CloseRounded';
import ArrowBackRoundedIcon from '@mui/icons-material/ArrowBackRounded';
import ChatBubbleOutlineRoundedIcon from '@mui/icons-material/ChatBubbleOutlineRounded';
import WrapTextRoundedIcon from '@mui/icons-material/WrapTextRounded';
import Alert from '@mui/material/Alert';
import VfsFileIcon from './VfsFileIcon';
@@ -69,6 +66,37 @@ export default class VfsTextEditor extends PureComponent {
}
};
handleMentionInChat = () => {
const ta = this.textareaRef.current;
let lineInfo = null;
if (ta) {
const start = ta.selectionStart;
const end = ta.selectionEnd;
if (typeof start === 'number' && typeof end === 'number' && end > start) {
const content = this.props.content || '';
const beforeStart = content.substring(0, start);
const startLine = beforeStart.split('\n').length;
let effectiveEnd = end;
if (effectiveEnd > start && content[effectiveEnd - 1] === '\n') {
effectiveEnd -= 1;
}
const beforeEnd = content.substring(0, effectiveEnd);
const endLine = Math.max(startLine, beforeEnd.split('\n').length);
lineInfo = {
startLine,
endLine,
selectedText: content.substring(start, end),
};
}
}
if (this.props.onMentionInChat) {
this.props.onMentionInChat(lineInfo);
}
};
handleKeyDown = (e) => {
// Ctrl+S or Cmd+S -> Save
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 's') {
@@ -159,11 +187,9 @@ export default class VfsTextEditor extends PureComponent {
>
{/* File Name & Status */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, minWidth: 0, mr: 1 }}>
<Tooltip title="Zurück zur Dateiliste">
<IconButton size="small" onClick={onClose} sx={{ p: 0.5, mr: 0.25 }}>
<ArrowBackRoundedIcon fontSize="small" />
</IconButton>
</Tooltip>
<IconButton size="small" onClick={onClose} aria-label="Zurück zur Dateiliste" sx={{ p: 0.5, mr: 0.25 }}>
<ArrowBackRoundedIcon fontSize="small" />
</IconButton>
<VfsFileIcon fileName={fileName} mime={file?.mime} fontSize="small" />
<Typography
variant="body2"
@@ -174,60 +200,29 @@ export default class VfsTextEditor extends PureComponent {
{fileName}
</Typography>
{isDirty ? (
{isDirty && (
<Chip
label="Ungespeichert"
size="small"
color="warning"
sx={{ height: 20, fontSize: '0.68rem', fontWeight: 600, borderRadius: 1 }}
/>
) : (
<Chip
label="Gespeichert"
size="small"
variant="outlined"
sx={{ height: 20, fontSize: '0.68rem', borderRadius: 1, opacity: 0.7 }}
/>
)}
</Box>
{/* Action Buttons */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, flexShrink: 0 }}>
{onMentionInChat && (
<Tooltip title="In Chat erwähnen">
<IconButton size="small" onClick={onMentionInChat} sx={{ p: 0.6 }}>
<ChatBubbleOutlineRoundedIcon fontSize="small" color="primary" />
</IconButton>
</Tooltip>
)}
<Tooltip title="Herunterladen">
<IconButton size="small" onClick={onDownload} sx={{ p: 0.6 }}>
<DownloadRoundedIcon fontSize="small" />
<IconButton size="small" onClick={this.handleMentionInChat} aria-label="In Chat erwähnen" sx={{ p: 0.6 }}>
<ChatBubbleOutlineRoundedIcon fontSize="small" color="primary" />
</IconButton>
</Tooltip>
)}
{isDirty && (
<Tooltip title="Änderungen verwerfen">
<IconButton size="small" onClick={this.handleRevert} sx={{ p: 0.6, color: 'text.secondary' }}>
<UndoRoundedIcon fontSize="small" />
</IconButton>
</Tooltip>
)}
<Tooltip title={this.state.wrapLines ? 'Zeilenumbruch ausschalten' : 'Zeilenumbruch einschalten'}>
<IconButton
size="small"
onClick={this.toggleWrap}
color={this.state.wrapLines ? 'primary' : 'default'}
sx={{
p: 0.6,
bgcolor: this.state.wrapLines ? 'action.selected' : 'transparent',
}}
>
<WrapTextRoundedIcon fontSize="small" />
<IconButton size="small" onClick={this.handleRevert} aria-label="Änderungen verwerfen" sx={{ p: 0.6, color: 'text.secondary' }}>
<UndoRoundedIcon fontSize="small" />
</IconButton>
</Tooltip>
)}
<Button
size="small"
@@ -247,11 +242,9 @@ export default class VfsTextEditor extends PureComponent {
{isSaving ? 'Speichern…' : 'Speichern (Strg+S)'}
</Button>
<Tooltip title="Editor schließen">
<IconButton size="small" onClick={onClose} sx={{ p: 0.6, ml: 0.5 }}>
<CloseRoundedIcon fontSize="small" />
</IconButton>
</Tooltip>
<IconButton size="small" onClick={onClose} aria-label="Editor schließen" sx={{ p: 0.6, ml: 0.5 }}>
<CloseRoundedIcon fontSize="small" />
</IconButton>
</Box>
</Box>