From 52908764edfd3693d102fa0205005c995d8a0083 Mon Sep 17 00:00:00 2001 From: Corey Date: Wed, 8 May 2024 10:57:25 +0000 Subject: [PATCH] Add more filters --- client/src/App.tsx | 2 +- .../src/components/{ => Header}/AddServer.tsx | 6 +- client/src/components/{ => Header}/Header.tsx | 12 +- .../src/components/Header/SearchServers.tsx | 640 ++++++++++++++++++ client/src/components/SearchServers.tsx | 452 ------------- .../components/{ => Server}/ServerCard.tsx | 12 +- client/src/data/SearchState.tsx | 10 + client/src/pages/Home.tsx | 122 +++- client/src/pages/ServerDetails.tsx | 2 +- 9 files changed, 769 insertions(+), 489 deletions(-) rename client/src/components/{ => Header}/AddServer.tsx (96%) rename client/src/components/{ => Header}/Header.tsx (87%) create mode 100644 client/src/components/Header/SearchServers.tsx delete mode 100644 client/src/components/SearchServers.tsx rename client/src/components/{ => Server}/ServerCard.tsx (97%) diff --git a/client/src/App.tsx b/client/src/App.tsx index b05d9fe..fbffb76 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -3,7 +3,7 @@ import { useState } from 'react'; import { BrowserRouter, Route, Routes } from 'react-router-dom'; import AlertComponent from './components/Alert'; import Footer from './components/Footer'; -import Header from './components/Header'; +import Header from './components/Header/Header'; import { SearchState, SearchStateDefaults } from './data/SearchState'; import { ServerData } from './data/ServerData'; import About from './pages/About'; diff --git a/client/src/components/AddServer.tsx b/client/src/components/Header/AddServer.tsx similarity index 96% rename from client/src/components/AddServer.tsx rename to client/src/components/Header/AddServer.tsx index 5676aee..c179977 100644 --- a/client/src/components/AddServer.tsx +++ b/client/src/components/Header/AddServer.tsx @@ -11,9 +11,9 @@ import { } from '@mui/material'; import { useRef, useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import { postData } from '../apiUtil'; -import { useLoadingContext } from '../context/LoadingContext'; -import { ServerData } from '../data/ServerData'; +import { postData } from '../../apiUtil'; +import { useLoadingContext } from '../../context/LoadingContext'; +import { ServerData } from '../../data/ServerData'; const AddServerInput = styled('div')(({ theme }) => ({ position: 'relative', diff --git a/client/src/components/Header.tsx b/client/src/components/Header/Header.tsx similarity index 87% rename from client/src/components/Header.tsx rename to client/src/components/Header/Header.tsx index 7529811..babdef7 100644 --- a/client/src/components/Header.tsx +++ b/client/src/components/Header/Header.tsx @@ -10,10 +10,11 @@ import { import IconButton from '@mui/material/IconButton'; import { useCallback, useEffect, useState } from 'react'; import { Link } from 'react-router-dom'; -import { fetchDataFromBackend } from '../apiUtil'; -import { useLoadingContext } from '../context/LoadingContext'; -import { SearchState } from '../data/SearchState'; -import { ServerData } from '../data/ServerData'; +import { fetchDataFromBackend } from '../../apiUtil'; +import { useLoadingContext } from '../../context/LoadingContext'; +import { useThemeModeContext } from '../../context/ThemeContext'; +import { SearchState } from '../../data/SearchState'; +import { ServerData } from '../../data/ServerData'; import AddServer from './AddServer'; import SearchServers from './SearchServers'; @@ -30,6 +31,7 @@ export default function Header({ searchState, setSearchState, }: HeaderProps) { + const { themeMode } = useThemeModeContext(); const { progress, setProgress, showAlert } = useLoadingContext(); const [showSearchServer, setShowSearchServer] = useState(false); const toggleShowSearchServer = () => { @@ -67,7 +69,7 @@ export default function Header({ return ( - + >; +}; + +export default function SearchServers({ + showSearchServer, + searchState, + setSearchState, +}: SearchServersProps) { + const containerRef = useRef(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, + 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 ( + + 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)', + }} + > + + {/* Name */} + + { + handleChange('name', event.target.value); + }} + variant="standard" + autoComplete="false" + fullWidth + label="Name" + size="small" + className="pb-2" + /> + + {/* Max Level */} + + + Max Level + + + + { + 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', + }} + /> + { + handleChange('maxLevel', value, activeThumb); + }} + valueLabelDisplay="auto" + disableSwap + className="mx-4" + size="small" + min={1} + max={99} + /> + { + 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', + }} + /> + + {/* Expansion */} + + + + Expansions + + + + + None + + + RotZ + + + CoP + + + ToAU + + + WotG + + + SoA + + + + {/* Multiboxing */} + + + Multiboxing + + { + handleChange('multibox', value); + }} + size="small" + aria-labelledby="multibox-button-group" + sx={{ + display: 'flex', + width: '100%', + }} + exclusive + > + + None + + + Limited + + + Unlimited + + + + {/* Trusts */} + + + Trusts + + { + handleChange('trusts', value); + }} + size="small" + aria-labelledby="trust-button-group" + exclusive + sx={{ + display: 'flex', + width: '100%', + }} + > + + Disabled + + + Enabled + + + + {/* Level Sync */} + + + Level Sync + + { + handleChange('levelSync', value); + }} + size="small" + aria-labelledby="level-sync-button-group" + exclusive + sx={{ + display: 'flex', + width: '100%', + }} + > + + Disabled + + + Enabled + + + + {/* Home Point Teleport */} + + + Home Point Teleport + + { + handleChange('homePoint', value); + }} + size="small" + aria-labelledby="home-point-button-group" + exclusive + sx={{ + display: 'flex', + width: '100%', + }} + > + + Disabled + + + Enabled + + + + {/* Survival Guide Teleport */} + + + Survival Guides + + { + handleChange('survivalGuide', value); + }} + size="small" + aria-labelledby="survival-guide-button-group" + exclusive + sx={{ + display: 'flex', + width: '100%', + }} + > + + Disabled + + + Enabled + + + + {/* Records of Eminence */} + + + Records of Eminence + + { + handleChange('recordsOfEminence', value); + }} + size="small" + aria-labelledby="records-of-eminence-button-group" + exclusive + sx={{ + display: 'flex', + width: '100%', + }} + > + + Disabled + + + Enabled + + + + {/* Fields of Valor */} + + + Fields of Valor + + { + handleChange('fieldsOfValor', value); + }} + size="small" + aria-labelledby="fields-of-valor-button-group" + exclusive + sx={{ + display: 'flex', + width: '100%', + }} + > + + Disabled + + + Enabled + + + + {/* Grounds of Valor */} + + + Grounds of Valor + + { + handleChange('groundsOfValor', value); + }} + size="small" + aria-labelledby="grounds-of-valor-button-group" + exclusive + sx={{ + display: 'flex', + width: '100%', + }} + > + + Disabled + + + Enabled + + + + + + { + setSearchState(SearchStateDefaults); + }} + > + + + + + + + ); +} diff --git a/client/src/components/SearchServers.tsx b/client/src/components/SearchServers.tsx deleted file mode 100644 index 15ff51a..0000000 --- a/client/src/components/SearchServers.tsx +++ /dev/null @@ -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>; -}; - -export default function SearchServers({ - showSearchServer, - searchState, - setSearchState, -}: SearchServersProps) { - const containerRef = useRef(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) => { - setSearchState({ ...searchState, name: event.target.value }); - }; - - const handleSearchMultibox = ( - _event: React.MouseEvent, - newSearchMultibox: string[] - ) => { - setSearchState({ ...searchState, multibox: newSearchMultibox }); - }; - - const handleSearchTrusts = ( - _event: React.MouseEvent, - newSearchTrusts: string[] - ) => { - setSearchState({ ...searchState, trusts: newSearchTrusts }); - }; - - const handleSearchLevelSync = ( - _event: React.MouseEvent, - newSearchLevelSync: string[] - ) => { - setSearchState({ ...searchState, levelSync: newSearchLevelSync }); - }; - - const handleSearchMaxLevelMin = ( - event: React.ChangeEvent - ) => { - 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 - ) => { - 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, - 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 ( - <> - - theme.palette.mode === 'dark' - ? 'rgba(255, 255, 255, .06)' - : 'rgba(0, 0, 0, .06)', - }} - > - - {/* Name */} - - - - {/* Max Level */} - - - Max Level - - - - - - - - {/* Expansion */} - - - - Expansions - - - - - None - - - RotZ - - - CoP - - - ToAU - - - WotG - - - SoA - - - - {/* Multiboxing */} - - - Multiboxing - - - - None - - - Limited - - - Unlimited - - - - {/* Trusts */} - - - Trusts - - - - Disabled - - - Enabled - - - - {/* Level Sync */} - - - Level Sync - - - - Disabled - - - Enabled - - - - - - {contentHeight > 0 && } - - ); -} diff --git a/client/src/components/ServerCard.tsx b/client/src/components/Server/ServerCard.tsx similarity index 97% rename from client/src/components/ServerCard.tsx rename to client/src/components/Server/ServerCard.tsx index f18d159..d03c22f 100644 --- a/client/src/components/ServerCard.tsx +++ b/client/src/components/Server/ServerCard.tsx @@ -26,16 +26,16 @@ import { useState, } from 'react'; import { Link } from 'react-router-dom'; -import { useThemeModeContext } from '../context/ThemeContext'; -import { ServerData } from '../data/ServerData'; -import CopyImageIcon from '../images/copy-image.png'; -import ExpansionBar from './Server/ExpansionsBar'; -import SettingsDataGrid from './Server/SettingsDataGrid'; +import { useThemeModeContext } from '../../context/ThemeContext'; +import { ServerData } from '../../data/ServerData'; +import CopyImageIcon from '../../images/copy-image.png'; import { Accordion, AccordionDetails, AccordionSummary, -} from './Themed/Accordion'; +} from '../Themed/Accordion'; +import ExpansionBar from './ExpansionsBar'; +import SettingsDataGrid from './SettingsDataGrid'; function scrollToTop() { window.scrollTo({ top: 0, behavior: 'smooth' }); diff --git a/client/src/data/SearchState.tsx b/client/src/data/SearchState.tsx index 0622673..308a31d 100644 --- a/client/src/data/SearchState.tsx +++ b/client/src/data/SearchState.tsx @@ -3,6 +3,11 @@ export type SearchState = { 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; }; @@ -12,6 +17,11 @@ export const SearchStateDefaults: SearchState = { multibox: null, trusts: null, levelSync: null, + homePoint: null, + recordsOfEminence: null, + fieldsOfValor: null, + groundsOfValor: null, + survivalGuide: null, maxLevel: [1, 99], expansions: null, }; diff --git a/client/src/pages/Home.tsx b/client/src/pages/Home.tsx index b721926..a5aa2da 100644 --- a/client/src/pages/Home.tsx +++ b/client/src/pages/Home.tsx @@ -1,7 +1,7 @@ import { Box } from '@mui/material'; import ErrorCard from '../components/ErrorCard'; +import ServerCard from '../components/Server/ServerCard'; import SettingsChipCloud from '../components/Server/SettingsChipCloud'; -import ServerCard from '../components/ServerCard'; import { SearchState } from '../data/SearchState'; import { ServerData } from '../data/ServerData'; @@ -13,51 +13,131 @@ export default function Home({ searchState: SearchState; }) { const filterServers = (server: ServerData): boolean => { + // Name if (searchState.name.length > 0) { const serverName = server.name.toLowerCase(); if (!serverName.includes(searchState.name.toLowerCase())) { return false; } } - + // Max Level if (server.max_level < searchState.maxLevel[0]) { return false; } if (server.max_level > searchState.maxLevel[1]) { return false; } - + // Trusts if ( searchState.trusts && typeof server.settings_summary['MAIN.ENABLE_TRUST_CASTING'] === 'number' ) { - const serverTrusts = + const serverEnabled = server.settings_summary['MAIN.ENABLE_TRUST_CASTING'] === 1; const searchEnabled = searchState.trusts.includes('enabled'); const searchDisabled = searchState.trusts.includes('disabled'); - if (serverTrusts && searchDisabled && !searchEnabled) { - return false; - } - if (!serverTrusts && searchEnabled && !searchDisabled) { + if ( + (serverEnabled && searchDisabled && !searchEnabled) || + (!serverEnabled && searchEnabled && !searchDisabled) + ) { return false; } } - + // Level Sync if ( searchState.levelSync && 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 searchDisabled = searchState.levelSync.includes('disabled'); - if (serverLevelSync && searchDisabled && !searchEnabled) { - return false; - } - if (!serverLevelSync && searchEnabled && !searchDisabled) { + if ( + (serverEnabled && searchDisabled && !searchEnabled) || + (!serverEnabled && searchEnabled && !searchDisabled) + ) { 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 (!server.expansions) { return false; @@ -72,23 +152,23 @@ export default function Home({ ) { return false; } - if (searchState.expansions.includes('rotz') && server.expansions.rotz) { + if (searchState.expansions.includes('rotz') !== server.expansions.rotz) { return false; } - if (searchState.expansions.includes('cop') && server.expansions.cop) { + if (searchState.expansions.includes('cop') !== server.expansions.cop) { return false; } - if (searchState.expansions.includes('toau') && server.expansions.toau) { + if (searchState.expansions.includes('toau') !== server.expansions.toau) { return false; } - if (searchState.expansions.includes('wotg') && server.expansions.wotg) { + if (searchState.expansions.includes('wotg') !== server.expansions.wotg) { return false; } - if (searchState.expansions.includes('soa') && server.expansions.soa) { + if (searchState.expansions.includes('soa') !== server.expansions.soa) { return false; } } - + // Multibox if (searchState.multibox) { const serverMultibox = server.login_limit; if (searchState.multibox.includes('none') && serverMultibox !== 1) { diff --git a/client/src/pages/ServerDetails.tsx b/client/src/pages/ServerDetails.tsx index 6158a5f..8e9c0be 100644 --- a/client/src/pages/ServerDetails.tsx +++ b/client/src/pages/ServerDetails.tsx @@ -3,8 +3,8 @@ import { useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; import { fetchDataFromBackend, fetchDemo } from '../apiUtil'; import ErrorCard from '../components/ErrorCard'; +import ServerCard from '../components/Server/ServerCard'; import SettingsDataGrid from '../components/Server/SettingsDataGrid'; -import ServerCard from '../components/ServerCard'; import { useLoadingContext } from '../context/LoadingContext'; import { ServerData } from '../data/ServerData';