uploader
This commit is contained in:
parent
7c228c3f4b
commit
4980a99ef2
|
@ -9,6 +9,7 @@
|
||||||
"@fontsource/source-sans-pro": "^4.2.2",
|
"@fontsource/source-sans-pro": "^4.2.2",
|
||||||
"@svgr/webpack": "^5.5.0",
|
"@svgr/webpack": "^5.5.0",
|
||||||
"autoprefixer": "^10.2.5",
|
"autoprefixer": "^10.2.5",
|
||||||
|
"bytes": "^3.1.0",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"framer-motion": "^4.0.3",
|
"framer-motion": "^4.0.3",
|
||||||
"gatsby": "^3.0.1",
|
"gatsby": "^3.0.1",
|
||||||
|
@ -26,16 +27,21 @@
|
||||||
"gatsby-transformer-json": "^3.1.0",
|
"gatsby-transformer-json": "^3.1.0",
|
||||||
"gatsby-transformer-sharp": "^3.0.0",
|
"gatsby-transformer-sharp": "^3.0.0",
|
||||||
"gbimage-bridge": "^0.1.1",
|
"gbimage-bridge": "^0.1.1",
|
||||||
|
"http-status-codes": "^2.1.4",
|
||||||
"normalize.css": "^8.0.1",
|
"normalize.css": "^8.0.1",
|
||||||
|
"path-browserify": "^0.0.1",
|
||||||
|
"polished": "^4.1.1",
|
||||||
"popmotion": "^9.3.4",
|
"popmotion": "^9.3.4",
|
||||||
"postcss": "^8.2.8",
|
"postcss": "^8.2.8",
|
||||||
"preact-svg-loader": "^0.2.1",
|
"preact-svg-loader": "^0.2.1",
|
||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
"react": "^17.0.1",
|
"react": "^17.0.1",
|
||||||
"react-dom": "^17.0.1",
|
"react-dom": "^17.0.1",
|
||||||
|
"react-dropzone": "^11.3.1",
|
||||||
"react-helmet": "^6.1.0",
|
"react-helmet": "^6.1.0",
|
||||||
"react-svg-loader": "^3.0.3",
|
"react-svg-loader": "^3.0.3",
|
||||||
"react-use": "^17.2.1",
|
"react-use": "^17.2.1",
|
||||||
|
"skynet-js": "^3.0.0",
|
||||||
"tailwindcss": "^2.0.3"
|
"tailwindcss": "^2.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
export { default } from "./HeroStartPage";
|
|
@ -0,0 +1,14 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="66" height="66" viewBox="0 0 66 66">
|
||||||
|
<defs>
|
||||||
|
<filter id="add-a">
|
||||||
|
<feColorMatrix in="SourceGraphic" values="0 0 0 0 0.050980 0 0 0 0 0.050980 0 0 0 0 0.050980 0 0 0 1.000000 0"/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
<g fill="none" fill-rule="evenodd" transform="translate(-.097 .903)">
|
||||||
|
<circle cx="33.097" cy="33.097" r="32" fill="#00C65E"/>
|
||||||
|
<path stroke="#0D0D0D" stroke-width="2" d="M59.7279532,50.4620828 C62.9634424,45.4615529 64.8418319,39.5010119 64.8418319,33.1017494 C64.8418319,15.4286374 50.5149439,1.1017494 32.8418319,1.1017494 C15.1687199,1.1017494 0.84183186,15.4286374 0.84183186,33.1017494" transform="rotate(-2 32.842 25.782)"/>
|
||||||
|
<g filter="url(#add-a)" transform="translate(13.097 13.097)">
|
||||||
|
<path fill="#FFF" d="M21,12 L21,19 L28,19 L28,21 L21,21 L21,28 L19,28 L19,21 L12,21 L12,19 L19,19 L19,12 L21,12 Z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 912 B |
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40">
|
||||||
|
<path fill="#0D0D0D" d="M20,4 C28.836,4 36,11.164 36,20 C36,28.836 28.836,36 20,36 C11.164,36 4,28.836 4,20 C4,11.164 11.164,4 20,4 Z M20,6 C12.2685695,6 6,12.2685695 6,20 C6,27.7314305 12.2685695,34 20,34 C27.7314305,34 34,27.7314305 34,20 C34,12.2685695 27.7314305,6 20,6 Z M19.4363719,13.4966307 C19.8299265,13.1934645 20.3968364,13.2236414 20.7557217,13.5855059 L20.7557217,13.5855059 L25.7100234,18.5809324 L24.2899766,19.9892886 L21,16.673 L21,26 L19,26 L19,16.733 L15.7038828,20.0000001 L14.2961172,18.5793679 L19.3418155,13.5793679 Z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 638 B |
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40">
|
||||||
|
<path fill="#0D0D0D" d="M20,4 C28.836,4 36,11.164 36,20 C36,28.836 28.836,36 20,36 C11.164,36 4,28.836 4,20 C4,11.164 11.164,4 20,4 Z M20,6 C12.2685695,6 6,12.2685695 6,20 C6,27.7314305 12.2685695,34 20,34 C27.7314305,34 34,27.7314305 34,20 C34,12.2685695 27.7314305,6 20,6 Z M26.0574126,15.2928932 L27.4716262,16.7071068 L18.0464454,26.1322875 L13.2928932,21.3787353 L14.7071068,19.9645218 L18.046,23.303 L26.0574126,15.2928932 Z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 527 B |
|
@ -0,0 +1,20 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="62" height="54" viewBox="0 0 62 54">
|
||||||
|
<defs>
|
||||||
|
<filter id="cloud-a">
|
||||||
|
<feColorMatrix in="SourceGraphic" values="0 0 0 0 0.050980 0 0 0 0 0.050980 0 0 0 0 0.050980 0 0 0 1.000000 0"/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
<g fill="none" fill-rule="evenodd">
|
||||||
|
<polygon fill="#00C65E" points="25.097 46.291 25.097 32 19.354 32 30 21 40.343 32 35 32 35 50"/>
|
||||||
|
<g filter="url(#cloud-a)">
|
||||||
|
<path stroke="#222829" stroke-width="2" d="M41 40L50 40C56.627 40 62 34.628 62 28 62 22.053 58 17 52 17 52 12 49 8 43 8L42 8C39.272 3.076 34.028 0 28 0 19.164 0 12 7.164 12 16 5.373 16 0 21.373 0 28 0 34.628 5.373 40 12 40L21 40M18 17C18 10.925 23 6 28 6"/>
|
||||||
|
<polyline stroke="#222829" stroke-linejoin="round" stroke-width="2" points="25 54 25 32 19 32 30 21 41 32 35 32 35 54"/>
|
||||||
|
<line x1="30" x2="30" y1="31" y2="33" stroke="#222829" stroke-linejoin="round" stroke-width="2"/>
|
||||||
|
<line x1="30" x2="30" y1="35" y2="37" stroke="#222829" stroke-linejoin="round" stroke-width="2"/>
|
||||||
|
<line x1="30" x2="30" y1="39" y2="41" stroke="#222829" stroke-linejoin="round" stroke-width="2"/>
|
||||||
|
<line x1="30" x2="30" y1="43" y2="45" stroke="#222829" stroke-linejoin="round" stroke-width="2"/>
|
||||||
|
<line x1="30" x2="30" y1="47" y2="49" stroke="#222829" stroke-linejoin="round" stroke-width="2"/>
|
||||||
|
<line x1="30" x2="30" y1="51" y2="53" stroke="#222829" stroke-linejoin="round" stroke-width="2"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
|
@ -1,5 +1,9 @@
|
||||||
|
export { default as Add } from "./Add.svg";
|
||||||
export { default as ArrowRight } from "./ArrowRight.svg";
|
export { default as ArrowRight } from "./ArrowRight.svg";
|
||||||
|
export { default as ArrowUpCircle } from "./ArrowUpCircle.svg";
|
||||||
export { default as CheckActive } from "./CheckActive.svg";
|
export { default as CheckActive } from "./CheckActive.svg";
|
||||||
|
export { default as CheckCircle } from "./CheckCircle.svg";
|
||||||
|
export { default as Cloud } from "./Cloud.svg";
|
||||||
export { default as DiscordSmall } from "./DiscordSmall.svg";
|
export { default as DiscordSmall } from "./DiscordSmall.svg";
|
||||||
export { default as DiscordSmallWhite } from "./DiscordSmallWhite.svg";
|
export { default as DiscordSmallWhite } from "./DiscordSmallWhite.svg";
|
||||||
export { default as LogoBlackText } from "./LogoBlackText.svg";
|
export { default as LogoBlackText } from "./LogoBlackText.svg";
|
||||||
|
|
|
@ -15,16 +15,26 @@ import Navigation from "../Navigation/Navigation";
|
||||||
import NewsHeader from "../NewsHeader/NewsHeader";
|
import NewsHeader from "../NewsHeader/NewsHeader";
|
||||||
import Footer from "../Footer/Footer";
|
import Footer from "../Footer/Footer";
|
||||||
import FooterNavigation from "../FooterNavigation/FooterNavigation";
|
import FooterNavigation from "../FooterNavigation/FooterNavigation";
|
||||||
// import { useWindowScroll } from "react-use";
|
import { useWindowScroll } from "react-use";
|
||||||
import classnames from "classnames";
|
import classnames from "classnames";
|
||||||
|
import { readableColor } from "polished";
|
||||||
|
|
||||||
|
const modeMap = { "#fff": "dark", "#000": "light" };
|
||||||
|
|
||||||
const StickyHeader = () => {
|
const StickyHeader = () => {
|
||||||
// const { y } = useWindowScroll();
|
useWindowScroll();
|
||||||
|
|
||||||
|
const ref = React.useRef(null);
|
||||||
|
const element = document.elementFromPoint(0, ref.current?.offsetHeight ?? 0);
|
||||||
|
|
||||||
|
const backgroundColor = window.getComputedStyle(element, null).getPropertyValue("background-color");
|
||||||
|
const color = React.useMemo(() => readableColor(backgroundColor), [backgroundColor]);
|
||||||
|
const mode = modeMap[color];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classnames("sticky top-0", { "bg-white border-b border-palette-200": false })}>
|
<div ref={ref} className={classnames("sticky top-0 z-50", { "bg-white border-b border-palette-200": false })}>
|
||||||
<NewsHeader />
|
<NewsHeader />
|
||||||
<Navigation mode={false ? "light" : "dark"} />
|
<Navigation mode={mode} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
export { default } from "./Layout";
|
|
@ -7,6 +7,8 @@ import LogoBlackText from "../Icons/LogoBlackText.svg";
|
||||||
import MenuMobile from "../Icons/MenuMobile.svg";
|
import MenuMobile from "../Icons/MenuMobile.svg";
|
||||||
import MenuMobileClose from "../Icons/MenuMobileClose.svg";
|
import MenuMobileClose from "../Icons/MenuMobileClose.svg";
|
||||||
import DiscordSmall from "../Icons/DiscordSmall.svg";
|
import DiscordSmall from "../Icons/DiscordSmall.svg";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import { useWindowSize, useWindowScroll } from "react-use";
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{ title: "Home", route: "/" },
|
{ title: "Home", route: "/" },
|
||||||
|
@ -48,17 +50,45 @@ const SignUpButton = ({ className, ...props }) => (
|
||||||
);
|
);
|
||||||
|
|
||||||
const Navigation = ({ mode }) => {
|
const Navigation = ({ mode }) => {
|
||||||
|
const navRef = React.useRef(null);
|
||||||
const [open, setOpen] = React.useState(false);
|
const [open, setOpen] = React.useState(false);
|
||||||
|
const windowSize = useWindowSize();
|
||||||
|
const { y: offsetY } = useWindowScroll();
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setOpen(false);
|
||||||
|
}, [windowSize, setOpen]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (open && document.body.style.overflow !== "hidden") {
|
||||||
|
document.body.style.overflow = "hidden";
|
||||||
|
} else if (document.body.style.overflow === "hidden") {
|
||||||
|
document.body.style.overflow = "unset";
|
||||||
|
}
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
|
const mobileMenuOffset = navRef.current ? navRef.current.offsetTop : 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className={classnames("relative px-8 py-12", { "bg-palette-600": open }, "desktop:bg-transparent")}>
|
<motion.nav
|
||||||
<div className="max-w-layout mx-auto">
|
className={classnames("relative px-8 transition-colors duration-500", {
|
||||||
|
"bg-white border-b border-palette-200": mode === "light",
|
||||||
|
"bg-palette-600 bg-opacity-50": mode === "dark",
|
||||||
|
})}
|
||||||
|
initial={false}
|
||||||
|
animate={{
|
||||||
|
paddingTop: offsetY ? "24px" : "48px",
|
||||||
|
paddingBottom: offsetY ? "24px" : "48px",
|
||||||
|
}}
|
||||||
|
ref={navRef}
|
||||||
|
>
|
||||||
|
<div className={classnames("max-w-layout mx-auto")}>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<Link to="/" className={classnames("flex flex-shrink-0 items-center", { hidden: open }, "desktop:flex")}>
|
<Link to="/" className={classnames("flex flex-shrink-0 items-center", { hidden: open }, "desktop:flex")}>
|
||||||
{mode === "dark" && <LogoWhiteText className="h-8 w-auto" />}
|
{mode === "dark" && <LogoWhiteText className="h-8 w-auto" />}
|
||||||
{mode === "light" && <LogoBlackText className="h-8 w-auto" />}
|
{mode === "light" && <LogoBlackText className="h-8 w-auto" />}
|
||||||
</Link>
|
</Link>
|
||||||
<div className="ml-auto flex items-center desktop:hidden">
|
<div className="ml-auto flex items-center desktop:hidden z-10">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="inline-flex items-center justify-center focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white"
|
className="inline-flex items-center justify-center focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white"
|
||||||
|
@ -71,12 +101,13 @@ const Navigation = ({ mode }) => {
|
||||||
<MenuMobileClose className={classnames({ hidden: !open })} aria-hidden="true" />
|
<MenuMobileClose className={classnames({ hidden: !open })} aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="hidden desktop:ml-6 desktop:flex desktop:items-center desktop:space-x-12">
|
<div className="hidden desktop:ml-6 desktop:flex desktop:items-center desktop:space-x-12">
|
||||||
{routes.map(({ title, route }) => (
|
{routes.map(({ title, route }) => (
|
||||||
<Link
|
<Link
|
||||||
key={route}
|
key={route}
|
||||||
to={route}
|
to={route}
|
||||||
className={classnames("text-sm font-light", {
|
className={classnames("text-sm font-light transition-colors duration-500", {
|
||||||
"text-white": mode === "dark",
|
"text-white": mode === "dark",
|
||||||
"text-palette-600": mode === "light",
|
"text-palette-600": mode === "light",
|
||||||
})}
|
})}
|
||||||
|
@ -88,42 +119,43 @@ const Navigation = ({ mode }) => {
|
||||||
<SignUpButton />
|
<SignUpButton />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
className={classnames("absolute bg-palette-600 inset-x-0 px-8 pb-12 desktop:hidden", {
|
className={classnames("fixed bg-palette-600 inset-0 px-8 py-12 desktop:hidden", {
|
||||||
block: open,
|
block: open,
|
||||||
hidden: !open,
|
hidden: !open,
|
||||||
})}
|
})}
|
||||||
id="mobile-menu"
|
style={{ marginTop: mobileMenuOffset }}
|
||||||
>
|
>
|
||||||
<ul className="pt-4 pb-10 space-y-2">
|
<ul className="py-10 space-y-2">
|
||||||
{routes.map(({ title, route }) => (
|
{routes.map(({ title, route }) => (
|
||||||
<li key={title}>
|
<li key={title}>
|
||||||
<Link key={route} to={route} className="text-xl leading-7 font-semibold text-white">
|
<Link key={route} to={route} className="text-xl leading-7 font-semibold text-white">
|
||||||
{title}
|
{title}
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
<div className="border-t border-palette-500 py-7">
|
<div className="border-t border-palette-500 py-7">
|
||||||
<a
|
<a
|
||||||
href="https://discordapp.com/invite/sia"
|
href="https://discordapp.com/invite/sia"
|
||||||
className="text-palette-300 text-m font-content flex items-center"
|
className="text-palette-300 text-m font-content flex items-center"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
<DiscordSmall className="mr-2 fill-current text-white" />
|
<DiscordSmall className="mr-2 fill-current text-white" />
|
||||||
<span>Join our Discord</span>
|
<span>Join our Discord</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div className="pt-12 pb-8 border-t border-palette-500">
|
<div className="pt-12 pb-8 border-t border-palette-500">
|
||||||
<div className="flex items-center px-4 space-x-6">
|
<div className="flex items-center justify-center px-4 space-x-6">
|
||||||
<LogInButton className="flex-grow" />
|
<LogInButton className="px-10" />
|
||||||
<SignUpButton className="flex-grow" />
|
<SignUpButton className="px-10" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</motion.nav>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,255 @@
|
||||||
|
import * as React from "react";
|
||||||
|
import classnames from "classnames";
|
||||||
|
import { Add, Cloud, ArrowUpCircle, CheckCircle } from "../Icons";
|
||||||
|
import bytes from "bytes";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import { getReasonPhrase, StatusCodes } from "http-status-codes";
|
||||||
|
import path from "path-browserify";
|
||||||
|
import { useDropzone } from "react-dropzone";
|
||||||
|
import { SkynetClient } from "skynet-js";
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 createUploadErrorMessage = (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.";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const client = new SkynetClient("https://siasky.net");
|
||||||
|
|
||||||
|
const UploadElement = ({ file, progress, status, url }) => {
|
||||||
|
const handleCopy = (url) => {
|
||||||
|
console.log(url);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center">
|
||||||
|
{status === "uploading" && <ArrowUpCircle />}
|
||||||
|
{status === "processing" && <ArrowUpCircle />}
|
||||||
|
{status === "complete" && <CheckCircle />}
|
||||||
|
<div className="flex flex-col flex-grow ml-3">
|
||||||
|
<div className="text-palette-600 text-sm font-light">{file.name}</div>
|
||||||
|
<div className="flex justify-between text-palette-400 text-xs">
|
||||||
|
<div className="font-content">
|
||||||
|
{status === "uploading" && (
|
||||||
|
<span>
|
||||||
|
Uploading {bytes(file.size * progress)} of {bytes(file.size)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{status === "processing" && <span>Processing...</span>}
|
||||||
|
|
||||||
|
{status === "complete" && (
|
||||||
|
<a href={url} target="_blank" rel="noopener noreferrer">
|
||||||
|
{url}
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{status === "uploading" && <span className="uppercase">{Math.floor(progress * 100)}% completed</span>}
|
||||||
|
{status === "complete" && (
|
||||||
|
<button className="uppercase" onClick={() => handleCopy(url)}>
|
||||||
|
Copy link
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex bg-palette-200 mt-1" style={{ height: "5px" }}>
|
||||||
|
<div style={{ width: `${Math.floor(progress * 100)}%` }} className="bg-primary" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Uploader = () => {
|
||||||
|
const [mode, setMode] = React.useState("file");
|
||||||
|
const [files, setFiles] = React.useState([]);
|
||||||
|
|
||||||
|
const handleDrop = async (acceptedFiles) => {
|
||||||
|
if (mode === "directory" && acceptedFiles.length) {
|
||||||
|
const rootDir = getRootDirectory(acceptedFiles[0]); // get the file path from the first file
|
||||||
|
|
||||||
|
acceptedFiles = [{ name: rootDir, directory: true, files: acceptedFiles }];
|
||||||
|
}
|
||||||
|
|
||||||
|
setFiles((previousFiles) => [...acceptedFiles.map((file) => ({ file, status: "uploading" })), ...previousFiles]);
|
||||||
|
|
||||||
|
const onFileStateChange = (file, state) => {
|
||||||
|
setFiles((previousFiles) => {
|
||||||
|
const index = previousFiles.findIndex((f) => f.file === file);
|
||||||
|
|
||||||
|
return [
|
||||||
|
...previousFiles.slice(0, index),
|
||||||
|
{
|
||||||
|
...previousFiles[index],
|
||||||
|
...state,
|
||||||
|
},
|
||||||
|
...previousFiles.slice(index + 1),
|
||||||
|
];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
acceptedFiles.forEach((file) => {
|
||||||
|
const onUploadProgress = (progress) => {
|
||||||
|
const status = progress === 1 ? "processing" : "uploading";
|
||||||
|
|
||||||
|
onFileStateChange(file, { status, progress });
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reject files larger than our hard limit of 1 GB with proper message
|
||||||
|
if (file.size > bytes("1 GB")) {
|
||||||
|
onFileStateChange(file, { status: "error", error: "This file size exceeds the maximum allowed size of 1 GB." });
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const upload = async () => {
|
||||||
|
try {
|
||||||
|
let response;
|
||||||
|
|
||||||
|
if (file.directory) {
|
||||||
|
const directory = file.files.reduce((acc, file) => ({ ...acc, [getRelativeFilePath(file)]: file }), {});
|
||||||
|
|
||||||
|
response = await client.uploadDirectory(directory, encodeURIComponent(file.name), { onUploadProgress });
|
||||||
|
} else {
|
||||||
|
response = await client.uploadFile(file, { onUploadProgress });
|
||||||
|
}
|
||||||
|
|
||||||
|
onFileStateChange(file, { status: "complete", url: client.getSkylinkUrl(response.skylink) });
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response && error.response.status === StatusCodes.TOO_MANY_REQUESTS) {
|
||||||
|
onFileStateChange(file, { progress: -1 });
|
||||||
|
|
||||||
|
return new Promise((resolve) => setTimeout(() => resolve(upload()), 3000));
|
||||||
|
}
|
||||||
|
|
||||||
|
onFileStateChange(file, { status: "error", error: createUploadErrorMessage(error) });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
upload();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const { getRootProps, getInputProps, isDragActive, inputRef } = useDropzone({ onDrop: handleDrop });
|
||||||
|
const inputElement = inputRef.current;
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (mode === "directory") {
|
||||||
|
inputElement.setAttribute("webkitdirectory", "true");
|
||||||
|
} else {
|
||||||
|
inputElement.removeAttribute("webkitdirectory");
|
||||||
|
}
|
||||||
|
}, [mode, inputElement]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="px-8 py-12">
|
||||||
|
<div className="max-w-content mx-auto rounded-lg shadow bg-white z-0 relative">
|
||||||
|
<div className="flex">
|
||||||
|
<button
|
||||||
|
className={classnames("uppercase text-xxs desktop:text-xs w-1/2 p-3 rounded-tl-lg leading-8", {
|
||||||
|
"bg-primary": mode === "file",
|
||||||
|
"bg-palette-200": mode === "directory",
|
||||||
|
})}
|
||||||
|
onClick={() => setMode("file")}
|
||||||
|
>
|
||||||
|
<span className="hidden desktop:inline">Try it now and upload your files</span>
|
||||||
|
<span className="inline desktop:hidden">Upload files</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={classnames("uppercase text-xxs desktop:text-xs w-1/2 p-3 rounded-tr-lg leading-8", {
|
||||||
|
"bg-primary": mode === "directory",
|
||||||
|
"bg-palette-200": mode === "file",
|
||||||
|
})}
|
||||||
|
onClick={() => setMode("directory")}
|
||||||
|
>
|
||||||
|
<span className="hidden desktop:inline">Do you want to upload an entire directory?</span>
|
||||||
|
<span className="inline desktop:hidden">Upload directory</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={classNames("p-4 relative home-upload-dropzone", {
|
||||||
|
"drop-active": isDragActive,
|
||||||
|
})}
|
||||||
|
{...getRootProps()}
|
||||||
|
>
|
||||||
|
<input {...getInputProps()} />
|
||||||
|
<div
|
||||||
|
className={classnames(
|
||||||
|
"p-8 border-2 border-dashed border-palette-200 rounded-lg flex flex-col items-center",
|
||||||
|
{
|
||||||
|
"bg-palette-100": isDragActive,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Cloud />
|
||||||
|
<h4 className="font-light text-palette-600 text-lg mt-2 text-center">
|
||||||
|
Add or drop your files here to pin to Skynet
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div className="absolute left-1/2 -bottom-4 desktop:-bottom-8">
|
||||||
|
<div className="relative -left-1/2" role="button">
|
||||||
|
<Add />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{files.length > 0 && (
|
||||||
|
<div className="flex flex-col space-y-5 p-14">
|
||||||
|
{files.map((file, index) => (
|
||||||
|
<UploadElement key={index} {...file} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Uploader.propTypes = {};
|
||||||
|
|
||||||
|
Uploader.defaultProps = {};
|
||||||
|
|
||||||
|
export default Uploader;
|
|
@ -0,0 +1 @@
|
||||||
|
export { default } from "./Uploader";
|
|
@ -1,9 +1,10 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
// import { StaticImage } from "gatsby-plugin-image";
|
// import { StaticImage } from "gatsby-plugin-image";
|
||||||
import Layout from "../components/Layout/Layout";
|
import Layout from "../components/Layout";
|
||||||
import SEO from "../components/seo";
|
import SEO from "../components/seo";
|
||||||
import HeroStartPage from "../components/HeroStartPage/HeroStartPage";
|
import HeroStartPage from "../components/HeroStartPage";
|
||||||
import CommunitySection from "../components/CommunitySection";
|
import CommunitySection from "../components/CommunitySection";
|
||||||
|
import Uploader from "../components/Uploader";
|
||||||
import {
|
import {
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
SkynetToolBig,
|
SkynetToolBig,
|
||||||
|
@ -17,8 +18,8 @@ import {
|
||||||
} from "../components/Icons";
|
} from "../components/Icons";
|
||||||
import classnames from "classnames";
|
import classnames from "classnames";
|
||||||
|
|
||||||
const Section = ({ children, className }) => (
|
const Section = ({ children, className, ...props }) => (
|
||||||
<div className={classnames("px-8 p-3", className)}>
|
<div className={classnames("p-8", className)} {...props}>
|
||||||
<div className="max-w-content mx-auto">{children}</div>
|
<div className="max-w-content mx-auto">{children}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -57,10 +58,15 @@ const IndexPage = () => (
|
||||||
<Layout>
|
<Layout>
|
||||||
<SEO title="Home" />
|
<SEO title="Home" />
|
||||||
|
|
||||||
<Section className="py-24">
|
<Section>
|
||||||
<HeroStartPage />
|
<HeroStartPage />
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
|
<Section className="relative">
|
||||||
|
{/* <div className="absolute inset-x-0 bg-white bottom-0" style={{ top: 256 }}></div> */}
|
||||||
|
<Uploader />
|
||||||
|
</Section>
|
||||||
|
|
||||||
<Section className="bg-white py-32">
|
<Section className="bg-white py-32">
|
||||||
<SectionTitle className="text-center mb-11">The new decentralized internet is here</SectionTitle>
|
<SectionTitle className="text-center mb-11">The new decentralized internet is here</SectionTitle>
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ module.exports = {
|
||||||
tablet: "640px",
|
tablet: "640px",
|
||||||
md: "768px",
|
md: "768px",
|
||||||
lg: "1024px",
|
lg: "1024px",
|
||||||
desktop: "1088px",
|
desktop: "1024px",
|
||||||
xl: "1280px",
|
xl: "1280px",
|
||||||
hires: "1408px",
|
hires: "1408px",
|
||||||
"2xl": "1536px",
|
"2xl": "1536px",
|
||||||
|
@ -32,6 +32,9 @@ module.exports = {
|
||||||
sans: ["Sora", ...defaultTheme.fontFamily.sans],
|
sans: ["Sora", ...defaultTheme.fontFamily.sans],
|
||||||
content: ["Source\\ Sans\\ Pro", ...defaultTheme.fontFamily.sans],
|
content: ["Source\\ Sans\\ Pro", ...defaultTheme.fontFamily.sans],
|
||||||
},
|
},
|
||||||
|
fontSize: {
|
||||||
|
xxs: ["0.625rem", "0.75rem"],
|
||||||
|
},
|
||||||
colors: {
|
colors: {
|
||||||
primary: {
|
primary: {
|
||||||
DEFAULT: "#00c65e",
|
DEFAULT: "#00c65e",
|
||||||
|
@ -51,6 +54,7 @@ module.exports = {
|
||||||
},
|
},
|
||||||
variants: {
|
variants: {
|
||||||
extend: {
|
extend: {
|
||||||
|
animation: ["hover"],
|
||||||
backgroundColor: ["disabled"],
|
backgroundColor: ["disabled"],
|
||||||
textColor: ["disabled"],
|
textColor: ["disabled"],
|
||||||
margin: ["first"],
|
margin: ["first"],
|
||||||
|
|
Reference in New Issue