mirror of https://github.com/cocosolos/ixion.git
Move alerts and progress into context
This commit is contained in:
parent
ddf74e8343
commit
5f215050b7
|
|
@ -12,10 +12,6 @@ import NotFound from './pages/NotFound';
|
||||||
import ServerDetails from './pages/ServerDetails';
|
import ServerDetails from './pages/ServerDetails';
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
const [alertInfo, setAlertInfo] = useState<{
|
|
||||||
message: string;
|
|
||||||
severity: 'error' | 'warning' | 'info' | 'success';
|
|
||||||
} | null>(null);
|
|
||||||
const [servers, setServers] = useState<ServerData[]>([]);
|
const [servers, setServers] = useState<ServerData[]>([]);
|
||||||
|
|
||||||
const [searchState, setSearchState] =
|
const [searchState, setSearchState] =
|
||||||
|
|
@ -24,13 +20,12 @@ export function App() {
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen flex-col">
|
<div className="flex min-h-screen flex-col">
|
||||||
<Header
|
<Header
|
||||||
setAlertInfo={setAlertInfo}
|
|
||||||
servers={servers}
|
servers={servers}
|
||||||
setServers={setServers}
|
setServers={setServers}
|
||||||
searchState={searchState}
|
searchState={searchState}
|
||||||
setSearchState={setSearchState}
|
setSearchState={setSearchState}
|
||||||
/>
|
/>
|
||||||
<AlertComponent alertInfo={alertInfo} setAlertInfo={setAlertInfo} />
|
<AlertComponent />
|
||||||
<Container className="grow p-0">
|
<Container className="grow p-0">
|
||||||
<Box className="m-2">
|
<Box className="m-2">
|
||||||
<Routes>
|
<Routes>
|
||||||
|
|
@ -38,10 +33,7 @@ export function App() {
|
||||||
path="/"
|
path="/"
|
||||||
element={<Home servers={servers} searchState={searchState} />}
|
element={<Home servers={servers} searchState={searchState} />}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route path="/server/:url" element={<ServerDetails />} />
|
||||||
path="/server/:url"
|
|
||||||
element={<ServerDetails setAlertInfo={setAlertInfo} />}
|
|
||||||
/>
|
|
||||||
<Route path="/about" element={<About />} />
|
<Route path="/about" element={<About />} />
|
||||||
<Route path="*" element={<NotFound />} />
|
<Route path="*" element={<NotFound />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ import {
|
||||||
import { useRef, useState } from 'react';
|
import { useRef, useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { postData } from '../apiUtil';
|
import { postData } from '../apiUtil';
|
||||||
|
import { useLoadingContext } from '../context/LoadingContext';
|
||||||
import { ServerData } from '../data/ServerData';
|
import { ServerData } from '../data/ServerData';
|
||||||
import { AlertResponse } from './Alert';
|
|
||||||
|
|
||||||
const AddServerInput = styled('div')(({ theme }) => ({
|
const AddServerInput = styled('div')(({ theme }) => ({
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
|
|
@ -43,15 +43,13 @@ const StyledInputBase = styled(InputBase)(({ theme }) => ({
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default function AddServer({
|
type AddServerProps = {
|
||||||
setAlertInfo,
|
|
||||||
servers,
|
|
||||||
setServers,
|
|
||||||
}: {
|
|
||||||
setAlertInfo: React.Dispatch<React.SetStateAction<AlertResponse>>;
|
|
||||||
servers: ServerData[];
|
servers: ServerData[];
|
||||||
setServers: React.Dispatch<React.SetStateAction<ServerData[]>>;
|
setServers: React.Dispatch<React.SetStateAction<ServerData[]>>;
|
||||||
}) {
|
};
|
||||||
|
|
||||||
|
export default function AddServer({ servers, setServers }: AddServerProps) {
|
||||||
|
const { showAlert } = useLoadingContext();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const containerRef = useRef<HTMLElement>(null);
|
const containerRef = useRef<HTMLElement>(null);
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
@ -68,7 +66,7 @@ export default function AddServer({
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const response = await postData(inputText);
|
const response = await postData(inputText);
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
setAlertInfo({
|
showAlert({
|
||||||
message: response.message,
|
message: response.message,
|
||||||
severity: 'success',
|
severity: 'success',
|
||||||
});
|
});
|
||||||
|
|
@ -81,7 +79,7 @@ export default function AddServer({
|
||||||
toggleShowAddServer();
|
toggleShowAddServer();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setAlertInfo({
|
showAlert({
|
||||||
message: response.message || 'Failed to send data to backend.',
|
message: response.message || 'Failed to send data to backend.',
|
||||||
severity: 'error',
|
severity: 'error',
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,21 @@
|
||||||
import { Alert, Box, Collapse } from '@mui/material';
|
import { Alert, Box, Collapse } from '@mui/material';
|
||||||
|
import { useLoadingContext } from '../context/LoadingContext';
|
||||||
|
|
||||||
export type AlertResponse = {
|
export default function AlertComponent() {
|
||||||
message: string;
|
const { alert, showAlert } = useLoadingContext();
|
||||||
severity: 'error' | 'warning' | 'info' | 'success';
|
|
||||||
} | null;
|
|
||||||
|
|
||||||
export default function AlertComponent({
|
|
||||||
alertInfo,
|
|
||||||
setAlertInfo,
|
|
||||||
}: {
|
|
||||||
alertInfo: AlertResponse;
|
|
||||||
setAlertInfo: React.Dispatch<React.SetStateAction<AlertResponse>>;
|
|
||||||
}) {
|
|
||||||
return (
|
return (
|
||||||
<Box className="m-0">
|
<Box className="m-0">
|
||||||
{alertInfo && (
|
{alert && (
|
||||||
<Collapse in>
|
<Collapse in>
|
||||||
<Alert
|
<Alert
|
||||||
severity={alertInfo.severity}
|
severity={alert.severity}
|
||||||
onClose={() => setAlertInfo(null)}
|
onClose={() => showAlert(null)}
|
||||||
variant="filled"
|
variant="filled"
|
||||||
className="py-0"
|
className="py-0"
|
||||||
square
|
square
|
||||||
>
|
>
|
||||||
{alertInfo.message}
|
{alert.message}
|
||||||
</Alert>
|
</Alert>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import { Card, CardContent, Typography } from '@mui/material';
|
import { Card, CardContent, Typography } from '@mui/material';
|
||||||
|
|
||||||
export default function ErrorCard({ error }: { error: string }) {
|
type ErrorCardProps = { error: string };
|
||||||
|
|
||||||
|
export default function ErrorCard({ error }: ErrorCardProps) {
|
||||||
return (
|
return (
|
||||||
<Card className="mb-2">
|
<Card className="mb-2">
|
||||||
<CardContent className="p-4">
|
<CardContent className="p-4">
|
||||||
|
|
|
||||||
|
|
@ -11,26 +11,26 @@ import IconButton from '@mui/material/IconButton';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { fetchDataFromBackend } from '../apiUtil';
|
import { fetchDataFromBackend } from '../apiUtil';
|
||||||
|
import { useLoadingContext } from '../context/LoadingContext';
|
||||||
import { SearchState } from '../data/SearchState';
|
import { SearchState } from '../data/SearchState';
|
||||||
import { ServerData } from '../data/ServerData';
|
import { ServerData } from '../data/ServerData';
|
||||||
import AddServer from './AddServer';
|
import AddServer from './AddServer';
|
||||||
import { AlertResponse } from './Alert';
|
|
||||||
import SearchServers from './SearchServers';
|
import SearchServers from './SearchServers';
|
||||||
|
|
||||||
export default function Header({
|
type HeaderProps = {
|
||||||
setAlertInfo,
|
|
||||||
servers,
|
|
||||||
setServers,
|
|
||||||
searchState,
|
|
||||||
setSearchState,
|
|
||||||
}: {
|
|
||||||
setAlertInfo: React.Dispatch<React.SetStateAction<AlertResponse>>;
|
|
||||||
servers: ServerData[];
|
servers: ServerData[];
|
||||||
setServers: React.Dispatch<React.SetStateAction<ServerData[]>>;
|
setServers: React.Dispatch<React.SetStateAction<ServerData[]>>;
|
||||||
searchState: SearchState;
|
searchState: SearchState;
|
||||||
setSearchState: React.Dispatch<React.SetStateAction<SearchState>>;
|
setSearchState: React.Dispatch<React.SetStateAction<SearchState>>;
|
||||||
}) {
|
};
|
||||||
const [fetchLoading, setFetchLoading] = useState(0);
|
|
||||||
|
export default function Header({
|
||||||
|
servers,
|
||||||
|
setServers,
|
||||||
|
searchState,
|
||||||
|
setSearchState,
|
||||||
|
}: HeaderProps) {
|
||||||
|
const { progress, setProgress, showAlert } = useLoadingContext();
|
||||||
const [showSearchServer, setShowSearchServer] = useState(false);
|
const [showSearchServer, setShowSearchServer] = useState(false);
|
||||||
const toggleShowSearchServer = () => {
|
const toggleShowSearchServer = () => {
|
||||||
setShowSearchServer((prev) => !prev);
|
setShowSearchServer((prev) => !prev);
|
||||||
|
|
@ -39,27 +39,27 @@ export default function Header({
|
||||||
const fetchServerData = useCallback(async () => {
|
const fetchServerData = useCallback(async () => {
|
||||||
let data: ServerData[] = [];
|
let data: ServerData[] = [];
|
||||||
try {
|
try {
|
||||||
setFetchLoading(25);
|
setProgress(25);
|
||||||
data = await fetchDataFromBackend();
|
data = await fetchDataFromBackend();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof Error) {
|
if (err instanceof Error) {
|
||||||
setAlertInfo({
|
showAlert({
|
||||||
message: err.message,
|
message: err.message,
|
||||||
severity: 'error',
|
severity: 'error',
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setAlertInfo({
|
showAlert({
|
||||||
message: 'An unknown error occurred.',
|
message: 'An unknown error occurred.',
|
||||||
severity: 'error',
|
severity: 'error',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setServers(data);
|
setServers(data);
|
||||||
setFetchLoading(100);
|
setProgress(100);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setFetchLoading(0);
|
setProgress(0);
|
||||||
}, 500);
|
}, 500);
|
||||||
}, [setFetchLoading, setAlertInfo, setServers]);
|
}, [showAlert, setServers, setProgress]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchServerData();
|
fetchServerData();
|
||||||
|
|
@ -81,13 +81,9 @@ export default function Header({
|
||||||
>
|
>
|
||||||
IXION
|
IXION
|
||||||
</Typography>
|
</Typography>
|
||||||
<AddServer
|
<AddServer servers={servers} setServers={setServers} />
|
||||||
setAlertInfo={setAlertInfo}
|
|
||||||
servers={servers}
|
|
||||||
setServers={setServers}
|
|
||||||
/>
|
|
||||||
<Tooltip arrow disableInteractive title="Refresh server data.">
|
<Tooltip arrow disableInteractive title="Refresh server data.">
|
||||||
<IconButton onClick={fetchServerData} disabled={fetchLoading !== 0}>
|
<IconButton onClick={fetchServerData} disabled={progress !== 0}>
|
||||||
<Refresh />
|
<Refresh />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
@ -100,10 +96,10 @@ export default function Header({
|
||||||
</AppBar>
|
</AppBar>
|
||||||
<LinearProgress
|
<LinearProgress
|
||||||
variant="determinate"
|
variant="determinate"
|
||||||
value={fetchLoading}
|
value={progress}
|
||||||
sx={{
|
sx={{
|
||||||
visibility: fetchLoading === 0 ? 'hidden' : 'visible',
|
visibility: progress === 0 ? 'hidden' : 'visible',
|
||||||
height: fetchLoading === 0 ? 0 : '1px',
|
height: progress === 0 ? 0 : '1px',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<SearchServers
|
<SearchServers
|
||||||
|
|
|
||||||
|
|
@ -12,15 +12,17 @@ import {
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { SearchState } from '../data/SearchState';
|
import { SearchState } from '../data/SearchState';
|
||||||
|
|
||||||
|
type SearchServersProps = {
|
||||||
|
showSearchServer: boolean;
|
||||||
|
searchState: SearchState;
|
||||||
|
setSearchState: React.Dispatch<React.SetStateAction<SearchState>>;
|
||||||
|
};
|
||||||
|
|
||||||
export default function SearchServers({
|
export default function SearchServers({
|
||||||
showSearchServer,
|
showSearchServer,
|
||||||
searchState,
|
searchState,
|
||||||
setSearchState,
|
setSearchState,
|
||||||
}: {
|
}: SearchServersProps) {
|
||||||
showSearchServer: boolean;
|
|
||||||
searchState: SearchState;
|
|
||||||
setSearchState: React.Dispatch<React.SetStateAction<SearchState>>;
|
|
||||||
}) {
|
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const [contentHeight, setContentHeight] = useState(0);
|
const [contentHeight, setContentHeight] = useState(0);
|
||||||
const minDistance = 0;
|
const minDistance = 0;
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,9 @@ const expansions = [
|
||||||
</ToggleButton>,
|
</ToggleButton>,
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function ExpansionBar({ server }: { server: ServerData }) {
|
type ExpansionBarProps = { server: ServerData };
|
||||||
|
|
||||||
|
export default function ExpansionBar({ server }: ExpansionBarProps) {
|
||||||
return (
|
return (
|
||||||
<ToggleButtonGroup
|
<ToggleButtonGroup
|
||||||
size="small"
|
size="small"
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,9 @@ import {
|
||||||
ServerSettingsInfo,
|
ServerSettingsInfo,
|
||||||
} from '../../data/ServerData';
|
} from '../../data/ServerData';
|
||||||
|
|
||||||
export default function SettingsChipCloud({ server }: { server: ServerData }) {
|
type SettingsChipCloudProps = { server: ServerData };
|
||||||
|
|
||||||
|
export default function SettingsChipCloud({ server }: SettingsChipCloudProps) {
|
||||||
const renderSettingsChip = ([key, value]: [
|
const renderSettingsChip = ([key, value]: [
|
||||||
string,
|
string,
|
||||||
boolean | string | number,
|
boolean | string | number,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
import {
|
||||||
|
ReactNode,
|
||||||
|
createContext,
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
|
export type AlertInfo = {
|
||||||
|
message: string;
|
||||||
|
severity: 'error' | 'warning' | 'info' | 'success';
|
||||||
|
} | null;
|
||||||
|
|
||||||
|
type LoadingState = {
|
||||||
|
alert: AlertInfo;
|
||||||
|
showAlert: (alertInfo: AlertInfo) => void;
|
||||||
|
progress: number;
|
||||||
|
setProgress: (progress: number) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const LoadingContext = createContext<LoadingState | null>(null);
|
||||||
|
|
||||||
|
// eslint-disable-next-line react-refresh/only-export-components
|
||||||
|
export function useLoadingContext() {
|
||||||
|
const context = useContext(LoadingContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('Loading context error.');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoadingContextProviderProps = {
|
||||||
|
children: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function LoadingContextProvider({
|
||||||
|
children,
|
||||||
|
}: LoadingContextProviderProps) {
|
||||||
|
const [alert, setAlert] = useState<AlertInfo>(null);
|
||||||
|
const [progress, setProgress] = useState(0);
|
||||||
|
|
||||||
|
const showAlert = useCallback((alertInfo: AlertInfo) => {
|
||||||
|
setAlert(alertInfo);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LoadingContext.Provider
|
||||||
|
// eslint-disable-next-line react/jsx-no-constructed-context-values
|
||||||
|
value={{ alert, showAlert, progress, setProgress }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</LoadingContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -16,7 +16,16 @@ type ThemeModeState = {
|
||||||
setThemeMode: React.Dispatch<React.SetStateAction<ThemeMode>>;
|
setThemeMode: React.Dispatch<React.SetStateAction<ThemeMode>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ThemeModeContext = createContext<ThemeModeState | null>(null);
|
const ThemeModeContext = createContext<ThemeModeState | null>(null);
|
||||||
|
|
||||||
|
// eslint-disable-next-line react-refresh/only-export-components
|
||||||
|
export function useThemeModeContext() {
|
||||||
|
const context = useContext(ThemeModeContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('Theme context error.');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
type ThemeModeContextProviderProps = {
|
type ThemeModeContextProviderProps = {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
|
|
@ -82,12 +91,3 @@ export function ThemeContextProvider({
|
||||||
</StyledEngineProvider>
|
</StyledEngineProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line react-refresh/only-export-components
|
|
||||||
export function useThemeModeContext() {
|
|
||||||
const context = useContext(ThemeModeContext);
|
|
||||||
if (!context) {
|
|
||||||
throw new Error('Theme context error.');
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { StyledEngineProvider } from '@mui/material/styles';
|
||||||
import { StrictMode } from 'react';
|
import { StrictMode } from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import { WrappedApp } from './App';
|
import { WrappedApp } from './App';
|
||||||
|
import { LoadingContextProvider } from './context/LoadingContext';
|
||||||
import { ThemeContextProvider } from './context/ThemeContext';
|
import { ThemeContextProvider } from './context/ThemeContext';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
|
|
@ -13,8 +14,10 @@ root.render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<StyledEngineProvider injectFirst>
|
<StyledEngineProvider injectFirst>
|
||||||
<ThemeContextProvider>
|
<ThemeContextProvider>
|
||||||
<CssBaseline />
|
<LoadingContextProvider>
|
||||||
<WrappedApp />
|
<CssBaseline />
|
||||||
|
<WrappedApp />
|
||||||
|
</LoadingContextProvider>
|
||||||
</ThemeContextProvider>
|
</ThemeContextProvider>
|
||||||
</StyledEngineProvider>
|
</StyledEngineProvider>
|
||||||
</StrictMode>
|
</StrictMode>
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,14 @@ import { Box } from '@mui/material';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { fetchDataFromBackend, fetchDemo } from '../apiUtil';
|
import { fetchDataFromBackend, fetchDemo } from '../apiUtil';
|
||||||
import { AlertResponse } from '../components/Alert';
|
|
||||||
import ErrorCard from '../components/ErrorCard';
|
import ErrorCard from '../components/ErrorCard';
|
||||||
import SettingsDataGrid from '../components/Server/SettingsDataGrid';
|
import SettingsDataGrid from '../components/Server/SettingsDataGrid';
|
||||||
import ServerCard from '../components/ServerCard';
|
import ServerCard from '../components/ServerCard';
|
||||||
|
import { useLoadingContext } from '../context/LoadingContext';
|
||||||
import { ServerData } from '../data/ServerData';
|
import { ServerData } from '../data/ServerData';
|
||||||
|
|
||||||
export default function ServerDetails({
|
export default function ServerDetails() {
|
||||||
setAlertInfo,
|
const { showAlert } = useLoadingContext();
|
||||||
}: {
|
|
||||||
setAlertInfo: React.Dispatch<React.SetStateAction<AlertResponse>>;
|
|
||||||
}) {
|
|
||||||
const { url } = useParams();
|
const { url } = useParams();
|
||||||
const [server, setServer] = useState<ServerData>();
|
const [server, setServer] = useState<ServerData>();
|
||||||
const [error, setError] = useState<string>('');
|
const [error, setError] = useState<string>('');
|
||||||
|
|
@ -28,7 +25,7 @@ export default function ServerDetails({
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof Error) {
|
if (err instanceof Error) {
|
||||||
setAlertInfo({
|
showAlert({
|
||||||
message: err.message,
|
message: err.message,
|
||||||
severity: 'error',
|
severity: 'error',
|
||||||
});
|
});
|
||||||
|
|
@ -41,7 +38,7 @@ export default function ServerDetails({
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchServerData();
|
fetchServerData();
|
||||||
}, [url, setServer, setAlertInfo, setError]);
|
}, [url, setServer, showAlert, setError]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue