Compare commits

..

15 Commits

Author SHA1 Message Date
1d2f25ef04 added parent to ObjID 2022-02-07 15:51:16 +01:00
9462a74437 invalidate local cache when moving dir 2021-11-22 01:28:44 +01:00
1683c0d467 moved apollo cache in seperate file 2021-11-22 01:28:22 +01:00
615292ac24 implmented moveDir 2021-11-02 00:34:46 +01:00
6bcb41820c updated schema 2021-11-02 00:34:29 +01:00
6b8c922d5b fixed breadcrum onDirClick 2021-10-14 18:59:52 +02:00
91e75a2c72 table tr for head 2021-10-02 17:42:43 +02:00
fd688021ea added table to bucket select 2021-10-02 00:17:21 +02:00
fcb34aeacc use Table for FileBrowserList 2021-10-01 23:44:12 +02:00
a287df5e0c added table component 2021-10-01 23:43:49 +02:00
9ca6770cd4 added padding on App 2021-10-01 21:23:45 +02:00
06b7c323ae made drag and drop handler global 2021-10-01 21:23:27 +02:00
c12181fe16 fixed srcID on context menu action 2021-09-29 22:28:27 +02:00
9664eb07b8 breadcrum cursor seperator 2021-09-29 22:20:29 +02:00
f38a34eb2a use buckets gql query 2021-09-29 21:42:21 +02:00
18 changed files with 1552 additions and 1374 deletions

View File

@@ -20,7 +20,7 @@ const App: React.FC = () => {
},[data])
return (
<div className="dark:text-gray-300">
<div className="dark:text-gray-300 px-2 pt-1">
{
ready&&
<Router>

55
src/Cache.ts Normal file
View File

@@ -0,0 +1,55 @@
import { InMemoryCache } from "@apollo/client"
import ObjID from "./types/ObjID"
const cache = new InMemoryCache({
typePolicies:{
File:{
fields:{
id:{
merge(_,incomming){
// HACK: i use the merge function to change the id from a string to ObjID object.
// afaik apollo does not yet support custom scalar types.
if (!incomming){
return incomming
}else if (incomming instanceof ObjID){
return incomming
}else{
return ObjID.fromString(incomming as string)
}
}
}
}
},
Directory:{
fields:{
id:{
merge(_,incomming){
if (!incomming){
return incomming
}else if (incomming instanceof ObjID){
return incomming
}else{
return ObjID.fromString(incomming as string)
}
}
}
}
},
Query: {
fields: {
files: {
merge(existing, incoming){
return incoming
}
},
directorys:{
merge(existing, incoming){
return incoming
}
}
}
}
}
})
export default cache

View File

@@ -30,7 +30,7 @@ const Breadcrum: React.FC<Props> = ({path,onDirClick}) => {
</Link>
</div>
<div className="breadcrum-item">
<BreadcrumImage className="h-5 w-auto text-gray-400" />
<BreadcrumImage className="h-5 w-auto text-gray-400 cursor-default" />
<li>
<a className={!keyParts.length?"text-blue-500":""} onClick={()=>{
onDirClick?.(new ObjID(path.bucket,"/"))
@@ -43,13 +43,13 @@ const Breadcrum: React.FC<Props> = ({path,onDirClick}) => {
{keyParts.map((e,i,arr)=>{
const last = i == arr.length - 1
return <div key={e} className="breadcrum-item">
<BreadcrumImage className="h-5 w-auto text-gray-400" />
<BreadcrumImage className="h-5 w-auto text-gray-400 cursor-default" />
<li>
<a
className={`${last?"text-blue-500":""}`}
onClick={()=>{
if (!last){
onDirClick?.(new ObjID(path.bucket,"/"+arr.slice(0,i-1).join("/")))
onDirClick?.(new ObjID(path.bucket,arr.slice(0,i+1).join("/")))
}
}}>{e}</a>
</li>

View File

@@ -1,12 +1,18 @@
import React from "react"
import { useState } from "react"
import { Link } from "react-router-dom"
import { useHistory } from "react-router-dom"
import { useListBucktesQuery } from "../generated/graphql"
import Breadcrum from "./Breadcrum"
import MoreMenu from "./MoreMenu"
import { ReactComponent as Spinner } from "./../assets/spinner.svg"
import Table, { Data } from "./Table"
const BucketSelect: React.FC = () => {
const [buckets] = useState(["dev"])
const history = useHistory()
const {data: buckets, loading} = useListBucktesQuery()
const tableData: Data = {
headers:[{name:"Bucket"}],
body:(!buckets?[]:buckets.buckets.map(e=>{return {cells:[{name:e||""}],onClick:()=>{history.push(`/f/${e}`)}}}))
}
return (
<div>
@@ -16,13 +22,12 @@ const BucketSelect: React.FC = () => {
<MoreMenu />
</div>
</div>
<ul>
{buckets.map((e)=>
<li key={e}>
<Link to={`/f/${e}/`} >{e}</Link>
</li>
)}
</ul>
<Table data={tableData}/>
{loading &&
<div className="flex justify-center mt-4">
<Spinner className="animate-spin h-6 w-6 dark:text-white" />
</div>
}
</div>
)
}

View File

@@ -1,23 +0,0 @@
import React from "react"
import { Directory } from "../generated/graphql"
import { MdFolderOpen } from "react-icons/md"
interface Props {
dir: Directory
}
const DirectoryElement: React.FC<Props> = (props) => {
return (
<>
<td>
<MdFolderOpen className="inline"/> {props.dir.name}
</td>
<td>
</td>
<td>
</td>
</>
)
}
export default DirectoryElement

View File

@@ -5,10 +5,10 @@ import { RouteComponentProps } from "react-router-dom"
import downloadFile from "../functions/downloadFile"
import genDownloadLink from "../functions/genDownloadLink"
import uploadFile from "../functions/uploadFile"
import { useCopyMutation, useCreateDirMutation, useDeleteDirMutation, useDeleteFileMutation, useMoveMutation, useOpenDirQuery } from "../generated/graphql"
import { useCopyMutation, useCreateDirMutation, useDeleteDirMutation, useDeleteFileMutation, useMoveDirMutation, useMoveMutation, useOpenDirQuery } from "../generated/graphql"
import Breadcrum from "./Breadcrum"
import CreateDirButton from "./CreateDirButton"
import DragAndDrop from "./DragAndDrop"
import GlobalDragAndDrop from "./GlobalDragAndDrop"
import FileBrowserContextMenu, { Action, CONTEXT_MENU_DIR, CONTEXT_MENU_FILE } from "./FileBrowserContextMenu"
import FileOpen from "./FileOpen"
import FileUploadButton from "./FileUploadButton"
@@ -16,15 +16,16 @@ import { ReactComponent as Spinner } from "./../assets/spinner.svg"
import FileBrowserList from "./FileBrowserList"
import MoreMenu from "./MoreMenu"
import ObjID from "../types/ObjID"
import cache from "../Cache"
const FileBrowser: React.FC<RouteComponentProps> = (props) => {
const path = ObjID.fromURI(props.location.pathname)
const [openFileId, setOpenFileId] = useState<ObjID>()
const [showFile, setShowFile] = useState(false)
const [srcID,setSrcID] = useState<ObjID>(path)
const [srcID,setSrcID] = useState<ObjID | null>()
const [pasteAction,setPasteAction] = useState<Action>()
const [editID,setEditID] = useState<ObjID>(path)
const [editID,setEditID] = useState<ObjID>()
const [editEnable,setEditEnable] = useState(false)
const [deleteMutation] = useDeleteFileMutation()
@@ -32,6 +33,7 @@ const FileBrowser: React.FC<RouteComponentProps> = (props) => {
const [moveMutation] = useMoveMutation()
const [createDirMutation] = useCreateDirMutation()
const [deleteDirMutation] = useDeleteDirMutation()
const [moveDirMutation] = useMoveDirMutation()
const { show: showFileContext } = useContextMenu({
id: CONTEXT_MENU_FILE,
@@ -66,19 +68,33 @@ const FileBrowser: React.FC<RouteComponentProps> = (props) => {
break
case Action.FileCopy:
case Action.FileMove:
case Action.DirMove:
setSrcID(id)
setPasteAction(action)
break
case Action.FilePaste:
if (pasteAction === Action.FileCopy){
if (pasteAction === Action.FileCopy && srcID){
await copyMutation({variables:{src:srcID,dest:path}})
refetchDir()
}
if (pasteAction === Action.FileMove){
if (pasteAction === Action.FileMove && srcID){
await moveMutation({variables:{src:srcID,dest:path}})
refetchDir()
setSrcID(null)
}
if (pasteAction === Action.DirMove && srcID){
await moveDirMutation({variables:{src:srcID,dest:path}})
refetchDir()
cache.evict({
id: `Directory:${srcID.toString()}` // TODO: check for a better way to generate cache ids
})
setSrcID(null)
}
break
case Action.DirDelete:
await deleteDirMutation({variables:{path:id}})
@@ -102,84 +118,82 @@ const FileBrowser: React.FC<RouteComponentProps> = (props) => {
onSelect={onContextSelect}
pasteActive={!!srcID}
/>
<DragAndDrop
<GlobalDragAndDrop
handleDrop={async (files)=>{
await handleDrop(files)
}}
>
<div className="flex justify-between h-12">
<Breadcrum path={path} onDirClick={(newPath)=>{
props.history.push(newPath.toURI())
}}/>
<div className="ml-auto">
<CreateDirButton
onPressed={async (dirName)=>{
const dirID = new ObjID(path.bucket,path.key + dirName + "/")
await createDirMutation({variables:{path: dirID}})
refetchDir()
}}
/>
</div>
<div>
<FileUploadButton
onUpload={(files)=>handleDrop(files)}
/>
</div>
<div>
<MoreMenu />
</div>
</div>
<div>
<FileBrowserList
directorys={data?.directories || []}
files={data?.files || []}
onDirClick={(e,path)=>{
props.history.push(path.toURI())
}}
onDirContext={(e,path)=>{
e.preventDefault()
showDirContext(e,{props:{id:path}})
}}
onFileClick={(e,id)=>{
setOpenFileId(id)
setShowFile(true)
}}
onFileContext={(e,id)=>{
e.preventDefault()
showFileContext(e,{props:{id}})
}}
editId={editID}
editEnable={editEnable}
onRenameDone={async (id,changed,newName)=>{
setEditEnable(false)
if (changed){
// TODO: maybe change the name on client so it seems more smooth rather then haveing it refetch
// TODO: input check & error handling
await moveMutation({variables:{
src:id,
dest: id.rename(newName)
}})
refetchDir()
}
}}/>
<div className="flex justify-between h-12">
<Breadcrum path={path} onDirClick={(newPath)=>{
props.history.push(newPath.toURI())
}}/>
<div className="ml-auto">
<CreateDirButton
onPressed={async (dirName)=>{
const dirID = new ObjID(path.bucket,path.key + dirName + "/")
await createDirMutation({variables:{path: dirID}})
refetchDir()
}}
/>
{loading &&
<div className="flex justify-center mt-4">
<Spinner className="animate-spin h-6 w-6 dark:text-white" />
</div>
}
</div>
<FileOpen id={openFileId} show={showFile} onCloseClick={()=>{
setShowFile(false)
}} />
</DragAndDrop>
<div>
<FileUploadButton
onUpload={(files)=>handleDrop(files)}
/>
</div>
<div>
<MoreMenu />
</div>
</div>
<div>
<FileBrowserList
directorys={data?.directories || []}
files={data?.files || []}
onDirClick={(e,path)=>{
props.history.push(path.toURI())
}}
onDirContext={(e,path)=>{
e.preventDefault()
showDirContext(e,{props:{id:path}})
}}
onFileClick={(e,id)=>{
setOpenFileId(id)
setShowFile(true)
}}
onFileContext={(e,id)=>{
e.preventDefault()
showFileContext(e,{props:{id}})
}}
editId={editID}
editEnable={editEnable}
onRenameDone={async (id,changed,newName)=>{
setEditEnable(false)
if (changed){
// TODO: maybe change the name on client so it seems more smooth rather then haveing it refetch
// TODO: input check & error handling
await moveMutation({variables:{
src:id,
dest: id.rename(newName)
}})
refetchDir()
}
}}
/>
{loading &&
<div className="flex justify-center mt-4">
<Spinner className="animate-spin h-6 w-6 dark:text-white" />
</div>
}
</div>
<FileOpen id={openFileId} show={showFile} onCloseClick={()=>{
setShowFile(false)
}} />
</div>
)
}

View File

@@ -12,7 +12,8 @@ export enum Action {
FileMove,
FileDownload,
FileRename,
DirDelete
DirDelete,
DirMove,
}
interface Props {
@@ -40,7 +41,7 @@ const FileBrowserContextMenu: React.FC<Props> = (props) => {
</Menu>
<Menu id={CONTEXT_MENU_DIR} animation={false} className="dark:bg-gray-400">
<Item onClick={onClick} data={Action.DirDelete} >Delete</Item>
<Item onClick={onClick} >Item 2</Item>
<Item onClick={onClick} data={Action.DirMove} >Move</Item>
<Separator />
<Item onClick={onClick} data={Action.FilePaste} disabled={!props.pasteActive}>Paste</Item>
</Menu>

View File

@@ -1,43 +0,0 @@
import React from "react"
import { Directory, File } from "../generated/graphql"
import DirectoryComponent from "./DirectoryElement"
import FileElement from "./FileElement"
interface Props {
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={(e)=>{
if(props.file){
props.onClick?.(e,props.file)
}else if(props.dir){
props.onClick?.(e,props.dir)
}
}}
onContextMenu={(e)=>props.onContextMenu?.(e)}
>
{(props.file) ? <FileElement
edit={props.edit}
file={props.file}
onCancleRename={props.onCancleRename}
onRename={props.onRename}
/>:(props.dir)?<DirectoryComponent
dir={props.dir}
/>:<></>}
</tr>
)
}
export default FileBrowserElement

View File

@@ -1,7 +1,12 @@
import React from "react"
import { FaRegFileAlt } from "react-icons/fa"
import { MdFolderOpen } from "react-icons/md"
import dateFormat from "../functions/dateFomat"
import sizeToReadable from "../functions/sizeToReadable"
import { Directory, File } from "../generated/graphql"
import ObjID from "../types/ObjID"
import FileBrowserElement from "./FileBrowserElement"
import Renameable from "./Renameable"
import Table, { Data, Row } from "./Table"
interface Props{
directorys: Directory[]
@@ -17,65 +22,59 @@ interface Props{
onRenameDone?: (id: ObjID, 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.toString()}
dir={v}
onClick={(e,dir)=>{
props.onDirClick?.(e,dir.id)
}}
onContextMenu={(e)=>{
props.onDirContext?.(e,v.id)
}}
const FileBrowserList: React.FC<Props> = ({
files,directorys,onDirClick,onDirContext,onFileClick,onFileContext,onRenameDone,editId,editEnable
}) => {
edit={props.editEnable && (v.id === props.editId)}
const tableData: Data = {
headers: [
{name: "Name"},
{name: "Last Modified"},
{name: "Size"}
],
body:[
...directorys.map((e):Row<Directory>=>{return {
cells:[
{
name:e.name || "",
component: <div>
<MdFolderOpen className="inline" />
<Renameable
text={e.name || ""}
edit={editEnable && (e.id === editId)}
onRename={(newName)=>{e.name != newName?onRenameDone?.(e.id,true,newName):onRenameDone?.(e.id,false,newName)}}
onCancleRename={()=>onRenameDone?.(e.id,false,"")}
/>
</div>
}],
data: e,
onClick: ((e,data)=>onDirClick?.(e,data.id)),
onContextMenu: ((e,data)=>onDirContext?.(e,data.id))
}}),
...files.map((e):Row<File>=>{return {
cells:[
{
name:e.name || "",
component: <div>
<FaRegFileAlt className="inline" />
<Renameable
text={e.name || ""}
edit={editEnable && (e.id === editId)}
onRename={(newName)=>{e.name != newName?onRenameDone?.(e.id,true,newName):onRenameDone?.(e.id,false,newName)}}
onCancleRename={()=>onRenameDone?.(e.id,false,"")}
/>
</div>
},
{name:dateFormat(e.lastModified)},{name:sizeToReadable(e.size)}],
data: e,
onClick: ((e,data)=>onFileClick?.(e,data.id)),
onContextMenu: ((e,data)=>onFileContext?.(e,data.id))
}}),
],
}
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.toString()}
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>
</>
return <Table data={tableData} />
}
export default FileBrowserList

View File

@@ -1,38 +0,0 @@
import React from "react"
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,
edit: boolean
onRename?: (newName: string)=>void
onCancleRename?: ()=>void
}
const FileElement: React.FC<Props> = (props) => {
return (
<>
<td>
<FaRegFileAlt className="inline" />
<Renameable
text={props.file.name || ""}
edit={props.edit}
onCancleRename={props.onCancleRename}
onRename={props.onRename}
/>
</td>
<td>
{dateFormat(props.file.lastModified)}
</td>
<td>
{sizeToReadable(props.file.size)}
</td>
</>
)
}
export default FileElement

View File

@@ -10,12 +10,10 @@ interface Props {
handleDrop?: (files: FileList)=>void
}
const DragAndDrop: React.FC<Props> = (props) => {
const dropRef = React.createRef<HTMLDivElement>()
const GlobalDragAndDrop: React.FC<Props> = (props) => {
const [hover,setHover] = useState(false)
function handleDragEnter(event: DragEvent) {
// console.debug("dragenter",event)
event.preventDefault()
event.stopPropagation()
setHover(true)
@@ -23,7 +21,6 @@ const DragAndDrop: React.FC<Props> = (props) => {
}
function handleDragLeave(event: DragEvent) {
// console.debug("dragleave",event)
event.preventDefault()
event.stopPropagation()
setHover(false)
@@ -38,7 +35,6 @@ const DragAndDrop: React.FC<Props> = (props) => {
}
function handleDrop(event: DragEvent) {
console.debug("drop",event)
event.preventDefault()
event.stopPropagation()
setHover(false)
@@ -49,31 +45,25 @@ const DragAndDrop: React.FC<Props> = (props) => {
}
useEffect(()=>{
if(dropRef.current){
dropRef.current.addEventListener("dragenter",handleDragEnter)
dropRef.current.addEventListener("dragleave",handleDragLeave)
dropRef.current.addEventListener("dragover",handleDragOver)
dropRef.current.addEventListener("drop",handleDrop)
document.addEventListener("dragenter",handleDragEnter)
document.addEventListener("dragleave",handleDragLeave)
document.addEventListener("dragover",handleDragOver)
document.addEventListener("drop",handleDrop)
return ()=>{
if (dropRef.current){
dropRef.current.removeEventListener("dragenter",handleDragEnter)
dropRef.current.removeEventListener("dragleave",handleDragLeave)
dropRef.current.removeEventListener("dragover",handleDragOver)
dropRef.current.removeEventListener("drop",handleDrop)
}
}
return ()=>{
document.removeEventListener("dragenter",handleDragEnter)
document.removeEventListener("dragleave",handleDragLeave)
document.removeEventListener("dragover",handleDragOver)
document.removeEventListener("drop",handleDrop)
}
},[])
return (
<div ref={dropRef}
className={`fixed top-0 left-0 w-full h-full z-10
${hover? "border-dashed border-gray-600 border-4":""}`}
>
{props.children}
</div>
<div className={`fixed top-0 left-0 w-full h-full z-10 pointer-events-none
${hover? "border-dashed border-gray-600 border-4":""}`} />
)
}
export default DragAndDrop
export default GlobalDragAndDrop

61
src/components/Table.tsx Normal file
View File

@@ -0,0 +1,61 @@
import React from "react"
export interface Header {
name: string
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export interface Row<T = any> {
cells: Cell[]
data?: T,
// TODO: maybe there is some TS magic to apply here so that rowData has the same type as data without using generics
onClick?: (e: React.MouseEvent, rowData: T ) => void
onContextMenu?: (e: React.MouseEvent, data: T)=>void
}
export interface Cell {
name: string
component?: React.ReactNode
}
export interface Data {
headers: Header[]
body: Row[]
}
interface Props {
data: Data
}
const Table: React.FC<Props> = ({data:{headers,body}}) => {
const numCol = headers.length
return (
<table className="w-full">
<thead className="border-b-2 dark:border-gray-900">
<tr>
{headers.map((e,i)=><th key={i} className="text-left">{e.name}</th>)}
</tr>
</thead>
<tbody className="divide-y dark:divide-gray-900">
{body.map((row,rowIndex)=>
<tr
className="hover:bg-gray-100 dark:hover:bg-gray-900 text-lg"
key={rowIndex}
onClick={e=>row.onClick?.(e,row.data)}
onContextMenu={e=>row.onContextMenu?.(e,row.data)}
>
{(row.cells.length >= numCol?
row.cells:
row.cells.concat(Array(numCol - row.cells.length).fill({name:""}))) // Pad array to fit numCol
.map((cell,cellIndex)=>
<td key={cellIndex}>
{cell.component || cell.name}
</td>
)}
</tr>)}
</tbody>
</table>
)
}
export default Table

View File

@@ -62,7 +62,8 @@ export type RootMutation = {
delete?: Maybe<Scalars["String"]>;
deleteDir: Scalars["String"];
login: LoginResut;
move?: Maybe<File>;
move: File;
moveDir: Array<Maybe<File>>;
};
@@ -98,10 +99,18 @@ export type RootMutationMoveArgs = {
dest: Scalars["objID"];
};
export type RootMutationMoveDirArgs = {
src: Scalars["objID"];
dest: Scalars["objID"];
};
export type RootQuery = {
__typename?: "RootQuery";
/** True if the user is authorized */
authorized: Scalars["Boolean"];
/** List available buckets */
buckets: Array<Maybe<Scalars["String"]>>;
directories: Array<Directory>;
file?: Maybe<File>;
files: Array<File>;
@@ -191,6 +200,14 @@ export type IsAuthQuery = (
& Pick<RootQuery, "authorized">
);
export type ListBucktesQueryVariables = Exact<{ [key: string]: never; }>;
export type ListBucktesQuery = (
{ __typename?: "RootQuery" }
& Pick<RootQuery, "buckets">
);
export type LoginMutationVariables = Exact<{
username: Scalars["String"];
password: Scalars["String"];
@@ -213,10 +230,24 @@ export type MoveMutationVariables = Exact<{
export type MoveMutation = (
{ __typename?: "RootMutation" }
& { move?: Maybe<(
& { move: (
{ __typename?: "File" }
& Pick<File, "id">
)> }
) }
);
export type MoveDirMutationVariables = Exact<{
src: Scalars["objID"];
dest: Scalars["objID"];
}>;
export type MoveDirMutation = (
{ __typename?: "RootMutation" }
& { moveDir: Array<Maybe<(
{ __typename?: "File" }
& Pick<File, "id">
)>> }
);
export type OpenDirQueryVariables = Exact<{
@@ -436,6 +467,38 @@ export function useIsAuthLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<IsA
export type IsAuthQueryHookResult = ReturnType<typeof useIsAuthQuery>;
export type IsAuthLazyQueryHookResult = ReturnType<typeof useIsAuthLazyQuery>;
export type IsAuthQueryResult = Apollo.QueryResult<IsAuthQuery, IsAuthQueryVariables>;
export const ListBucktesDocument = gql`
query listBucktes {
buckets
}
`
/**
* __useListBucktesQuery__
*
* To run a query within a React component, call `useListBucktesQuery` and pass it any options that fit your needs.
* When your component renders, `useListBucktesQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useListBucktesQuery({
* variables: {
* },
* });
*/
export function useListBucktesQuery(baseOptions?: Apollo.QueryHookOptions<ListBucktesQuery, ListBucktesQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ListBucktesQuery, ListBucktesQueryVariables>(ListBucktesDocument, options)
}
export function useListBucktesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ListBucktesQuery, ListBucktesQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ListBucktesQuery, ListBucktesQueryVariables>(ListBucktesDocument, options)
}
export type ListBucktesQueryHookResult = ReturnType<typeof useListBucktesQuery>;
export type ListBucktesLazyQueryHookResult = ReturnType<typeof useListBucktesLazyQuery>;
export type ListBucktesQueryResult = Apollo.QueryResult<ListBucktesQuery, ListBucktesQueryVariables>;
export const LoginDocument = gql`
mutation Login($username: String!, $password: String!) {
login(username: $username, password: $password) {
@@ -505,6 +568,40 @@ export function useMoveMutation(baseOptions?: Apollo.MutationHookOptions<MoveMut
export type MoveMutationHookResult = ReturnType<typeof useMoveMutation>;
export type MoveMutationResult = Apollo.MutationResult<MoveMutation>;
export type MoveMutationOptions = Apollo.BaseMutationOptions<MoveMutation, MoveMutationVariables>;
export const MoveDirDocument = gql`
mutation moveDir($src: objID!, $dest: objID!) {
moveDir(src: $src, dest: $dest) {
id
}
}
`
export type MoveDirMutationFn = Apollo.MutationFunction<MoveDirMutation, MoveDirMutationVariables>;
/**
* __useMoveDirMutation__
*
* To run a mutation, you first call `useMoveDirMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useMoveDirMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [moveDirMutation, { data, loading, error }] = useMoveDirMutation({
* variables: {
* src: // value for 'src'
* dest: // value for 'dest'
* },
* });
*/
export function useMoveDirMutation(baseOptions?: Apollo.MutationHookOptions<MoveDirMutation, MoveDirMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<MoveDirMutation, MoveDirMutationVariables>(MoveDirDocument, options)
}
export type MoveDirMutationHookResult = ReturnType<typeof useMoveDirMutation>;
export type MoveDirMutationResult = Apollo.MutationResult<MoveDirMutation>;
export type MoveDirMutationOptions = Apollo.BaseMutationOptions<MoveDirMutation, MoveDirMutationVariables>;
export const OpenDirDocument = gql`
query openDir($path: objID!) {
files(path: $path) {

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
query listBucktes{
buckets
}

View File

@@ -0,0 +1,5 @@
mutation moveDir($src: objID!, $dest: objID!) {
moveDir(src: $src,dest: $dest){
id
}
}

View File

@@ -2,62 +2,13 @@ import React from "react"
import ReactDOM from "react-dom"
import "./index.scss"
import App from "./App"
import { ApolloClient, ApolloProvider, InMemoryCache } from "@apollo/client"
import { ApolloClient, ApolloProvider } from "@apollo/client"
import "react-contexify/dist/ReactContexify.css"
import ObjID from "./types/ObjID"
import cache from "./Cache"
const client = new ApolloClient({
uri: "/api/graphql",
cache: new InMemoryCache({
typePolicies:{
File:{
fields:{
id:{
merge(_,incomming){
// HACK: i use the merge function to change the id from a string to ObjID object.
// afaik apollo does not yet support custom scalar types.
if (!incomming){
return incomming
}else if (incomming instanceof ObjID){
return incomming
}else{
return ObjID.fromString(incomming as string)
}
}
}
}
},
Directory:{
fields:{
id:{
merge(_,incomming){
if (!incomming){
return incomming
}else if (incomming instanceof ObjID){
return incomming
}else{
return ObjID.fromString(incomming as string)
}
}
}
}
},
Query: {
fields: {
files: {
merge(existing, incoming){
return incoming
}
},
directorys:{
merge(existing, incoming){
return incoming
}
}
}
}
}
})
cache: cache,
})
// Disable drag and drop behaviour on document

View File

@@ -53,6 +53,30 @@ class ObjID {
return new ObjID(this.bucket,parts.join("/"))
}
/**
* Get the parent of the object. If Obj is a file then the containing directory
* if Obj is a directory it returns the parent
* @returns parent ObjID or null if already at root
*/
public parent(): ObjID | null {
if (this.key == "/") {
// Already at root. We dont have a parent
return null
}
if (this.isDirectory()) {
const parts = this.key.split("/")
const parent = new ObjID(this.bucket, parts.slice(0,-2).join("/") + "/")
parent.normalize()
return parent
} else {
const parts = this.key.split("/")
const parent = new ObjID(this.bucket, parts.slice(0,-1).join("/") + "/")
parent.normalize()
return parent
}
}
public static fromString(from: string): ObjID{
const match = stringRegex.exec(from)