From 81d311eb7fd931968efaff16feed0c919584f795 Mon Sep 17 00:00:00 2001 From: Mitchell McCaffrey Date: Fri, 20 Mar 2020 11:05:40 +1100 Subject: [PATCH] Added token dragging from side bar into map view --- package.json | 3 +- src/components/Map.js | 33 +++++- src/components/ProxyToken.js | 126 +++++++++++++++++++++ src/components/Token.js | 23 ++-- src/components/Tokens.js | 56 ++++++---- src/helpers/usePortal.js | 85 ++++++++++++++ src/routes/Game.js | 36 +++--- yarn.lock | 207 ++++++++++++++++++++++++++++++++--- 8 files changed, 501 insertions(+), 68 deletions(-) create mode 100644 src/components/ProxyToken.js create mode 100644 src/helpers/usePortal.js diff --git a/package.json b/package.json index c79918e..dc3c1aa 100644 --- a/package.json +++ b/package.json @@ -7,12 +7,13 @@ "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", "hookrouter": "^1.2.3", + "interactjs": "^1.9.7", "peerjs": "^1.2.0", "react": "^16.13.0", "react-dom": "^16.13.0", - "react-draggable": "^4.2.0", "react-modal": "^3.11.2", "react-scripts": "3.4.0", + "shortid": "^2.2.15", "theme-ui": "^0.3.1" }, "scripts": { diff --git a/src/components/Map.js b/src/components/Map.js index 55a95c9..517cbfc 100644 --- a/src/components/Map.js +++ b/src/components/Map.js @@ -1,9 +1,36 @@ import React from "react"; -import { Image, Flex } from "theme-ui"; +import { Image, Flex, Box } from "theme-ui"; -function Map({ imageSource }) { +import Token from "../components/Token"; + +function Map({ imageSource, tokens }) { return ( - + + + {Object.values(tokens).map(token => ( + + + + ))} + ); diff --git a/src/components/ProxyToken.js b/src/components/ProxyToken.js new file mode 100644 index 0000000..18f0069 --- /dev/null +++ b/src/components/ProxyToken.js @@ -0,0 +1,126 @@ +import React, { useEffect, useRef, useState } from "react"; +import ReactDOM from "react-dom"; +import { Image, Box } from "theme-ui"; +import interact from "interactjs"; + +import usePortal from "../helpers/usePortal"; + +function ProxyToken({ tokenClassName, onProxyDragEnd }) { + const proxyContainer = usePortal("root"); + + const [imageSource, setImageSource] = useState(""); + const imageRef = useRef(); + + const proxyOnMap = useRef(false); + + useEffect(() => { + interact(`.${tokenClassName}`).draggable({ + listeners: { + start: event => { + let target = event.target; + // Hide the token and copy it's image to the proxy + target.style.opacity = "0.25"; + setImageSource(target.src); + + let proxy = imageRef.current; + if (proxy) { + // Find and set the initial offset of the token to the proxy + const proxyRect = proxy.getBoundingClientRect(); + const targetRect = target.getBoundingClientRect(); + const xOffset = targetRect.left - proxyRect.left; + const yOffset = targetRect.top - proxyRect.top; + proxy.style.transform = `translate(${xOffset}px, ${yOffset}px)`; + proxy.setAttribute("data-x", xOffset); + proxy.setAttribute("data-y", yOffset); + } + }, + + move: event => { + let proxy = imageRef.current; + // Move the proxy based off of the movment of the token + if (proxy) { + // keep the dragged position in the data-x/data-y attributes + const x = + (parseFloat(proxy.getAttribute("data-x")) || 0) + event.dx; + const y = + (parseFloat(proxy.getAttribute("data-y")) || 0) + event.dy; + proxy.style.transform = `translate(${x}px, ${y}px)`; + + // Check whether the proxy is on the right or left hand side of the screen + // if not set proxyOnMap to true + if (proxyContainer) { + const proxyContainerRect = proxyContainer.getBoundingClientRect(); + const proxyRect = proxy.getBoundingClientRect(); + proxyOnMap.current = + proxyContainerRect.right - proxyRect.right > 80 && + proxyRect.left > 192; + } + + // update the posiion attributes + proxy.setAttribute("data-x", x); + proxy.setAttribute("data-y", y); + } + }, + + end: event => { + let target = event.target; + let proxy = imageRef.current; + if (proxy) { + if (onProxyDragEnd) { + const endX = parseFloat(proxy.getAttribute("data-x")) || 0; + const endY = parseFloat(proxy.getAttribute("data-y")) || 0; + onProxyDragEnd(proxyOnMap.current, { + image: imageSource, + x: endX, + y: endY + }); + } + + // Reset the proxy position + proxy.style.transform = "translate(0px, 0px)"; + proxy.setAttribute("data-x", 0); + proxy.setAttribute("data-y", 0); + } + + // Show the token + target.style.opacity = "1"; + setImageSource(""); + } + } + }); + }, [imageSource, onProxyDragEnd, tokenClassName, proxyContainer]); + + if (!imageSource) { + return null; + } + + // Create a portal to allow the proxy to move past the bounds of the token + return ReactDOM.createPortal( + + + , + proxyContainer + ); +} + +export default ProxyToken; diff --git a/src/components/Token.js b/src/components/Token.js index 294cd2d..2037760 100644 --- a/src/components/Token.js +++ b/src/components/Token.js @@ -1,18 +1,17 @@ import React from "react"; -import Draggable from "react-draggable"; +import { Image } from "theme-ui"; -function Token({ onDrag, position }) { +function Token({ image, className }) { return ( - -
-
+ ); } diff --git a/src/components/Tokens.js b/src/components/Tokens.js index 39c525e..663d233 100644 --- a/src/components/Tokens.js +++ b/src/components/Tokens.js @@ -1,31 +1,43 @@ import React from "react"; -import { Flex, Image } from "theme-ui"; +import { Flex } from "theme-ui"; +import shortid from "shortid"; import * as tokens from "../tokens"; -// import Token from "./Token"; +import Token from "./Token"; +import ProxyToken from "./ProxyToken"; + +function Tokens({ onCreateMapToken }) { + const tokenClassName = "list-token"; + + function handleProxyDragEnd(isOnMap, token) { + if (isOnMap && onCreateMapToken) { + // Give the token an id + onCreateMapToken({ id: shortid.generate(), ...token }); + } + } -function Tokens() { return ( - - {Object.entries(tokens).map(([id, image]) => ( - - ))} - + <> + + {Object.entries(tokens).map(([id, image]) => ( + + ))} + + + ); } diff --git a/src/helpers/usePortal.js b/src/helpers/usePortal.js new file mode 100644 index 0000000..cb8c2f9 --- /dev/null +++ b/src/helpers/usePortal.js @@ -0,0 +1,85 @@ +import { useRef, useEffect } from "react"; + +// Based on https://www.jayfreestone.com/writing/react-portals-with-hooks/ + +/** + * Creates DOM element to be used as React root. + * @returns {HTMLElement} + */ +function createRootElement(id) { + const rootContainer = document.createElement("div"); + rootContainer.setAttribute("id", id); + return rootContainer; +} + +/** + * Appends element as last child of body. + * @param {HTMLElement} rootElem + */ +function addRootElement(rootElem) { + document.body.insertBefore( + rootElem, + document.body.lastElementChild.nextElementSibling + ); +} + +/** + * Hook to create a React Portal. + * Automatically handles creating and tearing-down the root elements (no SRR + * makes this trivial), so there is no need to ensure the parent target already + * exists. + * @example + * const target = usePortal(id, [id]); + * return createPortal(children, target); + * @param {String} id The id of the target container, e.g 'modal' or 'spotlight' + * @returns {HTMLElement} The DOM node to use as the Portal target. + */ +function usePortal(id) { + const rootElemRef = useRef(null); + + useEffect( + function setupElement() { + // Look for existing target dom element to append to + const existingParent = document.querySelector(`#${id}`); + // Parent is either a new root or the existing dom element + const parentElem = existingParent || createRootElement(id); + + // If there is no existing DOM element, add a new one. + if (!existingParent) { + addRootElement(parentElem); + } + + // Add the detached element to the parent + parentElem.appendChild(rootElemRef.current); + + return function removeElement() { + rootElemRef.current.remove(); + if (parentElem.childNodes.length === -1) { + parentElem.remove(); + } + }; + }, + [id] + ); + + /** + * It's important we evaluate this lazily: + * - We need first render to contain the DOM element, so it shouldn't happen + * in useEffect. We would normally put this in the constructor(). + * - We can't do 'const rootElemRef = useRef(document.createElement('div))', + * since this will run every single render (that's a lot). + * - We want the ref to consistently point to the same DOM element and only + * ever run once. + * @link https://reactjs.org/docs/hooks-faq.html#how-to-create-expensive-objects-lazily + */ + function getRootElem() { + if (!rootElemRef.current) { + rootElemRef.current = document.createElement("div"); + } + return rootElemRef.current; + } + + return getRootElem(); +} + +export default usePortal; diff --git a/src/routes/Game.js b/src/routes/Game.js index 1cd82fa..a5f0de3 100644 --- a/src/routes/Game.js +++ b/src/routes/Game.js @@ -10,7 +10,6 @@ import { Box, Flex } from "theme-ui"; import GameContext from "../contexts/GameContext"; import useSession from "../helpers/useSession"; -import Token from "../components/Token"; import Party from "../components/Party"; import Tokens from "../components/Tokens"; import Map from "../components/Map"; @@ -42,6 +41,18 @@ function Game() { } } + const [mapTokens, setMapTokens] = useState({}); + + function handleCreateMapToken(token) { + setMapTokens(prevMapTokens => ({ + ...prevMapTokens, + [token.id]: token + })); + for (let connection of Object.values(connections)) { + connection.send({ id: "token", data: token }); + } + } + function handleConnectionOpen(connection) { connection.on("data", data => { if (data.id === "image") { @@ -50,7 +61,11 @@ function Game() { setImageSource(URL.createObjectURL(imageDataRef.current)); } if (data.id === "token") { - setTokenPosition(data.data); + const token = data.data; + setMapTokens(prevMapTokens => ({ + ...prevMapTokens, + [token.id]: token + })); } }); } @@ -59,17 +74,7 @@ function Game() { if (imageSource) { connection.send({ id: "image", data: imageDataRef.current }); } - connection.send({ id: "token", data: tokenPosition }); - } - - const [tokenPosition, setTokenPosition] = useState({ x: 0, y: 0 }); - - function handleTokenDrag(event, data) { - const position = { x: data.x, y: data.y }; - setTokenPosition(position); - for (let connection of Object.values(connections)) { - connection.send({ id: "token", data: position }); - } + connection.send({ id: "token", data: mapTokens }); } return ( @@ -89,9 +94,8 @@ function Game() { sx={{ justifyContent: "space-between", flexGrow: 1, height: "100%" }} > - - - {/* */} + +
); diff --git a/yarn.lock b/yarn.lock index e19e224..456b4e9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1048,6 +1048,178 @@ dependencies: "@hapi/hoek" "^8.3.0" +"@interactjs/actions@1.9.7": + version "1.9.7" + resolved "https://registry.yarnpkg.com/@interactjs/actions/-/actions-1.9.7.tgz#b015931f406e090e48d3244343cf7b2cc53c90f1" + integrity sha512-j5+2Cj1Ra3/0BgfCqL7hKGw7kOZyOBg3HXi+EJ9AT0sJzetjwsRt5YlvCm9URH8XcY7sNlFi8gD5mPFjD8DZaw== + optionalDependencies: + "@interactjs/interact" "1.9.7" + +"@interactjs/arrange@1.9.7": + version "1.9.7" + resolved "https://registry.yarnpkg.com/@interactjs/arrange/-/arrange-1.9.7.tgz#42323ce4b1af905d6e4ab76fc066cd1573bd41ee" + integrity sha512-gXcj4Gti3WsRmJ4Ohr5toUl7AjsBOY6Ik5SX69X3AZKrQpDJfkNBcOZJ/AlAPLXIwjBV8rRQiddYgB+SXCJcBg== + +"@interactjs/auto-scroll@1.9.7": + version "1.9.7" + resolved "https://registry.yarnpkg.com/@interactjs/auto-scroll/-/auto-scroll-1.9.7.tgz#e6c2e67b6ed43dd0dd6a84e96b5d1389a20a647a" + integrity sha512-/753R5h8z1VQ/aS5VsqI6C0V6fwb3934tP7s+htHL27aPVxyz20kfZVd74t5BmPSUAxAyDqwNVZ1vgjGLCSyAQ== + optionalDependencies: + "@interactjs/interact" "1.9.7" + +"@interactjs/auto-start@1.9.7": + version "1.9.7" + resolved "https://registry.yarnpkg.com/@interactjs/auto-start/-/auto-start-1.9.7.tgz#4afa53c7c869170cb3a01a0019aa30791b3c5d18" + integrity sha512-fKn7Yrl6idw7gxbAC0zvcQLSTsUE9WGs71+Y9WVj+ICQMkTO19ci9Y1lCBdo1AP/njLaQp8yPbHoUe5pI7eW6w== + optionalDependencies: + "@interactjs/interact" "1.9.7" + +"@interactjs/clone@1.9.7": + version "1.9.7" + resolved "https://registry.yarnpkg.com/@interactjs/clone/-/clone-1.9.7.tgz#fdde8307835cecadd9e29f597ecff890d03167fc" + integrity sha512-GNlGh5sKVFYcpOn7Z3HNOLItBdzG3f6t38soUowCmNv4Rd19nmnXkvKKVhfodjfBk26fsdXFI6EZ3iduzYsmAg== + +"@interactjs/components@1.9.7": + version "1.9.7" + resolved "https://registry.yarnpkg.com/@interactjs/components/-/components-1.9.7.tgz#40ec9889fd1e94d07c67001b36dd39d113b3e762" + integrity sha512-TdNlJVbKKLy2ZmNtpjhxQkQls1SqXXFSyY4JfAGRG23UOOMrjoG4+56RAe4tlOpHSIIfEER5skvgBSv6UFajHw== + +"@interactjs/core@1.9.7": + version "1.9.7" + resolved "https://registry.yarnpkg.com/@interactjs/core/-/core-1.9.7.tgz#1fdf924c369df23ffe1300af9c945f2651538043" + integrity sha512-fN7OfPNU9jgS+hEqe0+OJvJjp/XgNE9lkuCnH57/xtT4nfDca/43BkE57bNoEXhM9Ym5js2Ium+MbUTUVyZ+3A== + +"@interactjs/dev-tools@1.9.7": + version "1.9.7" + resolved "https://registry.yarnpkg.com/@interactjs/dev-tools/-/dev-tools-1.9.7.tgz#91eab3f10806b84b95c313ef5f06408c2d3251cf" + integrity sha512-LhLeLZw2joG13j3hUAuhmC9HTsVCnUEggcScgCcpY/SQRA51JCCM9lEcXnwTxTxd02v6A/tueoyUdONm2lniCA== + dependencies: + "@interactjs/utils" "1.9.7" + optionalDependencies: + "@interactjs/interact" "1.9.7" + +"@interactjs/feedback@1.9.7": + version "1.9.7" + resolved "https://registry.yarnpkg.com/@interactjs/feedback/-/feedback-1.9.7.tgz#0e3ea3c8e863fdf89d8afee0a1ae5838e9f13d09" + integrity sha512-CcgTv2mhCWpH0w6Fq12K6kiV1BSozt8Rdy02yXg927ruKChr7pIQvvZP7TpxfZoBFCN9JJxN3KvQpcfsUQsafQ== + +"@interactjs/inertia@1.9.7": + version "1.9.7" + resolved "https://registry.yarnpkg.com/@interactjs/inertia/-/inertia-1.9.7.tgz#e8c8ed702bd23d0182a1a0ddbca4be7419826511" + integrity sha512-WeVKgztqODjE+fbYuIayJv4cdqwBz4wJ45RC2qYhYWhzsHL0LUbJhEs6sCnV3q+2wZEVMdB8oURh8tnqECBIGg== + dependencies: + "@interactjs/offset" "1.9.7" + optionalDependencies: + "@interactjs/interact" "1.9.7" + +"@interactjs/interact@1.9.7": + version "1.9.7" + resolved "https://registry.yarnpkg.com/@interactjs/interact/-/interact-1.9.7.tgz#be988cfb15b13f2cfc0aa68ce826761d49f46e8a" + integrity sha512-BocTUB8TfZ3USuN1s7xP+3Uhvb7dnZFNTfr3Ux6Nv2BxKLMz/bIU2fZhYAHw7xApIMKfYyRuUNxSyGM5iyCaEA== + dependencies: + "@interactjs/core" "1.9.7" + "@interactjs/types" "1.9.7" + "@interactjs/utils" "1.9.7" + +"@interactjs/interactjs@1.9.7": + version "1.9.7" + resolved "https://registry.yarnpkg.com/@interactjs/interactjs/-/interactjs-1.9.7.tgz#995b6d9b4abd03a84b02c887315768ab2421b35c" + integrity sha512-D3wdlwUQRS8t8r7TopYlkjKsONxazrZu8R4DBL+czUrm26Il/0427+uoV92iMgDUZpw5fYLt1dzqzozUL/y6Dw== + dependencies: + "@interactjs/actions" "1.9.7" + "@interactjs/arrange" "1.9.7" + "@interactjs/auto-scroll" "1.9.7" + "@interactjs/auto-start" "1.9.7" + "@interactjs/clone" "1.9.7" + "@interactjs/components" "1.9.7" + "@interactjs/core" "1.9.7" + "@interactjs/dev-tools" "1.9.7" + "@interactjs/feedback" "1.9.7" + "@interactjs/inertia" "1.9.7" + "@interactjs/interact" "1.9.7" + "@interactjs/modifiers" "1.9.7" + "@interactjs/multi-target" "1.9.7" + "@interactjs/offset" "1.9.7" + "@interactjs/pointer-events" "1.9.7" + "@interactjs/reflow" "1.9.7" + "@interactjs/types" "1.9.7" + "@interactjs/utils" "1.9.7" + "@interactjs/vue" "1.9.7" + +"@interactjs/modifiers@1.9.7": + version "1.9.7" + resolved "https://registry.yarnpkg.com/@interactjs/modifiers/-/modifiers-1.9.7.tgz#dcb37e3782c91d7f8b28008d68ec5ebc26101e7a" + integrity sha512-1trdJ6E85DiUn2u0zvqLYt77aoug0VYDg8c1GyzcLm64t8CYw+PggTQKqhy+kJiKb30QsgUuhsR5aQdlotguew== + dependencies: + "@interactjs/snappers" "1.9.7" + optionalDependencies: + "@interactjs/interact" "1.9.7" + +"@interactjs/multi-target@1.9.7": + version "1.9.7" + resolved "https://registry.yarnpkg.com/@interactjs/multi-target/-/multi-target-1.9.7.tgz#bb4de8612f6a649e18d40b93ed5461cde72f79ee" + integrity sha512-hqw4sCKYxUZV00u0ikEM66t6zrCP8t3TDgDPrph274LmEUIdfGrCsv7xWLIEdke63h0pt3lO1V5H8N/rMoGs0A== + +"@interactjs/offset@1.9.7": + version "1.9.7" + resolved "https://registry.yarnpkg.com/@interactjs/offset/-/offset-1.9.7.tgz#5a2aec0f25781bd1728c71fe8c16a55edc3f5b8e" + integrity sha512-IMCizwVDk2hj8yNNTfLnqdK9GuSlFSS8GxK2ZGUGNSQSPh3b6vrkkIrxwA/qHjDGUUuDP+ZWvlAF4D0/OAc74g== + optionalDependencies: + "@interactjs/interact" "1.9.7" + +"@interactjs/pointer-events@1.9.7": + version "1.9.7" + resolved "https://registry.yarnpkg.com/@interactjs/pointer-events/-/pointer-events-1.9.7.tgz#5a0e0daff1c322c6cbff8cbb8052c531d66d1aad" + integrity sha512-Vl/+GTVnZlw8DQ7YN7D1oIIZLiqPL/KgdfRrGHSidsLmo/WJNM2TsJJsJcRMY/IiEQrtTvp/fSKG6rNWyzIr5w== + optionalDependencies: + "@interactjs/interact" "1.9.7" + +"@interactjs/reflow@1.9.7": + version "1.9.7" + resolved "https://registry.yarnpkg.com/@interactjs/reflow/-/reflow-1.9.7.tgz#77494d83e1a13236ca841adba997faaaf6eb633c" + integrity sha512-npuIadX3YBoFFqJbM+LQZgPyw/IYq3HwYM2WNi8Bc/feCy1Ia12uF4Lwi5Rj+8jcA5FyInHzZXBUJCcHO1zaFQ== + optionalDependencies: + "@interactjs/interact" "1.9.7" + +"@interactjs/snappers@1.9.7": + version "1.9.7" + resolved "https://registry.yarnpkg.com/@interactjs/snappers/-/snappers-1.9.7.tgz#b2f3752ab25c886cd5ae1f4c0478964b9b8b652e" + integrity sha512-EfKsgTi8yCtNWRHSU2INfMob5eMUBY8Wrh53tszgh94JstyKwlmzG2mH6eGwdN7pU3//h0AM4ThuqOmbIYUPCw== + +"@interactjs/symbol-tree@1.9.7": + version "1.9.7" + resolved "https://registry.yarnpkg.com/@interactjs/symbol-tree/-/symbol-tree-1.9.7.tgz#2aec371ded47be71cfa359305da959b382de66ff" + integrity sha512-zLRpXVtzq/TxG6MwT1FhkBNoFGZlYxNd52RZ6Wy5XyPAsxaZoXqNzDUT6See/xe2X80gzvT78dkT5uKSUE3I2w== + +"@interactjs/types@1.9.7": + version "1.9.7" + resolved "https://registry.yarnpkg.com/@interactjs/types/-/types-1.9.7.tgz#6e7a8bc26d99bd5f324312950b948b936f6d42e0" + integrity sha512-Z6fPHp2JjCYg4SJ/DhaIqelY1WA6Oppw87PscOM+28jPc2q5pTUMlFzAY0QQlF43t4PFiCAZVhmBGZzgMfyF0A== + dependencies: + "@interactjs/actions" "1.9.7" + "@interactjs/arrange" "1.9.7" + "@interactjs/auto-scroll" "1.9.7" + "@interactjs/auto-start" "1.9.7" + "@interactjs/core" "1.9.7" + "@interactjs/dev-tools" "1.9.7" + "@interactjs/inertia" "1.9.7" + "@interactjs/modifiers" "1.9.7" + "@interactjs/pointer-events" "1.9.7" + "@interactjs/reflow" "1.9.7" + "@interactjs/snappers" "1.9.7" + "@interactjs/symbol-tree" "1.9.7" + "@interactjs/utils" "1.9.7" + +"@interactjs/utils@1.9.7": + version "1.9.7" + resolved "https://registry.yarnpkg.com/@interactjs/utils/-/utils-1.9.7.tgz#390f574c5b8adbf9cc527affb686d9233d241eec" + integrity sha512-7/j0UmeuBHi20FHlqKN9KbndulLEuTiPIPID5nEw+1F/jMKucn3uPIXfruSkbcGswltqTCR0QHhICMLXhFRVhw== + +"@interactjs/vue@1.9.7": + version "1.9.7" + resolved "https://registry.yarnpkg.com/@interactjs/vue/-/vue-1.9.7.tgz#9942407a144b4cd9d1f264ccaaf43d75f5a00a60" + integrity sha512-Y1mjCB5tGxsaUkfYP/i1dIoBRnxkDpvZtmfKp38u1SRt3QCfNznKf4252QC7RBTSgIL/Cohi8c/oAq++ZpkTSQ== + "@jest/console@^24.7.1", "@jest/console@^24.9.0": version "24.9.0" resolved "https://registry.yarnpkg.com/@jest/console/-/console-24.9.0.tgz#79b1bc06fb74a8cfb01cbdedf945584b1b9707f0" @@ -2984,11 +3156,6 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -classnames@^2.2.5: - version "2.2.6" - resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" - integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q== - clean-css@^4.2.1: version "4.2.3" resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.3.tgz#507b5de7d97b48ee53d84adb0160ff6216380f78" @@ -5480,6 +5647,14 @@ inquirer@7.0.4, inquirer@^7.0.0: strip-ansi "^5.1.0" through "^2.3.6" +interactjs@^1.9.7: + version "1.9.7" + resolved "https://registry.yarnpkg.com/interactjs/-/interactjs-1.9.7.tgz#ebcbc3cab9c63f4cd66aa650a5918bcc2aaeeead" + integrity sha512-X8Y4iSeFdy8bnFkbrDJ5C/HwGKCgLUHhOdfvxLsx/f1YlYZ23qBA4aYoB74Qw0CG/mMenYiOFLxbDIaly8hhzQ== + dependencies: + "@interactjs/interactjs" "1.9.7" + "@interactjs/types" "1.9.7" + internal-ip@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907" @@ -7035,6 +7210,11 @@ nan@^2.12.1: resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== +nanoid@^2.1.0: + version "2.1.11" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-2.1.11.tgz#ec24b8a758d591561531b4176a01e3ab4f0f0280" + integrity sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA== + nanomatch@^1.2.9: version "1.2.13" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" @@ -8586,7 +8766,7 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.3" -prop-types@^15.5.10, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2: +prop-types@^15.5.10, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -8792,14 +8972,6 @@ react-dom@^16.13.0: prop-types "^15.6.2" scheduler "^0.19.0" -react-draggable@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-4.2.0.tgz#40cc5209082ca7d613104bf6daf31372cc0e1114" - integrity sha512-5wFq//gEoeTYprnd4ze8GrFc+Rbnx+9RkOMR3vk4EbWxj02U6L6T3yrlKeiw4X5CtjD2ma2+b3WujghcXNRzkw== - dependencies: - classnames "^2.2.5" - prop-types "^15.6.0" - react-error-overlay@^6.0.6: version "6.0.6" resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.6.tgz#ac4d9dc4c1b5c536c2c312bf66aa2b09bfa384e2" @@ -9604,6 +9776,13 @@ shellwords@^0.1.1: resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== +shortid@^2.2.15: + version "2.2.15" + resolved "https://registry.yarnpkg.com/shortid/-/shortid-2.2.15.tgz#2b902eaa93a69b11120373cd42a1f1fe4437c122" + integrity sha512-5EaCy2mx2Jgc/Fdn9uuDuNIIfWBpzY4XIlhoqtXF6qsf+/+SGZ+FxDdX/ZsMZiWupIWNqAEmiNY4RC+LSmCeOw== + dependencies: + nanoid "^2.1.0" + signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"