Change interfaces to types, adjust theme context

This commit is contained in:
Corey 2024-05-07 00:05:37 +00:00
parent 2d1a162100
commit 5d041d5e77
14 changed files with 98 additions and 87 deletions

View File

@ -4,8 +4,8 @@ import { BrowserRouter, Route, Routes } from 'react-router-dom';
import AlertComponent from './components/Alert'; import AlertComponent from './components/Alert';
import Footer from './components/Footer'; import Footer from './components/Footer';
import Header from './components/Header'; import Header from './components/Header';
import SearchState, { SearchStateDefaults } from './data/SearchState'; import { SearchState, SearchStateDefaults } from './data/SearchState';
import ServerData from './data/ServerData'; import { ServerData } from './data/ServerData';
import About from './pages/About'; import About from './pages/About';
import Home from './pages/Home'; import Home from './pages/Home';
import NotFound from './pages/NotFound'; import NotFound from './pages/NotFound';

View File

@ -12,7 +12,7 @@ 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 ServerData from '../data/ServerData'; import { ServerData } from '../data/ServerData';
import { AlertResponse } from './Alert'; import { AlertResponse } from './Alert';
const AddServerInput = styled('div')(({ theme }) => ({ const AddServerInput = styled('div')(({ theme }) => ({

View File

@ -1,5 +1,5 @@
import { ToggleButton, ToggleButtonGroup } from '@mui/material'; import { ToggleButton, ToggleButtonGroup } from '@mui/material';
import ServerData from '../data/ServerData'; import { ServerData } from '../data/ServerData';
const expansions = [ const expansions = [
<ToggleButton <ToggleButton

View File

@ -1,17 +1,15 @@
import { Brightness4, Brightness7, QuestionMark } from '@mui/icons-material'; import { Brightness4, Brightness7, QuestionMark } from '@mui/icons-material';
import { AppBar, Box, Toolbar, Tooltip, useTheme } from '@mui/material'; import { AppBar, Box, Toolbar, Tooltip } from '@mui/material';
import IconButton from '@mui/material/IconButton'; import IconButton from '@mui/material/IconButton';
import { useContext } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { ThemeContext } from '../context/ThemeContext'; import { ThemeMode, useThemeModeContext } from '../context/ThemeContext';
function scrollToTop() { function scrollToTop() {
window.scrollTo({ top: 0, behavior: 'smooth' }); // Scrolls to the top of the page smoothly window.scrollTo({ top: 0, behavior: 'smooth' }); // Scrolls to the top of the page smoothly
} }
export default function Footer() { export default function Footer() {
const theme = useTheme(); const { themeMode, setThemeMode } = useThemeModeContext();
const { switchColorMode } = useContext(ThemeContext);
return ( return (
<Box className="bottom-0 w-full flex-none"> <Box className="bottom-0 w-full flex-none">
@ -42,12 +40,14 @@ export default function Footer() {
</IconButton> </IconButton>
</Tooltip> </Tooltip>
<Tooltip title="Toggle Theme" arrow disableInteractive> <Tooltip title="Toggle Theme" arrow disableInteractive>
<IconButton onClick={switchColorMode}> <IconButton
{theme.palette.mode === 'dark' ? ( onClick={() => {
<Brightness7 /> setThemeMode((prev: ThemeMode) =>
) : ( prev === 'light' ? 'dark' : 'light'
<Brightness4 /> );
)} }}
>
{themeMode === 'dark' ? <Brightness7 /> : <Brightness4 />}
</IconButton> </IconButton>
</Tooltip> </Tooltip>
</Box> </Box>

View File

@ -11,8 +11,8 @@ 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 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 { AlertResponse } from './Alert';
import SearchServers from './SearchServers'; import SearchServers from './SearchServers';

View File

@ -10,7 +10,7 @@ import {
Typography, Typography,
} from '@mui/material'; } from '@mui/material';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import SearchState from '../data/SearchState'; import { SearchState } from '../data/SearchState';
export default function SearchServers({ export default function SearchServers({
showSearchServer, showSearchServer,

View File

@ -22,7 +22,8 @@ import {
} from '@mui/material'; } from '@mui/material';
import { useState } from 'react'; import { useState } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import ServerData, { import {
ServerData,
ServerSetting, ServerSetting,
ServerSettingsInfo, ServerSettingsInfo,
} from '../data/ServerData'; } from '../data/ServerData';

View File

@ -3,7 +3,7 @@ import { Card, IconButton, Tooltip, Typography } from '@mui/material';
import Modal from '@mui/material/Modal'; import Modal from '@mui/material/Modal';
import { useState } from 'react'; import { useState } from 'react';
import { fetchDataFromBackend } from '../apiUtil'; import { fetchDataFromBackend } from '../apiUtil';
import ServerData from '../data/ServerData'; import { ServerData } from '../data/ServerData';
import ServerSettingsDataGrid from './ServerSettingsDataGrid'; import ServerSettingsDataGrid from './ServerSettingsDataGrid';
export default function ServerDetailsModal({ id }: { id: number }) { export default function ServerDetailsModal({ id }: { id: number }) {

View File

@ -6,14 +6,14 @@ import {
ServerSettingsInfo, ServerSettingsInfo,
} from '../data/ServerData'; } from '../data/ServerData';
interface KeyValueRow { type KeyValueRow = {
id: number; id: number;
key: string; key: string;
rawValue: string | number | boolean; rawValue: string | number | boolean;
name: string; name: string;
value: string | number | boolean; value: string | number | boolean;
description: string; description: string;
} };
const columns: GridColDef[] = [ const columns: GridColDef[] = [
{ field: 'key', headerName: 'Key', align: 'right', flex: 4 }, { field: 'key', headerName: 'Key', align: 'right', flex: 4 },

View File

@ -1,83 +1,93 @@
import { useMediaQuery } from '@mui/material'; import { useMediaQuery } from '@mui/material';
import { createTheme, StyledEngineProvider } from '@mui/material/styles'; import { createTheme, StyledEngineProvider } from '@mui/material/styles';
import ThemeProvider from '@mui/material/styles/ThemeProvider'; import ThemeProvider from '@mui/material/styles/ThemeProvider';
import { createContext, ReactNode, useEffect, useMemo, useState } from 'react'; import React, {
createContext,
ReactNode,
useContext,
useEffect,
useState,
} from 'react';
type ThemeContextType = { export type ThemeMode = 'dark' | 'light';
switchColorMode: () => void;
type ThemeModeState = {
themeMode: ThemeMode;
setThemeMode: React.Dispatch<React.SetStateAction<ThemeMode>>;
}; };
type ThemeProviderProps = { export const ThemeModeContext = createContext<ThemeModeState | null>(null);
type ThemeModeContextProviderProps = {
children: ReactNode; children: ReactNode;
}; };
export const ThemeContext = createContext<ThemeContextType>({ export function ThemeContextProvider({
switchColorMode: () => {}, children,
}); }: ThemeModeContextProviderProps) {
export function ThemeContextProvider({ children }: ThemeProviderProps) {
const rootElement = document.getElementById('root'); const rootElement = document.getElementById('root');
const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)'); const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
const [mode, setMode] = useState<'light' | 'dark'>(() => { const [themeMode, setThemeMode] = useState<ThemeMode>(() => {
const manualTheme = localStorage.getItem('theme'); const manualThemeMode = localStorage.getItem('themeMode') as ThemeMode;
if (manualTheme && (manualTheme === 'light' || manualTheme === 'dark')) { if (manualThemeMode) {
return manualTheme; return manualThemeMode;
} }
return prefersDarkMode ? 'dark' : 'light'; return prefersDarkMode ? 'dark' : 'light';
}); });
useEffect(() => { useEffect(() => {
localStorage.setItem('theme', mode); localStorage.setItem('themeMode', themeMode as ThemeMode);
}, [mode]); }, [themeMode]);
const switchColorMode = () => { const muiTheme = createTheme({
setMode((prevMode) => (prevMode === 'light' ? 'dark' : 'light')); palette: {
}; mode: themeMode,
},
const theme = useMemo( // All `Portal`-related components need to have the the main app wrapper element as a container
() => // so that they are in the subtree under the element used in the `important` option of the Tailwind's config.
createTheme({ components: {
palette: { MuiPopover: {
mode, defaultProps: {
container: rootElement,
}, },
// All `Portal`-related components need to have the the main app wrapper element as a container },
// so that they are in the subtree under the element used in the `important` option of the Tailwind's config. MuiPopper: {
components: { defaultProps: {
MuiPopover: { container: rootElement,
defaultProps: {
container: rootElement,
},
},
MuiPopper: {
defaultProps: {
container: rootElement,
},
},
MuiDialog: {
defaultProps: {
container: rootElement,
},
},
MuiModal: {
defaultProps: {
container: rootElement,
},
},
}, },
}), },
[rootElement, mode] MuiDialog: {
); defaultProps: {
container: rootElement,
},
},
MuiModal: {
defaultProps: {
container: rootElement,
},
},
},
});
return ( return (
<StyledEngineProvider injectFirst> <StyledEngineProvider injectFirst>
<ThemeContext.Provider <ThemeModeContext.Provider
value={ value={
// eslint-disable-next-line react/jsx-no-constructed-context-values // eslint-disable-next-line react/jsx-no-constructed-context-values
{ switchColorMode } { themeMode, setThemeMode }
} }
> >
<ThemeProvider theme={theme}>{children}</ThemeProvider> <ThemeProvider theme={muiTheme}>{children}</ThemeProvider>
</ThemeContext.Provider> </ThemeModeContext.Provider>
</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;
}

View File

@ -1,11 +1,11 @@
export default interface SearchState { export type SearchState = {
name: string; name: string;
multibox: string[] | null; multibox: string[] | null;
trusts: string[] | null; trusts: string[] | null;
levelSync: string[] | null; levelSync: string[] | null;
maxLevel: number[]; maxLevel: number[];
expansions: string[] | null; expansions: string[] | null;
} };
export const SearchStateDefaults: SearchState = { export const SearchStateDefaults: SearchState = {
name: '', name: '',

View File

@ -1,17 +1,17 @@
import LsbDefaults from '../../../api/defaultLsbSettings.json'; import LsbDefaults from '../../../api/defaultLsbSettings.json';
export interface ServerSetting { export type ServerSetting = {
name: string; name: string;
description: string; description: string;
unit?: string; unit?: string;
transform?: (arg: boolean | string | number) => boolean | string | number; transform?: (arg: boolean | string | number) => boolean | string | number;
} };
export interface ServerSettings { export type ServerSettings = {
[key: string]: boolean | string | number; [key: string]: boolean | string | number;
} };
export default interface ServerData { export type ServerData = {
name: string; name: string;
url: string; url: string;
location: string; location: string;
@ -22,7 +22,7 @@ export default interface ServerData {
active_sessions: number; active_sessions: number;
updated: string; updated: string;
up: boolean; up: boolean;
} };
export const DemoServerData: ServerData = { export const DemoServerData: ServerData = {
name: 'LandSandBoat Demo', name: 'LandSandBoat Demo',

View File

@ -1,8 +1,8 @@
import { Box } from '@mui/material'; import { Box } from '@mui/material';
import ErrorCard from '../components/ErrorCard'; import ErrorCard from '../components/ErrorCard';
import ServerCard from '../components/ServerCard'; import ServerCard from '../components/ServerCard';
import SearchState from '../data/SearchState'; import { SearchState } from '../data/SearchState';
import ServerData from '../data/ServerData'; import { ServerData } from '../data/ServerData';
export default function Home({ export default function Home({
servers, servers,

View File

@ -22,7 +22,7 @@ import { AlertResponse } from '../components/Alert';
import ErrorCard from '../components/ErrorCard'; import ErrorCard from '../components/ErrorCard';
import ExpansionBar from '../components/ExpansionsBar'; import ExpansionBar from '../components/ExpansionsBar';
import ServerSettingsDataGrid from '../components/ServerSettingsDataGrid'; import ServerSettingsDataGrid from '../components/ServerSettingsDataGrid';
import ServerData from '../data/ServerData'; import { ServerData } from '../data/ServerData';
import CopyImageIcon from '../images/copy-image.png'; import CopyImageIcon from '../images/copy-image.png';
export default function ServerDetails({ export default function ServerDetails({