Move drag and drop to dnd-kit

This commit is contained in:
Mitchell McCaffrey 2021-05-06 15:04:53 +10:00
parent bd522017a7
commit 8aa3bda90d
16 changed files with 322 additions and 359 deletions

View File

@ -5,6 +5,7 @@
"dependencies": { "dependencies": {
"@babylonjs/core": "^4.2.0", "@babylonjs/core": "^4.2.0",
"@babylonjs/loaders": "^4.2.0", "@babylonjs/loaders": "^4.2.0",
"@dnd-kit/core": "3.0.0",
"@mitchemmc/dexie-export-import": "^1.0.1", "@mitchemmc/dexie-export-import": "^1.0.1",
"@msgpack/msgpack": "^2.4.1", "@msgpack/msgpack": "^2.4.1",
"@sentry/react": "^6.2.2", "@sentry/react": "^6.2.2",
@ -25,7 +26,6 @@
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"fuse.js": "^6.4.6", "fuse.js": "^6.4.6",
"image-outline": "^0.1.0", "image-outline": "^0.1.0",
"interactjs": "^1.10.8",
"konva": "^7.2.5", "konva": "^7.2.5",
"lodash.clonedeep": "^4.5.0", "lodash.clonedeep": "^4.5.0",
"lodash.get": "^4.4.2", "lodash.get": "^4.4.2",

View File

@ -13,12 +13,8 @@ import Donate from "./routes/Donate";
import { AuthProvider } from "./contexts/AuthContext"; import { AuthProvider } from "./contexts/AuthContext";
import { DatabaseProvider } from "./contexts/DatabaseContext"; 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 { SettingsProvider } from "./contexts/SettingsContext";
import { KeyboardProvider } from "./contexts/KeyboardContext"; import { KeyboardProvider } from "./contexts/KeyboardContext";
import { AssetsProvider, AssetURLsProvider } from "./contexts/AssetsContext";
import { ToastProvider } from "./components/Toast"; import { ToastProvider } from "./components/Toast";
@ -49,17 +45,7 @@ function App() {
<FAQ /> <FAQ />
</Route> </Route>
<Route path="/game/:id"> <Route path="/game/:id">
<AssetsProvider>
<AssetURLsProvider>
<MapLoadingProvider>
<MapDataProvider>
<TokenDataProvider>
<Game /> <Game />
</TokenDataProvider>
</MapDataProvider>
</MapLoadingProvider>
</AssetURLsProvider>
</AssetsProvider>
</Route> </Route>
<Route path="/"> <Route path="/">
<Home /> <Home />

View File

@ -0,0 +1,27 @@
import React from "react";
import { useDraggable } from "@dnd-kit/core";
function Draggable({ id, children, data }) {
const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
id,
data,
});
const style = {
border: "none",
background: "transparent",
margin: "0px",
padding: "0px",
cursor: "pointer",
touchAction: "none",
opacity: isDragging ? 0.5 : undefined,
};
return (
<button ref={setNodeRef} style={style} {...listeners} {...attributes}>
{children}
</button>
);
}
export default Draggable;

View File

@ -0,0 +1,18 @@
import React from "react";
import { useDroppable } from "@dnd-kit/core";
function Droppable({ id, children, disabled }) {
const { setNodeRef } = useDroppable({ id, disabled });
return (
<div style={{ width: "100%", height: "100%" }} ref={setNodeRef}>
{children}
</div>
);
}
Droppable.defaultProps = {
disabled: false,
};
export default Droppable;

View File

@ -1,7 +1,7 @@
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import { Box, IconButton } from "theme-ui"; import { Box, IconButton } from "theme-ui";
import RemoveTokenIcon from "../icons/RemoveTokenIcon"; import RemoveTokenIcon from "../../icons/RemoveTokenIcon";
function DragOverlay({ dragging, node, onRemove }) { function DragOverlay({ dragging, node, onRemove }) {
const [isRemoveHovered, setIsRemoveHovered] = useState(false); const [isRemoveHovered, setIsRemoveHovered] = useState(false);

View File

@ -1,4 +1,5 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { Box } from "theme-ui";
import MapControls from "./MapControls"; import MapControls from "./MapControls";
import MapInteraction from "./MapInteraction"; import MapInteraction from "./MapInteraction";
@ -18,6 +19,8 @@ import TokenDragOverlay from "../token/TokenDragOverlay";
import NoteMenu from "../note/NoteMenu"; import NoteMenu from "../note/NoteMenu";
import NoteDragOverlay from "../note/NoteDragOverlay"; import NoteDragOverlay from "../note/NoteDragOverlay";
import Droppable from "../Droppable";
import { import {
AddShapeAction, AddShapeAction,
CutShapeAction, CutShapeAction,
@ -336,6 +339,8 @@ function Map({
); );
return ( return (
<Box sx={{ flexGrow: 1 }}>
<Droppable id="map" disabled={!map}>
<MapInteraction <MapInteraction
map={map} map={map}
mapState={mapState} mapState={mapState}
@ -360,6 +365,8 @@ function Map({
{mapPointer} {mapPointer}
{mapMeasure} {mapMeasure}
</MapInteraction> </MapInteraction>
</Droppable>
</Box>
); );
} }

View File

@ -181,11 +181,12 @@ function MapInteraction({
<GridProvider grid={map?.grid} width={mapWidth} height={mapHeight}> <GridProvider grid={map?.grid} width={mapWidth} height={mapHeight}>
<Box <Box
sx={{ sx={{
flexGrow: 1,
position: "relative", position: "relative",
cursor: getCursorForTool(selectedToolId), cursor: getCursorForTool(selectedToolId),
touchAction: "none", touchAction: "none",
outline: "none", outline: "none",
width: "100%",
height: "100%",
}} }}
ref={containerRef} ref={containerRef}
className="map" className="map"

View File

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import DragOverlay from "../DragOverlay"; import DragOverlay from "../map/DragOverlay";
function NoteDragOverlay({ onNoteRemove, noteId, noteGroup, dragging }) { function NoteDragOverlay({ onNoteRemove, noteId, noteGroup, dragging }) {
function handleNoteRemove() { function handleNoteRemove() {

View File

@ -7,7 +7,7 @@ import { useDataURL } from "../../contexts/AssetsContext";
import { tokenSources, unknownSource } from "../../tokens"; import { tokenSources, unknownSource } from "../../tokens";
function ListToken({ token, className }) { function ListToken({ token }) {
const tokenURL = useDataURL( const tokenURL = useDataURL(
token, token,
tokenSources, tokenSources,
@ -20,11 +20,10 @@ function ListToken({ token, className }) {
usePreventTouch(imageRef); usePreventTouch(imageRef);
return ( return (
<Box my={2} mx={3} sx={{ width: "48px", height: "48px" }}> <Box py={1} sx={{ width: "48px", height: "56px" }}>
<Image <Image
src={tokenURL} src={tokenURL}
ref={imageRef} ref={imageRef}
className={className}
sx={{ sx={{
userSelect: "none", userSelect: "none",
touchAction: "none", touchAction: "none",
@ -32,8 +31,6 @@ function ListToken({ token, className }) {
height: "100%", height: "100%",
objectFit: "cover", objectFit: "cover",
}} }}
// pass id into the dom element which is then used by the ProxyToken
data-id={token.id}
alt={token.name} alt={token.name}
title={token.name} title={token.name}
/> />

View File

@ -1,172 +0,0 @@
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 "../../hooks/usePortal";
import { useMapStage } from "../../contexts/MapStageContext";
/**
* @callback onProxyDragEnd
* @param {boolean} isOnMap whether the token was dropped on the map
* @param {Object} token the token that was dropped
*/
/**
*
* @param {string} tokenClassName The class name to attach the interactjs handler to
* @param {onProxyDragEnd} onProxyDragEnd Called when the proxy token is dropped
* @param {Object} tokens An optional mapping of tokens to use as a base when calling OnProxyDragEnd
*/
function ProxyToken({ tokenClassName, onProxyDragEnd, tokens }) {
const proxyContainer = usePortal("root");
const [imageSource, setImageSource] = useState("");
const proxyRef = useRef();
// Store the tokens in a ref and access in the interactjs loop
// This is needed to stop interactjs from creating multiple listeners
const tokensRef = useRef(tokens);
useEffect(() => {
tokensRef.current = tokens;
}, [tokens]);
const proxyOnMap = useRef(false);
const mapStageRef = useMapStage();
useEffect(() => {
interact(`.${tokenClassName}`).draggable({
listeners: {
start: (event) => {
let target = event.target;
// Hide the token and copy it's image to the proxy
target.parentElement.style.opacity = "0.25";
setImageSource(target.src);
let proxy = proxyRef.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);
// Copy width and height of target
proxy.style.width = `${targetRect.width}px`;
proxy.style.height = `${targetRect.height}px`;
}
},
move: (event) => {
let proxy = proxyRef.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
const proxyRect = proxy.getBoundingClientRect();
const map = document.querySelector(".map");
const mapRect = map.getBoundingClientRect();
proxyOnMap.current =
proxyRect.left > mapRect.left && proxyRect.right < mapRect.right;
// update the posiion attributes
proxy.setAttribute("data-x", x);
proxy.setAttribute("data-y", y);
}
},
end: (event) => {
let target = event.target;
const id = target.dataset.id;
let proxy = proxyRef.current;
if (proxy) {
const mapStage = mapStageRef.current;
if (onProxyDragEnd && mapStage) {
const mapImage = mapStage.findOne("#mapImage");
const map = document.querySelector(".map");
const mapRect = map.getBoundingClientRect();
const position = {
x: event.clientX - mapRect.left,
y: event.clientY - mapRect.top,
};
const transform = mapImage.getAbsoluteTransform().copy().invert();
const relativePosition = transform.point(position);
const normalizedPosition = {
x: relativePosition.x / mapImage.width(),
y: relativePosition.y / mapImage.height(),
};
// Get the token from the supplied tokens if it exists
const token = tokensRef.current[id] || {};
onProxyDragEnd(proxyOnMap.current, {
...token,
x: normalizedPosition.x,
y: normalizedPosition.y,
});
}
// Reset the proxy position
proxy.style.transform = "translate(0px, 0px)";
proxy.setAttribute("data-x", 0);
proxy.setAttribute("data-y", 0);
}
// Show the token
target.parentElement.style.opacity = "1";
setImageSource("");
},
},
});
}, [onProxyDragEnd, tokenClassName, proxyContainer, mapStageRef]);
if (!imageSource) {
return null;
}
// Create a portal to allow the proxy to move past the bounds of the token
return ReactDOM.createPortal(
<Box
sx={{
position: "absolute",
overflow: "hidden",
top: 0,
left: 0,
bottom: 0,
right: 0,
}}
>
<Box
sx={{ position: "absolute", display: "flex", flexDirection: "column" }}
ref={proxyRef}
>
<Image
src={imageSource}
sx={{
touchAction: "none",
userSelect: "none",
width: "100%",
}}
/>
</Box>
</Box>,
proxyContainer
);
}
ProxyToken.defaultProps = {
tokens: {},
};
export default ProxyToken;

View File

@ -1,79 +1,52 @@
import React from "react"; import React from "react";
import { createPortal } from "react-dom";
import { Box, Flex } from "theme-ui"; import { Box, Flex } from "theme-ui";
import shortid from "shortid";
import SimpleBar from "simplebar-react"; import SimpleBar from "simplebar-react";
import { DragOverlay } from "@dnd-kit/core";
import ListToken from "./ListToken"; import ListToken from "./ListToken";
import ProxyToken from "./ProxyToken";
import SelectTokensButton from "./SelectTokensButton"; import SelectTokensButton from "./SelectTokensButton";
import Draggable from "../Draggable";
import { fromEntries } from "../../helpers/shared";
import useSetting from "../../hooks/useSetting"; import useSetting from "../../hooks/useSetting";
import { useAuth } from "../../contexts/AuthContext";
import { useTokenData } from "../../contexts/TokenDataContext"; import { useTokenData } from "../../contexts/TokenDataContext";
import { useDragId } from "../../contexts/DragContext";
const listTokenClassName = "list-token"; function TokenBar() {
const { ownedTokens } = useTokenData();
function TokenBar({ onMapTokenStateCreate }) {
const { userId } = useAuth();
const { ownedTokens, tokens } = useTokenData();
const [fullScreen] = useSetting("map.fullScreen"); const [fullScreen] = useSetting("map.fullScreen");
function handleProxyDragEnd(isOnMap, token) { const activeDragId = useDragId();
if (isOnMap && onMapTokenStateCreate) {
// Create a token state from the dragged token
let tokenState = {
id: shortid.generate(),
tokenId: token.id,
owner: userId,
size: token.defaultSize,
category: token.defaultCategory,
label: token.defaultLabel,
statuses: [],
x: token.x,
y: token.y,
lastModifiedBy: userId,
lastModified: Date.now(),
rotation: 0,
locked: false,
visible: true,
type: token.type,
outline: token.outline,
width: token.width,
height: token.height,
};
if (token.type === "file") {
tokenState.file = token.file;
} else if (token.type === "default") {
tokenState.key = token.key;
}
onMapTokenStateCreate(tokenState);
}
}
return ( return (
<>
<Box <Box
sx={{ sx={{
height: "100%", height: "100%",
width: "80px", width: "80px",
minWidth: "80px", minWidth: "80px",
overflow: "hidden", overflowY: "scroll",
overflowX: "hidden",
display: fullScreen ? "none" : "block", display: fullScreen ? "none" : "block",
}} }}
> >
<SimpleBar style={{ height: "calc(100% - 48px)", overflowX: "hidden" }}> <SimpleBar
style={{
height: "calc(100% - 48px)",
overflowX: "hidden",
padding: "0 16px",
}}
>
{ownedTokens {ownedTokens
.filter((token) => !token.hideInSidebar) .filter((token) => !token.hideInSidebar)
.map((token) => ( .map((token) => (
<ListToken <Draggable
id={`sidebar-${token.id}`}
key={token.id} key={token.id}
token={token} data={{ tokenId: token.id }}
className={listTokenClassName} >
/> <ListToken token={token} />
</Draggable>
))} ))}
</SimpleBar> </SimpleBar>
<Flex <Flex
@ -86,13 +59,19 @@ function TokenBar({ onMapTokenStateCreate }) {
> >
<SelectTokensButton /> <SelectTokensButton />
</Flex> </Flex>
</Box> {createPortal(
<ProxyToken <DragOverlay>
tokenClassName={listTokenClassName} {activeDragId && (
onProxyDragEnd={handleProxyDragEnd} <ListToken
tokens={fromEntries(tokens.map((token) => [token.id, token]))} token={ownedTokens.find(
(token) => `sidebar-${token.id}` === activeDragId
)}
/> />
</> )}
</DragOverlay>,
document.body
)}
</Box>
); );
} }

View File

@ -6,7 +6,7 @@ import {
useMapHeight, useMapHeight,
} from "../../contexts/MapInteractionContext"; } from "../../contexts/MapInteractionContext";
import DragOverlay from "../DragOverlay"; import DragOverlay from "../map/DragOverlay";
function TokenDragOverlay({ function TokenDragOverlay({
onTokenStateRemove, onTokenStateRemove,

View File

@ -0,0 +1,36 @@
import React, { useState, useContext } from "react";
import { DndContext } from "@dnd-kit/core";
/**
* @type {React.Context<string|undefined>}
*/
const DragIdContext = React.createContext();
export function DragProvider({ children, onDragEnd }) {
const [activeDragId, setActiveDragId] = useState(null);
function handleDragStart({ active }) {
setActiveDragId(active.id);
}
function handleDragEnd(event) {
setActiveDragId(null);
onDragEnd && onDragEnd(event);
}
return (
<DndContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
<DragIdContext.Provider value={activeDragId}>
{children}
</DragIdContext.Provider>
</DndContext>
);
}
export function useDragId() {
const context = useContext(DragIdContext);
if (context === undefined) {
throw new Error("useDragId must be used within a DragProvider");
}
return context;
}

View File

@ -1,5 +1,6 @@
import React, { useState, useEffect, useRef } from "react"; import React, { useState, useEffect, useRef } from "react";
import { useToasts } from "react-toast-notifications"; import { useToasts } from "react-toast-notifications";
import { v4 as uuid } from "uuid";
import { useMapData } from "../contexts/MapDataContext"; import { useMapData } from "../contexts/MapDataContext";
import { useMapLoading } from "../contexts/MapLoadingContext"; import { useMapLoading } from "../contexts/MapLoadingContext";
@ -7,6 +8,9 @@ import { useAuth } from "../contexts/AuthContext";
import { useDatabase } from "../contexts/DatabaseContext"; import { useDatabase } from "../contexts/DatabaseContext";
import { useParty } from "../contexts/PartyContext"; import { useParty } from "../contexts/PartyContext";
import { useAssets } from "../contexts/AssetsContext"; import { useAssets } from "../contexts/AssetsContext";
import { DragProvider } from "../contexts/DragContext";
import { useTokenData } from "../contexts/TokenDataContext";
import { useMapStage } from "../contexts/MapStageContext";
import { omit } from "../helpers/shared"; import { omit } from "../helpers/shared";
@ -40,8 +44,10 @@ function NetworkedMapAndTokens({ session }) {
const { userId } = useAuth(); const { userId } = useAuth();
const partyState = useParty(); const partyState = useParty();
const { assetLoadStart, assetProgressUpdate, isLoading } = useMapLoading(); const { assetLoadStart, assetProgressUpdate, isLoading } = useMapLoading();
const mapStageRef = useMapStage();
const { updateMapState } = useMapData(); const { updateMapState } = useMapData();
const { tokensById } = useTokenData();
const { getAsset, putAsset } = useAssets(); const { getAsset, putAsset } = useAssets();
const [currentMap, setCurrentMap] = useState(null); const [currentMap, setCurrentMap] = useState(null);
@ -379,6 +385,53 @@ function NetworkedMapAndTokens({ session }) {
}); });
} }
function handleDragEnd({ active, over }) {
const tokenId = active?.data?.current?.tokenId;
const token = tokensById[tokenId];
const mapStage = mapStageRef.current;
if (over?.id === "map" && tokenId && token && mapStage) {
const mapImage = mapStage.findOne("#mapImage");
// TODO: Get proper pointer position when dnd-kit is updated
// https://github.com/clauderic/dnd-kit/issues/238
const pointerPosition = {
x: over.rect.width / 2,
y: over.rect.height / 2,
};
const transform = mapImage.getAbsoluteTransform().copy().invert();
const relativePosition = transform.point(pointerPosition);
const normalizedPosition = {
x: relativePosition.x / mapImage.width(),
y: relativePosition.y / mapImage.height(),
};
let tokenState = {
id: uuid(),
tokenId: token.id,
owner: userId,
size: token.defaultSize,
category: token.defaultCategory,
label: token.defaultLabel,
statuses: [],
x: normalizedPosition.x,
y: normalizedPosition.y,
lastModifiedBy: userId,
lastModified: Date.now(),
rotation: 0,
locked: false,
visible: true,
type: token.type,
outline: token.outline,
width: token.width,
height: token.height,
};
if (token.type === "file") {
tokenState.file = token.file;
} else if (token.type === "default") {
tokenState.key = token.key;
}
handleMapTokenStateCreate(tokenState);
}
}
useEffect(() => { useEffect(() => {
async function handlePeerData({ id, data, reply }) { async function handlePeerData({ id, data, reply }) {
if (id === "assetRequest") { if (id === "assetRequest") {
@ -451,7 +504,7 @@ function NetworkedMapAndTokens({ session }) {
} }
return ( return (
<> <DragProvider onDragEnd={handleDragEnd}>
<Map <Map
map={currentMap} map={currentMap}
mapState={currentMapState} mapState={currentMapState}
@ -475,8 +528,8 @@ function NetworkedMapAndTokens({ session }) {
disabledTokens={disabledMapTokens} disabledTokens={disabledMapTokens}
session={session} session={session}
/> />
<TokenBar onMapTokenStateCreate={handleMapTokenStateCreate} /> <TokenBar />
</> </DragProvider>
); );
} }

View File

@ -18,6 +18,10 @@ import { MapStageProvider } from "../contexts/MapStageContext";
import { useDatabase } from "../contexts/DatabaseContext"; import { useDatabase } from "../contexts/DatabaseContext";
import { PlayerProvider } from "../contexts/PlayerContext"; import { PlayerProvider } from "../contexts/PlayerContext";
import { PartyProvider } from "../contexts/PartyContext"; import { PartyProvider } from "../contexts/PartyContext";
import { AssetsProvider, AssetURLsProvider } from "../contexts/AssetsContext";
import { MapDataProvider } from "../contexts/MapDataContext";
import { TokenDataProvider } from "../contexts/TokenDataContext";
import { MapLoadingProvider } from "../contexts/MapLoadingContext";
import NetworkedMapAndTokens from "../network/NetworkedMapAndTokens"; import NetworkedMapAndTokens from "../network/NetworkedMapAndTokens";
import NetworkedParty from "../network/NetworkedParty"; import NetworkedParty from "../network/NetworkedParty";
@ -84,7 +88,6 @@ function Game() {
}; };
}, [session]); }, [session]);
// Join game // Join game
useEffect(() => { useEffect(() => {
if (sessionStatus === "ready" && databaseStatus !== "loading") { if (sessionStatus === "ready" && databaseStatus !== "loading") {
@ -103,6 +106,11 @@ function Game() {
const mapStageRef = useRef(); const mapStageRef = useRef();
return ( return (
<AssetsProvider>
<AssetURLsProvider>
<MapLoadingProvider>
<MapDataProvider>
<TokenDataProvider>
<PlayerProvider session={session}> <PlayerProvider session={session}>
<PartyProvider session={session}> <PartyProvider session={session}>
<MapStageProvider value={mapStageRef}> <MapStageProvider value={mapStageRef}>
@ -124,13 +132,15 @@ function Game() {
> >
<Box p={1}> <Box p={1}>
<Text as="p" variant="body2"> <Text as="p" variant="body2">
{peerError} See <Link to="/faq#connection">FAQ</Link> for more {peerError} See <Link to="/faq#connection">FAQ</Link>{" "}
information. for more information.
</Text> </Text>
</Box> </Box>
</Banner> </Banner>
<OfflineBanner isOpen={sessionStatus === "offline"} /> <OfflineBanner isOpen={sessionStatus === "offline"} />
<ReconnectBanner isOpen={sessionStatus === "reconnecting"} /> <ReconnectBanner
isOpen={sessionStatus === "reconnecting"}
/>
<AuthModal <AuthModal
isOpen={sessionStatus === "auth"} isOpen={sessionStatus === "auth"}
onSubmit={handleAuthSubmit} onSubmit={handleAuthSubmit}
@ -147,6 +157,11 @@ function Game() {
</MapStageProvider> </MapStageProvider>
</PartyProvider> </PartyProvider>
</PlayerProvider> </PlayerProvider>
</TokenDataProvider>
</MapDataProvider>
</MapLoadingProvider>
</AssetURLsProvider>
</AssetsProvider>
); );
} }

View File

@ -1796,6 +1796,29 @@
resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-10.1.0.tgz#f0950bba18819512d42f7197e56c518aa491cf18" resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-10.1.0.tgz#f0950bba18819512d42f7197e56c518aa491cf18"
integrity sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg== integrity sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg==
"@dnd-kit/accessibility@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@dnd-kit/accessibility/-/accessibility-3.0.0.tgz#b56e3750414fd907b7d6972b3116aa8f96d07fde"
integrity sha512-QwaQ1IJHQHMMuAGOOYHQSx7h7vMZPfO97aDts8t5N/MY7n2QTDSnW+kF7uRQ1tVBkr6vJ+BqHWG5dlgGvwVjow==
dependencies:
tslib "^2.0.0"
"@dnd-kit/core@3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@dnd-kit/core/-/core-3.0.0.tgz#96dadb6b2dba05ab177e0190b33ae219017bc167"
integrity sha512-QxHLfZHOLkQWK0FPbr5hefWZzsdZfDuluKPwIK1bT2lwp/4hmFXRA6ivqX3FT4g8T0d2de2C1jxYhKM4H3uMQw==
dependencies:
"@dnd-kit/accessibility" "^3.0.0"
"@dnd-kit/utilities" "^2.0.0"
tslib "^2.0.0"
"@dnd-kit/utilities@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@dnd-kit/utilities/-/utilities-2.0.0.tgz#a8462dff65c6f606ecbe95273c7e263b14a1ab97"
integrity sha512-bjs49yMNzMM+BYRsBUhTqhTk6HEvhuY3leFt6Em6NaYGgygaMbtGbbXof/UXBv7rqyyi0OkmBBnrCCcxqS2t/g==
dependencies:
tslib "^2.0.0"
"@emotion/cache@^10.0.27": "@emotion/cache@^10.0.27":
version "10.0.29" version "10.0.29"
resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-10.0.29.tgz#87e7e64f412c060102d589fe7c6dc042e6f9d1e0" resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-10.0.29.tgz#87e7e64f412c060102d589fe7c6dc042e6f9d1e0"
@ -2008,11 +2031,6 @@
dependencies: dependencies:
"@hapi/hoek" "^8.3.0" "@hapi/hoek" "^8.3.0"
"@interactjs/types@1.10.8":
version "1.10.8"
resolved "https://registry.yarnpkg.com/@interactjs/types/-/types-1.10.8.tgz#098da479de9c5ac9c8ba97d113746b7dcd9c2204"
integrity sha512-qU2QfnN7r8AU4mSd2W3XmRtR0d35R1PReIT9b5YzpNLX9S0OQgNBLrEEFyXpa9alq/9h6wYNIwPCVAsknF5uZw==
"@istanbuljs/load-nyc-config@^1.0.0": "@istanbuljs/load-nyc-config@^1.0.0":
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"
@ -7274,13 +7292,6 @@ ini@^1.3.5:
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
interactjs@^1.10.8:
version "1.10.8"
resolved "https://registry.yarnpkg.com/interactjs/-/interactjs-1.10.8.tgz#a85b6e89ebf2ed88ea1678287ffcf0becf0dfb1c"
integrity sha512-hIU82lF9mplmAHVTUmZbHMHKm96AwlD0zWGuf9krKt2dhALHsMOdU+yVilPqIv1VpNAGV66F9B14Rfs4ulS2nA==
dependencies:
"@interactjs/types" "1.10.8"
internal-ip@^4.3.0: internal-ip@^4.3.0:
version "4.3.0" version "4.3.0"
resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907" resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907"
@ -12816,6 +12827,11 @@ tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
tslib@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c"
integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==
tsutils@^3.17.1: tsutils@^3.17.1:
version "3.17.1" version "3.17.1"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759"