Merge pull request #62 from mitchemmc/release/v1.10.2

Release/v1.10.2
This commit is contained in:
Nicola Thouliss 2022-04-27 14:55:02 +10:00 committed by GitHub
commit 876810de33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 776 additions and 517 deletions

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "owlbear-rodeo",
"version": "1.10.1.1",
"version": "1.10.2",
"private": true,
"dependencies": {
"@babylonjs/core": "4.2.1",
@ -8,30 +8,30 @@
"@dnd-kit/core": "^3.1.1",
"@dnd-kit/sortable": "^4.0.0",
"@mitchemmc/dexie-export-import": "^1.0.1",
"@msgpack/msgpack": "^2.7.0",
"@react-spring/konva": "^9.2.4",
"@sentry/integrations": "^6.11.0",
"@sentry/react": "^6.11.0",
"@stripe/stripe-js": "^1.16.0",
"@tensorflow/tfjs": "^3.8.0",
"@testing-library/jest-dom": "^5.11.9",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.0.2",
"@msgpack/msgpack": "^2.7.2",
"@react-spring/konva": "9.4.4",
"@sentry/integrations": "^6.19.3",
"@sentry/react": "^6.19.3",
"@stripe/stripe-js": "^1.26.0",
"@tensorflow/tfjs": "^3.15.0",
"@testing-library/jest-dom": "^5.16.3",
"@testing-library/react": "^13.0.0",
"@testing-library/user-event": "^14.0.3",
"ajv": "^8.6.2",
"ammo.js": "kripken/ammo.js#85c0614cd5338aa0843814fe7bbd2df48164ece7",
"case": "^1.6.3",
"color": "^3.2.1",
"comlink": "^4.3.1",
"deep-diff": "^1.0.2",
"dexie": "^3.2.0-beta.3",
"dexie": "^3.2.1",
"dexie-react-hooks": "^1.0.7",
"err-code": "^3.0.1",
"fake-indexeddb": "^3.1.3",
"fake-indexeddb": "^3.1.7",
"file-saver": "^2.0.5",
"fuse.js": "^6.4.6",
"fuse.js": "^6.5.3",
"image-outline": "^0.1.0",
"intersection-observer": "^0.12.0",
"konva": "^8.3.1",
"konva": "8.3.5",
"lodash.chunk": "^4.2.0",
"lodash.clonedeep": "^4.5.0",
"lodash.get": "^4.4.2",
@ -40,37 +40,37 @@
"normalize-wheel": "^1.0.1",
"pepjs": "^0.5.3",
"polygon-clipping": "^0.15.3",
"pretty-bytes": "^5.6.0",
"pretty-bytes": "^6.0.0",
"raw.macro": "^0.4.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-intersection-observer": "^8.32.0",
"react-konva": "^17.0.2-5",
"react-konva-utils": "^0.1.7",
"react-intersection-observer": "8.33.1",
"react-konva": "^17.0.2-6",
"react-konva-utils": "^0.2.0",
"react-markdown": "4",
"react-media": "^2.0.0-rc.1",
"react-modal": "^3.14.3",
"react-resize-detector": "^6.7.4",
"react-modal": "^3.14.4",
"react-resize-detector": "^7.0.0",
"react-router-dom": "^5.1.2",
"react-router-hash-link": "^2.4.3",
"react-scripts": "^4.0.3",
"react-select": "^4.3.1",
"react-spring": "^9.2.4",
"react-select": "^5.2.2",
"react-spring": "9.4.4",
"react-textarea-autosize": "^8.3.3",
"react-toast-notifications": "^2.5.1",
"react-use-gesture": "^9.1.3",
"shortid": "^2.2.15",
"simple-peer": "^9.11.0",
"simplebar-react": "^2.3.5",
"simple-peer": "^9.11.1",
"simplebar-react": "^2.3.6",
"simplify-js": "^1.2.4",
"socket.io-client": "^4.1.3",
"socket.io-client": "^4.4.1",
"socket.io-msgpack-parser": "^3.0.1",
"source-map-explorer": "^2.5.2",
"theme-ui": "^0.10.0",
"tiny-typed-emitter": "^2.1.0",
"use-image": "^1.0.8",
"use-image": "1.0.10",
"uuid": "^8.3.2",
"webrtc-adapter": "^8.1.0"
"webrtc-adapter": "^8.1.1"
},
"resolutions": {
"simple-peer/get-browser-rtc": "substack/get-browser-rtc#4/head"
@ -100,8 +100,8 @@
"devDependencies": {
"@types/color": "^3.0.1",
"@types/deep-diff": "^1.0.0",
"@types/file-saver": "^2.0.2",
"@types/jest": "^26.0.23",
"@types/file-saver": "^2.0.5",
"@types/jest": "^27.4.1",
"@types/lodash.chunk": "^4.2.6",
"@types/lodash.clonedeep": "^4.5.6",
"@types/lodash.get": "^4.4.6",
@ -110,12 +110,12 @@
"@types/normalize-wheel": "^1.0.0",
"@types/react": "^17.0.6",
"@types/react-dom": "^17.0.5",
"@types/react-modal": "^3.12.0",
"@types/react-modal": "^3.13.1",
"@types/react-router-dom": "^5.1.7",
"@types/react-select": "^4.0.17",
"@types/react-select": "^5.0.1",
"@types/shortid": "^0.0.29",
"@types/simple-peer": "^9.11.1",
"@types/uuid": "^8.3.1",
"@types/simple-peer": "^9.11.4",
"@types/uuid": "^8.3.4",
"typescript": "^4.2.4",
"worker-loader": "^3.0.8"
}

View File

@ -1,13 +1,27 @@
import React from "react";
import { Box, Text } from "theme-ui";
import { ToastProvider as DefaultToastProvider } from "react-toast-notifications";
import { AppearanceTypes, ToastProvider as DefaultToastProvider } from "react-toast-notifications";
function CustomToast({ children }: { children?: React.ReactNode }) {
function getToastAppearance(appearance: AppearanceTypes) {
let colour = "overlay"
if (appearance === "error") {
colour = "highlight"
} else if (appearance === "info") {
colour = "overlay"
} else if (appearance === "warning") {
colour = "secondary"
} else if (appearance === "success") {
colour = "primary"
}
return colour;
}
function CustomToast({ appearance, children }: { appearance: AppearanceTypes, children: React.ReactNode }) {
return (
<Box
m={2}
mb={0}
bg="overlay"
bg={appearance ? getToastAppearance(appearance) : "overlay"}
sx={{ borderRadius: "4px", padding: "12px 16px" }}
>
<Text as="p" variant="body2">

View File

@ -20,6 +20,7 @@ import usePreventTouch from "../../hooks/usePreventTouch";
import ErrorBanner from "../banner/ErrorBanner";
import { AbstractMesh } from "@babylonjs/core/Meshes/abstractMesh";
import { isSafari } from "../../helpers/shared";
const diceThrowSpeed = 2;
@ -57,7 +58,9 @@ function DiceInteraction({
}
try {
const engine = new Engine(canvas, true, {
// iOS 15.4 introduced a bug - animation trails ar visible on dice in Safari. Have to disable antialiasing on Safari
// https://forum.babylonjs.com/t/render-loop-issue-on-iphone-11-unwanted-animation-trails/29742
const engine = new Engine(canvas, !isSafari, {
preserveDrawingBuffer: true,
stencil: true,
// Prevent XR from loading as Safari 15 crashes with this enabled
@ -108,8 +111,10 @@ function DiceInteraction({
}
});
} catch (error) {
if (error instanceof Error) {
setError(error);
}
}
}, [onSceneMount]);
const selectedMeshRef = useRef<AbstractMesh | null>(null);

View File

@ -325,7 +325,7 @@ function FogTool({
setDrawingShape(null);
}
eraseHoveredShapes();
eraseHoveredShapes(props);
setIsBrushDown(false);
}
@ -566,7 +566,10 @@ function FogTool({
});
}, [toolSettings.useFogCut]);
function eraseHoveredShapes() {
function eraseHoveredShapes(event: any) {
if (!leftMouseButton(event)) {
return;
}
// Erase
if (hoveredShapes.length > 0) {
if (toolSettings.type === "remove") {
@ -605,9 +608,14 @@ function FogTool({
<FogShape
key={shape.id}
fog={shape}
onMouseMove={() => handleShapeOver(shape, isBrushDown)}
onMouseMove={(e: Konva.KonvaEventObject<MouseEvent>) =>
(!isBrushDown || leftMouseButton(e)) &&
handleShapeOver(shape, isBrushDown)
}
onTouchOver={() => handleShapeOver(shape, isBrushDown)}
onMouseDown={() => handleShapeOver(shape, true)}
onMouseDown={(e: Konva.KonvaEventObject<MouseEvent>) =>
leftMouseButton(e) && handleShapeOver(shape, true)
}
onTouchStart={() => handleShapeOver(shape, true)}
onMouseUp={eraseHoveredShapes}
onTouchEnd={eraseHoveredShapes}

View File

@ -0,0 +1,20 @@
## Minor Changes
This is a small release just adding some QoL improvements and updating dependencies.
- Updated most package dependencies
- Added functionality to allow imports that contain corrupted assests. Previously, having one corrupt asset would cause the import to fail. Now you should be notified if any assets were unable to be imported
- Added message about our new blog on home page
- Fixed bug that was causing animation trails on dice in Safari iOS. Unfortunately, this fix means dice appear in lower quality on Safari browsers
- Fixed toggle fog being triggered with middle mouse drag
We are currently working on some big changes that will take some time so continue to expect only minor changes. 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. We also have a [blog](https://blog.owlbear.rodeo/) that will release updates about a month later than our Patreon
[Reddit](https://www.reddit.com/r/OwlbearRodeo/comments/qco76o/beta_v1101_released_bug_fixes/)
[Twitter](https://twitter.com/OwlbearRodeo/status/1451123265246691330)
[Patreon](https://www.patreon.com/posts/57673962)
[Owlbear Rodeo Blog](https://blog.owlbear.rodeo/)
---
April 27 2022

View File

@ -562,8 +562,10 @@ export async function getGridSizeFromImage(image: HTMLImageElement) {
try {
prediction = await gridSizeML(image, candidates);
} catch (error) {
if (error instanceof Error) {
logError(error);
}
}
if (!prediction) {
prediction = gridSizeHeuristic(image, candidates);

View File

@ -86,6 +86,8 @@ export function groupBy(array: Record<PropertyKey, any>[], key: string) {
export const isMacLike = /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform);
export const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
export function shuffle<Type>(array: Type[]) {
let temp = [...array];
var currentIndex = temp.length,

View File

@ -67,6 +67,7 @@ function useImageDrop(
}
}
} catch (e) {
if (e instanceof Error) {
if (e.message === "Failed to fetch") {
addToast("Unable to import image: failed to fetch");
} else {
@ -74,6 +75,7 @@ function useImageDrop(
}
}
}
}
const files = event.dataTransfer?.files || [];
for (let file of files) {

View File

@ -55,20 +55,40 @@ function ImportExportModal({
const { addToast } = useToasts();
function addSuccessToast(
message: string,
maps: SelectData[],
tokens: SelectData[]
maps: number,
tokens: number
) {
const mapText = `${maps.length} map${maps.length > 1 ? "s" : ""}`;
const tokenText = `${tokens.length} token${tokens.length > 1 ? "s" : ""}`;
if (maps.length > 0 && tokens.length > 0) {
const mapText = `${maps} map${maps > 1 ? "s" : ""}`;
const tokenText = `${tokens} token${tokens > 1 ? "s" : ""}`;
if (maps > 0 && tokens > 0) {
addToast(`${message} ${mapText} and ${tokenText}`);
} else if (maps.length > 0) {
} else if (maps > 0) {
addToast(`${message} ${mapText}`);
} else if (tokens.length > 0) {
} else if (tokens > 0) {
addToast(`${message} ${tokenText}`);
}
}
function addWarningToast(
message: string,
items: string[],
) {
let text = "";
if (items.length > 0) {
if (items.length === 1) {
text += `${items[0]}`
} else {
for (let item in items) {
text += `${items[item]}, `
}
text = text.replace(/,\s*$/, "");
}
}
const toastMessage = <span>{message} <b>{text}</b></span>
addToast(toastMessage, { appearance: "warning", autoDismiss: true });
}
function openFileDialog() {
if (fileInputRef.current) {
fileInputRef.current.click();
@ -103,6 +123,7 @@ function ImportExportModal({
} catch (e) {
setIsLoading(false);
backgroundTaskRunningRef.current = false;
if (e instanceof (Error)) {
if (e.message.startsWith("Max buffer length exceeded")) {
setError(
new Error(
@ -114,6 +135,7 @@ function ImportExportModal({
setError(e);
}
}
}
// Set file input to null to allow adding the same data 2 times in a row
if (fileInputRef.current) {
fileInputRef.current.value = "";
@ -170,27 +192,39 @@ function ImportExportModal({
let newTokenIds: Record<string, string> = {};
// Mapping of old asset ids to new asset ids
let newAssetIds: Record<string, string> = {};
// Mapping of old asset ids to old maps
let oldAssetIds: Record<string, { itemName: string, itemId: string, item: "map" | "token", assetType: "file" | "thumbnail" | "resolution", newId: string }> = {};
// Mapping of old maps ids to new map ids
let newMapIds: Record<string, string> = {};
let newTokens: Token[] = [];
if (checkedTokens.length > 0) {
const tokenIds = checkedTokens.map((token) => token.id);
const tokensToAdd = await importDB.table("tokens").bulkGet(tokenIds);
const tokensToAdd: Token[] | undefined = await importDB.table("tokens").bulkGet(tokenIds);
if (tokensToAdd) {
for (let token of tokensToAdd) {
if (token) {
// Generate new ids
const newId = uuid();
newTokenIds[token.id] = newId;
if (token.type === "default") {
if (userId) {
newTokens.push({ ...token, id: newId, owner: userId });
}
} else {
const newFileId = uuid();
const newThumbnailId = uuid();
newAssetIds[token.file] = newFileId;
newAssetIds[token.thumbnail] = newThumbnailId;
oldAssetIds[token.file] = { itemName: token.name, itemId: token.id, item: "token", assetType: "file", newId: newId };
oldAssetIds[token.thumbnail] = { itemName: token.name, itemId: token.id, item: "token", assetType: "thumbnail", newId: newId };
// Change ids and owner
if (userId) {
newTokens.push({
...token,
id: newId,
@ -201,13 +235,19 @@ function ImportExportModal({
}
}
}
}
}
}
let newMaps: Map[] = [];
let newStates: MapState[] = [];
if (checkedMaps.length > 0) {
const mapIds = checkedMaps.map((map) => map.id);
const mapsToAdd = await importDB.table("maps").bulkGet(mapIds);
if (mapsToAdd) {
for (let map of mapsToAdd) {
if (map) {
let state: MapState = await importDB.table("states").get(map.id);
// Apply new token ids to imported state
for (let tokenState of Object.values(state.tokens)) {
@ -228,17 +268,26 @@ function ImportExportModal({
newMapIds[map.id] = newId;
if (map.type === "default") {
if (userId) {
newMaps.push({ ...map, id: newId, owner: userId });
}
} else {
const newFileId = uuid();
const newThumbnailId = uuid();
newAssetIds[map.file] = newFileId;
newAssetIds[map.thumbnail] = newThumbnailId;
oldAssetIds[map.file] = { itemName: map.name, itemId: map.id, item: "map", assetType: "file", newId: newId };
oldAssetIds[map.thumbnail] = { itemName: map.name, itemId: map.id, item: "map", assetType: "thumbnail", newId: newId };
const newResolutionIds: Record<string, string> = {};
for (let res of Object.keys(map.resolutions)) {
newResolutionIds[res] = uuid();
newAssetIds[map.resolutions[res]] = newResolutionIds[res];
oldAssetIds[map.resolutions[res]] = { itemName: map.name, itemId: map.id, item: "map", assetType: "resolution", newId: newId };
}
if (userId) {
// Change ids and owner
newMaps.push({
...map,
@ -249,27 +298,73 @@ function ImportExportModal({
resolutions: newResolutionIds,
});
}
}
newStates.push({ ...state, mapId: newId });
}
}
}
}
// Add assets with new ids
const assetsToAdd = await importDB
const assetsToAdd: Asset[] | undefined = await importDB
.table("assets")
.bulkGet(Object.keys(newAssetIds));
let newAssets: Asset[] = [];
const processedAssetIds: string[] = []
if (assetsToAdd) {
for (let asset of assetsToAdd) {
if (asset) {
if (asset && userId) {
newAssets.push({
...asset,
id: newAssetIds[asset.id],
owner: userId,
});
} else {
throw new MissingAssetError("Import missing assets");
processedAssetIds.push(asset.id)
}
}
}
// compare items added to newAssetIds against those that were processed
const unprocessedAssets = Object.keys(newAssetIds).filter(item => processedAssetIds.indexOf(item) < 0);
let unprocessedMaps = 0
let unprocessedTokens = 0
// check if there are any items that have been unprocessed
if (unprocessedAssets.length > 0) {
const unprocessedItems: { id: string, name: string }[] = []
for (let item of unprocessedAssets) {
// get information of unprocessed item from oldAssetIds list
let unprocessedItem = oldAssetIds[item]
// should only remove corrupted asset once (one map can have multiple unprocessed assets)
if (!!!(unprocessedItems.some(value => value.id === unprocessedItem.itemId))) {
unprocessedItems.push({ id: unprocessedItem.itemId, name: unprocessedItem.itemName })
if (unprocessedItem.item === "map") {
unprocessedMaps += 1
// remove corrupt map from newMaps list -> otherwise corrupt data will be imported
const index = newMaps.findIndex(map => map.id === unprocessedItem.newId)
if (index !== -1) {
newMaps.splice(index, 1)
}
const stateIndex = newStates.findIndex(state => state.mapId === unprocessedItem.newId)
if (stateIndex !== -1) {
newStates.splice(stateIndex, 1)
}
} else if (unprocessedItem.item === "token") {
unprocessedTokens += 1
const index = newTokens.findIndex(token => token.id === unprocessedItem.newId)
if (index !== -1) {
newTokens.splice(index, 1)
}
}
}
}
const unprocessedItemNames = unprocessedItems.map(item => item.name)
addWarningToast("Could not import item(s)", unprocessedItemNames)
}
// Add map groups with new ids
let newMapGroups: Group[] = [];
@ -320,32 +415,34 @@ function ImportExportModal({
],
async () => {
if (newTokens.length > 0) {
await db.table("tokens").bulkAdd(newTokens);
await db.table<Token>("tokens").bulkAdd(newTokens);
}
if (newMaps.length > 0) {
await db.table("maps").bulkAdd(newMaps);
await db.table<Map>("maps").bulkAdd(newMaps);
}
if (newStates.length > 0) {
await db.table("states").bulkAdd(newStates);
}
if (newAssets.length > 0) {
await db.table("assets").bulkAdd(newAssets);
await db.table<Asset>("assets").bulkAdd(newAssets);
}
if (newMapGroups.length > 0) {
const mapGroup = await db.table("groups").get("maps");
await db
.table("groups")
.table<Group>("groups")
.update("maps", { items: [...newMapGroups, ...mapGroup.items] });
}
if (newTokenGroups.length > 0) {
const tokenGroup = await db.table("groups").get("tokens");
await db.table("groups").update("tokens", {
await db.table<Group>("groups").update("tokens", {
items: [...newTokenGroups, ...tokenGroup.items],
});
}
}
);
addSuccessToast("Imported", checkedMaps, checkedTokens);
const totalImportedMaps = checkedMaps.length - unprocessedMaps
const totalImportedTokens = checkedTokens.length - unprocessedTokens
addSuccessToast("Imported", totalImportedMaps, totalImportedTokens);
} catch (e) {
console.error(e);
if (e instanceof MissingAssetError) {
@ -392,11 +489,13 @@ function ImportExportModal({
);
const blob = new Blob([buffer]);
saveAs(blob, `${shortid.generate()}.owlbear`);
addSuccessToast("Exported", checkedMaps, checkedTokens);
} catch (e) {
addSuccessToast("Exported", checkedMaps.length, checkedTokens.length);
} catch (e: unknown) {
if (e instanceof (Error)) {
console.error(e);
setError(e);
}
}
setIsLoading(false);
backgroundTaskRunningRef.current = false;
}

View File

@ -1,5 +1,14 @@
import { useState, useEffect } from "react";
import { Flex, Button, Image, Text, IconButton, Link } from "theme-ui";
import {
Flex,
Button,
Image,
Text,
IconButton,
Link,
Message,
Paragraph,
} from "theme-ui";
import { useHistory } from "react-router-dom";
import Footer from "../components/Footer";
@ -56,6 +65,18 @@ function Home() {
Owlbear Rodeo
</Text>
<Image src={owlington} m={2} />
<Message mb={4}>
<Paragraph
sx={{
fontSize: "12px",
fontFamily:
"system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',sans-serif",
}}
>
Check out our new <Link href="https://blog.owlbear.rodeo/">blog</Link>{" "}
for all the news on the next version of Owlbear Rodeo
</Paragraph>
</Message>
<Button
variant="secondary"
m={2}

View File

@ -28,6 +28,7 @@ const v181 = raw("../docs/releaseNotes/v1.8.1.md");
const v190 = raw("../docs/releaseNotes/v1.9.0.md");
const v1100 = raw("../docs/releaseNotes/v1.10.0.md");
const v1101 = raw("../docs/releaseNotes/v1.10.1.md");
const v1102 = raw("../docs/releaseNotes/v1.10.2.md");
function ReleaseNotes() {
const location = useLocation();
@ -52,13 +53,18 @@ function ReleaseNotes() {
<Text mb={2} variant="heading" as="h1" sx={{ fontSize: 5 }}>
Release Notes
</Text>
<div id="v1102">
<Accordion heading="v1.10.2" defaultOpen>
<Markdown source={v1102} />
</Accordion>
</div>
<div id="v1101">
<Accordion heading="v1.10.1" defaultOpen>
<Accordion heading="v1.10.1" defaultOpen={location.hash === "#v1101"}>
<Markdown source={v1101} />
</Accordion>
</div>
<div id="v1100">
<Accordion heading="v1.10.0" defaultOpen>
<Accordion heading="v1.10.0" defaultOpen={location.hash === "#v1100"}>
<Markdown source={v1100} />
</Accordion>
</div>

View File

@ -12,6 +12,7 @@ import blobToBuffer from "../helpers/blobToBuffer";
import { Map } from "../types/Map";
import { Token } from "../types/Token";
import { Asset } from "../types/Asset";
type ProgressCallback = (progress: ExportProgress) => boolean;
@ -34,7 +35,7 @@ let service = {
// Load entire table
let items: T[] = [];
// Use a cursor instead of toArray to prevent IPC max size error
await db.table(table).each((item) => {
await db.table(table).each((item: any) => {
items.push(item);
});
@ -77,7 +78,7 @@ let service = {
let db = getDatabase({});
// Add assets for selected maps and tokens
const maps = await db
const maps: Map[] = await db
.table<Map>("maps")
.where("id")
.anyOf(mapIds)
@ -210,7 +211,7 @@ let service = {
.table("assets")
.where("owner")
.notEqual(userId)
.each((asset) => {
.each((asset: Asset) => {
assetSizes.push({ id: asset.id, size: asset.file.byteLength });
});
const totalSize = assetSizes.reduce((acc, cur) => acc + cur.size, 0);

807
yarn.lock

File diff suppressed because it is too large Load Diff