diff --git a/.env.production b/.env.production index 4d18c3b..b3a7971 100644 --- a/.env.production +++ b/.env.production @@ -6,4 +6,4 @@ REACT_APP_VERSION=$npm_package_version REACT_APP_PREVIEW=false REACT_APP_LOGGING=true REACT_APP_FATHOM_SITE_ID=VMSHBPKD -REACT_APP_SENTRY_DSN=https://5257021c3a114649baa5e3b8ba775bfe@o467475.ingest.sentry.io/5493956 +REACT_APP_SENTRY_DSN=https://d6d22c5233b54c4d91df8fa29d5ffeb0@o467475.ingest.sentry.io/5493956 diff --git a/package.json b/package.json index 6f426c6..28677fc 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,16 @@ { "name": "owlbear-rodeo", - "version": "1.8.1", + "version": "1.9.0", "private": true, "dependencies": { "@babylonjs/core": "^4.2.0", "@babylonjs/loaders": "^4.2.0", + "@dnd-kit/core": "^3.0.4", + "@dnd-kit/sortable": "^3.1.0", "@mitchemmc/dexie-export-import": "^1.0.1", "@msgpack/msgpack": "^2.4.1", - "@sentry/react": "^6.2.2", + "@sentry/integrations": "^6.3.0", + "@sentry/react": "^6.3.0", "@stripe/stripe-js": "^1.13.1", "@tensorflow/tfjs": "^3.6.0", "@testing-library/jest-dom": "^5.11.9", @@ -18,17 +21,20 @@ "color": "^3.1.3", "comlink": "^4.3.0", "deep-diff": "^1.0.2", - "dexie": "^3.0.3", - "dexie-observable": "^3.0.0-beta.10", + "dexie": "3.1.0-beta.13", + "dexie-react-hooks": "^1.0.6", "err-code": "^3.0.1", "fake-indexeddb": "^3.1.2", "file-saver": "^2.0.5", "fuse.js": "^6.4.6", - "interactjs": "^1.10.8", + "image-outline": "^0.1.0", + "intersection-observer": "^0.12.0", "konva": "^7.2.5", + "lodash.chunk": "^4.2.0", "lodash.clonedeep": "^4.5.0", "lodash.get": "^4.4.2", "lodash.set": "^4.3.2", + "lodash.unset": "^4.5.2", "normalize-wheel": "^1.0.1", "pepjs": "^0.5.3", "polygon-clipping": "^0.15.2", @@ -36,6 +42,7 @@ "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-3", "react-markdown": "4", "react-media": "^2.0.0-rc.1", @@ -46,6 +53,7 @@ "react-scripts": "^4.0.3", "react-select": "^4.2.1", "react-spring": "^8.0.27", + "react-textarea-autosize": "^8.3.3", "react-toast-notifications": "^2.4.3", "react-use-gesture": "^9.1.3", "shortid": "^2.2.15", @@ -57,6 +65,7 @@ "source-map-explorer": "^2.5.2", "theme-ui": "^0.8.4", "use-image": "^1.0.7", + "uuid": "^8.3.2", "webrtc-adapter": "^7.7.1" }, "resolutions": { diff --git a/src/App.tsx b/src/App.tsx index 0a0427f..94eb780 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -11,63 +11,54 @@ import HowTo from "./routes/HowTo"; import Donate from "./routes/Donate"; import { AuthProvider } from "./contexts/AuthContext"; -import { DatabaseProvider } from "./contexts/DatabaseContext"; -import { MapDataProvider } from "./contexts/MapDataContext"; -import { TokenDataProvider } from "./contexts/TokenDataContext"; -import { MapLoadingProvider } from "./contexts/MapLoadingContext"; import { SettingsProvider } from "./contexts/SettingsContext"; import { KeyboardProvider } from "./contexts/KeyboardContext"; -import { ImageSourcesProvider } from "./contexts/ImageSourceContext"; +import { DatabaseProvider } from "./contexts/DatabaseContext"; +import { UserIdProvider } from "./contexts/UserIdContext"; import { ToastProvider } from "./components/Toast"; function App() { return ( - - - - - - - - - - - - {/* Legacy support camel case routes */} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + {/* Legacy support camel case routes */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); } diff --git a/src/actions/CutShapeAction.js b/src/actions/CutShapeAction.js index 534b941..59688e6 100644 --- a/src/actions/CutShapeAction.js +++ b/src/actions/CutShapeAction.js @@ -4,27 +4,25 @@ import Action from "./Action"; import { addPolygonDifferenceToShapes, addPolygonIntersectionToShapes, + shapeToGeometry, } from "../helpers/actions"; class CutShapeAction extends Action { constructor(shapes) { super(); this.update = (shapesById) => { - const actionGeom = shapes.map((actionShape) => [ - actionShape.data.points.map(({ x, y }) => [x, y]), - ]); + let actionGeom = shapes.map(shapeToGeometry); let cutShapes = {}; for (let shape of Object.values(shapesById)) { - const shapePoints = shape.data.points.map(({ x, y }) => [x, y]); - const shapeHoles = shape.data.holes.map((hole) => - hole.map(({ x, y }) => [x, y]) - ); - let shapeGeom = [[shapePoints, ...shapeHoles]]; + const shapeGeom = shapeToGeometry(shape); try { - const difference = polygonClipping.difference(shapeGeom, actionGeom); + const difference = polygonClipping.difference( + shapeGeom, + ...actionGeom + ); const intersection = polygonClipping.intersection( shapeGeom, - actionGeom + ...actionGeom ); addPolygonDifferenceToShapes(shape, difference, cutShapes); addPolygonIntersectionToShapes(shape, intersection, cutShapes); diff --git a/src/actions/SubtractShapeAction.js b/src/actions/SubtractShapeAction.js index 712bbff..13f915a 100644 --- a/src/actions/SubtractShapeAction.js +++ b/src/actions/SubtractShapeAction.js @@ -1,24 +1,24 @@ import polygonClipping from "polygon-clipping"; import Action from "./Action"; -import { addPolygonDifferenceToShapes } from "../helpers/actions"; +import { + addPolygonDifferenceToShapes, + shapeToGeometry, +} from "../helpers/actions"; class SubtractShapeAction extends Action { constructor(shapes) { super(); this.update = (shapesById) => { - const actionGeom = shapes.map((actionShape) => [ - actionShape.data.points.map(({ x, y }) => [x, y]), - ]); + const actionGeom = shapes.map(shapeToGeometry); let subtractedShapes = {}; for (let shape of Object.values(shapesById)) { - const shapePoints = shape.data.points.map(({ x, y }) => [x, y]); - const shapeHoles = shape.data.holes.map((hole) => - hole.map(({ x, y }) => [x, y]) - ); - let shapeGeom = [[shapePoints, ...shapeHoles]]; + const shapeGeom = shapeToGeometry(shape); try { - const difference = polygonClipping.difference(shapeGeom, actionGeom); + const difference = polygonClipping.difference( + shapeGeom, + ...actionGeom + ); addPolygonDifferenceToShapes(shape, difference, subtractedShapes); } catch { console.error("Unable to find difference for shapes"); diff --git a/src/actions/index.js b/src/actions/index.js index 7f3489d..7822cc1 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -4,43 +4,6 @@ import EditShapeAction from "./EditShapeAction"; import RemoveShapeAction from "./RemoveShapeAction"; import SubtractShapeAction from "./SubtractShapeAction"; -/** - * Convert from the previous representation of actions (1.7.0) to the new representation (1.8.0) - * and combine into shapes - * @param {Array} actions - * @param {number} actionIndex - */ -export function convertOldActionsToShapes(actions, actionIndex) { - let newShapes = {}; - for (let i = 0; i <= actionIndex; i++) { - const action = actions[i]; - if (!action) { - continue; - } - let newAction; - if (action.shapes) { - if (action.type === "add") { - newAction = new AddShapeAction(action.shapes); - } else if (action.type === "edit") { - newAction = new EditShapeAction(action.shapes); - } else if (action.type === "remove") { - newAction = new RemoveShapeAction(action.shapes); - } else if (action.type === "subtract") { - newAction = new SubtractShapeAction(action.shapes); - } else if (action.type === "cut") { - newAction = new CutShapeAction(action.shapes); - } - } else if (action.type === "remove" && action.shapeIds) { - newAction = new RemoveShapeAction(action.shapeIds); - } - - if (newAction) { - newShapes = newAction.execute(newShapes); - } - } - return newShapes; -} - export { AddShapeAction, CutShapeAction, diff --git a/src/components/ImageDrop.js b/src/components/ImageDrop.js deleted file mode 100644 index 4cbc74b..0000000 --- a/src/components/ImageDrop.js +++ /dev/null @@ -1,86 +0,0 @@ -import React, { useState } from "react"; -import { Box, Flex, Text } from "theme-ui"; - -function ImageDrop({ onDrop, dropText, children }) { - const [dragging, setDragging] = useState(false); - function handleImageDragEnter(event) { - event.preventDefault(); - event.stopPropagation(); - setDragging(true); - } - - function handleImageDragLeave(event) { - event.preventDefault(); - event.stopPropagation(); - setDragging(false); - } - - async function handleImageDrop(event) { - event.preventDefault(); - event.stopPropagation(); - let imageFiles = []; - - // Check if the dropped image is from a URL - const html = event.dataTransfer.getData("text/html"); - if (html) { - try { - const urlMatch = html.match(/src="?([^"\s]+)"?\s*/); - const url = urlMatch[1].replace("&", "&"); // Reverse html encoding of url parameters - let name = ""; - const altMatch = html.match(/alt="?([^"]+)"?\s*/); - if (altMatch && altMatch.length > 1) { - name = altMatch[1]; - } - const response = await fetch(url); - if (response.ok) { - const file = await response.blob(); - file.name = name; - imageFiles.push(file); - } - } catch {} - } - - const files = event.dataTransfer.files; - for (let file of files) { - if (file.type.startsWith("image")) { - imageFiles.push(file); - } - } - onDrop(imageFiles); - setDragging(false); - } - - return ( - - {children} - {dragging && ( - { - e.preventDefault(); - e.stopPropagation(); - e.dataTransfer.dropEffect = "copy"; - }} - onDrop={handleImageDrop} - > - - {dropText || "Drop image to upload"} - - - )} - - ); -} - -export default ImageDrop; diff --git a/src/components/LoadingOverlay.tsx b/src/components/LoadingOverlay.tsx index 62cff28..55f2ce8 100644 --- a/src/components/LoadingOverlay.tsx +++ b/src/components/LoadingOverlay.tsx @@ -1,8 +1,15 @@ +import React from "react"; import { Box } from "theme-ui"; import Spinner from "./Spinner"; -function LoadingOverlay({ bg }: any ) { +function LoadingOverlay({ + bg, + children, +}: { + bg: string; + children?: React.ReactNode; +}) { return ( + {children} ); } diff --git a/src/components/Markdown.js b/src/components/Markdown.js index 52d67fe..388ea77 100644 --- a/src/components/Markdown.js +++ b/src/components/Markdown.js @@ -9,7 +9,7 @@ import { import ReactMarkdown from "react-markdown"; function Paragraph(props) { - return ; + return ; } function Heading({ level, ...props }) { @@ -27,6 +27,9 @@ function Heading({ level, ...props }) { } function Image(props) { + if (props.alt === "embed:") { + return ; + } if (props.src.endsWith(".mp4")) { return (