Compare commits

..

6 Commits

Author SHA1 Message Date
61387e9fd8 rename dont select file ext 2021-08-29 21:31:01 +02:00
1354383860 missed one 2021-08-29 19:59:41 +02:00
6f25be49ca first implementation renaming 2021-08-29 19:59:25 +02:00
5f4da5d13f minor cleanup 2021-08-28 17:39:09 +02:00
24e0bcbf92 refactor file browser list 2021-08-28 15:28:38 +02:00
5e7169079c added loading spinner 2021-08-27 21:50:14 +02:00
17 changed files with 221 additions and 126 deletions

4
src/assets/spinner.svg Normal file
View File

@@ -0,0 +1,4 @@
<svg class="animate-spin h-6 w-6 dark:text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>

After

Width:  |  Height:  |  Size: 400 B

View File

@@ -4,7 +4,6 @@ import genDownloadLink from "../functions/genDownloadLink"
import { FileOpenerProps } from "../types/FileOpenerProps"
const AudioOpener: React.FC<FileOpenerProps> = (props) => {
const audio = React.createRef<HTMLAudioElement>()
useEffect(()=>{

View File

@@ -1,5 +1,4 @@
import React from "react"
import PropTypes from "prop-types"
import { ReactComponent as BreadcrumImage } from "./../assets/breadcrum.svg"
interface Props{
@@ -19,6 +18,7 @@ const Breadcrum: React.FC<Props> = (props) => {
Root
</a>
</li>
{parts.map((e,i,arr)=>{
const last = i == arr.length - 1
return <div key={e} className="inline-flex items-center cursor-pointer">
@@ -39,9 +39,4 @@ const Breadcrum: React.FC<Props> = (props) => {
)
}
Breadcrum.propTypes = {
path: PropTypes.string.isRequired,
onDirClick: PropTypes.func
}
export default Breadcrum

View File

@@ -8,7 +8,6 @@ interface Props {
}
const CreateDirButton: React.FC<Props> = (props) => {
const [name,setName] = useState("")
const [show,setShow] = useState(false)
const input = useRef<HTMLInputElement>(null)
@@ -37,6 +36,7 @@ const CreateDirButton: React.FC<Props> = (props) => {
setName("")
setShow(false)
}}>
<input
className="bg-transparent dark:text-gray-300 outline-none mx-1 border-b"
type="text"
@@ -48,15 +48,6 @@ const CreateDirButton: React.FC<Props> = (props) => {
setName("")
}}
/>
{/* <button
onClick={()=>{
props.onPressed?.(name)
setName("")
setShow(false)
}}
>
Create
</button> */}
</form>
</div>
</div>

View File

@@ -1,5 +1,4 @@
import React from "react"
import PropTypes from "prop-types"
import { Directory } from "../generated/graphql"
import { MdFolderOpen } from "react-icons/md"
@@ -21,8 +20,4 @@ const DirectoryElement: React.FC<Props> = (props) => {
)
}
DirectoryElement.propTypes = {
dir: PropTypes.any.isRequired // TODO: maybe you can use the interface
}
export default DirectoryElement

View File

@@ -45,7 +45,6 @@ const DragAndDrop: React.FC<Props> = (props) => {
props.onDrop?.()
if (event.dataTransfer?.files && event.dataTransfer.files.length > 0) {
props.handleDrop?.(event.dataTransfer.files)
// event.dataTransfer.clearData()
}
}

View File

@@ -11,9 +11,10 @@ import Breadcrum from "./Breadcrum"
import CreateDirButton from "./CreateDirButton"
import DragAndDrop from "./DragAndDrop"
import FileBrowserContextMenu, { Action, CONTEXT_MENU_DIR, CONTEXT_MENU_FILE } from "./FileBrowserContextMenu"
import FileBrowserElement from "./FileBrowserElement"
import FileOpen from "./FileOpen"
import FileUploadButton from "./FileUploadButton"
import { ReactComponent as Spinner } from "./../assets/spinner.svg"
import FileBrowserList from "./FileBrowserList"
function uriToPath(pathname:string) {
// strip the "/f" from e.g. "/f/dir1/dir2"
@@ -32,6 +33,8 @@ const FileBrowser: React.FC<RouteComponentProps> = (props) => {
const [srcID,setSrcID] = useState("")
const [pasteAction,setPasteAction] = useState<Action>()
const [editID,setEditID] = useState("")
const [editEnable,setEditEnable] = useState(false)
const [deleteMutation] = useDeleteFileMutation()
const [copyMutation] = useCopyMutation()
@@ -64,24 +67,6 @@ const FileBrowser: React.FC<RouteComponentProps> = (props) => {
refetchDir()
}
function openFileContextMenu(e: React.MouseEvent, id: string) {
e.preventDefault()
showFileContext(e,{
props:{
id
}
})
}
function openDirContextMenu(e: React.MouseEvent, id: string) {
e.preventDefault()
showDirContext(e,{
props:{
id
}
})
}
async function onContextSelect(action:Action, id: string) {
switch (action) {
case Action.FileDelete:
@@ -111,6 +96,10 @@ const FileBrowser: React.FC<RouteComponentProps> = (props) => {
case Action.FileDownload:
downloadFile(genDownloadLink(id))
break
case Action.FileRename:
setEditID(id)
setEditEnable(true)
break
default:
break
}
@@ -147,49 +136,51 @@ const FileBrowser: React.FC<RouteComponentProps> = (props) => {
</div>
</div>
<div>
{loading &&
<div>Loading...</div> // TODO: center
}
<table className="w-full">
<thead className="border-b-2 dark:border-gray-900">
<tr>
<th className="text-left">Name</th>
<th className="text-left">Last Modified</th>
<th className="text-left">Size</th>
</tr>
</thead>
<tbody className="divide-y dark:divide-gray-900">
<FileBrowserList
directorys={data?.directorys || []}
files={data?.files || []}
{ data?.directorys.map(v => (<FileBrowserElement
key={v?.id}
dir={v}
onClick={(dir)=>{
props.history.push(pathToUri(dir.id))
onDirClick={(e,path)=>{
props.history.push(pathToUri(path))
}}
onContextMenu={(e)=>{
openDirContextMenu(e,v.id)
}}
/>))}
{ data?.files.map(v => (<FileBrowserElement
key={v?.id}
file={v}
onClick={(file)=>{
setOpenFileId(file.id)
onDirContext={(e,path)=>{
e.preventDefault()
showDirContext(e,{props:{id:path}})
}}
onFileClick={(e,id)=>{
setOpenFileId(id)
setShowFile(true)
}}
onContextMenu={(e)=>{
openFileContextMenu(e,v.id)
onFileContext={(e,id)=>{
e.preventDefault()
showFileContext(e,{props:{id}})
}}
/>))}
</tbody>
</table>
editId={editID}
editEnable={editEnable}
onRenameDone={(id,changed,newName)=>{
setEditEnable(false)
if (changed){
console.debug("Changed: ",newName)
}else{
console.debug("Not changed")
}
}}
/>
{loading &&
<div className="flex justify-center mt-4">
<Spinner />
</div>
{<FileOpen id={openFileId} show={showFile} onCloseClick={()=>{
}
</div>
<FileOpen id={openFileId} show={showFile} onCloseClick={()=>{
setShowFile(false)
}} />}
}} />
</DragAndDrop>
</div>
)

View File

@@ -11,6 +11,7 @@ export enum Action {
FilePaste,
FileMove,
FileDownload,
FileRename,
DirDelete
}
@@ -20,7 +21,6 @@ interface Props {
}
const FileBrowserContextMenu: React.FC<Props> = (props) => {
function onClick({ props: itemProps, data }: ItemParams<{id:string}, Action>) {
if (itemProps?.id && data != null){
props.onSelect?.(data,itemProps.id)
@@ -33,6 +33,7 @@ const FileBrowserContextMenu: React.FC<Props> = (props) => {
<Item onClick={onClick} data={Action.FileDelete} >Delete</Item>
<Item onClick={onClick} data={Action.FileCopy} >Copy</Item>
<Item onClick={onClick} data={Action.FileMove} >Move</Item>
<Item onClick={onClick} data={Action.FileRename} >Rename</Item>
<Item onClick={onClick} data={Action.FileDownload} >Download</Item>
<Separator />
<Item onClick={onClick} data={Action.FilePaste} disabled={!props.pasteActive}>Paste</Item>

View File

@@ -1,40 +1,43 @@
import React from "react"
import PropTypes from "prop-types"
import { Directory, File } from "../generated/graphql"
import DirectoryComponent from "./DirectoryElement"
import FileElement from "./FileElement"
interface Props {
file?: File | null
dir?: Directory | null
onClick?: (data: File | Directory) => void
file?: File
dir?: Directory
onClick?: (event: React.MouseEvent ,data: File | Directory) => void
onContextMenu?: (e:React.MouseEvent) => void
edit: boolean
onRename?: (newName: string)=>void
onCancleRename?: ()=>void
}
const FileBrowserElement: React.FC<Props> = (props) => {
return (
<tr
className="hover:bg-gray-100 dark:hover:bg-gray-900 text-lg"
onClick={()=>{
onClick={(e)=>{
if(props.file){
props.onClick?.(props.file)
props.onClick?.(e,props.file)
}else if(props.dir){
props.onClick?.(props.dir)
props.onClick?.(e,props.dir)
}
}}
onContextMenu={(e)=>props.onContextMenu?.(e)}
>
{(props.file) ? <FileElement file={props.file}/>:(props.dir)?<DirectoryComponent dir={props.dir} />:<></>}
{(props.file) ? <FileElement
edit={props.edit}
file={props.file}
onCancleRename={props.onCancleRename}
onRename={props.onRename}
/>:(props.dir)?<DirectoryComponent
dir={props.dir}
/>:<></>}
</tr>
)
}
FileBrowserElement.propTypes = {
dir: PropTypes.any,
file: PropTypes.any,
onClick: PropTypes.func
}
export default FileBrowserElement

View File

@@ -0,0 +1,80 @@
import React from "react"
import { Directory, File } from "../generated/graphql"
import FileBrowserElement from "./FileBrowserElement"
interface Props{
directorys: Directory[]
files: File[]
onFileContext?: (event: React.MouseEvent, id: string)=>void
onDirContext?: (event: React.MouseEvent, path: string)=>void
onFileClick?: (event: React.MouseEvent,id: string)=>void
onDirClick?: (event: React.MouseEvent,path: string)=>void
editId: string
editEnable: boolean
onRenameDone?: (id: string, changed: boolean, newName: string)=>void
}
const FileBrowserList: React.FC<Props> = (props) => {
return <>
<table className="w-full">
<thead className="border-b-2 dark:border-gray-900">
<tr>
<th className="text-left">Name</th>
<th className="text-left">Last Modified</th>
<th className="text-left">Size</th>
</tr>
</thead>
<tbody className="divide-y dark:divide-gray-900">
{ props.directorys.map(v => (<FileBrowserElement
key={v.id}
dir={v}
onClick={(e,dir)=>{
props.onDirClick?.(e,dir.id)
}}
onContextMenu={(e)=>{
props.onDirContext?.(e,v.id)
}}
edit={props.editEnable && (v.id === props.editId)}
onRename={(newName)=>{
if (v.name != newName){
props.onRenameDone?.(v.id,true,newName)
}else{
props.onRenameDone?.(v.id,false,newName)
}
}}
onCancleRename={()=>props.onRenameDone?.(v.id,false,"")}
/>))}
{ props.files.map(v => (<FileBrowserElement
key={v.id}
file={v}
onClick={(e,file)=>{
props.onFileClick?.(e,file.id)
}}
onContextMenu={(e)=>{
props.onFileContext?.(e,v.id)
}}
edit={props.editEnable && (v.id === props.editId)}
onRename={(newName)=>{
if (v.name != newName){
props.onRenameDone?.(v.id,true,newName)
}else{
props.onRenameDone?.(v.id,false,newName)
}
}}
onCancleRename={()=>props.onRenameDone?.(v.id,false,"")}
/>))}
</tbody>
</table>
</>
}
export default FileBrowserList

View File

@@ -1,19 +1,28 @@
import React from "react"
import PropTypes from "prop-types"
import { File } from "../generated/graphql"
import sizeToReadable from "../functions/sizeToReadable"
import dateFormat from "../functions/dateFomat"
import { FaRegFileAlt } from "react-icons/fa"
import Renameable from "./Renameable"
interface Props {
file: File
file: File,
edit: boolean
onRename?: (newName: string)=>void
onCancleRename?: ()=>void
}
const FileElement: React.FC<Props> = (props) => {
return (
<>
<td>
<FaRegFileAlt className="inline" /> {props.file.name}
<FaRegFileAlt className="inline" />
<Renameable
text={props.file.name || ""}
edit={props.edit}
onCancleRename={props.onCancleRename}
onRename={props.onRename}
/>
</td>
<td>
{dateFormat(props.file.lastModified)}
@@ -26,8 +35,4 @@ const FileElement: React.FC<Props> = (props) => {
)
}
FileElement.propTypes = {
file: PropTypes.any.isRequired
}
export default FileElement

View File

@@ -1,5 +1,4 @@
import React from "react"
import PropTypes from "prop-types"
import { useGetFileQuery } from "../generated/graphql"
import ImageOpener from "./ImageOpener"
import TextOpener from "./TextOpener"
@@ -13,7 +12,6 @@ interface Props {
}
const FileOpen: React.FC<Props> = (props) => {
if (!props.id) {
return <></>
}
@@ -66,10 +64,4 @@ const FileOpen: React.FC<Props> = (props) => {
)
}
FileOpen.propTypes = {
id: PropTypes.string.isRequired,
show: PropTypes.bool.isRequired,
onCloseClick: PropTypes.func
}
export default FileOpen

View File

@@ -7,7 +7,6 @@ interface Props {
}
const FileUploadButton: React.FC<Props> = (props) => {
const inputRef = useRef<HTMLInputElement>(null)
return (

View File

@@ -1,5 +1,4 @@
import React from "react"
import PropTypes from "prop-types"
import genDownloadLink from "../functions/genDownloadLink"
import { FileOpenerProps } from "../types/FileOpenerProps"
@@ -9,8 +8,4 @@ const ImageOpener: React.FC<FileOpenerProps> = (props) => {
)
}
ImageOpener.propTypes = {
file: PropTypes.any.isRequired
}
export default ImageOpener

View File

@@ -0,0 +1,51 @@
import React from "react"
import { useEffect } from "react"
import { useRef } from "react"
import { useState } from "react"
interface Props {
text: string
edit: boolean
onRename?: (newName: string)=>void
onCancleRename?: ()=>void
}
const Renameable: React.FC<Props> = (props) => {
const [inputValue,setinputValue] = useState(props.text)
const inputRef = useRef<HTMLInputElement>(null)
useEffect(()=>{
if (props.edit){
const i = inputValue.lastIndexOf(".")
inputRef.current?.select()
inputRef.current?.setSelectionRange(0,i == -1?inputValue.length:i)
}else{
setinputValue(props.text)
}
},[props.edit])
return (
<>
{props.edit && <form className="inline" onSubmit={(e)=>{
e.preventDefault()
props.onRename?.(inputValue)
}}>
<input
className="bg-transparent dark:text-gray-300 outline-none inline"
type="text"
ref={inputRef}
value={inputValue}
onClick={(e)=>e.stopPropagation()}
onChange={(e)=>{setinputValue(e.target.value)}}
onBlur={props.onCancleRename}
/></form>
}
{!props.edit &&
<div className="inline">{props.text}</div>
}
</>
)
}
export default Renameable

View File

@@ -1,5 +1,4 @@
import React from "react"
import PropTypes from "prop-types"
import { FileOpenerProps } from "../types/FileOpenerProps"
import { useEffect } from "react"
import { useState } from "react"
@@ -23,8 +22,4 @@ const TextOpener: React.FC<FileOpenerProps> = (props) => {
)
}
TextOpener.propTypes = {
file: PropTypes.any.isRequired,
}
export default TextOpener