mirror of https://github.com/cocosolos/ixion.git
366 lines
11 KiB
TypeScript
366 lines
11 KiB
TypeScript
import {
|
|
Check,
|
|
Close,
|
|
ContentCopy,
|
|
ExpandMore,
|
|
Launch,
|
|
Public,
|
|
PublicOff,
|
|
Warning,
|
|
} from '@mui/icons-material';
|
|
import {
|
|
Box,
|
|
Card,
|
|
CardContent,
|
|
Chip,
|
|
Divider,
|
|
IconButton,
|
|
ToggleButton,
|
|
ToggleButtonGroup,
|
|
Tooltip,
|
|
Typography,
|
|
alpha,
|
|
} from '@mui/material';
|
|
import { useState } from 'react';
|
|
import { Link } from 'react-router-dom';
|
|
import ServerData, {
|
|
ServerSetting,
|
|
ServerSettingsInfo,
|
|
} from '../data/ServerData';
|
|
import CopyImageIcon from '../images/copy-image.png';
|
|
import { Accordion, AccordionDetails, AccordionSummary } from './Accordion';
|
|
|
|
export default function ServerCard({ server }: { server: ServerData }) {
|
|
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 (error) {
|
|
handleClipboardTooltipOpen();
|
|
if (error instanceof Error) {
|
|
setClipboardTooltip(error.message);
|
|
} else {
|
|
setClipboardTooltip('Something went wrong!');
|
|
}
|
|
setTimeout(() => {
|
|
handleClipboardTooltipClose();
|
|
setClipboardTooltip('Copy Server URL.');
|
|
}, 3000);
|
|
}
|
|
};
|
|
|
|
function formatExternalUrl(url: string): string {
|
|
// prepend 'https://' to the URL if it's not already there
|
|
if (!/^https?:\/\//i.test(url)) {
|
|
return `https://${url}`;
|
|
}
|
|
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]
|
|
);
|
|
if (typeof chipValue === 'boolean') {
|
|
chipValue = chipValue ? (
|
|
<Check color="success" />
|
|
) : (
|
|
<Close color="error" />
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Tooltip
|
|
arrow
|
|
disableInteractive
|
|
title={ServerSettingsInfo[key].description}
|
|
>
|
|
<Chip
|
|
key={key}
|
|
label={`${ServerSettingsInfo[key].name === '' ? key : ServerSettingsInfo[key].name}`}
|
|
avatar={
|
|
typeof chipValue === 'object' ? undefined : (
|
|
<Chip label={chipValue} size="small" />
|
|
)
|
|
}
|
|
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)',
|
|
}}
|
|
/>
|
|
</Tooltip>
|
|
);
|
|
};
|
|
|
|
const expansions = [
|
|
<ToggleButton
|
|
key="rotz"
|
|
value="rotz"
|
|
aria-label="rotz-enabled"
|
|
className="py-0"
|
|
disabled
|
|
>
|
|
RotZ
|
|
</ToggleButton>,
|
|
<ToggleButton
|
|
key="cop"
|
|
value="cop"
|
|
aria-label="cop-enabled"
|
|
className="py-0"
|
|
disabled
|
|
>
|
|
CoP
|
|
</ToggleButton>,
|
|
<ToggleButton
|
|
key="toau"
|
|
value="toau"
|
|
aria-label="toau-enabled"
|
|
className="py-0"
|
|
disabled
|
|
>
|
|
ToAU
|
|
</ToggleButton>,
|
|
<ToggleButton
|
|
key="wotg"
|
|
value="wotg"
|
|
aria-label="wotg-enabled"
|
|
className="py-0"
|
|
disabled
|
|
>
|
|
WotG
|
|
</ToggleButton>,
|
|
<ToggleButton
|
|
key="soa"
|
|
value="soa"
|
|
aria-label="soa-enabled"
|
|
className="py-0"
|
|
disabled
|
|
>
|
|
SoA
|
|
</ToggleButton>,
|
|
];
|
|
|
|
return (
|
|
<Card className="mb-2">
|
|
<Accordion className="my-0">
|
|
<AccordionSummary expandIcon={<ExpandMore />}>
|
|
<Box className="flex items-center justify-center">
|
|
{server.customizations['LOGIN.MAINT_MODE'] === 1 ? (
|
|
<Tooltip
|
|
arrow
|
|
disableInteractive
|
|
title="Server is undergoing maintenance."
|
|
>
|
|
<Warning color="warning" />
|
|
</Tooltip>
|
|
) : (
|
|
<div>
|
|
{server.inactivity_counter > 0 ? (
|
|
<Tooltip
|
|
arrow
|
|
disableInteractive
|
|
title={`Offline for ${server.inactivity_counter} hours.`}
|
|
>
|
|
<PublicOff color="error" />
|
|
</Tooltip>
|
|
) : (
|
|
<Tooltip arrow disableInteractive title="Server is online.">
|
|
<Public color="success" />
|
|
</Tooltip>
|
|
)}
|
|
</div>
|
|
)}
|
|
</Box>
|
|
<CardContent className="grow py-1">
|
|
<Box className="flex content-center">
|
|
<Typography
|
|
variant="h5"
|
|
color={(theme) => alpha(theme.palette.text.primary, 0.87)}
|
|
sx={{ lineHeight: 1.0 }}
|
|
>
|
|
{server.name}
|
|
</Typography>
|
|
{typeof server.customizations['API.WEBSITE'] === 'string' &&
|
|
server.customizations['API.WEBSITE'] !== '' && (
|
|
<Tooltip arrow disableInteractive title="Visit website.">
|
|
<IconButton
|
|
component={Link}
|
|
to={formatExternalUrl(
|
|
server.customizations['API.WEBSITE']
|
|
)}
|
|
target="_blank"
|
|
rel="noopener"
|
|
size="small"
|
|
className="py-0"
|
|
onClick={(event) => {
|
|
event.stopPropagation();
|
|
}}
|
|
disableRipple
|
|
>
|
|
<Launch
|
|
sx={{
|
|
fontSize: (theme) =>
|
|
theme.typography.caption.fontSize,
|
|
}}
|
|
/>
|
|
</IconButton>
|
|
</Tooltip>
|
|
)}
|
|
</Box>
|
|
<Box className="flex content-center">
|
|
<Typography
|
|
variant="subtitle2"
|
|
color={(theme) => alpha(theme.palette.text.primary, 0.6)}
|
|
>
|
|
{server.url}
|
|
</Typography>
|
|
{window.isSecureContext && (
|
|
<Tooltip
|
|
arrow
|
|
disableInteractive
|
|
title={clipboardTooltip}
|
|
open={
|
|
clipboardTooltip === 'Copied server URL!' ||
|
|
clipboardTooltipOpen
|
|
}
|
|
onOpen={handleClipboardTooltipOpen}
|
|
onClose={handleClipboardTooltipClose}
|
|
>
|
|
<IconButton
|
|
size="small"
|
|
className="py-0"
|
|
onClick={(event) => {
|
|
event.stopPropagation();
|
|
copyServerUrlToClipboard(server.url);
|
|
}}
|
|
disableRipple
|
|
>
|
|
<ContentCopy
|
|
sx={{
|
|
fontSize: (theme) => theme.typography.caption.fontSize,
|
|
}}
|
|
/>
|
|
</IconButton>
|
|
</Tooltip>
|
|
)}
|
|
</Box>
|
|
<ToggleButtonGroup
|
|
size="small"
|
|
value={[
|
|
server.customizations['LOGIN.RISE_OF_ZILART'] && 'rotz',
|
|
server.customizations['LOGIN.CHAINS_OF_PROMATHIA'] && 'cop',
|
|
server.customizations['LOGIN.TREASURES_OF_AHT_URGHAN'] &&
|
|
'toau',
|
|
server.customizations['LOGIN.WINGS_OF_THE_GODDESS'] && 'wotg',
|
|
server.customizations['LOGIN.SEEKERS_OF_ADOULIN'] && 'soa',
|
|
]}
|
|
sx={{ '& button': { lineHeight: 1.0 } }}
|
|
>
|
|
{expansions}
|
|
</ToggleButtonGroup>
|
|
</CardContent>
|
|
<Box className="flex items-center justify-center">
|
|
<Typography
|
|
variant="h5"
|
|
color={(theme) => theme.palette.text.secondary}
|
|
>
|
|
{`Lv.${server.max_level}`}
|
|
</Typography>
|
|
</Box>
|
|
</AccordionSummary>
|
|
<AccordionDetails className="p-2">
|
|
{server.customizations['MAIN.SERVER_MESSAGE'] && (
|
|
<>
|
|
<Box className="flex justify-center">
|
|
<Typography variant="body2">
|
|
{server.customizations['MAIN.SERVER_MESSAGE']}
|
|
</Typography>
|
|
</Box>
|
|
<Divider sx={{ marginY: 1 }} />
|
|
</>
|
|
)}
|
|
<Box
|
|
sx={{
|
|
display: 'flex',
|
|
flexWrap: 'wrap',
|
|
justifyContent: 'center',
|
|
maxWidth: '100%',
|
|
}}
|
|
>
|
|
{Object.entries(server.customizations).map(renderSettingsChip)}
|
|
</Box>
|
|
</AccordionDetails>
|
|
</Accordion>
|
|
<Divider />
|
|
<CardContent className="flex justify-between py-1">
|
|
<Typography variant="caption">
|
|
{server.active_sessions} active sessions
|
|
{server.login_limit !== 1 && (
|
|
<Tooltip
|
|
title={`Server allows ${server.login_limit === 0 ? 'unlimited' : server.login_limit} simultaneous game sessions per IP.`}
|
|
arrow
|
|
disableInteractive
|
|
>
|
|
<img
|
|
src={CopyImageIcon}
|
|
alt=""
|
|
style={{
|
|
maxHeight: '1.5em',
|
|
marginLeft: '0.5em',
|
|
verticalAlign: 'middle',
|
|
}}
|
|
onContextMenu={(event) => event.preventDefault()}
|
|
/>
|
|
</Tooltip>
|
|
)}
|
|
</Typography>
|
|
<Typography variant="caption">
|
|
Updated: {new Date(server.updated).toLocaleString()}
|
|
</Typography>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|