This commit is contained in:
seb
2026-10-15 20:15:19 +02:00
parent 8e783a571f
commit beed294cb0
26 changed files with 3165 additions and 906 deletions

View File

@@ -14,11 +14,16 @@ import CodeRoundedIcon from '@mui/icons-material/CodeRounded';
import ChatBubbleOutlineRoundedIcon from '@mui/icons-material/ChatBubbleOutlineRounded';
import InputRoundedIcon from '@mui/icons-material/InputRounded';
import OutputRoundedIcon from '@mui/icons-material/OutputRounded';
import PushPinIcon from '@mui/icons-material/PushPin';
import PushPinOutlinedIcon from '@mui/icons-material/PushPinOutlined';
import Tooltip from '@mui/material/Tooltip';
import { enqueueSnackbar } from 'notistack';
import VfsFileIcon from './VfsFileIcon';
import VfsAppletForm from './VfsAppletForm';
import { parseAppletSchemas, buildAppletDefaults, parseCsvRows } from './vfsUtils';
import { vfs as vfsApi } from '../../api';
import pinnedAppletAutomat, { normalizeRoute } from '../../automats/pinnedAppletAutomat';
/**
* @file VfsAppletRunner.jsx
@@ -46,9 +51,26 @@ export default class VfsAppletRunner extends PureComponent {
stdout: '',
durationMs: null,
truncated: false,
isPinned: Boolean(
props.fileName &&
pinnedAppletAutomat.actions.isPinned(props.fileName, window.location.pathname)
),
};
}
componentDidMount() {
this.unsubPinned = pinnedAppletAutomat.subscribe(this, () => ({
isPinned: Boolean(
this.props.fileName &&
pinnedAppletAutomat.actions.isPinned(this.props.fileName, window.location.pathname)
),
}));
}
componentWillUnmount() {
this.unsubPinned?.();
}
componentDidUpdate(prevProps) {
if (prevProps.content !== this.props.content || prevProps.fileName !== this.props.fileName) {
const { inputSchema, outputColumns, description } = parseAppletSchemas(this.props.content || '');
@@ -66,10 +88,27 @@ export default class VfsAppletRunner extends PureComponent {
stdout: '',
durationMs: null,
truncated: false,
isPinned: Boolean(
this.props.fileName &&
pinnedAppletAutomat.actions.isPinned(this.props.fileName, window.location.pathname)
),
});
}
}
handleTogglePin = () => {
const { fileName } = this.props;
if (!fileName) return;
const currentRoute = normalizeRoute(window.location.pathname);
const nextPinned = pinnedAppletAutomat.actions.togglePin(fileName, currentRoute);
this.setState({ isPinned: nextPinned });
if (nextPinned) {
enqueueSnackbar(`Applet an „${currentRoute}“ angepinnt`, { variant: 'success' });
} else {
enqueueSnackbar(`Applet von „${currentRoute}“ gelöst`, { variant: 'info' });
}
};
handleInputChange = (nextValues) => {
this.setState({ inputValues: nextValues });
};
@@ -248,6 +287,7 @@ export default class VfsAppletRunner extends PureComponent {
stdout,
durationMs,
truncated,
isPinned,
} = this.state;
const description = stateDescription || file?.description;
@@ -308,6 +348,32 @@ export default class VfsAppletRunner extends PureComponent {
Code
</Button>
)}
{fileName && (
<Tooltip
title={
isPinned
? `Von dieser Route (${normalizeRoute(window.location.pathname)}) lösen`
: `An diese Route (${normalizeRoute(window.location.pathname)}) anpinnen`
}
>
<Button
size="small"
variant={isPinned ? 'contained' : 'outlined'}
color={isPinned ? 'primary' : 'inherit'}
startIcon={isPinned ? <PushPinIcon /> : <PushPinOutlinedIcon />}
onClick={this.handleTogglePin}
sx={{
borderRadius: 2,
fontSize: '0.78rem',
fontWeight: 600,
textTransform: 'none',
...(isPinned ? { boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)' } : {}),
}}
>
{isPinned ? 'Angepinnt' : 'Anpinnen'}
</Button>
</Tooltip>
)}
{onMentionInChat && (
<IconButton size="small" onClick={onMentionInChat} aria-label="In Chat erwähnen" sx={{ p: 0.6 }}>
<ChatBubbleOutlineRoundedIcon fontSize="small" color="primary" />