commit
b6b6a86454
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "owlbear-rodeo",
|
"name": "owlbear-rodeo",
|
||||||
"version": "1.6.0",
|
"version": "1.6.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babylonjs/core": "^4.1.0",
|
"@babylonjs/core": "^4.1.0",
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useRef, useEffect, useContext } from "react";
|
import React, { useState, useRef, useContext } from "react";
|
||||||
import { Box, IconButton } from "theme-ui";
|
import { Box, IconButton } from "theme-ui";
|
||||||
import { Stage, Layer, Image } from "react-konva";
|
import { Stage, Layer, Image } from "react-konva";
|
||||||
import ReactResizeDetector from "react-resize-detector";
|
import ReactResizeDetector from "react-resize-detector";
|
||||||
@ -6,7 +6,8 @@ import ReactResizeDetector from "react-resize-detector";
|
|||||||
import useMapImage from "../../helpers/useMapImage";
|
import useMapImage from "../../helpers/useMapImage";
|
||||||
import usePreventOverscroll from "../../helpers/usePreventOverscroll";
|
import usePreventOverscroll from "../../helpers/usePreventOverscroll";
|
||||||
import useStageInteraction from "../../helpers/useStageInteraction";
|
import useStageInteraction from "../../helpers/useStageInteraction";
|
||||||
import { getMapDefaultInset } from "../../helpers/map";
|
import useImageCenter from "../../helpers/useImageCenter";
|
||||||
|
import { getMapDefaultInset, getMapMaxZoom } from "../../helpers/map";
|
||||||
|
|
||||||
import { MapInteractionProvider } from "../../contexts/MapInteractionContext";
|
import { MapInteractionProvider } from "../../contexts/MapInteractionContext";
|
||||||
import KeyboardContext from "../../contexts/KeyboardContext";
|
import KeyboardContext from "../../contexts/KeyboardContext";
|
||||||
@ -25,19 +26,6 @@ function MapEditor({ map, onSettingsChange }) {
|
|||||||
const [stageHeight, setStageHeight] = useState(1);
|
const [stageHeight, setStageHeight] = useState(1);
|
||||||
const [stageScale, setStageScale] = useState(1);
|
const [stageScale, setStageScale] = useState(1);
|
||||||
|
|
||||||
const stageRatio = stageWidth / stageHeight;
|
|
||||||
const mapRatio = map ? map.width / map.height : 1;
|
|
||||||
|
|
||||||
let mapWidth;
|
|
||||||
let mapHeight;
|
|
||||||
if (stageRatio > mapRatio) {
|
|
||||||
mapWidth = map ? stageHeight / (map.height / map.width) : stageWidth;
|
|
||||||
mapHeight = stageHeight;
|
|
||||||
} else {
|
|
||||||
mapWidth = stageWidth;
|
|
||||||
mapHeight = map ? stageWidth * (map.height / map.width) : stageHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultInset = getMapDefaultInset(
|
const defaultInset = getMapDefaultInset(
|
||||||
map.width,
|
map.width,
|
||||||
map.height,
|
map.height,
|
||||||
@ -46,6 +34,7 @@ function MapEditor({ map, onSettingsChange }) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const stageTranslateRef = useRef({ x: 0, y: 0 });
|
const stageTranslateRef = useRef({ x: 0, y: 0 });
|
||||||
|
const mapStageRef = useRef();
|
||||||
const mapLayerRef = useRef();
|
const mapLayerRef = useRef();
|
||||||
const [preventMapInteraction, setPreventMapInteraction] = useState(false);
|
const [preventMapInteraction, setPreventMapInteraction] = useState(false);
|
||||||
|
|
||||||
@ -54,45 +43,32 @@ function MapEditor({ map, onSettingsChange }) {
|
|||||||
setStageHeight(height);
|
setStageHeight(height);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset map translate and scale
|
const containerRef = useRef();
|
||||||
useEffect(() => {
|
usePreventOverscroll(containerRef);
|
||||||
const layer = mapLayerRef.current;
|
|
||||||
const containerRect = containerRef.current.getBoundingClientRect();
|
|
||||||
if (layer) {
|
|
||||||
let newTranslate;
|
|
||||||
if (stageRatio > mapRatio) {
|
|
||||||
newTranslate = {
|
|
||||||
x: -(mapWidth - containerRect.width) / 2,
|
|
||||||
y: 0,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
newTranslate = {
|
|
||||||
x: 0,
|
|
||||||
y: -(mapHeight - containerRect.height) / 2,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
layer.x(newTranslate.x);
|
const [mapWidth, mapHeight] = useImageCenter(
|
||||||
layer.y(newTranslate.y);
|
map,
|
||||||
layer.draw();
|
mapStageRef,
|
||||||
stageTranslateRef.current = newTranslate;
|
stageWidth,
|
||||||
|
stageHeight,
|
||||||
setStageScale(1);
|
stageTranslateRef,
|
||||||
}
|
setStageScale,
|
||||||
}, [map.id, mapWidth, mapHeight, stageRatio, mapRatio]);
|
mapLayerRef,
|
||||||
|
containerRef,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
const bind = useStageInteraction(
|
const bind = useStageInteraction(
|
||||||
mapLayerRef.current,
|
mapStageRef.current,
|
||||||
stageScale,
|
stageScale,
|
||||||
setStageScale,
|
setStageScale,
|
||||||
stageTranslateRef,
|
stageTranslateRef,
|
||||||
|
mapLayerRef.current,
|
||||||
|
getMapMaxZoom(map),
|
||||||
"pan",
|
"pan",
|
||||||
preventMapInteraction
|
preventMapInteraction
|
||||||
);
|
);
|
||||||
|
|
||||||
const containerRef = useRef();
|
|
||||||
usePreventOverscroll(containerRef);
|
|
||||||
|
|
||||||
function handleGridChange(inset) {
|
function handleGridChange(inset) {
|
||||||
onSettingsChange("grid", {
|
onSettingsChange("grid", {
|
||||||
...map.grid,
|
...map.grid,
|
||||||
@ -128,7 +104,6 @@ function MapEditor({ map, onSettingsChange }) {
|
|||||||
map.grid.inset.topLeft.y !== defaultInset.topLeft.y ||
|
map.grid.inset.topLeft.y !== defaultInset.topLeft.y ||
|
||||||
map.grid.inset.bottomRight.x !== defaultInset.bottomRight.x ||
|
map.grid.inset.bottomRight.x !== defaultInset.bottomRight.x ||
|
||||||
map.grid.inset.bottomRight.y !== defaultInset.bottomRight.y;
|
map.grid.inset.bottomRight.y !== defaultInset.bottomRight.y;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -148,19 +123,17 @@ function MapEditor({ map, onSettingsChange }) {
|
|||||||
width={stageWidth}
|
width={stageWidth}
|
||||||
height={stageHeight}
|
height={stageHeight}
|
||||||
scale={{ x: stageScale, y: stageScale }}
|
scale={{ x: stageScale, y: stageScale }}
|
||||||
x={stageWidth / 2}
|
ref={mapStageRef}
|
||||||
y={stageHeight / 2}
|
|
||||||
offset={{ x: stageWidth / 2, y: stageHeight / 2 }}
|
|
||||||
>
|
>
|
||||||
<Layer ref={mapLayerRef}>
|
<Layer ref={mapLayerRef}>
|
||||||
<Image image={mapImageSource} width={mapWidth} height={mapHeight} />
|
<Image image={mapImageSource} width={mapWidth} height={mapHeight} />
|
||||||
<KeyboardContext.Provider value={keyboardValue}>
|
<KeyboardContext.Provider value={keyboardValue}>
|
||||||
<MapInteractionProvider value={mapInteraction}>
|
<MapInteractionProvider value={mapInteraction}>
|
||||||
{showGridControls && canEditGrid && (
|
{showGridControls && canEditGrid && (
|
||||||
|
<>
|
||||||
<MapGrid map={map} strokeWidth={0.5} />
|
<MapGrid map={map} strokeWidth={0.5} />
|
||||||
)}
|
|
||||||
{showGridControls && canEditGrid && (
|
|
||||||
<MapGridEditor map={map} onGridChange={handleGridChange} />
|
<MapGridEditor map={map} onGridChange={handleGridChange} />
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</MapInteractionProvider>
|
</MapInteractionProvider>
|
||||||
</KeyboardContext.Provider>
|
</KeyboardContext.Provider>
|
||||||
|
@ -8,6 +8,8 @@ import useMapImage from "../../helpers/useMapImage";
|
|||||||
import usePreventOverscroll from "../../helpers/usePreventOverscroll";
|
import usePreventOverscroll from "../../helpers/usePreventOverscroll";
|
||||||
import useKeyboard from "../../helpers/useKeyboard";
|
import useKeyboard from "../../helpers/useKeyboard";
|
||||||
import useStageInteraction from "../../helpers/useStageInteraction";
|
import useStageInteraction from "../../helpers/useStageInteraction";
|
||||||
|
import useImageCenter from "../../helpers/useImageCenter";
|
||||||
|
import { getMapMaxZoom } from "../../helpers/map";
|
||||||
|
|
||||||
import { MapInteractionProvider } from "../../contexts/MapInteractionContext";
|
import { MapInteractionProvider } from "../../contexts/MapInteractionContext";
|
||||||
import MapStageContext, {
|
import MapStageContext, {
|
||||||
@ -42,52 +44,42 @@ function MapInteraction({
|
|||||||
const [stageScale, setStageScale] = useState(1);
|
const [stageScale, setStageScale] = useState(1);
|
||||||
const [preventMapInteraction, setPreventMapInteraction] = useState(false);
|
const [preventMapInteraction, setPreventMapInteraction] = useState(false);
|
||||||
|
|
||||||
const stageWidthRef = useRef(stageWidth);
|
|
||||||
const stageHeightRef = useRef(stageHeight);
|
|
||||||
// Avoid state udpates when panning the map by using a ref and updating the konva element directly
|
// Avoid state udpates when panning the map by using a ref and updating the konva element directly
|
||||||
const stageTranslateRef = useRef({ x: 0, y: 0 });
|
const stageTranslateRef = useRef({ x: 0, y: 0 });
|
||||||
|
const mapStageRef = useContext(MapStageContext);
|
||||||
// Reset transform when map changes
|
const mapLayerRef = useRef();
|
||||||
const previousMapIdRef = useRef();
|
const mapImageRef = useRef();
|
||||||
useEffect(() => {
|
|
||||||
const layer = mapLayerRef.current;
|
|
||||||
const previousMapId = previousMapIdRef.current;
|
|
||||||
if (map && layer && previousMapId !== map.id) {
|
|
||||||
const mapHeight = stageWidthRef.current * (map.height / map.width);
|
|
||||||
const newTranslate = {
|
|
||||||
x: 0,
|
|
||||||
y: -(mapHeight - stageHeightRef.current) / 2,
|
|
||||||
};
|
|
||||||
layer.x(newTranslate.x);
|
|
||||||
layer.y(newTranslate.y);
|
|
||||||
layer.draw();
|
|
||||||
stageTranslateRef.current = newTranslate;
|
|
||||||
|
|
||||||
setStageScale(1);
|
|
||||||
}
|
|
||||||
previousMapIdRef.current = map && map.id;
|
|
||||||
}, [map]);
|
|
||||||
|
|
||||||
function handleResize(width, height) {
|
function handleResize(width, height) {
|
||||||
setStageWidth(width);
|
setStageWidth(width);
|
||||||
setStageHeight(height);
|
setStageHeight(height);
|
||||||
stageWidthRef.current = width;
|
|
||||||
stageHeightRef.current = height;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStageRef = useContext(MapStageContext);
|
const containerRef = useRef();
|
||||||
const mapLayerRef = useRef();
|
usePreventOverscroll(containerRef);
|
||||||
const mapImageRef = useRef();
|
|
||||||
|
const [mapWidth, mapHeight] = useImageCenter(
|
||||||
|
map,
|
||||||
|
mapStageRef,
|
||||||
|
stageWidth,
|
||||||
|
stageHeight,
|
||||||
|
stageTranslateRef,
|
||||||
|
setStageScale,
|
||||||
|
mapLayerRef,
|
||||||
|
containerRef
|
||||||
|
);
|
||||||
|
|
||||||
const previousSelectedToolRef = useRef(selectedToolId);
|
const previousSelectedToolRef = useRef(selectedToolId);
|
||||||
|
|
||||||
const [interactionEmitter] = useState(new EventEmitter());
|
const [interactionEmitter] = useState(new EventEmitter());
|
||||||
|
|
||||||
const bind = useStageInteraction(
|
const bind = useStageInteraction(
|
||||||
mapLayerRef.current,
|
mapStageRef.current,
|
||||||
stageScale,
|
stageScale,
|
||||||
setStageScale,
|
setStageScale,
|
||||||
stageTranslateRef,
|
stageTranslateRef,
|
||||||
|
mapLayerRef.current,
|
||||||
|
getMapMaxZoom(map),
|
||||||
selectedToolId,
|
selectedToolId,
|
||||||
preventMapInteraction,
|
preventMapInteraction,
|
||||||
{
|
{
|
||||||
@ -169,12 +161,6 @@ function MapInteraction({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const containerRef = useRef();
|
|
||||||
usePreventOverscroll(containerRef);
|
|
||||||
|
|
||||||
const mapWidth = stageWidth;
|
|
||||||
const mapHeight = map ? stageWidth * (map.height / map.width) : stageHeight;
|
|
||||||
|
|
||||||
const auth = useContext(AuthContext);
|
const auth = useContext(AuthContext);
|
||||||
const settings = useContext(SettingsContext);
|
const settings = useContext(SettingsContext);
|
||||||
|
|
||||||
@ -206,9 +192,6 @@ function MapInteraction({
|
|||||||
width={stageWidth}
|
width={stageWidth}
|
||||||
height={stageHeight}
|
height={stageHeight}
|
||||||
scale={{ x: stageScale, y: stageScale }}
|
scale={{ x: stageScale, y: stageScale }}
|
||||||
x={stageWidth / 2}
|
|
||||||
y={stageHeight / 2}
|
|
||||||
offset={{ x: stageWidth / 2, y: stageHeight / 2 }}
|
|
||||||
ref={mapStageRef}
|
ref={mapStageRef}
|
||||||
>
|
>
|
||||||
<Layer ref={mapLayerRef}>
|
<Layer ref={mapLayerRef}>
|
||||||
|
@ -160,7 +160,7 @@ function MapSettings({
|
|||||||
onSettingsChange("showGrid", e.target.checked)
|
onSettingsChange("showGrid", e.target.checked)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
Show Grid
|
Draw Grid
|
||||||
</Label>
|
</Label>
|
||||||
<Label>
|
<Label>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
@ -109,7 +109,7 @@ function MapTiles({
|
|||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: 0,
|
top: "39px",
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
|
@ -7,6 +7,7 @@ import useImage from "use-image";
|
|||||||
import usePreventOverscroll from "../../helpers/usePreventOverscroll";
|
import usePreventOverscroll from "../../helpers/usePreventOverscroll";
|
||||||
import useStageInteraction from "../../helpers/useStageInteraction";
|
import useStageInteraction from "../../helpers/useStageInteraction";
|
||||||
import useDataSource from "../../helpers/useDataSource";
|
import useDataSource from "../../helpers/useDataSource";
|
||||||
|
import useImageCenter from "../../helpers/useImageCenter";
|
||||||
|
|
||||||
import GridOnIcon from "../../icons/GridOnIcon";
|
import GridOnIcon from "../../icons/GridOnIcon";
|
||||||
import GridOffIcon from "../../icons/GridOffIcon";
|
import GridOffIcon from "../../icons/GridOffIcon";
|
||||||
@ -29,80 +30,43 @@ function TokenPreview({ token }) {
|
|||||||
unknownSource
|
unknownSource
|
||||||
);
|
);
|
||||||
const [tokenSourceImage] = useImage(tokenSource);
|
const [tokenSourceImage] = useImage(tokenSource);
|
||||||
const [tokenRatio, setTokenRatio] = useState(1);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (tokenSourceImage) {
|
|
||||||
setTokenRatio(tokenSourceImage.width / tokenSourceImage.height);
|
|
||||||
}
|
|
||||||
}, [tokenSourceImage]);
|
|
||||||
|
|
||||||
const [stageWidth, setStageWidth] = useState(1);
|
const [stageWidth, setStageWidth] = useState(1);
|
||||||
const [stageHeight, setStageHeight] = useState(1);
|
const [stageHeight, setStageHeight] = useState(1);
|
||||||
const [stageScale, setStageScale] = useState(1);
|
const [stageScale, setStageScale] = useState(1);
|
||||||
|
|
||||||
const stageRatio = stageWidth / stageHeight;
|
|
||||||
|
|
||||||
let tokenWidth;
|
|
||||||
let tokenHeight;
|
|
||||||
if (stageRatio > tokenRatio) {
|
|
||||||
tokenWidth = tokenSourceImage
|
|
||||||
? stageHeight / (tokenSourceImage.height / tokenSourceImage.width)
|
|
||||||
: stageWidth;
|
|
||||||
tokenHeight = stageHeight;
|
|
||||||
} else {
|
|
||||||
tokenWidth = stageWidth;
|
|
||||||
tokenHeight = tokenSourceImage
|
|
||||||
? stageWidth * (tokenSourceImage.height / tokenSourceImage.width)
|
|
||||||
: stageHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
const stageTranslateRef = useRef({ x: 0, y: 0 });
|
const stageTranslateRef = useRef({ x: 0, y: 0 });
|
||||||
const mapLayerRef = useRef();
|
const tokenStageRef = useRef();
|
||||||
|
const tokenLayerRef = useRef();
|
||||||
|
|
||||||
function handleResize(width, height) {
|
function handleResize(width, height) {
|
||||||
setStageWidth(width);
|
setStageWidth(width);
|
||||||
setStageHeight(height);
|
setStageHeight(height);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset map translate and scale
|
const containerRef = useRef();
|
||||||
useEffect(() => {
|
usePreventOverscroll(containerRef);
|
||||||
const layer = mapLayerRef.current;
|
|
||||||
const containerRect = containerRef.current.getBoundingClientRect();
|
|
||||||
if (layer) {
|
|
||||||
let newTranslate;
|
|
||||||
if (stageRatio > tokenRatio) {
|
|
||||||
newTranslate = {
|
|
||||||
x: -(tokenWidth - containerRect.width) / 2,
|
|
||||||
y: 0,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
newTranslate = {
|
|
||||||
x: 0,
|
|
||||||
y: -(tokenHeight - containerRect.height) / 2,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
layer.x(newTranslate.x);
|
const [tokenWidth, tokenHeight] = useImageCenter(
|
||||||
layer.y(newTranslate.y);
|
token,
|
||||||
layer.draw();
|
tokenStageRef,
|
||||||
stageTranslateRef.current = newTranslate;
|
stageWidth,
|
||||||
|
stageHeight,
|
||||||
setStageScale(1);
|
stageTranslateRef,
|
||||||
}
|
setStageScale,
|
||||||
}, [token.id, tokenWidth, tokenHeight, stageRatio, tokenRatio]);
|
tokenLayerRef,
|
||||||
|
containerRef,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
const bind = useStageInteraction(
|
const bind = useStageInteraction(
|
||||||
mapLayerRef.current,
|
tokenStageRef.current,
|
||||||
stageScale,
|
stageScale,
|
||||||
setStageScale,
|
setStageScale,
|
||||||
stageTranslateRef,
|
stageTranslateRef,
|
||||||
"pan"
|
tokenLayerRef.current
|
||||||
);
|
);
|
||||||
|
|
||||||
const containerRef = useRef();
|
|
||||||
usePreventOverscroll(containerRef);
|
|
||||||
|
|
||||||
const [showGridPreview, setShowGridPreview] = useState(true);
|
const [showGridPreview, setShowGridPreview] = useState(true);
|
||||||
const gridWidth = tokenWidth;
|
const gridWidth = tokenWidth;
|
||||||
const gridX = token.defaultSize;
|
const gridX = token.defaultSize;
|
||||||
@ -133,11 +97,9 @@ function TokenPreview({ token }) {
|
|||||||
width={stageWidth}
|
width={stageWidth}
|
||||||
height={stageHeight}
|
height={stageHeight}
|
||||||
scale={{ x: stageScale, y: stageScale }}
|
scale={{ x: stageScale, y: stageScale }}
|
||||||
x={stageWidth / 2}
|
ref={tokenStageRef}
|
||||||
y={stageHeight / 2}
|
|
||||||
offset={{ x: stageWidth / 2, y: stageHeight / 2 }}
|
|
||||||
>
|
>
|
||||||
<Layer ref={mapLayerRef}>
|
<Layer ref={tokenLayerRef}>
|
||||||
<Image
|
<Image
|
||||||
image={tokenSourceImage}
|
image={tokenSourceImage}
|
||||||
width={tokenWidth}
|
width={tokenWidth}
|
||||||
|
@ -114,7 +114,7 @@ function TokenTiles({
|
|||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: 0,
|
top: "39px",
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
|
@ -19,7 +19,7 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function AuthProvider({ children }) {
|
export function AuthProvider({ children }) {
|
||||||
const { database } = useContext(DatabaseContext);
|
const { database, databaseStatus } = useContext(DatabaseContext);
|
||||||
|
|
||||||
const [password, setPassword] = useState(storage.getItem("auth") || "");
|
const [password, setPassword] = useState(storage.getItem("auth") || "");
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ export function AuthProvider({ children }) {
|
|||||||
|
|
||||||
const [userId, setUserId] = useState();
|
const [userId, setUserId] = useState();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!database) {
|
if (!database || databaseStatus === "loading") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
async function loadUserId() {
|
async function loadUserId() {
|
||||||
@ -46,11 +46,11 @@ export function AuthProvider({ children }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadUserId();
|
loadUserId();
|
||||||
}, [database]);
|
}, [database, databaseStatus]);
|
||||||
|
|
||||||
const [nickname, setNickname] = useState("");
|
const [nickname, setNickname] = useState("");
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!database) {
|
if (!database || databaseStatus === "loading") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
async function loadNickname() {
|
async function loadNickname() {
|
||||||
@ -65,13 +65,17 @@ export function AuthProvider({ children }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadNickname();
|
loadNickname();
|
||||||
}, [database]);
|
}, [database, databaseStatus]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (nickname !== undefined && database !== undefined) {
|
if (
|
||||||
|
nickname !== undefined &&
|
||||||
|
database !== undefined &&
|
||||||
|
databaseStatus !== "loading"
|
||||||
|
) {
|
||||||
database.table("user").update("nickname", { value: nickname });
|
database.table("user").update("nickname", { value: nickname });
|
||||||
}
|
}
|
||||||
}, [nickname, database]);
|
}, [nickname, database, databaseStatus]);
|
||||||
|
|
||||||
const value = {
|
const value = {
|
||||||
userId,
|
userId,
|
||||||
|
@ -11,11 +11,14 @@ export function DatabaseProvider({ children }) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Create a test database and open it to see if indexedDB is enabled
|
// Create a test database and open it to see if indexedDB is enabled
|
||||||
let testDBRequest = window.indexedDB.open("__test");
|
let testDBRequest = window.indexedDB.open("__test");
|
||||||
testDBRequest.onsuccess = function () {
|
testDBRequest.onsuccess = async function () {
|
||||||
testDBRequest.result.close();
|
testDBRequest.result.close();
|
||||||
let db = getDatabase();
|
let db = getDatabase({ autoOpen: false });
|
||||||
setDatabase(db);
|
setDatabase(db);
|
||||||
|
db.on("ready", () => {
|
||||||
setDatabaseStatus("loaded");
|
setDatabaseStatus("loaded");
|
||||||
|
});
|
||||||
|
await db.open();
|
||||||
window.indexedDB.deleteDatabase("__test");
|
window.indexedDB.deleteDatabase("__test");
|
||||||
};
|
};
|
||||||
// If indexedb disabled create an in memory database
|
// If indexedb disabled create an in memory database
|
||||||
@ -23,9 +26,12 @@ export function DatabaseProvider({ children }) {
|
|||||||
console.warn("Database is disabled, no state will be saved");
|
console.warn("Database is disabled, no state will be saved");
|
||||||
const indexedDB = await import("fake-indexeddb");
|
const indexedDB = await import("fake-indexeddb");
|
||||||
const IDBKeyRange = await import("fake-indexeddb/lib/FDBKeyRange");
|
const IDBKeyRange = await import("fake-indexeddb/lib/FDBKeyRange");
|
||||||
let db = getDatabase({ indexedDB, IDBKeyRange });
|
let db = getDatabase({ indexedDB, IDBKeyRange, autoOpen: false });
|
||||||
setDatabase(db);
|
setDatabase(db);
|
||||||
|
db.on("ready", () => {
|
||||||
setDatabaseStatus("disabled");
|
setDatabaseStatus("disabled");
|
||||||
|
});
|
||||||
|
await db.open();
|
||||||
window.indexedDB.deleteDatabase("__test");
|
window.indexedDB.deleteDatabase("__test");
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -23,14 +23,14 @@ const defaultMapState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function MapDataProvider({ children }) {
|
export function MapDataProvider({ children }) {
|
||||||
const { database } = useContext(DatabaseContext);
|
const { database, databaseStatus } = useContext(DatabaseContext);
|
||||||
const { userId } = useContext(AuthContext);
|
const { userId } = useContext(AuthContext);
|
||||||
|
|
||||||
const [maps, setMaps] = useState([]);
|
const [maps, setMaps] = useState([]);
|
||||||
const [mapStates, setMapStates] = useState([]);
|
const [mapStates, setMapStates] = useState([]);
|
||||||
// Load maps from the database and ensure state is properly setup
|
// Load maps from the database and ensure state is properly setup
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!userId || !database) {
|
if (!userId || !database || databaseStatus === "loading") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
async function getDefaultMaps() {
|
async function getDefaultMaps() {
|
||||||
@ -71,7 +71,7 @@ export function MapDataProvider({ children }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadMaps();
|
loadMaps();
|
||||||
}, [userId, database]);
|
}, [userId, database, databaseStatus]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a map to the database, also adds an assosiated state for that map
|
* Adds a map to the database, also adds an assosiated state for that map
|
||||||
@ -131,7 +131,14 @@ export function MapDataProvider({ children }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function updateMap(id, update) {
|
async function updateMap(id, update) {
|
||||||
|
// fake-indexeddb throws an error when updating maps in production.
|
||||||
|
// Catch that error and use put when it fails
|
||||||
|
try {
|
||||||
await database.table("maps").update(id, update);
|
await database.table("maps").update(id, update);
|
||||||
|
} catch (error) {
|
||||||
|
const map = (await getMapFromDB(id)) || {};
|
||||||
|
await database.table("maps").put({ ...map, id, ...update });
|
||||||
|
}
|
||||||
setMaps((prevMaps) => {
|
setMaps((prevMaps) => {
|
||||||
const newMaps = [...prevMaps];
|
const newMaps = [...prevMaps];
|
||||||
const i = newMaps.findIndex((map) => map.id === id);
|
const i = newMaps.findIndex((map) => map.id === id);
|
||||||
@ -219,7 +226,8 @@ export function MapDataProvider({ children }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getMapFromDB(mapId) {
|
async function getMapFromDB(mapId) {
|
||||||
return await database.table("maps").get(mapId);
|
let map = await database.table("maps").get(mapId);
|
||||||
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ownedMaps = maps.filter((map) => map.owner === userId);
|
const ownedMaps = maps.filter((map) => map.owner === userId);
|
||||||
|
@ -10,13 +10,13 @@ const TokenDataContext = React.createContext();
|
|||||||
const cachedTokenMax = 100;
|
const cachedTokenMax = 100;
|
||||||
|
|
||||||
export function TokenDataProvider({ children }) {
|
export function TokenDataProvider({ children }) {
|
||||||
const { database } = useContext(DatabaseContext);
|
const { database, databaseStatus } = useContext(DatabaseContext);
|
||||||
const { userId } = useContext(AuthContext);
|
const { userId } = useContext(AuthContext);
|
||||||
|
|
||||||
const [tokens, setTokens] = useState([]);
|
const [tokens, setTokens] = useState([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!userId || !database) {
|
if (!userId || !database || databaseStatus === "loading") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
function getDefaultTokes() {
|
function getDefaultTokes() {
|
||||||
@ -43,7 +43,7 @@ export function TokenDataProvider({ children }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadTokens();
|
loadTokens();
|
||||||
}, [userId, database]);
|
}, [userId, database, databaseStatus]);
|
||||||
|
|
||||||
async function addToken(token) {
|
async function addToken(token) {
|
||||||
await database.table("tokens").add(token);
|
await database.table("tokens").add(token);
|
||||||
|
@ -242,6 +242,32 @@ function loadVersions(db) {
|
|||||||
token.group = "";
|
token.group = "";
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
// v1.6.1 - Added width and height to tokens
|
||||||
|
db.version(15)
|
||||||
|
.stores({})
|
||||||
|
.upgrade(async (tx) => {
|
||||||
|
const tokens = await Dexie.waitFor(tx.table("tokens").toArray());
|
||||||
|
let tokenSizes = {};
|
||||||
|
for (let token of tokens) {
|
||||||
|
const url = URL.createObjectURL(new Blob([token.file]));
|
||||||
|
let image = new Image();
|
||||||
|
tokenSizes[token.id] = await Dexie.waitFor(
|
||||||
|
new Promise((resolve) => {
|
||||||
|
image.onload = () => {
|
||||||
|
resolve({ width: image.width, height: image.height });
|
||||||
|
};
|
||||||
|
image.src = url;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return tx
|
||||||
|
.table("tokens")
|
||||||
|
.toCollection()
|
||||||
|
.modify((token) => {
|
||||||
|
token.width = tokenSizes[token.id].width;
|
||||||
|
token.height = tokenSizes[token.id].height;
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the dexie database used in DatabaseContext
|
// Get the dexie database used in DatabaseContext
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 152 KiB After Width: | Height: | Size: 159 KiB |
@ -50,7 +50,7 @@ To get access to these settings, click the Show More button under the Name input
|
|||||||
A brief summary of these settings is listed below.
|
A brief summary of these settings is listed below.
|
||||||
|
|
||||||
- Grid Type: Change the type of grid to use for the map. Currently only the Square type is supported however Hex will be added in a future release.
|
- Grid Type: Change the type of grid to use for the map. Currently only the Square type is supported however Hex will be added in a future release.
|
||||||
- Show Grid: When enabled Owlbear Rodeo will draw a grid on top of your map, this is useful if a custom map you have uploaded doesn't include a grid.
|
- Draw Grid: When enabled Owlbear Rodeo will draw a grid on top of your map, this is useful if a custom map you have uploaded doesn't include a grid.
|
||||||
- Snap to Grid: When enabled tokens, drawing, fog and measurements will attempt to snap to the grid.
|
- Snap to Grid: When enabled tokens, drawing, fog and measurements will attempt to snap to the grid.
|
||||||
- Quality: When uploading a map Owlbear Rodeo will automatically generate various quality options, selecting a lower quality may help speed up map sending in resource constrained environments.
|
- Quality: When uploading a map Owlbear Rodeo will automatically generate various quality options, selecting a lower quality may help speed up map sending in resource constrained environments.
|
||||||
- Allow others to edit: These properties control what other party members can edit when viewing your map.
|
- Allow others to edit: These properties control what other party members can edit when viewing your map.
|
||||||
|
20
src/docs/releaseNotes/v1.6.1.md
Normal file
20
src/docs/releaseNotes/v1.6.1.md
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
## Minor Changes
|
||||||
|
|
||||||
|
This release fixes a few map sending bugs and reworks map zooming.
|
||||||
|
|
||||||
|
- Change map max zoom to be based off of the maps grid size. This means you should always be able to zoom up to see a grid cell almost fullscreen.
|
||||||
|
- Updated map zoom speed to be linear throughout it's zoom range.
|
||||||
|
- Update map zoom to be centered on the mouse instead of screen.
|
||||||
|
- Updated map grid from file name functionality to better reject non-grid values.
|
||||||
|
- Fixed a bug that caused sending large maps to fail.
|
||||||
|
- Made advanced map options shown by default.
|
||||||
|
- Renamed Show Grid map option to Draw Grid to help with the similarity with the Show Grid Controls option.
|
||||||
|
- Fixed a bug where map states sometimes wouldn't be saved if another player changed the map.
|
||||||
|
- Fixed a bug that would cause custom maps to not send to Firefox private browsing sessions.
|
||||||
|
|
||||||
|
[Reddit]()
|
||||||
|
[Twitter]()
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
October 24 2020
|
@ -39,7 +39,7 @@ const gridSizeStd = { x: 14.438842, y: 15.582376 };
|
|||||||
const minGridSize = 10;
|
const minGridSize = 10;
|
||||||
const maxGridSize = 200;
|
const maxGridSize = 200;
|
||||||
|
|
||||||
function gridSizeVaild(x, y) {
|
export function gridSizeVaild(x, y) {
|
||||||
return (
|
return (
|
||||||
x > minGridSize && y > minGridSize && x < maxGridSize && y < maxGridSize
|
x > minGridSize && y > minGridSize && x < maxGridSize && y < maxGridSize
|
||||||
);
|
);
|
||||||
@ -133,7 +133,16 @@ async function gridSizeML(image, candidates) {
|
|||||||
|
|
||||||
export async function getGridSize(image) {
|
export async function getGridSize(image) {
|
||||||
const candidates = dividers(image.width, image.height);
|
const candidates = dividers(image.width, image.height);
|
||||||
let prediction = await gridSizeML(image, candidates);
|
let prediction;
|
||||||
|
|
||||||
|
// Try and use ML grid detection
|
||||||
|
// TODO: Fix possible error on Android
|
||||||
|
try {
|
||||||
|
prediction = await gridSizeML(image, candidates);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
if (!prediction) {
|
if (!prediction) {
|
||||||
prediction = gridSizeHeuristic(image, candidates);
|
prediction = gridSizeHeuristic(image, candidates);
|
||||||
}
|
}
|
||||||
@ -143,3 +152,11 @@ export async function getGridSize(image) {
|
|||||||
|
|
||||||
return prediction;
|
return prediction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getMapMaxZoom(map) {
|
||||||
|
if (!map) {
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
// Return max grid size / 2
|
||||||
|
return Math.max(Math.min(map.grid.size.x, map.grid.size.y) / 2, 5);
|
||||||
|
}
|
||||||
|
71
src/helpers/useImageCenter.js
Normal file
71
src/helpers/useImageCenter.js
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import { useEffect, useRef } from "react";
|
||||||
|
|
||||||
|
function useImageCenter(
|
||||||
|
data,
|
||||||
|
stageRef,
|
||||||
|
stageWidth,
|
||||||
|
stageHeight,
|
||||||
|
stageTranslateRef,
|
||||||
|
setStageScale,
|
||||||
|
imageLayerRef,
|
||||||
|
containerRef,
|
||||||
|
responsive = false
|
||||||
|
) {
|
||||||
|
const stageRatio = stageWidth / stageHeight;
|
||||||
|
const imageRatio = data ? data.width / data.height : 1;
|
||||||
|
|
||||||
|
let imageWidth;
|
||||||
|
let imageHeight;
|
||||||
|
if (stageRatio > imageRatio) {
|
||||||
|
imageWidth = data ? stageHeight / (data.height / data.width) : stageWidth;
|
||||||
|
imageHeight = stageHeight;
|
||||||
|
} else {
|
||||||
|
imageWidth = stageWidth;
|
||||||
|
imageHeight = data ? stageWidth * (data.height / data.width) : stageHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset image translate and stage scale
|
||||||
|
const previousDataIdRef = useRef();
|
||||||
|
const previousStageRatioRef = useRef(stageRatio);
|
||||||
|
useEffect(() => {
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const layer = imageLayerRef.current;
|
||||||
|
const containerRect = containerRef.current.getBoundingClientRect();
|
||||||
|
const previousDataId = previousDataIdRef.current;
|
||||||
|
const previousStageRatio = previousStageRatioRef.current;
|
||||||
|
|
||||||
|
// Update when the id has changed and if responsive update when the stage changes
|
||||||
|
const shouldUpdate = responsive
|
||||||
|
? previousDataId !== data.id || previousStageRatio !== stageRatio
|
||||||
|
: previousDataId !== data.id;
|
||||||
|
|
||||||
|
if (layer && shouldUpdate) {
|
||||||
|
let newTranslate;
|
||||||
|
if (stageRatio > imageRatio) {
|
||||||
|
newTranslate = {
|
||||||
|
x: -(imageWidth - containerRect.width) / 2,
|
||||||
|
y: 0,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
newTranslate = {
|
||||||
|
x: 0,
|
||||||
|
y: -(imageHeight - containerRect.height) / 2,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
layer.position(newTranslate);
|
||||||
|
stageRef.current.position({ x: 0, y: 0 });
|
||||||
|
stageTranslateRef.current = { x: 0, y: 0 };
|
||||||
|
|
||||||
|
setStageScale(1);
|
||||||
|
}
|
||||||
|
previousDataIdRef.current = data.id;
|
||||||
|
previousStageRatioRef.current = stageRatio;
|
||||||
|
});
|
||||||
|
|
||||||
|
return [imageWidth, imageHeight];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useImageCenter;
|
@ -2,16 +2,17 @@ import { useRef } from "react";
|
|||||||
import { useGesture } from "react-use-gesture";
|
import { useGesture } from "react-use-gesture";
|
||||||
import normalizeWheel from "normalize-wheel";
|
import normalizeWheel from "normalize-wheel";
|
||||||
|
|
||||||
const wheelZoomSpeed = -0.001;
|
const wheelZoomSpeed = -1;
|
||||||
const touchZoomSpeed = 0.005;
|
const touchZoomSpeed = 0.005;
|
||||||
const minZoom = 0.1;
|
const minZoom = 0.1;
|
||||||
const maxZoom = 10;
|
|
||||||
|
|
||||||
function useStageInteraction(
|
function useStageInteraction(
|
||||||
layer,
|
stage,
|
||||||
stageScale,
|
stageScale,
|
||||||
onStageScaleChange,
|
onStageScaleChange,
|
||||||
stageTranslateRef,
|
stageTranslateRef,
|
||||||
|
layer,
|
||||||
|
maxZoom = 10,
|
||||||
tool = "pan",
|
tool = "pan",
|
||||||
preventInteraction = false,
|
preventInteraction = false,
|
||||||
gesture = {}
|
gesture = {}
|
||||||
@ -36,42 +37,85 @@ function useStageInteraction(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const newScale = Math.min(
|
const newScale = Math.min(
|
||||||
Math.max(stageScale + pixelY * wheelZoomSpeed, minZoom),
|
Math.max(
|
||||||
|
stageScale +
|
||||||
|
(pixelY * wheelZoomSpeed * stageScale) / window.innerHeight,
|
||||||
|
minZoom
|
||||||
|
),
|
||||||
maxZoom
|
maxZoom
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Center on pointer
|
||||||
|
const pointer = stage.getPointerPosition();
|
||||||
|
const newTranslate = {
|
||||||
|
x: pointer.x - ((pointer.x - stage.x()) / stageScale) * newScale,
|
||||||
|
y: pointer.y - ((pointer.y - stage.y()) / stageScale) * newScale,
|
||||||
|
};
|
||||||
|
|
||||||
|
stage.position(newTranslate);
|
||||||
|
stageTranslateRef.current = newTranslate;
|
||||||
|
|
||||||
onStageScaleChange(newScale);
|
onStageScaleChange(newScale);
|
||||||
gesture.onWheel && gesture.onWheel(props);
|
gesture.onWheel && gesture.onWheel(props);
|
||||||
},
|
},
|
||||||
onPinch: (props) => {
|
onPinchStart: (props) => {
|
||||||
const { da, origin, first } = props;
|
const { event } = props;
|
||||||
|
isInteractingWithCanvas.current =
|
||||||
|
event.target === layer.getCanvas()._canvas;
|
||||||
|
const { da, origin } = props;
|
||||||
const [distance] = da;
|
const [distance] = da;
|
||||||
const [originX, originY] = origin;
|
const [originX, originY] = origin;
|
||||||
if (first) {
|
|
||||||
pinchPreviousDistanceRef.current = distance;
|
pinchPreviousDistanceRef.current = distance;
|
||||||
pinchPreviousOriginRef.current = { x: originX, y: originY };
|
pinchPreviousOriginRef.current = { x: originX, y: originY };
|
||||||
|
gesture.onPinchStart && gesture.onPinchStart(props);
|
||||||
|
},
|
||||||
|
onPinch: (props) => {
|
||||||
|
if (preventInteraction || !isInteractingWithCanvas.current) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
const { da, origin } = props;
|
||||||
|
const [distance] = da;
|
||||||
|
const [originX, originY] = origin;
|
||||||
|
|
||||||
// Apply scale
|
// Apply scale
|
||||||
const distanceDelta = distance - pinchPreviousDistanceRef.current;
|
const distanceDelta = distance - pinchPreviousDistanceRef.current;
|
||||||
const originXDelta = originX - pinchPreviousOriginRef.current.x;
|
const originXDelta = originX - pinchPreviousOriginRef.current.x;
|
||||||
const originYDelta = originY - pinchPreviousOriginRef.current.y;
|
const originYDelta = originY - pinchPreviousOriginRef.current.y;
|
||||||
const newScale = Math.min(
|
const newScale = Math.min(
|
||||||
Math.max(stageScale + distanceDelta * touchZoomSpeed, minZoom),
|
Math.max(
|
||||||
|
stageScale + distanceDelta * touchZoomSpeed * stageScale,
|
||||||
|
minZoom
|
||||||
|
),
|
||||||
maxZoom
|
maxZoom
|
||||||
);
|
);
|
||||||
onStageScaleChange(newScale);
|
|
||||||
|
|
||||||
// Apply translate
|
const canvasRect = layer.getCanvas()._canvas.getBoundingClientRect();
|
||||||
const stageTranslate = stageTranslateRef.current;
|
const relativeOrigin = {
|
||||||
const newTranslate = {
|
x: originX - canvasRect.left,
|
||||||
x: stageTranslate.x + originXDelta / newScale,
|
y: originY - canvasRect.top,
|
||||||
y: stageTranslate.y + originYDelta / newScale,
|
|
||||||
};
|
};
|
||||||
layer.x(newTranslate.x);
|
|
||||||
layer.y(newTranslate.y);
|
// Center on pinch origin
|
||||||
layer.draw();
|
const centeredTranslate = {
|
||||||
|
x:
|
||||||
|
relativeOrigin.x -
|
||||||
|
((relativeOrigin.x - stage.x()) / stageScale) * newScale,
|
||||||
|
y:
|
||||||
|
relativeOrigin.y -
|
||||||
|
((relativeOrigin.y - stage.y()) / stageScale) * newScale,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add pinch movement
|
||||||
|
const newTranslate = {
|
||||||
|
x: centeredTranslate.x + originXDelta,
|
||||||
|
y: centeredTranslate.y + originYDelta,
|
||||||
|
};
|
||||||
|
|
||||||
|
stage.position(newTranslate);
|
||||||
stageTranslateRef.current = newTranslate;
|
stageTranslateRef.current = newTranslate;
|
||||||
|
|
||||||
|
onStageScaleChange(newScale);
|
||||||
|
|
||||||
pinchPreviousDistanceRef.current = distance;
|
pinchPreviousDistanceRef.current = distance;
|
||||||
pinchPreviousOriginRef.current = { x: originX, y: originY };
|
pinchPreviousOriginRef.current = { x: originX, y: originY };
|
||||||
gesture.onPinch && gesture.onPinch(props);
|
gesture.onPinch && gesture.onPinch(props);
|
||||||
@ -92,12 +136,11 @@ function useStageInteraction(
|
|||||||
const stageTranslate = stageTranslateRef.current;
|
const stageTranslate = stageTranslateRef.current;
|
||||||
if (tool === "pan") {
|
if (tool === "pan") {
|
||||||
const newTranslate = {
|
const newTranslate = {
|
||||||
x: stageTranslate.x + dx / stageScale,
|
x: stageTranslate.x + dx,
|
||||||
y: stageTranslate.y + dy / stageScale,
|
y: stageTranslate.y + dy,
|
||||||
};
|
};
|
||||||
layer.x(newTranslate.x);
|
stage.position(newTranslate);
|
||||||
layer.y(newTranslate.y);
|
stage.draw();
|
||||||
layer.draw();
|
|
||||||
stageTranslateRef.current = newTranslate;
|
stageTranslateRef.current = newTranslate;
|
||||||
}
|
}
|
||||||
gesture.onDrag && gesture.onDrag(props);
|
gesture.onDrag && gesture.onDrag(props);
|
||||||
|
@ -96,7 +96,7 @@ function EditMapModal({ isOpen, onDone, map, mapState }) {
|
|||||||
...mapStateSettingChanges,
|
...mapStateSettingChanges,
|
||||||
};
|
};
|
||||||
|
|
||||||
const [showMoreSettings, setShowMoreSettings] = useState(false);
|
const [showMoreSettings, setShowMoreSettings] = useState(true);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
@ -16,7 +16,7 @@ import blobToBuffer from "../helpers/blobToBuffer";
|
|||||||
import useKeyboard from "../helpers/useKeyboard";
|
import useKeyboard from "../helpers/useKeyboard";
|
||||||
import { resizeImage } from "../helpers/image";
|
import { resizeImage } from "../helpers/image";
|
||||||
import { useSearch, useGroup, handleItemSelect } from "../helpers/select";
|
import { useSearch, useGroup, handleItemSelect } from "../helpers/select";
|
||||||
import { getMapDefaultInset, getGridSize } from "../helpers/map";
|
import { getMapDefaultInset, getGridSize, gridSizeVaild } from "../helpers/map";
|
||||||
|
|
||||||
import MapDataContext from "../contexts/MapDataContext";
|
import MapDataContext from "../contexts/MapDataContext";
|
||||||
import AuthContext from "../contexts/AuthContext";
|
import AuthContext from "../contexts/AuthContext";
|
||||||
@ -120,11 +120,14 @@ function SelectMapModal({
|
|||||||
// Match against a regex to find the grid size in the file name
|
// Match against a regex to find the grid size in the file name
|
||||||
// e.g. Cave 22x23 will return [["22x22", "22", "x", "23"]]
|
// e.g. Cave 22x23 will return [["22x22", "22", "x", "23"]]
|
||||||
const gridMatches = [...file.name.matchAll(/(\d+) ?(x|X) ?(\d+)/g)];
|
const gridMatches = [...file.name.matchAll(/(\d+) ?(x|X) ?(\d+)/g)];
|
||||||
if (gridMatches.length > 0) {
|
for (let match of gridMatches) {
|
||||||
const lastMatch = gridMatches[gridMatches.length - 1];
|
const matchX = parseInt(match[1]);
|
||||||
const matchX = parseInt(lastMatch[1]);
|
const matchY = parseInt(match[3]);
|
||||||
const matchY = parseInt(lastMatch[3]);
|
if (
|
||||||
if (!isNaN(matchX) && !isNaN(matchY)) {
|
!isNaN(matchX) &&
|
||||||
|
!isNaN(matchY) &&
|
||||||
|
gridSizeVaild(matchX, matchY)
|
||||||
|
) {
|
||||||
gridSize = { x: matchX, y: matchY };
|
gridSize = { x: matchX, y: matchY };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,6 +109,8 @@ function SelectTokensModal({ isOpen, onRequestClose }) {
|
|||||||
category: "character",
|
category: "character",
|
||||||
hideInSidebar: false,
|
hideInSidebar: false,
|
||||||
group: "",
|
group: "",
|
||||||
|
width: image.width,
|
||||||
|
height: image.height,
|
||||||
});
|
});
|
||||||
setImageLoading(false);
|
setImageLoading(false);
|
||||||
resolve();
|
resolve();
|
||||||
|
@ -2,7 +2,7 @@ import SimplePeer from "simple-peer";
|
|||||||
import { encode, decode } from "@msgpack/msgpack";
|
import { encode, decode } from "@msgpack/msgpack";
|
||||||
import shortid from "shortid";
|
import shortid from "shortid";
|
||||||
|
|
||||||
import blobToBuffer from "./blobToBuffer";
|
import blobToBuffer from "../helpers/blobToBuffer";
|
||||||
|
|
||||||
// Limit buffer size to 16kb to avoid issues with chrome packet size
|
// Limit buffer size to 16kb to avoid issues with chrome packet size
|
||||||
// http://viblast.com/blog/2015/2/5/webrtc-data-channel-message-size/
|
// http://viblast.com/blog/2015/2/5/webrtc-data-channel-message-size/
|
||||||
@ -63,7 +63,8 @@ class Connection extends SimplePeer {
|
|||||||
const chunks = this.chunk(packedData);
|
const chunks = this.chunk(packedData);
|
||||||
for (let chunk of chunks) {
|
for (let chunk of chunks) {
|
||||||
if (this.dataChannels[channel]) {
|
if (this.dataChannels[channel]) {
|
||||||
this.dataChannels[channel].send(encode(chunk));
|
// Write to the stream to allow for buffer / backpressure handling
|
||||||
|
this.dataChannels[channel].write(encode(chunk));
|
||||||
} else {
|
} else {
|
||||||
super.send(encode(chunk));
|
super.send(encode(chunk));
|
||||||
}
|
}
|
@ -10,7 +10,7 @@ import { omit } from "../helpers/shared";
|
|||||||
import useDebounce from "../helpers/useDebounce";
|
import useDebounce from "../helpers/useDebounce";
|
||||||
// Load session for auto complete
|
// Load session for auto complete
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
import Session from "../helpers/Session";
|
import Session from "./Session";
|
||||||
|
|
||||||
import Map from "../components/map/Map";
|
import Map from "../components/map/Map";
|
||||||
import Tokens from "../components/token/Tokens";
|
import Tokens from "../components/token/Tokens";
|
||||||
@ -33,7 +33,9 @@ function NetworkedMapAndTokens({ session }) {
|
|||||||
} = useContext(MapLoadingContext);
|
} = useContext(MapLoadingContext);
|
||||||
|
|
||||||
const { putToken, getToken, updateToken } = useContext(TokenDataContext);
|
const { putToken, getToken, updateToken } = useContext(TokenDataContext);
|
||||||
const { putMap, updateMap, getMapFromDB } = useContext(MapDataContext);
|
const { putMap, updateMap, getMapFromDB, updateMapState } = useContext(
|
||||||
|
MapDataContext
|
||||||
|
);
|
||||||
|
|
||||||
const [currentMap, setCurrentMap] = useState(null);
|
const [currentMap, setCurrentMap] = useState(null);
|
||||||
const [currentMapState, setCurrentMapState] = useState(null);
|
const [currentMapState, setCurrentMapState] = useState(null);
|
||||||
@ -53,11 +55,9 @@ function NetworkedMapAndTokens({ session }) {
|
|||||||
currentMap.owner === userId &&
|
currentMap.owner === userId &&
|
||||||
database
|
database
|
||||||
) {
|
) {
|
||||||
// Update the database directly to avoid re-renders
|
updateMapState(debouncedMapState.mapId, debouncedMapState);
|
||||||
database
|
|
||||||
.table("states")
|
|
||||||
.update(debouncedMapState.mapId, debouncedMapState);
|
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [currentMap, debouncedMapState, userId, database]);
|
}, [currentMap, debouncedMapState, userId, database]);
|
||||||
|
|
||||||
function handleMapChange(newMap, newMapState) {
|
function handleMapChange(newMap, newMapState) {
|
||||||
@ -318,9 +318,10 @@ function NetworkedMapAndTokens({ session }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (id === "mapResponse") {
|
if (id === "mapResponse") {
|
||||||
await updateMap(data.id, data);
|
const { id, ...update } = data;
|
||||||
const newMap = await getMapFromDB(data.id);
|
await updateMap(id, update);
|
||||||
setCurrentMap(newMap);
|
const updatedMap = await getMapFromDB(data.id);
|
||||||
|
setCurrentMap(updatedMap);
|
||||||
}
|
}
|
||||||
if (id === "mapState") {
|
if (id === "mapState") {
|
||||||
setCurrentMapState(data);
|
setCurrentMapState(data);
|
||||||
|
@ -2,7 +2,7 @@ import React, { useContext, useState, useEffect, useCallback } from "react";
|
|||||||
|
|
||||||
// Load session for auto complete
|
// Load session for auto complete
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
import Session from "../helpers/Session";
|
import Session from "./Session";
|
||||||
import { isStreamStopped, omit, fromEntries } from "../helpers/shared";
|
import { isStreamStopped, omit, fromEntries } from "../helpers/shared";
|
||||||
|
|
||||||
import AuthContext from "../contexts/AuthContext";
|
import AuthContext from "../contexts/AuthContext";
|
||||||
|
@ -3,7 +3,7 @@ import { EventEmitter } from "events";
|
|||||||
|
|
||||||
import Connection from "./Connection";
|
import Connection from "./Connection";
|
||||||
|
|
||||||
import { omit } from "./shared";
|
import { omit } from "../helpers/shared";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {object} SessionPeer
|
* @typedef {object} SessionPeer
|
@ -10,11 +10,12 @@ import AuthModal from "../modals/AuthModal";
|
|||||||
|
|
||||||
import AuthContext from "../contexts/AuthContext";
|
import AuthContext from "../contexts/AuthContext";
|
||||||
import { MapStageProvider } from "../contexts/MapStageContext";
|
import { MapStageProvider } from "../contexts/MapStageContext";
|
||||||
|
import DatabaseContext from "../contexts/DatabaseContext";
|
||||||
|
|
||||||
import NetworkedMapAndTokens from "../network/NetworkedMapAndTokens";
|
import NetworkedMapAndTokens from "../network/NetworkedMapAndTokens";
|
||||||
import NetworkedParty from "../network/NetworkedParty";
|
import NetworkedParty from "../network/NetworkedParty";
|
||||||
|
|
||||||
import Session from "../helpers/Session";
|
import Session from "../network/Session";
|
||||||
|
|
||||||
const session = new Session();
|
const session = new Session();
|
||||||
|
|
||||||
@ -25,6 +26,7 @@ function Game() {
|
|||||||
password,
|
password,
|
||||||
setAuthenticationStatus,
|
setAuthenticationStatus,
|
||||||
} = useContext(AuthContext);
|
} = useContext(AuthContext);
|
||||||
|
const { databaseStatus } = useContext(DatabaseContext);
|
||||||
|
|
||||||
// Handle authentication status
|
// Handle authentication status
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -82,8 +84,10 @@ function Game() {
|
|||||||
|
|
||||||
// Join game
|
// Join game
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (databaseStatus !== "loading") {
|
||||||
session.joinParty(gameId, password);
|
session.joinParty(gameId, password);
|
||||||
}, [gameId, password]);
|
}
|
||||||
|
}, [gameId, password, databaseStatus]);
|
||||||
|
|
||||||
// A ref to the Konva stage
|
// A ref to the Konva stage
|
||||||
// the ref will be assigned in the MapInteraction component
|
// the ref will be assigned in the MapInteraction component
|
||||||
|
@ -21,6 +21,7 @@ const v150 = raw("../docs/releaseNotes/v1.5.0.md");
|
|||||||
const v151 = raw("../docs/releaseNotes/v1.5.1.md");
|
const v151 = raw("../docs/releaseNotes/v1.5.1.md");
|
||||||
const v152 = raw("../docs/releaseNotes/v1.5.2.md");
|
const v152 = raw("../docs/releaseNotes/v1.5.2.md");
|
||||||
const v160 = raw("../docs/releaseNotes/v1.6.0.md");
|
const v160 = raw("../docs/releaseNotes/v1.6.0.md");
|
||||||
|
const v161 = raw("../docs/releaseNotes/v1.6.1.md");
|
||||||
|
|
||||||
function ReleaseNotes() {
|
function ReleaseNotes() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@ -45,7 +46,12 @@ function ReleaseNotes() {
|
|||||||
<Text mb={2} variant="heading" as="h1" sx={{ fontSize: 5 }}>
|
<Text mb={2} variant="heading" as="h1" sx={{ fontSize: 5 }}>
|
||||||
Release Notes
|
Release Notes
|
||||||
</Text>
|
</Text>
|
||||||
<div id="v152">
|
<div id="v161">
|
||||||
|
<Accordion heading="v1.6.1" defaultOpen>
|
||||||
|
<Markdown source={v161} />
|
||||||
|
</Accordion>
|
||||||
|
</div>
|
||||||
|
<div id="v160">
|
||||||
<Accordion heading="v1.6.0" defaultOpen>
|
<Accordion heading="v1.6.0" defaultOpen>
|
||||||
<Markdown source={v160} />
|
<Markdown source={v160} />
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
@ -87,6 +87,8 @@ export const tokens = Object.keys(tokenSources).map((key) => ({
|
|||||||
defaultSize: getDefaultTokenSize(key),
|
defaultSize: getDefaultTokenSize(key),
|
||||||
category: "character",
|
category: "character",
|
||||||
hideInSidebar: false,
|
hideInSidebar: false,
|
||||||
|
width: 256,
|
||||||
|
height: 256,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const unknownSource = unknown;
|
export const unknownSource = unknown;
|
||||||
|
Loading…
Reference in New Issue
Block a user