Add back map edit bar and refactor select map modal edit functions

This commit is contained in:
Mitchell McCaffrey 2021-06-04 21:54:26 +10:00
parent e508511ab5
commit 0f2776d774
5 changed files with 283 additions and 154 deletions

View File

@ -0,0 +1,178 @@
import React, { useState, useEffect } from "react";
import { Flex, Close, IconButton } from "theme-ui";
import { groupsFromIds, itemsFromGroups } from "../../helpers/group";
import ConfirmModal from "../../modals/ConfirmModal";
import ResetMapIcon from "../../icons/ResetMapIcon";
import RemoveMapIcon from "../../icons/RemoveMapIcon";
import { useGroup } from "../../contexts/GroupContext";
import { useMapData } from "../../contexts/MapDataContext";
import { useKeyboard } from "../../contexts/KeyboardContext";
import shortcuts from "../../shortcuts";
function MapEditBar({ currentMap, disabled, onMapChange, onMapReset, onLoad }) {
const [hasMapState, setHasMapState] = useState(false);
const [hasSelectedDefaultMap, setHasSelectedDefaultMap] = useState(false);
const { maps, mapStates, removeMaps, resetMap } = useMapData();
const {
groups: allGroups,
selectedGroupIds,
onGroupSelect,
openGroupId,
openGroupItems,
} = useGroup();
const groups = openGroupId ? openGroupItems : allGroups;
useEffect(() => {
const selectedGroups = groupsFromIds(selectedGroupIds, groups);
const selectedMaps = itemsFromGroups(selectedGroups, maps);
const selectedMapStates = itemsFromGroups(
selectedGroups,
mapStates,
"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 getSelectedMaps() {
const selectedGroups = groupsFromIds(selectedGroupIds, groups);
return itemsFromGroups(selectedGroups, maps);
}
const [isMapsRemoveModalOpen, setIsMapsRemoveModalOpen] = useState(false);
async function handleMapsRemove() {
onLoad(true);
setIsMapsRemoveModalOpen(false);
const selectedMaps = getSelectedMaps();
const selectedMapIds = selectedMaps.map((map) => map.id);
await removeMaps(selectedMapIds);
onGroupSelect();
// Removed the map from the map screen if needed
if (currentMap && selectedMapIds.includes(currentMap.id)) {
onMapChange(null, null);
}
onLoad(false);
}
const [isMapsResetModalOpen, setIsMapsResetModalOpen] = useState(false);
async function handleMapsReset() {
onLoad(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
if (currentMap && currentMap.id === id) {
onMapReset(newState);
}
}
onLoad(false);
}
/**
* Shortcuts
*/
function handleKeyDown(event) {
if (disabled) {
return;
}
if (shortcuts.delete(event)) {
const selectedMaps = getSelectedMaps();
// Selected maps and none are default
if (
selectedMaps.length > 0 &&
!selectedMaps.some((map) => map.type === "default")
) {
setIsMapsResetModalOpen(false);
setIsMapsRemoveModalOpen(true);
}
}
}
useKeyboard(handleKeyDown);
if (selectedGroupIds.length === 0) {
return null;
}
return (
<Flex
sx={{
position: "absolute",
bottom: 0,
left: 0,
right: 0,
justifyContent: "space-between",
}}
bg="overlay"
>
<Close
title="Clear Selection"
aria-label="Clear Selection"
onClick={() => onGroupSelect()}
/>
<Flex>
<IconButton
aria-label="Reset Selected Map(s)"
title="Reset Selected Map(s)"
onClick={() => setIsMapsResetModalOpen(true)}
disabled={!hasMapState}
>
<ResetMapIcon />
</IconButton>
<IconButton
aria-label="Remove Selected Map(s)"
title="Remove Selected Map(s)"
onClick={() => setIsMapsRemoveModalOpen(true)}
disabled={hasSelectedDefaultMap}
>
<RemoveMapIcon />
</IconButton>
</Flex>
<ConfirmModal
isOpen={isMapsResetModalOpen}
onRequestClose={() => setIsMapsResetModalOpen(false)}
onConfirm={handleMapsReset}
confirmText="Reset"
label="Reset Selected Map(s)"
description="This will remove all fog, drawings and tokens from the selected maps."
/>
<ConfirmModal
isOpen={isMapsRemoveModalOpen}
onRequestClose={() => setIsMapsRemoveModalOpen(false)}
onConfirm={handleMapsRemove}
confirmText="Remove"
label="Remove Selected Map(s)"
description="This operation cannot be undone."
/>
</Flex>
);
}
export default MapEditBar;

View File

@ -0,0 +1,39 @@
import React from "react";
import { Button } from "theme-ui";
import { useGroup } from "../../contexts/GroupContext";
import { findGroup } from "../../helpers/group";
function SelectMapSelectButton({ onMapSelect, disabled }) {
const {
groups: allGroups,
selectedGroupIds,
openGroupId,
openGroupItems,
} = useGroup();
const groups = openGroupId ? openGroupItems : allGroups;
function handleSelectClick() {
if (selectedGroupIds.length === 1) {
const group = findGroup(groups, selectedGroupIds[0]);
if (group && group.type === "item") {
onMapSelect(group.id);
}
}
}
return (
<Button
variant="primary"
disabled={disabled || selectedGroupIds.length > 1}
onClick={handleSelectClick}
mt={2}
>
Select
</Button>
);
}
export default SelectMapSelectButton;

View File

@ -85,6 +85,7 @@ export function TileDragProvider({ onDragAdd, children }) {
selectedIndices = selectedIndices.sort((a, b) => a - b); selectedIndices = selectedIndices.sort((a, b) => a - b);
if (over.id.startsWith(GROUP_ID_PREFIX)) { if (over.id.startsWith(GROUP_ID_PREFIX)) {
onGroupSelect();
// Handle tile group // Handle tile group
const overId = over.id.slice(9); const overId = over.id.slice(9);
if (overId === active.id) { if (overId === active.id) {
@ -96,8 +97,8 @@ export function TileDragProvider({ onDragAdd, children }) {
moveGroupsInto(groups, overGroupIndex, selectedIndices), moveGroupsInto(groups, overGroupIndex, selectedIndices),
openGroupId openGroupId
); );
onGroupSelect();
} else if (over.id.startsWith(UNGROUP_ID_PREFIX)) { } else if (over.id.startsWith(UNGROUP_ID_PREFIX)) {
onGroupSelect();
// Handle tile ungroup // Handle tile ungroup
const newGroups = ungroup(allGroups, openGroupId, selectedIndices); const newGroups = ungroup(allGroups, openGroupId, selectedIndices);
// Close group if it was removed // Close group if it was removed
@ -105,7 +106,6 @@ export function TileDragProvider({ onDragAdd, children }) {
onGroupClose(); onGroupClose();
} }
onGroupsChange(newGroups); onGroupsChange(newGroups);
onGroupSelect();
} else if (over.id.startsWith(ADD_TO_MAP_ID_PREFIX)) { } else if (over.id.startsWith(ADD_TO_MAP_ID_PREFIX)) {
onDragAdd && onDragAdd(selectedGroupIds, over.rect); onDragAdd && onDragAdd(selectedGroupIds, over.rect);
} else { } else {

View File

@ -103,6 +103,10 @@ function EditMapModal({
const layout = useResponsiveLayout(); const layout = useResponsiveLayout();
if (!map) {
return null;
}
return ( return (
<Modal <Modal
isOpen={isOpen} isOpen={isOpen}

View File

@ -1,5 +1,5 @@
import React, { useRef, useState } from "react"; import React, { useRef, useState } from "react";
import { Button, Flex, Label, Box } from "theme-ui"; import { Flex, Label, Box } from "theme-ui";
import { useToasts } from "react-toast-notifications"; import { useToasts } from "react-toast-notifications";
import ReactResizeDetector from "react-resize-detector"; import ReactResizeDetector from "react-resize-detector";
@ -11,25 +11,24 @@ import ImageDrop from "../components/ImageDrop";
import LoadingOverlay from "../components/LoadingOverlay"; import LoadingOverlay from "../components/LoadingOverlay";
import MapTiles from "../components/map/MapTiles"; import MapTiles from "../components/map/MapTiles";
import MapEditBar from "../components/map/MapEditBar";
import SelectMapSelectButton from "../components/map/SelectMapSelectButton";
import TilesOverlay from "../components/tile/TilesOverlay"; import TilesOverlay from "../components/tile/TilesOverlay";
import TilesContainer from "../components/tile/TilesContainer"; import TilesContainer from "../components/tile/TilesContainer";
import TilesAddDroppable from "../components/tile/TilesAddDroppable"; import TilesAddDroppable from "../components/tile/TilesAddDroppable";
import { groupsFromIds, itemsFromGroups, findGroup } from "../helpers/group"; import { findGroup } from "../helpers/group";
import { createMapFromFile } from "../helpers/map"; import { createMapFromFile } from "../helpers/map";
import useResponsiveLayout from "../hooks/useResponsiveLayout"; import useResponsiveLayout from "../hooks/useResponsiveLayout";
import { useMapData } from "../contexts/MapDataContext"; import { useMapData } from "../contexts/MapDataContext";
import { useAuth } from "../contexts/AuthContext"; import { useAuth } from "../contexts/AuthContext";
import { useKeyboard } from "../contexts/KeyboardContext";
import { useAssets } from "../contexts/AssetsContext"; import { useAssets } from "../contexts/AssetsContext";
import { GroupProvider } from "../contexts/GroupContext"; import { GroupProvider } from "../contexts/GroupContext";
import { TileDragProvider } from "../contexts/TileDragContext"; import { TileDragProvider } from "../contexts/TileDragContext";
import shortcuts from "../shortcuts";
function SelectMapModal({ function SelectMapModal({
isOpen, isOpen,
onDone, onDone,
@ -46,8 +45,6 @@ function SelectMapModal({
mapStates, mapStates,
mapGroups, mapGroups,
addMap, addMap,
removeMaps,
resetMap,
mapsLoading, mapsLoading,
getMapState, getMapState,
getMap, getMap,
@ -129,48 +126,6 @@ function SelectMapModal({
setIsLoading(false); setIsLoading(false);
} }
/**
* Map Controls
*/
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
const [selectedGroupIds, setSelectedGroupIds] = useState([]);
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);
setSelectedGroupIds([]);
// Removed the map from the map screen if needed
if (currentMap && selectedMapIds.includes(currentMap.id)) {
onMapChange(null, null);
}
setIsLoading(false);
}
const [isMapsResetModalOpen, setIsMapsResetModalOpen] = useState(false);
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
if (currentMap && currentMap.id === id) {
onMapReset(newState);
}
}
setIsLoading(false);
}
/** /**
* Modal Controls * Modal Controls
*/ */
@ -179,69 +134,44 @@ function SelectMapModal({
onDone(); onDone();
} }
/**
* Map Controls
*/
async function handleMapSelect(mapId) { async function handleMapSelect(mapId) {
if (isLoading) { if (isLoading) {
return; return;
} }
setIsLoading(true); if (mapId) {
const map = await getMap(mapId); setIsLoading(true);
const mapState = await getMapState(mapId); const map = await getMap(mapId);
onMapChange(map, mapState); const mapState = await getMapState(mapId);
setIsLoading(false); onMapChange(map, mapState);
setIsLoading(false);
} else {
onMapChange(null, null);
}
onDone(); onDone();
} }
const [editingMapId, setEditingMapId] = useState();
const [canAddDraggedMap, setCanAddDraggedMap] = useState(false); const [canAddDraggedMap, setCanAddDraggedMap] = useState(false);
function handleGroupsSelect(groupIds) { function handleGroupsSelect(groupIds) {
setSelectedGroupIds(groupIds); if (!canAddDraggedMap && groupIds.length === 1) {
if (groupIds.length === 1) {
// Only allow adding a map from dragging if there is a single group item selected // Only allow adding a map from dragging if there is a single group item selected
const group = findGroup(mapGroups, groupIds[0]); const group = findGroup(mapGroups, groupIds[0]);
setCanAddDraggedMap(group && group.type === "item"); setCanAddDraggedMap(group && group.type === "item");
} else { } else if (canAddDraggedMap) {
setCanAddDraggedMap(false); setCanAddDraggedMap(false);
} }
} }
function handleSelectClick() { function handleDragAdd(groupIds) {
if (isLoading) { if (groupIds.length === 1) {
return; handleMapSelect(groupIds[0]);
}
if (selectedGroupIds.length === 1) {
const group = findGroup(mapGroups, selectedGroupIds[0]);
if (group && group.type === "item") {
handleMapSelect(group.id);
}
} else {
onMapChange(null, null);
onDone();
} }
} }
/**
* Shortcuts
*/
function handleKeyDown(event) {
if (!isOpen) {
return;
}
if (shortcuts.delete(event)) {
const selectedMaps = getSelectedMaps();
// Selected maps and none are default
if (
selectedMaps.length > 0 &&
!selectedMaps.some((map) => map.type === "default")
) {
// Ensure all other modals are closed
setIsEditModalOpen(false);
setIsMapsResetModalOpen(false);
setIsMapsRemoveModalOpen(true);
}
}
}
useKeyboard(handleKeyDown);
const layout = useResponsiveLayout(); const layout = useResponsiveLayout();
const [modalSize, setModalSize] = useState({ width: 0, height: 0 }); const [modalSize, setModalSize] = useState({ width: 0, height: 0 });
@ -269,92 +199,70 @@ function SelectMapModal({
handleHeight handleHeight
onResize={handleModalResize} onResize={handleModalResize}
> >
<Flex <GroupProvider
sx={{ groups={mapGroups}
flexDirection: "column", onGroupsChange={updateMapGroups}
}} onGroupsSelect={handleGroupsSelect}
disabled={!isOpen}
> >
<Label pt={2} pb={1}> <Flex
Select or import a map sx={{
</Label> flexDirection: "column",
<Box sx={{ position: "relative" }}> }}
<GroupProvider >
groups={mapGroups} <Label pt={2} pb={1}>
onGroupsChange={updateMapGroups} Select or import a map
onGroupsSelect={handleGroupsSelect} </Label>
disabled={!isOpen} <Box sx={{ position: "relative" }}>
> <TileDragProvider onDragAdd={canAddDraggedMap && handleDragAdd}>
<TileDragProvider
onDragAdd={canAddDraggedMap && handleSelectClick}
>
<TilesAddDroppable containerSize={modalSize} /> <TilesAddDroppable containerSize={modalSize} />
<TilesContainer> <TilesContainer>
<MapTiles <MapTiles
maps={maps} maps={maps}
onMapEdit={() => setIsEditModalOpen(true)} onMapEdit={setEditingMapId}
onMapSelect={handleMapSelect} onMapSelect={handleMapSelect}
/> />
</TilesContainer> </TilesContainer>
</TileDragProvider> </TileDragProvider>
<TileDragProvider <TileDragProvider onDragAdd={canAddDraggedMap && handleDragAdd}>
onDragAdd={canAddDraggedMap && handleSelectClick}
>
<TilesAddDroppable containerSize={modalSize} /> <TilesAddDroppable containerSize={modalSize} />
<TilesOverlay> <TilesOverlay>
<MapTiles <MapTiles
maps={maps} maps={maps}
onMapEdit={() => setIsEditModalOpen(true)} onMapEdit={setEditingMapId}
onMapSelect={handleMapSelect} onMapSelect={handleMapSelect}
subgroup subgroup
/> />
</TilesOverlay> </TilesOverlay>
</TileDragProvider> </TileDragProvider>
</GroupProvider> <MapEditBar
</Box> currentMap={currentMap}
<Button disabled={isLoading || editingMapId}
variant="primary" onMapChange={onMapChange}
disabled={isLoading || selectedGroupIds.length > 1} onMapReset={onMapReset}
onClick={handleSelectClick} onLoad={setIsLoading}
mt={2} />
> </Box>
Select <SelectMapSelectButton
</Button> onMapSelect={handleMapSelect}
</Flex> disabled={isLoading}
/>
</Flex>
</GroupProvider>
</ReactResizeDetector> </ReactResizeDetector>
</ImageDrop> </ImageDrop>
{(isLoading || mapsLoading) && <LoadingOverlay bg="overlay" />} {(isLoading || mapsLoading) && <LoadingOverlay bg="overlay" />}
<EditMapModal <EditMapModal
isOpen={isEditModalOpen} isOpen={!!editingMapId}
onDone={() => setIsEditModalOpen(false)} onDone={() => setEditingMapId()}
map={ map={editingMapId && maps.find((map) => map.id === editingMapId)}
selectedGroupIds.length === 1 &&
maps.find((map) => map.id === selectedGroupIds[0])
}
mapState={ mapState={
selectedGroupIds.length === 1 && editingMapId &&
mapStates.find((state) => state.mapId === selectedGroupIds[0]) mapStates.find((state) => state.mapId === editingMapId)
} }
onUpdateMap={updateMap} onUpdateMap={updateMap}
onUpdateMapState={updateMapState} onUpdateMapState={updateMapState}
/> />
<ConfirmModal
isOpen={isMapsResetModalOpen}
onRequestClose={() => setIsMapsResetModalOpen(false)}
onConfirm={handleMapsReset}
confirmText="Reset"
label={`Reset ${selectedGroupIds.length} Map${
selectedGroupIds.length > 1 ? "s" : ""
}`}
description="This will remove all fog, drawings and tokens from the selected maps."
/>
<ConfirmModal
isOpen={isMapsRemoveModalOpen}
onRequestClose={() => setIsMapsRemoveModalOpen(false)}
onConfirm={handleMapsRemove}
confirmText="Remove"
label="Remove Map(s)"
description="This operation cannot be undone."
/>
<ConfirmModal <ConfirmModal
isOpen={isLargeImageWarningModalOpen} isOpen={isLargeImageWarningModalOpen}
onRequestClose={handleLargeImageWarningCancel} onRequestClose={handleLargeImageWarningCancel}