diff --git a/digital-course-file/src/hooks/useFolder.js b/digital-course-file/src/hooks/useFolder.js index b4c921b402a0b8860925e13712a497de1d53b8ba..2ec2265a68598e60b77ab61dcfa3a041fc00547e 100644 --- a/digital-course-file/src/hooks/useFolder.js +++ b/digital-course-file/src/hooks/useFolder.js @@ -8,11 +8,12 @@ export const ROOT_FOLDER = {name: 'Root', id : null , path : [] , parents : []}; export function useFolder( folderId = null, folder= null) { // const {currentUser} = firebase.auth.currentUser; - + const ACTIONS = { SELECT_FOLDER : 'select-folder', UPDATE_FOLDER : 'update-folder', SET_CHILD_FOLDERS : 'set_child_folders', + SET_CHILD_FILES: "set-child-files", } function reducer( state, { type,payload } ){ @@ -36,6 +37,11 @@ export function useFolder( folderId = null, folder= null) { return{ ...state, childFolders : payload.childFolders, + }; + case ACTIONS.SET_CHILD_FILES: + return { + ...state, + childFiles: payload.childFiles, }; default: @@ -104,6 +110,23 @@ export function useFolder( folderId = null, folder= null) { }) }) },[folderId]) + useEffect(() => { + if(firebase.auth().currentUser) + { + return ( + database.files + .where("folderId", "==", folderId) + .where("userId", "==", firebase.auth().currentUser.uid) + .onSnapshot(snapshot => { + dispatch({ + type: ACTIONS.SET_CHILD_FILES, + payload: { childFiles: snapshot.docs.map(database.formatDoc) }, + }) + }) + ) + } + + }, [folderId]) return state; } \ No newline at end of file diff --git a/digital-course-file/src/user/AddFile.js b/digital-course-file/src/user/AddFile.js new file mode 100644 index 0000000000000000000000000000000000000000..3de4ef2711f3399060d00d356e4fc0a8a415fc2d --- /dev/null +++ b/digital-course-file/src/user/AddFile.js @@ -0,0 +1,145 @@ +import React, { useState } from "react" +import ReactDOM from "react-dom" +import { faFileUpload } from "@fortawesome/free-solid-svg-icons" +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" +import { storage, database } from '../fire.js' +import { ROOT_FOLDER } from "../hooks/useFolder" +import { v4 as uuidV4 } from "uuid" +import { ProgressBar, Toast } from "react-bootstrap" +import firebase from 'firebase' + +export default function AddFile({ currentFolder }) { + const [uploadingFiles, setUploadingFiles] = useState([]) + + + function handleUpload(e) { + const file = e.target.files[0] + 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 ( + <> + <label className="btn btn-outline-success btn-sm m-0 mr-2"> + <FontAwesomeIcon icon={faFileUpload} /> + <input + type="file" + onChange={handleUpload} + style={{ opacity: 0, position: "absolute", left: "-9999px" }} + /> + </label> + {uploadingFiles.length > 0 && + ReactDOM.createPortal( + <div + style={{ + position: "absolute", + bottom: "1rem", + right: "1rem", + 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 + )} + </> + ) +} \ No newline at end of file diff --git a/digital-course-file/src/user/File.js b/digital-course-file/src/user/File.js new file mode 100644 index 0000000000000000000000000000000000000000..e157c4cc26a369879ad38773ecd602e95e4c9ad8 --- /dev/null +++ b/digital-course-file/src/user/File.js @@ -0,0 +1,16 @@ +import { faFile } from "@fortawesome/free-solid-svg-icons" +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" +import React from "react" + +export default function File({ file }) { + return ( + <a + href={file.url} + target="_blank" + className="btn btn-outline-dark text-truncate w-100" + > + <FontAwesomeIcon icon={faFile} className="mr-2" /> + {file.name} + </a> + ) +} diff --git a/digital-course-file/src/user/FolderNav.js b/digital-course-file/src/user/FolderNav.js index ca6738406fef6a74b981c907cd2e2e942c8a63d8..fbfe064a5b58a4ceb95fd8c37130667fe06e5ab9 100644 --- a/digital-course-file/src/user/FolderNav.js +++ b/digital-course-file/src/user/FolderNav.js @@ -10,7 +10,6 @@ export default function FolderNav( {currentFolder} ) { path = [...path,...currentFolder.path]; } - return( <Breadcrumb className="flex-grow-1" diff --git a/digital-course-file/src/user/Hero.js b/digital-course-file/src/user/Hero.js index 0e9e903e6e2146066a39ea213534d447e06bc347..8beb26b2cf5c07a705d08eda3627fe08a0c44bf8 100644 --- a/digital-course-file/src/user/Hero.js +++ b/digital-course-file/src/user/Hero.js @@ -1,20 +1,25 @@ -import React, { useEffect, useState } from 'react' +import React, { useState, Component } from "react" import AddFolder from './AddFolder' +import AddFile from './AddFile' import { Container, Button, Navbar, Nav } from 'react-bootstrap' -import { ROOT_FOLDER, useFolder } from '.././hooks/useFolder' +import { useFolder } from '.././hooks/useFolder' import Folder from './Folder' import FolderNav from './FolderNav' import Deffolders from './Deffolders' -import Deletefolder from './Deletefolder' import Sharelink from './Sharelink' -import { useParams } from 'react-router-dom' -import copyright from './copyright' +import Deletefolder from './Deletefolder' +import { useParams,useLocation } from 'react-router-dom' import { Link } from 'react-router-dom' +import File from './File' +import 'firebase/storage'; +import ReactDOM from "react-dom" +import firebase from "../fire"; import Loader from 'react-loader-spinner' const Hero = ({ handleLogout }) => { const { folderId } = useParams() - const { folder, childFolders } = useFolder(folderId) + const { state = {} } = useLocation() + const { folder, childFolders, childFiles } = useFolder(folderId, state.folder) if (!folder) { return ( @@ -44,6 +49,7 @@ const Hero = ({ handleLogout }) => { )} {folder.id!=null && ( <Sharelink currentFolder={folder} /> )} {folder.id!=null && ( <Deletefolder currentFolder={folder} /> )} + {folder.id!=null && ( <AddFile currentFolder={folder} /> )} </div> {childFolders.length > 0 && ( @@ -59,8 +65,109 @@ const Hero = ({ handleLogout }) => { ))} </div> )} + + {childFolders.length > 0 && childFiles.length > 0 && <hr />} + {childFiles.length > 0 && ( + <div className="d-flex flex-wrap"> + {childFiles.map(childFile => ( + <div + key={childFile.id} + style={{ maxWidth: "250px" }} + className="p-2" + > + <File file={childFile} /> + <div id="root"> + + </div> + </div> + ))} + </div> + )} + </Container> + <Navbar fixed='bottom' variant='light' bg='light'> + <Container className='ml-sm-2'> + <Nav.Link eventKey={2} href='copyright'> + © Digital Course File Group 2 + </Nav.Link> + </Container> + </Navbar> </> ) } +class ContextMenu extends React.Component { + state = { + visible: false, + }; + + componentDidMount() { + document.addEventListener('contextmenu', this._handleContextMenu); + document.addEventListener('click', this._handleClick); + document.addEventListener('scroll', this._handleScroll); + }; + + componentWillUnmount() { + document.removeEventListener('contextmenu', this._handleContextMenu); + document.removeEventListener('click', this._handleClick); + document.removeEventListener('scroll', this._handleScroll); + } + + _handleContextMenu = (event) => { + event.preventDefault(); + + this.setState({ visible: true }); + + const clickX = event.clientX; + const clickY = event.clientY; + const screenW = window.innerWidth; + const screenH = window.innerHeight; + const rootW = this.root.offsetWidth; + const rootH = this.root.offsetHeight; + + const right = (screenW - clickX) > rootW; + const left = !right; + const top = (screenH - clickY) > rootH; + const bottom = !top; + + if (right) { + this.root.style.left = `${clickX + 5}px`; + } + + if (left) { + this.root.style.left = `${clickX - rootW - 5}px`; + } + + if (top) { + this.root.style.top = `${clickY + 5}px`; + } + + if (bottom) { + this.root.style.top = `${clickY - rootH - 5}px`; + } + }; + + _handleClick = (event) => { + const { visible } = this.state; + const wasOutside = !(event.target.contains === this.root); + + if (wasOutside && visible) this.setState({ visible: false, }); + }; + + _handleScroll = () => { + const { visible } = this.state; + + if (visible) this.setState({ visible: false, }); + }; + + render() { + const { visible } = this.state; + + return(visible || null) && + <div ref={ref => {this.root = ref}} className="contextMenu"> + <div className="contextMenu--option">Delete</div> + + </div> + }; +} +ReactDOM.render(<ContextMenu/>, document.getElementById('root')); export default Hero