Skip to content

Commit 7f3dd43

Browse files
committed
UI: Init new map settings views
1 parent ba6c651 commit 7f3dd43

File tree

17 files changed

+2022
-58
lines changed

17 files changed

+2022
-58
lines changed

rcongui/src/components/Header/nav-data.js

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -76,31 +76,40 @@ export const navMenus = [
7676
],
7777
},
7878
{
79-
name: "Maps",
80-
icon: <MapIcon />,
8179
links: [
8280
{
83-
name: "Change",
84-
to: "/settings/maps/change",
85-
icon: <RestartAltIcon />,
86-
},
87-
{
88-
name: "Rotation",
89-
to: "/settings/maps/rotation",
90-
icon: <ThreeSixtyIcon />,
91-
},
92-
{
93-
name: "Objectives",
94-
to: "/settings/maps/objectives",
95-
icon: <AdsClickIcon />,
96-
},
97-
{
98-
name: "Votemap",
99-
to: "/settings/maps/votemap",
100-
icon: <HowToVoteIcon />,
81+
name: "Maps",
82+
to: "/settings/maps/list",
83+
icon: <MapIcon />,
10184
},
10285
],
10386
},
87+
// {
88+
// name: "Maps",
89+
// icon: <MapIcon />,
90+
// links: [
91+
// {
92+
// name: "Change",
93+
// to: "/settings/maps/change",
94+
// icon: <RestartAltIcon />,
95+
// },
96+
// {
97+
// name: "Rotation",
98+
// to: "/settings/maps/rotation",
99+
// icon: <ThreeSixtyIcon />,
100+
// },
101+
// {
102+
// name: "Objectives",
103+
// to: "/settings/maps/objectives",
104+
// icon: <AdsClickIcon />,
105+
// },
106+
// {
107+
// name: "Votemap",
108+
// to: "/settings/maps/votemap",
109+
// icon: <HowToVoteIcon />,
110+
// },
111+
// ],
112+
// },
104113
{
105114
name: "Records",
106115
icon: <FolderIcon />,
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { useClipboard } from "@/hooks/useClipboard";
2+
import {
3+
Button,
4+
Dialog,
5+
DialogTitle,
6+
DialogContent,
7+
Typography,
8+
} from "@mui/material";
9+
import ContentCopyIcon from "@mui/icons-material/ContentCopy";
10+
import DoneAllIcon from "@mui/icons-material/DoneAll";
11+
import { useState } from "react";
12+
13+
export default function CopyToClipboardButton({ text, ...props }) {
14+
const { isClipboardAvailable, isCopied, copyToClipboard } = useClipboard();
15+
const [dialogOpen, setDialogOpen] = useState(false);
16+
17+
const handleClick = () => {
18+
if (isClipboardAvailable) {
19+
copyToClipboard(text);
20+
} else {
21+
setDialogOpen(true);
22+
}
23+
};
24+
25+
return (
26+
<>
27+
<Button
28+
variant="outlined"
29+
color="warning"
30+
onClick={handleClick}
31+
startIcon={isCopied ? <DoneAllIcon /> : <ContentCopyIcon />}
32+
{...props}
33+
>
34+
{isCopied ? "Copied!" : "Copy"}
35+
</Button>
36+
<Dialog open={dialogOpen} onClose={() => setDialogOpen(false)}>
37+
<DialogContent>
38+
<Typography>Copy to Clipboard only available with HTTPS</Typography>
39+
</DialogContent>
40+
<DialogContent>
41+
<Typography>{text}</Typography>
42+
</DialogContent>
43+
</Dialog>
44+
</>
45+
);
46+
}

rcongui/src/components/shared/CopyableText.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import React, { useState, useEffect } from "react";
33
import ContentCopyIcon from "@mui/icons-material/ContentCopy";
44
import DoneAllIcon from '@mui/icons-material/DoneAll';
55

6-
export default function CopyableText({ text, size = "1em", ...props }) {
6+
export default function CopyableText({ text, label, size = "1em", ...props }) {
77
const [isClipboardAvailable, setIsClipboardAvailable] = useState(false);
88
const [isCopied, setIsCopied] = useState(false);
99

@@ -41,7 +41,7 @@ export default function CopyableText({ text, size = "1em", ...props }) {
4141
visibility: "visible",
4242
},
4343
}}>
44-
{text}
44+
{label || text}
4545
{isClipboardAvailable && (
4646
<Tooltip title={isCopied ? "Copied!" : "Copy"}>
4747
<IconButton
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// A component that renders a tab with a link to a page
2+
// It is used in the Tabs component to create a tab that links to a page
3+
4+
import { Tab } from "@mui/material";
5+
import { Link } from "react-router-dom";
6+
7+
export function NavLinkTab({ label, to, ...props }) {
8+
return (
9+
<Tab
10+
component={Link}
11+
to={to}
12+
label={label}
13+
{...props}
14+
/>
15+
);
16+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Component for displaying a map name, image, and description
2+
3+
import { getMapLayerImageSrc } from "@/components/MapManager/helpers";
4+
import { Typography, Box } from "@mui/material";
5+
import CopyableText from "@/components/shared/CopyableText";
6+
7+
export function MapDetailsCard({ mapLayer }) {
8+
return (
9+
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
10+
<Box
11+
component="img"
12+
src={getMapLayerImageSrc(mapLayer)}
13+
alt={mapLayer.map.pretty_name}
14+
sx={{ width: 48, height: 48, borderRadius: 1, objectFit: "cover" }}
15+
/>
16+
<Box>
17+
<Typography variant="body1" fontWeight="medium">
18+
<CopyableText text={mapLayer.id} label={mapLayer.map.pretty_name} />
19+
</Typography>
20+
<Box sx={{ display: "flex", gap: 1, mt: 0.5 }}>
21+
<Typography variant="body2" color="text.secondary">
22+
{mapLayer.game_mode}
23+
</Typography>
24+
<Typography variant="body2" color="text.secondary">
25+
{mapLayer.environment}
26+
</Typography>
27+
</Box>
28+
</Box>
29+
</Box>
30+
);
31+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import {
2+
Box,
3+
FormControl,
4+
InputLabel,
5+
Select,
6+
MenuItem,
7+
Button,
8+
} from "@mui/material";
9+
import { DebouncedSearchInput } from "@/components/shared/DebouncedSearchInput";
10+
import CloseIcon from "@mui/icons-material/Close";
11+
import { useState, useEffect, useMemo } from "react";
12+
/**
13+
* @typedef {Function} onFilterChange
14+
* @param {Object[]} maps - The list of maps to filter
15+
*/
16+
17+
/**
18+
* @typedef {Object} MapFilterProps
19+
* @property {Object[]} maps - The list of maps to filter
20+
* @property {Function} onFilterChange - The function that is called when the filter changes
21+
*/
22+
23+
/**
24+
* A component that renders a filter for the map list
25+
* It accepts a list of maps and a function that returns a filtered list of maps on filter change
26+
* @param {MapFilterProps} props - The component props
27+
* @returns {JSX.Element} The rendered component
28+
*/
29+
export const MapFilter = ({ maps, onFilterChange }) => {
30+
const [searchTerm, setSearchTerm] = useState("");
31+
const [selectedMode, setSelectedMode] = useState("");
32+
const [selectedWeather, setSelectedWeather] = useState("");
33+
34+
const allModes = useMemo(
35+
() => Array.from(new Set(maps.map((map) => map.game_mode))).sort(),
36+
[maps]
37+
);
38+
39+
const allWeather = useMemo(
40+
() => Array.from(new Set(maps.map((map) => map.environment))).sort(),
41+
[maps]
42+
);
43+
44+
const clearFilters = () => {
45+
setSearchTerm("");
46+
setSelectedMode("");
47+
setSelectedWeather("");
48+
onFilterChange(maps);
49+
};
50+
51+
useEffect(() => {
52+
// Filter map variants based on search term and filters
53+
const filteredMaps = maps.filter((mapLayer) => {
54+
const matchesSearch = mapLayer.map.pretty_name
55+
.toLowerCase()
56+
.includes(searchTerm.toLowerCase());
57+
const matchesMode = selectedMode
58+
? mapLayer.game_mode === selectedMode
59+
: true;
60+
const matchesWeather = selectedWeather
61+
? mapLayer.environment === selectedWeather
62+
: true;
63+
return matchesSearch && matchesMode && matchesWeather;
64+
});
65+
onFilterChange(filteredMaps);
66+
}, [maps, searchTerm, selectedMode, selectedWeather]);
67+
68+
return (
69+
<Box>
70+
<Box sx={{ display: "flex", gap: 2, mb: 2 }}>
71+
<DebouncedSearchInput
72+
fullWidth
73+
size="small"
74+
placeholder="Search maps..."
75+
onChange={setSearchTerm}
76+
/>
77+
</Box>
78+
79+
<Box sx={{ display: "flex", gap: 1, mb: 2 }}>
80+
<FormControl size="small" sx={{ minWidth: 180 }}>
81+
<InputLabel>Game Mode</InputLabel>
82+
<Select
83+
value={selectedMode}
84+
onChange={(e) => setSelectedMode(e.target.value)}
85+
label="Game Mode"
86+
>
87+
<MenuItem value="">All game modes</MenuItem>
88+
{allModes.map((mode) => (
89+
<MenuItem key={mode} value={mode}>
90+
{mode}
91+
</MenuItem>
92+
))}
93+
</Select>
94+
</FormControl>
95+
96+
<FormControl size="small" sx={{ minWidth: 180 }}>
97+
<InputLabel>Weather</InputLabel>
98+
<Select
99+
value={selectedWeather}
100+
onChange={(e) => setSelectedWeather(e.target.value)}
101+
label="Weather"
102+
>
103+
<MenuItem value="">All weather</MenuItem>
104+
{allWeather.map((weather) => (
105+
<MenuItem key={weather} value={weather}>
106+
{weather}
107+
</MenuItem>
108+
))}
109+
</Select>
110+
</FormControl>
111+
112+
{(selectedMode || selectedWeather) && (
113+
<Button
114+
variant="outlined"
115+
size="small"
116+
onClick={clearFilters}
117+
startIcon={<CloseIcon />}
118+
>
119+
Clear Filters
120+
</Button>
121+
)}
122+
</Box>
123+
</Box>
124+
);
125+
};
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { Box, Typography } from "@mui/material";
2+
import { MapListItem } from "./rotation/MapListItem";
3+
4+
const EmptyMapList = () => {
5+
return (
6+
<Box
7+
sx={{
8+
py: 5,
9+
display: "flex",
10+
flexDirection: "column",
11+
alignItems: "center",
12+
justifyContent: "center",
13+
color: "text.secondary",
14+
}}
15+
>
16+
<Box sx={{ mb: 2, opacity: 0.5 }}>
17+
<svg width="48" height="48" viewBox="0 0 24 24">
18+
<path
19+
fill="currentColor"
20+
d="M12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,12.5A1.5,1.5 0 0,1 10.5,11A1.5,1.5 0 0,1 12,9.5A1.5,1.5 0 0,1 13.5,11A1.5,1.5 0 0,1 12,12.5M12,7.2C9.9,7.2 8.2,8.9 8.2,11C8.2,14 12,17.5 12,17.5C12,17.5 15.8,14 15.8,11C15.8,8.9 14.1,7.2 12,7.2Z"
21+
/>
22+
</svg>
23+
</Box>
24+
<Typography variant="h6" gutterBottom>
25+
No maps found
26+
</Typography>
27+
<Typography variant="body2">
28+
Try adjusting your filters or search term
29+
</Typography>
30+
</Box>
31+
);
32+
};
33+
export const MapList = ({ maps, renderItem }) => {
34+
if (maps.length === 0) {
35+
return <EmptyMapList />;
36+
}
37+
38+
return (
39+
<Box sx={{ overflow: "auto" }}>
40+
{maps.sort((a, b) => a.map.name.localeCompare(b.map.name)).map((mapLayer) => (
41+
renderItem ? renderItem(mapLayer) : <MapListItem mapLayer={mapLayer} />
42+
))}
43+
</Box>
44+
);
45+
};

0 commit comments

Comments
 (0)