Merge pull request #53 from mitchemmc/test/v1.10.1

Test/v1.10.1
This commit is contained in:
Mitchell McCaffrey 2021-10-21 20:38:07 +11:00 committed by GitHub
commit 2a7817006f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 435 additions and 167 deletions

View File

@ -1,5 +1,5 @@
REACT_APP_BROKER_URL=https://prod.owlbear.rodeo REACT_APP_BROKER_URL=https://stage.owlbear.rodeo
REACT_APP_ICE_SERVERS_URL=https://prod.owlbear.rodeo/iceservers REACT_APP_ICE_SERVERS_URL=https://stage.owlbear.rodeo/iceservers
REACT_APP_STRIPE_API_KEY=pk_live_MJjzi5djj524Y7h3fL5PNh4e00a852XD51 REACT_APP_STRIPE_API_KEY=pk_live_MJjzi5djj524Y7h3fL5PNh4e00a852XD51
REACT_APP_STRIPE_URL=https://payment.owlbear.rodeo REACT_APP_STRIPE_URL=https://payment.owlbear.rodeo
REACT_APP_VERSION=$npm_package_version REACT_APP_VERSION=$npm_package_version

View File

@ -1,6 +1,6 @@
{ {
"name": "owlbear-rodeo", "name": "owlbear-rodeo",
"version": "1.10.0.2", "version": "1.10.1",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@babylonjs/core": "^4.2.0", "@babylonjs/core": "^4.2.0",
@ -67,6 +67,7 @@
"socket.io-msgpack-parser": "^3.0.1", "socket.io-msgpack-parser": "^3.0.1",
"source-map-explorer": "^2.5.2", "source-map-explorer": "^2.5.2",
"theme-ui": "^0.10.0", "theme-ui": "^0.10.0",
"tiny-typed-emitter": "^2.1.0",
"use-image": "^1.0.8", "use-image": "^1.0.8",
"uuid": "^8.3.2", "uuid": "^8.3.2",
"webrtc-adapter": "^8.1.0" "webrtc-adapter": "^8.1.0"

View File

@ -0,0 +1,47 @@
import { Rect } from "react-konva";
import {
useMapWidth,
useMapHeight,
} from "../../contexts/MapInteractionContext";
import { useGridCellPixelSize } from "../../contexts/GridContext";
import colors from "../../helpers/colors";
import { Note as NoteType } from "../../types/Note";
type NoteProps = {
note: NoteType;
};
function BlankNote({ note }: NoteProps) {
const mapWidth = useMapWidth();
const mapHeight = useMapHeight();
const gridCellPixelSize = useGridCellPixelSize();
const minCellSize = Math.min(
gridCellPixelSize.width,
gridCellPixelSize.height
);
const noteWidth = minCellSize * note.size;
const noteHeight = noteWidth;
return (
<Rect
x={note.x * mapWidth}
y={note.y * mapHeight}
width={noteWidth}
height={noteHeight}
offsetX={noteWidth / 2}
offsetY={noteHeight / 2}
shadowColor="rgba(0, 0, 0, 0.16)"
shadowOffset={{ x: 3, y: 6 }}
shadowBlur={6}
cornerRadius={0.25}
fill={colors[note.color]}
/>
);
}
export default BlankNote;

View File

@ -8,6 +8,7 @@ import {
useSetPreventMapInteraction, useSetPreventMapInteraction,
useMapWidth, useMapWidth,
useMapHeight, useMapHeight,
leftMouseButton,
} from "../../contexts/MapInteractionContext"; } from "../../contexts/MapInteractionContext";
import { useGridCellPixelSize } from "../../contexts/GridContext"; import { useGridCellPixelSize } from "../../contexts/GridContext";
@ -102,6 +103,9 @@ function Note({
} }
function handleClick(event: Konva.KonvaEventObject<MouseEvent>) { function handleClick(event: Konva.KonvaEventObject<MouseEvent>) {
if (!leftMouseButton(event)) {
return;
}
if (draggable) { if (draggable) {
const noteNode = event.target; const noteNode = event.target;
onNoteMenuOpen && onNoteMenuOpen(note.id, noteNode, true); onNoteMenuOpen && onNoteMenuOpen(note.id, noteNode, true);
@ -111,6 +115,9 @@ function Note({
// Store note pointer down time to check for a click when note is locked // Store note pointer down time to check for a click when note is locked
const notePointerDownTimeRef = useRef<number>(0); const notePointerDownTimeRef = useRef<number>(0);
function handlePointerDown(event: Konva.KonvaEventObject<PointerEvent>) { function handlePointerDown(event: Konva.KonvaEventObject<PointerEvent>) {
if (!leftMouseButton(event)) {
return;
}
if (draggable) { if (draggable) {
setPreventMapInteraction(true); setPreventMapInteraction(true);
} }
@ -120,6 +127,9 @@ function Note({
} }
function handlePointerUp(event: Konva.KonvaEventObject<PointerEvent>) { function handlePointerUp(event: Konva.KonvaEventObject<PointerEvent>) {
if (!leftMouseButton(event)) {
return;
}
if (draggable) { if (draggable) {
setPreventMapInteraction(false); setPreventMapInteraction(false);
} }

View File

@ -12,6 +12,7 @@ import {
useSetPreventMapInteraction, useSetPreventMapInteraction,
useMapWidth, useMapWidth,
useMapHeight, useMapHeight,
leftMouseButton,
} from "../../contexts/MapInteractionContext"; } from "../../contexts/MapInteractionContext";
import { useGridCellPixelSize } from "../../contexts/GridContext"; import { useGridCellPixelSize } from "../../contexts/GridContext";
import { useDataURL } from "../../contexts/AssetsContext"; import { useDataURL } from "../../contexts/AssetsContext";
@ -197,7 +198,10 @@ function Token({
setAttachmentOverCharacter(false); setAttachmentOverCharacter(false);
} }
function handleClick() { function handleClick(event: Konva.KonvaEventObject<MouseEvent>) {
if (!leftMouseButton(event)) {
return;
}
if (selectable && draggable && transformRootRef.current) { if (selectable && draggable && transformRootRef.current) {
onTokenMenuOpen(tokenState.id, transformRootRef.current, true); onTokenMenuOpen(tokenState.id, transformRootRef.current, true);
} }
@ -207,6 +211,9 @@ function Token({
// Store token pointer down time to check for a click when token is locked // Store token pointer down time to check for a click when token is locked
const tokenPointerDownTimeRef = useRef<number>(0); const tokenPointerDownTimeRef = useRef<number>(0);
function handlePointerDown(event: Konva.KonvaEventObject<PointerEvent>) { function handlePointerDown(event: Konva.KonvaEventObject<PointerEvent>) {
if (!leftMouseButton(event)) {
return;
}
if (draggable) { if (draggable) {
setPreventMapInteraction(true); setPreventMapInteraction(true);
} }
@ -216,6 +223,9 @@ function Token({
} }
function handlePointerUp(event: Konva.KonvaEventObject<PointerEvent>) { function handlePointerUp(event: Konva.KonvaEventObject<PointerEvent>) {
if (!leftMouseButton(event)) {
return;
}
if (draggable) { if (draggable) {
setPreventMapInteraction(false); setPreventMapInteraction(false);
} }

View File

@ -3,17 +3,20 @@ import { Box } from "theme-ui";
import ReactResizeDetector from "react-resize-detector"; import ReactResizeDetector from "react-resize-detector";
import { Stage, Layer, Image, Group } from "react-konva"; import { Stage, Layer, Image, Group } from "react-konva";
import Konva from "konva"; import Konva from "konva";
import { EventEmitter } from "events";
import useMapImage from "../../hooks/useMapImage"; import useMapImage from "../../hooks/useMapImage";
import usePreventOverscroll from "../../hooks/usePreventOverscroll"; import usePreventOverscroll from "../../hooks/usePreventOverscroll";
import useStageInteraction from "../../hooks/useStageInteraction"; import useStageInteraction from "../../hooks/useStageInteraction";
import useImageCenter from "../../hooks/useImageCenter"; import useImageCenter from "../../hooks/useImageCenter";
import usePreventContextMenu from "../../hooks/usePreventContextMenu";
import { getGridMaxZoom } from "../../helpers/grid"; import { getGridMaxZoom } from "../../helpers/grid";
import KonvaBridge from "../../helpers/KonvaBridge"; import KonvaBridge from "../../helpers/KonvaBridge";
import { MapInteractionProvider } from "../../contexts/MapInteractionContext"; import {
MapInteractionEmitter,
MapInteractionProvider,
} from "../../contexts/MapInteractionContext";
import { useMapStage } from "../../contexts/MapStageContext"; import { useMapStage } from "../../contexts/MapStageContext";
import { GridProvider } from "../../contexts/GridContext"; import { GridProvider } from "../../contexts/GridContext";
import { useKeyboard } from "../../contexts/KeyboardContext"; import { useKeyboard } from "../../contexts/KeyboardContext";
@ -72,6 +75,7 @@ function MapInteraction({
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
usePreventOverscroll(containerRef); usePreventOverscroll(containerRef);
usePreventContextMenu(containerRef);
const [mapWidth, mapHeight] = useImageCenter( const [mapWidth, mapHeight] = useImageCenter(
map, map,
@ -85,8 +89,9 @@ function MapInteraction({
); );
const previousSelectedToolRef = useRef(selectedToolId); const previousSelectedToolRef = useRef(selectedToolId);
const [currentMouseButtons, setCurentMouseButtons] = useState(0);
const [interactionEmitter] = useState(new EventEmitter()); const [interactionEmitter] = useState(new MapInteractionEmitter());
useStageInteraction( useStageInteraction(
mapStageRef, mapStageRef,
@ -106,13 +111,17 @@ function MapInteraction({
onPinchEnd: () => { onPinchEnd: () => {
onSelectedToolChange(previousSelectedToolRef.current); onSelectedToolChange(previousSelectedToolRef.current);
}, },
onDrag: ({ first, last }) => { onDrag: (props) => {
const { first, last, buttons } = props;
if (buttons !== currentMouseButtons) {
setCurentMouseButtons(buttons);
}
if (first) { if (first) {
interactionEmitter.emit("dragStart"); interactionEmitter.emit("dragStart", props);
} else if (last) { } else if (last) {
interactionEmitter.emit("dragEnd"); interactionEmitter.emit("dragEnd", props);
} else { } else {
interactionEmitter.emit("drag"); interactionEmitter.emit("drag", props);
} }
}, },
} }
@ -143,6 +152,11 @@ function MapInteraction({
useKeyboard(handleKeyDown, handleKeyUp); useKeyboard(handleKeyDown, handleKeyUp);
function getCursorForTool(tool: MapToolId) { function getCursorForTool(tool: MapToolId) {
if (currentMouseButtons === 2) {
return "crosshair";
} else if (currentMouseButtons > 2) {
return "move";
}
switch (tool) { switch (tool) {
case "move": case "move":
return "move"; return "move";

View File

@ -7,6 +7,8 @@ import {
useMapWidth, useMapWidth,
useMapHeight, useMapHeight,
useInteractionEmitter, useInteractionEmitter,
leftMouseButton,
MapDragEvent,
} from "../../contexts/MapInteractionContext"; } from "../../contexts/MapInteractionContext";
import { useMapStage } from "../../contexts/MapStageContext"; import { useMapStage } from "../../contexts/MapStageContext";
import { import {
@ -103,7 +105,10 @@ function DrawingTool({
}); });
} }
function handleBrushDown() { function handleBrushDown(props: MapDragEvent) {
if (!leftMouseButton(props)) {
return;
}
const brushPosition = getBrushPosition(); const brushPosition = getBrushPosition();
if (!brushPosition) { if (!brushPosition) {
return; return;
@ -135,7 +140,10 @@ function DrawingTool({
setIsBrushDown(true); setIsBrushDown(true);
} }
function handleBrushMove() { function handleBrushMove(props: MapDragEvent) {
if (!leftMouseButton(props)) {
return;
}
const brushPosition = getBrushPosition(); const brushPosition = getBrushPosition();
if (!brushPosition) { if (!brushPosition) {
return; return;
@ -186,7 +194,10 @@ function DrawingTool({
} }
} }
function handleBrushUp() { function handleBrushUp(props: MapDragEvent) {
if (!leftMouseButton(props)) {
return;
}
if (isBrush && drawing && drawing.type === "path") { if (isBrush && drawing && drawing.type === "path") {
if (drawing.data.points.length > 1) { if (drawing.data.points.length > 1) {
onShapeAdd(drawing); onShapeAdd(drawing);

View File

@ -3,6 +3,7 @@ import shortid from "shortid";
import { Group, Line } from "react-konva"; import { Group, Line } from "react-konva";
import useImage from "use-image"; import useImage from "use-image";
import Color from "color"; import Color from "color";
import Konva from "konva";
import diagonalPattern from "../../images/DiagonalPattern.png"; import diagonalPattern from "../../images/DiagonalPattern.png";
@ -11,6 +12,8 @@ import {
useMapWidth, useMapWidth,
useMapHeight, useMapHeight,
useInteractionEmitter, useInteractionEmitter,
MapDragEvent,
leftMouseButton,
} from "../../contexts/MapInteractionContext"; } from "../../contexts/MapInteractionContext";
import { useMapStage } from "../../contexts/MapStageContext"; import { useMapStage } from "../../contexts/MapStageContext";
import { import {
@ -104,7 +107,7 @@ function FogTool({
const [drawingShape, setDrawingShape] = useState<Fog | null>(null); const [drawingShape, setDrawingShape] = useState<Fog | null>(null);
const [isBrushDown, setIsBrushDown] = useState(false); const [isBrushDown, setIsBrushDown] = useState(false);
const [editingShapes, setEditingShapes] = useState<Fog[]>([]); const [hoveredShapes, setHoveredShapes] = useState<Fog[]>([]);
// Shapes that have been merged for fog // Shapes that have been merged for fog
const [fogShapes, setFogShapes] = useState(shapes); const [fogShapes, setFogShapes] = useState(shapes);
@ -160,7 +163,10 @@ function FogTool({
}); });
} }
function handleBrushDown() { function handleBrushDown(props: MapDragEvent) {
if (!leftMouseButton(props)) {
return;
}
if (toolSettings.type === "brush") { if (toolSettings.type === "brush") {
const brushPosition = getBrushPosition(); const brushPosition = getBrushPosition();
if (!brushPosition) { if (!brushPosition) {
@ -203,7 +209,10 @@ function FogTool({
setIsBrushDown(true); setIsBrushDown(true);
} }
function handleBrushMove() { function handleBrushMove(props: MapDragEvent) {
if (!leftMouseButton(props)) {
return;
}
if (toolSettings.type === "brush" && isBrushDown && drawingShape) { if (toolSettings.type === "brush" && isBrushDown && drawingShape) {
const brushPosition = getBrushPosition(); const brushPosition = getBrushPosition();
if (!brushPosition) { if (!brushPosition) {
@ -258,7 +267,10 @@ function FogTool({
} }
} }
function handleBrushUp() { function handleBrushUp(props: MapDragEvent) {
if (!leftMouseButton(props)) {
return;
}
if ( if (
(toolSettings.type === "brush" || toolSettings.type === "rectangle") && (toolSettings.type === "brush" || toolSettings.type === "rectangle") &&
drawingShape drawingShape
@ -318,7 +330,10 @@ function FogTool({
setIsBrushDown(false); setIsBrushDown(false);
} }
function handlePointerClick() { function handlePointerClick(event: Konva.KonvaEventObject<MouseEvent>) {
if (!leftMouseButton(event)) {
return;
}
if (toolSettings.type === "polygon") { if (toolSettings.type === "polygon") {
const brushPosition = getBrushPosition(); const brushPosition = getBrushPosition();
if (brushPosition) { if (brushPosition) {
@ -553,25 +568,25 @@ function FogTool({
function eraseHoveredShapes() { function eraseHoveredShapes() {
// Erase // Erase
if (editingShapes.length > 0) { if (hoveredShapes.length > 0) {
if (toolSettings.type === "remove") { if (toolSettings.type === "remove") {
onShapesRemove(editingShapes.map((shape) => shape.id)); onShapesRemove(hoveredShapes.map((shape) => shape.id));
} else if (toolSettings.type === "toggle") { } else if (toolSettings.type === "toggle") {
onShapesEdit( onShapesEdit(
editingShapes.map((shape) => ({ hoveredShapes.map((shape) => ({
id: shape.id, id: shape.id,
visible: !shape.visible, visible: !shape.visible,
})) }))
); );
} }
setEditingShapes([]); setHoveredShapes([]);
} }
} }
function handleShapeOver(shape: Fog, isDown: boolean) { function handleShapeOver(shape: Fog, isDown: boolean) {
if (shouldHover && isDown) { if (shouldHover && isDown) {
if (editingShapes.findIndex((s) => s.id === shape.id) === -1) { if (hoveredShapes.findIndex((s) => s.id === shape.id) === -1) {
setEditingShapes((prevShapes) => [...prevShapes, shape]); setHoveredShapes((prevShapes) => [...prevShapes, shape]);
} }
} }
} }
@ -609,7 +624,7 @@ function FogTool({
); );
} }
function renderEditingShape(shape: Fog) { function renderHoveredShape(shape: Fog) {
const editingShape: Fog = { const editingShape: Fog = {
...shape, ...shape,
color: "primary", color: "primary",
@ -617,6 +632,27 @@ function FogTool({
return renderShape(editingShape); return renderShape(editingShape);
} }
function renderDrawingShape(shape: Fog) {
const opacity = editable ? editOpacity : 1;
const stroke =
editable && active
? colors.lightGray
: colors[shape.color] || shape.color;
const fill = new Color(colors[shape.color] || shape.color)
.alpha(opacity)
.string();
return (
<FogShape
fog={shape}
fill={fill}
stroke={stroke}
opacity={opacity}
strokeWidth={gridStrokeWidth * shape.strokeWidth}
hitFunc={() => {}}
/>
);
}
function renderPolygonAcceptTick(shape: Fog) { function renderPolygonAcceptTick(shape: Fog) {
if (shape.data.points.length === 0) { if (shape.data.points.length === 0) {
return null; return null;
@ -681,12 +717,12 @@ function FogTool({
<Group> <Group>
<Group>{fogShapes.map(renderShape)}</Group> <Group>{fogShapes.map(renderShape)}</Group>
{shouldRenderGuides && renderGuides()} {shouldRenderGuides && renderGuides()}
{drawingShape && renderShape(drawingShape)} {drawingShape && renderDrawingShape(drawingShape)}
{drawingShape && {drawingShape &&
toolSettings && toolSettings &&
toolSettings.type === "polygon" && toolSettings.type === "polygon" &&
renderPolygonAcceptTick(drawingShape)} renderPolygonAcceptTick(drawingShape)}
{editingShapes.length > 0 && editingShapes.map(renderEditingShape)} {hoveredShapes.length > 0 && hoveredShapes.map(renderHoveredShape)}
</Group> </Group>
); );
} }

View File

@ -1,7 +1,11 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { Group } from "react-konva"; import { Group } from "react-konva";
import { useInteractionEmitter } from "../../contexts/MapInteractionContext"; import {
useInteractionEmitter,
MapDragEvent,
leftMouseButton,
} from "../../contexts/MapInteractionContext";
import { useMapStage } from "../../contexts/MapStageContext"; import { useMapStage } from "../../contexts/MapStageContext";
import { import {
useGrid, useGrid,
@ -40,8 +44,9 @@ function MeasureTool({ map, active }: MapMeasureProps) {
const gridOffset = useGridOffset(); const gridOffset = useGridOffset();
const mapStageRef = useMapStage(); const mapStageRef = useMapStage();
const [drawingShapeData, setDrawingShapeData] = const [drawingShapeData, setDrawingShapeData] = useState<MeasureData | null>(
useState<MeasureData | null>(null); null
);
const [isBrushDown, setIsBrushDown] = useState(false); const [isBrushDown, setIsBrushDown] = useState(false);
const gridScale = parseGridScale(active ? grid.measurement.scale : null); const gridScale = parseGridScale(active ? grid.measurement.scale : null);
@ -75,7 +80,10 @@ function MeasureTool({ map, active }: MapMeasureProps) {
}); });
} }
function handleBrushDown() { function handleBrushDown(props: MapDragEvent) {
if (!leftMouseButton(props)) {
return;
}
const brushPosition = getBrushPosition(); const brushPosition = getBrushPosition();
if (!brushPosition) { if (!brushPosition) {
return; return;
@ -89,7 +97,10 @@ function MeasureTool({ map, active }: MapMeasureProps) {
setIsBrushDown(true); setIsBrushDown(true);
} }
function handleBrushMove() { function handleBrushMove(props: MapDragEvent) {
if (!leftMouseButton(props)) {
return;
}
const brushPosition = getBrushPosition(); const brushPosition = getBrushPosition();
if (isBrushDown && drawingShapeData && brushPosition && mapImage) { if (isBrushDown && drawingShapeData && brushPosition && mapImage) {
const { points } = getUpdatedShapeData( const { points } = getUpdatedShapeData(
@ -123,7 +134,10 @@ function MeasureTool({ map, active }: MapMeasureProps) {
} }
} }
function handleBrushUp() { function handleBrushUp(props: MapDragEvent) {
if (!leftMouseButton(props)) {
return;
}
setDrawingShapeData(null); setDrawingShapeData(null);
setIsBrushDown(false); setIsBrushDown(false);
} }

View File

@ -3,7 +3,11 @@ import shortid from "shortid";
import { Group } from "react-konva"; import { Group } from "react-konva";
import Konva from "konva"; import Konva from "konva";
import { useInteractionEmitter } from "../../contexts/MapInteractionContext"; import {
useInteractionEmitter,
MapDragEvent,
leftMouseButton,
} from "../../contexts/MapInteractionContext";
import { useMapStage } from "../../contexts/MapStageContext"; import { useMapStage } from "../../contexts/MapStageContext";
import { useUserId } from "../../contexts/UserIdContext"; import { useUserId } from "../../contexts/UserIdContext";
@ -12,7 +16,7 @@ import { getRelativePointerPosition } from "../../helpers/konva";
import useGridSnapping from "../../hooks/useGridSnapping"; import useGridSnapping from "../../hooks/useGridSnapping";
import Note from "../konva/Note"; import BlankNote from "../konva/BlankNote";
import { Map } from "../../types/Map"; import { Map } from "../../types/Map";
import { Note as NoteType } from "../../types/Note"; import { Note as NoteType } from "../../types/Note";
@ -28,20 +32,12 @@ type MapNoteProps = {
active: boolean; active: boolean;
onNoteCreate: NoteCreateEventHander; onNoteCreate: NoteCreateEventHander;
onNoteMenuOpen: NoteMenuOpenEventHandler; onNoteMenuOpen: NoteMenuOpenEventHandler;
children: React.ReactNode;
}; };
function NoteTool({ function NoteTool({ map, active, onNoteCreate, onNoteMenuOpen }: MapNoteProps) {
map,
active,
onNoteCreate,
onNoteMenuOpen,
children,
}: MapNoteProps) {
const interactionEmitter = useInteractionEmitter(); const interactionEmitter = useInteractionEmitter();
const userId = useUserId(); const userId = useUserId();
const mapStageRef = useMapStage(); const mapStageRef = useMapStage();
const [isBrushDown, setIsBrushDown] = useState(false);
const [noteData, setNoteData] = useState<NoteType | null>(null); const [noteData, setNoteData] = useState<NoteType | null>(null);
const creatingNoteRef = useRef<Konva.Group>(null); const creatingNoteRef = useRef<Konva.Group>(null);
@ -72,7 +68,10 @@ function NoteTool({
}); });
} }
function handleBrushDown() { function handleBrushDown(props: MapDragEvent) {
if (!leftMouseButton(props)) {
return;
}
const brushPosition = getBrushPosition(); const brushPosition = getBrushPosition();
if (!brushPosition || !userId) { if (!brushPosition || !userId) {
return; return;
@ -91,10 +90,15 @@ function NoteTool({
textOnly: false, textOnly: false,
rotation: 0, rotation: 0,
}); });
setIsBrushDown(true); if (creatingNoteRef.current) {
creatingNoteRef.current.visible(true);
}
} }
function handleBrushMove() { function handleBrushMove(props: MapDragEvent) {
if (!leftMouseButton(props)) {
return;
}
if (noteData) { if (noteData) {
const brushPosition = getBrushPosition(); const brushPosition = getBrushPosition();
if (!brushPosition) { if (!brushPosition) {
@ -110,17 +114,20 @@ function NoteTool({
y: brushPosition.y, y: brushPosition.y,
}; };
}); });
setIsBrushDown(true);
} }
} }
function handleBrushUp() { function handleBrushUp(props: MapDragEvent) {
if (!leftMouseButton(props)) {
return;
}
if (noteData && creatingNoteRef.current) { if (noteData && creatingNoteRef.current) {
onNoteCreate([noteData]); onNoteCreate([noteData]);
onNoteMenuOpen(noteData.id, creatingNoteRef.current, true); onNoteMenuOpen(noteData.id, creatingNoteRef.current, true);
// Hide creating note tool here as settings noteData to null
// was causing performance issues in FireFox
creatingNoteRef.current.visible(false);
} }
setNoteData(null);
setIsBrushDown(false);
} }
interactionEmitter?.on("dragStart", handleBrushDown); interactionEmitter?.on("dragStart", handleBrushDown);
@ -135,13 +142,8 @@ function NoteTool({
}); });
return ( return (
<Group id="notes"> <Group ref={creatingNoteRef}>
{children} {noteData && <BlankNote note={noteData} />}
<Group ref={creatingNoteRef}>
{isBrushDown && noteData && (
<Note note={noteData} map={map} selected={false} />
)}
</Group>
</Group> </Group>
); );
} }

View File

@ -1,10 +1,13 @@
import { useEffect } from "react"; import { useEffect, useRef } from "react";
import { Group } from "react-konva"; import { Group } from "react-konva";
import { import {
useMapWidth, useMapWidth,
useMapHeight, useMapHeight,
useInteractionEmitter, useInteractionEmitter,
MapDragEvent,
leftMouseButton,
rightMouseButton,
} from "../../contexts/MapInteractionContext"; } from "../../contexts/MapInteractionContext";
import { useMapStage } from "../../contexts/MapStageContext"; import { useMapStage } from "../../contexts/MapStageContext";
import { useGridStrokeWidth } from "../../contexts/GridContext"; import { useGridStrokeWidth } from "../../contexts/GridContext";
@ -41,11 +44,9 @@ function PointerTool({
const gridStrokeWidth = useGridStrokeWidth(); const gridStrokeWidth = useGridStrokeWidth();
const mapStageRef = useMapStage(); const mapStageRef = useMapStage();
useEffect(() => { const brushDownRef = useRef(false);
if (!active) {
return;
}
useEffect(() => {
const mapStage = mapStageRef.current; const mapStage = mapStageRef.current;
function getBrushPosition() { function getBrushPosition() {
@ -56,19 +57,27 @@ function PointerTool({
return getRelativePointerPositionNormalized(mapImage); return getRelativePointerPositionNormalized(mapImage);
} }
function handleBrushDown() { function handleBrushDown(props: MapDragEvent) {
const brushPosition = getBrushPosition(); if ((leftMouseButton(props) && active) || rightMouseButton(props)) {
brushPosition && onPointerDown?.(brushPosition); const brushPosition = getBrushPosition();
brushPosition && onPointerDown?.(brushPosition);
brushDownRef.current = true;
}
} }
function handleBrushMove() { function handleBrushMove() {
const brushPosition = getBrushPosition(); if (brushDownRef.current) {
brushPosition && visible && onPointerMove?.(brushPosition); const brushPosition = getBrushPosition();
brushPosition && visible && onPointerMove?.(brushPosition);
}
} }
function handleBrushUp() { function handleBrushUp() {
const brushPosition = getBrushPosition(); if (brushDownRef.current) {
brushPosition && onPointerUp?.(brushPosition); const brushPosition = getBrushPosition();
brushPosition && onPointerUp?.(brushPosition);
brushDownRef.current = false;
}
} }
interactionEmitter?.on("dragStart", handleBrushDown); interactionEmitter?.on("dragStart", handleBrushDown);

View File

@ -7,6 +7,8 @@ import {
useMapWidth, useMapWidth,
useMapHeight, useMapHeight,
useInteractionEmitter, useInteractionEmitter,
MapDragEvent,
leftMouseButton,
} from "../../contexts/MapInteractionContext"; } from "../../contexts/MapInteractionContext";
import { useMapStage } from "../../contexts/MapStageContext"; import { useMapStage } from "../../contexts/MapStageContext";
@ -96,7 +98,10 @@ function SelectTool({
}); });
} }
function handleBrushDown() { function handleBrushDown(props: MapDragEvent) {
if (!leftMouseButton(props)) {
return;
}
const brushPosition = getBrushPosition(); const brushPosition = getBrushPosition();
if (!brushPosition || preventSelectionRef.current) { if (!brushPosition || preventSelectionRef.current) {
return; return;
@ -121,7 +126,10 @@ function SelectTool({
setIsBrushDown(true); setIsBrushDown(true);
} }
function handleBrushMove() { function handleBrushMove(props: MapDragEvent) {
if (!leftMouseButton(props)) {
return;
}
const brushPosition = getBrushPosition(); const brushPosition = getBrushPosition();
if (!brushPosition || preventSelectionRef.current) { if (!brushPosition || preventSelectionRef.current) {
return; return;
@ -172,7 +180,10 @@ function SelectTool({
} }
} }
function handleBrushUp() { function handleBrushUp(props: MapDragEvent) {
if (!leftMouseButton(props)) {
return;
}
if (preventSelectionRef.current) { if (preventSelectionRef.current) {
return; return;
} }

View File

@ -1,6 +1,22 @@
import React, { useContext } from "react"; import React, { useContext } from "react";
import { EventEmitter } from "stream"; import { FullGestureState } from "react-use-gesture/dist/types";
import useDebounce from "../hooks/useDebounce"; import useDebounce from "../hooks/useDebounce";
import { TypedEmitter } from "tiny-typed-emitter";
import Konva from "konva";
export type MapDragEvent = Omit<FullGestureState<"drag">, "event"> & {
event: React.PointerEvent<Element> | PointerEvent;
};
export type MapDragEventHandler = (props: MapDragEvent) => void;
export interface MapInteractionEvents {
dragStart: MapDragEventHandler;
drag: MapDragEventHandler;
dragEnd: MapDragEventHandler;
}
export class MapInteractionEmitter extends TypedEmitter<MapInteractionEvents> {}
type MapInteraction = { type MapInteraction = {
stageScale: number; stageScale: number;
@ -9,29 +25,33 @@ type MapInteraction = {
setPreventMapInteraction: React.Dispatch<React.SetStateAction<boolean>>; setPreventMapInteraction: React.Dispatch<React.SetStateAction<boolean>>;
mapWidth: number; mapWidth: number;
mapHeight: number; mapHeight: number;
interactionEmitter: EventEmitter | null; interactionEmitter: MapInteractionEmitter | null;
}; };
export const StageScaleContext = export const StageScaleContext = React.createContext<
React.createContext<MapInteraction["stageScale"] | undefined>(undefined); MapInteraction["stageScale"] | undefined
export const DebouncedStageScaleContext = >(undefined);
React.createContext<MapInteraction["stageScale"] | undefined>(undefined); export const DebouncedStageScaleContext = React.createContext<
export const StageWidthContext = MapInteraction["stageScale"] | undefined
React.createContext<MapInteraction["stageWidth"] | undefined>(undefined); >(undefined);
export const StageHeightContext = export const StageWidthContext = React.createContext<
React.createContext<MapInteraction["stageHeight"] | undefined>(undefined); MapInteraction["stageWidth"] | undefined
export const SetPreventMapInteractionContext = >(undefined);
React.createContext<MapInteraction["setPreventMapInteraction"] | undefined>( export const StageHeightContext = React.createContext<
undefined MapInteraction["stageHeight"] | undefined
); >(undefined);
export const MapWidthContext = export const SetPreventMapInteractionContext = React.createContext<
React.createContext<MapInteraction["mapWidth"] | undefined>(undefined); MapInteraction["setPreventMapInteraction"] | undefined
export const MapHeightContext = >(undefined);
React.createContext<MapInteraction["mapHeight"] | undefined>(undefined); export const MapWidthContext = React.createContext<
export const InteractionEmitterContext = MapInteraction["mapWidth"] | undefined
React.createContext<MapInteraction["interactionEmitter"] | undefined>( >(undefined);
undefined export const MapHeightContext = React.createContext<
); MapInteraction["mapHeight"] | undefined
>(undefined);
export const InteractionEmitterContext = React.createContext<
MapInteraction["interactionEmitter"] | undefined
>(undefined);
export function MapInteractionProvider({ export function MapInteractionProvider({
value, value,
@ -152,3 +172,30 @@ export function useDebouncedStageScale() {
} }
return context; return context;
} }
export function leftMouseButton(event: MapDragEvent): boolean;
export function leftMouseButton(
event: Konva.KonvaEventObject<PointerEvent>
): boolean;
export function leftMouseButton(
event: Konva.KonvaEventObject<MouseEvent>
): boolean;
export function leftMouseButton(event: any) {
if (event.evt) {
// Konva events
// Check for undefined (touch) and mouse left click (0)
return event.evt.button === undefined || event.evt.button === 0;
} else {
// Drag event
return event.buttons <= 1;
}
}
export function middleMouseButton(event: MapDragEvent) {
return event.buttons === 4;
}
export function rightMouseButton(event: MapDragEvent) {
return event.buttons === 2;
}

View File

@ -23,10 +23,10 @@ export function PartyProvider({ session, children }: PartyProviderProps) {
} }
} }
session.socket?.on("party_state", handleSocketPartyState); session.socket.on("party_state", handleSocketPartyState);
return () => { return () => {
session.socket?.off("party_state", handleSocketPartyState); session.socket.off("party_state", handleSocketPartyState);
}; };
}); });

View File

@ -9,10 +9,12 @@ import useNetworkedState, {
import Session, { SessionStatus } from "../network/Session"; import Session, { SessionStatus } from "../network/Session";
import { PlayerState } from "../types/PlayerState"; import { PlayerState } from "../types/PlayerState";
export const PlayerStateContext = export const PlayerStateContext = React.createContext<PlayerState | undefined>(
React.createContext<PlayerState | undefined>(undefined); undefined
export const PlayerUpdaterContext = );
React.createContext<SetNetworkedState<PlayerState> | undefined>(undefined); export const PlayerUpdaterContext = React.createContext<
SetNetworkedState<PlayerState> | undefined
>(undefined);
type PlayerProviderProps = { type PlayerProviderProps = {
session: Session; session: Session;
@ -104,13 +106,13 @@ export function PlayerProvider({ session, children }: PlayerProviderProps) {
} }
session.on("status", handleSocketStatus); session.on("status", handleSocketStatus);
session.socket?.on("connect", handleSocketConnect); session.socket.on("connect", handleSocketConnect);
session.socket?.io.on("reconnect", handleSocketConnect); session.socket.io.on("reconnect", handleSocketConnect);
return () => { return () => {
session.off("status", handleSocketStatus); session.off("status", handleSocketStatus);
session.socket?.off("connect", handleSocketConnect); session.socket.off("connect", handleSocketConnect);
session.socket?.io.off("reconnect", handleSocketConnect); session.socket.io.off("reconnect", handleSocketConnect);
}; };
}); });

View File

@ -1,21 +1,23 @@
## General ## General
| Shortcut | Description | | Shortcut | Description |
| ---------------- | ----------------------- | | ------------------- | ----------------------- |
| W | Move Tool | | W | Move Tool |
| Space Bar (Hold) | Move Tool | | Space Bar (Hold) | Move Tool |
| F | Fog Tool | | Middle Mouse Button | Move Tool |
| D | Drawing Tool | | F | Fog Tool |
| M | Measure Tool | | D | Drawing Tool |
| Q | Pointer Tool | | M | Measure Tool |
| N | Note Tool | | Q | Pointer Tool |
| + | Zoom In | | Right Mouse Button | Pointer Tool |
| - | Zoom Out | | N | Note Tool |
| Shift + Zoom | Precision Zoom | | + | Zoom In |
| Ctrl + Z | Undo | | - | Zoom Out |
| Ctrl + Shift + Z | Redo | | Shift + Zoom | Precision Zoom |
| Ctrl (Hold) | Disabled Grid Snapping | | Ctrl + Z | Undo |
| Alt + Drag | Duplicate Token or Note | | Ctrl + Shift + Z | Redo |
| Ctrl (Hold) | Disabled Grid Snapping |
| Alt + Drag | Duplicate Token or Note |
## Select Tool ## Select Tool

View File

@ -1,5 +1,3 @@
![embed:](https://www.youtube.com/embed/Er_grVmqpk0)
Owlbear Rodeo supports a physically simulated 3D dice tray and dice. To access these features click the Show Dice Tray icon in the top left of the map view. Owlbear Rodeo supports a physically simulated 3D dice tray and dice. To access these features click the Show Dice Tray icon in the top left of the map view.
![Open Dice Tray](openDiceTray) ![Open Dice Tray](openDiceTray)

View File

@ -1,5 +1,3 @@
![embed:](https://www.youtube.com/embed/2e07DtB-Xrc)
The Drawing Tool allows you to draw on top of a map. To access the Drawing Tool click the Drawing Tool button in the top right of the map view. The Drawing Tool allows you to draw on top of a map. To access the Drawing Tool click the Drawing Tool button in the top right of the map view.
![Using Drawing](usingDrawing) ![Using Drawing](usingDrawing)

View File

@ -1,3 +1,5 @@
![embed:](https://www.youtube.com/embed/MCOzpQ4auqs)
Once you have a map shared between a party all players can drag tokens from the Token List on the right hand side of the screen. Tokens can then be used to represent players, monsters or any other object that needs to be moved around the map. Once you have a map shared between a party all players can drag tokens from the Token List on the right hand side of the screen. Tokens can then be used to represent players, monsters or any other object that needs to be moved around the map.
## Default Tokens ## Default Tokens

View File

@ -0,0 +1,20 @@
## Minor Changes
This is a small release fixing a few bugs and adding some QoL improvements.
- Updated alternating diagonals measurement to fix inaccuracy with diagonal distances.
- Added support for middle mouse button move shortcut.
- Added support for right mouse button pointer shortcut.
- Optimised note creation to reduce lag when creating a note on FireFox.
- Fixed a bug with using the polygon fog tool on touch devices.
- Fixed a bug that would cause phantom players to be shown in the party view on a server error.
We are currently working on some big changes that will take some time so expect smaller releases like this one until that is ready. If you'd like to keep up with the development of these changes check out our [Patreon](https://patreon.com/owlbearrodeo) where we are sharing previews of what's to come.
[Reddit]()
[Twitter]()
[Patreon]()
---
October 21 2021

View File

@ -325,7 +325,7 @@ export function gridDistance(
const delta = Vector2.abs(Vector2.subtract(aCoord, bCoord)); const delta = Vector2.abs(Vector2.subtract(aCoord, bCoord));
const max = Vector2.componentMax(delta); const max = Vector2.componentMax(delta);
const min = Vector2.componentMin(delta); const min = Vector2.componentMin(delta);
return max - min + Math.floor(1.5 * min); return max + Math.floor(0.5 * min);
} else if (grid.measurement.type === "euclidean") { } else if (grid.measurement.type === "euclidean") {
return Vector2.magnitude( return Vector2.magnitude(
Vector2.divide(Vector2.subtract(a, b), cellSize) Vector2.divide(Vector2.subtract(a, b), cellSize)

View File

@ -1,4 +1,5 @@
import Konva from "konva"; import Konva from "konva";
import { Group } from "react-konva";
import { KonvaEventObject } from "konva/lib/Node"; import { KonvaEventObject } from "konva/lib/Node";
import { useState } from "react"; import { useState } from "react";
import { v4 as uuid } from "uuid"; import { v4 as uuid } from "uuid";
@ -95,12 +96,7 @@ function useMapNotes(
useBlur(handleBlur); useBlur(handleBlur);
const notes = ( const notes = (
<NoteTool <Group id="notes">
map={map}
active={selectedToolId === "note"}
onNoteCreate={onNoteCreate}
onNoteMenuOpen={handleNoteMenuOpen}
>
{(mapState {(mapState
? Object.values(mapState.notes).sort((a, b) => ? Object.values(mapState.notes).sort((a, b) =>
sortNotes(a, b, noteDraggingOptions) sortNotes(a, b, noteDraggingOptions)
@ -129,7 +125,13 @@ function useMapNotes(
} }
/> />
))} ))}
</NoteTool> <NoteTool
map={map}
active={selectedToolId === "note"}
onNoteCreate={onNoteCreate}
onNoteMenuOpen={handleNoteMenuOpen}
/>
</Group>
); );
const noteMenu = ( const noteMenu = (

View File

@ -60,7 +60,7 @@ function useNetworkedState<S extends { readonly [x: string]: any } | null>(
const debouncedState = useDebounce(state, debounceRate); const debouncedState = useDebounce(state, debounceRate);
const lastSyncedStateRef = useRef<S>(); const lastSyncedStateRef = useRef<S>();
useEffect(() => { useEffect(() => {
if (session.socket && dirtyRef.current) { if (dirtyRef.current) {
// If partial updates enabled, send just the changes to the socket // If partial updates enabled, send just the changes to the socket
if ( if (
lastSyncedStateRef.current && lastSyncedStateRef.current &&
@ -112,11 +112,11 @@ function useNetworkedState<S extends { readonly [x: string]: any } | null>(
}); });
} }
session.socket?.on(eventName, handleSocketEvent); session.socket.on(eventName, handleSocketEvent);
session.socket?.on(`${eventName}_update`, handleSocketUpdateEvent); session.socket.on(`${eventName}_update`, handleSocketUpdateEvent);
return () => { return () => {
session.socket?.off(eventName, handleSocketEvent); session.socket.off(eventName, handleSocketEvent);
session.socket?.off(`${eventName}_update`, handleSocketUpdateEvent); session.socket.off(`${eventName}_update`, handleSocketUpdateEvent);
}; };
}, [session.socket, eventName, partialUpdatesKey]); }, [session.socket, eventName, partialUpdatesKey]);

View File

@ -0,0 +1,25 @@
import React, { useEffect } from "react";
function usePreventContextMenu(elementRef: React.RefObject<HTMLElement>) {
useEffect(() => {
// Stop conext menu i.e. right click dialog
function preventContextMenu(event: MouseEvent) {
event.preventDefault();
return false;
}
const element = elementRef.current;
if (element) {
element.addEventListener("contextmenu", preventContextMenu, {
passive: false,
});
}
return () => {
if (element) {
element.removeEventListener("contextmenu", preventContextMenu);
}
};
}, [elementRef]);
}
export default usePreventContextMenu;

View File

@ -67,6 +67,7 @@ function useStageInteraction(
return; return;
} }
const { event, last } = props; const { event, last } = props;
// Prevent double zoom on wheel end
if (!last) { if (!last) {
const { pixelY } = normalizeWheel(event); const { pixelY } = normalizeWheel(event);
@ -178,7 +179,7 @@ function useStageInteraction(
gesture.onDragStart && gesture.onDragStart(props); gesture.onDragStart && gesture.onDragStart(props);
}, },
onDrag: (props) => { onDrag: (props) => {
const { delta, pinching } = props; const { delta, pinching, buttons } = props;
const stage = stageRef.current; const stage = stageRef.current;
if ( if (
preventInteraction || preventInteraction ||
@ -191,7 +192,8 @@ function useStageInteraction(
const [dx, dy] = delta; const [dx, dy] = delta;
const stageTranslate = stageTranslateRef.current; const stageTranslate = stageTranslateRef.current;
if (tool === "move") { // Move with move tool and left click or any mouse button but right click
if ((tool === "move" && buttons < 2) || buttons > 2) {
const newTranslate = { const newTranslate = {
x: stageTranslate.x + dx, x: stageTranslate.x + dx,
y: stageTranslate.y + dy, y: stageTranslate.y + dy,

View File

@ -202,12 +202,12 @@ function NetworkedMapAndTokens({ session }: { session: Session }) {
) { ) {
// Clear map before sending new one // Clear map before sending new one
setCurrentMap(null); setCurrentMap(null);
session.socket?.emit("map", null); session.socket.emit("map", null);
setCurrentMapState(newMapState, true, true); setCurrentMapState(newMapState, true, true);
setCurrentMap(newMap); setCurrentMap(newMap);
session.socket?.emit("map", newMap); session.socket.emit("map", newMap);
if (!newMap || !newMapState) { if (!newMap || !newMapState) {
setAssetManifest(null, true, true); setAssetManifest(null, true, true);
@ -395,12 +395,12 @@ function NetworkedMapAndTokens({ session }: { session: Session }) {
session.on("peerData", handlePeerData); session.on("peerData", handlePeerData);
session.on("peerDataProgress", handlePeerDataProgress); session.on("peerDataProgress", handlePeerDataProgress);
session.socket?.on("map", handleSocketMap); session.socket.on("map", handleSocketMap);
return () => { return () => {
session.off("peerData", handlePeerData); session.off("peerData", handlePeerData);
session.off("peerDataProgress", handlePeerDataProgress); session.off("peerDataProgress", handlePeerDataProgress);
session.socket?.off("map", handleSocketMap); session.socket.off("map", handleSocketMap);
}; };
}); });

View File

@ -74,7 +74,7 @@ function NetworkedMapPointer({ session, active }: NetworkedMapPointerProps) {
sessionRef.current && sessionRef.current &&
sessionRef.current.socket sessionRef.current.socket
) { ) {
sessionRef.current.socket.emit( sessionRef.current.socket.volatile.emit(
"player_pointer", "player_pointer",
ownPointerUpdateRef.current ownPointerUpdateRef.current
); );
@ -152,10 +152,10 @@ function NetworkedMapPointer({ session, active }: NetworkedMapPointerProps) {
} }
} }
session.socket?.on("player_pointer", handleSocketPlayerPointer); session.socket.on("player_pointer", handleSocketPlayerPointer);
return () => { return () => {
session.socket?.off("player_pointer", handleSocketPlayerPointer); session.socket.off("player_pointer", handleSocketPlayerPointer);
}; };
}, [session]); }, [session]);

View File

@ -1,4 +1,4 @@
import io, { Socket } from "socket.io-client"; import io from "socket.io-client";
import msgParser from "socket.io-msgpack-parser"; import msgParser from "socket.io-msgpack-parser";
import { EventEmitter } from "events"; import { EventEmitter } from "events";
@ -32,10 +32,11 @@ export type PeerReply = (id: string, data: PeerData, chunkId?: string) => void;
class Session extends EventEmitter { class Session extends EventEmitter {
/** /**
* The socket io connection * The socket io connection
*
* @type {io.Socket}
*/ */
socket?: Socket; socket = io(process.env.REACT_APP_BROKER_URL!, {
withCredentials: true,
parser: msgParser,
});
/** /**
* A mapping of socket ids to session peers * A mapping of socket ids to session peers
@ -45,7 +46,7 @@ class Session extends EventEmitter {
peers: Record<string, SessionPeer>; peers: Record<string, SessionPeer>;
get id() { get id() {
return this.socket?.id || ""; return this.socket.id;
} }
_iceServers: RTCIceServer[] = []; _iceServers: RTCIceServer[] = [];
@ -76,14 +77,6 @@ class Session extends EventEmitter {
const data = await response.json(); const data = await response.json();
this._iceServers = data.iceServers; this._iceServers = data.iceServers;
if (!process.env.REACT_APP_BROKER_URL) {
return;
}
this.socket = io(process.env.REACT_APP_BROKER_URL, {
withCredentials: true,
parser: msgParser,
});
this.socket.on("player_joined", this._handlePlayerJoined.bind(this)); this.socket.on("player_joined", this._handlePlayerJoined.bind(this));
this.socket.on("player_left", this._handlePlayerLeft.bind(this)); this.socket.on("player_left", this._handlePlayerLeft.bind(this));
this.socket.on("joined_game", this._handleJoinedGame.bind(this)); this.socket.on("joined_game", this._handleJoinedGame.bind(this));
@ -95,14 +88,14 @@ class Session extends EventEmitter {
this.socket.on("force_update", this._handleForceUpdate.bind(this)); this.socket.on("force_update", this._handleForceUpdate.bind(this));
this.emit("status", "ready"); this.emit("status", "ready");
} catch (error) { } catch (error: any) {
logError(error); logError(error);
this.emit("status", "offline"); this.emit("status", "offline");
} }
} }
disconnect() { disconnect() {
this.socket?.disconnect(); this.socket.disconnect();
} }
/** /**
@ -191,7 +184,7 @@ class Session extends EventEmitter {
this._gameId = gameId; this._gameId = gameId;
this._password = password; this._password = password;
this.socket?.emit( this.socket.emit(
"join_game", "join_game",
gameId, gameId,
password, password,
@ -224,7 +217,7 @@ class Session extends EventEmitter {
}; };
const handleSignal = (signal: SignalData) => { const handleSignal = (signal: SignalData) => {
this.socket?.emit("signal", JSON.stringify({ to: peer.id, signal })); this.socket.emit("signal", JSON.stringify({ to: peer.id, signal }));
}; };
const handleConnect = () => { const handleConnect = () => {
@ -309,7 +302,7 @@ class Session extends EventEmitter {
this.peers[id] = peer; this.peers[id] = peer;
return true; return true;
} catch (error) { } catch (error: any) {
logError(error); logError(error);
this.emit("peerError", { error }); this.emit("peerError", { error });
for (let peer of Object.values(this.peers)) { for (let peer of Object.values(this.peers)) {
@ -367,13 +360,14 @@ class Session extends EventEmitter {
} }
_handleSocketReconnect() { _handleSocketReconnect() {
this.socket.sendBuffer = [];
if (this._gameId) { if (this._gameId) {
this.joinGame(this._gameId, this._password); this.joinGame(this._gameId, this._password);
} }
} }
_handleForceUpdate() { _handleForceUpdate() {
this.socket?.disconnect(); this.socket.disconnect();
this.emit("status", "needs_update"); this.emit("status", "needs_update");
} }
} }

View File

@ -27,6 +27,7 @@ const v180 = raw("../docs/releaseNotes/v1.8.0.md");
const v181 = raw("../docs/releaseNotes/v1.8.1.md"); const v181 = raw("../docs/releaseNotes/v1.8.1.md");
const v190 = raw("../docs/releaseNotes/v1.9.0.md"); const v190 = raw("../docs/releaseNotes/v1.9.0.md");
const v1100 = raw("../docs/releaseNotes/v1.10.0.md"); const v1100 = raw("../docs/releaseNotes/v1.10.0.md");
const v1101 = raw("../docs/releaseNotes/v1.10.1.md");
function ReleaseNotes() { function ReleaseNotes() {
const location = useLocation(); const location = useLocation();
@ -51,6 +52,11 @@ 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="v1101">
<Accordion heading="v1.10.1" defaultOpen>
<Markdown source={v1101} />
</Accordion>
</div>
<div id="v1100"> <div id="v1100">
<Accordion heading="v1.10.0" defaultOpen> <Accordion heading="v1.10.0" defaultOpen>
<Markdown source={v1100} /> <Markdown source={v1100} />

View File

@ -13181,6 +13181,11 @@ tiny-invariant@^1.0.2:
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875" resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875"
integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw== integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==
tiny-typed-emitter@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz#b3b027fdd389ff81a152c8e847ee2f5be9fad7b5"
integrity sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==
tiny-warning@^1.0.0, tiny-warning@^1.0.3: tiny-warning@^1.0.0, tiny-warning@^1.0.3:
version "1.0.3" version "1.0.3"
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"