From ddf74e834306c5d5b6966070528623b456374c70 Mon Sep 17 00:00:00 2001 From: Corey Date: Tue, 7 May 2024 05:31:53 +0000 Subject: [PATCH] Change ServerCard to accept children This allows me to pass in chips for summary view or data grid for detailed view. Disable accordion features when data grid child is present. --- .../components/{ => Server}/ExpansionsBar.tsx | 2 +- .../components/Server/SettingsChipCloud.tsx | 84 ++++++ .../SettingsDataGrid.tsx} | 12 +- client/src/components/ServerCard.tsx | 146 +++++------ client/src/components/ServerDetailsModal.tsx | 79 ------ .../src/components/{ => Themed}/Accordion.tsx | 0 client/src/pages/Home.tsx | 5 +- client/src/pages/ServerDetails.tsx | 248 +----------------- 8 files changed, 167 insertions(+), 409 deletions(-) rename client/src/components/{ => Server}/ExpansionsBar.tsx (96%) create mode 100644 client/src/components/Server/SettingsChipCloud.tsx rename client/src/components/{ServerSettingsDataGrid.tsx => Server/SettingsDataGrid.tsx} (93%) delete mode 100644 client/src/components/ServerDetailsModal.tsx rename client/src/components/{ => Themed}/Accordion.tsx (100%) diff --git a/client/src/components/ExpansionsBar.tsx b/client/src/components/Server/ExpansionsBar.tsx similarity index 96% rename from client/src/components/ExpansionsBar.tsx rename to client/src/components/Server/ExpansionsBar.tsx index 823da03..b43bcb9 100644 --- a/client/src/components/ExpansionsBar.tsx +++ b/client/src/components/Server/ExpansionsBar.tsx @@ -1,5 +1,5 @@ import { ToggleButton, ToggleButtonGroup } from '@mui/material'; -import { ServerData } from '../data/ServerData'; +import { ServerData } from '../../data/ServerData'; const expansions = [ { + if (!ServerSettingsInfo[key]) return null; + + const transformValue = ( + v: boolean | string | number, + setting: ServerSetting + ): string | number | boolean => { + return setting.transform?.(v) ?? v; + }; + + let chipValue: string | number | boolean | JSX.Element = transformValue( + value, + ServerSettingsInfo[key] + ); + if (typeof chipValue === 'boolean') { + chipValue = chipValue ? ( + + ) : ( + + ); + } + + return ( + + + ) + } + icon={typeof chipValue === 'object' ? chipValue : undefined} + size="small" + className="m-1 pr-1" + sx={{ + '& .MuiChip-avatar': { + width: 'auto', + marginX: 0, + order: 2, + }, + '& .MuiChip-icon': { + marginX: 0, + order: 2, + }, + '&> .MuiChip-label': { + paddingRight: 0.5, + }, + boxShadow: '0px 3px 3px rgba(0, 0, 0, .25)', + }} + /> + + ); + }; + + return ( + + {Object.entries(server.customizations).map(renderSettingsChip)} + + ); +} diff --git a/client/src/components/ServerSettingsDataGrid.tsx b/client/src/components/Server/SettingsDataGrid.tsx similarity index 93% rename from client/src/components/ServerSettingsDataGrid.tsx rename to client/src/components/Server/SettingsDataGrid.tsx index 0e6371e..7654a5c 100644 --- a/client/src/components/ServerSettingsDataGrid.tsx +++ b/client/src/components/Server/SettingsDataGrid.tsx @@ -4,7 +4,7 @@ import { ServerSetting, ServerSettings, ServerSettingsInfo, -} from '../data/ServerData'; +} from '../../data/ServerData'; type KeyValueRow = { id: number; @@ -23,11 +23,13 @@ const columns: GridColDef[] = [ { field: 'description', headerName: 'Description', flex: 6 }, ]; -export default function ServerSettingsDataGrid({ - serverSettings, -}: { +type SettingsDataGridProps = { serverSettings: ServerSettings; -}) { +}; + +export default function SettingsDataGrid({ + serverSettings, +}: SettingsDataGridProps) { const transformValue = ( v: boolean | string | number, setting: ServerSetting diff --git a/client/src/components/ServerCard.tsx b/client/src/components/ServerCard.tsx index aad877a..6992314 100644 --- a/client/src/components/ServerCard.tsx +++ b/client/src/components/ServerCard.tsx @@ -1,6 +1,5 @@ import { - Check, - Close, + ArrowCircleUp, ContentCopy, ExpandMore, Info, @@ -13,29 +12,40 @@ import { Box, Card, CardContent, - Chip, Divider, IconButton, Tooltip, Typography, alpha, } from '@mui/material'; -import { useState } from 'react'; -import { Link } from 'react-router-dom'; import { - ServerData, - ServerSetting, - ServerSettingsInfo, -} from '../data/ServerData'; + Children, + ElementType, + ReactNode, + isValidElement, + useState, +} from 'react'; +import { Link } from 'react-router-dom'; +import { ServerData } from '../data/ServerData'; import CopyImageIcon from '../images/copy-image.png'; -import { Accordion, AccordionDetails, AccordionSummary } from './Accordion'; -import ExpansionBar from './ExpansionsBar'; +import ExpansionBar from './Server/ExpansionsBar'; +import SettingsDataGrid from './Server/SettingsDataGrid'; +import { + Accordion, + AccordionDetails, + AccordionSummary, +} from './Themed/Accordion'; function scrollToTop() { window.scrollTo({ top: 0, behavior: 'smooth' }); } -export default function ServerCard({ server }: { server: ServerData }) { +type ServerCardProps = { + server: ServerData; + children: ReactNode; +}; + +export default function ServerCard({ server, children }: ServerCardProps) { const [clipboardTooltip, setClipboardTooltip] = useState('Copy server URL.'); const [clipboardTooltipOpen, setClipboardTooltipOpen] = useState(false); const handleClipboardTooltipClose = () => { @@ -77,72 +87,39 @@ export default function ServerCard({ server }: { server: ServerData }) { return url; } - const renderSettingsChip = ([key, value]: [ - string, - boolean | string | number, - ]) => { - if (!ServerSettingsInfo[key]) return null; - - const transformValue = ( - v: boolean | string | number, - setting: ServerSetting - ): string | number | boolean => { - return setting.transform?.(v) ?? v; - }; - - let chipValue: string | number | boolean | JSX.Element = transformValue( - value, - ServerSettingsInfo[key] + // Check if this is a detailed server card with a data grid + const hasSpecificComponent = ( + childrenProps: ReactNode, + componentType: ElementType + ): boolean => { + const childrenArray = Children.toArray(childrenProps); + return childrenArray.some( + (child) => isValidElement(child) && child.type === componentType ); - if (typeof chipValue === 'boolean') { - chipValue = chipValue ? ( - - ) : ( - - ); + }; + const isSettingsDataGridChild = hasSpecificComponent( + children, + SettingsDataGrid + ); + // Manually handle the accordion state to disable closing when detailed view + const [expand, setExpand] = useState(isSettingsDataGridChild); + const toggleAcordion = () => { + if (!isSettingsDataGridChild) { + setExpand((prev) => !prev); } - - return ( - - - ) - } - icon={typeof chipValue === 'object' ? chipValue : undefined} - size="small" - className="m-1 pr-1" - sx={{ - '& .MuiChip-avatar': { - width: 'auto', - marginX: 0, - order: 2, - }, - '& .MuiChip-icon': { - marginX: 0, - order: 2, - }, - '&> .MuiChip-label': { - paddingRight: 0.5, - }, - boxShadow: '0px 3px 3px rgba(0, 0, 0, .25)', - }} - /> - - ); }; return ( - - }> + + } + onClick={toggleAcordion} + > {server.customizations['LOGIN.MAINT_MODE'] === 1 ? ( @@ -290,16 +267,7 @@ export default function ServerCard({ server }: { server: ServerData }) { )} - - {Object.entries(server.customizations).map(renderSettingsChip)} - + {children} @@ -328,7 +296,13 @@ export default function ServerCard({ server }: { server: ServerData }) { {/* */} - + - + {isSettingsDataGridChild ? ( + + ) : ( + + )} diff --git a/client/src/components/ServerDetailsModal.tsx b/client/src/components/ServerDetailsModal.tsx deleted file mode 100644 index c5359e5..0000000 --- a/client/src/components/ServerDetailsModal.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { Info } from '@mui/icons-material'; -import { Card, IconButton, Tooltip, Typography } from '@mui/material'; -import Modal from '@mui/material/Modal'; -import { useState } from 'react'; -import { fetchDataFromBackend } from '../apiUtil'; -import { ServerData } from '../data/ServerData'; -import ServerSettingsDataGrid from './ServerSettingsDataGrid'; - -export default function ServerDetailsModal({ id }: { id: number }) { - const [server, setServer] = useState(); - const [error, setError] = useState(''); - - const [open, setOpen] = useState(false); - const handleOpen = () => { - let data: ServerData; - const fetchServerData = async () => { - try { - data = await fetchDataFromBackend(`server/${id}`); - } catch (err) { - if (err instanceof Error) { - setError(err.message); - } else { - setError('An unknown error occurred.'); - } - } - setServer(data); - }; - fetchServerData(); - setOpen(true); - }; - const handleClose = () => setOpen(false); - - return ( -
- - - - - - - - - {!server || !server.settings ? ( - - {error} - - ) : ( - - )} - - - -
- ); -} diff --git a/client/src/components/Accordion.tsx b/client/src/components/Themed/Accordion.tsx similarity index 100% rename from client/src/components/Accordion.tsx rename to client/src/components/Themed/Accordion.tsx diff --git a/client/src/pages/Home.tsx b/client/src/pages/Home.tsx index da47c6d..2ee90a9 100644 --- a/client/src/pages/Home.tsx +++ b/client/src/pages/Home.tsx @@ -1,5 +1,6 @@ import { Box } from '@mui/material'; import ErrorCard from '../components/ErrorCard'; +import SettingsChipCloud from '../components/Server/SettingsChipCloud'; import ServerCard from '../components/ServerCard'; import { SearchState } from '../data/SearchState'; import { ServerData } from '../data/ServerData'; @@ -129,7 +130,9 @@ export default function Home({ ) : ( filteredServers.map((server: ServerData) => ( - + + + )) )}
diff --git a/client/src/pages/ServerDetails.tsx b/client/src/pages/ServerDetails.tsx index babb6b6..e3c2d1f 100644 --- a/client/src/pages/ServerDetails.tsx +++ b/client/src/pages/ServerDetails.tsx @@ -1,29 +1,12 @@ -import { - ContentCopy, - Launch, - Public, - PublicOff, - Warning, -} from '@mui/icons-material'; -import { - Box, - Card, - CardContent, - Divider, - IconButton, - Tooltip, - Typography, - alpha, -} from '@mui/material'; +import { Box } from '@mui/material'; import { useEffect, useState } from 'react'; -import { Link, useParams } from 'react-router-dom'; +import { useParams } from 'react-router-dom'; import { fetchDataFromBackend, fetchDemo } from '../apiUtil'; import { AlertResponse } from '../components/Alert'; import ErrorCard from '../components/ErrorCard'; -import ExpansionBar from '../components/ExpansionsBar'; -import ServerSettingsDataGrid from '../components/ServerSettingsDataGrid'; +import SettingsDataGrid from '../components/Server/SettingsDataGrid'; +import ServerCard from '../components/ServerCard'; import { ServerData } from '../data/ServerData'; -import CopyImageIcon from '../images/copy-image.png'; export default function ServerDetails({ setAlertInfo, @@ -60,229 +43,16 @@ export default function ServerDetails({ fetchServerData(); }, [url, setServer, setAlertInfo, setError]); - const [clipboardTooltip, setClipboardTooltip] = useState('Copy server URL.'); - const [clipboardTooltipOpen, setClipboardTooltipOpen] = useState(false); - const handleClipboardTooltipClose = () => { - setClipboardTooltipOpen(false); - }; - - const handleClipboardTooltipOpen = () => { - setClipboardTooltipOpen(true); - }; - - const copyServerUrlToClipboard = async (text: string) => { - try { - navigator.clipboard.writeText(text); - setClipboardTooltip('Copied server URL!'); - handleClipboardTooltipOpen(); - setTimeout(() => { - handleClipboardTooltipClose(); - setClipboardTooltip('Copy server URL.'); - }, 3000); - } catch (err) { - handleClipboardTooltipOpen(); - if (err instanceof Error) { - setClipboardTooltip(err.message); - } else { - setClipboardTooltip('Something went wrong!'); - } - setTimeout(() => { - handleClipboardTooltipClose(); - setClipboardTooltip('Copy server URL.'); - }, 3000); - } - }; - - function formatExternalUrl(serverUrl: string): string { - // prepend 'https://' to the URL if it's not already there - if (!/^https?:\/\//i.test(serverUrl)) { - return `https://${serverUrl}`; - } - return serverUrl; - } - return ( {error || !server ? ( ) : ( - - - - - {server.customizations['LOGIN.MAINT_MODE'] === 1 ? ( - - - - ) : ( -
- {!server.up ? ( - - - - ) : ( - - - - )} -
- )} -
- - - { - event.stopPropagation(); - }} - sx={{ - textDecoration: 'none', - }} - color={(theme) => alpha(theme.palette.text.primary, 0.5)} - > - {server.location} - - - -
- - - alpha(theme.palette.text.primary, 0.87)} - sx={{ lineHeight: 1.0 }} - > - {server.name} - - {typeof server.customizations['API.WEBSITE'] === 'string' && - server.customizations['API.WEBSITE'] !== '' && ( - - { - event.stopPropagation(); - }} - disableRipple - > - - theme.typography.caption.fontSize, - }} - /> - - - )} - - - alpha(theme.palette.text.primary, 0.6)} - > - {server.url} - - {window.isSecureContext && ( - - { - event.stopPropagation(); - copyServerUrlToClipboard(server.url); - }} - disableRipple - > - - theme.typography.caption.fontSize, - }} - /> - - - )} - - - - - theme.palette.text.secondary} - > - {`Lv.${server.max_level}`} - - -
- - - {server.settings && ( - - )} - - - - - {server.active_sessions} active sessions - {server.login_limit !== 1 && ( - - event.preventDefault()} - /> - - )} - - - Updated: {new Date(server.updated).toLocaleString()} - - -
+ + {server.settings && ( + + )} + )}
);