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() {
+ 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 + + . +
+ )}