diff --git a/digital-course-file/package-lock.json b/digital-course-file/package-lock.json index 79f64b6380227a9b92d614caa4d35e132b598299..926d82a87abd301cd42ae23198bab3ec948aa47f 100644 --- a/digital-course-file/package-lock.json +++ b/digital-course-file/package-lock.json @@ -24,6 +24,7 @@ "react-contexify": "^5.0.0", "react-copy-to-clipboard": "^5.0.3", "react-dom": "^17.0.1", + "react-dropzone": "^11.3.2", "react-github-login-button": "^1.0.1", "react-google-button": "^0.7.2", "react-icons": "^4.2.0", @@ -1960,6 +1961,7 @@ "jest-resolve": "^26.6.2", "jest-util": "^26.6.2", "jest-worker": "^26.6.2", + "node-notifier": "^8.0.0", "slash": "^3.0.0", "source-map": "^0.6.0", "string-length": "^4.0.1", @@ -3917,6 +3919,14 @@ "node": ">= 4.5.0" } }, + "node_modules/attr-accept": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz", + "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==", + "engines": { + "node": ">=4" + } + }, "node_modules/autoprefixer": { "version": "9.8.6", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.6.tgz", @@ -6932,7 +6942,8 @@ "esprima": "^4.0.1", "estraverse": "^4.2.0", "esutils": "^2.0.2", - "optionator": "^0.8.1" + "optionator": "^0.8.1", + "source-map": "~0.6.1" }, "bin": { "escodegen": "bin/escodegen.js", @@ -8062,6 +8073,17 @@ "node": ">= 10.13.0" } }, + "node_modules/file-selector": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.2.4.tgz", + "integrity": "sha512-ZDsQNbrv6qRi1YTDOEWzf5J2KjZ9KMI1Q2SGeTkCJmNNW25Jg4TW4UMcmoqcg4WrAyKRcpBXdbWRxkfrOzVRbA==", + "dependencies": { + "tslib": "^2.0.3" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -10726,6 +10748,7 @@ "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", + "fsevents": "^2.1.2", "graceful-fs": "^4.2.4", "jest-regex-util": "^26.0.0", "jest-serializer": "^26.6.2", @@ -15676,6 +15699,22 @@ "scheduler": "^0.20.1" } }, + "node_modules/react-dropzone": { + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-11.3.2.tgz", + "integrity": "sha512-Z0l/YHcrNK1r85o6RT77Z5XgTARmlZZGfEKBl3tqTXL9fZNQDuIdRx/J0QjvR60X+yYu26dnHeaG2pWU+1HHvw==", + "dependencies": { + "attr-accept": "^2.2.1", + "file-selector": "^0.2.2", + "prop-types": "^15.7.2" + }, + "engines": { + "node": ">= 10" + }, + "peerDependencies": { + "react": ">= 16.8" + } + }, "node_modules/react-error-overlay": { "version": "6.0.9", "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz", @@ -15837,6 +15876,7 @@ "eslint-webpack-plugin": "^2.1.0", "file-loader": "6.1.1", "fs-extra": "^9.0.1", + "fsevents": "^2.1.3", "html-webpack-plugin": "4.5.0", "identity-obj-proxy": "3.0.0", "jest": "26.6.0", @@ -19185,8 +19225,10 @@ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", "integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==", "dependencies": { + "chokidar": "^3.4.1", "graceful-fs": "^4.1.2", - "neo-async": "^2.5.0" + "neo-async": "^2.5.0", + "watchpack-chokidar2": "^2.0.1" }, "optionalDependencies": { "chokidar": "^3.4.1", @@ -19683,6 +19725,7 @@ "anymatch": "^2.0.0", "async-each": "^1.0.1", "braces": "^2.3.2", + "fsevents": "^1.2.7", "glob-parent": "^3.1.0", "inherits": "^2.0.3", "is-binary-path": "^1.0.0", @@ -24469,6 +24512,11 @@ "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" }, + "attr-accept": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz", + "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==" + }, "autoprefixer": { "version": "9.8.6", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.6.tgz", @@ -27980,6 +28028,14 @@ } } }, + "file-selector": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.2.4.tgz", + "integrity": "sha512-ZDsQNbrv6qRi1YTDOEWzf5J2KjZ9KMI1Q2SGeTkCJmNNW25Jg4TW4UMcmoqcg4WrAyKRcpBXdbWRxkfrOzVRbA==", + "requires": { + "tslib": "^2.0.3" + } + }, "file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -34184,6 +34240,16 @@ "scheduler": "^0.20.1" } }, + "react-dropzone": { + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-11.3.2.tgz", + "integrity": "sha512-Z0l/YHcrNK1r85o6RT77Z5XgTARmlZZGfEKBl3tqTXL9fZNQDuIdRx/J0QjvR60X+yYu26dnHeaG2pWU+1HHvw==", + "requires": { + "attr-accept": "^2.2.1", + "file-selector": "^0.2.2", + "prop-types": "^15.7.2" + } + }, "react-error-overlay": { "version": "6.0.9", "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz", diff --git a/digital-course-file/package.json b/digital-course-file/package.json index 5c03f0e6463e8a0eeda69098d8c67fdd92066ab8..9c11193cd1ad92cc70080279ab4fd9489bf5f70e 100644 --- a/digital-course-file/package.json +++ b/digital-course-file/package.json @@ -20,6 +20,7 @@ "react-contexify": "^5.0.0", "react-copy-to-clipboard": "^5.0.3", "react-dom": "^17.0.1", + "react-dropzone": "^11.3.2", "react-github-login-button": "^1.0.1", "react-google-button": "^0.7.2", "react-icons": "^4.2.0", diff --git a/digital-course-file/src/user/Dropzone.js b/digital-course-file/src/user/Dropzone.js new file mode 100644 index 0000000000000000000000000000000000000000..1e42498dbb3d7e7aca8f634c652fc5dee7c25574 --- /dev/null +++ b/digital-course-file/src/user/Dropzone.js @@ -0,0 +1,221 @@ +import React, { useState, useEffect } from 'react' +import { Button, Modal, ModalFooter } from 'react-bootstrap' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faHandRock } from '@fortawesome/free-solid-svg-icons' +import { database } from '../fire.js' +import firebase from 'firebase' +import { Link } from 'react-router-dom' +import { useDropzone } from 'react-dropzone' +import { v4 as uuidV4 } from 'uuid' +import { ProgressBar, Toast } from 'react-bootstrap' +import { storage } from '../fire.js' +import { ROOT_FOLDER } from '../hooks/useFolder' +import ReactDOM from 'react-dom' + +// {currentFolder} +export default function Dropzone({ currentFolder }) { + const [open, setOpen] = useState(false) + const temp_parent = '' + currentFolder.parentId + const [path, setpath] = useState(``) + const { acceptedFiles, getRootProps, getInputProps } = useDropzone() + const [uploadingFiles, setUploadingFiles] = useState([]) + + const files = acceptedFiles.map((file) => ( + <li key={file.path}> + {file.path} - {file.size} bytes + </li> + )) + + function openModal() { + { + temp_parent != 'null' && setpath(`/folder/${temp_parent}`) + } + setOpen(true) + } + + function closeModal() { + setOpen(false) + acceptedFiles.length = 0 + } + + function uploadinghelp() { + const files = acceptedFiles.map((file) => handleUpload(file)) + closeModal() + } + + function handleUpload(file) { + if (currentFolder == null || file == null) return + + const id = uuidV4() + setUploadingFiles((prevUploadingFiles) => [ + ...prevUploadingFiles, + { id: id, name: file.name, progress: 0, error: false }, + ]) + const filePath = + currentFolder === ROOT_FOLDER + ? `${currentFolder.path.join('/')}/${file.name}` + : `${currentFolder.path.join('/')}/${currentFolder.name}/${file.name}` + + const uploadTask = storage + .ref(`/files/${firebase.auth().currentUser.uid}/${filePath}`) + .put(file) + + uploadTask.on( + 'state_changed', + (snapshot) => { + const progress = snapshot.bytesTransferred / snapshot.totalBytes + setUploadingFiles((prevUploadingFiles) => { + return prevUploadingFiles.map((uploadFile) => { + if (uploadFile.id === id) { + return { ...uploadFile, progress: progress } + } + + return uploadFile + }) + }) + }, + () => { + setUploadingFiles((prevUploadingFiles) => { + return prevUploadingFiles.map((uploadFile) => { + if (uploadFile.id === id) { + return { ...uploadFile, error: true } + } + return uploadFile + }) + }) + }, + () => { + setUploadingFiles((prevUploadingFiles) => { + return prevUploadingFiles.filter((uploadFile) => { + return uploadFile.id !== id + }) + }) + + uploadTask.snapshot.ref.getDownloadURL().then((url) => { + database.files + .where('name', '==', file.name) + .where('userId', '==', firebase.auth().currentUser.uid) + .where('folderId', '==', currentFolder.id) + .get() + .then((existingFiles) => { + const existingFile = existingFiles.docs[0] + if (existingFile) { + existingFile.ref.update({ url: url }) + } else { + database.files.add({ + url: url, + name: file.name, + createdAt: database.getTime(), + folderId: currentFolder.id, + userId: firebase.auth().currentUser.uid, + }) + } + }) + }) + } + ) + } + + return ( + <> + <Button + name='drag-and-drop' + style={{ maxWidth: '80px' }} + className='mr-2' + onClick={openModal} + variant='warning' + size='sm' + > + <FontAwesomeIcon icon={faHandRock} /> + </Button> + <Modal + show={open} + onHide={closeModal} + size='xl' + aria-labelledby='example-custom-modal-styling-title' + centered + dialogClassName='modal-90w' + > + <Modal.Body> + {/* <div> + Drag and Drop files Here!! + </div> */} + + <section className='container'> + <div {...getRootProps({ className: 'dropzone' })}> + <input {...getInputProps()} /> + <p>Drag 'n' drop some files here, or click to select files</p> + </div> + <aside> + <ul>{files}</ul> + </aside> + </section> + </Modal.Body> + <ModalFooter> + <Button + name='del_confirm' + style={{ float: 'left' }} + className='mr-2' + variant='primary' + onClick={uploadinghelp} + as={Link} + > + Done + </Button> + <Button + style={{ maxWidth: '80px' }} + className='mr-2' + variant='danger' + onClick={closeModal} + > + Cancel + </Button> + </ModalFooter> + </Modal> + {uploadingFiles.length > 0 && + ReactDOM.createPortal( + <div + style={{ + position: 'absolute', + bottom: '4rem', + right: '2rem', + maxWidth: '250px', + }} + > + {uploadingFiles.map((file) => ( + <Toast + key={file.id} + onClose={() => { + setUploadingFiles((prevUploadingFiles) => { + return prevUploadingFiles.filter((uploadFile) => { + return uploadFile.id !== file.id + }) + }) + }} + > + <Toast.Header + closeButton={file.error} + className='text-truncate w-100 d-block' + > + {file.name} + </Toast.Header> + <Toast.Body> + <ProgressBar + animated={!file.error} + variant={file.error ? 'danger' : 'primary'} + now={file.error ? 100 : file.progress * 100} + label={ + file.error + ? 'Error' + : `${Math.round(file.progress * 100)}%` + } + /> + </Toast.Body> + </Toast> + ))} + </div>, + document.body + )} + </> + ) +} diff --git a/digital-course-file/src/user/Hero.js b/digital-course-file/src/user/Hero.js index 9f6432a7742d5a96bbff885cd882fd03d1be3c17..c91969c6c761b549d964f26e5afb9023ec9a4691 100644 --- a/digital-course-file/src/user/Hero.js +++ b/digital-course-file/src/user/Hero.js @@ -15,6 +15,7 @@ import 'firebase/storage'; import ReactDOM from "react-dom" import firebase from "../fire"; import Loader from 'react-loader-spinner' +import Dropzone from './Dropzone' const Hero = ({ handleLogout }) => { @@ -40,7 +41,9 @@ const Hero = ({ handleLogout }) => { if(folder.id!=="copyright"){ return ( + <> + <Container fluid> <div className='d-flex align-items-center'> <FolderNav currentFolder={folder} /> @@ -52,6 +55,7 @@ const Hero = ({ handleLogout }) => { {folder.id!=null && ( <Sharelink currentFolder={folder} /> )} {folder.id!=null && ( <Deletefolder currentFolder={folder} /> )} {folder.id!=null && ( <AddFile currentFolder={folder} /> )} + {folder.id!=null && ( <Dropzone currentFolder={folder} /> )} </div> {childFolders.length > 0 && (