Merge branch 'master' into typescript

This commit is contained in:
Mitchell McCaffrey
2021-07-02 15:54:54 +10:00
157 changed files with 8114 additions and 4055 deletions

View File

@@ -17,12 +17,16 @@ import {
/**
* Returns a function that when called will snap a node to the current grid
* @param {number=} snappingSensitivity 1 = Always snap, 0 = never snap if undefined the default user setting will be used
* @param {boolean=} useCorners Snap to grid cell corners
*/
function useGridSnapping(snappingSensitivity) {
function useGridSnapping(snappingSensitivity, useCorners = true) {
const [defaultSnappingSensitivity] = useSetting(
"map.gridSnappingSensitivity"
);
snappingSensitivity = snappingSensitivity || defaultSnappingSensitivity;
snappingSensitivity =
snappingSensitivity === undefined
? defaultSnappingSensitivity
: snappingSensitivity;
const grid = useGrid();
const gridOffset = useGridOffset();
@@ -57,7 +61,10 @@ function useGridSnapping(snappingSensitivity) {
gridCellPixelSize
);
const snapPoints = [cellPosition, ...cellCorners];
const snapPoints = [cellPosition];
if (useCorners) {
snapPoints.push(...cellCorners);
}
for (let snapPoint of snapPoints) {
const distanceToSnapPoint = Vector2.distance(offsetPosition, snapPoint);

85
src/hooks/useImageDrop.js Normal file
View File

@@ -0,0 +1,85 @@
import { useState } from "react";
import { useToasts } from "react-toast-notifications";
import Vector2 from "../helpers/Vector2";
function useImageDrop(
onImageDrop,
supportFileTypes = ["image/jpeg", "image/gif", "image/png", "image/webp"]
) {
const { addToast } = useToasts();
const [dragging, setDragging] = useState(false);
function onDragEnter(event) {
event.preventDefault();
event.stopPropagation();
setDragging(true);
}
function onDragLeave(event) {
event.preventDefault();
event.stopPropagation();
setDragging(false);
}
function onDragOver(event) {
event.preventDefault();
event.stopPropagation();
event.dataTransfer.dropEffect = "copy";
}
async function onDrop(event) {
event.preventDefault();
event.stopPropagation();
let imageFiles = [];
// Check if the dropped image is from a URL
const html = event.dataTransfer.getData("text/html");
if (html) {
try {
const urlMatch = html.match(/src="?([^"\s]+)"?\s*/);
const url = urlMatch[1].replace("&", "&"); // Reverse html encoding of url parameters
let name = "";
const altMatch = html.match(/alt="?([^"]+)"?\s*/);
if (altMatch && altMatch.length > 1) {
name = altMatch[1];
}
const response = await fetch(url);
if (response.ok) {
const file = await response.blob();
file.name = name;
if (supportFileTypes.includes(file.type)) {
imageFiles.push(file);
} else {
addToast(`Unsupported file type for ${file.name}`);
}
}
} catch (e) {
if (e.message === "Failed to fetch") {
addToast("Unable to import image: failed to fetch");
} else {
addToast("Unable to import image");
}
}
}
const files = event.dataTransfer.files;
for (let file of files) {
if (supportFileTypes.includes(file.type)) {
imageFiles.push(file);
} else {
addToast(`Unsupported file type for ${file.name}`);
}
}
const dropPosition = new Vector2(event.clientX, event.clientY);
onImageDrop(imageFiles, dropPosition);
setDragging(false);
}
const containerListeners = { onDragEnter };
const overlayListeners = { onDragLeave, onDragOver, onDrop };
return { dragging, containerListeners, overlayListeners };
}
export default useImageDrop;

View File

@@ -1,23 +1,23 @@
import { useEffect, useState } from "react";
import useImage from "use-image";
import { useImageSource } from "../contexts/ImageSourceContext";
import { useDataURL } from "../contexts/AssetsContext";
import { mapSources as defaultMapSources } from "../maps";
function useMapImage(map) {
const mapSource = useImageSource(map, defaultMapSources);
const [mapSourceImage, mapSourceImageStatus] = useImage(mapSource);
const mapURL = useDataURL(map, defaultMapSources);
const [mapImage, mapImageStatus] = useImage(mapURL);
// Create a map source that only updates when the image is fully loaded
const [loadedMapSourceImage, setLoadedMapSourceImage] = useState();
const [loadedMapImage, setLoadedMapImage] = useState();
useEffect(() => {
if (mapSourceImageStatus === "loaded") {
setLoadedMapSourceImage(mapSourceImage);
if (mapImageStatus === "loaded") {
setLoadedMapImage(mapImage);
}
}, [mapSourceImage, mapSourceImageStatus]);
}, [mapImage, mapImageStatus]);
return [loadedMapSourceImage, mapSourceImageStatus];
return [loadedMapImage, mapImageStatus];
}
export default useMapImage;

View File

@@ -1,4 +1,5 @@
import { useEffect, useState, useRef, useCallback } from "react";
import cloneDeep from "lodash.clonedeep";
import useDebounce from "./useDebounce";
import { diff, applyChanges } from "../helpers/diff";
@@ -76,7 +77,7 @@ function useNetworkedState(
}
dirtyRef.current = false;
forceUpdateRef.current = false;
lastSyncedStateRef.current = debouncedState;
lastSyncedStateRef.current = cloneDeep(debouncedState);
}
}, [
session.socket,

View File

@@ -0,0 +1,22 @@
function usePreventSelect() {
function clearSelection() {
if (window.getSelection) {
window.getSelection().removeAllRanges();
}
if (document.selection) {
document.selection.empty();
}
}
function preventSelect() {
clearSelection();
document.body.classList.add("no-select");
}
function resumeSelect() {
document.body.classList.remove("no-select");
}
return [preventSelect, resumeSelect];
}
export default usePreventSelect;

View File

@@ -21,7 +21,20 @@ function useResponsiveLayout() {
? "medium"
: "large";
return { screenSize, modalSize, tileSize };
const tileGridColumns = isLargeScreen ? 4 : isMediumScreen ? 3 : 2;
const groupGridColumns = isLargeScreen ? 3 : 2;
const tileContainerHeight = isLargeScreen ? "600px" : "400px";
return {
screenSize,
modalSize,
tileSize,
tileGridColumns,
tileContainerHeight,
groupGridColumns,
};
}
export default useResponsiveLayout;