Make tile groups selectable
This commit is contained in:
parent
05968c1964
commit
ef73ca0179
@ -6,8 +6,8 @@ import MapTileImage from "./MapTileImage";
|
||||
function MapTile({
|
||||
map,
|
||||
isSelected,
|
||||
onMapSelect,
|
||||
onMapEdit,
|
||||
onSelect,
|
||||
onEdit,
|
||||
onDone,
|
||||
canEdit,
|
||||
badges,
|
||||
@ -16,8 +16,8 @@ function MapTile({
|
||||
<Tile
|
||||
title={map.name}
|
||||
isSelected={isSelected}
|
||||
onSelect={() => onMapSelect(map)}
|
||||
onEdit={() => onMapEdit(map.id)}
|
||||
onSelect={() => onSelect({ id: map.id })}
|
||||
onEdit={() => onEdit(map.id)}
|
||||
onDoubleClick={() => canEdit && onDone()}
|
||||
canEdit={canEdit}
|
||||
badges={badges}
|
||||
|
@ -3,20 +3,13 @@ import React from "react";
|
||||
import Tile from "../Tile";
|
||||
import MapTileImage from "./MapTileImage";
|
||||
|
||||
function MapTileGroup({
|
||||
group,
|
||||
maps,
|
||||
isSelected,
|
||||
onGroupSelect,
|
||||
onOpen,
|
||||
canOpen,
|
||||
}) {
|
||||
function MapTileGroup({ group, maps, isSelected, onSelect, onOpen, canOpen }) {
|
||||
return (
|
||||
<Tile
|
||||
title={group.name}
|
||||
isSelected={isSelected}
|
||||
// onSelect={() => onGroupSelect(group)}
|
||||
// onDoubleClick={() => canOpen && onOpen()}
|
||||
onSelect={() => onSelect(group)}
|
||||
onDoubleClick={() => canOpen && onOpen()}
|
||||
columns="1fr 1fr"
|
||||
>
|
||||
{maps.slice(0, 4).map((map) => (
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Flex, Box, Text, IconButton, Close, Grid } from "theme-ui";
|
||||
import SimpleBar from "simplebar-react";
|
||||
|
||||
@ -13,16 +13,16 @@ import FilterBar from "../FilterBar";
|
||||
import Sortable from "../drag/Sortable";
|
||||
import SortableTiles from "../drag/SortableTiles";
|
||||
|
||||
import { useDatabase } from "../../contexts/DatabaseContext";
|
||||
|
||||
import useResponsiveLayout from "../../hooks/useResponsiveLayout";
|
||||
|
||||
import { groupsFromIds, itemsFromGroups } from "../../helpers/select";
|
||||
|
||||
function MapTiles({
|
||||
maps,
|
||||
mapStates,
|
||||
groups,
|
||||
selectedMaps,
|
||||
selectedMapStates,
|
||||
onMapSelect,
|
||||
selectedGroupIds,
|
||||
onTileSelect,
|
||||
onMapsRemove,
|
||||
onMapsReset,
|
||||
onMapAdd,
|
||||
@ -33,46 +33,65 @@ function MapTiles({
|
||||
search,
|
||||
onSearchChange,
|
||||
onMapsGroup,
|
||||
databaseDisabled,
|
||||
}) {
|
||||
const { databaseStatus } = useDatabase();
|
||||
const layout = useResponsiveLayout();
|
||||
|
||||
let hasMapState = false;
|
||||
for (let state of selectedMapStates) {
|
||||
if (
|
||||
Object.values(state.tokens).length > 0 ||
|
||||
Object.values(state.drawShapes).length > 0 ||
|
||||
Object.values(state.fogShapes).length > 0 ||
|
||||
Object.values(state.notes).length > 0
|
||||
) {
|
||||
hasMapState = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const [hasMapState, setHasMapState] = useState(false);
|
||||
const [hasSelectedDefaultMap, setHasSelectedDefaultMap] = useState(false);
|
||||
|
||||
let hasSelectedDefaultMap = selectedMaps.some(
|
||||
(map) => map.type === "default"
|
||||
);
|
||||
useEffect(() => {
|
||||
const selectedGroups = groupsFromIds(selectedGroupIds, groups);
|
||||
const selectedMaps = itemsFromGroups(selectedGroups, maps);
|
||||
const selectedMapStates = itemsFromGroups(
|
||||
selectedGroups,
|
||||
mapStates,
|
||||
true,
|
||||
"mapId"
|
||||
);
|
||||
|
||||
setHasSelectedDefaultMap(
|
||||
selectedMaps.some((map) => map.type === "default")
|
||||
);
|
||||
|
||||
let _hasMapState = false;
|
||||
for (let state of selectedMapStates) {
|
||||
if (
|
||||
Object.values(state.tokens).length > 0 ||
|
||||
Object.values(state.drawShapes).length > 0 ||
|
||||
Object.values(state.fogShapes).length > 0 ||
|
||||
Object.values(state.notes).length > 0
|
||||
) {
|
||||
_hasMapState = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
setHasMapState(_hasMapState);
|
||||
}, [selectedGroupIds, maps, mapStates, groups]);
|
||||
|
||||
function groupToMapTile(group) {
|
||||
if (group.type === "item") {
|
||||
const map = maps.find((map) => map.id === group.id);
|
||||
const isSelected = selectedMaps.includes(map);
|
||||
const isSelected = selectedGroupIds.includes(group.id);
|
||||
return (
|
||||
<MapTile
|
||||
key={map.id}
|
||||
map={map}
|
||||
isSelected={isSelected}
|
||||
onMapSelect={onMapSelect}
|
||||
onMapEdit={onMapEdit}
|
||||
onSelect={onTileSelect}
|
||||
onEdit={onMapEdit}
|
||||
onDone={onDone}
|
||||
canEdit={
|
||||
isSelected && selectMode === "single" && selectedMaps.length === 1
|
||||
isSelected &&
|
||||
selectMode === "single" &&
|
||||
selectedGroupIds.length === 1
|
||||
}
|
||||
badges={[`${map.grid.size.x}x${map.grid.size.y}`]}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
const isSelected = selectedGroupIds.includes(group.id);
|
||||
return (
|
||||
<MapTileGroup
|
||||
key={group.id}
|
||||
@ -80,12 +99,14 @@ function MapTiles({
|
||||
maps={group.items.map((item) =>
|
||||
maps.find((map) => map.id === item.id)
|
||||
)}
|
||||
isSelected={isSelected}
|
||||
onSelect={onTileSelect}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const multipleSelected = selectedMaps.length > 1;
|
||||
const multipleSelected = selectedGroupIds.length > 1;
|
||||
|
||||
return (
|
||||
<SortableTiles
|
||||
@ -95,7 +116,7 @@ function MapTiles({
|
||||
>
|
||||
<Box sx={{ position: "relative" }}>
|
||||
<FilterBar
|
||||
onFocus={() => onMapSelect()}
|
||||
onFocus={() => onTileSelect()}
|
||||
search={search}
|
||||
onSearchChange={onSearchChange}
|
||||
selectMode={selectMode}
|
||||
@ -111,7 +132,7 @@ function MapTiles({
|
||||
<Grid
|
||||
p={2}
|
||||
pb={4}
|
||||
pt={databaseStatus === "disabled" ? 4 : 2}
|
||||
pt={databaseDisabled ? 4 : 2}
|
||||
bg="muted"
|
||||
sx={{
|
||||
borderRadius: "4px",
|
||||
@ -120,7 +141,7 @@ function MapTiles({
|
||||
}}
|
||||
gap={2}
|
||||
columns={layout.gridTemplate}
|
||||
onClick={() => onMapSelect()}
|
||||
onClick={() => onTileSelect()}
|
||||
>
|
||||
{groups.map((group) => (
|
||||
<Sortable id={group.id} key={group.id}>
|
||||
@ -129,7 +150,7 @@ function MapTiles({
|
||||
))}
|
||||
</Grid>
|
||||
</SimpleBar>
|
||||
{databaseStatus === "disabled" && (
|
||||
{databaseDisabled && (
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
@ -148,7 +169,7 @@ function MapTiles({
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
{selectedMaps.length > 0 && (
|
||||
{selectedGroupIds.length > 0 && (
|
||||
<Flex
|
||||
sx={{
|
||||
position: "absolute",
|
||||
@ -162,7 +183,7 @@ function MapTiles({
|
||||
<Close
|
||||
title="Clear Selection"
|
||||
aria-label="Clear Selection"
|
||||
onClick={() => onMapSelect()}
|
||||
onClick={() => onTileSelect()}
|
||||
/>
|
||||
<Flex>
|
||||
<IconButton
|
||||
|
@ -7,7 +7,7 @@ function TokenTileGroup({
|
||||
group,
|
||||
tokens,
|
||||
isSelected,
|
||||
onGroupSelect,
|
||||
onSelect,
|
||||
onOpen,
|
||||
canOpen,
|
||||
}) {
|
||||
@ -15,8 +15,8 @@ function TokenTileGroup({
|
||||
<Tile
|
||||
title={group.name}
|
||||
isSelected={isSelected}
|
||||
// onSelect={() => onGroupSelect(group)}
|
||||
// onDoubleClick={() => canOpen && onOpen()}
|
||||
onSelect={() => onSelect(group)}
|
||||
onDoubleClick={() => canOpen && onOpen()}
|
||||
columns="1fr 1fr"
|
||||
>
|
||||
{tokens.slice(0, 4).map((token) => (
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Flex, Box, Text, IconButton, Close, Grid } from "theme-ui";
|
||||
import SimpleBar from "simplebar-react";
|
||||
|
||||
@ -14,17 +14,17 @@ import FilterBar from "../FilterBar";
|
||||
import Sortable from "../drag/Sortable";
|
||||
import SortableTiles from "../drag/SortableTiles";
|
||||
|
||||
import { useDatabase } from "../../contexts/DatabaseContext";
|
||||
|
||||
import useResponsiveLayout from "../../hooks/useResponsiveLayout";
|
||||
|
||||
import { groupsFromIds, itemsFromGroups } from "../../helpers/select";
|
||||
|
||||
function TokenTiles({
|
||||
tokens,
|
||||
groups,
|
||||
selectedGroupIds,
|
||||
onTileSelect,
|
||||
onTokenAdd,
|
||||
onTokenEdit,
|
||||
onTokenSelect,
|
||||
selectedTokens,
|
||||
onTokensRemove,
|
||||
selectMode,
|
||||
onSelectModeChange,
|
||||
@ -32,36 +32,45 @@ function TokenTiles({
|
||||
onSearchChange,
|
||||
onTokensGroup,
|
||||
onTokensHide,
|
||||
databaseDisabled,
|
||||
}) {
|
||||
const { databaseStatus } = useDatabase();
|
||||
const layout = useResponsiveLayout();
|
||||
|
||||
let hasSelectedDefaultToken = selectedTokens.some(
|
||||
(token) => token.type === "default"
|
||||
);
|
||||
let allTokensVisible = selectedTokens.every((token) => !token.hideInSidebar);
|
||||
const [hasSelectedDefaultToken, setHasSelectedDefaultToken] = useState(false);
|
||||
const [allTokensVisible, setAllTokensVisisble] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const selectedGroups = groupsFromIds(selectedGroupIds, groups);
|
||||
const selectedTokens = itemsFromGroups(selectedGroups, tokens);
|
||||
|
||||
setHasSelectedDefaultToken(
|
||||
selectedTokens.some((token) => token.type === "default")
|
||||
);
|
||||
setAllTokensVisisble(selectedTokens.every((token) => !token.hideInSidebar));
|
||||
}, [selectedGroupIds, tokens, groups]);
|
||||
|
||||
function groupToTokenTile(group) {
|
||||
if (group.type === "item") {
|
||||
const token = tokens.find((token) => token.id === group.id);
|
||||
const isSelected = selectedTokens.includes(token);
|
||||
const isSelected = selectedGroupIds.includes(group.id);
|
||||
return (
|
||||
<TokenTile
|
||||
key={token.id}
|
||||
token={token}
|
||||
isSelected={isSelected}
|
||||
onTokenSelect={onTokenSelect}
|
||||
onTokenSelect={onTileSelect}
|
||||
onTokenEdit={onTokenEdit}
|
||||
canEdit={
|
||||
isSelected &&
|
||||
token.type !== "default" &&
|
||||
selectMode === "single" &&
|
||||
selectedTokens.length === 1
|
||||
selectedGroupIds.length === 1
|
||||
}
|
||||
badges={[`${token.defaultSize}x`]}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
const isSelected = selectedGroupIds.includes(group.id);
|
||||
return (
|
||||
<TokenTileGroup
|
||||
key={group.id}
|
||||
@ -69,12 +78,14 @@ function TokenTiles({
|
||||
tokens={group.items.map((item) =>
|
||||
tokens.find((token) => token.id === item.id)
|
||||
)}
|
||||
isSelected={isSelected}
|
||||
onSelect={onTileSelect}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const multipleSelected = selectedTokens.length > 1;
|
||||
const multipleSelected = selectedGroupIds.length > 1;
|
||||
|
||||
let hideTitle = "";
|
||||
if (multipleSelected) {
|
||||
@ -99,7 +110,7 @@ function TokenTiles({
|
||||
>
|
||||
<Box sx={{ position: "relative" }}>
|
||||
<FilterBar
|
||||
onFocus={() => onTokenSelect()}
|
||||
onFocus={() => onTileSelect()}
|
||||
search={search}
|
||||
onSearchChange={onSearchChange}
|
||||
selectMode={selectMode}
|
||||
@ -115,7 +126,7 @@ function TokenTiles({
|
||||
<Grid
|
||||
p={2}
|
||||
pb={4}
|
||||
pt={databaseStatus === "disabled" ? 4 : 2}
|
||||
pt={databaseDisabled ? 4 : 2}
|
||||
bg="muted"
|
||||
sx={{
|
||||
borderRadius: "4px",
|
||||
@ -123,7 +134,7 @@ function TokenTiles({
|
||||
}}
|
||||
gap={2}
|
||||
columns={layout.gridTemplate}
|
||||
onClick={() => onTokenSelect()}
|
||||
onClick={() => onTileSelect()}
|
||||
>
|
||||
{groups.map((group) => (
|
||||
<Sortable id={group.id} key={group.id}>
|
||||
@ -132,7 +143,7 @@ function TokenTiles({
|
||||
))}
|
||||
</Grid>
|
||||
</SimpleBar>
|
||||
{databaseStatus === "disabled" && (
|
||||
{databaseDisabled && (
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
@ -151,7 +162,7 @@ function TokenTiles({
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
{selectedTokens.length > 0 && (
|
||||
{selectedGroupIds.length > 0 && (
|
||||
<Flex
|
||||
sx={{
|
||||
position: "absolute",
|
||||
@ -165,7 +176,7 @@ function TokenTiles({
|
||||
<Close
|
||||
title="Clear Selection"
|
||||
aria-label="Clear Selection"
|
||||
onClick={() => onTokenSelect()}
|
||||
onClick={() => onTileSelect()}
|
||||
/>
|
||||
<Flex>
|
||||
<IconButton
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import Fuse from "fuse.js";
|
||||
|
||||
import { groupBy } from "./shared";
|
||||
import { groupBy, keyBy } from "./shared";
|
||||
|
||||
/**
|
||||
* Helpers for the SelectMapModal and SelectTokenModal
|
||||
@ -134,3 +134,34 @@ export function handleItemSelect(
|
||||
setSelectedIds([]);
|
||||
}
|
||||
}
|
||||
|
||||
export function groupsFromIds(groupIds, groups) {
|
||||
const groupsByIds = keyBy(groups, "id");
|
||||
const filteredGroups = [];
|
||||
for (let groupId of groupIds) {
|
||||
filteredGroups.push(groupsByIds[groupId]);
|
||||
}
|
||||
return filteredGroups;
|
||||
}
|
||||
|
||||
export function itemsFromGroups(
|
||||
groups,
|
||||
allItems,
|
||||
includeGroupedItems = true,
|
||||
itemKey = "id"
|
||||
) {
|
||||
const allItemsById = keyBy(allItems, itemKey);
|
||||
const groupedItems = [];
|
||||
|
||||
for (let group of groups) {
|
||||
if (group.type === "item") {
|
||||
groupedItems.push(allItemsById[group.id]);
|
||||
} else if (group.type === "group" && includeGroupedItems) {
|
||||
for (let item of group.items) {
|
||||
groupedItems.push(allItemsById[item.id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return groupedItems;
|
||||
}
|
||||
|
@ -5,16 +5,19 @@ import Modal from "../components/Modal";
|
||||
import MapSettings from "../components/map/MapSettings";
|
||||
import MapEditor from "../components/map/MapEditor";
|
||||
|
||||
import { useMapData } from "../contexts/MapDataContext";
|
||||
|
||||
import { isEmpty } from "../helpers/shared";
|
||||
import { getGridDefaultInset } from "../helpers/grid";
|
||||
|
||||
import useResponsiveLayout from "../hooks/useResponsiveLayout";
|
||||
|
||||
function EditMapModal({ isOpen, onDone, map, mapState }) {
|
||||
const { updateMap, updateMapState } = useMapData();
|
||||
|
||||
function EditMapModal({
|
||||
isOpen,
|
||||
onDone,
|
||||
map,
|
||||
mapState,
|
||||
onUpdateMap,
|
||||
onUpdateMapState,
|
||||
}) {
|
||||
function handleClose() {
|
||||
setMapSettingChanges({});
|
||||
setMapStateSettingChanges({});
|
||||
@ -79,8 +82,8 @@ function EditMapModal({ isOpen, onDone, map, mapState }) {
|
||||
}
|
||||
}
|
||||
}
|
||||
await updateMap(map.id, mapSettingChanges);
|
||||
await updateMapState(map.id, mapStateSettingChanges);
|
||||
await onUpdateMap(map.id, mapSettingChanges);
|
||||
await onUpdateMapState(map.id, mapStateSettingChanges);
|
||||
|
||||
setMapSettingChanges({});
|
||||
setMapStateSettingChanges({});
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React, { useState } from "react";
|
||||
import { Button, Flex, Label } from "theme-ui";
|
||||
|
||||
import Modal from "../components/Modal";
|
||||
@ -6,31 +6,11 @@ import TokenSettings from "../components/token/TokenSettings";
|
||||
import TokenPreview from "../components/token/TokenPreview";
|
||||
import LoadingOverlay from "../components/LoadingOverlay";
|
||||
|
||||
import { useTokenData } from "../contexts/TokenDataContext";
|
||||
|
||||
import { isEmpty } from "../helpers/shared";
|
||||
|
||||
import useResponsiveLayout from "../hooks/useResponsiveLayout";
|
||||
|
||||
function EditTokenModal({ isOpen, onDone, tokenId }) {
|
||||
const { updateToken, getToken } = useTokenData();
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [token, setToken] = useState();
|
||||
useEffect(() => {
|
||||
async function loadToken() {
|
||||
setIsLoading(true);
|
||||
setToken(await getToken(tokenId));
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
if (isOpen && tokenId) {
|
||||
loadToken();
|
||||
} else {
|
||||
setToken();
|
||||
}
|
||||
}, [isOpen, tokenId, getToken]);
|
||||
|
||||
function EditTokenModal({ isOpen, onDone, token, onUpdateToken }) {
|
||||
function handleClose() {
|
||||
setTokenSettingChanges({});
|
||||
onDone();
|
||||
@ -55,7 +35,7 @@ function EditTokenModal({ isOpen, onDone, tokenId }) {
|
||||
verifiedChanges.defaultSize = verifiedChanges.defaultSize || 1;
|
||||
}
|
||||
|
||||
await updateToken(token.id, verifiedChanges);
|
||||
await onUpdateToken(token.id, verifiedChanges);
|
||||
setTokenSettingChanges({});
|
||||
}
|
||||
}
|
||||
@ -84,7 +64,7 @@ function EditTokenModal({ isOpen, onDone, tokenId }) {
|
||||
<Label pt={2} pb={1}>
|
||||
Edit token
|
||||
</Label>
|
||||
{isLoading || !token ? (
|
||||
{!token ? (
|
||||
<Flex
|
||||
sx={{
|
||||
width: "100%",
|
||||
|
@ -10,7 +10,11 @@ import MapTiles from "../components/map/MapTiles";
|
||||
import ImageDrop from "../components/ImageDrop";
|
||||
import LoadingOverlay from "../components/LoadingOverlay";
|
||||
|
||||
import { handleItemSelect } from "../helpers/select";
|
||||
import {
|
||||
groupsFromIds,
|
||||
handleItemSelect,
|
||||
itemsFromGroups,
|
||||
} from "../helpers/select";
|
||||
import { createMapFromFile } from "../helpers/map";
|
||||
|
||||
import useResponsiveLayout from "../hooks/useResponsiveLayout";
|
||||
@ -19,6 +23,7 @@ import { useMapData } from "../contexts/MapDataContext";
|
||||
import { useAuth } from "../contexts/AuthContext";
|
||||
import { useKeyboard, useBlur } from "../contexts/KeyboardContext";
|
||||
import { useAssets } from "../contexts/AssetsContext";
|
||||
import { useDatabase } from "../contexts/DatabaseContext";
|
||||
|
||||
import shortcuts from "../shortcuts";
|
||||
|
||||
@ -42,8 +47,12 @@ function SelectMapModal({
|
||||
resetMap,
|
||||
mapsLoading,
|
||||
getMapState,
|
||||
getMap,
|
||||
updateMapGroups,
|
||||
updateMap,
|
||||
updateMapState,
|
||||
} = useMapData();
|
||||
const { databaseStatus } = useDatabase();
|
||||
const { addAssets } = useAssets();
|
||||
|
||||
/**
|
||||
@ -126,7 +135,6 @@ function SelectMapModal({
|
||||
const { map, assets } = await createMapFromFile(file, userId);
|
||||
await addMap(map);
|
||||
await addAssets(assets);
|
||||
setSelectedMapIds([map.id]);
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
@ -140,20 +148,21 @@ function SelectMapModal({
|
||||
* Map Controls
|
||||
*/
|
||||
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
|
||||
// The map selected in the modal
|
||||
const [selectedMapIds, setSelectedMapIds] = useState([]);
|
||||
const [selectedGroupIds, setSelectedGroupIds] = useState([]);
|
||||
|
||||
const selectedMaps = maps.filter((map) => selectedMapIds.includes(map.id));
|
||||
const selectedMapStates = mapStates.filter((state) =>
|
||||
selectedMapIds.includes(state.mapId)
|
||||
);
|
||||
function getSelectedMaps() {
|
||||
const groups = groupsFromIds(selectedGroupIds, mapGroups);
|
||||
return itemsFromGroups(groups, maps);
|
||||
}
|
||||
|
||||
const [isMapsRemoveModalOpen, setIsMapsRemoveModalOpen] = useState(false);
|
||||
async function handleMapsRemove() {
|
||||
setIsLoading(true);
|
||||
setIsMapsRemoveModalOpen(false);
|
||||
const selectedMaps = getSelectedMaps();
|
||||
const selectedMapIds = selectedMaps.map((map) => map.id);
|
||||
await removeMaps(selectedMapIds);
|
||||
setSelectedMapIds([]);
|
||||
setSelectedGroupIds([]);
|
||||
// Removed the map from the map screen if needed
|
||||
if (currentMap && selectedMapIds.includes(currentMap.id)) {
|
||||
onMapChange(null, null);
|
||||
@ -165,6 +174,8 @@ function SelectMapModal({
|
||||
async function handleMapsReset() {
|
||||
setIsLoading(true);
|
||||
setIsMapsResetModalOpen(false);
|
||||
const selectedMaps = getSelectedMaps();
|
||||
const selectedMapIds = selectedMaps.map((map) => map.id);
|
||||
for (let id of selectedMapIds) {
|
||||
const newState = await resetMap(id);
|
||||
// Reset the state of the current map if needed
|
||||
@ -178,12 +189,12 @@ function SelectMapModal({
|
||||
// Either single, multiple or range
|
||||
const [selectMode, setSelectMode] = useState("single");
|
||||
|
||||
function handleMapSelect(map) {
|
||||
function handleTileSelect(item) {
|
||||
handleItemSelect(
|
||||
map,
|
||||
item,
|
||||
selectMode,
|
||||
selectedMapIds,
|
||||
setSelectedMapIds
|
||||
selectedGroupIds,
|
||||
setSelectedGroupIds
|
||||
// TODO: Add new group support
|
||||
);
|
||||
}
|
||||
@ -200,10 +211,11 @@ function SelectMapModal({
|
||||
if (isLoading) {
|
||||
return;
|
||||
}
|
||||
if (selectedMapIds.length === 1) {
|
||||
const groups = groupsFromIds(selectedGroupIds, mapGroups);
|
||||
if (groups.length === 1 && groups[0].type === "item") {
|
||||
setIsLoading(true);
|
||||
const map = selectedMaps[0];
|
||||
const mapState = await getMapState(map.id);
|
||||
const map = await getMap(groups[0].id);
|
||||
const mapState = await getMapState(groups[0].id);
|
||||
onMapChange(map, mapState);
|
||||
setIsLoading(false);
|
||||
} else {
|
||||
@ -226,9 +238,10 @@ function SelectMapModal({
|
||||
setSelectMode("multiple");
|
||||
}
|
||||
if (shortcuts.delete(event)) {
|
||||
const selectedMaps = getSelectedMaps();
|
||||
// Selected maps and none are default
|
||||
if (
|
||||
selectedMapIds.length > 0 &&
|
||||
selectedMaps.length > 0 &&
|
||||
!selectedMaps.some((map) => map.type === "default")
|
||||
) {
|
||||
// Ensure all other modals are closed
|
||||
@ -287,24 +300,25 @@ function SelectMapModal({
|
||||
</Label>
|
||||
<MapTiles
|
||||
maps={maps}
|
||||
mapStates={mapStates}
|
||||
groups={mapGroups}
|
||||
selectedGroupIds={selectedGroupIds}
|
||||
onMapAdd={openImageDialog}
|
||||
onMapEdit={() => setIsEditModalOpen(true)}
|
||||
onMapsReset={() => setIsMapsResetModalOpen(true)}
|
||||
onMapsRemove={() => setIsMapsRemoveModalOpen(true)}
|
||||
selectedMaps={selectedMaps}
|
||||
selectedMapStates={selectedMapStates}
|
||||
onMapSelect={handleMapSelect}
|
||||
onTileSelect={handleTileSelect}
|
||||
onDone={handleDone}
|
||||
selectMode={selectMode}
|
||||
onSelectModeChange={setSelectMode}
|
||||
search={search}
|
||||
onSearchChange={handleSearchChange}
|
||||
onMapsGroup={updateMapGroups}
|
||||
databaseDisabled={databaseStatus === "disabled"}
|
||||
/>
|
||||
<Button
|
||||
variant="primary"
|
||||
disabled={isLoading || selectedMapIds.length > 1}
|
||||
disabled={isLoading || selectedGroupIds.length > 1}
|
||||
onClick={handleDone}
|
||||
mt={2}
|
||||
>
|
||||
@ -316,16 +330,24 @@ function SelectMapModal({
|
||||
<EditMapModal
|
||||
isOpen={isEditModalOpen}
|
||||
onDone={() => setIsEditModalOpen(false)}
|
||||
map={selectedMaps.length === 1 && selectedMaps[0]}
|
||||
mapState={selectedMapStates.length === 1 && selectedMapStates[0]}
|
||||
map={
|
||||
selectedGroupIds.length === 1 &&
|
||||
maps.find((map) => map.id === selectedGroupIds[0])
|
||||
}
|
||||
mapState={
|
||||
selectedGroupIds.length === 1 &&
|
||||
mapStates.find((state) => state.mapId === selectedGroupIds[0])
|
||||
}
|
||||
onUpdateMap={updateMap}
|
||||
onUpdateMapState={updateMapState}
|
||||
/>
|
||||
<ConfirmModal
|
||||
isOpen={isMapsResetModalOpen}
|
||||
onRequestClose={() => setIsMapsResetModalOpen(false)}
|
||||
onConfirm={handleMapsReset}
|
||||
confirmText="Reset"
|
||||
label={`Reset ${selectedMapIds.length} Map${
|
||||
selectedMapIds.length > 1 ? "s" : ""
|
||||
label={`Reset ${selectedGroupIds.length} Map${
|
||||
selectedGroupIds.length > 1 ? "s" : ""
|
||||
}`}
|
||||
description="This will remove all fog, drawings and tokens from the selected maps."
|
||||
/>
|
||||
@ -334,9 +356,7 @@ function SelectMapModal({
|
||||
onRequestClose={() => setIsMapsRemoveModalOpen(false)}
|
||||
onConfirm={handleMapsRemove}
|
||||
confirmText="Remove"
|
||||
label={`Remove ${selectedMapIds.length} Map${
|
||||
selectedMapIds.length > 1 ? "s" : ""
|
||||
}`}
|
||||
label="Remove Map(s)"
|
||||
description="This operation cannot be undone."
|
||||
/>
|
||||
<ConfirmModal
|
||||
|
@ -11,7 +11,11 @@ import ImageDrop from "../components/ImageDrop";
|
||||
import TokenTiles from "../components/token/TokenTiles";
|
||||
import LoadingOverlay from "../components/LoadingOverlay";
|
||||
|
||||
import { handleItemSelect } from "../helpers/select";
|
||||
import {
|
||||
groupsFromIds,
|
||||
handleItemSelect,
|
||||
itemsFromGroups,
|
||||
} from "../helpers/select";
|
||||
import { createTokenFromFile } from "../helpers/token";
|
||||
|
||||
import useResponsiveLayout from "../hooks/useResponsiveLayout";
|
||||
@ -20,6 +24,7 @@ import { useTokenData } from "../contexts/TokenDataContext";
|
||||
import { useAuth } from "../contexts/AuthContext";
|
||||
import { useKeyboard, useBlur } from "../contexts/KeyboardContext";
|
||||
import { useAssets } from "../contexts/AssetsContext";
|
||||
import { useDatabase } from "../contexts/DatabaseContext";
|
||||
|
||||
import shortcuts from "../shortcuts";
|
||||
|
||||
@ -35,7 +40,9 @@ function SelectTokensModal({ isOpen, onRequestClose }) {
|
||||
tokensLoading,
|
||||
tokenGroups,
|
||||
updateTokenGroups,
|
||||
updateToken,
|
||||
} = useTokenData();
|
||||
const { databaseStatus } = useDatabase();
|
||||
const { addAssets } = useAssets();
|
||||
|
||||
/**
|
||||
@ -123,7 +130,6 @@ function SelectTokensModal({ isOpen, onRequestClose }) {
|
||||
const { token, assets } = await createTokenFromFile(file, userId);
|
||||
await addToken(token);
|
||||
await addAssets(assets);
|
||||
setSelectedTokenIds([token.id]);
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
@ -131,22 +137,28 @@ function SelectTokensModal({ isOpen, onRequestClose }) {
|
||||
* Token controls
|
||||
*/
|
||||
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
|
||||
const [selectedTokenIds, setSelectedTokenIds] = useState([]);
|
||||
const selectedTokens = tokens.filter((token) =>
|
||||
selectedTokenIds.includes(token.id)
|
||||
);
|
||||
const [selectedGroupIds, setSelectedGroupIds] = useState([]);
|
||||
|
||||
function getSelectedTokens() {
|
||||
const groups = groupsFromIds(selectedGroupIds, tokenGroups);
|
||||
return itemsFromGroups(groups, tokens);
|
||||
}
|
||||
|
||||
const [isTokensRemoveModalOpen, setIsTokensRemoveModalOpen] = useState(false);
|
||||
async function handleTokensRemove() {
|
||||
setIsLoading(true);
|
||||
setIsTokensRemoveModalOpen(false);
|
||||
const selectedTokens = getSelectedTokens();
|
||||
const selectedTokenIds = selectedTokens.map((token) => token.id);
|
||||
await removeTokens(selectedTokenIds);
|
||||
setSelectedTokenIds([]);
|
||||
setSelectedGroupIds([]);
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
async function handleTokensHide(hideInSidebar) {
|
||||
setIsLoading(true);
|
||||
const selectedTokens = getSelectedTokens();
|
||||
const selectedTokenIds = selectedTokens.map((token) => token.id);
|
||||
await updateTokens(selectedTokenIds, { hideInSidebar });
|
||||
setIsLoading(false);
|
||||
}
|
||||
@ -154,12 +166,12 @@ function SelectTokensModal({ isOpen, onRequestClose }) {
|
||||
// Either single, multiple or range
|
||||
const [selectMode, setSelectMode] = useState("single");
|
||||
|
||||
async function handleTokenSelect(token) {
|
||||
async function handleTileSelect(item) {
|
||||
handleItemSelect(
|
||||
token,
|
||||
item,
|
||||
selectMode,
|
||||
selectedTokenIds,
|
||||
setSelectedTokenIds
|
||||
selectedGroupIds,
|
||||
setSelectedGroupIds
|
||||
// TODO: Rework group support
|
||||
);
|
||||
}
|
||||
@ -178,9 +190,10 @@ function SelectTokensModal({ isOpen, onRequestClose }) {
|
||||
setSelectMode("multiple");
|
||||
}
|
||||
if (shortcuts.delete(event)) {
|
||||
const selectedTokens = getSelectedTokens();
|
||||
// Selected tokens and none are default
|
||||
if (
|
||||
selectedTokenIds.length > 0 &&
|
||||
selectedTokens.length > 0 &&
|
||||
!selectedTokens.some((token) => token.type === "default")
|
||||
) {
|
||||
// Ensure all other modals are closed
|
||||
@ -239,17 +252,18 @@ function SelectTokensModal({ isOpen, onRequestClose }) {
|
||||
<TokenTiles
|
||||
tokens={tokens}
|
||||
groups={tokenGroups}
|
||||
selectedGroupIds={selectedGroupIds}
|
||||
onTokenAdd={openImageDialog}
|
||||
onTokenEdit={() => setIsEditModalOpen(true)}
|
||||
onTokensRemove={() => setIsTokensRemoveModalOpen(true)}
|
||||
selectedTokens={selectedTokens}
|
||||
onTokenSelect={handleTokenSelect}
|
||||
onTileSelect={handleTileSelect}
|
||||
selectMode={selectMode}
|
||||
onSelectModeChange={setSelectMode}
|
||||
search={search}
|
||||
onSearchChange={handleSearchChange}
|
||||
onTokensGroup={updateTokenGroups}
|
||||
onTokensHide={handleTokensHide}
|
||||
databaseDisabled={databaseStatus === "disabled"}
|
||||
/>
|
||||
<Button
|
||||
variant="primary"
|
||||
@ -265,16 +279,18 @@ function SelectTokensModal({ isOpen, onRequestClose }) {
|
||||
<EditTokenModal
|
||||
isOpen={isEditModalOpen}
|
||||
onDone={() => setIsEditModalOpen(false)}
|
||||
tokenId={selectedTokens.length === 1 && selectedTokens[0].id}
|
||||
token={
|
||||
selectedGroupIds.length === 1 &&
|
||||
tokens.find((token) => token.id === selectedGroupIds[0])
|
||||
}
|
||||
onTokenUpdate={updateToken}
|
||||
/>
|
||||
<ConfirmModal
|
||||
isOpen={isTokensRemoveModalOpen}
|
||||
onRequestClose={() => setIsTokensRemoveModalOpen(false)}
|
||||
onConfirm={handleTokensRemove}
|
||||
confirmText="Remove"
|
||||
label={`Remove ${selectedTokenIds.length} Token${
|
||||
selectedTokenIds.length > 1 ? "s" : ""
|
||||
}`}
|
||||
label="Remove Token(s)"
|
||||
description="This operation cannot be undone."
|
||||
/>
|
||||
<ConfirmModal
|
||||
|
Loading…
Reference in New Issue
Block a user