diff --git a/src/components/fileBrowser.jsx b/src/components/fileBrowser.jsx index 454c604..1c7839a 100644 --- a/src/components/fileBrowser.jsx +++ b/src/components/fileBrowser.jsx @@ -1,7 +1,8 @@ import { Component, Fragment } from 'react' import path from 'path' import styled from 'styled-components' -import { Input, Label } from './fileBrowser.styled.js' +import { FileSettingsHeader, FileSettingsPopup, Label } from './fileBrowser.styled.js' +import { Input } from '../styled' // images for different filetypes import Back from '../assets/icons/arrowLeft.svg' @@ -41,8 +42,8 @@ import { Button } from '../styled.js' import assert from 'assert' import LineLoader from './LineLoader.jsx' -const ROW_HEIGHT = 20 -const ROW_GAP = 8 +const ROW_HEIGHT = 28 +const ROW_GAP = 0 const DATA_PADDING = 3 const DEBOUNCE_THRESHOLD = 100 @@ -89,10 +90,12 @@ const GridFileBrowser = styled.div` display: grid; grid-template-columns: 1px 1fr ${({shownColumns}) => shownColumns.datetime ? "10rem" : ""} ${({shownColumns}) => shownColumns.date ? "6rem" : ""} ${({shownColumns}) => shownColumns.size ? "6rem" : ""}; align-items: center; - gap: .5rem 1.5rem; + /* gap: .5rem 1.5rem; */ + gap: 0 1.5rem; width: 100%; transition: transform .3s; - padding: .5rem; + /* padding: .5rem; */ + padding: 0 .5rem; ` const BrowserHeader = styled.div` @@ -131,6 +134,7 @@ const EllipsisP = styled.p` overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + line-height: 28px; ` const SpanPathDirectory = styled.span` @@ -180,6 +184,19 @@ const SearchInput = styled(Input)` } ` +const FileMenuContainer = styled.div` + position: fixed; + top: ${({cursorY}) => cursorY - 3}px; + left: ${({cursorX}) => cursorX + 3}px; + background-color: var(--button-color); + z-index: 900; + border-radius: .2rem; + + div { + text-align: left; + } +` + const delay = t => new Promise(resolve => setTimeout(resolve, t)) class FileBrowser extends Component { @@ -193,6 +210,7 @@ class FileBrowser extends Component { prevPath: "", transitionFiles: 0, showMenu: false, + showNewFolder: false, cursorX: 0, cursorY: 0, clicked: "", @@ -239,7 +257,9 @@ class FileBrowser extends Component { window.removeEventListener('click', this.handleWindowClick) } - handleWindowClick = () => this.setState({ showMenu: false }) + handleWindowClick = () => this.setState({ showMenu: false, showNewFolder: false }) + + isMenuOpen = () => this.props.menuOpen || this.state.showMenu || this.state.showNewFolder // used to filter the files handleInputChange({target}) { @@ -254,7 +274,7 @@ class FileBrowser extends Component { // change the way files should be ordered updateOrder = orderBy => { - if (this.props.menuOpen || this.state.showMenu) return; + if (this.isMenuOpen()) return; if (this.state.orderBy === orderBy) { this.setState({ orderAscending: !this.state.orderAscending }) @@ -301,7 +321,7 @@ class FileBrowser extends Component { // after the user clicks on a folder updatePath = name => { - if (this.props.menuOpen || this.state.showMenu) return; + if (this.isMenuOpen()) return; const newPath = path.join(this.props.currentPath, name) @@ -311,7 +331,7 @@ class FileBrowser extends Component { // after the user clicks on the back button previousDirectory = () => { - if (this.props.menuOpen || this.state.showMenu) return; + if (this.isMenuOpen()) return; let currentPath = this.props.currentPath.split("/") currentPath.pop() @@ -322,7 +342,7 @@ class FileBrowser extends Component { // after the user click on the home button rootDirectory = () => { - if (this.props.menuOpen || this.state.showMenu) return; + if (this.isMenuOpen()) return; this.props.updateFiles("/") this.setState({filter: ""}) @@ -330,7 +350,7 @@ class FileBrowser extends Component { // after the user clicks on a path piece goToPath = index => { - if (this.props.menuOpen || this.state.showMenu) return; + if (this.isMenuOpen()) return; let currentPath = this.props.currentPath.split("/") @@ -353,8 +373,9 @@ class FileBrowser extends Component { * Opens the actions menu * @param {ElementEvent} e The event that called this function */ - openMenu = (e) => { + openMenu = (e, isFile) => { e.preventDefault() + e.stopPropagation() assert( typeof e.pageX === "number" @@ -363,34 +384,78 @@ class FileBrowser extends Component { && e.target.innerHTML.length > 0 ) - this.setState({ - cursorX: e.pageX, - cursorY: e.pageY, - showMenu: true, - clicked: e.target.innerHTML - }) + if (isFile) { + this.setState({ + cursorX: e.pageX, + cursorY: e.pageY, + showMenu: true, + clicked: e.target.innerHTML + }) + } else { + this.setState({ + cursorX: e.pageX, + cursorY: e.pageY, + showMenu: true, + clicked: "" + }) + } + } + + openNewFolderPopup = e => { + e.stopPropagation() + return this.setState({ showNewFolder: true, showMenu: false }) + } + + handleNewFolderSubmit = e => { + e.preventDefault() + this.props.action("newfolder", e.target.newFolderName.value) + .then(() => this.setState({ showNewFolder: false })) + .catch(err => { + console.error(err) + this.setState({ showNewFolder: false }) + }) // the user should see this error + } + + renderNewFolderPopup = () => { + if (this.state.showNewFolder) return ( + e.stopPropagation()}> + + +
+ + +
+
+ ) } /** * Renders a simple menu to perform actions on the clicked file */ renderMenu = () => { - if (this.state.showMenu) return ( -
this.setState({ showMenu: false })} - style={{ - position: "fixed", - top: this.state.cursorY - 3, - left: this.state.cursorX + 3, - backgroundColor: "var(--button-color)", - zIndex: 900, - borderRadius: "3px" - }} + cursorX={cursorX} cursorY={cursorY} > -
+ + + ) + + if (showMenu) return ( + this.setState({ showMenu: false })} + cursorX={cursorX} cursorY={cursorY} + > + + ) } @@ -408,17 +473,17 @@ class FileBrowser extends Component { files = this.getOrderedItems(files).slice(this.state.from, this.state.to) return files - .map(v => ( - - { this.renderImage(v.MimeType, v.Name) } + .map(({ Name, IsDir, Size, ModTime, MimeType }) => ( + + { this.renderImage(MimeType, Name) } { - v.IsDir ? - this.updatePath(v.Name)} onContextMenu={this.openMenu}>{ v.Name } + IsDir ? + this.updatePath(Name)} onContextMenu={e => this.openMenu(e, true)}>{ Name } : - { v.Name } + this.openMenu(e, true)}>{ Name } } - { shownColumns.datetime ? v.ModTime?.toLocaleString() : v.ModTime?.toLocaleDateString() } - { !v.IsDir ? bytesToString(v.Size, {}) : "" } + { shownColumns.datetime ? ModTime?.toLocaleString() : ModTime?.toLocaleDateString() } + { !IsDir ? bytesToString(Size, {}) : "" } )) } @@ -479,12 +544,10 @@ class FileBrowser extends Component { return ( - { - this.renderMenu() - } - { - loading ? : "" - } + { this.renderMenu() } + { this.renderNewFolderPopup() } + { loading === true && } + @@ -546,7 +609,7 @@ class FileBrowser extends Component { - + this.openMenu(e, false)}> { transitionFiles !== 0 && { + doAction = (brIndex, action, file) => { return new Promise((resolve, reject) => { + const fs = this.state.browserFs[brIndex] + ":", + remote = path.join(this.state.currentPath[brIndex], file) + switch(action) { case "copy": - console.log("did copy", path) - break; + const dstFs = this.state.browserFs[brIndex === 0 ? 1 : 0] + ":", + dstRemote = this.state.currentPath[brIndex === 0 ? 1 : 0] + + console.log({ fs, remote, dstFs, dstRemote }) + + return API.request({ + url: "/sync/copy", + data: { + srcFs: fs + remote, + dstFs: dstFs + dstRemote, + _async: true + } + }) + .then(resolve) + .catch(err => console.error(err)) case "move": - console.log("did move", path) + console.log("did move", file) break; case "delete": - console.log("did delete", path) + console.log("did delete", file) break; + case "newfolder": + return API.request({ + url: "/operations/mkdir", + data: { + fs, remote + } + }) + .then(() => { + let { files } = this.state + + files[brIndex].push({ + Name: file, + ModTime: new Date(), + Size: -1, + IsDir: true, + MimeType: "inode/directory" + }) + + this.setState({ files }) + + return resolve() + }) + .catch(reject) default: return reject(new Error("Invalid file action")) } }) @@ -236,8 +276,10 @@ class FileBrowserMenu extends Component { } renderRemoteButtons = () => { + const { browserFs, activeBrowser } = this.state + return this.props.remotes.map(v => ( - this.setRemote(v.name)}> { v.name } + this.setRemote(v.name)} active={browserFs[activeBrowser] === v.name}> { v.name } )) } diff --git a/src/styled.js b/src/styled.js index 9dc9b1e..cbcbd95 100644 --- a/src/styled.js +++ b/src/styled.js @@ -364,4 +364,22 @@ export const Checkbox = styled.input` height: 1rem; outline: unset; } +` + +export const Input = styled.input` + width: 100%; + padding: .6em 1em; + margin: .5em 0; + display: inline-block; + border-radius: 4px; + border: 1px solid var(--primary-color); + background-color: var(--button-color); + color: white; + transition: border .3s ease-in-out; + font-size: .9rem; + + &:focus { + outline: none; + background-color: var(--button-hover); + } ` \ No newline at end of file