From 96034cf10d29587ef81f84814dcb7e3e4c7fc7df Mon Sep 17 00:00:00 2001 From: Peter-Jan Brone Date: Tue, 10 Mar 2020 14:36:21 +0100 Subject: [PATCH] Directory Upload & Auto UUID (#60) --- package.json | 4 +- setup-scripts/skynet-nginx.conf | 21 ++-- src/components/CodeExamples/Code.js | 2 +- src/components/HomeUpload/HomeUpload.js | 142 ++++++++++++++++------ src/components/HomeUpload/HomeUpload.scss | 18 +++ src/global.scss | 9 ++ yarn.lock | 17 +-- 7 files changed, 155 insertions(+), 58 deletions(-) diff --git a/package.json b/package.json index 945d3214..89bf110e 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "gatsby-transformer-sharp": "^2.3.17", "jsonp": "^0.2.1", "node-sass": "^4.13.1", + "path-browserify": "^1.0.1", "prop-types": "^15.7.2", "react": "^16.13.0", "react-countup": "^4.3.3", @@ -29,8 +30,7 @@ "react-mailchimp-subscribe": "^2.1.0", "react-reveal": "^1.2.2", "react-syntax-highlighter": "^12.2.1", - "react-visibility-sensor": "^5.1.1", - "shortid": "^2.2.15" + "react-visibility-sensor": "^5.1.1" }, "devDependencies": { "cypress": "^4.1.0", diff --git a/setup-scripts/skynet-nginx.conf b/setup-scripts/skynet-nginx.conf index 68e223e5..a97cadeb 100644 --- a/setup-scripts/skynet-nginx.conf +++ b/setup-scripts/skynet-nginx.conf @@ -10,6 +10,11 @@ server { listen [::]:443 ssl http2; 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 location / { @@ -32,10 +37,11 @@ server { proxy_set_header Authorization "Basic BASE64_AUTHENTICATION"; } - location ~ "^/([a-zA-Z0-9-_]{46})$" { + location ~ "^/([a-zA-Z0-9-_]{46}(/.*)?)$" { proxy_read_timeout 600; - # proxy this call to siad /skynet/skylink/ endpoint (make sure the ip is correct) - proxy_pass http://127.0.0.1:9980/skynet/skylink/$1; + # proxy this call to siad /skynet/skylink/ endpoint (make sure the ip is + # correct) + proxy_pass http://127.0.0.1:9980/skynet/skylink/$1$is_args$args; proxy_set_header Access-Control-Allow-Origin: *; # make sure to override user agent header - siad requirement proxy_set_header User-Agent: Sia-Agent; @@ -45,11 +51,12 @@ server { #proxy_buffers 4 128k; } - location ~ "^/file/([a-zA-Z0-9-_]{46})$" { + location ~ "^/file/([a-zA-Z0-9-_]{46}(/.*)?)$" { proxy_read_timeout 600; - # proxy this call to siad /skunet/skylink/ endpoint (make sure the ip is correct) - # this alias also adds attachment=true url param to force download the file - proxy_pass http://127.0.0.1:9980/skynet/skylink/$1?attachment=true; + # proxy this call to siad /skunet/skylink/ endpoint (make sure the ip is + # correct) this alias also adds attachment=true url param to force + # download the file + proxy_pass http://127.0.0.1:9980/skynet/skylink/$1?attachment=true&$args; proxy_set_header Access-Control-Allow-Origin: *; # make sure to override user agent header - siad requirement proxy_set_header User-Agent: Sia-Agent; diff --git a/src/components/CodeExamples/Code.js b/src/components/CodeExamples/Code.js index 8c61af0c..336b5fab 100644 --- a/src/components/CodeExamples/Code.js +++ b/src/components/CodeExamples/Code.js @@ -9,7 +9,7 @@ Skynet.download_file("./dst.jpg", skylink) print("Download successful")`; 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 curl "https://siasky.net/[skylink]" -o dst.jpg`; diff --git a/src/components/HomeUpload/HomeUpload.js b/src/components/HomeUpload/HomeUpload.js index 48f2193b..e96d5a56 100644 --- a/src/components/HomeUpload/HomeUpload.js +++ b/src/components/HomeUpload/HomeUpload.js @@ -1,8 +1,8 @@ -import React, { useState, useContext } from "react"; +import React, { useState, useContext, useEffect } from "react"; 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 shortid from "shortid"; import { Button, UploadFile } from "../"; import { Deco3, Deco4, Deco5, Folder, DownArrow } from "../../svg"; import "./HomeUpload.scss"; @@ -12,8 +12,47 @@ import axios from "axios"; export default function HomeUpload() { const [files, setFiles] = useState([]); 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) => { + 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]); const onFileStateChange = (file, state) => { @@ -31,30 +70,45 @@ export default function HomeUpload() { }); }; - const onProgress = (file, { loaded, total }) => { - const progress = loaded / total; - const status = progress === 1 ? "processing" : "uploading"; + const upload = async (formData, directory, file) => { + const uploadUrl = `${apiUrl}/skynet/skyfile/${directory ? `?filename=${encodeURIComponent(directory)}` : ""}`; + 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) => { try { - const fd = new FormData(); - fd.append("file", file); + const formData = new FormData(); - const uuid = shortid.generate(); - const { data } = await axios.post(`${apiUrl}/skynet/skyfile/${uuid}`, fd, { - onUploadProgress: (event) => onProgress(file, event) - }); + if (file.directory) { + file.files.forEach((directoryFile) => { + 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) { onFileStateChange(file, { status: "error" }); } }); }; + const { getRootProps, getInputProps, isDragActive, inputRef } = useDropzone({ onDrop: handleDrop }); + const handleSkylink = (event) => { event.preventDefault(); @@ -71,28 +125,44 @@ export default function HomeUpload() {
- - {({ getRootProps, getInputProps, isDragActive }) => ( - <> -
- -

Upload your Files

- Drop your files here to pin to Skynet -
- -
- - - )} -
+
+ +

Upload your {directoryMode ? "Directory" : "Files"}

+ Drop your {directoryMode ? "directory" : "files"} here to pin to Skynet +
+ +
+ + + {directoryMode && ( +

+ 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{" "} + + caniuse.com + + . +

+ )}
diff --git a/src/components/HomeUpload/HomeUpload.scss b/src/components/HomeUpload/HomeUpload.scss index 54d1caae..714592f6 100644 --- a/src/components/HomeUpload/HomeUpload.scss +++ b/src/components/HomeUpload/HomeUpload.scss @@ -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 { color: $gray; display: block; diff --git a/src/global.scss b/src/global.scss index 42a56d49..fb692845 100644 --- a/src/global.scss +++ b/src/global.scss @@ -128,6 +128,15 @@ svg { color: $darkGray; } +.link { + color: $green; + transition: 0.2s color; + + &:hover { + color: $black; + } +} + .truncate { width: 250px; white-space: nowrap; diff --git a/yarn.lock b/yarn.lock index 10c761bf..3db96de2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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" 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: version "1.2.13" 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" 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: version "1.0.2" 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" 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: version "1.0.2" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.2.tgz#df5d1abadb4e4bf4af1cd8852bf132d2f7876947"