commit
876810de33
@ -1,5 +1,5 @@
|
|||||||
REACT_APP_BROKER_URL=https://rocket.owlbear.rodeo
|
REACT_APP_BROKER_URL=https://stage.owlbear.rodeo
|
||||||
REACT_APP_ICE_SERVERS_URL=https://rocket.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
|
||||||
|
66
package.json
66
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "owlbear-rodeo",
|
"name": "owlbear-rodeo",
|
||||||
"version": "1.10.1.1",
|
"version": "1.10.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babylonjs/core": "4.2.1",
|
"@babylonjs/core": "4.2.1",
|
||||||
@ -8,30 +8,30 @@
|
|||||||
"@dnd-kit/core": "^3.1.1",
|
"@dnd-kit/core": "^3.1.1",
|
||||||
"@dnd-kit/sortable": "^4.0.0",
|
"@dnd-kit/sortable": "^4.0.0",
|
||||||
"@mitchemmc/dexie-export-import": "^1.0.1",
|
"@mitchemmc/dexie-export-import": "^1.0.1",
|
||||||
"@msgpack/msgpack": "^2.7.0",
|
"@msgpack/msgpack": "^2.7.2",
|
||||||
"@react-spring/konva": "^9.2.4",
|
"@react-spring/konva": "9.4.4",
|
||||||
"@sentry/integrations": "^6.11.0",
|
"@sentry/integrations": "^6.19.3",
|
||||||
"@sentry/react": "^6.11.0",
|
"@sentry/react": "^6.19.3",
|
||||||
"@stripe/stripe-js": "^1.16.0",
|
"@stripe/stripe-js": "^1.26.0",
|
||||||
"@tensorflow/tfjs": "^3.8.0",
|
"@tensorflow/tfjs": "^3.15.0",
|
||||||
"@testing-library/jest-dom": "^5.11.9",
|
"@testing-library/jest-dom": "^5.16.3",
|
||||||
"@testing-library/react": "^11.2.5",
|
"@testing-library/react": "^13.0.0",
|
||||||
"@testing-library/user-event": "^13.0.2",
|
"@testing-library/user-event": "^14.0.3",
|
||||||
"ajv": "^8.6.2",
|
"ajv": "^8.6.2",
|
||||||
"ammo.js": "kripken/ammo.js#85c0614cd5338aa0843814fe7bbd2df48164ece7",
|
"ammo.js": "kripken/ammo.js#85c0614cd5338aa0843814fe7bbd2df48164ece7",
|
||||||
"case": "^1.6.3",
|
"case": "^1.6.3",
|
||||||
"color": "^3.2.1",
|
"color": "^3.2.1",
|
||||||
"comlink": "^4.3.1",
|
"comlink": "^4.3.1",
|
||||||
"deep-diff": "^1.0.2",
|
"deep-diff": "^1.0.2",
|
||||||
"dexie": "^3.2.0-beta.3",
|
"dexie": "^3.2.1",
|
||||||
"dexie-react-hooks": "^1.0.7",
|
"dexie-react-hooks": "^1.0.7",
|
||||||
"err-code": "^3.0.1",
|
"err-code": "^3.0.1",
|
||||||
"fake-indexeddb": "^3.1.3",
|
"fake-indexeddb": "^3.1.7",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"fuse.js": "^6.4.6",
|
"fuse.js": "^6.5.3",
|
||||||
"image-outline": "^0.1.0",
|
"image-outline": "^0.1.0",
|
||||||
"intersection-observer": "^0.12.0",
|
"intersection-observer": "^0.12.0",
|
||||||
"konva": "^8.3.1",
|
"konva": "8.3.5",
|
||||||
"lodash.chunk": "^4.2.0",
|
"lodash.chunk": "^4.2.0",
|
||||||
"lodash.clonedeep": "^4.5.0",
|
"lodash.clonedeep": "^4.5.0",
|
||||||
"lodash.get": "^4.4.2",
|
"lodash.get": "^4.4.2",
|
||||||
@ -40,37 +40,37 @@
|
|||||||
"normalize-wheel": "^1.0.1",
|
"normalize-wheel": "^1.0.1",
|
||||||
"pepjs": "^0.5.3",
|
"pepjs": "^0.5.3",
|
||||||
"polygon-clipping": "^0.15.3",
|
"polygon-clipping": "^0.15.3",
|
||||||
"pretty-bytes": "^5.6.0",
|
"pretty-bytes": "^6.0.0",
|
||||||
"raw.macro": "^0.4.2",
|
"raw.macro": "^0.4.2",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-intersection-observer": "^8.32.0",
|
"react-intersection-observer": "8.33.1",
|
||||||
"react-konva": "^17.0.2-5",
|
"react-konva": "^17.0.2-6",
|
||||||
"react-konva-utils": "^0.1.7",
|
"react-konva-utils": "^0.2.0",
|
||||||
"react-markdown": "4",
|
"react-markdown": "4",
|
||||||
"react-media": "^2.0.0-rc.1",
|
"react-media": "^2.0.0-rc.1",
|
||||||
"react-modal": "^3.14.3",
|
"react-modal": "^3.14.4",
|
||||||
"react-resize-detector": "^6.7.4",
|
"react-resize-detector": "^7.0.0",
|
||||||
"react-router-dom": "^5.1.2",
|
"react-router-dom": "^5.1.2",
|
||||||
"react-router-hash-link": "^2.4.3",
|
"react-router-hash-link": "^2.4.3",
|
||||||
"react-scripts": "^4.0.3",
|
"react-scripts": "^4.0.3",
|
||||||
"react-select": "^4.3.1",
|
"react-select": "^5.2.2",
|
||||||
"react-spring": "^9.2.4",
|
"react-spring": "9.4.4",
|
||||||
"react-textarea-autosize": "^8.3.3",
|
"react-textarea-autosize": "^8.3.3",
|
||||||
"react-toast-notifications": "^2.5.1",
|
"react-toast-notifications": "^2.5.1",
|
||||||
"react-use-gesture": "^9.1.3",
|
"react-use-gesture": "^9.1.3",
|
||||||
"shortid": "^2.2.15",
|
"shortid": "^2.2.15",
|
||||||
"simple-peer": "^9.11.0",
|
"simple-peer": "^9.11.1",
|
||||||
"simplebar-react": "^2.3.5",
|
"simplebar-react": "^2.3.6",
|
||||||
"simplify-js": "^1.2.4",
|
"simplify-js": "^1.2.4",
|
||||||
"socket.io-client": "^4.1.3",
|
"socket.io-client": "^4.4.1",
|
||||||
"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",
|
"tiny-typed-emitter": "^2.1.0",
|
||||||
"use-image": "^1.0.8",
|
"use-image": "1.0.10",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"webrtc-adapter": "^8.1.0"
|
"webrtc-adapter": "^8.1.1"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"simple-peer/get-browser-rtc": "substack/get-browser-rtc#4/head"
|
"simple-peer/get-browser-rtc": "substack/get-browser-rtc#4/head"
|
||||||
@ -100,8 +100,8 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/color": "^3.0.1",
|
"@types/color": "^3.0.1",
|
||||||
"@types/deep-diff": "^1.0.0",
|
"@types/deep-diff": "^1.0.0",
|
||||||
"@types/file-saver": "^2.0.2",
|
"@types/file-saver": "^2.0.5",
|
||||||
"@types/jest": "^26.0.23",
|
"@types/jest": "^27.4.1",
|
||||||
"@types/lodash.chunk": "^4.2.6",
|
"@types/lodash.chunk": "^4.2.6",
|
||||||
"@types/lodash.clonedeep": "^4.5.6",
|
"@types/lodash.clonedeep": "^4.5.6",
|
||||||
"@types/lodash.get": "^4.4.6",
|
"@types/lodash.get": "^4.4.6",
|
||||||
@ -110,12 +110,12 @@
|
|||||||
"@types/normalize-wheel": "^1.0.0",
|
"@types/normalize-wheel": "^1.0.0",
|
||||||
"@types/react": "^17.0.6",
|
"@types/react": "^17.0.6",
|
||||||
"@types/react-dom": "^17.0.5",
|
"@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-router-dom": "^5.1.7",
|
||||||
"@types/react-select": "^4.0.17",
|
"@types/react-select": "^5.0.1",
|
||||||
"@types/shortid": "^0.0.29",
|
"@types/shortid": "^0.0.29",
|
||||||
"@types/simple-peer": "^9.11.1",
|
"@types/simple-peer": "^9.11.4",
|
||||||
"@types/uuid": "^8.3.1",
|
"@types/uuid": "^8.3.4",
|
||||||
"typescript": "^4.2.4",
|
"typescript": "^4.2.4",
|
||||||
"worker-loader": "^3.0.8"
|
"worker-loader": "^3.0.8"
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,27 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Box, Text } from "theme-ui";
|
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 (
|
return (
|
||||||
<Box
|
<Box
|
||||||
m={2}
|
m={2}
|
||||||
mb={0}
|
mb={0}
|
||||||
bg="overlay"
|
bg={appearance ? getToastAppearance(appearance) : "overlay"}
|
||||||
sx={{ borderRadius: "4px", padding: "12px 16px" }}
|
sx={{ borderRadius: "4px", padding: "12px 16px" }}
|
||||||
>
|
>
|
||||||
<Text as="p" variant="body2">
|
<Text as="p" variant="body2">
|
||||||
|
@ -20,6 +20,7 @@ import usePreventTouch from "../../hooks/usePreventTouch";
|
|||||||
|
|
||||||
import ErrorBanner from "../banner/ErrorBanner";
|
import ErrorBanner from "../banner/ErrorBanner";
|
||||||
import { AbstractMesh } from "@babylonjs/core/Meshes/abstractMesh";
|
import { AbstractMesh } from "@babylonjs/core/Meshes/abstractMesh";
|
||||||
|
import { isSafari } from "../../helpers/shared";
|
||||||
|
|
||||||
const diceThrowSpeed = 2;
|
const diceThrowSpeed = 2;
|
||||||
|
|
||||||
@ -57,7 +58,9 @@ function DiceInteraction({
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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,
|
preserveDrawingBuffer: true,
|
||||||
stencil: true,
|
stencil: true,
|
||||||
// Prevent XR from loading as Safari 15 crashes with this enabled
|
// Prevent XR from loading as Safari 15 crashes with this enabled
|
||||||
@ -108,7 +111,9 @@ function DiceInteraction({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setError(error);
|
if (error instanceof Error) {
|
||||||
|
setError(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [onSceneMount]);
|
}, [onSceneMount]);
|
||||||
|
|
||||||
|
@ -325,7 +325,7 @@ function FogTool({
|
|||||||
setDrawingShape(null);
|
setDrawingShape(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
eraseHoveredShapes();
|
eraseHoveredShapes(props);
|
||||||
|
|
||||||
setIsBrushDown(false);
|
setIsBrushDown(false);
|
||||||
}
|
}
|
||||||
@ -566,7 +566,10 @@ function FogTool({
|
|||||||
});
|
});
|
||||||
}, [toolSettings.useFogCut]);
|
}, [toolSettings.useFogCut]);
|
||||||
|
|
||||||
function eraseHoveredShapes() {
|
function eraseHoveredShapes(event: any) {
|
||||||
|
if (!leftMouseButton(event)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Erase
|
// Erase
|
||||||
if (hoveredShapes.length > 0) {
|
if (hoveredShapes.length > 0) {
|
||||||
if (toolSettings.type === "remove") {
|
if (toolSettings.type === "remove") {
|
||||||
@ -605,9 +608,14 @@ function FogTool({
|
|||||||
<FogShape
|
<FogShape
|
||||||
key={shape.id}
|
key={shape.id}
|
||||||
fog={shape}
|
fog={shape}
|
||||||
onMouseMove={() => handleShapeOver(shape, isBrushDown)}
|
onMouseMove={(e: Konva.KonvaEventObject<MouseEvent>) =>
|
||||||
|
(!isBrushDown || leftMouseButton(e)) &&
|
||||||
|
handleShapeOver(shape, isBrushDown)
|
||||||
|
}
|
||||||
onTouchOver={() => 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)}
|
onTouchStart={() => handleShapeOver(shape, true)}
|
||||||
onMouseUp={eraseHoveredShapes}
|
onMouseUp={eraseHoveredShapes}
|
||||||
onTouchEnd={eraseHoveredShapes}
|
onTouchEnd={eraseHoveredShapes}
|
||||||
|
20
src/docs/releaseNotes/v1.10.2.md
Normal file
20
src/docs/releaseNotes/v1.10.2.md
Normal 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
|
@ -562,7 +562,9 @@ export async function getGridSizeFromImage(image: HTMLImageElement) {
|
|||||||
try {
|
try {
|
||||||
prediction = await gridSizeML(image, candidates);
|
prediction = await gridSizeML(image, candidates);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error);
|
if (error instanceof Error) {
|
||||||
|
logError(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!prediction) {
|
if (!prediction) {
|
||||||
|
@ -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 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[]) {
|
export function shuffle<Type>(array: Type[]) {
|
||||||
let temp = [...array];
|
let temp = [...array];
|
||||||
var currentIndex = temp.length,
|
var currentIndex = temp.length,
|
||||||
|
@ -67,10 +67,12 @@ function useImageDrop(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.message === "Failed to fetch") {
|
if (e instanceof Error) {
|
||||||
addToast("Unable to import image: failed to fetch");
|
if (e.message === "Failed to fetch") {
|
||||||
} else {
|
addToast("Unable to import image: failed to fetch");
|
||||||
addToast("Unable to import image");
|
} else {
|
||||||
|
addToast("Unable to import image");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,20 +55,40 @@ function ImportExportModal({
|
|||||||
const { addToast } = useToasts();
|
const { addToast } = useToasts();
|
||||||
function addSuccessToast(
|
function addSuccessToast(
|
||||||
message: string,
|
message: string,
|
||||||
maps: SelectData[],
|
maps: number,
|
||||||
tokens: SelectData[]
|
tokens: number
|
||||||
) {
|
) {
|
||||||
const mapText = `${maps.length} map${maps.length > 1 ? "s" : ""}`;
|
const mapText = `${maps} map${maps > 1 ? "s" : ""}`;
|
||||||
const tokenText = `${tokens.length} token${tokens.length > 1 ? "s" : ""}`;
|
const tokenText = `${tokens} token${tokens > 1 ? "s" : ""}`;
|
||||||
if (maps.length > 0 && tokens.length > 0) {
|
if (maps > 0 && tokens > 0) {
|
||||||
addToast(`${message} ${mapText} and ${tokenText}`);
|
addToast(`${message} ${mapText} and ${tokenText}`);
|
||||||
} else if (maps.length > 0) {
|
} else if (maps > 0) {
|
||||||
addToast(`${message} ${mapText}`);
|
addToast(`${message} ${mapText}`);
|
||||||
} else if (tokens.length > 0) {
|
} else if (tokens > 0) {
|
||||||
addToast(`${message} ${tokenText}`);
|
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() {
|
function openFileDialog() {
|
||||||
if (fileInputRef.current) {
|
if (fileInputRef.current) {
|
||||||
fileInputRef.current.click();
|
fileInputRef.current.click();
|
||||||
@ -103,15 +123,17 @@ function ImportExportModal({
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
backgroundTaskRunningRef.current = false;
|
backgroundTaskRunningRef.current = false;
|
||||||
if (e.message.startsWith("Max buffer length exceeded")) {
|
if (e instanceof (Error)) {
|
||||||
setError(
|
if (e.message.startsWith("Max buffer length exceeded")) {
|
||||||
new Error(
|
setError(
|
||||||
"Max image size exceeded ensure your database doesn't have an image over 100MB"
|
new Error(
|
||||||
)
|
"Max image size exceeded ensure your database doesn't have an image over 100MB"
|
||||||
);
|
)
|
||||||
} else {
|
);
|
||||||
console.error(e);
|
} else {
|
||||||
setError(e);
|
console.error(e);
|
||||||
|
setError(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Set file input to null to allow adding the same data 2 times in a row
|
// Set file input to null to allow adding the same data 2 times in a row
|
||||||
@ -170,34 +192,49 @@ function ImportExportModal({
|
|||||||
let newTokenIds: Record<string, string> = {};
|
let newTokenIds: Record<string, string> = {};
|
||||||
// Mapping of old asset ids to new asset ids
|
// Mapping of old asset ids to new asset ids
|
||||||
let newAssetIds: Record<string, string> = {};
|
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
|
// Mapping of old maps ids to new map ids
|
||||||
let newMapIds: Record<string, string> = {};
|
let newMapIds: Record<string, string> = {};
|
||||||
|
|
||||||
let newTokens: Token[] = [];
|
let newTokens: Token[] = [];
|
||||||
if (checkedTokens.length > 0) {
|
if (checkedTokens.length > 0) {
|
||||||
const tokenIds = checkedTokens.map((token) => token.id);
|
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);
|
||||||
for (let token of tokensToAdd) {
|
|
||||||
// Generate new ids
|
|
||||||
const newId = uuid();
|
|
||||||
newTokenIds[token.id] = newId;
|
|
||||||
|
|
||||||
if (token.type === "default") {
|
if (tokensToAdd) {
|
||||||
newTokens.push({ ...token, id: newId, owner: userId });
|
for (let token of tokensToAdd) {
|
||||||
} else {
|
if (token) {
|
||||||
const newFileId = uuid();
|
// Generate new ids
|
||||||
const newThumbnailId = uuid();
|
const newId = uuid();
|
||||||
newAssetIds[token.file] = newFileId;
|
newTokenIds[token.id] = newId;
|
||||||
newAssetIds[token.thumbnail] = newThumbnailId;
|
|
||||||
|
|
||||||
// Change ids and owner
|
if (token.type === "default") {
|
||||||
newTokens.push({
|
if (userId) {
|
||||||
...token,
|
newTokens.push({ ...token, id: newId, owner: userId });
|
||||||
id: newId,
|
}
|
||||||
owner: userId,
|
} else {
|
||||||
file: newFileId,
|
const newFileId = uuid();
|
||||||
thumbnail: newThumbnailId,
|
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,
|
||||||
|
owner: userId,
|
||||||
|
file: newFileId,
|
||||||
|
thumbnail: newThumbnailId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -207,70 +244,128 @@ function ImportExportModal({
|
|||||||
if (checkedMaps.length > 0) {
|
if (checkedMaps.length > 0) {
|
||||||
const mapIds = checkedMaps.map((map) => map.id);
|
const mapIds = checkedMaps.map((map) => map.id);
|
||||||
const mapsToAdd = await importDB.table("maps").bulkGet(mapIds);
|
const mapsToAdd = await importDB.table("maps").bulkGet(mapIds);
|
||||||
for (let map of mapsToAdd) {
|
if (mapsToAdd) {
|
||||||
let state: MapState = await importDB.table("states").get(map.id);
|
|
||||||
// Apply new token ids to imported state
|
for (let map of mapsToAdd) {
|
||||||
for (let tokenState of Object.values(state.tokens)) {
|
if (map) {
|
||||||
if (tokenState.tokenId in newTokenIds) {
|
let state: MapState = await importDB.table("states").get(map.id);
|
||||||
tokenState.tokenId = newTokenIds[tokenState.tokenId];
|
// Apply new token ids to imported state
|
||||||
}
|
for (let tokenState of Object.values(state.tokens)) {
|
||||||
// Change token state file asset id
|
if (tokenState.tokenId in newTokenIds) {
|
||||||
if (tokenState.type === "file" && tokenState.file in newAssetIds) {
|
tokenState.tokenId = newTokenIds[tokenState.tokenId];
|
||||||
tokenState.file = newAssetIds[tokenState.file];
|
}
|
||||||
}
|
// Change token state file asset id
|
||||||
// Change token state owner if owned by the user of the map
|
if (tokenState.type === "file" && tokenState.file in newAssetIds) {
|
||||||
if (tokenState.owner === map.owner && userId) {
|
tokenState.file = newAssetIds[tokenState.file];
|
||||||
tokenState.owner = userId;
|
}
|
||||||
|
// Change token state owner if owned by the user of the map
|
||||||
|
if (tokenState.owner === map.owner && userId) {
|
||||||
|
tokenState.owner = userId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Generate new ids
|
||||||
|
const newId = uuid();
|
||||||
|
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,
|
||||||
|
id: newId,
|
||||||
|
owner: userId,
|
||||||
|
file: newFileId,
|
||||||
|
thumbnail: newThumbnailId,
|
||||||
|
resolutions: newResolutionIds,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newStates.push({ ...state, mapId: newId });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Generate new ids
|
|
||||||
const newId = uuid();
|
|
||||||
newMapIds[map.id] = newId;
|
|
||||||
|
|
||||||
if (map.type === "default") {
|
|
||||||
newMaps.push({ ...map, id: newId, owner: userId });
|
|
||||||
} else {
|
|
||||||
const newFileId = uuid();
|
|
||||||
const newThumbnailId = uuid();
|
|
||||||
newAssetIds[map.file] = newFileId;
|
|
||||||
newAssetIds[map.thumbnail] = newThumbnailId;
|
|
||||||
const newResolutionIds: Record<string, string> = {};
|
|
||||||
for (let res of Object.keys(map.resolutions)) {
|
|
||||||
newResolutionIds[res] = uuid();
|
|
||||||
newAssetIds[map.resolutions[res]] = newResolutionIds[res];
|
|
||||||
}
|
|
||||||
// Change ids and owner
|
|
||||||
newMaps.push({
|
|
||||||
...map,
|
|
||||||
id: newId,
|
|
||||||
owner: userId,
|
|
||||||
file: newFileId,
|
|
||||||
thumbnail: newThumbnailId,
|
|
||||||
resolutions: newResolutionIds,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
newStates.push({ ...state, mapId: newId });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add assets with new ids
|
// Add assets with new ids
|
||||||
const assetsToAdd = await importDB
|
const assetsToAdd: Asset[] | undefined = await importDB
|
||||||
.table("assets")
|
.table("assets")
|
||||||
.bulkGet(Object.keys(newAssetIds));
|
.bulkGet(Object.keys(newAssetIds));
|
||||||
let newAssets: Asset[] = [];
|
let newAssets: Asset[] = [];
|
||||||
for (let asset of assetsToAdd) {
|
const processedAssetIds: string[] = []
|
||||||
if (asset) {
|
if (assetsToAdd) {
|
||||||
newAssets.push({
|
for (let asset of assetsToAdd) {
|
||||||
...asset,
|
if (asset && userId) {
|
||||||
id: newAssetIds[asset.id],
|
newAssets.push({
|
||||||
owner: userId,
|
...asset,
|
||||||
});
|
id: newAssetIds[asset.id],
|
||||||
} else {
|
owner: userId,
|
||||||
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
|
// Add map groups with new ids
|
||||||
let newMapGroups: Group[] = [];
|
let newMapGroups: Group[] = [];
|
||||||
if (checkedMapGroups.length > 0) {
|
if (checkedMapGroups.length > 0) {
|
||||||
@ -320,32 +415,34 @@ function ImportExportModal({
|
|||||||
],
|
],
|
||||||
async () => {
|
async () => {
|
||||||
if (newTokens.length > 0) {
|
if (newTokens.length > 0) {
|
||||||
await db.table("tokens").bulkAdd(newTokens);
|
await db.table<Token>("tokens").bulkAdd(newTokens);
|
||||||
}
|
}
|
||||||
if (newMaps.length > 0) {
|
if (newMaps.length > 0) {
|
||||||
await db.table("maps").bulkAdd(newMaps);
|
await db.table<Map>("maps").bulkAdd(newMaps);
|
||||||
}
|
}
|
||||||
if (newStates.length > 0) {
|
if (newStates.length > 0) {
|
||||||
await db.table("states").bulkAdd(newStates);
|
await db.table("states").bulkAdd(newStates);
|
||||||
}
|
}
|
||||||
if (newAssets.length > 0) {
|
if (newAssets.length > 0) {
|
||||||
await db.table("assets").bulkAdd(newAssets);
|
await db.table<Asset>("assets").bulkAdd(newAssets);
|
||||||
}
|
}
|
||||||
if (newMapGroups.length > 0) {
|
if (newMapGroups.length > 0) {
|
||||||
const mapGroup = await db.table("groups").get("maps");
|
const mapGroup = await db.table("groups").get("maps");
|
||||||
await db
|
await db
|
||||||
.table("groups")
|
.table<Group>("groups")
|
||||||
.update("maps", { items: [...newMapGroups, ...mapGroup.items] });
|
.update("maps", { items: [...newMapGroups, ...mapGroup.items] });
|
||||||
}
|
}
|
||||||
if (newTokenGroups.length > 0) {
|
if (newTokenGroups.length > 0) {
|
||||||
const tokenGroup = await db.table("groups").get("tokens");
|
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],
|
items: [...newTokenGroups, ...tokenGroup.items],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
addSuccessToast("Imported", checkedMaps, checkedTokens);
|
const totalImportedMaps = checkedMaps.length - unprocessedMaps
|
||||||
|
const totalImportedTokens = checkedTokens.length - unprocessedTokens
|
||||||
|
addSuccessToast("Imported", totalImportedMaps, totalImportedTokens);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
if (e instanceof MissingAssetError) {
|
if (e instanceof MissingAssetError) {
|
||||||
@ -392,10 +489,12 @@ function ImportExportModal({
|
|||||||
);
|
);
|
||||||
const blob = new Blob([buffer]);
|
const blob = new Blob([buffer]);
|
||||||
saveAs(blob, `${shortid.generate()}.owlbear`);
|
saveAs(blob, `${shortid.generate()}.owlbear`);
|
||||||
addSuccessToast("Exported", checkedMaps, checkedTokens);
|
addSuccessToast("Exported", checkedMaps.length, checkedTokens.length);
|
||||||
} catch (e) {
|
} catch (e: unknown) {
|
||||||
console.error(e);
|
if (e instanceof (Error)) {
|
||||||
setError(e);
|
console.error(e);
|
||||||
|
setError(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
backgroundTaskRunningRef.current = false;
|
backgroundTaskRunningRef.current = false;
|
||||||
|
@ -1,5 +1,14 @@
|
|||||||
import { useState, useEffect } from "react";
|
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 { useHistory } from "react-router-dom";
|
||||||
|
|
||||||
import Footer from "../components/Footer";
|
import Footer from "../components/Footer";
|
||||||
@ -56,6 +65,18 @@ function Home() {
|
|||||||
Owlbear Rodeo
|
Owlbear Rodeo
|
||||||
</Text>
|
</Text>
|
||||||
<Image src={owlington} m={2} />
|
<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
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
m={2}
|
m={2}
|
||||||
|
@ -28,6 +28,7 @@ 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");
|
const v1101 = raw("../docs/releaseNotes/v1.10.1.md");
|
||||||
|
const v1102 = raw("../docs/releaseNotes/v1.10.2.md");
|
||||||
|
|
||||||
function ReleaseNotes() {
|
function ReleaseNotes() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@ -52,13 +53,18 @@ 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="v1102">
|
||||||
|
<Accordion heading="v1.10.2" defaultOpen>
|
||||||
|
<Markdown source={v1102} />
|
||||||
|
</Accordion>
|
||||||
|
</div>
|
||||||
<div id="v1101">
|
<div id="v1101">
|
||||||
<Accordion heading="v1.10.1" defaultOpen>
|
<Accordion heading="v1.10.1" defaultOpen={location.hash === "#v1101"}>
|
||||||
<Markdown source={v1101} />
|
<Markdown source={v1101} />
|
||||||
</Accordion>
|
</Accordion>
|
||||||
</div>
|
</div>
|
||||||
<div id="v1100">
|
<div id="v1100">
|
||||||
<Accordion heading="v1.10.0" defaultOpen>
|
<Accordion heading="v1.10.0" defaultOpen={location.hash === "#v1100"}>
|
||||||
<Markdown source={v1100} />
|
<Markdown source={v1100} />
|
||||||
</Accordion>
|
</Accordion>
|
||||||
</div>
|
</div>
|
||||||
|
@ -12,6 +12,7 @@ import blobToBuffer from "../helpers/blobToBuffer";
|
|||||||
|
|
||||||
import { Map } from "../types/Map";
|
import { Map } from "../types/Map";
|
||||||
import { Token } from "../types/Token";
|
import { Token } from "../types/Token";
|
||||||
|
import { Asset } from "../types/Asset";
|
||||||
|
|
||||||
type ProgressCallback = (progress: ExportProgress) => boolean;
|
type ProgressCallback = (progress: ExportProgress) => boolean;
|
||||||
|
|
||||||
@ -34,7 +35,7 @@ let service = {
|
|||||||
// Load entire table
|
// Load entire table
|
||||||
let items: T[] = [];
|
let items: T[] = [];
|
||||||
// Use a cursor instead of toArray to prevent IPC max size error
|
// 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);
|
items.push(item);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -77,7 +78,7 @@ let service = {
|
|||||||
let db = getDatabase({});
|
let db = getDatabase({});
|
||||||
|
|
||||||
// Add assets for selected maps and tokens
|
// Add assets for selected maps and tokens
|
||||||
const maps = await db
|
const maps: Map[] = await db
|
||||||
.table<Map>("maps")
|
.table<Map>("maps")
|
||||||
.where("id")
|
.where("id")
|
||||||
.anyOf(mapIds)
|
.anyOf(mapIds)
|
||||||
@ -210,7 +211,7 @@ let service = {
|
|||||||
.table("assets")
|
.table("assets")
|
||||||
.where("owner")
|
.where("owner")
|
||||||
.notEqual(userId)
|
.notEqual(userId)
|
||||||
.each((asset) => {
|
.each((asset: Asset) => {
|
||||||
assetSizes.push({ id: asset.id, size: asset.file.byteLength });
|
assetSizes.push({ id: asset.id, size: asset.file.byteLength });
|
||||||
});
|
});
|
||||||
const totalSize = assetSizes.reduce((acc, cur) => acc + cur.size, 0);
|
const totalSize = assetSizes.reduce((acc, cur) => acc + cur.size, 0);
|
||||||
|
Loading…
Reference in New Issue
Block a user