parent
81e0d5fbdc
commit
243967259c
@ -1,7 +1,8 @@
|
|||||||
import { Component, Fragment } from 'react'
|
import { Component, Fragment } from 'react'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import styled from 'styled-components'
|
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
|
// images for different filetypes
|
||||||
import Back from '../assets/icons/arrowLeft.svg'
|
import Back from '../assets/icons/arrowLeft.svg'
|
||||||
@ -41,8 +42,8 @@ import { Button } from '../styled.js'
|
|||||||
import assert from 'assert'
|
import assert from 'assert'
|
||||||
import LineLoader from './LineLoader.jsx'
|
import LineLoader from './LineLoader.jsx'
|
||||||
|
|
||||||
const ROW_HEIGHT = 20
|
const ROW_HEIGHT = 28
|
||||||
const ROW_GAP = 8
|
const ROW_GAP = 0
|
||||||
const DATA_PADDING = 3
|
const DATA_PADDING = 3
|
||||||
const DEBOUNCE_THRESHOLD = 100
|
const DEBOUNCE_THRESHOLD = 100
|
||||||
|
|
||||||
@ -89,10 +90,12 @@ const GridFileBrowser = styled.div`
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1px 1fr ${({shownColumns}) => shownColumns.datetime ? "10rem" : ""} ${({shownColumns}) => shownColumns.date ? "6rem" : ""} ${({shownColumns}) => shownColumns.size ? "6rem" : ""};
|
grid-template-columns: 1px 1fr ${({shownColumns}) => shownColumns.datetime ? "10rem" : ""} ${({shownColumns}) => shownColumns.date ? "6rem" : ""} ${({shownColumns}) => shownColumns.size ? "6rem" : ""};
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: .5rem 1.5rem;
|
/* gap: .5rem 1.5rem; */
|
||||||
|
gap: 0 1.5rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
transition: transform .3s;
|
transition: transform .3s;
|
||||||
padding: .5rem;
|
/* padding: .5rem; */
|
||||||
|
padding: 0 .5rem;
|
||||||
`
|
`
|
||||||
|
|
||||||
const BrowserHeader = styled.div`
|
const BrowserHeader = styled.div`
|
||||||
@ -131,6 +134,7 @@ const EllipsisP = styled.p`
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
line-height: 28px;
|
||||||
`
|
`
|
||||||
|
|
||||||
const SpanPathDirectory = styled.span`
|
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))
|
const delay = t => new Promise(resolve => setTimeout(resolve, t))
|
||||||
|
|
||||||
class FileBrowser extends Component {
|
class FileBrowser extends Component {
|
||||||
@ -193,6 +210,7 @@ class FileBrowser extends Component {
|
|||||||
prevPath: "",
|
prevPath: "",
|
||||||
transitionFiles: 0,
|
transitionFiles: 0,
|
||||||
showMenu: false,
|
showMenu: false,
|
||||||
|
showNewFolder: false,
|
||||||
cursorX: 0,
|
cursorX: 0,
|
||||||
cursorY: 0,
|
cursorY: 0,
|
||||||
clicked: "",
|
clicked: "",
|
||||||
@ -239,7 +257,9 @@ class FileBrowser extends Component {
|
|||||||
window.removeEventListener('click', this.handleWindowClick)
|
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
|
// used to filter the files
|
||||||
handleInputChange({target}) {
|
handleInputChange({target}) {
|
||||||
@ -254,7 +274,7 @@ class FileBrowser extends Component {
|
|||||||
|
|
||||||
// change the way files should be ordered
|
// change the way files should be ordered
|
||||||
updateOrder = orderBy => {
|
updateOrder = orderBy => {
|
||||||
if (this.props.menuOpen || this.state.showMenu) return;
|
if (this.isMenuOpen()) return;
|
||||||
|
|
||||||
if (this.state.orderBy === orderBy) {
|
if (this.state.orderBy === orderBy) {
|
||||||
this.setState({ orderAscending: !this.state.orderAscending })
|
this.setState({ orderAscending: !this.state.orderAscending })
|
||||||
@ -301,7 +321,7 @@ class FileBrowser extends Component {
|
|||||||
|
|
||||||
// after the user clicks on a folder
|
// after the user clicks on a folder
|
||||||
updatePath = name => {
|
updatePath = name => {
|
||||||
if (this.props.menuOpen || this.state.showMenu) return;
|
if (this.isMenuOpen()) return;
|
||||||
|
|
||||||
const newPath = path.join(this.props.currentPath, name)
|
const newPath = path.join(this.props.currentPath, name)
|
||||||
|
|
||||||
@ -311,7 +331,7 @@ class FileBrowser extends Component {
|
|||||||
|
|
||||||
// after the user clicks on the back button
|
// after the user clicks on the back button
|
||||||
previousDirectory = () => {
|
previousDirectory = () => {
|
||||||
if (this.props.menuOpen || this.state.showMenu) return;
|
if (this.isMenuOpen()) return;
|
||||||
|
|
||||||
let currentPath = this.props.currentPath.split("/")
|
let currentPath = this.props.currentPath.split("/")
|
||||||
currentPath.pop()
|
currentPath.pop()
|
||||||
@ -322,7 +342,7 @@ class FileBrowser extends Component {
|
|||||||
|
|
||||||
// after the user click on the home button
|
// after the user click on the home button
|
||||||
rootDirectory = () => {
|
rootDirectory = () => {
|
||||||
if (this.props.menuOpen || this.state.showMenu) return;
|
if (this.isMenuOpen()) return;
|
||||||
|
|
||||||
this.props.updateFiles("/")
|
this.props.updateFiles("/")
|
||||||
this.setState({filter: ""})
|
this.setState({filter: ""})
|
||||||
@ -330,7 +350,7 @@ class FileBrowser extends Component {
|
|||||||
|
|
||||||
// after the user clicks on a path piece
|
// after the user clicks on a path piece
|
||||||
goToPath = index => {
|
goToPath = index => {
|
||||||
if (this.props.menuOpen || this.state.showMenu) return;
|
if (this.isMenuOpen()) return;
|
||||||
|
|
||||||
let currentPath = this.props.currentPath.split("/")
|
let currentPath = this.props.currentPath.split("/")
|
||||||
|
|
||||||
@ -353,8 +373,9 @@ class FileBrowser extends Component {
|
|||||||
* Opens the actions menu
|
* Opens the actions menu
|
||||||
* @param {ElementEvent} e The event that called this function
|
* @param {ElementEvent} e The event that called this function
|
||||||
*/
|
*/
|
||||||
openMenu = (e) => {
|
openMenu = (e, isFile) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
assert(
|
assert(
|
||||||
typeof e.pageX === "number"
|
typeof e.pageX === "number"
|
||||||
@ -363,34 +384,78 @@ class FileBrowser extends Component {
|
|||||||
&& e.target.innerHTML.length > 0
|
&& e.target.innerHTML.length > 0
|
||||||
)
|
)
|
||||||
|
|
||||||
this.setState({
|
if (isFile) {
|
||||||
cursorX: e.pageX,
|
this.setState({
|
||||||
cursorY: e.pageY,
|
cursorX: e.pageX,
|
||||||
showMenu: true,
|
cursorY: e.pageY,
|
||||||
clicked: e.target.innerHTML
|
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 (
|
||||||
|
<FileSettingsPopup onClick={e => e.stopPropagation()}>
|
||||||
|
<label htmlFor="newFolderName">
|
||||||
|
<FileSettingsHeader> New Folder </FileSettingsHeader>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<form onSubmit={this.handleNewFolderSubmit}>
|
||||||
|
<Input type="text" name="newFolderName" id="newFolderName" autoFocus defaultValue="" autoComplete="off" />
|
||||||
|
<input type="submit" style={{visibility: "hidden"}} />
|
||||||
|
</form>
|
||||||
|
</FileSettingsPopup>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders a simple menu to perform actions on the clicked file
|
* Renders a simple menu to perform actions on the clicked file
|
||||||
*/
|
*/
|
||||||
renderMenu = () => {
|
renderMenu = () => {
|
||||||
if (this.state.showMenu) return (
|
const { cursorX, cursorY, showMenu, clicked } = this.state
|
||||||
<div
|
|
||||||
|
if (showMenu && clicked.length) return (
|
||||||
|
<FileMenuContainer
|
||||||
onMouseLeave={() => this.setState({ showMenu: false })}
|
onMouseLeave={() => this.setState({ showMenu: false })}
|
||||||
style={{
|
cursorX={cursorX} cursorY={cursorY}
|
||||||
position: "fixed",
|
|
||||||
top: this.state.cursorY - 3,
|
|
||||||
left: this.state.cursorX + 3,
|
|
||||||
backgroundColor: "var(--button-color)",
|
|
||||||
zIndex: 900,
|
|
||||||
borderRadius: "3px"
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Button onClick={() => this.doAction("copy")}> Copy </Button>
|
<Button onClick={() => this.doAction("copy")}> Copy </Button>
|
||||||
<Button onClick={() => this.doAction("move")}> Move </Button>
|
<Button onClick={() => this.doAction("move")}> Move </Button>
|
||||||
<Button onClick={() => this.doAction("delete")}> Delete </Button>
|
<Button onClick={() => this.doAction("delete")}> Delete </Button>
|
||||||
</div>
|
<Button onClick={this.openNewFolderPopup}> New Folder </Button>
|
||||||
|
</FileMenuContainer>
|
||||||
|
)
|
||||||
|
|
||||||
|
if (showMenu) return (
|
||||||
|
<FileMenuContainer
|
||||||
|
onMouseLeave={() => this.setState({ showMenu: false })}
|
||||||
|
cursorX={cursorX} cursorY={cursorY}
|
||||||
|
>
|
||||||
|
<Button onClick={this.openNewFolderPopup}> New Folder </Button>
|
||||||
|
</FileMenuContainer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -408,17 +473,17 @@ class FileBrowser extends Component {
|
|||||||
files = this.getOrderedItems(files).slice(this.state.from, this.state.to)
|
files = this.getOrderedItems(files).slice(this.state.from, this.state.to)
|
||||||
|
|
||||||
return files
|
return files
|
||||||
.map(v => (
|
.map(({ Name, IsDir, Size, ModTime, MimeType }) => (
|
||||||
<Fragment key={v.Name + "file"}>
|
<Fragment key={Name + "file"}>
|
||||||
{ this.renderImage(v.MimeType, v.Name) }
|
{ this.renderImage(MimeType, Name) }
|
||||||
{
|
{
|
||||||
v.IsDir ?
|
IsDir ?
|
||||||
<DirNameP onClick={() => this.updatePath(v.Name)} onContextMenu={this.openMenu}>{ v.Name }</DirNameP>
|
<DirNameP onClick={() => this.updatePath(Name)} onContextMenu={e => this.openMenu(e, true)}>{ Name }</DirNameP>
|
||||||
:
|
:
|
||||||
<FilenameP onContextMenu={this.openMenu}>{ v.Name }</FilenameP>
|
<FilenameP onContextMenu={e => this.openMenu(e, true)}>{ Name }</FilenameP>
|
||||||
}
|
}
|
||||||
<ModifiedP shownColumns={shownColumns}> { shownColumns.datetime ? v.ModTime?.toLocaleString() : v.ModTime?.toLocaleDateString() } </ModifiedP>
|
<ModifiedP shownColumns={shownColumns}> { shownColumns.datetime ? ModTime?.toLocaleString() : ModTime?.toLocaleDateString() } </ModifiedP>
|
||||||
<SizeP shownColumns={shownColumns}> { !v.IsDir ? bytesToString(v.Size, {}) : "" } </SizeP>
|
<SizeP shownColumns={shownColumns}> { !IsDir ? bytesToString(Size, {}) : "" } </SizeP>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@ -479,12 +544,10 @@ class FileBrowser extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<FBContainer active={active} onClick={setActive}>
|
<FBContainer active={active} onClick={setActive}>
|
||||||
{
|
{ this.renderMenu() }
|
||||||
this.renderMenu()
|
{ this.renderNewFolderPopup() }
|
||||||
}
|
{ loading === true && <LineLoader/> }
|
||||||
{
|
|
||||||
loading ? <LineLoader/> : ""
|
|
||||||
}
|
|
||||||
<BrowserHeader>
|
<BrowserHeader>
|
||||||
<BrowserHeaderDiv>
|
<BrowserHeaderDiv>
|
||||||
<BrowseImage src={Back} alt="up directory" width="25" height="25" onClick={this.previousDirectory} />
|
<BrowseImage src={Back} alt="up directory" width="25" height="25" onClick={this.previousDirectory} />
|
||||||
@ -546,7 +609,7 @@ class FileBrowser extends Component {
|
|||||||
</GridFileBrowser>
|
</GridFileBrowser>
|
||||||
</BrowserHeader>
|
</BrowserHeader>
|
||||||
|
|
||||||
<BrowserWrapper onScroll={this.handleGridScroll}>
|
<BrowserWrapper onScroll={this.handleGridScroll} onContextMenu={e => this.openMenu(e, false)}>
|
||||||
{
|
{
|
||||||
transitionFiles !== 0 &&
|
transitionFiles !== 0 &&
|
||||||
<GridFileBrowser shownColumns={shownColumns} style={{
|
<GridFileBrowser shownColumns={shownColumns} style={{
|
||||||
|
@ -1,24 +1,6 @@
|
|||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import { Button } from '../styled'
|
import { Button } from '../styled'
|
||||||
|
|
||||||
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(--background-color);
|
|
||||||
color: white;
|
|
||||||
transition: border .3s ease-in-out;
|
|
||||||
font-size: .9rem;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
background-color: #282828;
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
export const Label = styled.label`
|
export const Label = styled.label`
|
||||||
margin-right: .7vw;
|
margin-right: .7vw;
|
||||||
`
|
`
|
||||||
|
@ -4,6 +4,7 @@ import API from '../utils/API'
|
|||||||
import FileBrowser from './fileBrowser'
|
import FileBrowser from './fileBrowser'
|
||||||
import { BrowserSettingButton, FileBrowserRemotes, FileBrowsersContainer, FileBrowserSettings, FileBrowserWrapper, FileSettingsPopup, FileSettingsHeader, FileColumnSettingsContainer, RemoteButton } from './fileBrowser.styled'
|
import { BrowserSettingButton, FileBrowserRemotes, FileBrowsersContainer, FileBrowserSettings, FileBrowserWrapper, FileSettingsPopup, FileSettingsHeader, FileColumnSettingsContainer, RemoteButton } from './fileBrowser.styled'
|
||||||
import assert from 'assert'
|
import assert from 'assert'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
import BrowserSingle from '../assets/icons/browserSingle.svg'
|
import BrowserSingle from '../assets/icons/browserSingle.svg'
|
||||||
import BrowserDual from '../assets/icons/browserDual.svg'
|
import BrowserDual from '../assets/icons/browserDual.svg'
|
||||||
@ -126,18 +127,57 @@ class FileBrowserMenu extends Component {
|
|||||||
* @param {String} action type of action to be performed
|
* @param {String} action type of action to be performed
|
||||||
* @param {String} path dir or file
|
* @param {String} path dir or file
|
||||||
*/
|
*/
|
||||||
doAction = (brIndex, action, path) => {
|
doAction = (brIndex, action, file) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
const fs = this.state.browserFs[brIndex] + ":",
|
||||||
|
remote = path.join(this.state.currentPath[brIndex], file)
|
||||||
|
|
||||||
switch(action) {
|
switch(action) {
|
||||||
case "copy":
|
case "copy":
|
||||||
console.log("did copy", path)
|
const dstFs = this.state.browserFs[brIndex === 0 ? 1 : 0] + ":",
|
||||||
break;
|
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":
|
case "move":
|
||||||
console.log("did move", path)
|
console.log("did move", file)
|
||||||
break;
|
break;
|
||||||
case "delete":
|
case "delete":
|
||||||
console.log("did delete", path)
|
console.log("did delete", file)
|
||||||
break;
|
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"))
|
default: return reject(new Error("Invalid file action"))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -236,8 +276,10 @@ class FileBrowserMenu extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderRemoteButtons = () => {
|
renderRemoteButtons = () => {
|
||||||
|
const { browserFs, activeBrowser } = this.state
|
||||||
|
|
||||||
return this.props.remotes.map(v => (
|
return this.props.remotes.map(v => (
|
||||||
<RemoteButton key={v.name} onClick={() => this.setRemote(v.name)}> { v.name } </RemoteButton>
|
<RemoteButton key={v.name} onClick={() => this.setRemote(v.name)} active={browserFs[activeBrowser] === v.name}> { v.name } </RemoteButton>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -364,4 +364,22 @@ export const Checkbox = styled.input`
|
|||||||
height: 1rem;
|
height: 1rem;
|
||||||
outline: unset;
|
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);
|
||||||
|
}
|
||||||
`
|
`
|
Loading…
x
Reference in New Issue
Block a user