u
This commit is contained in:
680
src/components/chat/ChatDrawer.jsx
Normal file
680
src/components/chat/ChatDrawer.jsx
Normal file
@@ -0,0 +1,680 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import Drawer from '@mui/material/Drawer';
|
||||
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 TextField from '@mui/material/TextField';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import Select from '@mui/material/Select';
|
||||
import FormControl from '@mui/material/FormControl';
|
||||
import InputLabel from '@mui/material/InputLabel';
|
||||
import Chip from '@mui/material/Chip';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
import CloseRoundedIcon from '@mui/icons-material/CloseRounded';
|
||||
import SendRoundedIcon from '@mui/icons-material/SendRounded';
|
||||
import AddRoundedIcon from '@mui/icons-material/AddRounded';
|
||||
import AutoAwesomeRoundedIcon from '@mui/icons-material/AutoAwesomeRounded';
|
||||
import DeleteOutlineRoundedIcon from '@mui/icons-material/DeleteOutlineRounded';
|
||||
|
||||
import chatAutomat, { selectPendingAskUser } from '../../automats/chatAutomat';
|
||||
import userSettingsAutomat from '../../automats/userSettingsAutomat';
|
||||
import ChatFlowList from './ChatFlowList';
|
||||
import AskUserPrompt from './AskUserPrompt';
|
||||
|
||||
const DEFAULT_DRAWER_WIDTH = 460;
|
||||
const MIN_DRAWER_WIDTH = 320;
|
||||
|
||||
export default class ChatDrawer extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const s = chatAutomat.state;
|
||||
const settings = userSettingsAutomat.state;
|
||||
this.state = {
|
||||
isOpen: s.isOpen,
|
||||
conversations: s.conversations,
|
||||
selectedConversationId: s.selectedConversationId,
|
||||
draftConversation: s.draftConversation,
|
||||
conversationState: s.conversationState,
|
||||
provider: s.provider,
|
||||
model: s.model,
|
||||
messages: s.messages,
|
||||
loadingMessages: s.loadingMessages,
|
||||
streamingText: s.streamingText,
|
||||
isSending: s.isSending,
|
||||
loadingConversations: s.loadingConversations,
|
||||
sseStatus: s.sseStatus,
|
||||
sendError: s.sendError,
|
||||
inputText: '',
|
||||
drawerWidth: settings?.chatDrawerWidth || DEFAULT_DRAWER_WIDTH,
|
||||
isResizing: false,
|
||||
};
|
||||
this.inputRef = React.createRef();
|
||||
this.focusTimeout = null;
|
||||
}
|
||||
|
||||
focusInput = (delay = 50) => {
|
||||
if (this.focusTimeout) clearTimeout(this.focusTimeout);
|
||||
this.focusTimeout = setTimeout(() => {
|
||||
this.inputRef.current?.focus?.();
|
||||
}, delay);
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
if (this.state.isOpen) {
|
||||
this.focusInput(150);
|
||||
}
|
||||
this.unsub = chatAutomat.subscribe(this, (state) => {
|
||||
const wasOpen = this.state.isOpen;
|
||||
const willOpen = state.isOpen;
|
||||
if (!wasOpen && willOpen) {
|
||||
this.focusInput(150);
|
||||
}
|
||||
return {
|
||||
isOpen: state.isOpen,
|
||||
conversations: state.conversations,
|
||||
selectedConversationId: state.selectedConversationId,
|
||||
draftConversation: state.draftConversation,
|
||||
conversationState: state.conversationState,
|
||||
provider: state.provider,
|
||||
model: state.model,
|
||||
messages: state.messages,
|
||||
loadingMessages: state.loadingMessages,
|
||||
streamingText: state.streamingText,
|
||||
isSending: state.isSending,
|
||||
loadingConversations: state.loadingConversations,
|
||||
sseStatus: state.sseStatus,
|
||||
sendError: state.sendError,
|
||||
};
|
||||
});
|
||||
|
||||
this.unsubSettings = userSettingsAutomat.subscribe(this, (state) => {
|
||||
if (!this.state.isResizing) {
|
||||
return { drawerWidth: state.chatDrawerWidth || DEFAULT_DRAWER_WIDTH };
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.focusTimeout) clearTimeout(this.focusTimeout);
|
||||
this.unsub?.();
|
||||
this.unsubSettings?.();
|
||||
this.cleanupResizeListeners();
|
||||
}
|
||||
|
||||
handleClose = () => {
|
||||
chatAutomat.actions.closeChat();
|
||||
};
|
||||
|
||||
handleResizeStart = (e) => {
|
||||
if (e.button !== undefined && e.button !== 0) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const startX = e.clientX ?? (e.touches && e.touches[0]?.clientX);
|
||||
if (startX === undefined) return;
|
||||
|
||||
const startWidth = this.state.drawerWidth;
|
||||
this.setState({ isResizing: true });
|
||||
|
||||
document.body.style.cursor = 'col-resize';
|
||||
document.body.style.userSelect = 'none';
|
||||
|
||||
let currentWidth = startWidth;
|
||||
|
||||
const onMove = (moveEvt) => {
|
||||
moveEvt.preventDefault();
|
||||
const clientX = moveEvt.clientX ?? (moveEvt.touches && moveEvt.touches[0]?.clientX);
|
||||
if (clientX === undefined) return;
|
||||
|
||||
const deltaX = startX - clientX;
|
||||
const maxWidth = Math.min(window.innerWidth - 40, 1400);
|
||||
const newWidth = Math.max(MIN_DRAWER_WIDTH, Math.min(maxWidth, Math.round(startWidth + deltaX)));
|
||||
|
||||
currentWidth = newWidth;
|
||||
this.setState({ drawerWidth: newWidth });
|
||||
};
|
||||
|
||||
const onEnd = () => {
|
||||
document.body.style.cursor = '';
|
||||
document.body.style.userSelect = '';
|
||||
window.removeEventListener('pointermove', onMove);
|
||||
window.removeEventListener('pointerup', onEnd);
|
||||
window.removeEventListener('pointercancel', onEnd);
|
||||
window.removeEventListener('mousemove', onMove);
|
||||
window.removeEventListener('mouseup', onEnd);
|
||||
window.removeEventListener('touchmove', onMove);
|
||||
window.removeEventListener('touchend', onEnd);
|
||||
|
||||
this.setState({ isResizing: false });
|
||||
userSettingsAutomat.actions.setChatDrawerWidth(currentWidth);
|
||||
};
|
||||
|
||||
window.addEventListener('pointermove', onMove, { passive: false });
|
||||
window.addEventListener('pointerup', onEnd);
|
||||
window.addEventListener('pointercancel', onEnd);
|
||||
window.addEventListener('mousemove', onMove, { passive: false });
|
||||
window.addEventListener('mouseup', onEnd);
|
||||
window.addEventListener('touchmove', onMove, { passive: false });
|
||||
window.addEventListener('touchend', onEnd);
|
||||
};
|
||||
|
||||
handleResizeDoubleClick = () => {
|
||||
this.setState({ drawerWidth: DEFAULT_DRAWER_WIDTH });
|
||||
userSettingsAutomat.actions.setChatDrawerWidth(DEFAULT_DRAWER_WIDTH);
|
||||
};
|
||||
|
||||
cleanupResizeListeners = () => {
|
||||
document.body.style.cursor = '';
|
||||
document.body.style.userSelect = '';
|
||||
};
|
||||
|
||||
handleSelectConversation = (e) => {
|
||||
const id = Number(e.target.value);
|
||||
if (id) {
|
||||
chatAutomat.actions.selectConversation(id);
|
||||
this.focusInput(50);
|
||||
}
|
||||
};
|
||||
|
||||
handleNewChat = () => {
|
||||
chatAutomat.actions.startDraft();
|
||||
this.focusInput(50);
|
||||
};
|
||||
|
||||
handleDeleteConversation = async (e, convId) => {
|
||||
e?.stopPropagation();
|
||||
e?.preventDefault();
|
||||
if (!convId) return;
|
||||
try {
|
||||
await chatAutomat.actions.deleteConversation(convId);
|
||||
} catch (err) {
|
||||
console.error('[ChatDrawer] Failed to delete conversation:', err);
|
||||
} finally {
|
||||
this.focusInput(50);
|
||||
}
|
||||
};
|
||||
|
||||
handleSend = async (e) => {
|
||||
e?.preventDefault();
|
||||
const text = this.state.inputText.trim();
|
||||
if (!text || this.state.conversationState === 'busy' || this.state.isSending) {
|
||||
return;
|
||||
}
|
||||
this.setState({ inputText: '' });
|
||||
try {
|
||||
await chatAutomat.actions.sendMessage(text);
|
||||
} catch {
|
||||
// Error is tracked in chatAutomat state (sendError)
|
||||
}
|
||||
};
|
||||
|
||||
handleKeyDown = (e) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
void this.handleSend();
|
||||
}
|
||||
};
|
||||
|
||||
handleAnswerTool = async ({ tool_call_id, answer }) => {
|
||||
try {
|
||||
await chatAutomat.actions.sendToolResponse({ tool_call_id, answer });
|
||||
} catch (err) {
|
||||
console.error('[ChatDrawer] Failed to send tool response:', err);
|
||||
}
|
||||
};
|
||||
|
||||
getPendingAskUser = () => selectPendingAskUser(this.state);
|
||||
|
||||
renderHeader() {
|
||||
const {
|
||||
conversations,
|
||||
selectedConversationId,
|
||||
sseStatus,
|
||||
loadingConversations,
|
||||
} = this.state;
|
||||
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
p: 2,
|
||||
borderBottom: 1,
|
||||
borderColor: 'divider',
|
||||
bgcolor: 'background.paper',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 1.5,
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.25 }}>
|
||||
<Box
|
||||
sx={{
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: 2,
|
||||
bgcolor: 'primary.main',
|
||||
color: 'primary.contrastText',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<AutoAwesomeRoundedIcon sx={{ fontSize: 18 }} />
|
||||
</Box>
|
||||
<Typography variant="subtitle1" fontWeight={700}>
|
||||
KI-Assistent
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<IconButton size="small" onClick={this.handleClose} aria-label="Chat schließen">
|
||||
<CloseRoundedIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Box>
|
||||
|
||||
{/* Conversation Selector Bar */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<FormControl fullWidth size="small">
|
||||
<InputLabel id="chat-conversation-label">Konversation</InputLabel>
|
||||
<Select
|
||||
labelId="chat-conversation-label"
|
||||
value={selectedConversationId || ''}
|
||||
label="Konversation"
|
||||
onChange={this.handleSelectConversation}
|
||||
disabled={loadingConversations || conversations.length === 0}
|
||||
sx={{ borderRadius: 2 }}
|
||||
renderValue={(selectedId) => {
|
||||
const conv = conversations.find((c) => c.id === selectedId);
|
||||
if (!conv) return '';
|
||||
return (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, overflow: 'hidden' }}>
|
||||
<Typography variant="body2" fontWeight={600} noWrap>
|
||||
#{conv.id}
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary" noWrap>
|
||||
{conv.model || 'Standard'}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}}
|
||||
>
|
||||
{conversations.map((c) => (
|
||||
<MenuItem key={c.id} value={c.id}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, width: '100%', overflow: 'hidden' }}>
|
||||
<Typography variant="body2" fontWeight={600} noWrap>
|
||||
#{c.id}
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary" noWrap sx={{ flexGrow: 1 }}>
|
||||
{c.model || 'Standard'}
|
||||
</Typography>
|
||||
</Box>
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<IconButton
|
||||
size="small"
|
||||
color="error"
|
||||
disabled={!selectedConversationId}
|
||||
onClick={(e) => this.handleDeleteConversation(e, selectedConversationId)}
|
||||
aria-label="Konversation löschen"
|
||||
sx={{
|
||||
borderRadius: 2,
|
||||
border: 1,
|
||||
borderColor: 'divider',
|
||||
p: 0.9,
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<DeleteOutlineRoundedIcon fontSize="small" />
|
||||
</IconButton>
|
||||
|
||||
<IconButton
|
||||
size="small"
|
||||
color="primary"
|
||||
disabled={!selectedConversationId}
|
||||
onClick={this.handleNewChat}
|
||||
aria-label="Neuer Chat"
|
||||
sx={{
|
||||
borderRadius: 2,
|
||||
border: 1,
|
||||
borderColor: 'divider',
|
||||
p: 0.9,
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<AddRoundedIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
renderBody() {
|
||||
const {
|
||||
conversations,
|
||||
selectedConversationId,
|
||||
draftConversation,
|
||||
conversationState,
|
||||
messages,
|
||||
streamingText,
|
||||
loadingConversations,
|
||||
loadingMessages,
|
||||
model,
|
||||
} = this.state;
|
||||
|
||||
if (loadingConversations && conversations.length === 0) {
|
||||
return (
|
||||
<Box sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', p: 3, gap: 2 }}>
|
||||
<CircularProgress size={32} />
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Lade Konversationen…
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (conversations.length === 0 && !draftConversation) {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
p: 4,
|
||||
textAlign: 'center',
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: 56,
|
||||
height: 56,
|
||||
borderRadius: '50%',
|
||||
bgcolor: 'primary.light',
|
||||
color: 'primary.main',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<AutoAwesomeRoundedIcon sx={{ fontSize: 32 }} />
|
||||
</Box>
|
||||
<Typography variant="h6" fontWeight={700}>
|
||||
Willkommen im Chat
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ maxWidth: 280 }}>
|
||||
Starte eine Unterhaltung mit dem KI-Modell, um Fragen zu stellen oder Daten abzufragen.
|
||||
</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<AddRoundedIcon />}
|
||||
onClick={this.handleNewChat}
|
||||
sx={{ borderRadius: 2, mt: 1 }}
|
||||
>
|
||||
Neuen Chat starten
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
const pendingAsk = this.getPendingAskUser();
|
||||
|
||||
return (
|
||||
<Box sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column', minHeight: 0, overflow: 'hidden' }}>
|
||||
{messages.length === 0 ? (
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
) : (
|
||||
<ChatFlowList
|
||||
messages={
|
||||
streamingText
|
||||
? [
|
||||
...messages,
|
||||
{
|
||||
id: 'streaming',
|
||||
conversation_id: selectedConversationId,
|
||||
role: 'assistant',
|
||||
message: streamingText,
|
||||
streaming: true,
|
||||
},
|
||||
]
|
||||
: messages
|
||||
}
|
||||
conversationState={conversationState}
|
||||
model={model}
|
||||
/>
|
||||
)}
|
||||
|
||||
{pendingAsk && (
|
||||
<AskUserPrompt
|
||||
askData={pendingAsk}
|
||||
onAnswer={this.handleAnswerTool}
|
||||
disabled={this.state.isSending}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
renderFooter() {
|
||||
const {
|
||||
conversationState,
|
||||
isSending,
|
||||
selectedConversationId,
|
||||
draftConversation,
|
||||
inputText,
|
||||
sendError,
|
||||
} = this.state;
|
||||
|
||||
const pendingAsk = this.getPendingAskUser();
|
||||
const isBusy = conversationState === 'busy';
|
||||
const isWorking = isBusy || isSending;
|
||||
const canChat = (Boolean(selectedConversationId) || draftConversation) && !pendingAsk;
|
||||
const canSend = !isWorking && canChat && Boolean(inputText.trim());
|
||||
|
||||
return (
|
||||
<Box
|
||||
component="form"
|
||||
onSubmit={this.handleSend}
|
||||
sx={{
|
||||
p: 2,
|
||||
borderTop: 1,
|
||||
borderColor: 'divider',
|
||||
bgcolor: 'background.paper',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 1,
|
||||
}}
|
||||
>
|
||||
{sendError && (
|
||||
<Box
|
||||
sx={{
|
||||
p: 1,
|
||||
borderRadius: 1.5,
|
||||
bgcolor: 'error.light',
|
||||
color: 'error.contrastText',
|
||||
fontSize: '0.75rem',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Typography variant="caption">{sendError}</Typography>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => chatAutomat.setState({ sendError: null })}
|
||||
sx={{ color: 'inherit', p: 0.2 }}
|
||||
>
|
||||
<CloseRoundedIcon sx={{ fontSize: 14 }} />
|
||||
</IconButton>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box sx={{ display: 'flex', alignItems: 'flex-end', gap: 1 }}>
|
||||
<TextField
|
||||
inputRef={this.inputRef}
|
||||
multiline
|
||||
maxRows={4}
|
||||
size="small"
|
||||
fullWidth
|
||||
value={inputText}
|
||||
onChange={(e) => this.setState({ inputText: e.target.value })}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
disabled={!canChat}
|
||||
placeholder={
|
||||
pendingAsk
|
||||
? 'Bitte Frage oben beantworten…'
|
||||
: 'Nachricht eingeben… (Enter)'
|
||||
}
|
||||
slotProps={{
|
||||
htmlInput: {
|
||||
spellCheck: false,
|
||||
autoCorrect: 'off',
|
||||
autoCapitalize: 'off',
|
||||
},
|
||||
}}
|
||||
sx={{
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: 2.5,
|
||||
bgcolor: 'background.default',
|
||||
fontSize: '0.875rem',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
type="submit"
|
||||
color="primary"
|
||||
disabled={!canSend}
|
||||
title={
|
||||
isWorking
|
||||
? 'Modell antwortet gerade…'
|
||||
: 'Senden (Enter)'
|
||||
}
|
||||
sx={{
|
||||
bgcolor: isWorking ? 'action.hover' : 'primary.main',
|
||||
color: isWorking ? 'warning.main' : 'primary.contrastText',
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 2.5,
|
||||
'&:hover': {
|
||||
bgcolor: 'primary.dark',
|
||||
},
|
||||
'&.Mui-disabled': {
|
||||
bgcolor: isWorking ? 'action.hover' : 'primary.main',
|
||||
color: isWorking ? 'warning.main' : 'primary.contrastText',
|
||||
cursor: 'default',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{isWorking ? (
|
||||
<CircularProgress size={18} color="inherit" thickness={4.5} />
|
||||
) : (
|
||||
<SendRoundedIcon sx={{ fontSize: 20 }} />
|
||||
)}
|
||||
</IconButton>
|
||||
</Box>
|
||||
|
||||
<Typography variant="caption" sx={{ color: 'text.disabled', fontSize: '0.68rem', textAlign: 'center' }}>
|
||||
Enter zum Senden • Shift+Enter für Zeilenumbruch
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isOpen, drawerWidth, isResizing } = this.state;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Drawer
|
||||
anchor="right"
|
||||
open={isOpen}
|
||||
onClose={this.handleClose}
|
||||
sx={(theme) => ({
|
||||
zIndex: theme.zIndex.drawer + 2,
|
||||
'& .MuiDrawer-paper': {
|
||||
width: { xs: '100vw', sm: `${drawerWidth}px` },
|
||||
maxWidth: '100vw',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
boxShadow: '-8px 0 32px rgba(0, 0, 0, 0.12)',
|
||||
position: 'fixed',
|
||||
overflow: 'visible',
|
||||
transition: isResizing ? 'none !important' : undefined,
|
||||
},
|
||||
})}
|
||||
slotProps={{
|
||||
transition: {
|
||||
onEntered: () => this.focusInput(20),
|
||||
},
|
||||
paper: {
|
||||
sx: {
|
||||
width: { xs: '100vw', sm: `${drawerWidth}px` },
|
||||
maxWidth: '100vw',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
boxShadow: '-8px 0 32px rgba(0, 0, 0, 0.12)',
|
||||
position: 'fixed',
|
||||
overflow: 'visible',
|
||||
transition: isResizing ? 'none !important' : undefined,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{/* Draggable Resizer Handle */}
|
||||
<Box
|
||||
onPointerDown={this.handleResizeStart}
|
||||
onMouseDown={this.handleResizeStart}
|
||||
onTouchStart={this.handleResizeStart}
|
||||
onDoubleClick={this.handleResizeDoubleClick}
|
||||
title="Ziehen zum Anpassen der Breite • Doppelklick zum Zurücksetzen"
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
width: 16,
|
||||
cursor: 'col-resize',
|
||||
zIndex: 9999,
|
||||
display: { xs: 'none', sm: 'flex' },
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
touchAction: 'none',
|
||||
userSelect: 'none',
|
||||
'&:hover .drawer-resizer-line, &:active .drawer-resizer-line': {
|
||||
bgcolor: 'primary.main',
|
||||
height: 56,
|
||||
width: 4,
|
||||
opacity: 1,
|
||||
boxShadow: '0 0 10px rgba(59, 91, 219, 0.6)',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
className="drawer-resizer-line"
|
||||
sx={{
|
||||
width: isResizing ? 4 : 3,
|
||||
height: isResizing ? 56 : 36,
|
||||
borderRadius: 2,
|
||||
bgcolor: isResizing ? 'primary.main' : 'divider',
|
||||
opacity: isResizing ? 1 : 0.6,
|
||||
transition: 'background-color 0.15s, height 0.15s, width 0.15s, opacity 0.15s, box-shadow 0.15s',
|
||||
boxShadow: isResizing ? '0 0 10px rgba(59, 91, 219, 0.6)' : 'none',
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{this.renderHeader()}
|
||||
{this.renderBody()}
|
||||
{this.renderFooter()}
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user