diff --git a/packages/dashboard-v2/package.json b/packages/dashboard-v2/package.json
index c94cfa3f..7b620128 100644
--- a/packages/dashboard-v2/package.json
+++ b/packages/dashboard-v2/package.json
@@ -21,15 +21,21 @@
"dependencies": {
"@fontsource/sora": "^4.5.3",
"@fontsource/source-sans-pro": "^4.5.3",
+ "classnames": "^2.3.1",
+ "copy-text-to-clipboard": "^3.0.1",
"gatsby": "^4.6.2",
"gatsby-plugin-postcss": "^5.7.0",
"http-status-codes": "^2.2.0",
+ "nanoid": "^3.3.1",
+ "path-browserify": "^1.0.1",
"postcss": "^8.4.6",
"pretty-bytes": "^6.0.0",
"react": "^17.0.1",
"react-dom": "^17.0.1",
+ "react-dropzone": "^12.0.4",
"react-helmet": "^6.1.0",
"react-use": "^17.3.2",
+ "skynet-js": "^3.0.2",
"swr": "^1.2.2",
"tailwindcss": "^3.0.23"
},
diff --git a/packages/dashboard-v2/src/components/Uploader/Uploader.js b/packages/dashboard-v2/src/components/Uploader/Uploader.js
new file mode 100644
index 00000000..269d9fbe
--- /dev/null
+++ b/packages/dashboard-v2/src/components/Uploader/Uploader.js
@@ -0,0 +1,135 @@
+import { useCallback, useEffect, useState } from "react";
+import PropTypes from "prop-types";
+import cn from "classnames";
+import path from "path-browserify";
+import { useDropzone } from "react-dropzone";
+import { nanoid } from "nanoid";
+
+import { Button } from "../Button";
+import { UploadIcon } from "../Icons/icons/UploadIcon";
+import { FolderUploadIcon } from "../Icons/icons/FolderUploadIcon";
+
+import UploaderItem from "./UploaderItem";
+import { PlusIcon } from "../Icons";
+
+const MAX_PARALLEL_UPLOADS = 1;
+
+const getFilePath = (file) => file.webkitRelativePath || file.path || file.name;
+
+const getRootDirectory = (file) => {
+ const filePath = getFilePath(file);
+ const { root, dir } = path.parse(filePath);
+
+ return path.normalize(dir).slice(root.length).split(path.sep)[0];
+};
+
+const Uploader = ({ mode }) => {
+ const [uploads, setUploads] = useState([]);
+
+ const onUploadStateChange = useCallback((id, state) => {
+ setUploads((uploads) => {
+ const index = uploads.findIndex((upload) => upload.id === id);
+
+ return [...uploads.slice(0, index), { ...uploads[index], ...state }, ...uploads.slice(index + 1)];
+ });
+ }, []);
+
+ const handleDrop = async (files) => {
+ if (mode === "directory" && files.length) {
+ const name = getRootDirectory(files[0]); // get the file path from the first file
+ const size = files.reduce((acc, file) => acc + file.size, 0);
+
+ files = [{ name, size, files }];
+ }
+
+ setUploads((uploads) => [
+ ...files.map((file) => ({ id: nanoid(), file, progress: 0, mode, status: "enqueued" })),
+ ...uploads,
+ ]);
+ };
+
+ useEffect(() => {
+ const enqueued = uploads.filter(({ status }) => status === "enqueued");
+ const uploading = uploads.filter(({ status }) => ["uploading", "processing", "retrying"].includes(status));
+ const queue = enqueued.slice(0, MAX_PARALLEL_UPLOADS - uploading.length).map(({ id }) => id);
+
+ if (queue.length && uploading.length < MAX_PARALLEL_UPLOADS) {
+ setUploads((uploads) => {
+ return uploads.map((upload) => {
+ if (queue.includes(upload.id)) return { ...upload, status: "uploading" };
+ return upload;
+ });
+ });
+ }
+ }, [uploads]);
+
+ const { getRootProps, getInputProps, isDragActive, inputRef } = useDropzone({
+ onDrop: handleDrop,
+ useFsAccessApi: false,
+ });
+ const inputElement = inputRef.current;
+
+ useEffect(() => {
+ if (!inputElement) return;
+ if (mode === "directory") inputElement.setAttribute("webkitdirectory", "true");
+ if (mode === "file") inputElement.removeAttribute("webkitdirectory");
+ }, [inputElement, mode]);
+
+ return (
+
+
+
+ {uploads.length === 0 ? (
+
+ {mode === "file" ? (
+ <>
+
+
Add, or drop your files here to pin to Skynet
+ >
+ ) : (
+ <>
+
+
Drop any folder with an index.html file to deploy to Skynet
+ >
+ )}
+
+
+ ) : (
+
+
+
Add, or drop your files here
+
+ )}
+
+
+ {uploads.length > 0 && (
+
+ {uploads.map((upload) => (
+
+ ))}
+
+ )}
+
+ );
+};
+
+Uploader.propTypes = {
+ mode: PropTypes.oneOf(["file", "directory"]),
+};
+
+Uploader.defaultProps = {
+ mode: "file",
+};
+
+export default Uploader;
diff --git a/packages/dashboard-v2/src/components/Uploader/UploaderItem.js b/packages/dashboard-v2/src/components/Uploader/UploaderItem.js
new file mode 100644
index 00000000..7e2f5e89
--- /dev/null
+++ b/packages/dashboard-v2/src/components/Uploader/UploaderItem.js
@@ -0,0 +1,134 @@
+import * as React from "react";
+import cn from "classnames";
+import bytes from "pretty-bytes";
+import { StatusCodes } from "http-status-codes";
+import copy from "copy-text-to-clipboard";
+import path from "path-browserify";
+import { useTimeoutFn } from "react-use";
+// import ms from "ms";
+import { SkynetClient } from "skynet-js";
+import { ProgressBar } from "./ProgressBar";
+import UploaderItemIcon from "./UploaderItemIcon";
+import buildUploadErrorMessage from "./buildUploadErrorMessage";
+
+const skynetClient = new SkynetClient("https://siasky.net"); //TODO: proper API url
+
+const getFilePath = (file) => file.webkitRelativePath || file.path || file.name;
+
+const getRelativeFilePath = (file) => {
+ const filePath = getFilePath(file);
+ const { root, dir, base } = path.parse(filePath);
+ const relative = path.normalize(dir).slice(root.length).split(path.sep).slice(1);
+
+ return path.join(...relative, base);
+};
+
+export default function UploaderItem({ onUploadStateChange, upload }) {
+ const [copied, setCopied] = React.useState(false);
+ const [, , reset] = useTimeoutFn(() => setCopied(false), 3000);
+ const [retryTimeout, setRetryTimeout] = React.useState(3000); // retry delay after "429: TOO_MANY_REQUESTS"
+
+ const handleCopy = (url) => {
+ copy(url);
+ setCopied(true);
+ reset();
+ };
+
+ React.useEffect(() => {
+ if (upload.status === "uploading" && !upload.startedTime) {
+ onUploadStateChange(upload.id, { startedTime: Date.now() });
+
+ (async () => {
+ const onUploadProgress = (progress) => {
+ const status = progress === 1 ? "processing" : "uploading";
+
+ onUploadStateChange(upload.id, { status, progress });
+ };
+
+ try {
+ let response;
+
+ if (upload.mode === "directory") {
+ const files = upload.file.files;
+ const directory = files.reduce((acc, file) => ({ ...acc, [getRelativeFilePath(file)]: file }), {});
+ const name = encodeURIComponent(upload.file.name);
+
+ response = await skynetClient.uploadDirectory(directory, name, { onUploadProgress });
+ } else {
+ response = await skynetClient.uploadFile(upload.file, { onUploadProgress });
+ }
+
+ const url = await skynetClient.getSkylinkUrl(response.skylink, { subdomain: upload.mode === "directory" });
+
+ onUploadStateChange(upload.id, { status: "complete", url });
+ } catch (error) {
+ if (error?.response?.status === StatusCodes.TOO_MANY_REQUESTS) {
+ onUploadStateChange(upload.id, { status: "retrying", progress: 0 });
+
+ setTimeout(() => {
+ onUploadStateChange(upload.id, { status: "enqueued", startedTime: null });
+ setRetryTimeout((timeout) => timeout * 2); // increase timeout on next retry
+ }, retryTimeout);
+ } else {
+ onUploadStateChange(upload.id, { status: "error", error: buildUploadErrorMessage(error) });
+ }
+ }
+ })();
+ }
+ }, [onUploadStateChange, upload, retryTimeout]);
+
+ return (
+
+
+
+
+
+
+
+
+
+ {upload.status === "uploading" && (
+
+ Uploading {bytes(upload.file.size * upload.progress)} of {bytes(upload.file.size)}
+
+ )}
+ {upload.status === "enqueued" &&
Upload in queue, please wait}
+ {upload.status === "processing" &&
Processing...}
+ {upload.status === "complete" && (
+
+ {upload.url}
+
+ )}
+ {upload.status === "error" && upload.error &&
{upload.error}}
+ {upload.status === "retrying" && (
+
Too many parallel requests, retrying in {retryTimeout / 1000} seconds
+ )}
+
+
+
+
+ {upload.status === "uploading" && (
+ {Math.floor(upload.progress * 100)}%
+ )}
+ {upload.status === "processing" && Wait}
+ {upload.status === "complete" && (
+
+ )}
+
+
+
+
+
+ );
+}
diff --git a/packages/dashboard-v2/src/components/Uploader/UploaderItemIcon.js b/packages/dashboard-v2/src/components/Uploader/UploaderItemIcon.js
new file mode 100644
index 00000000..c0d6a766
--- /dev/null
+++ b/packages/dashboard-v2/src/components/Uploader/UploaderItemIcon.js
@@ -0,0 +1,20 @@
+import cn from "classnames";
+
+import { CircledCheckmarkIcon, CircledErrorIcon, CircledProgressIcon, CircledArrowUpIcon } from "../Icons";
+
+export default function UploaderItemIcon({ status }) {
+ switch (status) {
+ case "enqueued":
+ case "retrying":
+ case "uploading":
+ return ;
+ case "processing":
+ return ;
+ case "complete":
+ return ;
+ case "error":
+ return ;
+ default:
+ return null;
+ }
+}
diff --git a/packages/dashboard-v2/src/components/Uploader/buildUploadErrorMessage.js b/packages/dashboard-v2/src/components/Uploader/buildUploadErrorMessage.js
new file mode 100644
index 00000000..c41cd717
--- /dev/null
+++ b/packages/dashboard-v2/src/components/Uploader/buildUploadErrorMessage.js
@@ -0,0 +1,37 @@
+import { getReasonPhrase } from "http-status-codes";
+import bytes from "pretty-bytes";
+
+export default function buildUploadErrorMessage(error) {
+ // The request was made and the server responded with a status code that falls out of the range of 2xx
+ if (error.response) {
+ if (error.response.data.message) {
+ return `Upload failed with error: ${error.response.data.message}`;
+ }
+
+ const statusCode = error.response.status;
+ const statusText = getReasonPhrase(error.response.status);
+
+ return `Upload failed, our server received your request but failed with status code: ${statusCode} ${statusText}`;
+ }
+
+ // The request was made but no response was received. The best we can do is detect whether browser is online.
+ // This will be triggered mostly if the server is offline or misconfigured and doesn't respond to valid request.
+ if (error.request) {
+ if (!navigator.onLine) {
+ return "You are offline, please connect to the internet and try again";
+ }
+
+ // TODO: We should add a note "our team has been notified" and have some kind of notification with this error.
+ return "Server failed to respond to your request, please try again later.";
+ }
+
+ // Match the error message to a message returned by TUS when upload exceeds max file size
+ const matchTusMaxFileSizeError = error.message.match(/upload exceeds maximum size: \d+ > (?\d+)/);
+
+ if (matchTusMaxFileSizeError) {
+ return `File exceeds size limit of ${bytes(parseInt(matchTusMaxFileSizeError.groups.limit, 10))}`;
+ }
+
+ // TODO: We should add a note "our team has been notified" and have some kind of notification with this error.
+ return `Critical error, please refresh the application and try again. ${error.message}`;
+}
diff --git a/packages/dashboard-v2/yarn.lock b/packages/dashboard-v2/yarn.lock
index c03ae52a..e860c61c 100644
--- a/packages/dashboard-v2/yarn.lock
+++ b/packages/dashboard-v2/yarn.lock
@@ -1107,7 +1107,7 @@
core-js-pure "^3.20.2"
regenerator-runtime "^0.13.4"
-"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.8", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.16.7", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
+"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.8", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.16.7", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
version "7.17.2"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.2.tgz#66f68591605e59da47523c631416b18508779941"
integrity sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==
@@ -4518,6 +4518,11 @@ atob@^2.1.2:
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
+attr-accept@^2.2.2:
+ version "2.2.2"
+ resolved "https://registry.yarnpkg.com/attr-accept/-/attr-accept-2.2.2.tgz#646613809660110749e92f2c10833b70968d929b"
+ integrity sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==
+
autoprefixer@^10.4.0, autoprefixer@^10.4.2:
version "10.4.2"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.2.tgz#25e1df09a31a9fba5c40b578936b90d35c9d4d3b"
@@ -4548,7 +4553,7 @@ axe-core@^4.3.5:
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.1.tgz#7dbdc25989298f9ad006645cd396782443757413"
integrity sha512-gd1kmb21kwNuWr6BQz8fv6GNECPBnUasepcoLbekws23NVBLODdsClRZ+bQ8+9Uomf3Sm3+Vwn0oYG9NvwnJCw==
-axios@^0.21.1:
+axios@^0.21.0, axios@^0.21.1:
version "0.21.4"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575"
integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==
@@ -4817,6 +4822,13 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
+base32-encode@^1.1.1:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/base32-encode/-/base32-encode-1.2.0.tgz#e150573a5e431af0a998e32bdfde7045725ca453"
+ integrity sha512-cHFU8XeRyx0GgmoWi5qHMCVRiqU6J3MHWxVgun7jggCBUpVzm1Ir7M9dYr2whjSNc3tFeXfQ/oZjQu/4u55h9A==
+ dependencies:
+ to-data-view "^1.1.0"
+
base64-arraybuffer@0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz#9818c79e059b1355f97e0428a017c838e90ba812"
@@ -4888,6 +4900,11 @@ bl@^4.0.0, bl@^4.0.3:
inherits "^2.0.4"
readable-stream "^3.4.0"
+blakejs@^1.1.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/blakejs/-/blakejs-1.1.1.tgz#bf313053978b2cd4c444a48795710be05c785702"
+ integrity sha512-bLG6PHOCZJKNshTjGRBvET0vTciwQE6zFKOKKXPDJfwFBd4Ac0yBfPZqcGvGJap50l7ktvlpFqc2jGVaUgbJgg==
+
bluebird@^3.3.5, bluebird@^3.5.5, bluebird@^3.7.2:
version "3.7.2"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
@@ -5114,6 +5131,14 @@ buffer@^5.2.0, buffer@^5.5.0, buffer@^5.7.0:
base64-js "^1.3.1"
ieee754 "^1.1.13"
+buffer@^6.0.1:
+ version "6.0.3"
+ resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6"
+ integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==
+ dependencies:
+ base64-js "^1.3.1"
+ ieee754 "^1.2.1"
+
builtin-status-codes@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
@@ -5455,6 +5480,11 @@ class-utils@^0.3.5:
isobject "^3.0.0"
static-extend "^0.1.1"
+classnames@^2.3.1:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e"
+ integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==
+
clean-css@^4.2.3:
version "4.2.4"
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.4.tgz#733bf46eba4e607c6891ea57c24a989356831178"
@@ -5820,6 +5850,11 @@ copy-descriptor@^0.1.0:
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
+copy-text-to-clipboard@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/copy-text-to-clipboard/-/copy-text-to-clipboard-3.0.1.tgz#8cbf8f90e0a47f12e4a24743736265d157bce69c"
+ integrity sha512-rvVsHrpFcL4F2P8ihsoLdFHmd404+CMg71S756oRSeQgqk51U3kicGdnvfkrxva0xXH92SjGS62B0XIJsbh+9Q==
+
copy-to-clipboard@^3.3.1:
version "3.3.1"
resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz#115aa1a9998ffab6196f93076ad6da3b913662ae"
@@ -7675,6 +7710,13 @@ file-loader@^6.2.0:
loader-utils "^2.0.0"
schema-utils "^3.0.0"
+file-selector@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/file-selector/-/file-selector-0.4.0.tgz#59ec4f27aa5baf0841e9c6385c8386bef4d18b17"
+ integrity sha512-iACCiXeMYOvZqlF1kTiYINzgepRBymz1wwjiuup9u9nayhb6g4fSwiyJ/6adli+EPwrWtpgQAh2PoS7HukEGEg==
+ dependencies:
+ tslib "^2.0.3"
+
file-system-cache@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/file-system-cache/-/file-system-cache-1.0.5.tgz#84259b36a2bbb8d3d6eb1021d3132ffe64cfff4f"
@@ -11136,7 +11178,7 @@ nano-css@^5.3.1:
stacktrace-js "^2.0.2"
stylis "^4.0.6"
-nanoid@^3.1.23, nanoid@^3.2.0:
+nanoid@^3.1.23, nanoid@^3.2.0, nanoid@^3.3.1:
version "3.3.1"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35"
integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==
@@ -12582,7 +12624,7 @@ prompts@^2.4.0, prompts@^2.4.2:
kleur "^3.0.3"
sisteransi "^1.0.5"
-prop-types@^15.0.0, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.7.2:
+prop-types@^15.0.0, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.7.2, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@@ -12731,6 +12773,11 @@ querystring@^0.2.0:
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.1.tgz#40d77615bb09d16902a85c3e38aa8b5ed761c2dd"
integrity sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==
+querystringify@^2.1.1:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
+ integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
+
queue-microtask@^1.2.2:
version "1.2.3"
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
@@ -12867,6 +12914,15 @@ react-draggable@^4.4.3:
clsx "^1.1.1"
prop-types "^15.6.0"
+react-dropzone@^12.0.4:
+ version "12.0.4"
+ resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-12.0.4.tgz#b88eeaa2c7118f7fd042404682b17a1d466f2fcf"
+ integrity sha512-fcqHEYe1MzAghU6/Hz86lHDlBNsA+lO48nAcm7/wA+kIzwS6uuJbUG33tBZjksj7GAZ1iUQ6NHwjUURPmSGang==
+ dependencies:
+ attr-accept "^2.2.2"
+ file-selector "^0.4.0"
+ prop-types "^15.8.1"
+
react-element-to-jsx-string@^14.3.4:
version "14.3.4"
resolved "https://registry.yarnpkg.com/react-element-to-jsx-string/-/react-element-to-jsx-string-14.3.4.tgz#709125bc72f06800b68f9f4db485f2c7d31218a8"
@@ -13843,6 +13899,30 @@ sisteransi@^1.0.5:
resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==
+sjcl@^1.0.8:
+ version "1.0.8"
+ resolved "https://registry.yarnpkg.com/sjcl/-/sjcl-1.0.8.tgz#f2ec8d7dc1f0f21b069b8914a41a8f236b0e252a"
+ integrity sha512-LzIjEQ0S0DpIgnxMEayM1rq9aGwGRG4OnZhCdjx7glTaJtf4zRfpg87ImfjSJjoW9vKpagd82McDOwbRT5kQKQ==
+
+skynet-js@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/skynet-js/-/skynet-js-3.0.2.tgz#d08a33066ee85b86e4ffc7c31591239a88da6fbe"
+ integrity sha512-rbmpOGbDwg2FcsZ7HkmGhVaUwWO6kaysRFKTBC3yGiV+b6fbnpPPNCskvh8kWwbTsj+koWkSRUFYqG7cc+eTuA==
+ dependencies:
+ "@babel/runtime" "^7.11.2"
+ axios "^0.21.0"
+ base32-encode "^1.1.1"
+ base64-js "^1.3.1"
+ blakejs "^1.1.0"
+ buffer "^6.0.1"
+ mime "^2.5.2"
+ path-browserify "^1.0.1"
+ randombytes "^2.1.0"
+ sjcl "^1.0.8"
+ tweetnacl "^1.0.3"
+ url-join "^4.0.1"
+ url-parse "^1.4.7"
+
slash@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44"
@@ -14774,6 +14854,11 @@ to-arraybuffer@^1.0.0:
resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=
+to-data-view@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/to-data-view/-/to-data-view-1.1.0.tgz#08d6492b0b8deb9b29bdf1f61c23eadfa8994d00"
+ integrity sha512-1eAdufMg6mwgmlojAx3QeMnzB/BTVp7Tbndi3U7ftcT2zCZadjxkkmLmd97zmaxWi+sgGcgWrokmpEoy0Dn0vQ==
+
to-fast-properties@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
@@ -14952,6 +15037,11 @@ tunnel-agent@^0.6.0:
dependencies:
safe-buffer "^5.0.1"
+tweetnacl@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596"
+ integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==
+
type-check@^0.4.0, type-check@~0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
@@ -15245,6 +15335,11 @@ urix@^0.1.0:
resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=
+url-join@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7"
+ integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==
+
url-loader@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-4.1.1.tgz#28505e905cae158cf07c92ca622d7f237e70a4e2"
@@ -15261,6 +15356,14 @@ url-parse-lax@^3.0.0:
dependencies:
prepend-http "^2.0.0"
+url-parse@^1.4.7:
+ version "1.5.10"
+ resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1"
+ integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==
+ dependencies:
+ querystringify "^2.1.1"
+ requires-port "^1.0.0"
+
url@^0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"