Added map group support
This commit is contained in:
parent
a67ff4f407
commit
3215efffa3
@ -1,13 +1,15 @@
|
|||||||
import React, { useContext } from "react";
|
import React, { useContext } from "react";
|
||||||
import { Flex, Box, Text, IconButton, Close } from "theme-ui";
|
import { Flex, Box, Text, IconButton, Close, Label } from "theme-ui";
|
||||||
import SimpleBar from "simplebar-react";
|
import SimpleBar from "simplebar-react";
|
||||||
import { useMedia } from "react-media";
|
import { useMedia } from "react-media";
|
||||||
|
import Case from "case";
|
||||||
|
|
||||||
import AddIcon from "../../icons/AddIcon";
|
import AddIcon from "../../icons/AddIcon";
|
||||||
import RemoveMapIcon from "../../icons/RemoveMapIcon";
|
import RemoveMapIcon from "../../icons/RemoveMapIcon";
|
||||||
import ResetMapIcon from "../../icons/ResetMapIcon";
|
import ResetMapIcon from "../../icons/ResetMapIcon";
|
||||||
import SelectMultipleIcon from "../../icons/SelectMultipleIcon";
|
import SelectMultipleIcon from "../../icons/SelectMultipleIcon";
|
||||||
import SelectSingleIcon from "../../icons/SelectSingleIcon";
|
import SelectSingleIcon from "../../icons/SelectSingleIcon";
|
||||||
|
import GroupIcon from "../../icons/GroupIcon";
|
||||||
|
|
||||||
import RadioIconButton from "./controls/RadioIconButton";
|
import RadioIconButton from "./controls/RadioIconButton";
|
||||||
|
|
||||||
@ -19,6 +21,7 @@ import DatabaseContext from "../../contexts/DatabaseContext";
|
|||||||
|
|
||||||
function MapTiles({
|
function MapTiles({
|
||||||
maps,
|
maps,
|
||||||
|
groups,
|
||||||
selectedMaps,
|
selectedMaps,
|
||||||
selectedMapStates,
|
selectedMapStates,
|
||||||
onMapSelect,
|
onMapSelect,
|
||||||
@ -31,6 +34,7 @@ function MapTiles({
|
|||||||
onSelectModeChange,
|
onSelectModeChange,
|
||||||
search,
|
search,
|
||||||
onSearchChange,
|
onSearchChange,
|
||||||
|
onMapsGroup,
|
||||||
}) {
|
}) {
|
||||||
const { databaseStatus } = useContext(DatabaseContext);
|
const { databaseStatus } = useContext(DatabaseContext);
|
||||||
const isSmallScreen = useMedia({ query: "(max-width: 500px)" });
|
const isSmallScreen = useMedia({ query: "(max-width: 500px)" });
|
||||||
@ -55,6 +59,26 @@ function MapTiles({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mapToTile(map) {
|
||||||
|
const isSelected = selectedMaps.includes(map);
|
||||||
|
return (
|
||||||
|
<MapTile
|
||||||
|
key={map.id}
|
||||||
|
map={map}
|
||||||
|
isSelected={isSelected}
|
||||||
|
onMapSelect={onMapSelect}
|
||||||
|
onMapEdit={onMapEdit}
|
||||||
|
onDone={onDone}
|
||||||
|
large={isSmallScreen}
|
||||||
|
canEdit={
|
||||||
|
isSelected && selectMode === "single" && selectedMaps.length === 1
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const multipleSelected = selectedMaps.length > 1;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ position: "relative" }}>
|
<Box sx={{ position: "relative" }}>
|
||||||
<Flex
|
<Flex
|
||||||
@ -116,28 +140,19 @@ function MapTiles({
|
|||||||
flexWrap: "wrap",
|
flexWrap: "wrap",
|
||||||
borderRadius: "4px",
|
borderRadius: "4px",
|
||||||
minHeight: "400px",
|
minHeight: "400px",
|
||||||
|
alignContent: "flex-start",
|
||||||
}}
|
}}
|
||||||
onClick={() => onMapSelect()}
|
onClick={() => onMapSelect()}
|
||||||
>
|
>
|
||||||
{maps.map((map) => {
|
{/* Render ungrouped maps, grouped maps then default maps */}
|
||||||
const isSelected = selectedMaps.includes(map);
|
{groups.map((group) => (
|
||||||
return (
|
<React.Fragment key={group}>
|
||||||
<MapTile
|
<Label mx={1} mt={2}>
|
||||||
key={map.id}
|
{Case.capital(group)}
|
||||||
map={map}
|
</Label>
|
||||||
isSelected={isSelected}
|
{maps[group].map(mapToTile)}
|
||||||
onMapSelect={onMapSelect}
|
</React.Fragment>
|
||||||
onMapEdit={onMapEdit}
|
))}
|
||||||
onDone={onDone}
|
|
||||||
large={isSmallScreen}
|
|
||||||
canEdit={
|
|
||||||
isSelected &&
|
|
||||||
selectMode === "single" &&
|
|
||||||
selectedMaps.length === 1
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</SimpleBar>
|
</SimpleBar>
|
||||||
{databaseStatus === "disabled" && (
|
{databaseStatus === "disabled" && (
|
||||||
@ -176,16 +191,24 @@ function MapTiles({
|
|||||||
/>
|
/>
|
||||||
<Flex>
|
<Flex>
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label="Reset Map"
|
aria-label={multipleSelected ? "Group Maps" : "Group Map"}
|
||||||
title="Reset Map"
|
title={multipleSelected ? "Group Maps" : "Group Map"}
|
||||||
|
onClick={() => onMapsGroup()}
|
||||||
|
disabled={hasSelectedDefaultMap}
|
||||||
|
>
|
||||||
|
<GroupIcon />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
aria-label={multipleSelected ? "Reset Maps" : "Reset Map"}
|
||||||
|
title={multipleSelected ? "Reset Maps" : "Reset Map"}
|
||||||
onClick={() => onMapsReset()}
|
onClick={() => onMapsReset()}
|
||||||
disabled={!hasMapState}
|
disabled={!hasMapState}
|
||||||
>
|
>
|
||||||
<ResetMapIcon />
|
<ResetMapIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label="Remove Map"
|
aria-label={multipleSelected ? "Remove Maps" : "Remove Map"}
|
||||||
title="Remove Map"
|
title={multipleSelected ? "Remove Maps" : "Remove Map"}
|
||||||
onClick={() => onMapsRemove()}
|
onClick={() => onMapsRemove()}
|
||||||
disabled={hasSelectedDefaultMap}
|
disabled={hasSelectedDefaultMap}
|
||||||
>
|
>
|
||||||
|
@ -48,6 +48,7 @@ export function MapDataProvider({ children }) {
|
|||||||
gridType: "grid",
|
gridType: "grid",
|
||||||
showGrid: false,
|
showGrid: false,
|
||||||
snapToGrid: true,
|
snapToGrid: true,
|
||||||
|
group: "default",
|
||||||
});
|
});
|
||||||
// Add a state for the map if there isn't one already
|
// Add a state for the map if there isn't one already
|
||||||
const state = await database.table("states").get(id);
|
const state = await database.table("states").get(id);
|
||||||
|
@ -187,7 +187,7 @@ function loadVersions(db) {
|
|||||||
// v1.5.2 - Added automatic cache invalidation to maps
|
// v1.5.2 - Added automatic cache invalidation to maps
|
||||||
db.version(11)
|
db.version(11)
|
||||||
.stores({})
|
.stores({})
|
||||||
.upgrade(async (tx) => {
|
.upgrade((tx) => {
|
||||||
return tx
|
return tx
|
||||||
.table("maps")
|
.table("maps")
|
||||||
.toCollection()
|
.toCollection()
|
||||||
@ -198,7 +198,7 @@ function loadVersions(db) {
|
|||||||
// v1.5.2 - Added automatic cache invalidation to tokens
|
// v1.5.2 - Added automatic cache invalidation to tokens
|
||||||
db.version(12)
|
db.version(12)
|
||||||
.stores({})
|
.stores({})
|
||||||
.upgrade(async (tx) => {
|
.upgrade((tx) => {
|
||||||
return tx
|
return tx
|
||||||
.table("tokens")
|
.table("tokens")
|
||||||
.toCollection()
|
.toCollection()
|
||||||
@ -206,6 +206,17 @@ function loadVersions(db) {
|
|||||||
token.lastUsed = token.lastModified;
|
token.lastUsed = token.lastModified;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
// v1.6.0 - Added map grouping
|
||||||
|
db.version(13)
|
||||||
|
.stores({})
|
||||||
|
.upgrade((tx) => {
|
||||||
|
return tx
|
||||||
|
.table("maps")
|
||||||
|
.toCollection()
|
||||||
|
.modify((map) => {
|
||||||
|
map.group = "";
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the dexie database used in DatabaseContext
|
// Get the dexie database used in DatabaseContext
|
||||||
|
18
src/icons/GroupIcon.js
Normal file
18
src/icons/GroupIcon.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
function GroupIcon() {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24"
|
||||||
|
fill="currentcolor"
|
||||||
|
>
|
||||||
|
<path d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M20 6h-8l-1.41-1.41C10.21 4.21 9.7 4 9.17 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm-1 12H5c-.55 0-1-.45-1-1V9c0-.55.45-1 1-1h14c.55 0 1 .45 1 1v8c0 .55-.45 1-1 1z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GroupIcon;
|
65
src/modals/EditGroupModal.js
Normal file
65
src/modals/EditGroupModal.js
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { Box, Button, Label, Flex } from "theme-ui";
|
||||||
|
|
||||||
|
import Modal from "../components/Modal";
|
||||||
|
import Select from "../components/Select";
|
||||||
|
|
||||||
|
function EditGroupModal({
|
||||||
|
isOpen,
|
||||||
|
onRequestClose,
|
||||||
|
onChange,
|
||||||
|
groups,
|
||||||
|
defaultGroup,
|
||||||
|
}) {
|
||||||
|
const [value, setValue] = useState();
|
||||||
|
const [options, setOptions] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (defaultGroup) {
|
||||||
|
setValue({ value: defaultGroup, label: defaultGroup });
|
||||||
|
} else {
|
||||||
|
setValue();
|
||||||
|
}
|
||||||
|
}, [defaultGroup]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setOptions(groups.map((group) => ({ value: group, label: group })));
|
||||||
|
}, [groups]);
|
||||||
|
|
||||||
|
function handleCreate(group) {
|
||||||
|
const newOption = { value: group, label: group };
|
||||||
|
setValue(newOption);
|
||||||
|
setOptions((prev) => [...prev, newOption]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleChange() {
|
||||||
|
onChange(value ? value.value : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onRequestClose={onRequestClose}
|
||||||
|
style={{ overflow: "visible" }}
|
||||||
|
>
|
||||||
|
<Box onSubmit={handleChange} sx={{ width: "300px" }}>
|
||||||
|
<Label py={2}>Select or add a group</Label>
|
||||||
|
<Select
|
||||||
|
creatable
|
||||||
|
options={options}
|
||||||
|
value={value}
|
||||||
|
onChange={setValue}
|
||||||
|
onCreateOption={handleCreate}
|
||||||
|
placeholder=""
|
||||||
|
/>
|
||||||
|
<Flex py={2}>
|
||||||
|
<Button sx={{ flexGrow: 1 }} onClick={handleChange}>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditGroupModal;
|
@ -4,6 +4,7 @@ import shortid from "shortid";
|
|||||||
import Fuse from "fuse.js";
|
import Fuse from "fuse.js";
|
||||||
|
|
||||||
import EditMapModal from "./EditMapModal";
|
import EditMapModal from "./EditMapModal";
|
||||||
|
import EditGroupModal from "./EditGroupModal";
|
||||||
|
|
||||||
import Modal from "../components/Modal";
|
import Modal from "../components/Modal";
|
||||||
import MapTiles from "../components/map/MapTiles";
|
import MapTiles from "../components/map/MapTiles";
|
||||||
@ -17,6 +18,7 @@ import MapDataContext from "../contexts/MapDataContext";
|
|||||||
import AuthContext from "../contexts/AuthContext";
|
import AuthContext from "../contexts/AuthContext";
|
||||||
|
|
||||||
import { resizeImage } from "../helpers/image";
|
import { resizeImage } from "../helpers/image";
|
||||||
|
import { groupBy } from "../helpers/shared";
|
||||||
|
|
||||||
const defaultMapSize = 22;
|
const defaultMapSize = 22;
|
||||||
const defaultMapProps = {
|
const defaultMapProps = {
|
||||||
@ -26,6 +28,7 @@ const defaultMapProps = {
|
|||||||
showGrid: false,
|
showGrid: false,
|
||||||
snapToGrid: true,
|
snapToGrid: true,
|
||||||
quality: "original",
|
quality: "original",
|
||||||
|
group: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapResolutions = [
|
const mapResolutions = [
|
||||||
@ -53,19 +56,29 @@ function SelectMapModal({
|
|||||||
updateMap,
|
updateMap,
|
||||||
} = useContext(MapDataContext);
|
} = useContext(MapDataContext);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search
|
||||||
|
*/
|
||||||
const [filteredMaps, setFilteredMaps] = useState([]);
|
const [filteredMaps, setFilteredMaps] = useState([]);
|
||||||
|
const [filteredMapScores, setFilteredMapScores] = useState({});
|
||||||
const [fuse, setFuse] = useState();
|
const [fuse, setFuse] = useState();
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
|
|
||||||
// Update search index when maps change
|
// Update search index when maps change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setFuse(new Fuse(ownedMaps, { keys: ["name"] }));
|
setFuse(
|
||||||
|
new Fuse(ownedMaps, { keys: ["name", "group"], includeScore: true })
|
||||||
|
);
|
||||||
}, [ownedMaps]);
|
}, [ownedMaps]);
|
||||||
|
|
||||||
// Perform search when search changes
|
// Perform search when search changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (search) {
|
if (search) {
|
||||||
setFilteredMaps(fuse.search(search).map((result) => result.item));
|
const query = fuse.search(search);
|
||||||
|
setFilteredMaps(query.map((result) => result.item));
|
||||||
|
setFilteredMapScores(
|
||||||
|
query.reduce((acc, value) => ({ ...acc, [value.item.id]: value.score }))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}, [search, ownedMaps, fuse]);
|
}, [search, ownedMaps, fuse]);
|
||||||
|
|
||||||
@ -73,21 +86,50 @@ function SelectMapModal({
|
|||||||
setSearch(event.target.value);
|
setSearch(event.target.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
const [imageLoading, setImageLoading] = useState(false);
|
/**
|
||||||
|
* Group
|
||||||
|
*/
|
||||||
|
const [isGroupModalOpen, setIsGroupModalOpen] = useState(false);
|
||||||
|
|
||||||
// The map selected in the modal
|
async function handleMapsGroup(group) {
|
||||||
const [selectedMapIds, setSelectedMapIds] = useState([]);
|
setIsGroupModalOpen(false);
|
||||||
|
for (let id of selectedMapIds) {
|
||||||
|
await updateMap(id, { group });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const selectedMaps = ownedMaps.filter((map) =>
|
const mapsByGroup = groupBy(search ? filteredMaps : ownedMaps, "group");
|
||||||
selectedMapIds.includes(map.id)
|
// Get the groups of the maps sorting by the average score if we're filtering or the alphabetical order
|
||||||
);
|
// with "" at the start and "default" at the end if not
|
||||||
const selectedMapStates = mapStates.filter((state) =>
|
let mapGroups = Object.keys(mapsByGroup);
|
||||||
selectedMapIds.includes(state.mapId)
|
if (search) {
|
||||||
);
|
mapGroups.sort((a, b) => {
|
||||||
|
const aScore = mapsByGroup[a].reduce(
|
||||||
|
(acc, map) => (acc + filteredMapScores[map.id]) / 2
|
||||||
|
);
|
||||||
|
const bScore = mapsByGroup[b].reduce(
|
||||||
|
(acc, map) => (acc + filteredMapScores[map.id]) / 2
|
||||||
|
);
|
||||||
|
return aScore - bScore;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
mapGroups.sort((a, b) => {
|
||||||
|
if (a === "" || b === "default") {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (b === "" || a === "default") {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return a.localeCompare(b);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
|
/**
|
||||||
|
* Image Upload
|
||||||
|
*/
|
||||||
|
|
||||||
const fileInputRef = useRef();
|
const fileInputRef = useRef();
|
||||||
|
const [imageLoading, setImageLoading] = useState(false);
|
||||||
|
|
||||||
async function handleImagesUpload(files) {
|
async function handleImagesUpload(files) {
|
||||||
for (let file of files) {
|
for (let file of files) {
|
||||||
@ -193,6 +235,20 @@ function SelectMapModal({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map Controls
|
||||||
|
*/
|
||||||
|
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
|
||||||
|
// The map selected in the modal
|
||||||
|
const [selectedMapIds, setSelectedMapIds] = useState([]);
|
||||||
|
|
||||||
|
const selectedMaps = ownedMaps.filter((map) =>
|
||||||
|
selectedMapIds.includes(map.id)
|
||||||
|
);
|
||||||
|
const selectedMapStates = mapStates.filter((state) =>
|
||||||
|
selectedMapIds.includes(state.mapId)
|
||||||
|
);
|
||||||
|
|
||||||
async function handleMapAdd(map) {
|
async function handleMapAdd(map) {
|
||||||
await addMap(map);
|
await addMap(map);
|
||||||
setSelectedMapIds([map.id]);
|
setSelectedMapIds([map.id]);
|
||||||
@ -207,6 +263,16 @@ function SelectMapModal({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleMapsReset() {
|
||||||
|
for (let id of selectedMapIds) {
|
||||||
|
const newState = await resetMap(id);
|
||||||
|
// Reset the state of the current map if needed
|
||||||
|
if (currentMap && currentMap.id === id) {
|
||||||
|
onMapStateChange(newState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Either single, multiple or range
|
// Either single, multiple or range
|
||||||
const [selectMode, setSelectMode] = useState("single");
|
const [selectMode, setSelectMode] = useState("single");
|
||||||
|
|
||||||
@ -226,8 +292,12 @@ function SelectMapModal({
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "range":
|
case "range":
|
||||||
// Use filtered maps if we have searched
|
// Create maps array
|
||||||
const maps = search ? filteredMaps : ownedMaps;
|
let maps = mapGroups.reduce(
|
||||||
|
(acc, group) => [...acc, ...mapsByGroup[group]],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
// Add all items inbetween the previous selected map and the current selected
|
// Add all items inbetween the previous selected map and the current selected
|
||||||
if (selectedMapIds.length > 0) {
|
if (selectedMapIds.length > 0) {
|
||||||
const mapIndex = maps.findIndex((m) => m.id === map.id);
|
const mapIndex = maps.findIndex((m) => m.id === map.id);
|
||||||
@ -265,15 +335,9 @@ function SelectMapModal({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleMapsReset() {
|
/**
|
||||||
for (let id of selectedMapIds) {
|
* Modal Controls
|
||||||
const newState = await resetMap(id);
|
*/
|
||||||
// Reset the state of the current map if needed
|
|
||||||
if (currentMap && currentMap.id === id) {
|
|
||||||
onMapStateChange(newState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleClose() {
|
async function handleClose() {
|
||||||
onDone();
|
onDone();
|
||||||
@ -294,6 +358,9 @@ function SelectMapModal({
|
|||||||
onDone();
|
onDone();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shortcuts
|
||||||
|
*/
|
||||||
function handleKeyDown({ key }) {
|
function handleKeyDown({ key }) {
|
||||||
if (key === "Shift") {
|
if (key === "Shift") {
|
||||||
setSelectMode("range");
|
setSelectMode("range");
|
||||||
@ -338,7 +405,8 @@ function SelectMapModal({
|
|||||||
Select or import a map
|
Select or import a map
|
||||||
</Label>
|
</Label>
|
||||||
<MapTiles
|
<MapTiles
|
||||||
maps={search ? filteredMaps : ownedMaps}
|
maps={mapsByGroup}
|
||||||
|
groups={mapGroups}
|
||||||
onMapAdd={openImageDialog}
|
onMapAdd={openImageDialog}
|
||||||
onMapEdit={() => setIsEditModalOpen(true)}
|
onMapEdit={() => setIsEditModalOpen(true)}
|
||||||
onMapsReset={handleMapsReset}
|
onMapsReset={handleMapsReset}
|
||||||
@ -351,6 +419,7 @@ function SelectMapModal({
|
|||||||
onSelectModeChange={setSelectMode}
|
onSelectModeChange={setSelectMode}
|
||||||
search={search}
|
search={search}
|
||||||
onSearchChange={handleSearchChange}
|
onSearchChange={handleSearchChange}
|
||||||
|
onMapsGroup={() => setIsGroupModalOpen(true)}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
@ -369,6 +438,21 @@ function SelectMapModal({
|
|||||||
map={selectedMaps.length === 1 && selectedMaps[0]}
|
map={selectedMaps.length === 1 && selectedMaps[0]}
|
||||||
mapState={selectedMapStates.length === 1 && selectedMapStates[0]}
|
mapState={selectedMapStates.length === 1 && selectedMapStates[0]}
|
||||||
/>
|
/>
|
||||||
|
<EditGroupModal
|
||||||
|
isOpen={isGroupModalOpen}
|
||||||
|
onChange={handleMapsGroup}
|
||||||
|
groups={mapGroups.filter(
|
||||||
|
(group) => group !== "" && group !== "default"
|
||||||
|
)}
|
||||||
|
onRequestClose={() => setIsGroupModalOpen(false)}
|
||||||
|
// Select the default group by testing whether all selected maps are the same
|
||||||
|
defaultGroup={
|
||||||
|
selectedMaps.length > 0 &&
|
||||||
|
selectedMaps
|
||||||
|
.map((map) => map.group)
|
||||||
|
.reduce((prev, curr) => (prev === curr ? curr : undefined))
|
||||||
|
}
|
||||||
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user