diff --git a/src/components/fileBrowser.jsx b/src/components/fileBrowser.jsx
index 1c7839a..c9e3a8d 100644
--- a/src/components/fileBrowser.jsx
+++ b/src/components/fileBrowser.jsx
@@ -1,7 +1,7 @@
import { Component, Fragment } from 'react'
import path from 'path'
import styled from 'styled-components'
-import { FileSettingsHeader, FileSettingsPopup, Label } from './fileBrowser.styled.js'
+import { Label } from './fileBrowser.styled.js'
import { Input } from '../styled'
// images for different filetypes
@@ -38,8 +38,6 @@ import ZIP from '../assets/fileTypes/zip.svg'
import CaretDown from '../assets/icons/caretDown.svg'
import bytesToString from '../utils/bytestring.js'
-import { Button } from '../styled.js'
-import assert from 'assert'
import LineLoader from './LineLoader.jsx'
const ROW_HEIGHT = 28
@@ -184,19 +182,6 @@ 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 {
@@ -232,10 +217,9 @@ class FileBrowser extends Component {
window.addEventListener('click', this.handleWindowClick)
}
- componentDidUpdate = () => {
+ componentDidUpdate = (prevProps) => {
// if the component was just created set the path
- if (this.state.prevPath === "" && this.props.currentPath !== "") return this.setState({ prevPath: this.props.currentPath, files: this.props.files })
-
+ if (prevProps.currentPath === "" && this.props.currentPath !== "") return this.setState({ prevPath: this.props.currentPath, files: this.props.files })
// the path changed
if (this.props.currentPath !== this.state.prevPath) {
@@ -250,6 +234,8 @@ class FileBrowser extends Component {
delay(300).then(() => this.setState({ transitionFiles: 3 * direction, files: this.props.files }))
// delay(11500).then(() => this.setState({ transitionFiles: 3 }))
delay(600).then(() => this.setState({ transitionFiles: 0 }))
+ } else if (prevProps.files !== this.props.files) {
+ this.setState({ files: this.props.files })
}
}
@@ -362,103 +348,6 @@ class FileBrowser extends Component {
}
}
- /**
- * Wrapper for the props.action function
- * @param {String} a type of action to be performed
- * @returns {Promise}
- */
- doAction = a => this.props.action(a, this.state.clicked).catch(err => console.error(err))
-
- /**
- * Opens the actions menu
- * @param {ElementEvent} e The event that called this function
- */
- openMenu = (e, isFile) => {
- e.preventDefault()
- e.stopPropagation()
-
- assert(
- typeof e.pageX === "number"
- && typeof e.pageY === "number"
- && typeof e.target.innerHTML === "string"
- && e.target.innerHTML.length > 0
- )
-
- 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 = () => {
- const { cursorX, cursorY, showMenu, clicked } = this.state
-
- if (showMenu && clicked.length) return (
- this.setState({ showMenu: false })}
- cursorX={cursorX} cursorY={cursorY}
- >
-
-
-
-
-
- )
-
- if (showMenu) return (
- this.setState({ showMenu: false })}
- cursorX={cursorX} cursorY={cursorY}
- >
-
-
- )
- }
-
/**
* Render the files found in a directory
* @param {Boolean} isNext are these the next files to be rendered, if so use props instead of state
@@ -478,9 +367,9 @@ class FileBrowser extends Component {
{ this.renderImage(MimeType, Name) }
{
IsDir ?
- this.updatePath(Name)} onContextMenu={e => this.openMenu(e, true)}>{ Name }
+ this.updatePath(Name)} onContextMenu={e => this.props.openMenu(e, true)}>{ Name }
:
- this.openMenu(e, true)}>{ Name }
+ this.props.openMenu(e, true)}>{ Name }
}
{ shownColumns.datetime ? ModTime?.toLocaleString() : ModTime?.toLocaleDateString() }
{ !IsDir ? bytesToString(Size, {}) : "" }
@@ -544,8 +433,6 @@ class FileBrowser extends Component {
return (
- { this.renderMenu() }
- { this.renderNewFolderPopup() }
{ loading === true && }
@@ -609,7 +496,7 @@ class FileBrowser extends Component {
- this.openMenu(e, false)}>
+ this.props.openMenu(e, false)}>
{
transitionFiles !== 0 &&
{
this.getFiles(0, tempPath)
@@ -67,13 +76,31 @@ class FileBrowserMenu extends Component {
window.removeEventListener('click', this.handleWindowClick)
window.removeEventListener("orientationchange", this.handleOrientationChange)
+ window.removeEventListener('keyup', this.handleKeyup)
}
- // stopPropagation does not work because it is a different event cause
- handleWindowClick = () => this.setState({ showFileSettings: false })
+ /**
+ * closes any open menu
+ * stopPropagation does not work here because it is a different event
+ */
+ handleWindowClick = () => {
+ this.setState({ showFileSettings: false })
+ this.closeMenu()
+ }
+ // update the innerwidth, used to determine wether to show one or two browsers
handleOrientationChange = () => this.setState({ windowWidth: window.innerWidth })
+ /**
+ * closes the menu if the user presses the escape key
+ */
+ handleKeyup = e => {
+ if (e.key === "Escape") {
+ this.setState({ showFileSettings: false })
+ this.closeMenu()
+ }
+ }
+
/**
*
* @param {Number} brIndex identify which browser wants new files
@@ -125,62 +152,119 @@ class FileBrowserMenu extends Component {
*
* @param {Number} brIndex identify which browser wants to do the action
* @param {String} action type of action to be performed
- * @param {String} path dir or file
+ * @param {String} newFile only used when creating a dir or renaming
*/
- doAction = (brIndex, action, file) => {
- return new Promise((resolve, reject) => {
- const fs = this.state.browserFs[brIndex] + ":",
- remote = path.join(this.state.currentPath[brIndex], file)
+ doAction = (brIndex, action, newFile) => {
+ if (!action) return this.closeMenu()
- switch(action) {
- case "copy":
- const dstFs = this.state.browserFs[brIndex === 0 ? 1 : 0] + ":",
- dstRemote = this.state.currentPath[brIndex === 0 ? 1 : 0]
+ // dir or file, only used when renaming
+ let clickedFile = this.state.menuInfo.file
- console.log({ fs, remote, dstFs, dstRemote })
+ const fs = this.state.browserFs[brIndex] + ":",
+ remote = path.join(this.state.currentPath[brIndex], clickedFile.Name || newFile),
+ dstFs = this.state.browserFs[brIndex === 0 ? 1 : 0] + ":",
+ dstRemote = path.join(this.state.currentPath[brIndex === 0 ? 1 : 0], newFile || clickedFile.Name)
+ switch(action) {
+ case "copy":
+ return API.request({
+ url: clickedFile.IsDir ? "/sync/copy" : "/operations/copyfile",
+ data: Object.assign({
+ _async: true
+ }, clickedFile.IsDir ? { srcFs: fs + remote, dstFs: dstFs + dstRemote, } : { srcFs: fs, srcRemote: remote, dstFs, dstRemote })
+ })
+ .catch(err => console.error(err))
+ .finally(this.closeMenu)
+ case "move":
+ return API.request({
+ url: clickedFile.IsDir ? "/sync/move" : "/operations/movefile",
+ data: Object.assign({
+ _async: true
+ }, clickedFile.IsDir ? { srcFs: fs + remote, dstFs: dstFs + dstRemote, } : { srcFs: fs, srcRemote: remote, dstFs, dstRemote })
+ })
+ .catch(err => console.error(err))
+ .finally(this.closeMenu)
+ case "delete":
+ return API.request({
+ url: "operations/" + (clickedFile.IsDir ? "purge" : "deletefile"),
+ data: { fs, remote, _async: true }
+ })
+ .then(() => {
+ let { files } = this.state
+ files[brIndex] = files[brIndex].filter(v => v.Name !== clickedFile.Name)
+ this.setState({ files })
+ })
+ .catch(err => console.error(err))
+ .finally(this.closeMenu)
+ case "rename":
+ if (clickedFile.IsDir) return API.request({
+ url: "/operations/mkdir",
+ data: { fs, remote: dstRemote }
+ })
+ .then(() => {
return API.request({
- url: "/sync/copy",
+ url: "/sync/move",
data: {
srcFs: fs + remote,
- dstFs: dstFs + dstRemote,
- _async: true
- }
- })
- .then(resolve)
- .catch(err => console.error(err))
- case "move":
- console.log("did move", file)
- break;
- case "delete":
- console.log("did delete", file)
- break;
- case "newfolder":
- return API.request({
- url: "/operations/mkdir",
- data: {
- fs, remote
+ dstFs: fs + path.join(this.state.currentPath[brIndex], newFile)
}
})
.then(() => {
- let { files } = this.state
-
- files[brIndex].push({
- Name: file,
- ModTime: new Date(),
- Size: -1,
- IsDir: true,
- MimeType: "inode/directory"
+ return API.request({
+ url: "/operations/purge",
+ data: { fs, remote, _async: true }
})
-
- this.setState({ files })
-
- return resolve()
+ .then(() => {
+ let { files } = this.state
+ let f = files[brIndex].filter(v => v.Name === clickedFile.Name)[0]
+ f.Name = newFile
+ this.setState({ files })
+ })
+ .catch(err => console.error(err))
})
- .catch(reject)
- default: return reject(new Error("Invalid file action"))
- }
- })
+ .catch(err => console.error(err))
+ })
+ .catch(err => console.error(err))
+ .finally(this.closeMenu)
+
+ return API.request({
+ url: clickedFile.IsDir ? "/sync/copy" : "/operations/movefile",
+ data: { srcFs: fs,
+ srcRemote: remote,
+ dstFs: fs,
+ dstRemote: path.join(this.state.currentPath[brIndex], newFile)
+ }
+ })
+ .then(() => {
+ let { files } = this.state
+ let f = files[brIndex].filter(v => v.Name === clickedFile.Name)[0]
+ f.Name = newFile
+ this.setState({ files })
+ })
+ .catch(err => console.error(err))
+ .finally(this.closeMenu)
+ case "newfolder":
+ return API.request({
+ url: "/operations/mkdir",
+ data: { fs, remote }
+ })
+ .then(() => {
+ let { files } = this.state
+
+ files[brIndex].push({
+ Name: newFile,
+ ModTime: new Date(),
+ Size: -1,
+ IsDir: true,
+ MimeType: "inode/directory"
+ })
+
+ this.setState({ files })
+ })
+ .catch(() => {})
+ .finally(this.closeMenu)
+ default: assert(false);
+ }
}
handleColumnChange = ({target}) => {
@@ -216,37 +300,53 @@ class FileBrowserMenu extends Component {
}
}
+ /**
+ * Opens the actions menu
+ * @param {ElementEvent} e The event that called this function
+ * @param {Boolean} isFile was a file clicked
+ */
+ openMenu = (brIndex, e, isFile) => {
+ e.preventDefault()
+ e.stopPropagation()
+
+ assert(
+ typeof e.pageX === "number"
+ && typeof e.pageY === "number"
+ && typeof e.target.innerHTML === "string"
+ && e.target.innerHTML.length > 0
+ )
+
+ let file = {}
+ if (isFile) file = this.state.files[brIndex].filter(v => v.Name === e.target.innerHTML)[0]
+
+ this.setState({
+ menuInfo: {
+ file,
+ brIndex,
+ cursorX: e.pageX,
+ cursorY: e.pageY,
+ otherPath: this.state.currentPath[brIndex === 0 ? 1 : 0]
+ }
+ })
+ }
+
+ closeMenu = () => {
+ this.setState({
+ menuInfo: {
+ cursorX: 0,
+ cursorY: 0,
+ brIndex: -1,
+ file: {},
+ otherPath: ""
+ }
+ })
+ }
+
openFileSettings = e => {
e.stopPropagation()
this.setState({ showFileSettings: true })
}
- renderFileSettings = () => {
- const { size, date, datetime } = this.state.shownColumns
-
- if (this.state.showFileSettings) return (
- e.stopPropagation()}>
- Columns
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )
- }
-
switchBrowserMode = () => {
if (this.state.loading[0] || this.state.loading[1]) return;
@@ -275,6 +375,42 @@ class FileBrowserMenu extends Component {
}, 50)
}
+ /**
+ * Renders a simple menu to perform actions on the clicked file
+ */
+ renderMenu = () => {
+ const { menuInfo } = this.state
+ const { brIndex } = menuInfo
+
+ if (brIndex !== -1) return this.doAction(brIndex, action, file)} hideMenu={() => this.setState({ showMenu: false })} />
+ }
+
+ renderFileSettings = () => {
+ const { size, date, datetime } = this.state.shownColumns
+
+ if (this.state.showFileSettings) return (
+ e.stopPropagation()}>
+ Columns
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+ }
+
renderRemoteButtons = () => {
const { browserFs, activeBrowser } = this.state
@@ -286,46 +422,28 @@ class FileBrowserMenu extends Component {
renderBrowser = () => {
const { files, currentPath, loading, dualBrowser, activeBrowser, shownColumns, windowWidth } = this.state
+ const data = files.map((files, i) => ({
+ files,
+ setActive: () => this.setActiveBrowser(i),
+ updateFiles: path => this.getFiles(i, path),
+ currentPath: currentPath[i],
+ loading: loading[i],
+ shownColumns: shownColumns,
+ active: activeBrowser === i && dualBrowser,
+ openMenu: (e, isFile) => this.openMenu(i, e, isFile)
+ }))
+
if (windowWidth >= 1200) return (
- this.setActiveBrowser(0)}
- action={(a, p) => this.doAction(0, a, p)}
- files={files[0]}
- updateFiles={path => this.getFiles(0, path)}
- currentPath={currentPath[0]}
- loading={loading[0]}
- shownColumns={shownColumns}
- active={activeBrowser === 0}
- />
+
{
dualBrowser &&
- this.setActiveBrowser(1)}
- action={(a, p) => this.doAction(1, a, p)}
- files={files[1]}
- updateFiles={path => this.getFiles(1, path)}
- currentPath={currentPath[1]}
- loading={loading[1]}
- shownColumns={shownColumns}
- active={activeBrowser === 1 && dualBrowser}
- />
+
}
)
- return (
- this.setActiveBrowser(activeBrowser)}
- action={(a, p) => this.doAction(activeBrowser, a, p)}
- files={files[activeBrowser]}
- updateFiles={path => this.getFiles(activeBrowser, path)}
- currentPath={currentPath[activeBrowser]}
- loading={loading[activeBrowser]}
- shownColumns={shownColumns}
- active={false}
- />
- )
+ return
}
renderSwitchBrowser = () => {
@@ -347,9 +465,8 @@ class FileBrowserMenu extends Component {
render = () => {
return (
- {
- this.renderFileSettings()
- }
+ { this.renderFileSettings() }
+ { this.renderMenu() }
File Browser
Close