From b776b861866957341b68536746a6098c338b49ec Mon Sep 17 00:00:00 2001 From: Mitchell McCaffrey Date: Mon, 24 May 2021 13:34:21 +1000 Subject: [PATCH] Refactored group functions into common context and added group overlay --- src/components/dice/DiceTile.js | 2 +- src/components/map/MapTile.js | 8 +- src/components/map/MapTileGroup.js | 16 +- src/components/map/MapTiles.js | 200 +++--------------- src/components/{drag => tile}/SortableTile.js | 7 +- .../{drag => tile}/SortableTiles.js | 55 +++-- src/components/{ => tile}/Tile.js | 2 +- src/components/tile/TilesContainer.js | 16 ++ src/components/tile/TilesOverlay.js | 49 +++++ src/components/token/TokenTile.js | 6 +- src/components/token/TokenTileGroup.js | 19 +- src/components/token/TokenTiles.js | 197 +++-------------- src/contexts/GroupContext.js | 142 +++++++++++++ src/helpers/select.js | 163 +++----------- src/hooks/useResponsiveLayout.js | 4 +- src/modals/SelectMapModal.js | 148 +++++-------- src/modals/SelectTokensModal.js | 131 ++++-------- 17 files changed, 465 insertions(+), 700 deletions(-) rename src/components/{drag => tile}/SortableTile.js (89%) rename src/components/{drag => tile}/SortableTiles.js (72%) rename src/components/{ => tile}/Tile.js (98%) create mode 100644 src/components/tile/TilesContainer.js create mode 100644 src/components/tile/TilesOverlay.js create mode 100644 src/contexts/GroupContext.js diff --git a/src/components/dice/DiceTile.js b/src/components/dice/DiceTile.js index e482908..857eda7 100644 --- a/src/components/dice/DiceTile.js +++ b/src/components/dice/DiceTile.js @@ -1,7 +1,7 @@ import React from "react"; import { Image } from "theme-ui"; -import Tile from "../Tile"; +import Tile from "../tile/Tile"; function DiceTile({ dice, isSelected, onDiceSelect, onDone }) { return ( diff --git a/src/components/map/MapTile.js b/src/components/map/MapTile.js index 520ce2a..c30c4c2 100644 --- a/src/components/map/MapTile.js +++ b/src/components/map/MapTile.js @@ -1,6 +1,6 @@ import React from "react"; -import Tile from "../Tile"; +import Tile from "../tile/Tile"; import MapTileImage from "./MapTileImage"; function MapTile({ @@ -8,7 +8,7 @@ function MapTile({ isSelected, onSelect, onEdit, - onDone, + onDoubleClick, canEdit, badges, }) { @@ -16,9 +16,9 @@ function MapTile({ onSelect({ id: map.id })} + onSelect={() => onSelect(map.id)} onEdit={() => onEdit(map.id)} - onDoubleClick={() => canEdit && onDone()} + onDoubleClick={() => canEdit && onDoubleClick()} canEdit={canEdit} badges={badges} editTitle="Edit Map" diff --git a/src/components/map/MapTileGroup.js b/src/components/map/MapTileGroup.js index ca78cdd..91c334d 100644 --- a/src/components/map/MapTileGroup.js +++ b/src/components/map/MapTileGroup.js @@ -1,19 +1,23 @@ import React from "react"; import { Grid } from "theme-ui"; -import Tile from "../Tile"; +import Tile from "../tile/Tile"; import MapTileImage from "./MapTileImage"; -function MapTileGroup({ group, maps, isSelected, onSelect, onOpen, canOpen }) { +import useResponsiveLayout from "../../hooks/useResponsiveLayout"; + +function MapTileGroup({ group, maps, isSelected, onSelect, onDoubleClick }) { + const layout = useResponsiveLayout(); + return ( onSelect(group)} - onDoubleClick={() => canOpen && onOpen()} + onSelect={() => onSelect(group.id)} + onDoubleClick={onDoubleClick} > - - {maps.slice(0, 4).map((map) => ( + + {maps.slice(0, 16).map((map) => ( ))} diff --git a/src/components/map/MapTiles.js b/src/components/map/MapTiles.js index dcc6d94..25dde25 100644 --- a/src/components/map/MapTiles.js +++ b/src/components/map/MapTiles.js @@ -1,214 +1,68 @@ -import React, { useEffect, useState } from "react"; -import { Flex, Box, Text, IconButton, Close, Grid } from "theme-ui"; -import SimpleBar from "simplebar-react"; - -import RemoveMapIcon from "../../icons/RemoveMapIcon"; -import ResetMapIcon from "../../icons/ResetMapIcon"; +import React from "react"; import MapTile from "./MapTile"; import MapTileGroup from "./MapTileGroup"; -import Link from "../Link"; -import FilterBar from "../FilterBar"; -import SortableTiles from "../drag/SortableTiles"; +import SortableTiles from "../tile/SortableTiles"; -import useResponsiveLayout from "../../hooks/useResponsiveLayout"; +import { getGroupItems } from "../../helpers/select"; -import { - groupsFromIds, - itemsFromGroups, - getGroupItems, -} from "../../helpers/select"; +import { useGroup } from "../../contexts/GroupContext"; -function MapTiles({ - maps, - mapStates, - groups, - selectedGroupIds, - onTileSelect, - onMapsRemove, - onMapsReset, - onMapAdd, - onMapEdit, - onDone, - selectMode, - onSelectModeChange, - search, - onSearchChange, - onMapsGroup, - databaseDisabled, -}) { - const layout = useResponsiveLayout(); - - const [hasMapState, setHasMapState] = useState(false); - const [hasSelectedDefaultMap, setHasSelectedDefaultMap] = useState(false); - - 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 MapTiles({ maps, onMapEdit, onMapSelect, subgroup }) { + const { + groups, + selectedGroupIds, + openGroupItems, + selectMode, + onGroupOpen, + onGroupsChange, + onGroupSelect, + } = useGroup(); function renderTile(group) { if (group.type === "item") { const map = maps.find((map) => map.id === group.id); const isSelected = selectedGroupIds.includes(group.id); + const canEdit = + isSelected && selectMode === "single" && selectedGroupIds.length === 1; return ( canEdit && onMapSelect(group.id)} + canEdit={canEdit} badges={[`${map.grid.size.x}x${map.grid.size.y}`]} /> ); } else { const isSelected = selectedGroupIds.includes(group.id); const items = getGroupItems(group); + const canOpen = + isSelected && selectMode === "single" && selectedGroupIds.length === 1; return ( maps.find((map) => map.id === item.id))} isSelected={isSelected} - onSelect={onTileSelect} + onSelect={onGroupSelect} + onDoubleClick={() => canOpen && onGroupOpen(group.id)} /> ); } } - const multipleSelected = selectedGroupIds.length > 1; - - function renderTiles(tiles) { - return ( - - onTileSelect()} - search={search} - onSearchChange={onSearchChange} - selectMode={selectMode} - onSelectModeChange={onSelectModeChange} - onAdd={onMapAdd} - addTitle="Add Map" - /> - - onTileSelect()} - > - {tiles} - - - {databaseDisabled && ( - - - Map saving is unavailable. See FAQ{" "} - for more information. - - - )} - {selectedGroupIds.length > 0 && ( - - onTileSelect()} - /> - - onMapsReset()} - disabled={!hasMapState} - > - - - onMapsRemove()} - disabled={hasSelectedDefaultMap} - > - - - - - )} - - ); - } - return ( ); } diff --git a/src/components/drag/SortableTile.js b/src/components/tile/SortableTile.js similarity index 89% rename from src/components/drag/SortableTile.js rename to src/components/tile/SortableTile.js index af86a86..1e3dc8e 100644 --- a/src/components/drag/SortableTile.js +++ b/src/components/tile/SortableTile.js @@ -3,7 +3,7 @@ import { Box } from "theme-ui"; import { useDroppable } from "@dnd-kit/core"; import { useSortable } from "@dnd-kit/sortable"; -function Sortable({ id, children }) { +function Sortable({ id, disableGrouping, children }) { const { attributes, listeners, @@ -11,9 +11,11 @@ function Sortable({ id, children }) { setDroppableNodeRef, setDraggableNodeRef, over, + active, } = useSortable({ id }); const { setNodeRef: setGroupNodeRef } = useDroppable({ id: `__group__${id}`, + disabled: disableGrouping, }); const dragStyle = { @@ -42,7 +44,8 @@ function Sortable({ id, children }) { height: "100%", borderWidth: "4px", borderRadius: "4px", - borderStyle: over?.id === `__group__${id}` ? "solid" : "none", + borderStyle: + over?.id === `__group__${id}` && active.id !== id ? "solid" : "none", }; return ( diff --git a/src/components/drag/SortableTiles.js b/src/components/tile/SortableTiles.js similarity index 72% rename from src/components/drag/SortableTiles.js rename to src/components/tile/SortableTiles.js index 67e400a..592b73b 100644 --- a/src/components/drag/SortableTiles.js +++ b/src/components/tile/SortableTiles.js @@ -11,18 +11,23 @@ import { } from "@dnd-kit/core"; import { SortableContext, arrayMove } from "@dnd-kit/sortable"; import { animated, useSpring, config } from "react-spring"; +import { Grid } from "theme-ui"; import { combineGroups, moveGroups } from "../../helpers/select"; +import useResponsiveLayout from "../../hooks/useResponsiveLayout"; + import SortableTile from "./SortableTile"; function SortableTiles({ groups, onGroupChange, renderTile, - renderTiles, onTileSelect, + disableGrouping, }) { + const layout = useResponsiveLayout(); + const mouseSensor = useSensor(MouseSensor, { activationConstraint: { delay: 250, tolerance: 5 }, }); @@ -38,6 +43,7 @@ function SortableTiles({ function handleDragStart({ active, over }) { setDragId(active.id); setOverId(over?.id); + onTileSelect(active.id); } function handleDragMove({ over }) { @@ -77,6 +83,19 @@ function SortableTiles({ const overGroupId = overId && overId.startsWith("__group__") && overId.slice(9); + function renderSortableGroup(group) { + if (overGroupId === group.id && dragId && group.id !== dragId) { + // If dragging over a group render a preview of that group + return renderTile( + combineGroups( + group, + groups.find((group) => group.id === dragId) + ) + ); + } + return renderTile(group); + } + return ( - {renderTiles( - groups.map((group) => ( - - {dragId && overGroupId === group.id && group.id !== dragId - ? // If over a group render a preview of that group - renderTile( - combineGroups( - group, - groups.find((group) => group.id === dragId) - ) - ) - : renderTile(group)} + onTileSelect()} + > + {groups.map((group) => ( + + {renderSortableGroup(group)} - )) - )} + ))} + {createPortal( {dragId && ( diff --git a/src/components/Tile.js b/src/components/tile/Tile.js similarity index 98% rename from src/components/Tile.js rename to src/components/tile/Tile.js index 9869424..0904eb4 100644 --- a/src/components/Tile.js +++ b/src/components/tile/Tile.js @@ -1,7 +1,7 @@ import React from "react"; import { Flex, IconButton, Box, Text, Badge } from "theme-ui"; -import EditTileIcon from "../icons/EditTileIcon"; +import EditTileIcon from "../../icons/EditTileIcon"; function Tile({ title, diff --git a/src/components/tile/TilesContainer.js b/src/components/tile/TilesContainer.js new file mode 100644 index 0000000..438e933 --- /dev/null +++ b/src/components/tile/TilesContainer.js @@ -0,0 +1,16 @@ +import React from "react"; +import SimpleBar from "simplebar-react"; + +import useResponsiveLayout from "../../hooks/useResponsiveLayout"; + +function TilesContainer({ children }) { + const layout = useResponsiveLayout(); + + return ( + + {children} + + ); +} + +export default TilesContainer; diff --git a/src/components/tile/TilesOverlay.js b/src/components/tile/TilesOverlay.js new file mode 100644 index 0000000..1b37136 --- /dev/null +++ b/src/components/tile/TilesOverlay.js @@ -0,0 +1,49 @@ +import React from "react"; +import { Box, Close } from "theme-ui"; + +import { useGroup } from "../../contexts/GroupContext"; + +function TilesOverlay({ children }) { + const { openGroupId, onGroupClose } = useGroup(); + + if (!openGroupId) { + return null; + } + + return ( + onGroupClose()} + > + e.stopPropagation()} + p={3} + > + {children} + + onGroupClose()} + sx={{ position: "absolute", top: "16px", right: "16px" }} + /> + + ); +} + +export default TilesOverlay; diff --git a/src/components/token/TokenTile.js b/src/components/token/TokenTile.js index 1a98bae..864cc7b 100644 --- a/src/components/token/TokenTile.js +++ b/src/components/token/TokenTile.js @@ -1,12 +1,12 @@ import React from "react"; -import Tile from "../Tile"; +import Tile from "../tile/Tile"; import TokenTileImage from "./TokenTileImage"; function TokenTile({ token, isSelected, - onTokenSelect, + onSelect, onTokenEdit, canEdit, badges, @@ -15,7 +15,7 @@ function TokenTile({ onTokenSelect(token)} + onSelect={() => onSelect(token.id)} onEdit={() => onTokenEdit(token.id)} canEdit={canEdit} badges={badges} diff --git a/src/components/token/TokenTileGroup.js b/src/components/token/TokenTileGroup.js index 54553ef..8c749d6 100644 --- a/src/components/token/TokenTileGroup.js +++ b/src/components/token/TokenTileGroup.js @@ -1,29 +1,32 @@ import React from "react"; import { Grid } from "theme-ui"; -import Tile from "../Tile"; +import Tile from "../tile/Tile"; import TokenTileImage from "./TokenTileImage"; +import useResponsiveLayout from "../../hooks/useResponsiveLayout"; + function TokenTileGroup({ group, tokens, isSelected, onSelect, - onOpen, - canOpen, + onDoubleClick, }) { + const layout = useResponsiveLayout(); + return ( onSelect(group)} - onDoubleClick={() => canOpen && onOpen()} + onSelect={() => onSelect(group.id)} + onDoubleClick={onDoubleClick} columns="1fr 1fr" > - - {tokens.slice(0, 4).map((token) => ( + + {tokens.slice(0, 16).map((token) => ( diff --git a/src/components/token/TokenTiles.js b/src/components/token/TokenTiles.js index ebc703d..de38adb 100644 --- a/src/components/token/TokenTiles.js +++ b/src/components/token/TokenTiles.js @@ -1,80 +1,51 @@ -import React, { useState, useEffect } from "react"; -import { Flex, Box, Text, IconButton, Close, Grid } from "theme-ui"; -import SimpleBar from "simplebar-react"; - -import RemoveTokenIcon from "../../icons/RemoveTokenIcon"; -import TokenHideIcon from "../../icons/TokenHideIcon"; -import TokenShowIcon from "../../icons/TokenShowIcon"; +import React from "react"; import TokenTile from "./TokenTile"; import TokenTileGroup from "./TokenTileGroup"; -import Link from "../Link"; -import FilterBar from "../FilterBar"; -import SortableTiles from "../drag/SortableTiles"; +import SortableTiles from "../tile/SortableTiles"; -import useResponsiveLayout from "../../hooks/useResponsiveLayout"; +import { getGroupItems } from "../../helpers/select"; -import { - groupsFromIds, - itemsFromGroups, - getGroupItems, -} from "../../helpers/select"; +import { useGroup } from "../../contexts/GroupContext"; -function TokenTiles({ - tokens, - groups, - selectedGroupIds, - onTileSelect, - onTokenAdd, - onTokenEdit, - onTokensRemove, - selectMode, - onSelectModeChange, - search, - onSearchChange, - onTokensGroup, - onTokensHide, - databaseDisabled, -}) { - const layout = useResponsiveLayout(); - - 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 TokenTiles({ tokens, onTokenEdit, subgroup }) { + const { + groups, + selectedGroupIds, + openGroupItems, + selectMode, + onGroupOpen, + onGroupsChange, + onGroupSelect, + } = useGroup(); function renderTile(group) { if (group.type === "item") { const token = tokens.find((token) => token.id === group.id); const isSelected = selectedGroupIds.includes(group.id); + const canEdit = + isSelected && + token.type !== "default" && + selectMode === "single" && + selectedGroupIds.length === 1; + return ( ); } else { const isSelected = selectedGroupIds.includes(group.id); const items = getGroupItems(group); + const canOpen = + isSelected && selectMode === "single" && selectedGroupIds.length === 1; return ( token.id === item.id) )} isSelected={isSelected} - onSelect={onTileSelect} + onSelect={onGroupSelect} + onDoubleClick={() => canOpen && onGroupOpen(group.id)} /> ); } } - const multipleSelected = selectedGroupIds.length > 1; - - let hideTitle = ""; - if (multipleSelected) { - if (allTokensVisible) { - hideTitle = "Hide Tokens in Sidebar"; - } else { - hideTitle = "Show Tokens in Sidebar"; - } - } else { - if (allTokensVisible) { - hideTitle = "Hide Token in Sidebar"; - } else { - hideTitle = "Show Token in Sidebar"; - } - } - - function renderTiles(tiles) { - return ( - - onTileSelect()} - search={search} - onSearchChange={onSearchChange} - selectMode={selectMode} - onSelectModeChange={onSelectModeChange} - onAdd={onTokenAdd} - addTitle="Add Token" - /> - - onTileSelect()} - > - {tiles} - - - {databaseDisabled && ( - - - Token saving is unavailable. See FAQ{" "} - for more information. - - - )} - {selectedGroupIds.length > 0 && ( - - onTileSelect()} - /> - - onTokensHide(allTokensVisible)} - > - {allTokensVisible ? : } - - onTokensRemove()} - disabled={hasSelectedDefaultToken} - > - - - - - )} - - ); - } - return ( ); } diff --git a/src/contexts/GroupContext.js b/src/contexts/GroupContext.js new file mode 100644 index 0000000..d1bcaa0 --- /dev/null +++ b/src/contexts/GroupContext.js @@ -0,0 +1,142 @@ +import React, { useState, useContext, useEffect } from "react"; +import cloneDeep from "lodash.clonedeep"; + +import { useKeyboard, useBlur } from "./KeyboardContext"; + +import { getGroupItems, groupsFromIds } from "../helpers/select"; + +import shortcuts from "../shortcuts"; + +const GroupContext = React.createContext(); + +export function GroupProvider({ + groups, + onGroupsChange, + onGroupsSelect, + disabled, + children, +}) { + const [selectedGroupIds, setSelectedGroupIds] = useState([]); + const [openGroupId, setOpenGroupId] = useState(); + const [openGroupItems, setOpenGroupItems] = useState([]); + // Either single, multiple or range + const [selectMode, setSelectMode] = useState("single"); + + useEffect(() => { + if (openGroupId) { + setOpenGroupItems(getGroupItems(groupsFromIds([openGroupId], groups)[0])); + } else { + setOpenGroupItems([]); + } + }, [openGroupId, groups]); + + function handleGroupOpen(groupId) { + setSelectedGroupIds([]); + setOpenGroupId(groupId); + } + + function handleGroupClose() { + setSelectedGroupIds([]); + setOpenGroupId(); + } + + function handleGroupsChange(newGroups) { + if (openGroupId) { + // If a group is open then update that group with the new items + const groupIndex = groups.findIndex((group) => group.id === openGroupId); + let updatedGroups = cloneDeep(groups); + const group = updatedGroups[groupIndex]; + updatedGroups[groupIndex] = { ...group, items: newGroups }; + onGroupsChange(updatedGroups); + } else { + onGroupsChange(newGroups); + } + } + + function handleGroupSelect(groupId) { + let groupIds = []; + if (groupId) { + switch (selectMode) { + case "single": + groupIds = [groupId]; + break; + case "multiple": + if (selectedGroupIds.includes(groupId)) { + groupIds = selectedGroupIds.filter((id) => id !== groupId); + } else { + groupIds = [...selectedGroupIds, groupId]; + } + break; + case "range": + /// TODO: Fix when new groups system is added + return; + default: + groupIds = []; + } + } + setSelectedGroupIds(groupIds); + onGroupsSelect(groupIds); + } + + /** + * Shortcuts + */ + function handleKeyDown(event) { + if (disabled) { + return; + } + if (shortcuts.selectRange(event)) { + setSelectMode("range"); + } + if (shortcuts.selectMultiple(event)) { + setSelectMode("multiple"); + } + } + + function handleKeyUp(event) { + if (disabled) { + return; + } + if (shortcuts.selectRange(event) && selectMode === "range") { + setSelectMode("single"); + } + if (shortcuts.selectMultiple(event) && selectMode === "multiple") { + setSelectMode("single"); + } + } + + useKeyboard(handleKeyDown, handleKeyUp); + + // Set select mode to single when cmd+tabing + function handleBlur() { + setSelectMode("single"); + } + + useBlur(handleBlur); + + const value = { + groups, + openGroupId, + openGroupItems, + selectedGroupIds, + selectMode, + onGroupOpen: handleGroupOpen, + onGroupClose: handleGroupClose, + onGroupsChange: handleGroupsChange, + onGroupSelect: handleGroupSelect, + }; + + return ( + {children} + ); +} + +export function useGroup() { + const context = useContext(GroupContext); + if (context === undefined) { + throw new Error("useGroup must be used within a GroupProvider"); + } + return context; +} + +export default GroupContext; diff --git a/src/helpers/select.js b/src/helpers/select.js index d6d52ff..b62cb66 100644 --- a/src/helpers/select.js +++ b/src/helpers/select.js @@ -1,141 +1,7 @@ -import { useEffect, useState } from "react"; import { v4 as uuid } from "uuid"; -import Fuse from "fuse.js"; import cloneDeep from "lodash.clonedeep"; -import { groupBy, keyBy } from "./shared"; - -/** - * Helpers for the SelectMapModal and SelectTokenModal - */ - -// Helper for generating search results for items -export function useSearch(items, search) { - const [filteredItems, setFilteredItems] = useState([]); - const [filteredItemScores, setFilteredItemScores] = useState({}); - const [fuse, setFuse] = useState(); - - // Update search index when items change - useEffect(() => { - setFuse(new Fuse(items, { keys: ["name", "group"], includeScore: true })); - }, [items]); - - // Perform search when search changes - useEffect(() => { - if (search) { - const query = fuse.search(search); - setFilteredItems(query.map((result) => result.item)); - setFilteredItemScores( - query.reduce( - (acc, value) => ({ ...acc, [value.item.id]: value.score }), - {} - ) - ); - } - }, [search, items, fuse]); - - return [filteredItems, filteredItemScores]; -} - -// TODO: Rework group support -// Helper for grouping items -export function useGroup(items, filteredItems, useFiltered, filteredScores) { - const itemsByGroup = groupBy(useFiltered ? filteredItems : items, "group"); - // Get the groups of the items sorting by the average score if we're filtering or the alphabetical order - // with "" at the start and "default" at the end if not - let itemGroups = Object.keys(itemsByGroup); - if (useFiltered) { - itemGroups.sort((a, b) => { - const aScore = itemsByGroup[a].reduce( - (acc, item) => (acc + filteredScores[item.id]) / 2 - ); - const bScore = itemsByGroup[b].reduce( - (acc, item) => (acc + filteredScores[item.id]) / 2 - ); - return aScore - bScore; - }); - } else { - itemGroups.sort((a, b) => { - if (a === "" || b === "default") { - return -1; - } - if (b === "" || a === "default") { - return 1; - } - return a.localeCompare(b); - }); - } - return [itemsByGroup, itemGroups]; -} - -// Helper for handling selecting items -export function handleItemSelect( - item, - selectMode, - selectedIds, - setSelectedIds, - itemsByGroup, - itemGroups -) { - if (!item) { - setSelectedIds([]); - return; - } - switch (selectMode) { - case "single": - setSelectedIds([item.id]); - break; - case "multiple": - setSelectedIds((prev) => { - if (prev.includes(item.id)) { - return prev.filter((id) => id !== item.id); - } else { - return [...prev, item.id]; - } - }); - break; - case "range": - /// TODO: Fix when new groups system is added - return; - // Create items array - // let items = itemGroups.reduce( - // (acc, group) => [...acc, ...itemsByGroup[group]], - // [] - // ); - - // // Add all items inbetween the previous selected item and the current selected - // if (selectedIds.length > 0) { - // const mapIndex = items.findIndex((m) => m.id === item.id); - // const lastIndex = items.findIndex( - // (m) => m.id === selectedIds[selectedIds.length - 1] - // ); - // let idsToAdd = []; - // let idsToRemove = []; - // const direction = mapIndex > lastIndex ? 1 : -1; - // for ( - // let i = lastIndex + direction; - // direction < 0 ? i >= mapIndex : i <= mapIndex; - // i += direction - // ) { - // const itemId = items[i].id; - // if (selectedIds.includes(itemId)) { - // idsToRemove.push(itemId); - // } else { - // idsToAdd.push(itemId); - // } - // } - // setSelectedIds((prev) => { - // let ids = [...prev, ...idsToAdd]; - // return ids.filter((id) => !idsToRemove.includes(id)); - // }); - // } else { - // setSelectedIds([item.id]); - // } - // break; - default: - setSelectedIds([]); - } -} +import { keyBy } from "./shared"; /** * @typedef GroupItem @@ -232,6 +98,13 @@ export function combineGroups(a, b) { } } +/** + * Immutably move group at `bIndex` into `aIndex` + * @param {Group[]} groups + * @param {number} aIndex + * @param {number} bIndex + * @returns {Group[]} + */ export function moveGroups(groups, aIndex, bIndex) { const aGroup = groups[aIndex]; const bGroup = groups[bIndex]; @@ -241,3 +114,23 @@ export function moveGroups(groups, aIndex, bIndex) { newGroups.splice(bIndex, 1); return newGroups; } + +/** + * Recursively find a group within a group array + * @param {Group[]} groups + * @param {string} groupId + * @returns {Group} + */ +export function findGroup(groups, groupId) { + for (let group of groups) { + if (group.id === groupId) { + return group; + } + const items = getGroupItems(group); + for (let item of items) { + if (item.id === groupId) { + return item; + } + } + } +} diff --git a/src/hooks/useResponsiveLayout.js b/src/hooks/useResponsiveLayout.js index c50b3e6..6cf41b7 100644 --- a/src/hooks/useResponsiveLayout.js +++ b/src/hooks/useResponsiveLayout.js @@ -27,7 +27,9 @@ function useResponsiveLayout() { ? "1fr 1fr 1fr" : "1fr 1fr"; - return { screenSize, modalSize, tileSize, gridTemplate }; + const tileContainerHeight = isLargeScreen ? "600px" : "400px"; + + return { screenSize, modalSize, tileSize, gridTemplate, tileContainerHeight }; } export default useResponsiveLayout; diff --git a/src/modals/SelectMapModal.js b/src/modals/SelectMapModal.js index 6798b96..3a9ccd8 100644 --- a/src/modals/SelectMapModal.js +++ b/src/modals/SelectMapModal.js @@ -1,29 +1,29 @@ import React, { useRef, useState } from "react"; -import { Button, Flex, Label } from "theme-ui"; +import { Button, Flex, Label, Box } from "theme-ui"; import { useToasts } from "react-toast-notifications"; import EditMapModal from "./EditMapModal"; import ConfirmModal from "./ConfirmModal"; import Modal from "../components/Modal"; -import MapTiles from "../components/map/MapTiles"; import ImageDrop from "../components/ImageDrop"; import LoadingOverlay from "../components/LoadingOverlay"; -import { - groupsFromIds, - handleItemSelect, - itemsFromGroups, -} from "../helpers/select"; +import MapTiles from "../components/map/MapTiles"; + +import TilesOverlay from "../components/tile/TilesOverlay"; +import TilesContainer from "../components/tile/TilesContainer"; + +import { groupsFromIds, itemsFromGroups, findGroup } from "../helpers/select"; import { createMapFromFile } from "../helpers/map"; import useResponsiveLayout from "../hooks/useResponsiveLayout"; import { useMapData } from "../contexts/MapDataContext"; import { useAuth } from "../contexts/AuthContext"; -import { useKeyboard, useBlur } from "../contexts/KeyboardContext"; +import { useKeyboard } from "../contexts/KeyboardContext"; import { useAssets } from "../contexts/AssetsContext"; -import { useDatabase } from "../contexts/DatabaseContext"; +import { GroupProvider } from "../contexts/GroupContext"; import shortcuts from "../shortcuts"; @@ -52,20 +52,8 @@ function SelectMapModal({ updateMap, updateMapState, } = useMapData(); - const { databaseStatus } = useDatabase(); const { addAssets } = useAssets(); - /** - * Search - */ - const [search, setSearch] = useState(""); - // TODO: Add back with new group support - // const [filteredMaps, filteredMapScores] = useSearch(ownedMaps, search); - - function handleSearchChange(event) { - setSearch(event.target.value); - } - /** * Image Upload */ @@ -138,12 +126,6 @@ function SelectMapModal({ setIsLoading(false); } - function openImageDialog() { - if (fileInputRef.current) { - fileInputRef.current.click(); - } - } - /** * Map Controls */ @@ -186,19 +168,6 @@ function SelectMapModal({ setIsLoading(false); } - // Either single, multiple or range - const [selectMode, setSelectMode] = useState("single"); - - function handleTileSelect(item) { - handleItemSelect( - item, - selectMode, - selectedGroupIds, - setSelectedGroupIds - // TODO: Add new group support - ); - } - /** * Modal Controls */ @@ -207,21 +176,31 @@ function SelectMapModal({ onDone(); } - async function handleDone() { + async function handleMapSelect(mapId) { if (isLoading) { return; } - const groups = groupsFromIds(selectedGroupIds, mapGroups); - if (groups.length === 1 && groups[0].type === "item") { - setIsLoading(true); - const map = await getMap(groups[0].id); - const mapState = await getMapState(groups[0].id); - onMapChange(map, mapState); - setIsLoading(false); + setIsLoading(true); + const map = await getMap(mapId); + const mapState = await getMapState(mapId); + onMapChange(map, mapState); + setIsLoading(false); + onDone(); + } + + function handleSelectClick() { + if (isLoading) { + return; + } + if (selectedGroupIds.length === 1) { + const group = findGroup(mapGroups, selectedGroupIds[0]); + if (group && group.type === "item") { + handleMapSelect(group.id); + } } else { onMapChange(null, null); + onDone(); } - onDone(); } /** @@ -231,12 +210,6 @@ function SelectMapModal({ if (!isOpen) { return; } - if (shortcuts.selectRange(event)) { - setSelectMode("range"); - } - if (shortcuts.selectMultiple(event)) { - setSelectMode("multiple"); - } if (shortcuts.delete(event)) { const selectedMaps = getSelectedMaps(); // Selected maps and none are default @@ -252,26 +225,7 @@ function SelectMapModal({ } } - function handleKeyUp(event) { - if (!isOpen) { - return; - } - if (shortcuts.selectRange(event) && selectMode === "range") { - setSelectMode("single"); - } - if (shortcuts.selectMultiple(event) && selectMode === "multiple") { - setSelectMode("single"); - } - } - - useKeyboard(handleKeyDown, handleKeyUp); - - // Set select mode to single when cmd+tabing - function handleBlur() { - setSelectMode("single"); - } - - useBlur(handleBlur); + useKeyboard(handleKeyDown); const layout = useResponsiveLayout(); @@ -298,28 +252,34 @@ function SelectMapModal({ - setIsEditModalOpen(true)} - onMapsReset={() => setIsMapsResetModalOpen(true)} - onMapsRemove={() => setIsMapsRemoveModalOpen(true)} - onTileSelect={handleTileSelect} - onDone={handleDone} - selectMode={selectMode} - onSelectModeChange={setSelectMode} - search={search} - onSearchChange={handleSearchChange} - onMapsGroup={updateMapGroups} - databaseDisabled={databaseStatus === "disabled"} - /> + + + + setIsEditModalOpen(true)} + onMapSelect={handleMapSelect} + /> + + + setIsEditModalOpen(true)} + onMapSelect={handleMapSelect} + subgroup + /> + + +