Add sort to token and map tiles and refactor
This commit is contained in:
@@ -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)}
|
||||
|
||||
Reference in New Issue
Block a user