Compare commits
6 Commits
40416cdae5
...
056e4f63b6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
056e4f63b6 | ||
|
|
6578f7d1ac | ||
|
|
f91c50ef69 | ||
|
|
7f988c9d63 | ||
|
|
064dd2bd31 | ||
|
|
457c41ab1d |
35
index.html
35
index.html
@@ -104,41 +104,6 @@
|
|||||||
html {
|
html {
|
||||||
scrollbar-gutter: stable;
|
scrollbar-gutter: stable;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
|
||||||
--df-zoom: 1;
|
|
||||||
--df-vh: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1699.98px) {
|
|
||||||
:root {
|
|
||||||
--df-zoom: 0.8;
|
|
||||||
--df-vh: calc(100vh / 0.8);
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
zoom: 0.8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1399.98px) {
|
|
||||||
:root {
|
|
||||||
--df-zoom: 0.65;
|
|
||||||
--df-vh: calc(100vh / 0.65);
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
zoom: 0.65;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 999.98px) {
|
|
||||||
:root {
|
|
||||||
--df-zoom: 0.5;
|
|
||||||
--df-vh: calc(100vh / 0.5);
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
zoom: 0.5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|||||||
@@ -345,11 +345,18 @@ export const SHADOW_BUBBLE_ASSISTANT = `0 1px 4px ${TINT_ROW_HOVER}`;
|
|||||||
* dialog is open (`html.df-dialog-open` in `src/index.css`).
|
* dialog is open (`html.df-dialog-open` in `src/index.css`).
|
||||||
* -------------------------------------------------------------------------*/
|
* -------------------------------------------------------------------------*/
|
||||||
|
|
||||||
/** Opaque fog base — deep navy with a blue push. */
|
/** Raw deep-navy base tint used for the dialog fog backdrop. */
|
||||||
export const DIALOG_FOG_SOLID = mix(DARK_6, CHROMA.BLUE.dark, 0.42);
|
export const DIALOG_FOG_NAVY = mix(DARK_6, CHROMA.BLUE.dark, 0.42);
|
||||||
|
|
||||||
/** Translucent backdrop wash painted over the page. */
|
/** Translucent backdrop wash painted over the page. */
|
||||||
export const DIALOG_FOG = alpha(DIALOG_FOG_SOLID, 0.72);
|
export const DIALOG_FOG = alpha(DIALOG_FOG_NAVY, 0.72);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Solid fog color seen when DIALOG_FOG (72% opacity) is drawn over MAIN_BACKGROUND.
|
||||||
|
* Published as `--df-dialog-fog` so the reserved scrollbar-gutter strip on
|
||||||
|
* `html.df-dialog-open` seamlessly matches the translucent fog backdrop over the app.
|
||||||
|
*/
|
||||||
|
export const DIALOG_FOG_SOLID = mix(MAIN_BACKGROUND, DIALOG_FOG_NAVY, 0.72);
|
||||||
|
|
||||||
/* ────────────────────────────────────────────────────────────────────────────
|
/* ────────────────────────────────────────────────────────────────────────────
|
||||||
* 11. CSS VARIABLE BRIDGE
|
* 11. CSS VARIABLE BRIDGE
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ class AppDialog extends Component {
|
|||||||
transitionDuration: _transitionDuration,
|
transitionDuration: _transitionDuration,
|
||||||
TransitionProps: _transitionProps,
|
TransitionProps: _transitionProps,
|
||||||
TransitionComponent: _transitionComponent,
|
TransitionComponent: _transitionComponent,
|
||||||
|
disableScrollLock = true,
|
||||||
slotProps: userSlotProps,
|
slotProps: userSlotProps,
|
||||||
slots: userSlots,
|
slots: userSlots,
|
||||||
...rest
|
...rest
|
||||||
@@ -95,6 +96,7 @@ class AppDialog extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
|
disableScrollLock={disableScrollLock}
|
||||||
disableEnforceFocus
|
disableEnforceFocus
|
||||||
{...rest}
|
{...rest}
|
||||||
slots={restSlots}
|
slots={restSlots}
|
||||||
|
|||||||
@@ -185,6 +185,8 @@ class AppSubNav extends PureComponent {
|
|||||||
sx={{
|
sx={{
|
||||||
width: SUB_NAV_WIDTH,
|
width: SUB_NAV_WIDTH,
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
|
ml: 3,
|
||||||
|
mr: 2,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -204,7 +206,10 @@ class AppSubNav extends PureComponent {
|
|||||||
position: 'sticky',
|
position: 'sticky',
|
||||||
top: 0,
|
top: 0,
|
||||||
alignSelf: 'flex-start',
|
alignSelf: 'flex-start',
|
||||||
|
height: 'var(--df-vh, 100vh)',
|
||||||
py: 4,
|
py: 4,
|
||||||
|
ml: 3,
|
||||||
|
mr: 2,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{subNav.map(({ segment, label, title, help, icon: Icon }) => {
|
{subNav.map(({ segment, label, title, help, icon: Icon }) => {
|
||||||
@@ -296,26 +301,24 @@ export default function AppShell() {
|
|||||||
return (
|
return (
|
||||||
<Box sx={{ display: 'flex', minHeight: 'var(--df-vh, 100vh)', bgcolor: 'background.default' }}>
|
<Box sx={{ display: 'flex', minHeight: 'var(--df-vh, 100vh)', bgcolor: 'background.default' }}>
|
||||||
<AppSidebar activeNav={activeNav} navigate={navigate} />
|
<AppSidebar activeNav={activeNav} navigate={navigate} />
|
||||||
|
<AppSubNav
|
||||||
|
activeNav={activeNav}
|
||||||
|
activeSub={activeSub}
|
||||||
|
navigate={navigate}
|
||||||
|
/>
|
||||||
<Box
|
<Box
|
||||||
component="main"
|
component="main"
|
||||||
sx={(theme) => ({
|
sx={(theme) => ({
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
gap: 2,
|
flexDirection: 'column',
|
||||||
py: 4,
|
py: 4,
|
||||||
px: 3,
|
pr: 3,
|
||||||
bgcolor: theme.nav.mainBackground,
|
bgcolor: theme.nav.mainBackground,
|
||||||
minWidth: 0,
|
minWidth: 0,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<AppSubNav
|
<Outlet />
|
||||||
activeNav={activeNav}
|
|
||||||
activeSub={activeSub}
|
|
||||||
navigate={navigate}
|
|
||||||
/>
|
|
||||||
<Box sx={{ flexGrow: 1, minWidth: 0 }}>
|
|
||||||
<Outlet />
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
<ChatFab />
|
<ChatFab />
|
||||||
<ChatDrawer />
|
<ChatDrawer />
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import Autocomplete from '@mui/material/Autocomplete';
|
|||||||
import TextInput from '../../TextInput';
|
import TextInput from '../../TextInput';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import { EditablePanelTile } from 'dashgui/panels';
|
import { EditablePanelTile, PanelTile } from 'dashgui/panels';
|
||||||
import brandAutomat from '../../../automats/brandAutomat';
|
import brandAutomat from '../../../automats/brandAutomat';
|
||||||
import artikelAutomat from '../../../automats/artikelAutomat';
|
import artikelAutomat, { getArticle } from '../../../automats/artikelAutomat';
|
||||||
import { requireObject } from '../../../utils/apiShape';
|
import { requireObject } from '../../../utils/apiShape';
|
||||||
import { notifyError } from '../../../utils/notify';
|
import { notifyError } from '../../../utils/notify';
|
||||||
import InputAdornment from '@mui/material/InputAdornment';
|
import InputAdornment from '@mui/material/InputAdornment';
|
||||||
@@ -33,12 +33,13 @@ class PanelValue extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function brandDraftValue(article) {
|
function brandDraftValue(article, parentArticle) {
|
||||||
|
const brand = article?.brand || parentArticle?.brand;
|
||||||
return {
|
return {
|
||||||
name: article?.brand?.name ?? '',
|
name: brand?.name ?? '',
|
||||||
thumbHash: article?.brand?.thumbHash ?? null,
|
thumbHash: brand?.thumbHash ?? null,
|
||||||
thumbW: article?.brand?.thumbW ?? null,
|
thumbW: brand?.thumbW ?? null,
|
||||||
thumbH: article?.brand?.thumbH ?? null,
|
thumbH: brand?.thumbH ?? null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,12 +129,27 @@ export default class ArticleViewBrand extends PureComponent {
|
|||||||
this.unsub = brandAutomat.subscribe(this, (state) => ({
|
this.unsub = brandAutomat.subscribe(this, (state) => ({
|
||||||
brands: state.brands ?? [],
|
brands: state.brands ?? [],
|
||||||
}));
|
}));
|
||||||
|
const parentSku = this.props.article?.parentSku || this.props.article?.parent_sku;
|
||||||
|
if (parentSku && !getArticle(parentSku)) {
|
||||||
|
void artikelAutomat.actions.loadArticle(parentSku).catch(() => {});
|
||||||
|
}
|
||||||
|
this.unsubArtikel = artikelAutomat.subscribe(this, () => ({}));
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
const parentSku = this.props.article?.parentSku || this.props.article?.parent_sku;
|
||||||
|
const prevParentSku = prevProps.article?.parentSku || prevProps.article?.parent_sku;
|
||||||
|
if (parentSku && parentSku !== prevParentSku && !getArticle(parentSku)) {
|
||||||
|
void artikelAutomat.actions.loadArticle(parentSku).catch(() => {});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this._cancelled = true;
|
this._cancelled = true;
|
||||||
this.unsub?.();
|
this.unsub?.();
|
||||||
this.unsub = null;
|
this.unsub = null;
|
||||||
|
this.unsubArtikel?.();
|
||||||
|
this.unsubArtikel = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSave = async (draft) => {
|
handleSave = async (draft) => {
|
||||||
@@ -170,7 +186,40 @@ export default class ArticleViewBrand extends PureComponent {
|
|||||||
render() {
|
render() {
|
||||||
const { article: articleData } = this.props;
|
const { article: articleData } = this.props;
|
||||||
const { brands } = this.state;
|
const { brands } = this.state;
|
||||||
const value = brandDraftValue(articleData);
|
const isChild = Boolean(
|
||||||
|
articleData?.parentSku ||
|
||||||
|
articleData?.parent_sku ||
|
||||||
|
articleData?.parentArticleId != null ||
|
||||||
|
articleData?.parentId != null ||
|
||||||
|
articleData?.parent_id != null
|
||||||
|
);
|
||||||
|
|
||||||
|
const parentSku = articleData?.parentSku || articleData?.parent_sku;
|
||||||
|
const parentArticle = parentSku ? getArticle(parentSku) : null;
|
||||||
|
const value = brandDraftValue(articleData, parentArticle);
|
||||||
|
|
||||||
|
if (isChild) {
|
||||||
|
const brandObj = articleData?.brand || parentArticle?.brand || (value.name ? value : null);
|
||||||
|
return (
|
||||||
|
<PanelTile
|
||||||
|
title="Marke"
|
||||||
|
wide={1}
|
||||||
|
minHeight={SECOND_ROW_MIN_HEIGHT}
|
||||||
|
color={PURPLE_BG}
|
||||||
|
>
|
||||||
|
<PanelValue>
|
||||||
|
{value.name ? (
|
||||||
|
<BrandLabel
|
||||||
|
brand={brandObj}
|
||||||
|
name={value.name}
|
||||||
|
size={22}
|
||||||
|
typographyProps={{ variant: 'body2', fontWeight: 600 }}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</PanelValue>
|
||||||
|
</PanelTile>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EditablePanelTile
|
<EditablePanelTile
|
||||||
|
|||||||
@@ -85,10 +85,18 @@ export default class ArticleViewStock extends PureComponent {
|
|||||||
const hasWarehouses = warehouses.length > 0;
|
const hasWarehouses = warehouses.length > 0;
|
||||||
|
|
||||||
let totalVariantsStock = 0;
|
let totalVariantsStock = 0;
|
||||||
|
let anyVariantTracksStock = false;
|
||||||
if (isParent) {
|
if (isParent) {
|
||||||
const children = Array.isArray(articleData?.children) ? articleData.children : [];
|
const children = Array.isArray(articleData?.children) ? articleData.children : [];
|
||||||
for (const child of children) {
|
for (const child of children) {
|
||||||
const fullChild = getArticle(child.sku) || child;
|
const fullChild = getArticle(child.sku) || child;
|
||||||
|
const tracks =
|
||||||
|
fullChild.track_stock !== false &&
|
||||||
|
fullChild.track_stock !== 0 &&
|
||||||
|
fullChild.track_stock !== '0';
|
||||||
|
if (tracks) {
|
||||||
|
anyVariantTracksStock = true;
|
||||||
|
}
|
||||||
if (Array.isArray(fullChild.warehouses) && fullChild.warehouses.length > 0) {
|
if (Array.isArray(fullChild.warehouses) && fullChild.warehouses.length > 0) {
|
||||||
totalVariantsStock += fullChild.warehouses.reduce(
|
totalVariantsStock += fullChild.warehouses.reduce(
|
||||||
(sum, wh) => sum + (Number(wh.stock) || 0),
|
(sum, wh) => sum + (Number(wh.stock) || 0),
|
||||||
@@ -155,19 +163,21 @@ export default class ArticleViewStock extends PureComponent {
|
|||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
{isParent ? (
|
{isParent ? (
|
||||||
<Box sx={{ my: 0.75 }}>
|
anyVariantTracksStock || totalVariantsStock > 0 ? (
|
||||||
<Typography
|
<Box sx={{ my: 0.75 }}>
|
||||||
component="div"
|
<Typography
|
||||||
sx={{
|
component="div"
|
||||||
fontSize: '1.25rem',
|
sx={{
|
||||||
fontWeight: 700,
|
fontSize: '1.25rem',
|
||||||
lineHeight: 1.2,
|
fontWeight: 700,
|
||||||
color: 'text.primary',
|
lineHeight: 1.2,
|
||||||
}}
|
color: 'text.primary',
|
||||||
>
|
}}
|
||||||
Alle Varianten zusammen: {formatStock(totalVariantsStock)}
|
>
|
||||||
</Typography>
|
Alle Varianten zusammen: {formatStock(totalVariantsStock)}
|
||||||
</Box>
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
) : null
|
||||||
) : isStueckliste ? (
|
) : isStueckliste ? (
|
||||||
<Tooltip title="Virtueller Bestand – Anpassung erfolgt an den einzelnen Komponenten" arrow placement="top-start">
|
<Tooltip title="Virtueller Bestand – Anpassung erfolgt an den einzelnen Komponenten" arrow placement="top-start">
|
||||||
<Box sx={{ my: 0.5 }}>
|
<Box sx={{ my: 0.5 }}>
|
||||||
|
|||||||
@@ -290,7 +290,7 @@ export default class BrandImageSearchDialog extends PureComponent {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
|
|
||||||
<DialogContent dividers sx={{ p: 2.5 }}>
|
<DialogContent dividers>
|
||||||
{/* Search bar */}
|
{/* Search bar */}
|
||||||
<Box
|
<Box
|
||||||
component="form"
|
component="form"
|
||||||
|
|||||||
@@ -112,12 +112,24 @@ export default class ChatDrawer extends PureComponent {
|
|||||||
}, delay);
|
}, delay);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
updateGutterClass(isOpen) {
|
||||||
|
if (typeof document === 'undefined') return;
|
||||||
|
const html = document.documentElement;
|
||||||
|
if (!html) return;
|
||||||
|
if (isOpen) {
|
||||||
|
html.classList.add('df-chat-open');
|
||||||
|
} else {
|
||||||
|
html.classList.remove('df-chat-open');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
this.updateGutterClass(this.state.isOpen);
|
||||||
if (this.state.isOpen) {
|
if (this.state.isOpen) {
|
||||||
if (chatAutomat.state.pendingInput != null) {
|
if (chatAutomat.state.pendingInput != null) {
|
||||||
this.focusInputAtEnd(150);
|
this.focusInputAtEnd(0);
|
||||||
} else {
|
} else {
|
||||||
this.focusInput(150);
|
this.focusInput(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void this.loadModelSettings();
|
void this.loadModelSettings();
|
||||||
@@ -157,9 +169,9 @@ export default class ChatDrawer extends PureComponent {
|
|||||||
chatAutomat.setState({ pendingInput: null });
|
chatAutomat.setState({ pendingInput: null });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.focusInputAtEnd(150);
|
this.focusInputAtEnd(0);
|
||||||
} else if (!wasOpen && willOpen) {
|
} else if (!wasOpen && willOpen) {
|
||||||
this.focusInput(150);
|
this.focusInput(0);
|
||||||
}
|
}
|
||||||
return patch;
|
return patch;
|
||||||
});
|
});
|
||||||
@@ -177,7 +189,14 @@ export default class ChatDrawer extends PureComponent {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps, prevState) {
|
||||||
|
if (this.state.isOpen !== prevState.isOpen) {
|
||||||
|
this.updateGutterClass(this.state.isOpen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
this.updateGutterClass(false);
|
||||||
if (this.focusTimeout) clearTimeout(this.focusTimeout);
|
if (this.focusTimeout) clearTimeout(this.focusTimeout);
|
||||||
this.unsub?.();
|
this.unsub?.();
|
||||||
this.unsubSettings?.();
|
this.unsubSettings?.();
|
||||||
@@ -1605,13 +1624,17 @@ export default class ChatDrawer extends PureComponent {
|
|||||||
anchor="right"
|
anchor="right"
|
||||||
open={isOpen}
|
open={isOpen}
|
||||||
onClose={this.handleClose}
|
onClose={this.handleClose}
|
||||||
|
disableScrollLock
|
||||||
|
transitionDuration={0}
|
||||||
ModalProps={{
|
ModalProps={{
|
||||||
|
disableScrollLock: true,
|
||||||
disableEnforceFocus: true,
|
disableEnforceFocus: true,
|
||||||
}}
|
}}
|
||||||
sx={(theme) => ({
|
sx={(theme) => ({
|
||||||
zIndex: theme.zIndex.modal + 10,
|
zIndex: theme.zIndex.modal + 10,
|
||||||
'& .MuiBackdrop-root': {
|
'& .MuiBackdrop-root': {
|
||||||
backgroundColor: DIALOG_FOG,
|
backgroundColor: DIALOG_FOG,
|
||||||
|
transition: 'none !important',
|
||||||
},
|
},
|
||||||
'& .MuiDrawer-paper': {
|
'& .MuiDrawer-paper': {
|
||||||
width: { xs: '100vw', sm: `${totalWidth}px` },
|
width: { xs: '100vw', sm: `${totalWidth}px` },
|
||||||
@@ -1621,12 +1644,13 @@ export default class ChatDrawer extends PureComponent {
|
|||||||
boxShadow: SHADOW_DRAWER,
|
boxShadow: SHADOW_DRAWER,
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
overflow: 'visible',
|
overflow: 'visible',
|
||||||
transition: isResizing ? 'none !important' : undefined,
|
transition: 'none !important',
|
||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
slotProps={{
|
slotProps={{
|
||||||
transition: {
|
transition: {
|
||||||
onEntered: () => this.focusInput(20),
|
timeout: 0,
|
||||||
|
onEntered: () => this.focusInput(0),
|
||||||
},
|
},
|
||||||
paper: {
|
paper: {
|
||||||
sx: {
|
sx: {
|
||||||
@@ -1637,7 +1661,7 @@ export default class ChatDrawer extends PureComponent {
|
|||||||
boxShadow: SHADOW_DRAWER,
|
boxShadow: SHADOW_DRAWER,
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
overflow: 'visible',
|
overflow: 'visible',
|
||||||
transition: isResizing ? 'none !important' : undefined,
|
transition: 'none !important',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ import chatAutomat, { selectPendingAskUser, selectHasPendingAsk } from '../../au
|
|||||||
import {
|
import {
|
||||||
CHAT_BUSY,
|
CHAT_BUSY,
|
||||||
CHAT_FAB_BUSY,
|
CHAT_FAB_BUSY,
|
||||||
CHAT_FAB_PENDING, CHAT_FAB_SHADOW_BUSY,
|
CHAT_FAB_PENDING, CHAT_FAB_SHADOW_BUSY,
|
||||||
CHAT_FAB_SHADOW_IDLE,
|
CHAT_FAB_SHADOW_IDLE,
|
||||||
CHAT_FAB_SHADOW_PENDING, RED,
|
CHAT_FAB_SHADOW_PENDING, RED,
|
||||||
RED_GLOW,
|
RED_GLOW,
|
||||||
WHITE,
|
WHITE,
|
||||||
} from '../../colors';
|
} from '../../colors';
|
||||||
@@ -59,18 +59,12 @@ export default class ChatFab extends PureComponent {
|
|||||||
const { isOpen, conversationState, streamPhase, isSending, hasPendingAsk, pendingAsk } = this.state;
|
const { isOpen, conversationState, streamPhase, isSending, hasPendingAsk, pendingAsk } = this.state;
|
||||||
const isBusy = conversationState === 'busy' || streamPhase === 'thinking' || streamPhase === 'working' || isSending;
|
const isBusy = conversationState === 'busy' || streamPhase === 'thinking' || streamPhase === 'working' || isSending;
|
||||||
|
|
||||||
let tooltipTitle = 'KI-Chat öffnen';
|
|
||||||
let ariaLabel = 'KI-Chat';
|
let ariaLabel = 'KI-Chat';
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
tooltipTitle = 'KI-Chat schließen';
|
|
||||||
ariaLabel = 'KI-Chat schließen';
|
ariaLabel = 'KI-Chat schließen';
|
||||||
} else if (hasPendingAsk) {
|
} else if (hasPendingAsk) {
|
||||||
tooltipTitle = pendingAsk?.question
|
|
||||||
? `Antwort erforderlich: „${pendingAsk.question}“`
|
|
||||||
: 'Antwort erforderlich: Das Modell wartet auf Ihre Eingabe';
|
|
||||||
ariaLabel = 'KI-Chat: Antwort erforderlich';
|
ariaLabel = 'KI-Chat: Antwort erforderlich';
|
||||||
} else if (isBusy) {
|
} else if (isBusy) {
|
||||||
tooltipTitle = 'Modell antwortet gerade…';
|
|
||||||
ariaLabel = 'KI-Chat: Modell antwortet';
|
ariaLabel = 'KI-Chat: Modell antwortet';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,77 +137,75 @@ export default class ChatFab extends PureComponent {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Tooltip title={tooltipTitle} placement="right" arrow>
|
<Badge
|
||||||
<Badge
|
badgeContent={hasPendingAsk ? '?' : undefined}
|
||||||
badgeContent={hasPendingAsk ? '?' : undefined}
|
color={hasPendingAsk ? 'error' : isBusy ? 'warning' : 'default'}
|
||||||
color={hasPendingAsk ? 'error' : isBusy ? 'warning' : 'default'}
|
variant={hasPendingAsk ? 'standard' : 'dot'}
|
||||||
variant={hasPendingAsk ? 'standard' : 'dot'}
|
invisible={!hasPendingAsk && !isBusy}
|
||||||
invisible={!hasPendingAsk && !isBusy}
|
overlap="circular"
|
||||||
overlap="circular"
|
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
|
||||||
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
|
sx={{
|
||||||
sx={{
|
'& .MuiBadge-badge': hasPendingAsk
|
||||||
'& .MuiBadge-badge': hasPendingAsk
|
? {
|
||||||
? {
|
bgcolor: RED,
|
||||||
bgcolor: RED,
|
color: WHITE,
|
||||||
color: WHITE,
|
fontWeight: 900,
|
||||||
fontWeight: 900,
|
fontSize: '0.85rem',
|
||||||
fontSize: '0.85rem',
|
minWidth: 22,
|
||||||
minWidth: 22,
|
height: 22,
|
||||||
height: 22,
|
borderRadius: '50%',
|
||||||
borderRadius: '50%',
|
boxShadow: `0 2px 10px ${RED_GLOW}`,
|
||||||
boxShadow: `0 2px 10px ${RED_GLOW}`,
|
animation: 'badgePop 1.5s ease infinite',
|
||||||
animation: 'badgePop 1.5s ease infinite',
|
'@keyframes badgePop': {
|
||||||
'@keyframes badgePop': {
|
'0%, 100%': { transform: 'scale(1)' },
|
||||||
'0%, 100%': { transform: 'scale(1)' },
|
'50%': { transform: 'scale(1.2)' },
|
||||||
'50%': { transform: 'scale(1.2)' },
|
},
|
||||||
},
|
}
|
||||||
}
|
: undefined,
|
||||||
: undefined,
|
}}
|
||||||
}}
|
>
|
||||||
>
|
<Fab
|
||||||
<Fab
|
color="primary"
|
||||||
color="primary"
|
aria-label={ariaLabel}
|
||||||
aria-label={ariaLabel}
|
onClick={this.handleClick}
|
||||||
onClick={this.handleClick}
|
sx={(theme) => ({
|
||||||
sx={(theme) => ({
|
width: 54,
|
||||||
width: 54,
|
height: 54,
|
||||||
height: 54,
|
boxShadow: hasPendingAsk
|
||||||
boxShadow: hasPendingAsk
|
? CHAT_FAB_SHADOW_PENDING
|
||||||
? CHAT_FAB_SHADOW_PENDING
|
: isBusy
|
||||||
: isBusy
|
|
||||||
? CHAT_FAB_SHADOW_BUSY
|
? CHAT_FAB_SHADOW_BUSY
|
||||||
: CHAT_FAB_SHADOW_IDLE,
|
: CHAT_FAB_SHADOW_IDLE,
|
||||||
background: hasPendingAsk
|
background: hasPendingAsk
|
||||||
? CHAT_FAB_PENDING
|
? CHAT_FAB_PENDING
|
||||||
: isBusy
|
: isBusy
|
||||||
? CHAT_FAB_BUSY
|
? CHAT_FAB_BUSY
|
||||||
: `linear-gradient(135deg, ${theme.palette.primary.main} 0%, ${theme.palette.primary.dark} 100%)`,
|
: `linear-gradient(135deg, ${theme.palette.primary.main} 0%, ${theme.palette.primary.dark} 100%)`,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{isOpen ? (
|
{isOpen ? (
|
||||||
<CloseRoundedIcon sx={{ fontSize: 24 }} />
|
<CloseRoundedIcon sx={{ fontSize: 24 }} />
|
||||||
) : hasPendingAsk ? (
|
) : hasPendingAsk ? (
|
||||||
<HelpOutlineRoundedIcon
|
<HelpOutlineRoundedIcon
|
||||||
sx={{
|
sx={{
|
||||||
fontSize: 26,
|
fontSize: 26,
|
||||||
animation: 'fabWiggle 2s ease infinite',
|
animation: 'fabWiggle 2s ease infinite',
|
||||||
'@keyframes fabWiggle': {
|
'@keyframes fabWiggle': {
|
||||||
'0%, 100%': { transform: 'rotate(0deg)' },
|
'0%, 100%': { transform: 'rotate(0deg)' },
|
||||||
'15%': { transform: 'rotate(-12deg)' },
|
'15%': { transform: 'rotate(-12deg)' },
|
||||||
'30%': { transform: 'rotate(12deg)' },
|
'30%': { transform: 'rotate(12deg)' },
|
||||||
'45%': { transform: 'rotate(-8deg)' },
|
'45%': { transform: 'rotate(-8deg)' },
|
||||||
'60%': { transform: 'rotate(0deg)' },
|
'60%': { transform: 'rotate(0deg)' },
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : isBusy ? (
|
) : isBusy ? (
|
||||||
<AutoAwesomeRoundedIcon sx={{ fontSize: 24 }} />
|
<AutoAwesomeRoundedIcon sx={{ fontSize: 24 }} />
|
||||||
) : (
|
) : (
|
||||||
<ChatRoundedIcon sx={{ fontSize: 24 }} />
|
<ChatRoundedIcon sx={{ fontSize: 24 }} />
|
||||||
)}
|
)}
|
||||||
</Fab>
|
</Fab>
|
||||||
</Badge>
|
</Badge>
|
||||||
</Tooltip>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -44,6 +44,15 @@ html {
|
|||||||
scrollbar-gutter: stable;
|
scrollbar-gutter: stable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* While the assistant chat drawer is open on the right edge of the screen,
|
||||||
|
the reserved scrollbar-gutter strip must read as white to seamlessly
|
||||||
|
continue the drawer surface. */
|
||||||
|
html.df-chat-open,
|
||||||
|
html.df-chat-open body,
|
||||||
|
html.df-chat-open #root {
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
/* While any AppDialog is open, the reserved scrollbar-gutter strip must read
|
/* While any AppDialog is open, the reserved scrollbar-gutter strip must read
|
||||||
as fog, not app background. AppDialog toggles `df-dialog-open` on <html>
|
as fog, not app background. AppDialog toggles `df-dialog-open` on <html>
|
||||||
(ref-counted for stacked dialogs). The solid fog token is published by
|
(ref-counted for stacked dialogs). The solid fog token is published by
|
||||||
@@ -51,7 +60,7 @@ html {
|
|||||||
html.df-dialog-open,
|
html.df-dialog-open,
|
||||||
html.df-dialog-open body,
|
html.df-dialog-open body,
|
||||||
html.df-dialog-open #root {
|
html.df-dialog-open #root {
|
||||||
background-color: var(--df-dialog-fog, #152e73);
|
background-color: var(--df-dialog-fog, #345585);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* SortableJS: Kategorie-Reorder im Edit-Modus.
|
/* SortableJS: Kategorie-Reorder im Edit-Modus.
|
||||||
|
|||||||
@@ -44,9 +44,20 @@ export function createDefaultTheme() {
|
|||||||
},
|
},
|
||||||
nav,
|
nav,
|
||||||
components: {
|
components: {
|
||||||
|
MuiModal: {
|
||||||
|
defaultProps: {
|
||||||
|
disableScrollLock: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MuiDialog: {
|
||||||
|
defaultProps: {
|
||||||
|
disableScrollLock: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
MuiMenu: {
|
MuiMenu: {
|
||||||
defaultProps: {
|
defaultProps: {
|
||||||
transitionDuration: 0,
|
transitionDuration: 0,
|
||||||
|
disableScrollLock: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
MuiButton: {
|
MuiButton: {
|
||||||
@@ -79,6 +90,7 @@ export function createDefaultTheme() {
|
|||||||
MuiPopover: {
|
MuiPopover: {
|
||||||
defaultProps: {
|
defaultProps: {
|
||||||
transitionDuration: 0,
|
transitionDuration: 0,
|
||||||
|
disableScrollLock: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user