Refactored group functions into common context and added group overlay
This commit is contained in:
parent
4b225e5c14
commit
b776b86186
@ -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 (
|
||||
|
@ -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({
|
||||
<Tile
|
||||
title={map.name}
|
||||
isSelected={isSelected}
|
||||
onSelect={() => 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"
|
||||
|
@ -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 (
|
||||
<Tile
|
||||
title={group.name}
|
||||
isSelected={isSelected}
|
||||
onSelect={() => onSelect(group)}
|
||||
onDoubleClick={() => canOpen && onOpen()}
|
||||
onSelect={() => onSelect(group.id)}
|
||||
onDoubleClick={onDoubleClick}
|
||||
>
|
||||
<Grid columns="1fr 1fr" p={2} sx={{ gridGap: 2 }}>
|
||||
{maps.slice(0, 4).map((map) => (
|
||||
<Grid columns={layout.gridTemplate} p={2} sx={{ gridGap: 2 }}>
|
||||
{maps.slice(0, 16).map((map) => (
|
||||
<MapTileImage sx={{ borderRadius: "8px" }} map={map} key={map.id} />
|
||||
))}
|
||||
</Grid>
|
||||
|
@ -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 (
|
||||
<MapTile
|
||||
key={map.id}
|
||||
map={map}
|
||||
isSelected={isSelected}
|
||||
onSelect={onTileSelect}
|
||||
onSelect={onGroupSelect}
|
||||
onEdit={onMapEdit}
|
||||
onDone={onDone}
|
||||
canEdit={
|
||||
isSelected &&
|
||||
selectMode === "single" &&
|
||||
selectedGroupIds.length === 1
|
||||
}
|
||||
onDoubleClick={() => 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 (
|
||||
<MapTileGroup
|
||||
key={group.id}
|
||||
group={group}
|
||||
maps={items.map((item) => 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 (
|
||||
<Box sx={{ position: "relative" }}>
|
||||
<FilterBar
|
||||
onFocus={() => onTileSelect()}
|
||||
search={search}
|
||||
onSearchChange={onSearchChange}
|
||||
selectMode={selectMode}
|
||||
onSelectModeChange={onSelectModeChange}
|
||||
onAdd={onMapAdd}
|
||||
addTitle="Add Map"
|
||||
/>
|
||||
<SimpleBar
|
||||
style={{
|
||||
height: layout.screenSize === "large" ? "600px" : "400px",
|
||||
}}
|
||||
>
|
||||
<Grid
|
||||
p={2}
|
||||
pb={4}
|
||||
pt={databaseDisabled ? 4 : 2}
|
||||
bg="muted"
|
||||
sx={{
|
||||
borderRadius: "4px",
|
||||
minHeight: layout.screenSize === "large" ? "600px" : "400px",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
gap={2}
|
||||
columns={layout.gridTemplate}
|
||||
onClick={() => onTileSelect()}
|
||||
>
|
||||
{tiles}
|
||||
</Grid>
|
||||
</SimpleBar>
|
||||
{databaseDisabled && (
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: "39px",
|
||||
left: 0,
|
||||
right: 0,
|
||||
textAlign: "center",
|
||||
borderRadius: "2px",
|
||||
}}
|
||||
bg="highlight"
|
||||
p={1}
|
||||
>
|
||||
<Text as="p" variant="body2">
|
||||
Map saving is unavailable. See <Link to="/faq#saving">FAQ</Link>{" "}
|
||||
for more information.
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
{selectedGroupIds.length > 0 && (
|
||||
<Flex
|
||||
sx={{
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
bg="overlay"
|
||||
>
|
||||
<Close
|
||||
title="Clear Selection"
|
||||
aria-label="Clear Selection"
|
||||
onClick={() => onTileSelect()}
|
||||
/>
|
||||
<Flex>
|
||||
<IconButton
|
||||
aria-label={multipleSelected ? "Reset Maps" : "Reset Map"}
|
||||
title={multipleSelected ? "Reset Maps" : "Reset Map"}
|
||||
onClick={() => onMapsReset()}
|
||||
disabled={!hasMapState}
|
||||
>
|
||||
<ResetMapIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
aria-label={multipleSelected ? "Remove Maps" : "Remove Map"}
|
||||
title={multipleSelected ? "Remove Maps" : "Remove Map"}
|
||||
onClick={() => onMapsRemove()}
|
||||
disabled={hasSelectedDefaultMap}
|
||||
>
|
||||
<RemoveMapIcon />
|
||||
</IconButton>
|
||||
</Flex>
|
||||
</Flex>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SortableTiles
|
||||
groups={groups}
|
||||
onGroupChange={onMapsGroup}
|
||||
groups={subgroup ? openGroupItems : groups}
|
||||
onGroupChange={onGroupsChange}
|
||||
renderTile={renderTile}
|
||||
renderTiles={renderTiles}
|
||||
onTileSelect={onTileSelect}
|
||||
onTileSelect={onGroupSelect}
|
||||
disableGrouping={subgroup}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -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 (
|
@ -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 (
|
||||
<DndContext
|
||||
onDragStart={handleDragStart}
|
||||
@ -86,21 +105,27 @@ function SortableTiles({
|
||||
collisionDetection={closestCenter}
|
||||
>
|
||||
<SortableContext items={groups}>
|
||||
{renderTiles(
|
||||
groups.map((group) => (
|
||||
<SortableTile id={group.id} key={group.id}>
|
||||
{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)}
|
||||
<Grid
|
||||
p={3}
|
||||
pb={4}
|
||||
sx={{
|
||||
borderRadius: "4px",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
gap={2}
|
||||
columns={layout.gridTemplate}
|
||||
onClick={() => onTileSelect()}
|
||||
>
|
||||
{groups.map((group) => (
|
||||
<SortableTile
|
||||
id={group.id}
|
||||
key={group.id}
|
||||
disableGrouping={disableGrouping}
|
||||
>
|
||||
{renderSortableGroup(group)}
|
||||
</SortableTile>
|
||||
))
|
||||
)}
|
||||
))}
|
||||
</Grid>
|
||||
{createPortal(
|
||||
<DragOverlay dropAnimation={null}>
|
||||
{dragId && (
|
@ -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,
|
16
src/components/tile/TilesContainer.js
Normal file
16
src/components/tile/TilesContainer.js
Normal file
@ -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 (
|
||||
<SimpleBar style={{ height: layout.tileContainerHeight }}>
|
||||
{children}
|
||||
</SimpleBar>
|
||||
);
|
||||
}
|
||||
|
||||
export default TilesContainer;
|
49
src/components/tile/TilesOverlay.js
Normal file
49
src/components/tile/TilesOverlay.js
Normal file
@ -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 (
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
top: 0,
|
||||
cursor: "pointer",
|
||||
}}
|
||||
p={3}
|
||||
bg="overlay"
|
||||
onClick={() => onGroupClose()}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
borderRadius: "8px",
|
||||
border: "1px solid",
|
||||
borderColor: "border",
|
||||
cursor: "default",
|
||||
}}
|
||||
bg="muted"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
p={3}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
<Close
|
||||
onClick={() => onGroupClose()}
|
||||
sx={{ position: "absolute", top: "16px", right: "16px" }}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default TilesOverlay;
|
@ -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({
|
||||
<Tile
|
||||
title={token.name}
|
||||
isSelected={isSelected}
|
||||
onSelect={() => onTokenSelect(token)}
|
||||
onSelect={() => onSelect(token.id)}
|
||||
onEdit={() => onTokenEdit(token.id)}
|
||||
canEdit={canEdit}
|
||||
badges={badges}
|
||||
|
@ -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 (
|
||||
<Tile
|
||||
title={group.name}
|
||||
isSelected={isSelected}
|
||||
onSelect={() => onSelect(group)}
|
||||
onDoubleClick={() => canOpen && onOpen()}
|
||||
onSelect={() => onSelect(group.id)}
|
||||
onDoubleClick={onDoubleClick}
|
||||
columns="1fr 1fr"
|
||||
>
|
||||
<Grid columns="1fr 1fr" p={2} sx={{ gridGap: 2 }}>
|
||||
{tokens.slice(0, 4).map((token) => (
|
||||
<Grid columns={layout.gridTemplate} p={2} sx={{ gridGap: 2 }}>
|
||||
{tokens.slice(0, 16).map((token) => (
|
||||
<TokenTileImage
|
||||
sx={{ padding: 1, borderRadius: "8px" }}
|
||||
sx={{ borderRadius: "8px" }}
|
||||
token={token}
|
||||
key={token.id}
|
||||
/>
|
||||
|
@ -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 (
|
||||
<TokenTile
|
||||
key={token.id}
|
||||
token={token}
|
||||
isSelected={isSelected}
|
||||
onTokenSelect={onTileSelect}
|
||||
onSelect={onGroupSelect}
|
||||
onTokenEdit={onTokenEdit}
|
||||
canEdit={
|
||||
isSelected &&
|
||||
token.type !== "default" &&
|
||||
selectMode === "single" &&
|
||||
selectedGroupIds.length === 1
|
||||
}
|
||||
canEdit={canEdit}
|
||||
badges={[`${token.defaultSize}x`]}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
const isSelected = selectedGroupIds.includes(group.id);
|
||||
const items = getGroupItems(group);
|
||||
const canOpen =
|
||||
isSelected && selectMode === "single" && selectedGroupIds.length === 1;
|
||||
return (
|
||||
<TokenTileGroup
|
||||
key={group.id}
|
||||
@ -83,128 +54,20 @@ function TokenTiles({
|
||||
tokens.find((token) => 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 (
|
||||
<Box sx={{ position: "relative" }}>
|
||||
<FilterBar
|
||||
onFocus={() => onTileSelect()}
|
||||
search={search}
|
||||
onSearchChange={onSearchChange}
|
||||
selectMode={selectMode}
|
||||
onSelectModeChange={onSelectModeChange}
|
||||
onAdd={onTokenAdd}
|
||||
addTitle="Add Token"
|
||||
/>
|
||||
<SimpleBar
|
||||
style={{
|
||||
height: layout.screenSize === "large" ? "600px" : "400px",
|
||||
}}
|
||||
>
|
||||
<Grid
|
||||
p={2}
|
||||
pb={4}
|
||||
pt={databaseDisabled ? 4 : 2}
|
||||
bg="muted"
|
||||
sx={{
|
||||
borderRadius: "4px",
|
||||
minHeight: layout.screenSize === "large" ? "600px" : "400px",
|
||||
}}
|
||||
gap={2}
|
||||
columns={layout.gridTemplate}
|
||||
onClick={() => onTileSelect()}
|
||||
>
|
||||
{tiles}
|
||||
</Grid>
|
||||
</SimpleBar>
|
||||
{databaseDisabled && (
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: "39px",
|
||||
left: 0,
|
||||
right: 0,
|
||||
textAlign: "center",
|
||||
borderRadius: "2px",
|
||||
}}
|
||||
bg="highlight"
|
||||
p={1}
|
||||
>
|
||||
<Text as="p" variant="body2">
|
||||
Token saving is unavailable. See <Link to="/faq#saving">FAQ</Link>{" "}
|
||||
for more information.
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
{selectedGroupIds.length > 0 && (
|
||||
<Flex
|
||||
sx={{
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
bg="overlay"
|
||||
>
|
||||
<Close
|
||||
title="Clear Selection"
|
||||
aria-label="Clear Selection"
|
||||
onClick={() => onTileSelect()}
|
||||
/>
|
||||
<Flex>
|
||||
<IconButton
|
||||
aria-label={hideTitle}
|
||||
title={hideTitle}
|
||||
disabled={hasSelectedDefaultToken}
|
||||
onClick={() => onTokensHide(allTokensVisible)}
|
||||
>
|
||||
{allTokensVisible ? <TokenShowIcon /> : <TokenHideIcon />}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
aria-label={multipleSelected ? "Remove Tokens" : "Remove Token"}
|
||||
title={multipleSelected ? "Remove Tokens" : "Remove Token"}
|
||||
onClick={() => onTokensRemove()}
|
||||
disabled={hasSelectedDefaultToken}
|
||||
>
|
||||
<RemoveTokenIcon />
|
||||
</IconButton>
|
||||
</Flex>
|
||||
</Flex>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SortableTiles
|
||||
groups={groups}
|
||||
onGroupChange={onTokensGroup}
|
||||
groups={subgroup ? openGroupItems : groups}
|
||||
onGroupChange={onGroupsChange}
|
||||
renderTile={renderTile}
|
||||
renderTiles={renderTiles}
|
||||
onTileSelect={onTileSelect}
|
||||
onTileSelect={onGroupSelect}
|
||||
disableGrouping={subgroup}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
142
src/contexts/GroupContext.js
Normal file
142
src/contexts/GroupContext.js
Normal file
@ -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 (
|
||||
<GroupContext.Provider value={value}>{children}</GroupContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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({
|
||||
<Label pt={2} pb={1}>
|
||||
Select or import a map
|
||||
</Label>
|
||||
<MapTiles
|
||||
maps={maps}
|
||||
mapStates={mapStates}
|
||||
groups={mapGroups}
|
||||
selectedGroupIds={selectedGroupIds}
|
||||
onMapAdd={openImageDialog}
|
||||
onMapEdit={() => 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"}
|
||||
/>
|
||||
<Box sx={{ position: "relative" }} bg="muted">
|
||||
<GroupProvider
|
||||
groups={mapGroups}
|
||||
onGroupsChange={updateMapGroups}
|
||||
onGroupsSelect={setSelectedGroupIds}
|
||||
disabled={!isOpen}
|
||||
>
|
||||
<TilesContainer>
|
||||
<MapTiles
|
||||
maps={maps}
|
||||
onMapEdit={() => setIsEditModalOpen(true)}
|
||||
onMapSelect={handleMapSelect}
|
||||
/>
|
||||
</TilesContainer>
|
||||
<TilesOverlay>
|
||||
<MapTiles
|
||||
maps={maps}
|
||||
onMapEdit={() => setIsEditModalOpen(true)}
|
||||
onMapSelect={handleMapSelect}
|
||||
subgroup
|
||||
/>
|
||||
</TilesOverlay>
|
||||
</GroupProvider>
|
||||
</Box>
|
||||
<Button
|
||||
variant="primary"
|
||||
disabled={isLoading || selectedGroupIds.length > 1}
|
||||
onClick={handleDone}
|
||||
onClick={handleSelectClick}
|
||||
mt={2}
|
||||
>
|
||||
Select
|
||||
|
@ -1,6 +1,5 @@
|
||||
import React, { useRef, useState } from "react";
|
||||
import { Flex, Label, Button } from "theme-ui";
|
||||
|
||||
import { Flex, Label, Button, Box } from "theme-ui";
|
||||
import { useToasts } from "react-toast-notifications";
|
||||
|
||||
import EditTokenModal from "./EditTokenModal";
|
||||
@ -8,23 +7,23 @@ import ConfirmModal from "./ConfirmModal";
|
||||
|
||||
import Modal from "../components/Modal";
|
||||
import ImageDrop from "../components/ImageDrop";
|
||||
import TokenTiles from "../components/token/TokenTiles";
|
||||
import LoadingOverlay from "../components/LoadingOverlay";
|
||||
|
||||
import {
|
||||
groupsFromIds,
|
||||
handleItemSelect,
|
||||
itemsFromGroups,
|
||||
} from "../helpers/select";
|
||||
import TokenTiles from "../components/token/TokenTiles";
|
||||
|
||||
import TilesOverlay from "../components/tile/TilesOverlay";
|
||||
import TilesContainer from "../components/tile/TilesContainer";
|
||||
|
||||
import { groupsFromIds, itemsFromGroups } from "../helpers/select";
|
||||
import { createTokenFromFile } from "../helpers/token";
|
||||
|
||||
import useResponsiveLayout from "../hooks/useResponsiveLayout";
|
||||
|
||||
import { useTokenData } from "../contexts/TokenDataContext";
|
||||
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";
|
||||
|
||||
@ -36,25 +35,14 @@ function SelectTokensModal({ isOpen, onRequestClose }) {
|
||||
tokens,
|
||||
addToken,
|
||||
removeTokens,
|
||||
updateTokens,
|
||||
// updateTokens,
|
||||
tokensLoading,
|
||||
tokenGroups,
|
||||
updateTokenGroups,
|
||||
updateToken,
|
||||
} = useTokenData();
|
||||
const { databaseStatus } = useDatabase();
|
||||
const { addAssets } = useAssets();
|
||||
|
||||
/**
|
||||
* Search
|
||||
*/
|
||||
const [search, setSearch] = useState("");
|
||||
// const [filteredTokens, filteredTokenScores] = useSearch(ownedTokens, search);
|
||||
|
||||
function handleSearchChange(event) {
|
||||
setSearch(event.target.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Image Upload
|
||||
*/
|
||||
@ -67,12 +55,6 @@ function SelectTokensModal({ isOpen, onRequestClose }) {
|
||||
);
|
||||
const largeImageWarningFiles = useRef();
|
||||
|
||||
function openImageDialog() {
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.click();
|
||||
}
|
||||
}
|
||||
|
||||
async function handleImagesUpload(files) {
|
||||
if (navigator.storage) {
|
||||
// Attempt to enable persistant storage
|
||||
@ -155,26 +137,13 @@ function SelectTokensModal({ isOpen, onRequestClose }) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
async function handleTokensHide(hideInSidebar) {
|
||||
setIsLoading(true);
|
||||
const selectedTokens = getSelectedTokens();
|
||||
const selectedTokenIds = selectedTokens.map((token) => token.id);
|
||||
await updateTokens(selectedTokenIds, { hideInSidebar });
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
// Either single, multiple or range
|
||||
const [selectMode, setSelectMode] = useState("single");
|
||||
|
||||
async function handleTileSelect(item) {
|
||||
handleItemSelect(
|
||||
item,
|
||||
selectMode,
|
||||
selectedGroupIds,
|
||||
setSelectedGroupIds
|
||||
// TODO: Rework group support
|
||||
);
|
||||
}
|
||||
// async function handleTokensHide(hideInSidebar) {
|
||||
// setIsLoading(true);
|
||||
// const selectedTokens = getSelectedTokens();
|
||||
// const selectedTokenIds = selectedTokens.map((token) => token.id);
|
||||
// await updateTokens(selectedTokenIds, { hideInSidebar });
|
||||
// setIsLoading(false);
|
||||
// }
|
||||
|
||||
/**
|
||||
* Shortcuts
|
||||
@ -183,12 +152,6 @@ function SelectTokensModal({ isOpen, onRequestClose }) {
|
||||
if (!isOpen) {
|
||||
return;
|
||||
}
|
||||
if (shortcuts.selectRange(event)) {
|
||||
setSelectMode("range");
|
||||
}
|
||||
if (shortcuts.selectMultiple(event)) {
|
||||
setSelectMode("multiple");
|
||||
}
|
||||
if (shortcuts.delete(event)) {
|
||||
const selectedTokens = getSelectedTokens();
|
||||
// Selected tokens and none are default
|
||||
@ -203,26 +166,7 @@ function SelectTokensModal({ isOpen, onRequestClose }) {
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
@ -249,22 +193,29 @@ function SelectTokensModal({ isOpen, onRequestClose }) {
|
||||
<Label pt={2} pb={1}>
|
||||
Edit or import a token
|
||||
</Label>
|
||||
<TokenTiles
|
||||
tokens={tokens}
|
||||
groups={tokenGroups}
|
||||
selectedGroupIds={selectedGroupIds}
|
||||
onTokenAdd={openImageDialog}
|
||||
onTokenEdit={() => setIsEditModalOpen(true)}
|
||||
onTokensRemove={() => setIsTokensRemoveModalOpen(true)}
|
||||
onTileSelect={handleTileSelect}
|
||||
selectMode={selectMode}
|
||||
onSelectModeChange={setSelectMode}
|
||||
search={search}
|
||||
onSearchChange={handleSearchChange}
|
||||
onTokensGroup={updateTokenGroups}
|
||||
onTokensHide={handleTokensHide}
|
||||
databaseDisabled={databaseStatus === "disabled"}
|
||||
/>
|
||||
<Box sx={{ position: "relative" }} bg="muted">
|
||||
<GroupProvider
|
||||
groups={tokenGroups}
|
||||
onGroupsChange={updateTokenGroups}
|
||||
onGroupsSelect={setSelectedGroupIds}
|
||||
disabled={!isOpen}
|
||||
>
|
||||
<TilesContainer>
|
||||
<TokenTiles
|
||||
tokens={tokens}
|
||||
onTokenEdit={() => setIsEditModalOpen(true)}
|
||||
/>
|
||||
</TilesContainer>
|
||||
<TilesOverlay>
|
||||
<TokenTiles
|
||||
tokens={tokens}
|
||||
onTokenEdit={() => setIsEditModalOpen(true)}
|
||||
subgroup
|
||||
/>
|
||||
</TilesOverlay>
|
||||
</GroupProvider>
|
||||
</Box>
|
||||
|
||||
<Button
|
||||
variant="primary"
|
||||
disabled={isLoading}
|
||||
|
Loading…
x
Reference in New Issue
Block a user