Directory Upload & Auto UUID (#60)

This commit is contained in:
Peter-Jan Brone 2020-03-10 14:36:21 +01:00 committed by GitHub
parent f4760e4481
commit 96034cf10d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 155 additions and 58 deletions

View File

@ -19,6 +19,7 @@
"gatsby-transformer-sharp": "^2.3.17", "gatsby-transformer-sharp": "^2.3.17",
"jsonp": "^0.2.1", "jsonp": "^0.2.1",
"node-sass": "^4.13.1", "node-sass": "^4.13.1",
"path-browserify": "^1.0.1",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"react": "^16.13.0", "react": "^16.13.0",
"react-countup": "^4.3.3", "react-countup": "^4.3.3",
@ -29,8 +30,7 @@
"react-mailchimp-subscribe": "^2.1.0", "react-mailchimp-subscribe": "^2.1.0",
"react-reveal": "^1.2.2", "react-reveal": "^1.2.2",
"react-syntax-highlighter": "^12.2.1", "react-syntax-highlighter": "^12.2.1",
"react-visibility-sensor": "^5.1.1", "react-visibility-sensor": "^5.1.1"
"shortid": "^2.2.15"
}, },
"devDependencies": { "devDependencies": {
"cypress": "^4.1.0", "cypress": "^4.1.0",

View File

@ -10,6 +10,11 @@ server {
listen [::]:443 ssl http2; listen [::]:443 ssl http2;
server_name siasky.net www.siasky.net; # replace with actual server names server_name siasky.net www.siasky.net; # replace with actual server names
# Enable the following line if you want to have auto uuid support. This
# means users are able to upload Skyfiles without having to provide a uuid
# themselves.
# rewrite ^/skynet/skyfile/?$ /skynet/skyfile/$request_id$1;
# NOTE: make sure to enable any additional configuration you might need like gzip # NOTE: make sure to enable any additional configuration you might need like gzip
location / { location / {
@ -32,10 +37,11 @@ server {
proxy_set_header Authorization "Basic BASE64_AUTHENTICATION"; proxy_set_header Authorization "Basic BASE64_AUTHENTICATION";
} }
location ~ "^/([a-zA-Z0-9-_]{46})$" { location ~ "^/([a-zA-Z0-9-_]{46}(/.*)?)$" {
proxy_read_timeout 600; proxy_read_timeout 600;
# proxy this call to siad /skynet/skylink/ endpoint (make sure the ip is correct) # proxy this call to siad /skynet/skylink/ endpoint (make sure the ip is
proxy_pass http://127.0.0.1:9980/skynet/skylink/$1; # correct)
proxy_pass http://127.0.0.1:9980/skynet/skylink/$1$is_args$args;
proxy_set_header Access-Control-Allow-Origin: *; proxy_set_header Access-Control-Allow-Origin: *;
# make sure to override user agent header - siad requirement # make sure to override user agent header - siad requirement
proxy_set_header User-Agent: Sia-Agent; proxy_set_header User-Agent: Sia-Agent;
@ -45,11 +51,12 @@ server {
#proxy_buffers 4 128k; #proxy_buffers 4 128k;
} }
location ~ "^/file/([a-zA-Z0-9-_]{46})$" { location ~ "^/file/([a-zA-Z0-9-_]{46}(/.*)?)$" {
proxy_read_timeout 600; proxy_read_timeout 600;
# proxy this call to siad /skunet/skylink/ endpoint (make sure the ip is correct) # proxy this call to siad /skunet/skylink/ endpoint (make sure the ip is
# this alias also adds attachment=true url param to force download the file # correct) this alias also adds attachment=true url param to force
proxy_pass http://127.0.0.1:9980/skynet/skylink/$1?attachment=true; # download the file
proxy_pass http://127.0.0.1:9980/skynet/skylink/$1?attachment=true&$args;
proxy_set_header Access-Control-Allow-Origin: *; proxy_set_header Access-Control-Allow-Origin: *;
# make sure to override user agent header - siad requirement # make sure to override user agent header - siad requirement
proxy_set_header User-Agent: Sia-Agent; proxy_set_header User-Agent: Sia-Agent;

View File

@ -9,7 +9,7 @@ Skynet.download_file("./dst.jpg", skylink)
print("Download successful")`; print("Download successful")`;
export const curl = `# upload export const curl = `# upload
curl -X POST "https://siasky.net/skynet/skyfile/[uuid]" -F file=@src.jpg curl -X POST "https://siasky.net/skynet/skyfile" -F file=@src.jpg
# download # download
curl "https://siasky.net/[skylink]" -o dst.jpg`; curl "https://siasky.net/[skylink]" -o dst.jpg`;

View File

@ -1,8 +1,8 @@
import React, { useState, useContext } from "react"; import React, { useState, useContext, useEffect } from "react";
import classNames from "classnames"; import classNames from "classnames";
import Dropzone from "react-dropzone"; import path from "path-browserify";
import { useDropzone } from "react-dropzone";
import Reveal from "react-reveal/Reveal"; import Reveal from "react-reveal/Reveal";
import shortid from "shortid";
import { Button, UploadFile } from "../"; import { Button, UploadFile } from "../";
import { Deco3, Deco4, Deco5, Folder, DownArrow } from "../../svg"; import { Deco3, Deco4, Deco5, Folder, DownArrow } from "../../svg";
import "./HomeUpload.scss"; import "./HomeUpload.scss";
@ -12,8 +12,47 @@ import axios from "axios";
export default function HomeUpload() { export default function HomeUpload() {
const [files, setFiles] = useState([]); const [files, setFiles] = useState([]);
const { apiUrl } = useContext(AppContext); const { apiUrl } = useContext(AppContext);
const [directoryMode, setDirectoryMode] = useState(false);
useEffect(() => {
if (directoryMode) {
inputRef.current.setAttribute("webkitdirectory", "true");
} else {
inputRef.current.removeAttribute("webkitdirectory");
}
}, [directoryMode]);
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 handleDrop = async (acceptedFiles) => { const handleDrop = async (acceptedFiles) => {
if (directoryMode && 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]); setFiles((previousFiles) => [...acceptedFiles.map((file) => ({ file, status: "uploading" })), ...previousFiles]);
const onFileStateChange = (file, state) => { const onFileStateChange = (file, state) => {
@ -31,30 +70,45 @@ export default function HomeUpload() {
}); });
}; };
const onProgress = (file, { loaded, total }) => { const upload = async (formData, directory, file) => {
const progress = loaded / total; const uploadUrl = `${apiUrl}/skynet/skyfile/${directory ? `?filename=${encodeURIComponent(directory)}` : ""}`;
const status = progress === 1 ? "processing" : "uploading"; const { data } = await axios.post(uploadUrl, formData, {
onUploadProgress: ({ loaded, total }) => {
const progress = loaded / total;
const status = progress === 1 ? "processing" : "uploading";
onFileStateChange(file, { status, progress }); onFileStateChange(file, { status, progress });
}
});
return data;
}; };
acceptedFiles.forEach(async (file) => { acceptedFiles.forEach(async (file) => {
try { try {
const fd = new FormData(); const formData = new FormData();
fd.append("file", file);
const uuid = shortid.generate(); if (file.directory) {
const { data } = await axios.post(`${apiUrl}/skynet/skyfile/${uuid}`, fd, { file.files.forEach((directoryFile) => {
onUploadProgress: (event) => onProgress(file, event) const relativeFilePath = getRelativeFilePath(directoryFile);
});
onFileStateChange(file, { status: "complete", url: `${apiUrl}/${data.skylink}` }); formData.append("files[]", directoryFile, relativeFilePath);
});
} else {
formData.append("file", file);
}
const { skylink } = await upload(formData, directoryMode && file.name, file);
onFileStateChange(file, { status: "complete", url: `${apiUrl}/${skylink}` });
} catch (error) { } catch (error) {
onFileStateChange(file, { status: "error" }); onFileStateChange(file, { status: "error" });
} }
}); });
}; };
const { getRootProps, getInputProps, isDragActive, inputRef } = useDropzone({ onDrop: handleDrop });
const handleSkylink = (event) => { const handleSkylink = (event) => {
event.preventDefault(); event.preventDefault();
@ -71,28 +125,44 @@ export default function HomeUpload() {
<div className="home-upload-white fadeInUp delay4"> <div className="home-upload-white fadeInUp delay4">
<div className="home-upload-split"> <div className="home-upload-split">
<div className="home-upload-box "> <div className="home-upload-box ">
<Dropzone onDrop={handleDrop}> <div
{({ getRootProps, getInputProps, isDragActive }) => ( className={classNames("home-upload-dropzone", {
<> "drop-active": isDragActive
<div })}
className={classNames("home-upload-dropzone", { {...getRootProps()}
"drop-active": isDragActive >
})} <span className="home-upload-text">
{...getRootProps()} <h3>Upload your {directoryMode ? "Directory" : "Files"}</h3>
> Drop your {directoryMode ? "directory" : "files"} here to pin to Skynet
<span className="home-upload-text"> </span>
<h3>Upload your Files</h3> <Button iconLeft>
Drop your files here to pin to Skynet <Folder />
</span> Browse
<Button iconLeft> </Button>
<Folder /> </div>
Browse <input {...getInputProps()} className="offscreen" />
</Button> <button
</div> type="button"
<input {...getInputProps()} className="offscreen" /> className="home-upload-mode-switch link"
</> onClick={() => setDirectoryMode(!directoryMode)}
)} >
</Dropzone> {directoryMode ? "⇐ Switch back to uploading files" : "Do you want to upload entire directory?"}
</button>
{directoryMode && (
<p className="home-upload-directory-mode-notice">
Please note that directory upload is not a standard browser feature and the browser support is
limited. To check whether your browser is compatible, visit{" "}
<a
href="https://caniuse.com/#feat=mdn-api_htmlinputelement_webkitdirectory"
target="_blank"
rel="noopener noreferrer"
className="link"
>
caniuse.com
</a>
.
</p>
)}
</div> </div>
<div className="home-upload-retrieve"> <div className="home-upload-retrieve">

View File

@ -89,6 +89,24 @@
} }
} }
.home-upload-mode-switch {
display: block;
font-size: 13px;
margin: 10px auto 0;
@media (min-width: $largebp) {
font-size: 14px;
}
}
.home-upload-directory-mode-notice {
color: $lightGray;
font-size: 13px;
line-height: 1.2em;
margin-top: 20px;
text-align: center;
}
.home-upload-text { .home-upload-text {
color: $gray; color: $gray;
display: block; display: block;

View File

@ -128,6 +128,15 @@ svg {
color: $darkGray; color: $darkGray;
} }
.link {
color: $green;
transition: 0.2s color;
&:hover {
color: $black;
}
}
.truncate { .truncate {
width: 250px; width: 250px;
white-space: nowrap; white-space: nowrap;

View File

@ -8492,11 +8492,6 @@ nan@^2.12.1, nan@^2.13.2, nan@^2.14.0:
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==
nanoid@^2.1.0:
version "2.1.11"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-2.1.11.tgz#ec24b8a758d591561531b4176a01e3ab4f0f0280"
integrity sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA==
nanomatch@^1.2.9: nanomatch@^1.2.9:
version "1.2.13" version "1.2.13"
resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
@ -9380,6 +9375,11 @@ path-browserify@0.0.1:
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a"
integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==
path-browserify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd"
integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==
path-dirname@^1.0.0: path-dirname@^1.0.0:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0"
@ -11333,13 +11333,6 @@ shell-quote@1.6.1:
array-reduce "~0.0.0" array-reduce "~0.0.0"
jsonify "~0.0.0" jsonify "~0.0.0"
shortid@^2.2.15:
version "2.2.15"
resolved "https://registry.yarnpkg.com/shortid/-/shortid-2.2.15.tgz#2b902eaa93a69b11120373cd42a1f1fe4437c122"
integrity sha512-5EaCy2mx2Jgc/Fdn9uuDuNIIfWBpzY4XIlhoqtXF6qsf+/+SGZ+FxDdX/ZsMZiWupIWNqAEmiNY4RC+LSmCeOw==
dependencies:
nanoid "^2.1.0"
side-channel@^1.0.2: side-channel@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.2.tgz#df5d1abadb4e4bf4af1cd8852bf132d2f7876947" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.2.tgz#df5d1abadb4e4bf4af1cd8852bf132d2f7876947"