the big refactor 2: ObjID

This commit is contained in:
Djeeberjr 2021-09-28 16:07:00 +02:00
parent dc4133a705
commit 3e8fbb01bb
22 changed files with 1096 additions and 952 deletions

View File

@ -10,7 +10,10 @@ generates:
- "typescript"
- "typescript-operations"
- "typescript-react-apollo"
- add:
content: "import ObjID from './../types/ObjID'"
config:
withHooks: true
scalars:
DateTime: string
objID: ObjID

View File

@ -41,6 +41,7 @@
},
"devDependencies": {
"@craco/craco": "^6.2.0",
"@graphql-codegen/add": "^3.1.0",
"@graphql-codegen/cli": "1.21.7",
"@graphql-codegen/introspection": "1.18.2",
"@graphql-codegen/typescript": "^1.23.0",

View File

@ -1,25 +1,36 @@
import React from "react"
import ObjID from "../types/ObjID"
import { ReactComponent as BreadcrumImage } from "./../assets/breadcrum.svg"
interface Props{
path: string
onDirClick?: (path: string) => void
path: ObjID
onDirClick?: (path: ObjID) => void
}
const Breadcrum: React.FC<Props> = (props) => {
const parts = props.path.split("/").filter(e=>e.length > 0)
const keyParts = props.path.key.split("/").filter(e=>e.length > 0)
return (
<ul className="flex text-gray-500 dark:text-gray-400 text-lg ">
<li className="inline-flex items-center cursor-pointer">
<a onClick={()=>{
props.onDirClick?.("/")
}}>
Root
<div className="inline-flex items-center cursor-pointer">
<li>
<a>
{props.path.bucket}
</a>
</li>
</div>
<div className="inline-flex items-center cursor-pointer">
<BreadcrumImage className="h-5 w-auto text-gray-400" />
<li>
<a onClick={()=>{
props.onDirClick?.(new ObjID(props.path.bucket,"/"))
}}>
root
</a>
</li>
</div>
{parts.map((e,i,arr)=>{
{keyParts.map((e,i,arr)=>{
const last = i == arr.length - 1
return <div key={e} className="inline-flex items-center cursor-pointer">
<BreadcrumImage className="h-5 w-auto text-gray-400" />
@ -28,7 +39,7 @@ const Breadcrum: React.FC<Props> = (props) => {
className={`${last?"text-blue-500":""}`}
onClick={()=>{
if (!last){
props.onDirClick?.("/"+arr.slice(0,i-1).join("/"))
props.onDirClick?.(new ObjID(props.path.bucket,"/"+arr.slice(0,i-1).join("/")))
}
}}>{e}</a>
</li>

View File

@ -4,7 +4,6 @@ import { useContextMenu } from "react-contexify"
import { RouteComponentProps } from "react-router-dom"
import downloadFile from "../functions/downloadFile"
import genDownloadLink from "../functions/genDownloadLink"
import normalizeDirPath from "../functions/normalizeDirPath"
import uploadFile from "../functions/uploadFile"
import { useCopyMutation, useCreateDirMutation, useDeleteDirMutation, useDeleteFileMutation, useMoveMutation, useOpenDirQuery } from "../generated/graphql"
import Breadcrum from "./Breadcrum"
@ -15,31 +14,17 @@ import FileOpen from "./FileOpen"
import FileUploadButton from "./FileUploadButton"
import { ReactComponent as Spinner } from "./../assets/spinner.svg"
import FileBrowserList from "./FileBrowserList"
import pathRename from "../functions/pathRename"
import MoreMenu from "./MoreMenu"
function uriToPath(pathname:string) {
// strip the "/f" from e.g. "/f/dir1/dir2"
const path = pathname.substr(2)
if (!path.endsWith("/")){
return path + "/"
}
return path
}
function pathToUri(path:string) {
return (path.startsWith("/")?"/f":"/f/") + path
}
import ObjID from "../types/ObjID"
const FileBrowser: React.FC<RouteComponentProps> = (props) => {
const path = uriToPath(props.location.pathname)
const [openFileId, setOpenFileId] = useState("")
const path = ObjID.fromURI(props.location.pathname)
const [openFileId, setOpenFileId] = useState<ObjID>()
const [showFile, setShowFile] = useState(false)
const [srcID,setSrcID] = useState("")
const [srcID,setSrcID] = useState<ObjID>(path)
const [pasteAction,setPasteAction] = useState<Action>()
const [editID,setEditID] = useState("")
const [editID,setEditID] = useState<ObjID>(path)
const [editEnable,setEditEnable] = useState(false)
const [deleteMutation] = useDeleteFileMutation()
@ -58,7 +43,7 @@ const FileBrowser: React.FC<RouteComponentProps> = (props) => {
const { data, loading, refetch: refetchDir } = useOpenDirQuery({
variables:{
path
path: path
}
})
@ -66,14 +51,14 @@ const FileBrowser: React.FC<RouteComponentProps> = (props) => {
const wait: Promise<boolean>[] = []
for (let i = 0; i < files.length; i++) {
const file = files[i]
wait.push(uploadFile(file, path + file.name))
wait.push(uploadFile(file, new ObjID(path.bucket,path.key + file.name)))
}
await Promise.all(wait)
refetchDir()
}
async function onContextSelect(action:Action, id: string) {
async function onContextSelect(action:Action, id: ObjID) {
switch (action) {
case Action.FileDelete:
await deleteMutation({variables:{id}})
@ -124,13 +109,13 @@ const FileBrowser: React.FC<RouteComponentProps> = (props) => {
>
<div className="flex justify-between">
<Breadcrum path={path} onDirClick={(newPath)=>{
props.history.push(pathToUri(newPath))
props.history.push(newPath.toURI())
}}/>
<div className="ml-auto">
<CreateDirButton
onPressed={async (dirName)=>{
const fullPath = normalizeDirPath(path + dirName)
await createDirMutation({variables:{path: fullPath}})
const dirID = new ObjID(path.bucket,path.key + dirName)
await createDirMutation({variables:{path: dirID}})
refetchDir()
}}
/>
@ -147,11 +132,11 @@ const FileBrowser: React.FC<RouteComponentProps> = (props) => {
</div>
<div>
<FileBrowserList
directorys={data?.directorys || []}
directorys={data?.directories || []}
files={data?.files || []}
onDirClick={(e,path)=>{
props.history.push(pathToUri(path))
props.history.push(path.toURI())
}}
onDirContext={(e,path)=>{
@ -179,7 +164,7 @@ const FileBrowser: React.FC<RouteComponentProps> = (props) => {
// TODO: input check & error handling
await moveMutation({variables:{
src:id,
dest: pathRename(id,newName)
dest: id.rename(newName)
}})
refetchDir()
}

View File

@ -1,6 +1,6 @@
import React from "react"
import { Item, ItemParams, Menu, Separator } from "react-contexify"
import ObjID from "../types/ObjID"
export const CONTEXT_MENU_FILE = "CONTEXT_MENU_FILE"
export const CONTEXT_MENU_DIR = "CONTEXT_MENU_DIR"
@ -16,12 +16,12 @@ export enum Action {
}
interface Props {
onSelect?: (action: Action, id: string)=>void
onSelect?: (action: Action, id: ObjID)=>void
pasteActive?: boolean
}
const FileBrowserContextMenu: React.FC<Props> = (props) => {
function onClick({ props: itemProps, data }: ItemParams<{id:string}, Action>) {
function onClick({ props: itemProps, data }: ItemParams<{id:ObjID}, Action>) {
if (itemProps?.id && data != null){
props.onSelect?.(data,itemProps.id)
}

View File

@ -1,19 +1,20 @@
import React from "react"
import { Directory, File } from "../generated/graphql"
import ObjID from "../types/ObjID"
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
onFileContext?: (event: React.MouseEvent, id: ObjID)=>void
onDirContext?: (event: React.MouseEvent, path: ObjID)=>void
onFileClick?: (event: React.MouseEvent,id: ObjID)=>void
onDirClick?: (event: React.MouseEvent,path: ObjID)=>void
editId: string
editId?: ObjID
editEnable: boolean
onRenameDone?: (id: string, changed: boolean, newName: string)=>void
onRenameDone?: (id: ObjID, changed: boolean, newName: string)=>void
}
const FileBrowserList: React.FC<Props> = (props) => {
@ -28,7 +29,7 @@ const FileBrowserList: React.FC<Props> = (props) => {
</thead>
<tbody className="divide-y dark:divide-gray-900">
{ props.directorys.map(v => (<FileBrowserElement
key={v.id}
key={v.id.toString()}
dir={v}
onClick={(e,dir)=>{
props.onDirClick?.(e,dir.id)
@ -51,7 +52,7 @@ const FileBrowserList: React.FC<Props> = (props) => {
/>))}
{ props.files.map(v => (<FileBrowserElement
key={v.id}
key={v.id.toString()}
file={v}
onClick={(e,file)=>{
props.onFileClick?.(e,file.id)

View File

@ -4,9 +4,10 @@ import ImageOpener from "./ImageOpener"
import TextOpener from "./TextOpener"
import Modal from "./Modal"
import AudioOpener from "./AudioOpener"
import ObjID from "../types/ObjID"
interface Props {
id: string
id?: ObjID
show: boolean
onCloseClick?: ()=>void
}

View File

@ -1,5 +1,7 @@
function genDownloadLink(id:string): string {
return `/api/file?id=${encodeURIComponent(id)}`
import ObjID from "../types/ObjID"
function genDownloadLink(id:ObjID): string {
return `/api/file?id=${encodeURIComponent(id.toString())}`
}
export default genDownloadLink

View File

@ -1,11 +0,0 @@
function pathRename(id:string, newFilename: string): string {
const isDir = id.endsWith("/")
const parts = id.split("/")
if (!parts.length)
throw new Error("Maleformed id")
parts[parts.length - (isDir?2:1)] = newFilename
return parts.join("/")
}
export default pathRename

View File

@ -1,5 +1,7 @@
async function uploadFile(file:File,id: string): Promise<boolean> {
const res = await fetch(`/api/file?${new URLSearchParams({id:id}).toString()}`,{
import ObjID from "../types/ObjID"
async function uploadFile(file:File,id: ObjID): Promise<boolean> {
const res = await fetch(`/api/file?${new URLSearchParams({id:id.toString()}).toString()}`,{
method: "POST",
headers: {
"Content-Type": file.type

View File

@ -1,3 +1,4 @@
import ObjID from "./../types/ObjID"
import { gql } from "@apollo/client"
import * as Apollo from "@apollo/client"
export type Maybe<T> = T | null;
@ -14,6 +15,11 @@ export type Scalars = {
Float: number;
/** DateTime is a DateTime in ISO 8601 format */
DateTime: string;
/**
* String representing a bucket, key and version combination.
* Looks like this: "bucketName:/name/of/key" or "bucketName@version:/name/of/key"
*/
objID: ObjID;
};
@ -22,7 +28,7 @@ export type Directory = {
__typename?: "Directory";
directorys?: Maybe<Array<Maybe<Directory>>>;
files?: Maybe<Array<Maybe<File>>>;
id: Scalars["ID"];
id: Scalars["objID"];
name?: Maybe<Scalars["String"]>;
parent?: Maybe<Directory>;
};
@ -33,7 +39,7 @@ export type File = {
contentType?: Maybe<Scalars["String"]>;
etag?: Maybe<Scalars["String"]>;
/** The uniqe ID of the file. Represents the path and the s3 key. */
id: Scalars["ID"];
id: Scalars["objID"];
lastModified?: Maybe<Scalars["DateTime"]>;
name?: Maybe<Scalars["String"]>;
parent?: Maybe<Directory>;
@ -61,64 +67,65 @@ export type RootMutation = {
export type RootMutationCopyArgs = {
src: Scalars["ID"];
dest: Scalars["ID"];
src: Scalars["objID"];
dest: Scalars["objID"];
};
export type RootMutationCreateDirArgs = {
path: Scalars["ID"];
path: Scalars["objID"];
};
export type RootMutationDeleteArgs = {
id: Scalars["ID"];
id: Scalars["objID"];
};
export type RootMutationDeleteDirArgs = {
path: Scalars["ID"];
path: Scalars["objID"];
};
export type RootMutationLoginArgs = {
password: Scalars["String"];
username: Scalars["String"];
password: Scalars["String"];
};
export type RootMutationMoveArgs = {
src: Scalars["ID"];
dest: Scalars["ID"];
src: Scalars["objID"];
dest: Scalars["objID"];
};
export type RootQuery = {
__typename?: "RootQuery";
/** True if the user is authorized */
authorized: Scalars["Boolean"];
directorys: Array<Directory>;
directories: Array<Directory>;
file?: Maybe<File>;
files: Array<File>;
};
export type RootQueryDirectorysArgs = {
path: Scalars["String"];
export type RootQueryDirectoriesArgs = {
path: Scalars["objID"];
};
export type RootQueryFileArgs = {
id: Scalars["ID"];
id: Scalars["objID"];
};
export type RootQueryFilesArgs = {
path: Scalars["String"];
path: Scalars["objID"];
};
export type CopyMutationVariables = Exact<{
src: Scalars["ID"];
dest: Scalars["ID"];
src: Scalars["objID"];
dest: Scalars["objID"];
}>;
@ -131,7 +138,7 @@ export type CopyMutation = (
);
export type CreateDirMutationVariables = Exact<{
path: Scalars["ID"];
path: Scalars["objID"];
}>;
@ -144,7 +151,7 @@ export type CreateDirMutation = (
);
export type DeleteDirMutationVariables = Exact<{
path: Scalars["ID"];
path: Scalars["objID"];
}>;
@ -154,7 +161,7 @@ export type DeleteDirMutation = (
);
export type DeleteFileMutationVariables = Exact<{
id: Scalars["ID"];
id: Scalars["objID"];
}>;
@ -164,7 +171,7 @@ export type DeleteFileMutation = (
);
export type GetFileQueryVariables = Exact<{
id: Scalars["ID"];
id: Scalars["objID"];
}>;
@ -199,8 +206,8 @@ export type LoginMutation = (
);
export type MoveMutationVariables = Exact<{
src: Scalars["ID"];
dest: Scalars["ID"];
src: Scalars["objID"];
dest: Scalars["objID"];
}>;
@ -213,7 +220,7 @@ export type MoveMutation = (
);
export type OpenDirQueryVariables = Exact<{
path: Scalars["String"];
path: Scalars["objID"];
}>;
@ -222,7 +229,7 @@ export type OpenDirQuery = (
& { files: Array<(
{ __typename?: "File" }
& Pick<File, "id" | "name" | "size" | "lastModified">
)>, directorys: Array<(
)>, directories: Array<(
{ __typename?: "Directory" }
& Pick<Directory, "id" | "name">
)> }
@ -230,7 +237,7 @@ export type OpenDirQuery = (
export const CopyDocument = gql`
mutation copy($src: ID!, $dest: ID!) {
mutation copy($src: objID!, $dest: objID!) {
copy(src: $src, dest: $dest) {
id
}
@ -264,7 +271,7 @@ export type CopyMutationHookResult = ReturnType<typeof useCopyMutation>;
export type CopyMutationResult = Apollo.MutationResult<CopyMutation>;
export type CopyMutationOptions = Apollo.BaseMutationOptions<CopyMutation, CopyMutationVariables>;
export const CreateDirDocument = gql`
mutation createDir($path: ID!) {
mutation createDir($path: objID!) {
createDir(path: $path) {
id
}
@ -297,7 +304,7 @@ export type CreateDirMutationHookResult = ReturnType<typeof useCreateDirMutation
export type CreateDirMutationResult = Apollo.MutationResult<CreateDirMutation>;
export type CreateDirMutationOptions = Apollo.BaseMutationOptions<CreateDirMutation, CreateDirMutationVariables>;
export const DeleteDirDocument = gql`
mutation deleteDir($path: ID!) {
mutation deleteDir($path: objID!) {
deleteDir(path: $path)
}
`
@ -328,7 +335,7 @@ export type DeleteDirMutationHookResult = ReturnType<typeof useDeleteDirMutation
export type DeleteDirMutationResult = Apollo.MutationResult<DeleteDirMutation>;
export type DeleteDirMutationOptions = Apollo.BaseMutationOptions<DeleteDirMutation, DeleteDirMutationVariables>;
export const DeleteFileDocument = gql`
mutation deleteFile($id: ID!) {
mutation deleteFile($id: objID!) {
delete(id: $id)
}
`
@ -359,7 +366,7 @@ export type DeleteFileMutationHookResult = ReturnType<typeof useDeleteFileMutati
export type DeleteFileMutationResult = Apollo.MutationResult<DeleteFileMutation>;
export type DeleteFileMutationOptions = Apollo.BaseMutationOptions<DeleteFileMutation, DeleteFileMutationVariables>;
export const GetFileDocument = gql`
query getFile($id: ID!) {
query getFile($id: objID!) {
file(id: $id) {
id
name
@ -465,7 +472,7 @@ export type LoginMutationHookResult = ReturnType<typeof useLoginMutation>;
export type LoginMutationResult = Apollo.MutationResult<LoginMutation>;
export type LoginMutationOptions = Apollo.BaseMutationOptions<LoginMutation, LoginMutationVariables>;
export const MoveDocument = gql`
mutation move($src: ID!, $dest: ID!) {
mutation move($src: objID!, $dest: objID!) {
move(src: $src, dest: $dest) {
id
}
@ -499,14 +506,14 @@ export type MoveMutationHookResult = ReturnType<typeof useMoveMutation>;
export type MoveMutationResult = Apollo.MutationResult<MoveMutation>;
export type MoveMutationOptions = Apollo.BaseMutationOptions<MoveMutation, MoveMutationVariables>;
export const OpenDirDocument = gql`
query openDir($path: String!) {
query openDir($path: objID!) {
files(path: $path) {
id
name
size
lastModified
}
directorys(path: $path) {
directories(path: $path) {
id
name
}

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
mutation copy($src: ID!, $dest: ID!) {
mutation copy($src: objID!, $dest: objID!) {
copy(src: $src,dest: $dest){
id
}

View File

@ -1,4 +1,4 @@
mutation createDir($path: ID!){
mutation createDir($path: objID!){
createDir(path:$path){
id
}

View File

@ -1,3 +1,3 @@
mutation deleteDir($path: ID!){
mutation deleteDir($path: objID!){
deleteDir(path:$path)
}

View File

@ -1,3 +1,3 @@
mutation deleteFile($id: ID!) {
mutation deleteFile($id: objID!) {
delete(id:$id)
}

View File

@ -1,4 +1,4 @@
query getFile($id: ID!){
query getFile($id: objID!){
file(id: $id){
id
name

View File

@ -1,4 +1,4 @@
mutation move($src: ID!, $dest: ID!) {
mutation move($src: objID!, $dest: objID!) {
move(src: $src,dest: $dest){
id
}

View File

@ -1,11 +1,11 @@
query openDir($path: String!) {
query openDir($path: objID!) {
files(path:$path){
id
name
size
lastModified
}
directorys(path: $path){
directories(path: $path){
id
name
}

View File

@ -4,11 +4,44 @@ import "./index.scss"
import App from "./App"
import { ApolloClient, ApolloProvider, InMemoryCache } from "@apollo/client"
import "react-contexify/dist/ReactContexify.css"
import ObjID from "./types/ObjID"
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: {

82
src/types/ObjID.ts Normal file
View File

@ -0,0 +1,82 @@
const stringRegex = /(.*?)(@(.*))?:(.*)/
class ObjID {
public bucket: string
public key: string
public version?: string
constructor(bucket: string, key: string, version?: string) {
this.bucket = bucket
if(!key){
this.key = "/"
}else{
this.key = key
}
if (version){
this.version = version
}
this.normalize()
}
public toString(): string {
if (this.version) {
return `${this.bucket}@${this.version}:${this.key}`
}else{
return `${this.bucket}:${this.key}`
}
}
public normalize(): void{
if (!this.key.startsWith("/")){
this.key = "/" + this.key
}
}
public isDirectory(): boolean {
return this.key.endsWith("/")
}
public toURI(): string {
return `/f/${this.bucket}${this.key}`
}
public toJSON(): string{
// HACK: toJSON is required so that apollo can parse the ObjID back to a string
// that can be used in gql query
return this.toString()
}
public rename(name: string): ObjID{
const parts = this.key.split("/")
parts[parts.length - (this.isDirectory()?2:1)] = name
return new ObjID(this.bucket,parts.join("/"))
}
public static fromString(from: string): ObjID{
const match = stringRegex.exec(from)
if (!match){
throw new Error("Failed to parse ObjID")
}
return new ObjID(match[1],match[4],match[3])
}
public static fromURI(uri: string): ObjID{
let uri2 = uri
if (!uri2.endsWith("/")){
uri2 += "/"
}
const parts = uri2.split("/")
const bucket = parts[2]
const key = parts.slice(3).join("/")
return new ObjID(bucket,key)
}
}
export default ObjID

View File

@ -1392,6 +1392,14 @@
minimatch "^3.0.4"
strip-json-comments "^3.1.1"
"@graphql-codegen/add@^3.1.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@graphql-codegen/add/-/add-3.1.0.tgz#cd02fd6d80a7f62839cb27160b62e48366a237c5"
integrity sha512-vRRHpuUFadYXnPrb5RNiIzm3Ao39UxjvrdX760lEfXh2qeG7YddM5QFC+ev2BH3X452R15gv/gf/rXy0+Hqm1A==
dependencies:
"@graphql-codegen/plugin-helpers" "^2.1.0"
tslib "~2.3.0"
"@graphql-codegen/cli@1.21.7":
version "1.21.7"
resolved "https://registry.npmjs.org/@graphql-codegen/cli/-/cli-1.21.7.tgz"
@ -1466,6 +1474,18 @@
lodash "~4.17.0"
tslib "~2.3.0"
"@graphql-codegen/plugin-helpers@^2.1.0":
version "2.1.1"
resolved "https://registry.yarnpkg.com/@graphql-codegen/plugin-helpers/-/plugin-helpers-2.1.1.tgz#fc13e735763574ef308045bbb95c3e7201ec0027"
integrity sha512-7jjN9fekMQkpd7cRTbaBxgqt/hkR3CXeOUSsEyHFDDHKtvCrnev3iyc75IeWXpO9tOwDE8mVPTzEZnu4QukrNA==
dependencies:
"@graphql-tools/utils" "^8.1.1"
change-case-all "1.0.14"
common-tags "1.8.0"
import-from "4.0.0"
lodash "~4.17.0"
tslib "~2.3.0"
"@graphql-codegen/typescript-operations@1.18.4":
version "1.18.4"
resolved "https://registry.npmjs.org/@graphql-codegen/typescript-operations/-/typescript-operations-1.18.4.tgz"
@ -1720,6 +1740,13 @@
camel-case "4.1.2"
tslib "~2.2.0"
"@graphql-tools/utils@^8.1.1":
version "8.2.3"
resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-8.2.3.tgz#9d7b9e7e116d11d26c2687f4d9cfb2b54568838b"
integrity sha512-RR+aiusf2gIfnPmrDIH1uA45QuPiHB54RD+BmWyMcl88tWAjeJtqZeWPqUTq/1EXrNeocJAJQqogHV4Fbbzx3A==
dependencies:
tslib "~2.3.0"
"@graphql-tools/wrap@^7.0.4":
version "7.0.8"
resolved "https://registry.npmjs.org/@graphql-tools/wrap/-/wrap-7.0.8.tgz"