Merge pull request #1822 from SkynetLabs/dashboard-v2-uploader

Dashboard v2 - Uploader
This commit is contained in:
Michał Leszczyk 2022-03-03 15:28:30 +01:00 committed by GitHub
commit 38c9323832
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 750 additions and 46 deletions

View File

@ -21,15 +21,21 @@
"dependencies": { "dependencies": {
"@fontsource/sora": "^4.5.3", "@fontsource/sora": "^4.5.3",
"@fontsource/source-sans-pro": "^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": "^4.6.2",
"gatsby-plugin-postcss": "^5.7.0", "gatsby-plugin-postcss": "^5.7.0",
"http-status-codes": "^2.2.0", "http-status-codes": "^2.2.0",
"nanoid": "^3.3.1",
"path-browserify": "^1.0.1",
"postcss": "^8.4.6", "postcss": "^8.4.6",
"pretty-bytes": "^6.0.0", "pretty-bytes": "^6.0.0",
"react": "^17.0.1", "react": "^17.0.1",
"react-dom": "^17.0.1", "react-dom": "^17.0.1",
"react-dropzone": "^12.0.4",
"react-helmet": "^6.1.0", "react-helmet": "^6.1.0",
"react-use": "^17.3.2", "react-use": "^17.3.2",
"skynet-js": "^3.0.2",
"swr": "^1.2.2", "swr": "^1.2.2",
"tailwindcss": "^3.0.23" "tailwindcss": "^3.0.23"
}, },

View File

@ -1,41 +1,21 @@
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import styled from "styled-components";
/** /**
* Primary UI component for user interaction * Primary UI component for user interaction
*/ */
export const Button = ({ primary, label, ...props }) => { export const Button = styled.button.attrs(({ $primary }) => ({
return ( type: "button",
<button className: `px-6 py-3 rounded-full font-sans uppercase text-xs tracking-wide text-palette-600
type="button" ${$primary ? "bg-primary" : "bg-white border-2 border-black"}`,
className={`min-w-button min-h-button rounded-full font-sans uppercase tracking-wide text-button }))``;
${primary ? "bg-primary" : "bg-white border-2 border-black"}`}
{...props}
>
{label}
</button>
);
};
Button.propTypes = { Button.propTypes = {
/** /**
* Is this the principal call to action on the page? * Is this the principal call to action on the page?
*/ */
primary: PropTypes.bool, $primary: PropTypes.bool,
/**
* What background color to use
*/
backgroundColor: PropTypes.string,
/**
* Button contents
*/
label: PropTypes.string.isRequired,
/**
* Optional click handler
*/
onClick: PropTypes.func,
}; };
Button.defaultProps = { Button.defaultProps = {
primary: false, $primary: false,
onClick: undefined,
}; };

View File

@ -0,0 +1,18 @@
import { withIconProps } from "../withIconProps";
export const CircledArrowUpIcon = withIconProps(({ size, ...props }) => (
<svg
width={size}
height={size}
viewBox="0 0 32 32"
xmlns="http://www.w3.org/2000/svg"
shapeRendering="geometricPrecision"
{...props}
>
<circle cx="16" cy="16" r="15" fill="transparent" stroke="currentColor" strokeWidth="2" />
<path
fill="currentColor"
d="M16.21,10.51a1,1,0,0,1,1.32.09l4.95,5L21.06,17l-3.29-3.32V23h-2V13.75L12.48,17,11.07,15.6l5-5Z"
/>
</svg>
));

View File

@ -0,0 +1,18 @@
import { withIconProps } from "../withIconProps";
export const CircledCheckmarkIcon = withIconProps(({ size, ...props }) => (
<svg
width={size}
height={size}
viewBox="0 0 32 32"
xmlns="http://www.w3.org/2000/svg"
shapeRendering="geometricPrecision"
{...props}
>
<circle cx="16" cy="16" r="15" fill="transparent" stroke="currentColor" strokeWidth="2" />
<polygon
fill="currentColor"
points="22.45 11.19 23.86 12.61 14.44 22.03 9.69 17.28 11.1 15.86 14.44 19.2 22.45 11.19"
/>
</svg>
));

View File

@ -0,0 +1,19 @@
import { withIconProps } from "../withIconProps";
export const CircledErrorIcon = withIconProps(({ size, ...props }) => (
<svg
width={size}
height={size}
viewBox="0 0 32 32"
xmlns="http://www.w3.org/2000/svg"
shapeRendering="geometricPrecision"
{...props}
>
<circle cx="16" cy="16" r="15" fill="transparent" stroke="currentColor" strokeWidth="2" />
<polygon
fill="currentColor"
fillRule="evenodd"
points="21.72 10.25 23.14 11.66 18.19 16.61 23.14 21.56 21.72 22.98 16.77 18.02 11.82 22.98 10.41 21.56 15.36 16.61 10.41 11.66 11.82 10.25 16.77 15.2 21.72 10.25"
/>
</svg>
));

View File

@ -0,0 +1,22 @@
import { withIconProps } from "../withIconProps";
export const CircledProgressIcon = withIconProps(({ size, ...props }) => (
<svg
width={size}
height={size}
viewBox="0 0 32 32"
xmlns="http://www.w3.org/2000/svg"
shapeRendering="geometricPrecision"
{...props}
>
<circle cx="16" cy="16" r="15" fill="transparent" stroke="currentColor" strokeWidth="2" />
<rect fill="currentColor" x="15" y="22" width="2" height="4" />
<rect fill="currentColor" x="8.34" y="20.66" width="4" height="2" transform="translate(-12.28 13.66) rotate(-45)" />
<rect fill="currentColor" x="20.66" y="19.66" width="2" height="4" transform="translate(-8.97 21.66) rotate(-45)" />
<rect fill="currentColor" x="6" y="15" width="4" height="2" />
<rect fill="currentColor" x="22" y="15" width="4" height="2" />
<rect fill="currentColor" x="9.34" y="8.34" width="2" height="4" transform="translate(-4.28 10.34) rotate(-45)" />
<rect fill="currentColor" x="19.66" y="9.34" width="4" height="2" transform="translate(-0.97 18.34) rotate(-45)" />
<rect fill="currentColor" x="15" y="6" width="2" height="4" />
</svg>
));

View File

@ -0,0 +1,24 @@
import { withIconProps } from "../withIconProps";
export const FolderUploadIcon = withIconProps((props) => (
<svg width="64" height="56" viewBox="0 0 64 56" xmlns="http://www.w3.org/2000/svg" {...props}>
<g id="Layer_2" data-name="Layer 2">
<g id="Layer_1-2" data-name="Layer 1">
<path fill="#00c65e" fillRule="evenodd" d="M26.1,48.27V34H20.35L31,23,41.34,34H36V52Z" />
<path
fill="none"
stroke="#222829"
strokeWidth="2"
strokeLinejoin="round"
d="M27,56V34H21L32,23,43,34H37V56M32,33v2m0,2v2m0,2v2m0,2v2m0,2v2m0,2v2"
/>
<path
fill="none"
stroke="#222829"
strokeWidth="2"
d="M58,17H40m18,4H40M12,17h2m2,0h2m2,0h2M20,51H4M59,8V5H23L19,1H1V48a3,3,0,0,0,6,0V11H63V51H43.8"
/>
</g>
</g>
</svg>
));

View File

@ -0,0 +1,14 @@
import { withIconProps } from "../withIconProps";
export const PlusIcon = withIconProps(({ size, ...props }) => (
<svg
width={size}
height={size}
viewBox="0 0 32 32"
xmlns="http://www.w3.org/2000/svg"
shapeRendering="geometricPrecision"
{...props}
>
<path d="M18.67,0V13.33H32v5.34H18.67V32H13.33V18.67H0V13.33H13.33V0Z" fill="currentColor" />
</svg>
));

View File

@ -0,0 +1,40 @@
import { withIconProps } from "../withIconProps";
export const UploadIcon = withIconProps((props) => (
<svg
width="64"
height="55"
viewBox="0 0 64 55"
xmlns="http://www.w3.org/2000/svg"
x="0px"
y="0px"
shapeRendering="geometricPrecision"
{...props}
>
<g>
<path
fillRule="evenodd"
clipRule="evenOdd"
fill="#00C65E"
d="M26.1,47.3V33h-5.7L31,22l10.3,11H36v18L26.1,47.3z"
/>
<g>
<path
fill="none"
stroke="#0D0D0D"
strokeWidth="2"
d="M42,41h9c6.6,0,12-5.4,12-12c0-5.9-4-11-10-11c0-5-3-9-9-9h-1c-2.7-4.9-8-8-14-8c-8.8,0-16,7.2-16,16
C6.4,17,1,22.4,1,29c0,6.6,5.4,12,12,12h9"
/>
<path fill="none" stroke="#0D0D0D" strokeWidth="2" d="M19,18c0-6.1,5-11,10-11" />
<path
fill="none"
stroke="#0D0D0D"
strokeWidth="2"
strokeLinejoin="round"
d="M26,55V33h-6l11-11l11,11h-6v22 M31,32v2 M31,36v2 M31,40v2 M31,44v2 M31,48v2 M31,52v2"
/>
</g>
</g>
</svg>
));

View File

@ -4,3 +4,8 @@ export * from "./icons/LockClosedIcon";
export * from "./icons/SkynetLogoIcon"; export * from "./icons/SkynetLogoIcon";
export * from "./icons/ArrowRightIcon"; export * from "./icons/ArrowRightIcon";
export * from "./icons/InfoIcon"; export * from "./icons/InfoIcon";
export * from "./icons/CircledCheckmarkIcon";
export * from "./icons/CircledErrorIcon";
export * from "./icons/CircledProgressIcon";
export * from "./icons/CircledArrowUpIcon";
export * from "./icons/PlusIcon";

View File

@ -14,7 +14,7 @@ export default function Bullets({ visibleSlides, activeIndex, allSlides, changeS
key={index} key={index}
type="button" type="button"
className={`rounded-full w-3 h-3 ${activeIndex === index ? "bg-primary" : "border-2 cursor-pointer"}`} className={`rounded-full w-3 h-3 ${activeIndex === index ? "bg-primary" : "border-2 cursor-pointer"}`}
onClick={() => changeSlide(index)} onClick={(event) => changeSlide(event, index)}
/> />
))} ))}
</div> </div>

View File

@ -1,7 +1,6 @@
import * as React from "react"; import * as React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import styled, { css } from "styled-components"; import styled, { css } from "styled-components";
import theme from "../../lib/theme";
import useActiveBreakpoint from "./useActiveBreakpoint"; import useActiveBreakpoint from "./useActiveBreakpoint";
import Bullets from "./Bullets"; import Bullets from "./Bullets";
@ -32,7 +31,9 @@ const Slider = ({ slides, breakpoints }) => {
const { visibleSlides, scrollable } = useActiveBreakpoint(breakpoints); const { visibleSlides, scrollable } = useActiveBreakpoint(breakpoints);
const [activeIndex, setActiveIndex] = React.useState(0); const [activeIndex, setActiveIndex] = React.useState(0);
const changeSlide = React.useCallback( const changeSlide = React.useCallback(
(index) => { (event, index) => {
event.preventDefault();
event.stopPropagation();
setActiveIndex(Math.min(index, slides.length - visibleSlides)); // Don't let it scroll too far setActiveIndex(Math.min(index, slides.length - visibleSlides)); // Don't let it scroll too far
}, },
[slides, visibleSlides, setActiveIndex] [slides, visibleSlides, setActiveIndex]
@ -62,7 +63,7 @@ const Slider = ({ slides, breakpoints }) => {
<div key={`slide-${index}`}> <div key={`slide-${index}`}>
<Slide <Slide
isVisible={isVisible || !scrollable} isVisible={isVisible || !scrollable}
onClick={scrollable && !isVisible ? () => changeSlide(index) : null} onClickCapture={scrollable && !isVisible ? (event) => changeSlide(event, index) : null}
> >
{slide} {slide}
</Slide> </Slide>

View File

@ -1,15 +1,33 @@
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { useEffect, useState } from "react";
/** /**
* Besides documented props, it accepts all HMTL attributes a `<div>` element does. * Besides documented props, it accepts all HMTL attributes a `<div>` element does.
*/ */
export const TabPanel = ({ children, active, tabId, ...props }) => { export const TabPanel = ({ children, active, tabId, ...props }) => {
if (!active) { const [wasActivated, setWasActivated] = useState(false);
useEffect(() => {
if (active) {
setWasActivated(true);
}
}, [active]);
// If the TabPanel was never activated, let's not render its children at all.
// We'll only render them after the first activation and then won't unmount them
// unless the entire TabPanel is unmounted, too.
if (!active && !wasActivated) {
return null; return null;
} }
return ( return (
<div role="tabpanel" id={`tabpanel-${tabId}`} aria-labelledby={`tab-${tabId}`} {...props}> <div
role="tabpanel"
id={`tabpanel-${tabId}`}
aria-labelledby={`tab-${tabId}`}
{...props}
style={{ display: active ? "block" : "none" }}
>
{children} {children}
</div> </div>
); );

View File

@ -6,11 +6,11 @@ import { ActiveTabIndicator } from "./ActiveTabIndicator";
import { usePrefixedTabIds, useTabsChildren } from "./hooks"; import { usePrefixedTabIds, useTabsChildren } from "./hooks";
const Container = styled.div.attrs({ const Container = styled.div.attrs({
className: "tabs-container", className: "tabs-container flex flex-col h-full",
})``; })``;
const Header = styled.div.attrs({ const Header = styled.div.attrs({
className: "relative flex justify-start overflow-hidden", className: "relative flex justify-start overflow-hidden grow-0 shrink-0",
})``; })``;
const TabList = styled.div.attrs(({ variant }) => ({ const TabList = styled.div.attrs(({ variant }) => ({
@ -26,7 +26,7 @@ const Divider = styled.div.attrs({
right: calc(-100vw - 2px); right: calc(-100vw - 2px);
`; `;
const Body = styled.div``; const Body = styled.div.attrs({ className: "grow min-h-0" })``;
/** /**
* Besides documented props, it accepts all HMTL attributes a `<div>` element does. * Besides documented props, it accepts all HMTL attributes a `<div>` element does.

View File

@ -0,0 +1,71 @@
import cn from "classnames";
import PropTypes from "prop-types";
import styled, { css, keyframes } from "styled-components";
const moveAnimation = keyframes`
0% {
background-position: 0 0;
}
100% {
background-position: 15px 0;
}
`;
const Container = styled.div.attrs(({ $status }) => ({
className: cn("flex relative rounded-sm h-1", { "bg-palette-200": $status === "uploading" }),
}))``;
const Indicator = styled.div.attrs(({ $status }) => ({
className: cn(
`
rounded-sm bg-[length:15px_10px]
`,
{
"bg-primary": $status === "uploading" || $status === "complete",
"text-primary": $status !== "error",
"text-error": $status === "error",
"bg-dashed": $status === "error" || $status === "enqueued" || $status === "processing",
}
),
}))`
width: ${({ $status, $percentage }) => ($status === "uploading" ? $percentage : 100)}%;
&.bg-dashed {
opacity: 0.4;
background-image: linear-gradient(
-60deg,
transparent,
transparent 30%,
currentColor 30%,
currentColor 70%,
transparent 70%,
transparent
);
animation: ${css`
${moveAnimation} 1s linear infinite
`};
}
`;
/**
* Primary UI component for indicating progress of a given task
*/
export const ProgressBar = ({ status, percentage, ...props }) => (
<Container $status={status} {...props}>
<Indicator $status={status} $percentage={percentage} />
</Container>
);
ProgressBar.propTypes = {
/**
* Status of the task
*/
status: PropTypes.oneOf(["complete", "enqueued", "error", "uploading", "processing"]),
/**
* Progress of the task (in case status is "uploading")
*/
percentage: PropTypes.number,
};
ProgressBar.defaultProps = {
status: "enqueued",
percentage: 0,
};

View File

@ -0,0 +1,37 @@
import React from "react";
import { ProgressBar } from "./ProgressBar";
// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
export default {
title: "SkynetLibrary/ProgressBar",
component: ProgressBar,
// More on argTypes: https://storybook.js.org/docs/react/api/argtypes
};
// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
const Template = (args) => <ProgressBar {...args} />;
export const Uploading = Template.bind({});
// More on args: https://storybook.js.org/docs/react/writing-stories/args
Uploading.args = {
status: "uploading",
percentage: 65,
};
export const Complete = Template.bind({});
// More on args: https://storybook.js.org/docs/react/writing-stories/args
Complete.args = {
status: "complete",
};
export const Enqueued = Template.bind({});
// More on args: https://storybook.js.org/docs/react/writing-stories/args
Enqueued.args = {
status: "enqueued",
};
export const Error = Template.bind({});
// More on args: https://storybook.js.org/docs/react/writing-stories/args
Error.args = {
status: "error",
};

View File

@ -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 (
<div className="h-full">
<div
className={cn("relative px-8 text-palette-400 text-center flex flex-col justify-center", {
"drop-active": isDragActive,
"min-h-full": uploads.length === 0,
"bg-palette-100/50": !isDragActive,
"bg-palette-100": isDragActive,
})}
{...getRootProps()}
disabled={true}
>
<input {...getInputProps()} />
{uploads.length === 0 ? (
<div className="flex flex-col items-center">
{mode === "file" ? (
<>
<UploadIcon />
<p className="py-4">Add, or drop your files here to pin to Skynet</p>
</>
) : (
<>
<FolderUploadIcon />
<p className="py-4">Drop any folder with an index.html file to deploy to Skynet</p>
</>
)}
<Button $primary>Add files</Button>
</div>
) : (
<div className="p-5">
<Button $primary className="w-[40px] h-[40px] !p-0 inline-flex justify-center items-center">
<PlusIcon size={12} />
</Button>
<span className="ml-4">Add, or drop your files here</span>
</div>
)}
</div>
{uploads.length > 0 && (
<div className="flex flex-col space-y-4 py-10">
{uploads.map((upload) => (
<UploaderItem key={upload.id} onUploadStateChange={onUploadStateChange} upload={upload} />
))}
</div>
)}
</div>
);
};
Uploader.propTypes = {
mode: PropTypes.oneOf(["file", "directory"]),
};
Uploader.defaultProps = {
mode: "file",
};
export default Uploader;

View File

@ -0,0 +1,133 @@
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 { 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 (
<div>
<div className="grid grid-cols-[40px_1fr_min-content]">
<div className="p-1">
<UploaderItemIcon status={upload.status} />
</div>
<div className="flex flex-col ml-3 overflow-hidden">
<div className="text-palette-600">
<div className="truncate">{upload.file.name}</div>
</div>
<div className="flex justify-between text-palette-400 text-xs space-x-2 items-end">
<div className="font-content truncate">
{upload.status === "uploading" && (
<span className="tabular-nums">
Uploading {bytes(upload.file.size * upload.progress)} of {bytes(upload.file.size)}
</span>
)}
{upload.status === "enqueued" && <span className="text-palette-300">Upload in queue, please wait</span>}
{upload.status === "processing" && <span className="text-palette-300">Processing...</span>}
{upload.status === "complete" && (
<a href={upload.url} className="hover:text-primary transition-colors duration-200">
{upload.url}
</a>
)}
{upload.status === "error" && upload.error && <span className="text-error">{upload.error}</span>}
{upload.status === "retrying" && (
<span>Too many parallel requests, retrying in {retryTimeout / 1000} seconds</span>
)}
</div>
</div>
</div>
<div className="text-base self-end ml-2">
{upload.status === "uploading" && (
<span className="uppercase tabular-nums">{Math.floor(upload.progress * 100)}%</span>
)}
{upload.status === "processing" && <span className="uppercase text-palette-300">Wait</span>}
{upload.status === "complete" && (
<button
className="uppercase hover:text-primary transition-colors duration-200"
onClick={() => handleCopy(upload.url)}
>
{copied ? (
<span className={cn({ hidden: !copied })}>Copied</span>
) : (
<span className={cn({ hidden: copied })}>Copy</span>
)}
</button>
)}
</div>
</div>
<ProgressBar className="mt-1" status={upload.status} percentage={upload.progress * 100} />
</div>
);
}

View File

@ -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 <CircledArrowUpIcon className={cn({ "text-palette-300": status === "enqueued" })} />;
case "processing":
return <CircledProgressIcon className="animate-[spin_3s_linear_infinite]" />;
case "complete":
return <CircledCheckmarkIcon />;
case "error":
return <CircledErrorIcon className="text-error" />;
default:
return null;
}
}

View File

@ -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+ > (?<limit>\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}`;
}

View File

@ -9,6 +9,7 @@ import LatestActivity from "../components/LatestActivity/LatestActivity";
import DashboardLayout from "../layouts/DashboardLayout"; import DashboardLayout from "../layouts/DashboardLayout";
import Slider from "../components/Slider/Slider"; import Slider from "../components/Slider/Slider";
import CurrentUsage from "../components/CurrentUsage"; import CurrentUsage from "../components/CurrentUsage";
import Uploader from "../components/Uploader/Uploader";
const IndexPage = () => { const IndexPage = () => {
const showRecentActivity = useMedia(`(min-width: ${theme.screens.md})`); const showRecentActivity = useMedia(`(min-width: ${theme.screens.md})`);
@ -18,15 +19,15 @@ const IndexPage = () => {
<div className="w-full"> <div className="w-full">
<Slider <Slider
slides={[ slides={[
<Panel title="Upload"> <Panel title="Upload" className="h-[330px]">
<Tabs variant="fill"> <Tabs variant="fill">
<Tab id="files" title="Files" /> <Tab id="files" title="Files" />
<Tab id="directory" title="Directory" /> <Tab id="directory" title="Directory" />
<TabPanel tabId="files"> <TabPanel tabId="files" className="h-full overflow-y-auto">
<div className="w-full py-16 bg-palette-100/50 text-center">Upload files...</div> <Uploader mode="file" />
</TabPanel> </TabPanel>
<TabPanel tabId="directory"> <TabPanel tabId="directory" className="h-full overflow-y-auto">
<div className="w-full py-16 bg-palette-100/50 text-center">Upload a directory...</div> <Uploader mode="directory" />
</TabPanel> </TabPanel>
</Tabs> </Tabs>
</Panel>, </Panel>,
@ -36,6 +37,7 @@ const IndexPage = () => {
<ArrowRightIcon /> Usage <ArrowRightIcon /> Usage
</> </>
} }
className="h-[330px]"
> >
<CurrentUsage /> <CurrentUsage />
</Panel>, </Panel>,
@ -45,6 +47,7 @@ const IndexPage = () => {
<ArrowRightIcon /> Current plan <ArrowRightIcon /> Current plan
</> </>
} }
className="h-[330px]"
> >
<ul> <ul>
<li>Current</li> <li>Current</li>

View File

@ -1107,7 +1107,7 @@
core-js-pure "^3.20.2" core-js-pure "^3.20.2"
regenerator-runtime "^0.13.4" 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" version "7.17.2"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.2.tgz#66f68591605e59da47523c631416b18508779941" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.2.tgz#66f68591605e59da47523c631416b18508779941"
integrity sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw== 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" resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== 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: autoprefixer@^10.4.0, autoprefixer@^10.4.2:
version "10.4.2" version "10.4.2"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.2.tgz#25e1df09a31a9fba5c40b578936b90d35c9d4d3b" 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" resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.1.tgz#7dbdc25989298f9ad006645cd396782443757413"
integrity sha512-gd1kmb21kwNuWr6BQz8fv6GNECPBnUasepcoLbekws23NVBLODdsClRZ+bQ8+9Uomf3Sm3+Vwn0oYG9NvwnJCw== integrity sha512-gd1kmb21kwNuWr6BQz8fv6GNECPBnUasepcoLbekws23NVBLODdsClRZ+bQ8+9Uomf3Sm3+Vwn0oYG9NvwnJCw==
axios@^0.21.1: axios@^0.21.0, axios@^0.21.1:
version "0.21.4" version "0.21.4"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575"
integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== 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" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== 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: base64-arraybuffer@0.1.4:
version "0.1.4" version "0.1.4"
resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz#9818c79e059b1355f97e0428a017c838e90ba812" 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" inherits "^2.0.4"
readable-stream "^3.4.0" 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: bluebird@^3.3.5, bluebird@^3.5.5, bluebird@^3.7.2:
version "3.7.2" version "3.7.2"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" 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" base64-js "^1.3.1"
ieee754 "^1.1.13" 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: builtin-status-codes@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" 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" isobject "^3.0.0"
static-extend "^0.1.1" 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: clean-css@^4.2.3:
version "4.2.4" version "4.2.4"
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.4.tgz#733bf46eba4e607c6891ea57c24a989356831178" 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" resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= 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: copy-to-clipboard@^3.3.1:
version "3.3.1" version "3.3.1"
resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz#115aa1a9998ffab6196f93076ad6da3b913662ae" 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" loader-utils "^2.0.0"
schema-utils "^3.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: file-system-cache@^1.0.5:
version "1.0.5" version "1.0.5"
resolved "https://registry.yarnpkg.com/file-system-cache/-/file-system-cache-1.0.5.tgz#84259b36a2bbb8d3d6eb1021d3132ffe64cfff4f" 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" stacktrace-js "^2.0.2"
stylis "^4.0.6" 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" version "3.3.1"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35"
integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw== integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==
@ -12582,7 +12624,7 @@ prompts@^2.4.0, prompts@^2.4.2:
kleur "^3.0.3" kleur "^3.0.3"
sisteransi "^1.0.5" 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" version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== 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" resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.1.tgz#40d77615bb09d16902a85c3e38aa8b5ed761c2dd"
integrity sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg== 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: queue-microtask@^1.2.2:
version "1.2.3" version "1.2.3"
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" 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" clsx "^1.1.1"
prop-types "^15.6.0" 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: react-element-to-jsx-string@^14.3.4:
version "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" 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" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== 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: slash@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" 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" resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= 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: to-fast-properties@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" 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: dependencies:
safe-buffer "^5.0.1" 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: type-check@^0.4.0, type-check@~0.4.0:
version "0.4.0" version "0.4.0"
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" 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" resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= 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: url-loader@^4.1.1:
version "4.1.1" version "4.1.1"
resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-4.1.1.tgz#28505e905cae158cf07c92ca622d7f237e70a4e2" 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: dependencies:
prepend-http "^2.0.0" 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: url@^0.11.0:
version "0.11.0" version "0.11.0"
resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"