mirror of https://github.com/cocosolos/ixion.git
Add search state to URL params
This commit is contained in:
parent
1c0f3582cc
commit
9e598dc280
|
|
@ -1,6 +1,7 @@
|
|||
import { Refresh, Search } from '@mui/icons-material';
|
||||
import {
|
||||
AppBar,
|
||||
Badge,
|
||||
Box,
|
||||
LinearProgress,
|
||||
Toolbar,
|
||||
|
|
@ -9,11 +10,11 @@ import {
|
|||
} from '@mui/material';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import { fetchDataFromBackend } from '../../apiUtil';
|
||||
import { useLoadingContext } from '../../context/LoadingContext';
|
||||
import { useThemeModeContext } from '../../context/ThemeContext';
|
||||
import { SearchState } from '../../data/SearchState';
|
||||
import { SearchState, SearchStateDefaults } from '../../data/SearchState';
|
||||
import { ServerData } from '../../data/ServerData';
|
||||
import AlertComponent from '../Alert';
|
||||
import AddServer from './AddServer';
|
||||
|
|
@ -32,9 +33,12 @@ export default function Header({
|
|||
searchState,
|
||||
setSearchState,
|
||||
}: HeaderProps) {
|
||||
const location = useLocation();
|
||||
const { themeMode } = useThemeModeContext();
|
||||
const { progress, setProgress, showAlert } = useLoadingContext();
|
||||
const [showSearchServer, setShowSearchServer] = useState(false);
|
||||
const [filtersApplied, setFiltersApplied] = useState(false);
|
||||
const [initialFetch, setInitialFetch] = useState(false);
|
||||
const toggleShowSearchServer = () => {
|
||||
setShowSearchServer((prev) => !prev);
|
||||
};
|
||||
|
|
@ -65,8 +69,14 @@ export default function Header({
|
|||
}, [showAlert, setServers, setProgress]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!initialFetch) {
|
||||
fetchServerData();
|
||||
}, [fetchServerData]);
|
||||
setInitialFetch(true);
|
||||
}
|
||||
setFiltersApplied(
|
||||
JSON.stringify(searchState) !== JSON.stringify(SearchStateDefaults)
|
||||
);
|
||||
}, [initialFetch, fetchServerData, searchState]);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
|
|
@ -83,6 +93,11 @@ export default function Header({
|
|||
sx={{
|
||||
flexGrow: 1,
|
||||
}}
|
||||
onClick={(event) => {
|
||||
if (location.pathname === '/') {
|
||||
event.preventDefault();
|
||||
}
|
||||
}}
|
||||
>
|
||||
IXION
|
||||
</Typography>
|
||||
|
|
@ -94,7 +109,9 @@ export default function Header({
|
|||
</Tooltip>
|
||||
<Tooltip arrow disableInteractive title="Filter servers.">
|
||||
<IconButton onClick={toggleShowSearchServer}>
|
||||
<Badge color="error" variant="dot" invisible={!filtersApplied}>
|
||||
<Search />
|
||||
</Badge>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Toolbar>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import {
|
|||
Typography,
|
||||
} from '@mui/material';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { SearchState, SearchStateDefaults } from '../../data/SearchState';
|
||||
|
||||
type SearchServersProps = {
|
||||
|
|
@ -27,15 +28,51 @@ export default function SearchServers({
|
|||
setSearchState,
|
||||
}: SearchServersProps) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const location = useLocation();
|
||||
const [initialPath] = useState(location.pathname);
|
||||
const [contentHeight, setContentHeight] = useState(0);
|
||||
|
||||
function parseSearchParams(searchParams: string): SearchState {
|
||||
const params = new URLSearchParams(searchParams);
|
||||
const parsedState = SearchStateDefaults;
|
||||
|
||||
const keys = Object.keys(SearchStateDefaults) as Array<keyof SearchState>;
|
||||
|
||||
keys.forEach((key) => {
|
||||
if (params.has(key)) {
|
||||
const value = params.get(key) as string;
|
||||
const decodedValue = decodeURIComponent(value);
|
||||
|
||||
if (key === 'maxLevel') {
|
||||
const maxLevelArray = decodedValue.split(',').map(Number);
|
||||
parsedState[key] =
|
||||
maxLevelArray.length === 1 ? [1, maxLevelArray[0]] : maxLevelArray;
|
||||
} else if (key === 'expansionsEnabled') {
|
||||
parsedState[key] = decodedValue.split(',') as string[];
|
||||
} else {
|
||||
parsedState[key] = decodedValue;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return parsedState as SearchState;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// Populate search state with URL params only if loading the home page
|
||||
// Home page manages updating URL params
|
||||
if (initialPath === '/') {
|
||||
const params = parseSearchParams(location.search);
|
||||
if (params !== SearchStateDefaults) {
|
||||
setSearchState(params);
|
||||
}
|
||||
}
|
||||
if (showSearchServer && containerRef.current) {
|
||||
setContentHeight(containerRef.current.scrollHeight);
|
||||
} else {
|
||||
setContentHeight(0);
|
||||
}
|
||||
}, [showSearchServer]);
|
||||
}, [showSearchServer, location.search, setSearchState, initialPath]);
|
||||
|
||||
const handleChange = (
|
||||
name: string,
|
||||
|
|
@ -67,24 +104,29 @@ export default function SearchServers({
|
|||
newSearchExpansions: string[]
|
||||
) => {
|
||||
if (newSearchExpansions.length === 0) {
|
||||
setSearchState({ ...searchState, expansions: null });
|
||||
setSearchState({ ...searchState, expansionsEnabled: null });
|
||||
} else if (
|
||||
searchState.expansions &&
|
||||
searchState.expansions.includes('none') &&
|
||||
searchState.expansionsEnabled &&
|
||||
searchState.expansionsEnabled.includes('none') &&
|
||||
newSearchExpansions.length > 1
|
||||
) {
|
||||
setSearchState({
|
||||
...searchState,
|
||||
expansions: newSearchExpansions.filter((item) => item !== 'none'),
|
||||
expansionsEnabled: newSearchExpansions.filter(
|
||||
(item) => item !== 'none'
|
||||
),
|
||||
});
|
||||
} else if (
|
||||
searchState.expansions &&
|
||||
!searchState.expansions.includes('none') &&
|
||||
searchState.expansionsEnabled &&
|
||||
!searchState.expansionsEnabled.includes('none') &&
|
||||
newSearchExpansions.includes('none')
|
||||
) {
|
||||
setSearchState({ ...searchState, expansions: ['none'] });
|
||||
setSearchState({ ...searchState, expansionsEnabled: ['none'] });
|
||||
} else {
|
||||
setSearchState({ ...searchState, expansions: newSearchExpansions });
|
||||
setSearchState({
|
||||
...searchState,
|
||||
expansionsEnabled: newSearchExpansions,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -214,7 +256,7 @@ export default function SearchServers({
|
|||
</Typography>
|
||||
</Tooltip>
|
||||
<ToggleButtonGroup
|
||||
value={searchState.expansions}
|
||||
value={searchState.expansionsEnabled}
|
||||
onChange={handleSearchExpansions}
|
||||
size="small"
|
||||
aria-labelledby="expansions-button-group"
|
||||
|
|
@ -509,9 +551,9 @@ export default function SearchServers({
|
|||
Fields of Valor
|
||||
</Typography>
|
||||
<ToggleButtonGroup
|
||||
value={searchState.fieldsOfValor}
|
||||
value={searchState.fov}
|
||||
onChange={(_event, value) => {
|
||||
handleChange('fieldsOfValor', value);
|
||||
handleChange('fov', value);
|
||||
}}
|
||||
size="small"
|
||||
aria-labelledby="fields-of-valor-button-group"
|
||||
|
|
@ -551,9 +593,9 @@ export default function SearchServers({
|
|||
Grounds of Valor
|
||||
</Typography>
|
||||
<ToggleButtonGroup
|
||||
value={searchState.groundsOfValor}
|
||||
value={searchState.gov}
|
||||
onChange={(_event, value) => {
|
||||
handleChange('groundsOfValor', value);
|
||||
handleChange('gov', value);
|
||||
}}
|
||||
size="small"
|
||||
aria-labelledby="grounds-of-valor-button-group"
|
||||
|
|
@ -593,9 +635,9 @@ export default function SearchServers({
|
|||
Records of Eminence
|
||||
</Typography>
|
||||
<ToggleButtonGroup
|
||||
value={searchState.recordsOfEminence}
|
||||
value={searchState.roe}
|
||||
onChange={(_event, value) => {
|
||||
handleChange('recordsOfEminence', value);
|
||||
handleChange('roe', value);
|
||||
}}
|
||||
size="small"
|
||||
aria-labelledby="records-of-eminence-button-group"
|
||||
|
|
@ -648,6 +690,9 @@ export default function SearchServers({
|
|||
>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
if (location.pathname === '/') {
|
||||
window.history.replaceState({}, '', '/');
|
||||
}
|
||||
setSearchState(SearchStateDefaults);
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,27 +1,27 @@
|
|||
export type SearchState = {
|
||||
name: string;
|
||||
multibox: string[] | null;
|
||||
trusts: string[] | null;
|
||||
levelSync: string[] | null;
|
||||
homePoint: string[] | null;
|
||||
survivalGuide: string[] | null;
|
||||
recordsOfEminence: string[] | null;
|
||||
fieldsOfValor: string[] | null;
|
||||
groundsOfValor: string[] | null;
|
||||
maxLevel: number[];
|
||||
expansions: string[] | null;
|
||||
expansionsEnabled: string[] | null;
|
||||
multibox: string | null;
|
||||
trusts: string | null;
|
||||
levelSync: string | null;
|
||||
homePoint: string | null;
|
||||
survivalGuide: string | null;
|
||||
fov: string | null;
|
||||
gov: string | null;
|
||||
roe: string | null;
|
||||
};
|
||||
|
||||
export const SearchStateDefaults: SearchState = {
|
||||
name: '',
|
||||
maxLevel: [1, 99],
|
||||
expansionsEnabled: null,
|
||||
multibox: null,
|
||||
trusts: null,
|
||||
levelSync: null,
|
||||
homePoint: null,
|
||||
recordsOfEminence: null,
|
||||
fieldsOfValor: null,
|
||||
groundsOfValor: null,
|
||||
survivalGuide: null,
|
||||
maxLevel: [1, 99],
|
||||
expansions: null,
|
||||
fov: null,
|
||||
gov: null,
|
||||
roe: null,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { Box } from '@mui/material';
|
||||
import { useEffect } from 'react';
|
||||
import ErrorCard from '../components/ErrorCard';
|
||||
import ServerCard from '../components/Server/ServerCard';
|
||||
import SettingsChipCloud from '../components/Server/SettingsChipCloud';
|
||||
import { SearchState } from '../data/SearchState';
|
||||
import { SearchState, SearchStateDefaults } from '../data/SearchState';
|
||||
import { ServerData } from '../data/ServerData';
|
||||
|
||||
export default function Home({
|
||||
|
|
@ -12,6 +13,40 @@ export default function Home({
|
|||
servers: ServerData[];
|
||||
searchState: SearchState;
|
||||
}) {
|
||||
function serializeSearchState(search: SearchState): string {
|
||||
const params = new URLSearchParams();
|
||||
|
||||
Object.entries(search).forEach(([key, value]) => {
|
||||
if (
|
||||
value !== null &&
|
||||
value !== undefined &&
|
||||
!(key === 'name' && value === '') &&
|
||||
!(
|
||||
key === 'maxLevel' &&
|
||||
JSON.stringify(value) === JSON.stringify(SearchStateDefaults.maxLevel)
|
||||
)
|
||||
) {
|
||||
if (Array.isArray(value)) {
|
||||
params.append(key, value.join(','));
|
||||
} else {
|
||||
params.append(key, value);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return params.toString();
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// Update URL params whenever search state changes
|
||||
const searchParams = serializeSearchState(searchState);
|
||||
if (searchParams.length !== 0) {
|
||||
window.history.replaceState({}, '', `?${searchParams}`);
|
||||
} else {
|
||||
window.history.replaceState({}, '', '/');
|
||||
}
|
||||
}, [searchState]);
|
||||
|
||||
const filterServers = (server: ServerData): boolean => {
|
||||
// Name
|
||||
if (searchState.name.length > 0) {
|
||||
|
|
@ -92,12 +127,12 @@ export default function Home({
|
|||
}
|
||||
// Records of Eminence
|
||||
if (
|
||||
searchState.recordsOfEminence &&
|
||||
searchState.roe &&
|
||||
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');
|
||||
const searchEnabled = searchState.roe.includes('enabled');
|
||||
const searchDisabled = searchState.roe.includes('disabled');
|
||||
if (
|
||||
(serverEnabled && searchDisabled && !searchEnabled) ||
|
||||
(!serverEnabled && searchEnabled && !searchDisabled)
|
||||
|
|
@ -107,13 +142,13 @@ export default function Home({
|
|||
}
|
||||
// Fields of Valor
|
||||
if (
|
||||
searchState.fieldsOfValor &&
|
||||
searchState.fov &&
|
||||
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');
|
||||
const searchEnabled = searchState.fov.includes('enabled');
|
||||
const searchDisabled = searchState.fov.includes('disabled');
|
||||
if (
|
||||
(serverEnabled && searchDisabled && !searchEnabled) ||
|
||||
(!serverEnabled && searchEnabled && !searchDisabled)
|
||||
|
|
@ -123,13 +158,13 @@ export default function Home({
|
|||
}
|
||||
// Grounds of Valor
|
||||
if (
|
||||
searchState.groundsOfValor &&
|
||||
searchState.gov &&
|
||||
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');
|
||||
const searchEnabled = searchState.gov.includes('enabled');
|
||||
const searchDisabled = searchState.gov.includes('disabled');
|
||||
if (
|
||||
(serverEnabled && searchDisabled && !searchEnabled) ||
|
||||
(!serverEnabled && searchEnabled && !searchDisabled)
|
||||
|
|
@ -138,12 +173,12 @@ export default function Home({
|
|||
}
|
||||
}
|
||||
// Expansions
|
||||
if (searchState.expansions) {
|
||||
if (searchState.expansionsEnabled) {
|
||||
if (!server.expansions) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
searchState.expansions.includes('none') &&
|
||||
searchState.expansionsEnabled.includes('none') &&
|
||||
(server.expansions.rotz ||
|
||||
server.expansions.cop ||
|
||||
server.expansions.toau ||
|
||||
|
|
@ -152,19 +187,32 @@ export default function Home({
|
|||
) {
|
||||
return false;
|
||||
}
|
||||
if (searchState.expansions.includes('rotz') !== server.expansions.rotz) {
|
||||
if (
|
||||
searchState.expansionsEnabled.includes('rotz') !==
|
||||
server.expansions.rotz
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (searchState.expansions.includes('cop') !== server.expansions.cop) {
|
||||
if (
|
||||
searchState.expansionsEnabled.includes('cop') !== server.expansions.cop
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (searchState.expansions.includes('toau') !== server.expansions.toau) {
|
||||
if (
|
||||
searchState.expansionsEnabled.includes('toau') !==
|
||||
server.expansions.toau
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (searchState.expansions.includes('wotg') !== server.expansions.wotg) {
|
||||
if (
|
||||
searchState.expansionsEnabled.includes('wotg') !==
|
||||
server.expansions.wotg
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (searchState.expansions.includes('soa') !== server.expansions.soa) {
|
||||
if (
|
||||
searchState.expansionsEnabled.includes('soa') !== server.expansions.soa
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue