2021-03-19 13:29:07 +11:00
|
|
|
import React, { useRef, useState } from "react";
|
2021-05-24 13:34:21 +10:00
|
|
|
import { Flex, Label, Button, Box } from "theme-ui";
|
2021-04-15 16:17:12 +10:00
|
|
|
import { useToasts } from "react-toast-notifications";
|
2021-05-28 17:06:20 +10:00
|
|
|
import ReactResizeDetector from "react-resize-detector";
|
2020-05-19 16:21:01 +10:00
|
|
|
|
2020-10-01 22:32:21 +10:00
|
|
|
import EditTokenModal from "./EditTokenModal";
|
2020-10-10 15:32:59 +11:00
|
|
|
import ConfirmModal from "./ConfirmModal";
|
2020-10-01 22:32:21 +10:00
|
|
|
|
2020-05-19 16:21:01 +10:00
|
|
|
import Modal from "../components/Modal";
|
|
|
|
|
import ImageDrop from "../components/ImageDrop";
|
2020-11-26 16:29:10 +11:00
|
|
|
import LoadingOverlay from "../components/LoadingOverlay";
|
2020-05-19 16:21:01 +10:00
|
|
|
|
2021-05-24 13:34:21 +10:00
|
|
|
import TokenTiles from "../components/token/TokenTiles";
|
|
|
|
|
|
|
|
|
|
import TilesOverlay from "../components/tile/TilesOverlay";
|
|
|
|
|
import TilesContainer from "../components/tile/TilesContainer";
|
2021-05-28 17:06:20 +10:00
|
|
|
import TilesAddDroppable from "../components/tile/TilesAddDroppable";
|
2021-05-24 13:34:21 +10:00
|
|
|
|
2021-05-28 17:06:20 +10:00
|
|
|
import {
|
|
|
|
|
groupsFromIds,
|
|
|
|
|
itemsFromGroups,
|
|
|
|
|
getGroupItems,
|
|
|
|
|
} from "../helpers/group";
|
|
|
|
|
import {
|
|
|
|
|
createTokenFromFile,
|
|
|
|
|
createTokenState,
|
|
|
|
|
clientPositionToMapPosition,
|
|
|
|
|
} from "../helpers/token";
|
|
|
|
|
import Vector2 from "../helpers/Vector2";
|
2021-02-04 15:06:34 +11:00
|
|
|
|
|
|
|
|
import useResponsiveLayout from "../hooks/useResponsiveLayout";
|
2020-05-19 19:03:36 +10:00
|
|
|
|
2021-02-06 13:32:38 +11:00
|
|
|
import { useTokenData } from "../contexts/TokenDataContext";
|
|
|
|
|
import { useAuth } from "../contexts/AuthContext";
|
2021-05-24 13:34:21 +10:00
|
|
|
import { useKeyboard } from "../contexts/KeyboardContext";
|
2021-04-23 11:48:24 +10:00
|
|
|
import { useAssets } from "../contexts/AssetsContext";
|
2021-05-24 13:34:21 +10:00
|
|
|
import { GroupProvider } from "../contexts/GroupContext";
|
2021-05-28 13:13:21 +10:00
|
|
|
import { TileDragProvider } from "../contexts/TileDragContext";
|
2021-05-28 17:06:20 +10:00
|
|
|
import { useMapStage } from "../contexts/MapStageContext";
|
2020-05-19 16:21:01 +10:00
|
|
|
|
2021-03-25 16:31:06 +11:00
|
|
|
import shortcuts from "../shortcuts";
|
|
|
|
|
|
2021-05-28 17:06:20 +10:00
|
|
|
function SelectTokensModal({ isOpen, onRequestClose, onMapTokenStateCreate }) {
|
2021-04-15 16:17:12 +10:00
|
|
|
const { addToast } = useToasts();
|
|
|
|
|
|
2021-02-06 13:32:38 +11:00
|
|
|
const { userId } = useAuth();
|
2020-11-26 16:29:10 +11:00
|
|
|
const {
|
2021-05-09 12:04:31 +10:00
|
|
|
tokens,
|
2020-11-26 16:29:10 +11:00
|
|
|
addToken,
|
|
|
|
|
removeTokens,
|
2021-05-24 13:34:21 +10:00
|
|
|
// updateTokens,
|
2020-11-26 16:29:10 +11:00
|
|
|
tokensLoading,
|
2021-05-09 12:04:31 +10:00
|
|
|
tokenGroups,
|
|
|
|
|
updateTokenGroups,
|
2021-05-20 12:22:07 +10:00
|
|
|
updateToken,
|
2021-05-28 17:06:20 +10:00
|
|
|
tokensById,
|
2021-02-06 13:32:38 +11:00
|
|
|
} = useTokenData();
|
2021-04-23 11:48:24 +10:00
|
|
|
const { addAssets } = useAssets();
|
2020-05-19 16:21:01 +10:00
|
|
|
|
2020-10-01 22:32:21 +10:00
|
|
|
/**
|
|
|
|
|
* Image Upload
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
const fileInputRef = useRef();
|
2021-02-08 16:53:56 +11:00
|
|
|
const [isLoading, setIsLoading] = useState(false);
|
2020-10-01 22:32:21 +10:00
|
|
|
|
2021-04-15 16:17:12 +10:00
|
|
|
const [isLargeImageWarningModalOpen, setShowLargeImageWarning] = useState(
|
|
|
|
|
false
|
|
|
|
|
);
|
|
|
|
|
const largeImageWarningFiles = useRef();
|
|
|
|
|
|
2020-05-31 10:53:33 +10:00
|
|
|
async function handleImagesUpload(files) {
|
2020-11-27 21:00:08 +11:00
|
|
|
if (navigator.storage) {
|
|
|
|
|
// Attempt to enable persistant storage
|
|
|
|
|
await navigator.storage.persist();
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-15 16:17:12 +10:00
|
|
|
let tokenFiles = [];
|
|
|
|
|
for (let file of files) {
|
|
|
|
|
if (file.size > 5e7) {
|
|
|
|
|
addToast(`Unable to import token ${file.name} as it is over 50MB`);
|
|
|
|
|
} else {
|
|
|
|
|
tokenFiles.push(file);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Any file greater than 20MB
|
|
|
|
|
if (tokenFiles.some((file) => file.size > 2e7)) {
|
|
|
|
|
largeImageWarningFiles.current = tokenFiles;
|
|
|
|
|
setShowLargeImageWarning(true);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (let file of tokenFiles) {
|
|
|
|
|
await handleImageUpload(file);
|
|
|
|
|
}
|
2021-04-15 16:28:39 +10:00
|
|
|
|
|
|
|
|
clearFileInput();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function clearFileInput() {
|
|
|
|
|
// Set file input to null to allow adding the same image 2 times in a row
|
|
|
|
|
if (fileInputRef.current) {
|
|
|
|
|
fileInputRef.current.value = null;
|
|
|
|
|
}
|
2021-04-15 16:17:12 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleLargeImageWarningCancel() {
|
|
|
|
|
largeImageWarningFiles.current = undefined;
|
|
|
|
|
setShowLargeImageWarning(false);
|
2021-04-15 16:28:39 +10:00
|
|
|
clearFileInput();
|
2021-04-15 16:17:12 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function handleLargeImageWarningConfirm() {
|
|
|
|
|
setShowLargeImageWarning(false);
|
|
|
|
|
const files = largeImageWarningFiles.current;
|
|
|
|
|
for (let file of files) {
|
|
|
|
|
await handleImageUpload(file);
|
|
|
|
|
}
|
|
|
|
|
largeImageWarningFiles.current = undefined;
|
2021-04-15 16:28:39 +10:00
|
|
|
clearFileInput();
|
2020-05-31 10:53:33 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function handleImageUpload(file) {
|
2021-02-08 16:53:56 +11:00
|
|
|
setIsLoading(true);
|
2021-05-09 12:04:31 +10:00
|
|
|
const { token, assets } = await createTokenFromFile(file, userId);
|
|
|
|
|
await addToken(token);
|
|
|
|
|
await addAssets(assets);
|
|
|
|
|
setIsLoading(false);
|
2020-05-19 19:03:36 +10:00
|
|
|
}
|
|
|
|
|
|
2020-05-20 12:37:29 +10:00
|
|
|
/**
|
2020-10-01 22:32:21 +10:00
|
|
|
* Token controls
|
2020-05-20 12:37:29 +10:00
|
|
|
*/
|
2020-10-01 22:32:21 +10:00
|
|
|
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
|
2021-05-20 12:22:07 +10:00
|
|
|
const [selectedGroupIds, setSelectedGroupIds] = useState([]);
|
|
|
|
|
|
|
|
|
|
function getSelectedTokens() {
|
|
|
|
|
const groups = groupsFromIds(selectedGroupIds, tokenGroups);
|
|
|
|
|
return itemsFromGroups(groups, tokens);
|
|
|
|
|
}
|
2020-05-20 12:37:29 +10:00
|
|
|
|
2020-10-10 15:32:59 +11:00
|
|
|
const [isTokensRemoveModalOpen, setIsTokensRemoveModalOpen] = useState(false);
|
2020-10-01 22:32:21 +10:00
|
|
|
async function handleTokensRemove() {
|
2021-02-08 17:06:54 +11:00
|
|
|
setIsLoading(true);
|
2020-10-10 15:32:59 +11:00
|
|
|
setIsTokensRemoveModalOpen(false);
|
2021-05-20 12:22:07 +10:00
|
|
|
const selectedTokens = getSelectedTokens();
|
|
|
|
|
const selectedTokenIds = selectedTokens.map((token) => token.id);
|
2020-10-01 22:32:21 +10:00
|
|
|
await removeTokens(selectedTokenIds);
|
2021-05-20 12:22:07 +10:00
|
|
|
setSelectedGroupIds([]);
|
2021-02-08 17:06:54 +11:00
|
|
|
setIsLoading(false);
|
2020-06-28 15:43:45 +10:00
|
|
|
}
|
|
|
|
|
|
2021-05-24 13:34:21 +10:00
|
|
|
// async function handleTokensHide(hideInSidebar) {
|
|
|
|
|
// setIsLoading(true);
|
|
|
|
|
// const selectedTokens = getSelectedTokens();
|
|
|
|
|
// const selectedTokenIds = selectedTokens.map((token) => token.id);
|
|
|
|
|
// await updateTokens(selectedTokenIds, { hideInSidebar });
|
|
|
|
|
// setIsLoading(false);
|
|
|
|
|
// }
|
2020-10-01 22:32:21 +10:00
|
|
|
|
2021-05-28 17:06:20 +10:00
|
|
|
const mapStageRef = useMapStage();
|
|
|
|
|
function handleTokensAddToMap(groupIds, rect) {
|
|
|
|
|
let clientPosition = new Vector2(
|
|
|
|
|
rect.width / 2 + rect.offsetLeft,
|
|
|
|
|
rect.height / 2 + rect.offsetTop
|
|
|
|
|
);
|
|
|
|
|
const mapStage = mapStageRef.current;
|
|
|
|
|
if (!mapStage) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let position = clientPositionToMapPosition(mapStage, clientPosition, false);
|
|
|
|
|
if (!position) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (let id of groupIds) {
|
|
|
|
|
if (id in tokensById) {
|
|
|
|
|
onMapTokenStateCreate(
|
|
|
|
|
createTokenState(tokensById[id], position, userId)
|
|
|
|
|
);
|
|
|
|
|
position = Vector2.add(position, 0.01);
|
|
|
|
|
} else {
|
|
|
|
|
// Check if a group is selected
|
|
|
|
|
const group = tokenGroups.find(
|
|
|
|
|
(group) => group.id === id && group.type === "group"
|
|
|
|
|
);
|
|
|
|
|
if (group) {
|
|
|
|
|
// Add all tokens of group
|
|
|
|
|
const items = getGroupItems(group);
|
|
|
|
|
for (let item of items) {
|
|
|
|
|
if (item.id in tokensById) {
|
|
|
|
|
onMapTokenStateCreate(
|
|
|
|
|
createTokenState(tokensById[item.id], position, userId)
|
|
|
|
|
);
|
|
|
|
|
position = Vector2.add(position, 0.01);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-01 22:32:21 +10:00
|
|
|
/**
|
|
|
|
|
* Shortcuts
|
|
|
|
|
*/
|
2021-03-25 16:31:06 +11:00
|
|
|
function handleKeyDown(event) {
|
2020-10-01 22:32:21 +10:00
|
|
|
if (!isOpen) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2021-03-25 16:31:06 +11:00
|
|
|
if (shortcuts.delete(event)) {
|
2021-05-20 12:22:07 +10:00
|
|
|
const selectedTokens = getSelectedTokens();
|
2020-10-10 15:44:07 +11:00
|
|
|
// Selected tokens and none are default
|
|
|
|
|
if (
|
2021-05-20 12:22:07 +10:00
|
|
|
selectedTokens.length > 0 &&
|
2020-10-10 15:44:07 +11:00
|
|
|
!selectedTokens.some((token) => token.type === "default")
|
|
|
|
|
) {
|
2021-01-25 09:00:19 +11:00
|
|
|
// Ensure all other modals are closed
|
|
|
|
|
setIsEditModalOpen(false);
|
2020-10-10 15:44:07 +11:00
|
|
|
setIsTokensRemoveModalOpen(true);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-05-20 12:37:29 +10:00
|
|
|
}
|
|
|
|
|
|
2021-05-24 13:34:21 +10:00
|
|
|
useKeyboard(handleKeyDown);
|
2020-10-10 11:29:42 +11:00
|
|
|
|
2021-01-03 14:53:06 +11:00
|
|
|
const layout = useResponsiveLayout();
|
|
|
|
|
|
2021-05-28 17:06:20 +10:00
|
|
|
const [modalSize, setModalSize] = useState({ width: 0, height: 0 });
|
|
|
|
|
function handleModalResize(width, height) {
|
|
|
|
|
setModalSize({ width, height });
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-19 16:21:01 +10:00
|
|
|
return (
|
2020-09-06 13:20:05 +10:00
|
|
|
<Modal
|
|
|
|
|
isOpen={isOpen}
|
2020-10-01 22:32:21 +10:00
|
|
|
onRequestClose={onRequestClose}
|
2021-01-03 14:53:06 +11:00
|
|
|
style={{ maxWidth: layout.modalSize, width: "calc(100% - 16px)" }}
|
2020-09-06 13:20:05 +10:00
|
|
|
>
|
2020-05-31 10:53:33 +10:00
|
|
|
<ImageDrop onDrop={handleImagesUpload} dropText="Drop token to upload">
|
2020-05-19 16:21:01 +10:00
|
|
|
<input
|
2020-05-31 10:53:33 +10:00
|
|
|
onChange={(event) => handleImagesUpload(event.target.files)}
|
2020-05-19 16:21:01 +10:00
|
|
|
type="file"
|
|
|
|
|
accept="image/*"
|
|
|
|
|
style={{ display: "none" }}
|
|
|
|
|
ref={fileInputRef}
|
2020-05-31 10:53:33 +10:00
|
|
|
multiple
|
2020-05-19 16:21:01 +10:00
|
|
|
/>
|
2021-05-28 17:06:20 +10:00
|
|
|
<ReactResizeDetector
|
|
|
|
|
handleWidth
|
|
|
|
|
handleHeight
|
|
|
|
|
onResize={handleModalResize}
|
2020-05-19 16:21:01 +10:00
|
|
|
>
|
2021-05-28 17:06:20 +10:00
|
|
|
<Flex
|
|
|
|
|
sx={{
|
|
|
|
|
flexDirection: "column",
|
|
|
|
|
}}
|
2020-05-20 12:37:29 +10:00
|
|
|
>
|
2021-05-28 17:06:20 +10:00
|
|
|
<Label pt={2} pb={1}>
|
|
|
|
|
Edit or import a token
|
|
|
|
|
</Label>
|
|
|
|
|
<Box sx={{ position: "relative" }}>
|
|
|
|
|
<GroupProvider
|
|
|
|
|
groups={tokenGroups}
|
|
|
|
|
onGroupsChange={updateTokenGroups}
|
|
|
|
|
onGroupsSelect={setSelectedGroupIds}
|
|
|
|
|
disabled={!isOpen}
|
|
|
|
|
>
|
|
|
|
|
<TileDragProvider onDragAdd={handleTokensAddToMap}>
|
|
|
|
|
<TilesAddDroppable containerSize={modalSize} />
|
|
|
|
|
<TilesContainer>
|
|
|
|
|
<TokenTiles
|
|
|
|
|
tokens={tokens}
|
|
|
|
|
onTokenEdit={() => setIsEditModalOpen(true)}
|
|
|
|
|
/>
|
|
|
|
|
</TilesContainer>
|
|
|
|
|
</TileDragProvider>
|
|
|
|
|
<TileDragProvider onDragAdd={handleTokensAddToMap}>
|
|
|
|
|
<TilesAddDroppable containerSize={modalSize} />
|
|
|
|
|
<TilesOverlay>
|
|
|
|
|
<TokenTiles
|
|
|
|
|
tokens={tokens}
|
|
|
|
|
onTokenEdit={() => setIsEditModalOpen(true)}
|
|
|
|
|
subgroup
|
|
|
|
|
/>
|
|
|
|
|
</TilesOverlay>
|
|
|
|
|
</TileDragProvider>
|
|
|
|
|
</GroupProvider>
|
|
|
|
|
</Box>
|
|
|
|
|
|
|
|
|
|
<Button
|
|
|
|
|
variant="primary"
|
|
|
|
|
disabled={isLoading}
|
|
|
|
|
onClick={onRequestClose}
|
|
|
|
|
mt={2}
|
|
|
|
|
>
|
|
|
|
|
Done
|
|
|
|
|
</Button>
|
|
|
|
|
</Flex>
|
|
|
|
|
</ReactResizeDetector>
|
2020-05-19 16:21:01 +10:00
|
|
|
</ImageDrop>
|
2021-02-08 16:53:56 +11:00
|
|
|
{(isLoading || tokensLoading) && <LoadingOverlay bg="overlay" />}
|
2020-10-01 22:32:21 +10:00
|
|
|
<EditTokenModal
|
|
|
|
|
isOpen={isEditModalOpen}
|
|
|
|
|
onDone={() => setIsEditModalOpen(false)}
|
2021-05-20 12:22:07 +10:00
|
|
|
token={
|
|
|
|
|
selectedGroupIds.length === 1 &&
|
|
|
|
|
tokens.find((token) => token.id === selectedGroupIds[0])
|
|
|
|
|
}
|
|
|
|
|
onTokenUpdate={updateToken}
|
2020-10-01 22:32:21 +10:00
|
|
|
/>
|
2020-10-10 15:32:59 +11:00
|
|
|
<ConfirmModal
|
|
|
|
|
isOpen={isTokensRemoveModalOpen}
|
|
|
|
|
onRequestClose={() => setIsTokensRemoveModalOpen(false)}
|
|
|
|
|
onConfirm={handleTokensRemove}
|
|
|
|
|
confirmText="Remove"
|
2021-05-20 12:22:07 +10:00
|
|
|
label="Remove Token(s)"
|
2020-10-10 15:32:59 +11:00
|
|
|
description="This operation cannot be undone."
|
|
|
|
|
/>
|
2021-04-15 16:17:12 +10:00
|
|
|
<ConfirmModal
|
|
|
|
|
isOpen={isLargeImageWarningModalOpen}
|
|
|
|
|
onRequestClose={handleLargeImageWarningCancel}
|
|
|
|
|
onConfirm={handleLargeImageWarningConfirm}
|
|
|
|
|
confirmText="Continue"
|
|
|
|
|
label="Warning"
|
|
|
|
|
description="An imported image is larger than 20MB, this may cause slowness. Continue?"
|
|
|
|
|
/>
|
2020-05-19 16:21:01 +10:00
|
|
|
</Modal>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default SelectTokensModal;
|