Files
grungnet/src/modals/SelectTokensModal.js

294 lines
8.5 KiB
JavaScript
Raw Normal View History

2021-06-05 16:38:01 +10:00
import React, { useRef, useState, useEffect } from "react";
import { Flex, Label, Button, Box } from "theme-ui";
import { useToasts } from "react-toast-notifications";
import ReactResizeDetector from "react-resize-detector";
import EditTokenModal from "./EditTokenModal";
import ConfirmModal from "./ConfirmModal";
import Modal from "../components/Modal";
import LoadingOverlay from "../components/LoadingOverlay";
import ImageDrop from "../components/file/ImageDrop";
import TokenTiles from "../components/token/TokenTiles";
2021-06-05 13:04:12 +10:00
import TokenEditBar from "../components/token/TokenEditBar";
import TilesOverlay from "../components/tile/TilesOverlay";
import TilesContainer from "../components/tile/TilesContainer";
import TilesAddDroppable from "../components/tile/TilesAddDroppable";
2021-06-05 16:38:01 +10:00
import TileActionBar from "../components/tile/TileActionBar";
2021-06-05 16:38:01 +10:00
import { getGroupItems, getItemNames } from "../helpers/group";
import {
createTokenFromFile,
createTokenState,
clientPositionToMapPosition,
} from "../helpers/token";
import Vector2 from "../helpers/Vector2";
import useResponsiveLayout from "../hooks/useResponsiveLayout";
import { useTokenData } from "../contexts/TokenDataContext";
import { useAuth } from "../contexts/AuthContext";
2021-04-23 11:48:24 +10:00
import { useAssets } from "../contexts/AssetsContext";
import { GroupProvider } from "../contexts/GroupContext";
import { TileDragProvider } from "../contexts/TileDragContext";
import { useMapStage } from "../contexts/MapStageContext";
function SelectTokensModal({ isOpen, onRequestClose, onMapTokensStateCreate }) {
const { addToast } = useToasts();
const { userId } = useAuth();
const {
tokens,
addToken,
tokensLoading,
tokenGroups,
updateTokenGroups,
2021-05-20 12:22:07 +10:00
updateToken,
tokensById,
} = useTokenData();
2021-04-23 11:48:24 +10:00
const { addAssets } = useAssets();
2021-06-05 16:38:01 +10:00
// Get token names for group filtering
const [tokenNames, setTokenNames] = useState(getItemNames(tokens));
useEffect(() => {
setTokenNames(getItemNames(tokens));
}, [tokens]);
/**
* Image Upload
*/
const fileInputRef = useRef();
const [isLoading, setIsLoading] = useState(false);
const [isLargeImageWarningModalOpen, setShowLargeImageWarning] = useState(
false
);
const largeImageWarningFiles = useRef();
async function handleImagesUpload(files) {
if (navigator.storage) {
// Attempt to enable persistant storage
await navigator.storage.persist();
}
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-06-05 16:38:01 +10:00
function openImageDialog() {
if (fileInputRef.current) {
fileInputRef.current.click();
}
}
function handleLargeImageWarningCancel() {
largeImageWarningFiles.current = undefined;
setShowLargeImageWarning(false);
2021-04-15 16:28:39 +10:00
clearFileInput();
}
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();
}
async function handleImageUpload(file) {
setIsLoading(true);
const { token, assets } = await createTokenFromFile(file, userId);
await addToken(token);
await addAssets(assets);
setIsLoading(false);
}
2020-05-20 12:37:29 +10:00
/**
* Token controls
2020-05-20 12:37:29 +10:00
*/
2021-06-05 13:04:12 +10:00
const [editingTokenId, setEditingTokenId] = useState();
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;
}
let newTokenStates = [];
for (let id of groupIds) {
if (id in tokensById) {
newTokenStates.push(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) {
newTokenStates.push(
createTokenState(tokensById[item.id], position, userId)
);
position = Vector2.add(position, 0.01);
}
}
}
}
}
if (newTokenStates.length > 0) {
onMapTokensStateCreate(newTokenStates);
}
}
const layout = useResponsiveLayout();
const [modalSize, setModalSize] = useState({ width: 0, height: 0 });
function handleModalResize(width, height) {
setModalSize({ width, height });
}
return (
<Modal
isOpen={isOpen}
onRequestClose={onRequestClose}
style={{ maxWidth: layout.modalSize, width: "calc(100% - 16px)" }}
>
<ImageDrop onDrop={handleImagesUpload} dropText="Drop token to import">
<input
onChange={(event) => handleImagesUpload(event.target.files)}
type="file"
accept="image/jpeg, image/gif, image/png, image/webp"
style={{ display: "none" }}
ref={fileInputRef}
multiple
/>
<ReactResizeDetector
handleWidth
handleHeight
onResize={handleModalResize}
>
2021-06-05 13:04:12 +10:00
<GroupProvider
groups={tokenGroups}
2021-06-05 16:38:01 +10:00
itemNames={tokenNames}
2021-06-05 13:04:12 +10:00
onGroupsChange={updateTokenGroups}
disabled={!isOpen}
2020-05-20 12:37:29 +10:00
>
2021-06-05 13:04:12 +10:00
<Flex
sx={{
flexDirection: "column",
}}
>
<Label pt={2} pb={1}>
Edit or import a token
</Label>
2021-06-05 16:38:01 +10:00
<TileActionBar
onAdd={openImageDialog}
addTitle="Import Token(s)"
/>
2021-06-05 13:04:12 +10:00
<Box sx={{ position: "relative" }}>
<TileDragProvider onDragAdd={handleTokensAddToMap}>
<TilesAddDroppable containerSize={modalSize} />
<TilesContainer>
<TokenTiles
tokens={tokens}
2021-06-05 13:04:12 +10:00
onTokenEdit={setEditingTokenId}
/>
</TilesContainer>
</TileDragProvider>
<TileDragProvider onDragAdd={handleTokensAddToMap}>
<TilesAddDroppable containerSize={modalSize} />
<TilesOverlay>
<TokenTiles
tokens={tokens}
2021-06-05 13:04:12 +10:00
onTokenEdit={setEditingTokenId}
subgroup
/>
</TilesOverlay>
</TileDragProvider>
2021-06-05 13:04:12 +10:00
<TokenEditBar
onLoad={setIsLoading}
disabled={isLoading || !isOpen}
/>
</Box>
<Button
variant="primary"
disabled={isLoading}
onClick={onRequestClose}
mt={2}
>
Done
</Button>
</Flex>
</GroupProvider>
</ReactResizeDetector>
</ImageDrop>
{(isLoading || tokensLoading) && <LoadingOverlay bg="overlay" />}
<EditTokenModal
2021-06-05 13:04:12 +10:00
isOpen={!!editingTokenId}
onDone={() => setEditingTokenId()}
2021-05-20 12:22:07 +10:00
token={
2021-06-05 13:04:12 +10:00
editingTokenId && tokens.find((token) => token.id === editingTokenId)
2021-05-20 12:22:07 +10:00
}
2021-06-05 13:04:12 +10:00
onUpdateToken={updateToken}
/>
<ConfirmModal
isOpen={isLargeImageWarningModalOpen}
onRequestClose={handleLargeImageWarningCancel}
onConfirm={handleLargeImageWarningConfirm}
confirmText="Continue"
label="Warning"
description="An imported image is larger than 20MB, this may cause slowness. Continue?"
/>
</Modal>
);
}
export default SelectTokensModal;