Add more filters

This commit is contained in:
Corey 2024-05-08 10:57:25 +00:00
parent 814092a9f0
commit 52908764ed
9 changed files with 769 additions and 489 deletions

View File

@ -3,7 +3,7 @@ import { useState } from 'react';
import { BrowserRouter, Route, Routes } from 'react-router-dom'; 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/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';

View File

@ -11,9 +11,9 @@ import {
} from '@mui/material'; } from '@mui/material';
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 { useLoadingContext } from '../../context/LoadingContext';
import { ServerData } from '../data/ServerData'; import { ServerData } from '../../data/ServerData';
const AddServerInput = styled('div')(({ theme }) => ({ const AddServerInput = styled('div')(({ theme }) => ({
position: 'relative', position: 'relative',

View File

@ -10,10 +10,11 @@ import {
import IconButton from '@mui/material/IconButton'; 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 { useLoadingContext } from '../../context/LoadingContext';
import { SearchState } from '../data/SearchState'; import { useThemeModeContext } from '../../context/ThemeContext';
import { ServerData } from '../data/ServerData'; import { SearchState } from '../../data/SearchState';
import { ServerData } from '../../data/ServerData';
import AddServer from './AddServer'; import AddServer from './AddServer';
import SearchServers from './SearchServers'; import SearchServers from './SearchServers';
@ -30,6 +31,7 @@ export default function Header({
searchState, searchState,
setSearchState, setSearchState,
}: HeaderProps) { }: HeaderProps) {
const { themeMode } = useThemeModeContext();
const { progress, setProgress, showAlert } = useLoadingContext(); const { progress, setProgress, showAlert } = useLoadingContext();
const [showSearchServer, setShowSearchServer] = useState(false); const [showSearchServer, setShowSearchServer] = useState(false);
const toggleShowSearchServer = () => { const toggleShowSearchServer = () => {
@ -67,7 +69,7 @@ export default function Header({
return ( return (
<Box> <Box>
<AppBar position="static"> <AppBar position="static" elevation={themeMode === 'light' ? 8 : 3}>
<Toolbar className="min-h-min py-1"> <Toolbar className="min-h-min py-1">
<Typography <Typography
component={Link} component={Link}

View File

@ -0,0 +1,640 @@
import { RestartAlt } from '@mui/icons-material';
import {
Box,
Container,
Grid,
IconButton,
Input,
Slider,
TextField,
ToggleButton,
ToggleButtonGroup,
Tooltip,
Typography,
} from '@mui/material';
import { useEffect, useRef, useState } from 'react';
import { SearchState, SearchStateDefaults } from '../../data/SearchState';
type SearchServersProps = {
showSearchServer: boolean;
searchState: SearchState;
setSearchState: React.Dispatch<React.SetStateAction<SearchState>>;
};
export default function SearchServers({
showSearchServer,
searchState,
setSearchState,
}: SearchServersProps) {
const containerRef = useRef<HTMLDivElement>(null);
const [contentHeight, setContentHeight] = useState(0);
useEffect(() => {
if (showSearchServer && containerRef.current) {
setContentHeight(containerRef.current.scrollHeight);
} else {
setContentHeight(0);
}
}, [showSearchServer]);
const handleChange = (
name: string,
value: string | string[] | number | number[],
activeThumb?: number
) => {
let newValue = value;
if (name === 'maxLevel') {
if (
!Array.isArray(value) ||
!value.every((item) => typeof item === 'number')
) {
return;
}
newValue = [
activeThumb === 0
? Math.min((newValue as number[])[0], searchState.maxLevel[1])
: searchState.maxLevel[0],
activeThumb === 0
? searchState.maxLevel[1]
: Math.max((newValue as number[])[1], searchState.maxLevel[0]),
];
}
setSearchState({ ...searchState, [name]: newValue });
};
const handleSearchExpansions = (
_event: React.MouseEvent<HTMLElement>,
newSearchExpansions: string[]
) => {
if (newSearchExpansions.length === 0) {
setSearchState({ ...searchState, expansions: null });
} else if (
searchState.expansions &&
searchState.expansions.includes('none') &&
newSearchExpansions.length > 1
) {
setSearchState({
...searchState,
expansions: newSearchExpansions.filter((item) => item !== 'none'),
});
} else if (
searchState.expansions &&
!searchState.expansions.includes('none') &&
newSearchExpansions.includes('none')
) {
setSearchState({ ...searchState, expansions: ['none'] });
} else {
setSearchState({ ...searchState, expansions: newSearchExpansions });
}
};
return (
<Container
ref={containerRef}
className="overflow-hidden"
sx={{
transition: 'all 0.3s ease',
maxHeight: showSearchServer ? contentHeight : 0,
borderBottom: showSearchServer
? (theme) =>
theme.palette.mode === 'dark'
? '1px solid rgba(255, 255, 255, .12)'
: '1px solid rgba(0, 0, 0, .12)'
: 'none',
backgroundColor: (theme) =>
theme.palette.mode === 'dark'
? 'rgba(255, 255, 255, .06)'
: 'rgba(0, 0, 0, .06)',
}}
>
<Grid container spacing={2} className="py-2">
{/* Name */}
<Grid item xs={12}>
<TextField
value={searchState.name}
onChange={(event) => {
handleChange('name', event.target.value);
}}
variant="standard"
autoComplete="false"
fullWidth
label="Name"
size="small"
className="pb-2"
/>
</Grid>
{/* Max Level */}
<Grid
item
xs={12}
className="pt-0"
display="flex"
flexDirection="column"
alignItems="center"
>
<Typography id="max-level" variant="caption">
Max Level
</Typography>
</Grid>
<Grid item xs={12} className="flex pt-0">
<Input
value={searchState.maxLevel[0]}
size="small"
onChange={(event) => {
let value = parseInt(event.target.value, 10);
if (Number.isNaN(value) || value < 1) {
value = 1;
} else if (value > 99) {
value = 99;
}
handleChange('maxLevel', [value, searchState.maxLevel[1]], 0);
}}
// onBlur={handleBlur}
inputProps={{
name: 'max-level-min',
step: 1,
min: 1,
max: 99,
type: 'number',
'aria-labelledby': 'max-level',
}}
/>
<Slider
aria-labelledby="max-level"
value={searchState.maxLevel}
onChange={(_event, value, activeThumb) => {
handleChange('maxLevel', value, activeThumb);
}}
valueLabelDisplay="auto"
disableSwap
className="mx-4"
size="small"
min={1}
max={99}
/>
<Input
value={searchState.maxLevel[1]}
size="small"
onChange={(event) => {
let value = parseInt(event.target.value, 10);
if (Number.isNaN(value) || value < 1) {
value = 1;
} else if (value > 99) {
value = 99;
}
handleChange('maxLevel', [searchState.maxLevel[0], value], 1);
}}
// onBlur={handleBlur}
inputProps={{
name: 'max-level-max',
step: 1,
min: 1,
max: 99,
type: 'number',
'aria-labelledby': 'max-level',
}}
/>
</Grid>
{/* Expansion */}
<Grid
item
xs={12}
display="flex"
flexDirection="column"
alignItems="center"
>
<Tooltip
title="Enabled expansions, exact match."
arrow
disableInteractive
placement="top"
>
<Typography id="expansions-button-group" variant="caption">
Expansions
</Typography>
</Tooltip>
<ToggleButtonGroup
value={searchState.expansions}
onChange={handleSearchExpansions}
size="small"
aria-labelledby="expansions-button-group"
sx={{
display: 'flex',
width: '100%',
}}
>
<ToggleButton
key="none"
value="none"
aria-label="none-enabled"
className="py-0"
sx={{ width: '20%' }}
>
None
</ToggleButton>
<ToggleButton
key="rotz"
value="rotz"
aria-label="rotz-enabled"
className="py-0"
sx={{ width: '20%' }}
>
RotZ
</ToggleButton>
<ToggleButton
key="cop"
value="cop"
aria-label="cop-enabled"
className="py-0"
sx={{ width: '20%' }}
>
CoP
</ToggleButton>
<ToggleButton
key="toau"
value="toau"
aria-label="toau-enabled"
className="py-0"
sx={{ width: '20%' }}
>
ToAU
</ToggleButton>
<ToggleButton
key="wotg"
value="wotg"
aria-label="wotg-enabled"
className="py-0"
sx={{ width: '20%' }}
>
WotG
</ToggleButton>
<ToggleButton
key="soa"
value="soa"
aria-label="soa-enabled"
className="py-0"
sx={{ width: '20%' }}
>
SoA
</ToggleButton>
</ToggleButtonGroup>
</Grid>
{/* Multiboxing */}
<Grid
item
xs={12}
display="flex"
flexDirection="column"
alignItems="center"
>
<Typography id="multibox-button-group" variant="caption">
Multiboxing
</Typography>
<ToggleButtonGroup
value={searchState.multibox}
onChange={(_event, value) => {
handleChange('multibox', value);
}}
size="small"
aria-labelledby="multibox-button-group"
sx={{
display: 'flex',
width: '100%',
}}
exclusive
>
<ToggleButton
value="none"
aria-label="no-multiboxing"
className="py-0"
sx={{ width: '33.33%' }}
>
None
</ToggleButton>
<ToggleButton
value="limited"
aria-label="limited-multiboxing"
className="py-0"
sx={{ width: '33.33%' }}
>
Limited
</ToggleButton>
<ToggleButton
value="unlimited"
aria-label="unlimited-multiboxing"
className="py-0"
sx={{ width: '33.33%' }}
>
Unlimited
</ToggleButton>
</ToggleButtonGroup>
</Grid>
{/* Trusts */}
<Grid
item
xs={6}
display="flex"
flexDirection="column"
alignItems="center"
>
<Typography id="trust-button-group" variant="caption">
Trusts
</Typography>
<ToggleButtonGroup
value={searchState.trusts}
onChange={(_event, value) => {
handleChange('trusts', value);
}}
size="small"
aria-labelledby="trust-button-group"
exclusive
sx={{
display: 'flex',
width: '100%',
}}
>
<ToggleButton
value="disabled"
aria-label="trust-disabled"
className="py-0"
sx={{ width: '50%' }}
>
Disabled
</ToggleButton>
<ToggleButton
value="enabled"
aria-label="trust-enabled"
className="py-0"
sx={{ width: '50%' }}
>
Enabled
</ToggleButton>
</ToggleButtonGroup>
</Grid>
{/* Level Sync */}
<Grid
item
xs={6}
display="flex"
flexDirection="column"
alignItems="center"
>
<Typography id="level-sync-button-group" variant="caption">
Level Sync
</Typography>
<ToggleButtonGroup
value={searchState.levelSync}
onChange={(_event, value) => {
handleChange('levelSync', value);
}}
size="small"
aria-labelledby="level-sync-button-group"
exclusive
sx={{
display: 'flex',
width: '100%',
}}
>
<ToggleButton
value="disabled"
aria-label="level-sync-disabled"
className="py-0"
sx={{ width: '50%' }}
>
Disabled
</ToggleButton>
<ToggleButton
value="enabled"
aria-label="level-sync-enabled"
className="py-0"
sx={{ width: '50%' }}
>
Enabled
</ToggleButton>
</ToggleButtonGroup>
</Grid>
{/* Home Point Teleport */}
<Grid
item
xs={6}
display="flex"
flexDirection="column"
alignItems="center"
>
<Typography id="home-point-button-group" variant="caption">
Home Point Teleport
</Typography>
<ToggleButtonGroup
value={searchState.homePoint}
onChange={(_event, value) => {
handleChange('homePoint', value);
}}
size="small"
aria-labelledby="home-point-button-group"
exclusive
sx={{
display: 'flex',
width: '100%',
}}
>
<ToggleButton
value="disabled"
aria-label="home-point-disabled"
className="py-0"
sx={{ width: '50%' }}
>
Disabled
</ToggleButton>
<ToggleButton
value="enabled"
aria-label="home-point-enabled"
className="py-0"
sx={{ width: '50%' }}
>
Enabled
</ToggleButton>
</ToggleButtonGroup>
</Grid>
{/* Survival Guide Teleport */}
<Grid
item
xs={6}
display="flex"
flexDirection="column"
alignItems="center"
>
<Typography id="survival-guide-button-group" variant="caption">
Survival Guides
</Typography>
<ToggleButtonGroup
value={searchState.survivalGuide}
onChange={(_event, value) => {
handleChange('survivalGuide', value);
}}
size="small"
aria-labelledby="survival-guide-button-group"
exclusive
sx={{
display: 'flex',
width: '100%',
}}
>
<ToggleButton
value="disabled"
aria-label="survival-guide-disabled"
className="py-0"
sx={{ width: '50%' }}
>
Disabled
</ToggleButton>
<ToggleButton
value="enabled"
aria-label="survival-guide-enabled"
className="py-0"
sx={{ width: '50%' }}
>
Enabled
</ToggleButton>
</ToggleButtonGroup>
</Grid>
{/* Records of Eminence */}
<Grid
item
xs={4}
display="flex"
flexDirection="column"
alignItems="center"
>
<Typography id="roe-button-group" variant="caption">
Records of Eminence
</Typography>
<ToggleButtonGroup
value={searchState.recordsOfEminence}
onChange={(_event, value) => {
handleChange('recordsOfEminence', value);
}}
size="small"
aria-labelledby="records-of-eminence-button-group"
exclusive
sx={{
display: 'flex',
width: '100%',
}}
>
<ToggleButton
value="disabled"
aria-label="records-of-eminence-disabled"
className="py-0"
sx={{ width: '50%' }}
>
Disabled
</ToggleButton>
<ToggleButton
value="enabled"
aria-label="records-of-eminence-enabled"
className="py-0"
sx={{ width: '50%' }}
>
Enabled
</ToggleButton>
</ToggleButtonGroup>
</Grid>
{/* Fields of Valor */}
<Grid
item
xs={4}
display="flex"
flexDirection="column"
alignItems="center"
>
<Typography id="fields-of-valor-button-group" variant="caption">
Fields of Valor
</Typography>
<ToggleButtonGroup
value={searchState.fieldsOfValor}
onChange={(_event, value) => {
handleChange('fieldsOfValor', value);
}}
size="small"
aria-labelledby="fields-of-valor-button-group"
exclusive
sx={{
display: 'flex',
width: '100%',
}}
>
<ToggleButton
value="disabled"
aria-label="fields-of-valor-disabled"
className="py-0"
sx={{ width: '50%' }}
>
Disabled
</ToggleButton>
<ToggleButton
value="enabled"
aria-label="fields-of-valor-enabled"
className="py-0"
sx={{ width: '50%' }}
>
Enabled
</ToggleButton>
</ToggleButtonGroup>
</Grid>
{/* Grounds of Valor */}
<Grid
item
xs={4}
display="flex"
flexDirection="column"
alignItems="center"
>
<Typography id="grounds-of-valor-button-group" variant="caption">
Grounds of Valor
</Typography>
<ToggleButtonGroup
value={searchState.groundsOfValor}
onChange={(_event, value) => {
handleChange('groundsOfValor', value);
}}
size="small"
aria-labelledby="grounds-of-valor-button-group"
exclusive
sx={{
display: 'flex',
width: '100%',
}}
>
<ToggleButton
value="disabled"
aria-label="grounds-of-valor-disabled"
className="py-0"
sx={{ width: '50%' }}
>
Disabled
</ToggleButton>
<ToggleButton
value="enabled"
aria-label="grounds-of-valor-enabled"
className="py-0"
sx={{ width: '50%' }}
>
Enabled
</ToggleButton>
</ToggleButtonGroup>
</Grid>
<Box className="flex min-w-full justify-end pt-2">
<Tooltip title="Reset filters." arrow disableInteractive>
<IconButton
onClick={() => {
setSearchState(SearchStateDefaults);
}}
>
<RestartAlt />
</IconButton>
</Tooltip>
</Box>
</Grid>
</Container>
);
}

View File

@ -1,452 +0,0 @@
import {
Container,
Divider,
Grid,
Input,
Slider,
TextField,
ToggleButton,
ToggleButtonGroup,
Tooltip,
Typography,
} from '@mui/material';
import { useEffect, useRef, useState } from 'react';
import { SearchState } from '../data/SearchState';
type SearchServersProps = {
showSearchServer: boolean;
searchState: SearchState;
setSearchState: React.Dispatch<React.SetStateAction<SearchState>>;
};
export default function SearchServers({
showSearchServer,
searchState,
setSearchState,
}: SearchServersProps) {
const containerRef = useRef<HTMLDivElement>(null);
const [contentHeight, setContentHeight] = useState(0);
const minDistance = 0;
useEffect(() => {
if (showSearchServer && containerRef.current) {
setContentHeight(containerRef.current.scrollHeight);
} else {
setContentHeight(0);
}
}, [showSearchServer]);
const handleSearchName = (event: React.ChangeEvent<HTMLInputElement>) => {
setSearchState({ ...searchState, name: event.target.value });
};
const handleSearchMultibox = (
_event: React.MouseEvent<HTMLElement>,
newSearchMultibox: string[]
) => {
setSearchState({ ...searchState, multibox: newSearchMultibox });
};
const handleSearchTrusts = (
_event: React.MouseEvent<HTMLElement>,
newSearchTrusts: string[]
) => {
setSearchState({ ...searchState, trusts: newSearchTrusts });
};
const handleSearchLevelSync = (
_event: React.MouseEvent<HTMLElement>,
newSearchLevelSync: string[]
) => {
setSearchState({ ...searchState, levelSync: newSearchLevelSync });
};
const handleSearchMaxLevelMin = (
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => {
let newValue = parseInt(event.target.value, 10);
if (Number.isNaN(newValue) || newValue < 1) {
newValue = 1;
} else if (newValue > 99) {
newValue = 99;
}
setSearchState({
...searchState,
maxLevel: [
Math.min(newValue, searchState.maxLevel[1] - minDistance),
searchState.maxLevel[1],
],
});
};
const handleSearchMaxLevelMax = (
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => {
let newValue = parseInt(event.target.value, 10);
if (Number.isNaN(newValue) || newValue < 1) {
newValue = 1;
} else if (newValue > 99) {
newValue = 99;
}
setSearchState({
...searchState,
maxLevel: [
searchState.maxLevel[0],
Math.max(newValue, searchState.maxLevel[0] + minDistance),
],
});
};
const handleSearchMaxLevel = (
_event: Event,
newValue: number | number[],
activeThumb: number
) => {
if (!Array.isArray(newValue)) {
return;
}
if (activeThumb === 0) {
setSearchState({
...searchState,
maxLevel: [
Math.min(newValue[0], searchState.maxLevel[1] - minDistance),
searchState.maxLevel[1],
],
});
} else {
setSearchState({
...searchState,
maxLevel: [
searchState.maxLevel[0],
Math.max(newValue[1], searchState.maxLevel[0] + minDistance),
],
});
}
};
const handleSearchExpansions = (
_event: React.MouseEvent<HTMLElement>,
newSearchExpansions: string[]
) => {
if (newSearchExpansions.length === 0) {
setSearchState({ ...searchState, expansions: null });
} else if (
searchState.expansions &&
searchState.expansions.includes('none') &&
newSearchExpansions.length > 1
) {
setSearchState({
...searchState,
expansions: newSearchExpansions.filter((item) => item !== 'none'),
});
} else if (
searchState.expansions &&
!searchState.expansions.includes('none') &&
newSearchExpansions.includes('none')
) {
setSearchState({ ...searchState, expansions: ['none'] });
} else {
setSearchState({ ...searchState, expansions: newSearchExpansions });
}
};
return (
<>
<Container
ref={containerRef}
className="overflow-hidden"
sx={{
transition: 'all 0.3s ease',
maxHeight: showSearchServer ? contentHeight : 0,
backgroundColor: (theme) =>
theme.palette.mode === 'dark'
? 'rgba(255, 255, 255, .06)'
: 'rgba(0, 0, 0, .06)',
}}
>
<Grid container spacing={2} className="py-2">
{/* Name */}
<Grid item xs={12}>
<TextField
value={searchState.name}
onChange={handleSearchName}
variant="standard"
autoComplete="false"
fullWidth
label="Name"
size="small"
className="pb-2"
/>
</Grid>
{/* Max Level */}
<Grid
item
xs={12}
className="pt-0"
display="flex"
flexDirection="column"
alignItems="center"
>
<Typography id="max-level" variant="caption">
Max Level
</Typography>
</Grid>
<Grid item xs={12} className="flex pt-0">
<Input
value={searchState.maxLevel[0]}
size="small"
onChange={handleSearchMaxLevelMin}
// onBlur={handleBlur}
inputProps={{
name: 'max-level-min',
step: 1,
min: 1,
max: 99,
type: 'number',
'aria-labelledby': 'max-level',
}}
/>
<Slider
aria-labelledby="max-level"
value={searchState.maxLevel}
onChange={handleSearchMaxLevel}
valueLabelDisplay="auto"
disableSwap
className="mx-4"
size="small"
min={1}
max={99}
/>
<Input
value={searchState.maxLevel[1]}
size="small"
onChange={handleSearchMaxLevelMax}
// onBlur={handleBlur}
inputProps={{
name: 'max-level-max',
step: 1,
min: 1,
max: 99,
type: 'number',
'aria-labelledby': 'max-level',
}}
/>
</Grid>
{/* Expansion */}
<Grid
item
xs={12}
display="flex"
flexDirection="column"
alignItems="center"
>
<Tooltip
title="Enabled expansions, exact match."
arrow
disableInteractive
placement="top"
>
<Typography id="expansions-button-group" variant="caption">
Expansions
</Typography>
</Tooltip>
<ToggleButtonGroup
value={searchState.expansions}
onChange={handleSearchExpansions}
size="small"
aria-labelledby="expansions-button-group"
sx={{
display: 'flex',
width: '100%',
}}
>
<ToggleButton
key="none"
value="none"
aria-label="none-enabled"
className="py-0"
sx={{ width: '20%' }}
>
None
</ToggleButton>
<ToggleButton
key="rotz"
value="rotz"
aria-label="rotz-enabled"
className="py-0"
sx={{ width: '20%' }}
>
RotZ
</ToggleButton>
<ToggleButton
key="cop"
value="cop"
aria-label="cop-enabled"
className="py-0"
sx={{ width: '20%' }}
>
CoP
</ToggleButton>
<ToggleButton
key="toau"
value="toau"
aria-label="toau-enabled"
className="py-0"
sx={{ width: '20%' }}
>
ToAU
</ToggleButton>
<ToggleButton
key="wotg"
value="wotg"
aria-label="wotg-enabled"
className="py-0"
sx={{ width: '20%' }}
>
WotG
</ToggleButton>
<ToggleButton
key="soa"
value="soa"
aria-label="soa-enabled"
className="py-0"
sx={{ width: '20%' }}
>
SoA
</ToggleButton>
</ToggleButtonGroup>
</Grid>
{/* Multiboxing */}
<Grid
item
xs={12}
display="flex"
flexDirection="column"
alignItems="center"
>
<Typography id="multibox-button-group" variant="caption">
Multiboxing
</Typography>
<ToggleButtonGroup
value={searchState.multibox}
onChange={handleSearchMultibox}
size="small"
aria-labelledby="multibox-button-group"
sx={{
display: 'flex',
width: '100%',
}}
exclusive
>
<ToggleButton
value="none"
aria-label="no multiboxing"
className="py-0"
sx={{ width: '33.33%' }}
>
None
</ToggleButton>
<ToggleButton
value="limited"
aria-label="limited multiboxing"
className="py-0"
sx={{ width: '33.33%' }}
>
Limited
</ToggleButton>
<ToggleButton
value="unlimited"
aria-label="unlimited multiboxing"
className="py-0"
sx={{ width: '33.33%' }}
>
Unlimited
</ToggleButton>
</ToggleButtonGroup>
</Grid>
{/* Trusts */}
<Grid
item
xs={6}
display="flex"
flexDirection="column"
alignItems="center"
>
<Typography id="trust-button-group" variant="caption">
Trusts
</Typography>
<ToggleButtonGroup
value={searchState.trusts}
onChange={handleSearchTrusts}
size="small"
aria-labelledby="trust-button-group"
exclusive
sx={{
display: 'flex',
width: '100%',
}}
>
<ToggleButton
value="disabled"
aria-label="trust disabled"
className="py-0"
sx={{ width: '50%' }}
>
Disabled
</ToggleButton>
<ToggleButton
value="enabled"
aria-label="trust enabled"
className="py-0"
sx={{ width: '50%' }}
>
Enabled
</ToggleButton>
</ToggleButtonGroup>
</Grid>
{/* Level Sync */}
<Grid
item
xs={6}
display="flex"
flexDirection="column"
alignItems="center"
>
<Typography id="level-sync-button-group" variant="caption">
Level Sync
</Typography>
<ToggleButtonGroup
value={searchState.levelSync}
onChange={handleSearchLevelSync}
size="small"
aria-labelledby="level-sync-button-group"
exclusive
sx={{
display: 'flex',
width: '100%',
}}
>
<ToggleButton
value="disabled"
aria-label="level-sync-disabled"
className="py-0"
sx={{ width: '50%' }}
>
Disabled
</ToggleButton>
<ToggleButton
value="enabled"
aria-label="level-sync-enabled"
className="py-0"
sx={{ width: '50%' }}
>
Enabled
</ToggleButton>
</ToggleButtonGroup>
</Grid>
</Grid>
</Container>
{contentHeight > 0 && <Divider />}
</>
);
}

View File

@ -26,16 +26,16 @@ import {
useState, useState,
} from 'react'; } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { useThemeModeContext } from '../context/ThemeContext'; import { useThemeModeContext } from '../../context/ThemeContext';
import { ServerData } from '../data/ServerData'; import { ServerData } from '../../data/ServerData';
import CopyImageIcon from '../images/copy-image.png'; import CopyImageIcon from '../../images/copy-image.png';
import ExpansionBar from './Server/ExpansionsBar';
import SettingsDataGrid from './Server/SettingsDataGrid';
import { import {
Accordion, Accordion,
AccordionDetails, AccordionDetails,
AccordionSummary, AccordionSummary,
} from './Themed/Accordion'; } from '../Themed/Accordion';
import ExpansionBar from './ExpansionsBar';
import SettingsDataGrid from './SettingsDataGrid';
function scrollToTop() { function scrollToTop() {
window.scrollTo({ top: 0, behavior: 'smooth' }); window.scrollTo({ top: 0, behavior: 'smooth' });

View File

@ -3,6 +3,11 @@ export type SearchState = {
multibox: string[] | null; multibox: string[] | null;
trusts: string[] | null; trusts: string[] | null;
levelSync: string[] | null; levelSync: string[] | null;
homePoint: string[] | null;
survivalGuide: string[] | null;
recordsOfEminence: string[] | null;
fieldsOfValor: string[] | null;
groundsOfValor: string[] | null;
maxLevel: number[]; maxLevel: number[];
expansions: string[] | null; expansions: string[] | null;
}; };
@ -12,6 +17,11 @@ export const SearchStateDefaults: SearchState = {
multibox: null, multibox: null,
trusts: null, trusts: null,
levelSync: null, levelSync: null,
homePoint: null,
recordsOfEminence: null,
fieldsOfValor: null,
groundsOfValor: null,
survivalGuide: null,
maxLevel: [1, 99], maxLevel: [1, 99],
expansions: null, expansions: null,
}; };

View File

@ -1,7 +1,7 @@
import { Box } from '@mui/material'; import { Box } from '@mui/material';
import ErrorCard from '../components/ErrorCard'; import ErrorCard from '../components/ErrorCard';
import ServerCard from '../components/Server/ServerCard';
import SettingsChipCloud from '../components/Server/SettingsChipCloud'; import SettingsChipCloud from '../components/Server/SettingsChipCloud';
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';
@ -13,51 +13,131 @@ export default function Home({
searchState: SearchState; searchState: SearchState;
}) { }) {
const filterServers = (server: ServerData): boolean => { const filterServers = (server: ServerData): boolean => {
// Name
if (searchState.name.length > 0) { if (searchState.name.length > 0) {
const serverName = server.name.toLowerCase(); const serverName = server.name.toLowerCase();
if (!serverName.includes(searchState.name.toLowerCase())) { if (!serverName.includes(searchState.name.toLowerCase())) {
return false; return false;
} }
} }
// Max Level
if (server.max_level < searchState.maxLevel[0]) { if (server.max_level < searchState.maxLevel[0]) {
return false; return false;
} }
if (server.max_level > searchState.maxLevel[1]) { if (server.max_level > searchState.maxLevel[1]) {
return false; return false;
} }
// Trusts
if ( if (
searchState.trusts && searchState.trusts &&
typeof server.settings_summary['MAIN.ENABLE_TRUST_CASTING'] === 'number' typeof server.settings_summary['MAIN.ENABLE_TRUST_CASTING'] === 'number'
) { ) {
const serverTrusts = const serverEnabled =
server.settings_summary['MAIN.ENABLE_TRUST_CASTING'] === 1; server.settings_summary['MAIN.ENABLE_TRUST_CASTING'] === 1;
const searchEnabled = searchState.trusts.includes('enabled'); const searchEnabled = searchState.trusts.includes('enabled');
const searchDisabled = searchState.trusts.includes('disabled'); const searchDisabled = searchState.trusts.includes('disabled');
if (serverTrusts && searchDisabled && !searchEnabled) { if (
return false; (serverEnabled && searchDisabled && !searchEnabled) ||
} (!serverEnabled && searchEnabled && !searchDisabled)
if (!serverTrusts && searchEnabled && !searchDisabled) { ) {
return false; return false;
} }
} }
// Level Sync
if ( if (
searchState.levelSync && searchState.levelSync &&
typeof server.settings_summary['MAP.LEVEL_SYNC_ENABLE'] === 'boolean' typeof server.settings_summary['MAP.LEVEL_SYNC_ENABLE'] === 'boolean'
) { ) {
const serverLevelSync = server.settings_summary['MAP.LEVEL_SYNC_ENABLE']; const serverEnabled = server.settings_summary['MAP.LEVEL_SYNC_ENABLE'];
const searchEnabled = searchState.levelSync.includes('enabled'); const searchEnabled = searchState.levelSync.includes('enabled');
const searchDisabled = searchState.levelSync.includes('disabled'); const searchDisabled = searchState.levelSync.includes('disabled');
if (serverLevelSync && searchDisabled && !searchEnabled) { if (
return false; (serverEnabled && searchDisabled && !searchEnabled) ||
} (!serverEnabled && searchEnabled && !searchDisabled)
if (!serverLevelSync && searchEnabled && !searchDisabled) { ) {
return false; return false;
} }
} }
// Home Point Teleport
if (
searchState.homePoint &&
typeof server.settings_summary['MAIN.HOMEPOINT_TELEPORT'] === 'number'
) {
const serverEnabled =
server.settings_summary['MAIN.HOMEPOINT_TELEPORT'] === 1;
const searchEnabled = searchState.homePoint.includes('enabled');
const searchDisabled = searchState.homePoint.includes('disabled');
if (
(serverEnabled && searchDisabled && !searchEnabled) ||
(!serverEnabled && searchEnabled && !searchDisabled)
) {
return false;
}
}
// Survival Guides
if (
searchState.survivalGuide &&
typeof server.settings_summary['MAIN.ENABLE_SURVIVAL_GUIDE'] === 'number'
) {
const serverEnabled =
server.settings_summary['MAIN.ENABLE_SURVIVAL_GUIDE'] === 1;
const searchEnabled = searchState.survivalGuide.includes('enabled');
const searchDisabled = searchState.survivalGuide.includes('disabled');
if (
(serverEnabled && searchDisabled && !searchEnabled) ||
(!serverEnabled && searchEnabled && !searchDisabled)
) {
return false;
}
}
// Records of Eminence
if (
searchState.recordsOfEminence &&
typeof server.settings_summary['MAIN.ENABLE_ROE'] === 'number'
) {
const serverEnabled = server.settings_summary['MAIN.ENABLE_ROE'] === 1;
const searchEnabled = searchState.recordsOfEminence.includes('enabled');
const searchDisabled = searchState.recordsOfEminence.includes('disabled');
if (
(serverEnabled && searchDisabled && !searchEnabled) ||
(!serverEnabled && searchEnabled && !searchDisabled)
) {
return false;
}
}
// Fields of Valor
if (
searchState.fieldsOfValor &&
typeof server.settings_summary['MAIN.ENABLE_FIELD_MANUALS'] === 'number'
) {
const serverEnabled =
server.settings_summary['MAIN.ENABLE_FIELD_MANUALS'] === 1;
const searchEnabled = searchState.fieldsOfValor.includes('enabled');
const searchDisabled = searchState.fieldsOfValor.includes('disabled');
if (
(serverEnabled && searchDisabled && !searchEnabled) ||
(!serverEnabled && searchEnabled && !searchDisabled)
) {
return false;
}
}
// Grounds of Valor
if (
searchState.groundsOfValor &&
typeof server.settings_summary['MAIN.ENABLE_GROUNDS_TOMES'] === 'number'
) {
const serverEnabled =
server.settings_summary['MAIN.ENABLE_GROUNDS_TOMES'] === 1;
const searchEnabled = searchState.groundsOfValor.includes('enabled');
const searchDisabled = searchState.groundsOfValor.includes('disabled');
if (
(serverEnabled && searchDisabled && !searchEnabled) ||
(!serverEnabled && searchEnabled && !searchDisabled)
) {
return false;
}
}
// Expansions
if (searchState.expansions) { if (searchState.expansions) {
if (!server.expansions) { if (!server.expansions) {
return false; return false;
@ -72,23 +152,23 @@ export default function Home({
) { ) {
return false; return false;
} }
if (searchState.expansions.includes('rotz') && server.expansions.rotz) { if (searchState.expansions.includes('rotz') !== server.expansions.rotz) {
return false; return false;
} }
if (searchState.expansions.includes('cop') && server.expansions.cop) { if (searchState.expansions.includes('cop') !== server.expansions.cop) {
return false; return false;
} }
if (searchState.expansions.includes('toau') && server.expansions.toau) { if (searchState.expansions.includes('toau') !== server.expansions.toau) {
return false; return false;
} }
if (searchState.expansions.includes('wotg') && server.expansions.wotg) { if (searchState.expansions.includes('wotg') !== server.expansions.wotg) {
return false; return false;
} }
if (searchState.expansions.includes('soa') && server.expansions.soa) { if (searchState.expansions.includes('soa') !== server.expansions.soa) {
return false; return false;
} }
} }
// Multibox
if (searchState.multibox) { if (searchState.multibox) {
const serverMultibox = server.login_limit; const serverMultibox = server.login_limit;
if (searchState.multibox.includes('none') && serverMultibox !== 1) { if (searchState.multibox.includes('none') && serverMultibox !== 1) {

View File

@ -3,8 +3,8 @@ 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 ErrorCard from '../components/ErrorCard'; import ErrorCard from '../components/ErrorCard';
import ServerCard from '../components/Server/ServerCard';
import SettingsDataGrid from '../components/Server/SettingsDataGrid'; import SettingsDataGrid from '../components/Server/SettingsDataGrid';
import ServerCard from '../components/ServerCard';
import { useLoadingContext } from '../context/LoadingContext'; import { useLoadingContext } from '../context/LoadingContext';
import { ServerData } from '../data/ServerData'; import { ServerData } from '../data/ServerData';