mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2026-06-21 15:26:32 +03:00
own version of toast
This commit is contained in:
@@ -39,7 +39,6 @@
|
||||
"react-dom": "^19.2.7",
|
||||
"react-icons": "^5.6.0",
|
||||
"react-router": "^8.0.1",
|
||||
"react-toastify": "^11.1.0",
|
||||
"typesafe-i18n": "^5.27.1",
|
||||
"typescript": "^6.0.3"
|
||||
},
|
||||
|
||||
15
interface/pnpm-lock.yaml
generated
15
interface/pnpm-lock.yaml
generated
@@ -56,9 +56,6 @@ importers:
|
||||
react-router:
|
||||
specifier: ^8.0.1
|
||||
version: 8.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
react-toastify:
|
||||
specifier: ^11.1.0
|
||||
version: 11.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
typesafe-i18n:
|
||||
specifier: ^5.27.1
|
||||
version: 5.27.1(typescript@6.0.3)
|
||||
@@ -2447,12 +2444,6 @@ packages:
|
||||
react-dom:
|
||||
optional: true
|
||||
|
||||
react-toastify@11.1.0:
|
||||
resolution: {integrity: sha512-e9h23x3phN0wbFeB6yovmWp7lobzV4CaCH0LO8nVP6H7Y+3GbcLpIzMm9dJhcp1RXbpyfvjgpfXqO80QAmn7sg==}
|
||||
peerDependencies:
|
||||
react: ^18 || ^19
|
||||
react-dom: ^18 || ^19
|
||||
|
||||
react-transition-group@4.4.5:
|
||||
resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==}
|
||||
peerDependencies:
|
||||
@@ -5381,12 +5372,6 @@ snapshots:
|
||||
optionalDependencies:
|
||||
react-dom: 19.2.7(react@19.2.7)
|
||||
|
||||
react-toastify@11.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
|
||||
dependencies:
|
||||
clsx: 2.1.1
|
||||
react: 19.2.7
|
||||
react-dom: 19.2.7(react@19.2.7)
|
||||
|
||||
react-transition-group@4.4.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.7
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { memo, useEffect, useState } from 'react';
|
||||
import { ToastContainer, Zoom } from 'react-toastify';
|
||||
|
||||
import AppRouting from 'AppRouting';
|
||||
import CustomTheme from 'CustomTheme';
|
||||
import { Toaster } from 'components/toast';
|
||||
import TypesafeI18n from 'i18n/i18n-react';
|
||||
import type { Locales } from 'i18n/i18n-types';
|
||||
import { loadLocaleAsync } from 'i18n/i18n-util.async';
|
||||
@@ -22,26 +22,6 @@ const AVAILABLE_LOCALES = [
|
||||
'cz'
|
||||
] as Locales[];
|
||||
|
||||
// Static toast configuration - no need to recreate on every render
|
||||
const TOAST_CONTAINER_PROPS = {
|
||||
position: 'bottom-left' as const,
|
||||
autoClose: 3000,
|
||||
hideProgressBar: false,
|
||||
newestOnTop: false,
|
||||
closeOnClick: true,
|
||||
rtl: false,
|
||||
pauseOnFocusLoss: true,
|
||||
draggable: false,
|
||||
pauseOnHover: false,
|
||||
transition: Zoom,
|
||||
closeButton: false,
|
||||
theme: 'dark' as const,
|
||||
toastStyle: {
|
||||
border: '1px solid #177ac9',
|
||||
width: 'fit-content'
|
||||
}
|
||||
};
|
||||
|
||||
const App = memo(() => {
|
||||
const [wasLoaded, setWasLoaded] = useState(false);
|
||||
const [locale, setLocale] = useState<Locales>('en');
|
||||
@@ -64,7 +44,7 @@ const App = memo(() => {
|
||||
<TypesafeI18n locale={locale}>
|
||||
<CustomTheme>
|
||||
<AppRouting />
|
||||
<ToastContainer {...TOAST_CONTAINER_PROPS} />
|
||||
<Toaster />
|
||||
</CustomTheme>
|
||||
</TypesafeI18n>
|
||||
);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { type FC, memo, useContext, useEffect, useRef } from 'react';
|
||||
import { Navigate, Route, Routes } from 'react-router';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import AuthenticatedRouting from 'AuthenticatedRouting';
|
||||
import SignIn from 'SignIn';
|
||||
import { RequireAuthenticated, RequireUnauthenticated } from 'components';
|
||||
import { toast } from 'components/toast';
|
||||
import { Authentication, AuthenticationContext } from 'contexts/authentication';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { memo, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import ForwardIcon from '@mui/icons-material/Forward';
|
||||
import { Box, Button, Paper, Typography } from '@mui/material';
|
||||
@@ -13,6 +12,7 @@ import {
|
||||
ValidatedPasswordField,
|
||||
ValidatedTextField
|
||||
} from 'components';
|
||||
import { toast } from 'components/toast';
|
||||
import { AuthenticationContext } from 'contexts/authentication';
|
||||
import { PROJECT_NAME } from 'env';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { useBlocker } from 'react-router';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
@@ -27,6 +26,7 @@ import {
|
||||
SectionContent,
|
||||
useLayoutTitle
|
||||
} from 'components';
|
||||
import { toast } from 'components/toast';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { useInterval } from 'utils';
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
@@ -22,6 +21,7 @@ import { useRequest } from 'alova/client';
|
||||
import type Schema from 'async-validator';
|
||||
import type { ValidateFieldsError } from 'async-validator';
|
||||
import { ValidatedTextField } from 'components';
|
||||
import { toast } from 'components/toast';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { updateValue } from 'utils';
|
||||
import { ValidationError, validate } from 'validators';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { useBlocker } from 'react-router';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
@@ -26,6 +25,7 @@ import {
|
||||
SectionContent,
|
||||
useLayoutTitle
|
||||
} from 'components';
|
||||
import { toast } from 'components/toast';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { useInterval } from 'utils';
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useBlocker, useLocation } from 'react-router';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
@@ -46,6 +45,7 @@ import {
|
||||
SectionContent,
|
||||
useLayoutTitle
|
||||
} from 'components';
|
||||
import { toast } from 'components/toast';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
import {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { memo, useContext, useEffect, useState } from 'react';
|
||||
import { IconContext } from 'react-icons/lib';
|
||||
import { Link } from 'react-router';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
@@ -30,6 +29,7 @@ import {
|
||||
SectionContent,
|
||||
useLayoutTitle
|
||||
} from 'components';
|
||||
import { toast } from 'components/toast';
|
||||
import { AuthenticatedContext } from 'contexts/authentication';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { useInterval, usePersistState } from 'utils';
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
} from 'react';
|
||||
import { IconContext } from 'react-icons';
|
||||
import { Link, useNavigate } from 'react-router';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined';
|
||||
import ConstructionIcon from '@mui/icons-material/Construction';
|
||||
@@ -64,6 +63,7 @@ import {
|
||||
SectionContent,
|
||||
useLayoutTitle
|
||||
} from 'components';
|
||||
import { toast } from 'components/toast';
|
||||
import { AuthenticatedContext } from 'contexts/authentication';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { useInterval } from 'utils';
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
|
||||
@@ -26,6 +25,7 @@ import { useRequest } from 'alova/client';
|
||||
import type Schema from 'async-validator';
|
||||
import type { ValidateFieldsError } from 'async-validator';
|
||||
import { ValidatedTextField } from 'components';
|
||||
import { toast } from 'components/toast';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { numberValue, updateValue } from 'utils';
|
||||
import { ValidationError, validate } from 'validators';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { memo, useContext, useState } from 'react';
|
||||
import type { ReactElement } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import CommentIcon from '@mui/icons-material/CommentTwoTone';
|
||||
import DownloadIcon from '@mui/icons-material/GetApp';
|
||||
@@ -25,6 +24,7 @@ import type { SxProps, Theme } from '@mui/material/styles';
|
||||
|
||||
import { useRequest } from 'alova/client';
|
||||
import { SectionContent, useLayoutTitle } from 'components';
|
||||
import { toast } from 'components/toast';
|
||||
import { AuthenticatedContext } from 'contexts/authentication';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { saveFile } from 'utils';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { memo, useState } from 'react';
|
||||
import { useBlocker } from 'react-router';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import CircleIcon from '@mui/icons-material/Circle';
|
||||
@@ -25,6 +24,7 @@ import {
|
||||
SectionContent,
|
||||
useLayoutTitle
|
||||
} from 'components';
|
||||
import { toast } from 'components/toast';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
import { readModules, writeModules } from '../../api/app';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useBlocker } from 'react-router';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
@@ -26,6 +25,7 @@ import {
|
||||
SectionContent,
|
||||
useLayoutTitle
|
||||
} from 'components';
|
||||
import { toast } from 'components/toast';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { useInterval } from 'utils';
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useContext, useRef, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import AddCircleOutlineOutlinedIcon from '@mui/icons-material/AddCircleOutlineOutlined';
|
||||
import KeyboardArrowDownOutlinedIcon from '@mui/icons-material/KeyboardArrowDownOutlined';
|
||||
@@ -21,6 +20,7 @@ import { useTheme } from '@table-library/react-table-library/theme';
|
||||
import type { State } from '@table-library/react-table-library/types/common';
|
||||
import { useRequest } from 'alova/client';
|
||||
import { SectionContent, useLayoutTitle } from 'components';
|
||||
import { toast } from 'components/toast';
|
||||
import { AuthenticatedContext } from 'contexts/authentication';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { useInterval } from 'utils';
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
|
||||
@@ -32,6 +31,7 @@ import {
|
||||
ValidatedTextField,
|
||||
useLayoutTitle
|
||||
} from 'components';
|
||||
import { toast } from 'components/toast';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { numberValue, updateValueDirty, useRest } from 'utils';
|
||||
import { ValidationError, validate } from 'validators';
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import DownloadIcon from '@mui/icons-material/GetApp';
|
||||
@@ -27,6 +26,7 @@ import {
|
||||
SingleUpload,
|
||||
useLayoutTitle
|
||||
} from 'components';
|
||||
import { toast } from 'components/toast';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { saveFile } from 'utils';
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore';
|
||||
@@ -28,6 +27,7 @@ import {
|
||||
ValidatedTextField,
|
||||
useLayoutTitle
|
||||
} from 'components';
|
||||
import { toast } from 'components/toast';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import type { MqttSettingsType } from 'types';
|
||||
import { numberValue, updateValueDirty, useRest } from 'utils';
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
@@ -33,6 +32,7 @@ import {
|
||||
ValidatedTextField,
|
||||
useLayoutTitle
|
||||
} from 'components';
|
||||
import { toast } from 'components/toast';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import type { NTPSettingsType, Time } from 'types';
|
||||
import { formatLocalDateTime, updateValueDirty, useRest } from 'utils';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { memo, useContext, useMemo, useState } from 'react';
|
||||
import { Link } from 'react-router';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
@@ -39,6 +38,7 @@ import {
|
||||
SingleUpload,
|
||||
useLayoutTitle
|
||||
} from 'components';
|
||||
import { toast } from 'components/toast';
|
||||
import { AuthenticatedContext } from 'contexts/authentication';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import type { TranslationFunctions } from 'i18n/i18n-types';
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { memo, useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
@@ -37,6 +36,7 @@ import {
|
||||
ValidatedPasswordField,
|
||||
ValidatedTextField
|
||||
} from 'components';
|
||||
import { toast } from 'components/toast';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import type { NetworkSettingsType } from 'types';
|
||||
import { updateValueDirty, useRest } from 'utils';
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { memo, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import DownloadIcon from '@mui/icons-material/GetApp';
|
||||
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
|
||||
@@ -26,6 +25,7 @@ import {
|
||||
SectionContent,
|
||||
useLayoutTitle
|
||||
} from 'components';
|
||||
import { toast } from 'components/toast';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import type { LogEntry, LogSettings } from 'types';
|
||||
import { LogLevel } from 'types';
|
||||
|
||||
101
interface/src/components/toast/Toaster.tsx
Normal file
101
interface/src/components/toast/Toaster.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
import { memo, useEffect, useRef, useState, useSyncExternalStore } from 'react';
|
||||
|
||||
import Alert from '@mui/material/Alert';
|
||||
import Grow from '@mui/material/Grow';
|
||||
import LinearProgress from '@mui/material/LinearProgress';
|
||||
import Stack from '@mui/material/Stack';
|
||||
|
||||
import { type ToastItem, getSnapshot, removeToast, subscribe } from './toastStore';
|
||||
|
||||
const AUTO_CLOSE_MS = 3000;
|
||||
const TICK_MS = 50;
|
||||
|
||||
// Single toast row: owns its auto-dismiss timer + countdown progress bar, pauses
|
||||
// while the window is unfocused (matching react-toastify's pauseOnFocusLoss).
|
||||
const ToastRow = memo(({ item }: { item: ToastItem }) => {
|
||||
const [open, setOpen] = useState(true);
|
||||
const [remaining, setRemaining] = useState(AUTO_CLOSE_MS);
|
||||
const remainingRef = useRef(AUTO_CLOSE_MS);
|
||||
|
||||
useEffect(() => {
|
||||
let paused = document.hidden;
|
||||
const onVisibility = () => {
|
||||
paused = document.hidden;
|
||||
};
|
||||
document.addEventListener('visibilitychange', onVisibility);
|
||||
|
||||
const timer = setInterval(() => {
|
||||
if (paused) return;
|
||||
remainingRef.current = Math.max(0, remainingRef.current - TICK_MS);
|
||||
setRemaining(remainingRef.current);
|
||||
if (remainingRef.current === 0) setOpen(false);
|
||||
}, TICK_MS);
|
||||
|
||||
return () => {
|
||||
clearInterval(timer);
|
||||
document.removeEventListener('visibilitychange', onVisibility);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Grow in={open} onExited={() => removeToast(item.id)}>
|
||||
<Alert
|
||||
severity={item.severity}
|
||||
variant="filled"
|
||||
onClick={() => setOpen(false)}
|
||||
sx={{
|
||||
width: 'fit-content',
|
||||
maxWidth: 360,
|
||||
minHeight: 64,
|
||||
cursor: 'pointer',
|
||||
border: '1px solid #177ac9',
|
||||
boxShadow: 6,
|
||||
overflow: 'hidden',
|
||||
alignItems: 'center',
|
||||
'& .MuiAlert-icon': { py: 0 },
|
||||
'& .MuiAlert-message': { py: 0, textAlign: 'center', fontSize: '1rem' }
|
||||
}}
|
||||
>
|
||||
{item.message}
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={(remaining / AUTO_CLOSE_MS) * 100}
|
||||
color="inherit"
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
height: 3,
|
||||
opacity: 0.7,
|
||||
backgroundColor: 'transparent'
|
||||
}}
|
||||
/>
|
||||
</Alert>
|
||||
</Grow>
|
||||
);
|
||||
});
|
||||
|
||||
const Toaster = memo(() => {
|
||||
const toasts = useSyncExternalStore(subscribe, getSnapshot);
|
||||
|
||||
return (
|
||||
<Stack
|
||||
spacing={1}
|
||||
sx={{
|
||||
position: 'fixed',
|
||||
bottom: 16,
|
||||
left: 16,
|
||||
zIndex: (theme) => theme.zIndex.snackbar,
|
||||
pointerEvents: 'none',
|
||||
'& > *': { pointerEvents: 'auto' }
|
||||
}}
|
||||
>
|
||||
{toasts.map((item) => (
|
||||
<ToastRow key={item.id} item={item} />
|
||||
))}
|
||||
</Stack>
|
||||
);
|
||||
});
|
||||
|
||||
export default Toaster;
|
||||
3
interface/src/components/toast/index.ts
Normal file
3
interface/src/components/toast/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export { default as Toaster } from './Toaster';
|
||||
export { toast } from './toastStore';
|
||||
export type { ToastSeverity } from './toastStore';
|
||||
47
interface/src/components/toast/toastStore.ts
Normal file
47
interface/src/components/toast/toastStore.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
export type ToastSeverity = 'success' | 'error' | 'info' | 'warning';
|
||||
|
||||
export interface ToastItem {
|
||||
id: number;
|
||||
severity: ToastSeverity;
|
||||
message: string;
|
||||
}
|
||||
|
||||
let toasts: ToastItem[] = [];
|
||||
let nextId = 1;
|
||||
const listeners = new Set<() => void>();
|
||||
|
||||
const emit = () => {
|
||||
for (const listener of listeners) listener();
|
||||
};
|
||||
|
||||
export const subscribe = (listener: () => void): (() => void) => {
|
||||
listeners.add(listener);
|
||||
return () => {
|
||||
listeners.delete(listener);
|
||||
};
|
||||
};
|
||||
|
||||
export const getSnapshot = (): ToastItem[] => toasts;
|
||||
|
||||
const add = (severity: ToastSeverity, message: string): number => {
|
||||
const id = nextId++;
|
||||
toasts = [...toasts, { id, severity, message }];
|
||||
emit();
|
||||
return id;
|
||||
};
|
||||
|
||||
export const removeToast = (id: number): void => {
|
||||
const next = toasts.filter((t) => t.id !== id);
|
||||
if (next.length !== toasts.length) {
|
||||
toasts = next;
|
||||
emit();
|
||||
}
|
||||
};
|
||||
|
||||
// Imperative API mirroring the subset of react-toastify used across the app.
|
||||
export const toast = {
|
||||
success: (message: string) => add('success', message),
|
||||
error: (message: string) => add('error', message),
|
||||
info: (message: string) => add('info', message),
|
||||
warning: (message: string) => add('warning', message)
|
||||
};
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
useState
|
||||
} from 'react';
|
||||
import { Link } from 'react-router';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import CloudUploadIcon from '@mui/icons-material/CloudUpload';
|
||||
@@ -28,6 +27,7 @@ import { callAction } from 'api/app';
|
||||
|
||||
import { dialogStyle } from '@/CustomTheme';
|
||||
import { useRequest } from 'alova/client';
|
||||
import { toast } from 'components/toast';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
const DocumentUploader = styled(Box)<{ active?: boolean }>(({ theme, active }) => ({
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import { Box, Button, Typography } from '@mui/material';
|
||||
@@ -7,6 +6,7 @@ import { Box, Button, Typography } from '@mui/material';
|
||||
import * as SystemApi from 'api/system';
|
||||
|
||||
import { useRequest } from 'alova/client';
|
||||
import { toast } from 'components/toast';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
import DragNdrop from './DragNdrop';
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { redirect } from 'react-router';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import { callAction } from 'api/app';
|
||||
import { ACCESS_TOKEN } from 'api/endpoints';
|
||||
@@ -10,6 +9,7 @@ import * as AuthenticationApi from 'components/routing/authentication';
|
||||
import { useRequest } from 'alova/client';
|
||||
import { LoadingSpinner } from 'components';
|
||||
import { verifyAuthorization } from 'components/routing/authentication';
|
||||
import { toast } from 'components/toast';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import type { Me, VersionsResponse } from 'types';
|
||||
import type { RequiredChildrenProps } from 'utils';
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useBlocker } from 'react-router';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import type { AlovaGenerics, Method } from 'alova';
|
||||
import { useRequest } from 'alova/client';
|
||||
import { toast } from 'components/toast';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
export interface RestRequestOptions<D> {
|
||||
|
||||
Reference in New Issue
Block a user