Add sort to token and map tiles and refactor

This commit is contained in:
Mitchell McCaffrey
2021-05-09 12:04:31 +10:00
parent e1f5cb3014
commit 140ac7d61c
24 changed files with 771 additions and 801 deletions

View File

@@ -1,11 +1,8 @@
import React, { useRef, useState } from "react";
import { Button, Flex, Label } from "theme-ui";
import { v4 as uuid } from "uuid";
import Case from "case";
import { useToasts } from "react-toast-notifications";
import EditMapModal from "./EditMapModal";
import EditGroupModal from "./EditGroupModal";
import ConfirmModal from "./ConfirmModal";
import Modal from "../components/Modal";
@@ -13,15 +10,8 @@ import MapTiles from "../components/map/MapTiles";
import ImageDrop from "../components/ImageDrop";
import LoadingOverlay from "../components/LoadingOverlay";
import blobToBuffer from "../helpers/blobToBuffer";
import { resizeImage, createThumbnail } from "../helpers/image";
import { useSearch, useGroup, handleItemSelect } from "../helpers/select";
import {
getGridDefaultInset,
getGridSizeFromImage,
gridSizeVaild,
} from "../helpers/grid";
import Vector2 from "../helpers/Vector2";
import { handleItemSelect } from "../helpers/select";
import { createMapFromFile } from "../helpers/map";
import useResponsiveLayout from "../hooks/useResponsiveLayout";
@@ -32,24 +22,6 @@ import { useAssets } from "../contexts/AssetsContext";
import shortcuts from "../shortcuts";
const defaultMapProps = {
showGrid: false,
snapToGrid: true,
quality: "original",
group: "",
};
const mapResolutions = [
{
size: 30, // Pixels per grid
quality: 0.5, // JPEG compression quality
id: "low",
},
{ size: 70, quality: 0.6, id: "medium" },
{ size: 140, quality: 0.7, id: "high" },
{ size: 300, quality: 0.8, id: "ultra" },
];
function SelectMapModal({
isOpen,
onDone,
@@ -62,14 +34,15 @@ function SelectMapModal({
const { userId } = useAuth();
const {
ownedMaps,
maps,
mapStates,
mapGroups,
addMap,
removeMaps,
resetMap,
updateMaps,
mapsLoading,
getMapState,
updateMapGroups,
} = useMapData();
const { addAssets } = useAssets();
@@ -77,31 +50,13 @@ function SelectMapModal({
* Search
*/
const [search, setSearch] = useState("");
const [filteredMaps, filteredMapScores] = useSearch(ownedMaps, search);
// TODO: Add back with new group support
// const [filteredMaps, filteredMapScores] = useSearch(ownedMaps, search);
function handleSearchChange(event) {
setSearch(event.target.value);
}
/**
* Group
*/
const [isGroupModalOpen, setIsGroupModalOpen] = useState(false);
async function handleMapsGroup(group) {
setIsLoading(true);
setIsGroupModalOpen(false);
await updateMaps(selectedMapIds, { group });
setIsLoading(false);
}
const [mapsByGroup, mapGroups] = useGroup(
ownedMaps,
filteredMaps,
!!search,
filteredMapScores
);
/**
* Image Upload
*/
@@ -167,150 +122,12 @@ function SelectMapModal({
}
async function handleImageUpload(file) {
if (!file) {
return Promise.reject();
}
let image = new Image();
setIsLoading(true);
const buffer = await blobToBuffer(file);
// Copy file to avoid permissions issues
const blob = new Blob([buffer]);
// Create and load the image temporarily to get its dimensions
const url = URL.createObjectURL(blob);
return new Promise((resolve, reject) => {
image.onload = async function () {
// Find name and grid size
let gridSize;
let name = "Unknown Map";
if (file.name) {
if (file.name.matchAll) {
// Match against a regex to find the grid size in the file name
// e.g. Cave 22x23 will return [["22x22", "22", "x", "23"]]
const gridMatches = [...file.name.matchAll(/(\d+) ?(x|X) ?(\d+)/g)];
for (let match of gridMatches) {
const matchX = parseInt(match[1]);
const matchY = parseInt(match[3]);
if (
!isNaN(matchX) &&
!isNaN(matchY) &&
gridSizeVaild(matchX, matchY)
) {
gridSize = { x: matchX, y: matchY };
}
}
}
if (!gridSize) {
gridSize = await getGridSizeFromImage(image);
}
// Remove file extension
name = file.name.replace(/\.[^/.]+$/, "");
// Removed grid size expression
name = name.replace(/(\[ ?|\( ?)?\d+ ?(x|X) ?\d+( ?\]| ?\))?/, "");
// Clean string
name = name.replace(/ +/g, " ");
name = name.trim();
// Capitalize and remove underscores
name = Case.capital(name);
}
if (!gridSize) {
gridSize = { x: 22, y: 22 };
}
let assets = [];
// Create resolutions
const resolutions = {};
for (let resolution of mapResolutions) {
const resolutionPixelSize = Vector2.multiply(
gridSize,
resolution.size
);
if (
image.width >= resolutionPixelSize.x &&
image.height >= resolutionPixelSize.y
) {
const resized = await resizeImage(
image,
Vector2.max(resolutionPixelSize),
file.type,
resolution.quality
);
if (resized.blob) {
const assetId = uuid();
resolutions[resolution.id] = assetId;
const resizedBuffer = await blobToBuffer(resized.blob);
const asset = {
file: resizedBuffer,
width: resized.width,
height: resized.height,
id: assetId,
mime: file.type,
owner: userId,
};
assets.push(asset);
}
}
}
// Create thumbnail
const thumbnailImage = await createThumbnail(image, file.type);
const thumbnail = {
...thumbnailImage,
id: uuid(),
owner: userId,
};
assets.push(thumbnail);
const fileAsset = {
id: uuid(),
file: buffer,
width: image.width,
height: image.height,
mime: file.type,
owner: userId,
};
assets.push(fileAsset);
const map = {
name,
resolutions,
file: fileAsset.id,
thumbnail: thumbnail.id,
type: "file",
grid: {
size: gridSize,
inset: getGridDefaultInset(
{ size: gridSize, type: "square" },
image.width,
image.height
),
type: "square",
measurement: {
type: "chebyshev",
scale: "5ft",
},
},
width: image.width,
height: image.height,
id: uuid(),
created: Date.now(),
lastModified: Date.now(),
owner: userId,
...defaultMapProps,
};
handleMapAdd(map, assets);
setIsLoading(false);
URL.revokeObjectURL(url);
resolve();
};
image.onerror = reject;
image.src = url;
});
const { map, assets } = await createMapFromFile(file, userId);
await addMap(map);
await addAssets(assets);
setSelectedMapIds([map.id]);
setIsLoading(false);
}
function openImageDialog() {
@@ -326,19 +143,11 @@ function SelectMapModal({
// The map selected in the modal
const [selectedMapIds, setSelectedMapIds] = useState([]);
const selectedMaps = ownedMaps.filter((map) =>
selectedMapIds.includes(map.id)
);
const selectedMaps = maps.filter((map) => selectedMapIds.includes(map.id));
const selectedMapStates = mapStates.filter((state) =>
selectedMapIds.includes(state.mapId)
);
async function handleMapAdd(map, assets) {
await addMap(map);
await addAssets(assets);
setSelectedMapIds([map.id]);
}
const [isMapsRemoveModalOpen, setIsMapsRemoveModalOpen] = useState(false);
async function handleMapsRemove() {
setIsLoading(true);
@@ -374,9 +183,8 @@ function SelectMapModal({
map,
selectMode,
selectedMapIds,
setSelectedMapIds,
mapsByGroup,
mapGroups
setSelectedMapIds
// TODO: Add new group support
);
}
@@ -424,7 +232,6 @@ function SelectMapModal({
!selectedMaps.some((map) => map.type === "default")
) {
// Ensure all other modals are closed
setIsGroupModalOpen(false);
setIsEditModalOpen(false);
setIsMapsResetModalOpen(false);
setIsMapsRemoveModalOpen(true);
@@ -479,7 +286,7 @@ function SelectMapModal({
Select or import a map
</Label>
<MapTiles
maps={mapsByGroup}
maps={maps}
groups={mapGroups}
onMapAdd={openImageDialog}
onMapEdit={() => setIsEditModalOpen(true)}
@@ -493,7 +300,7 @@ function SelectMapModal({
onSelectModeChange={setSelectMode}
search={search}
onSearchChange={handleSearchChange}
onMapsGroup={() => setIsGroupModalOpen(true)}
onMapsGroup={updateMapGroups}
/>
<Button
variant="primary"
@@ -512,21 +319,6 @@ function SelectMapModal({
map={selectedMaps.length === 1 && selectedMaps[0]}
mapState={selectedMapStates.length === 1 && selectedMapStates[0]}
/>
<EditGroupModal
isOpen={isGroupModalOpen}
onChange={handleMapsGroup}
groups={mapGroups.filter(
(group) => group !== "" && group !== "default"
)}
onRequestClose={() => setIsGroupModalOpen(false)}
// Select the default group by testing whether all selected maps are the same
defaultGroup={
selectedMaps.length > 0 &&
selectedMaps
.map((map) => map.group)
.reduce((prev, curr) => (prev === curr ? curr : undefined))
}
/>
<ConfirmModal
isOpen={isMapsResetModalOpen}
onRequestClose={() => setIsMapsResetModalOpen(false)}