Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Folder uploads #1515

Merged
merged 30 commits into from
Sep 16, 2021
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
026aa7d
add basic folder upload
FSM1 Sep 10, 2021
934d464
wire up folder uploads for modal
FSM1 Sep 13, 2021
6166b12
Rename Modal
FSM1 Sep 13, 2021
1f0d90c
fix types
FSM1 Sep 13, 2021
1fd093a
remove types
FSM1 Sep 13, 2021
ac3bcc2
Merge branch 'dev' into feat/folder-upload-1014
FSM1 Sep 13, 2021
22ee35c
lingui extract
actions-user Sep 13, 2021
b601c07
Merge remote-tracking branch 'origin/dev' into feat/folder-upload-1014
FSM1 Sep 13, 2021
d54d811
fix lint
FSM1 Sep 13, 2021
9d07cb6
add source attribution
FSM1 Sep 13, 2021
66a1d75
Merge branch 'dev' into feat/folder-upload-1014
Tbaut Sep 13, 2021
8db5f02
Merge branch 'dev' into feat/folder-upload-1014
Tbaut Sep 13, 2021
2536a19
Merge branch 'dev' into feat/folder-upload-1014
Tbaut Sep 14, 2021
08af0f6
Merge branch 'dev' into feat/folder-upload-1014
RyRy79261 Sep 14, 2021
b1ea6d3
Merge branch 'dev' into feat/folder-upload-1014
Tbaut Sep 14, 2021
a41c434
lingui extract
actions-user Sep 14, 2021
685548f
Merge branch 'dev' into feat/folder-upload-1014
Tbaut Sep 14, 2021
ca614ab
incorporate feedback
FSM1 Sep 15, 2021
2601762
Merge branch 'dev' into feat/folder-upload-1014
FSM1 Sep 15, 2021
9a7d55b
lingui extract
actions-user Sep 15, 2021
350bb64
fix lint
FSM1 Sep 15, 2021
bbd53be
Fix casing
FSM1 Sep 15, 2021
30195bb
Rename component for consistency
FSM1 Sep 15, 2021
cbd21d8
Merge branch 'dev' into feat/folder-upload-1014
Tbaut Sep 16, 2021
2eb309f
Merge branch 'dev' into feat/folder-upload-1014
FSM1 Sep 16, 2021
3fc3666
Merge branch 'dev' into feat/folder-upload-1014
FSM1 Sep 16, 2021
26fbb6a
lingui extract
actions-user Sep 16, 2021
88d0b53
clean up types
FSM1 Sep 16, 2021
c05983e
Merge branch 'feat/folder-upload-1014' of https://github.com/ChainSaf…
FSM1 Sep 16, 2021
1581b00
Merge branch 'dev' into feat/folder-upload-1014
FSM1 Sep 16, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 deletions packages/common-components/src/FileInput/FileInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ const useStyles = makeStyles(({ constants, palette, overrides }: ITheme) =>
})
)

interface FileWithPath extends File {
path?: string
}

interface IFileInputProps extends DropzoneOptions {
className?: string
variant?: "dropzone" | "filepicker"
Expand Down Expand Up @@ -137,21 +141,22 @@ const FileInput = ({
const classes = useStyles()
const [previews, setPreviews] = useState<any[]>([])
const [errors, setErrors] = useState<any[]>([])
const [{ value }, meta, helpers] = useField(name)
const [{ value }, meta, helpers] = useField<Array<FileWithPath>>(name)

useEffect(() => {
onFileNumberChange && onFileNumberChange(value.length)
}, [onFileNumberChange, value.length])

const onDrop = useCallback(
async (acceptedFiles: File[], fileRejections: FileRejection[]) => {
async (acceptedFiles: Array<FileWithPath>, fileRejections: FileRejection[]) => {
const filtered = acceptedFiles.filter((file) =>
FSM1 marked this conversation as resolved.
Show resolved Hide resolved
maxFileSize ? file.size <= maxFileSize : true
)

setErrors([])
if (showPreviews) {
setPreviews(
filtered.map((file: any) =>
filtered.map((file) =>
Object.assign(file, {
preview: URL.createObjectURL(file)
})
Expand Down Expand Up @@ -188,7 +193,7 @@ const FileInput = ({
})

const removeItem = (i: number) => {
const items = value as any[]
const items = value
items.splice(i, 1)
helpers.setValue(items)
}
Expand Down Expand Up @@ -220,12 +225,12 @@ const FileInput = ({
>
<ScrollbarWrapper className={clsx("scrollbar")}>
<ul>
{value.map((file: any, i: any) => (
{value.map((file: any, i: number) => (
<li
className={clsx(classes.item, classNames?.item)}
key={i}
>
<span className={classes.itemText}>{file.name}</span>
<span className={classes.itemText}>{file.path}</span>
<Button
testId="remove-from-file-list"
className={clsx(classes.crossIcon, classNames?.closeIcon)}
Expand All @@ -246,9 +251,9 @@ const FileInput = ({
)
) : (
<>
{value.value?.length === 0
{value?.length === 0
? "No files selected"
: `${value.value?.length} file(s) selected`}
: `${value?.length} file(s) selected`}
<Button
onClick={open}
size="small"
Expand Down
2 changes: 2 additions & 0 deletions packages/files-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@tkey/security-questions": "3.12.0",
"@tkey/web-storage": "3.12.0",
"@toruslabs/torus-direct-web-sdk": "4.10.0",
"@types/filesystem": "^0.0.32",
"@types/uuid": "^8.3.0",
"axios": "0.21.4",
"babel-loader": "8.1.0",
Expand All @@ -24,6 +25,7 @@
"babel-preset-react": "^6.24.1",
"bnc-onboard": "1.32.0",
"clsx": "^1.1.1",
"datatransfer-files-promise": "^1.3.1",
"dayjs": "^1.9.7",
"eth-crypto": "^1.8.0",
"ethers": "^5.4.3",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { useLocalStorage } from "@chainsafe/browser-storage-hooks"
import { DISMISSED_SURVEY_KEY } from "../../SurveyBanner"
import { FileBrowserContext } from "../../../Contexts/FileBrowserContext"
import { parseFileContentResponse } from "../../../Utils/Helpers"
import getFilesFromDataTransferItems from "../../../Utils/getFilesFromDataTransferItems"

const CSFFileBrowser: React.FC<IFileBrowserModuleProps> = () => {
const {
Expand Down Expand Up @@ -178,23 +179,12 @@ const CSFFileBrowser: React.FC<IFileBrowserModuleProps> = () => {

const handleUploadOnDrop = useCallback(async (files: File[], fileItems: DataTransferItemList, path: string) => {
if (!bucket) return
let hasFolder = false
for (let i = 0; i < files.length; i++) {
if (fileItems[i].webkitGetAsEntry()?.isDirectory) {
hasFolder = true
}
}
if (hasFolder) {
addToast({
title: t`Folder uploads are not supported currently`,
type: "error"
})
} else {
uploadFiles(bucket, files, path)
.then(() => refreshContents())
.catch(console.error)
}
}, [addToast, uploadFiles, bucket, refreshContents])
const flattenedFiles = await getFilesFromDataTransferItems(fileItems)
const paths = [...new Set(flattenedFiles.map(f => f.filepath))]
paths.forEach(p => {
uploadFiles(bucket, flattenedFiles.filter(f => f.filepath === p), getPathWithFile(path, p))
})
}, [uploadFiles, bucket])

const viewFolder = useCallback((cid: string) => {
const fileSystemItem = pathContents.find(f => f.cid === cid)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { Button, FileInput } from "@chainsafe/common-components"
import { useFiles } from "../../Contexts/FilesContext"
import { useFiles } from "../../../Contexts/FilesContext"
import { createStyles, makeStyles } from "@chainsafe/common-theme"
import React, { useCallback, useState } from "react"
import { Formik, Form } from "formik"
import { array, object } from "yup"
import CustomModal from "../Elements/CustomModal"
import CustomModal from "../../Elements/CustomModal"
import { Trans, t } from "@lingui/macro"
import clsx from "clsx"
import { CSFTheme } from "../../Themes/types"
import { useFileBrowser } from "../../Contexts/FileBrowserContext"
import { CSFTheme } from "../../../Themes/types"
import { useFileBrowser } from "../../../Contexts/FileBrowserContext"
import { getPathWithFile } from "../../../Utils/pathUtils"

const useStyles = makeStyles(({ constants, breakpoints }: CSFTheme) =>
createStyles({
Expand Down Expand Up @@ -89,15 +90,19 @@ const UploadFileModule = ({ modalOpen, close }: IUploadFileModuleProps) => {
setIsDoneDisabled(filesNumber === 0)
}, [])

const onSubmit = useCallback(async (values, helpers) => {
const onSubmit = useCallback(async (values: {files: Array<File & {path: string}>}, helpers) => {
if (!bucket) return
helpers.setSubmitting(true)
try {
close()
await uploadFiles(bucket, values.files, currentPath)
const paths = [...new Set(values.files.map(f => f.path.substring(0, f.path.lastIndexOf("/"))))]
paths.forEach(async p => {
const filesToUpload = values.files.filter((f => f.path.substring(0, f.path.lastIndexOf("/")) === p))
await uploadFiles(bucket, filesToUpload, getPathWithFile(currentPath, p))
})
refreshContents && refreshContents()
helpers.resetForm()
} catch (errors) {
} catch (errors: any) {
if (errors[0].message.includes("conflict with existing")) {
helpers.setFieldError("files", "File/Folder exists")
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import FileSystemItem from "./FileSystemItem/FileSystemItem"
import FilePreviewModal from "../../FilePreviewModal"

import CreateFolderModal from "../CreateFolderModal"
import UploadFileModule from "../../UploadFileModule"
import UploadFileModule from "../UploadFileModal"
import MoveFileModal from "../MoveFileModal"
import FileInfoModal from "../FileInfoModal"
import { CONTENT_TYPES } from "../../../../Utils/Constants"
Expand Down
1 change: 0 additions & 1 deletion packages/files-ui/src/Contexts/FilesContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,6 @@ const FilesProvider = ({ children }: FilesContextProps) => {
}
})
)

await filesApiClient.uploadBucketObjects(
bucket.id,
filesParam,
Expand Down
78 changes: 78 additions & 0 deletions packages/files-ui/src/Utils/getFilesFromDataTransferItems.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//Shamelessly borrowed from https://github.com/anatol-grabowski/datatransfer-files-promise with added Types

type FileWithPath = File & {filepath: string}

const getFilesFromDataTransferItems = async (dataTransferItems: DataTransferItemList): Promise<Array<FileWithPath>> => {
const readFile = (entry: FileEntry, path = ""): Promise<FileWithPath> => {
return new Promise((resolve, reject) => {
entry.file((file: File) => {
Object.defineProperty(file, "filepath", {
value: path
})
resolve(file as FileWithPath)
}, (err: Error) => {
reject(err)
})
})
}

const dirReadEntries = (dirReader: DirectoryReader, path: string): Promise<FileWithPath[]> => {
return new Promise((resolve, reject) => {
dirReader.readEntries(async (entries: FileSystemEntry[]) => {
let files = [] as Array<FileWithPath>
for (const entry of entries) {
const itemFiles = await getFilesFromEntry(entry, path) as Array<FileWithPath>
files = files.concat(itemFiles)
}
resolve(files)
}, (err: Error) => {
reject(err)
})
})
}

const readDir = async (entry: DirectoryEntry, path: string) => {
const dirReader = entry.createReader()
const newPath = path + entry.name + "/"
let files = [] as Array<FileWithPath>
let newFiles
do {
newFiles = await dirReadEntries(dirReader, newPath)
files = files.concat(newFiles)
} while (newFiles.length > 0)
return files
}

const getFilesFromEntry = async (entry: FileSystemEntry, path = "") => {
if (entry.isFile) {
const file = await readFile(entry as FileEntry, path)
return [file]
}
if (entry.isDirectory) {
const files = await readDir(entry as DirectoryEntry, path)
return files
}
}

let files = [] as Array<FileWithPath>
const entries = []

// Pull out all entries before reading them
for (let i = 0, ii = dataTransferItems.length; i < ii; i++) {
entries.push(dataTransferItems[i].webkitGetAsEntry())
}

// Recursively read through all entries
for (const entry of entries) {
if (entry) {
const newFiles = await getFilesFromEntry(entry)
if (newFiles) {
files = files.concat(newFiles)
}
}
}

return files
}

export default getFilesFromDataTransferItems
3 changes: 1 addition & 2 deletions packages/files-ui/src/locales/fr/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ msgstr ""
"POT-Creation-Date: 2021-04-23 11:05+0200\n"
"PO-Revision-Date: 2021-09-15 21:36+0000\n"
"Last-Translator: J. Lavoie <j.lavoie@net-c.ca>\n"
"Language-Team: French <https://hosted.weblate.org/projects/chainsafe-files/"
"chainsafe-files-user-interface/fr/>\n"
"Language-Team: French <https://hosted.weblate.org/projects/chainsafe-files/chainsafe-files-user-interface/fr/>\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import FileSystemItem from "../FileSystemItem/FileSystemItem"
import UploadProgressModals from "../UploadProgressModals"
import DownloadProgressModals from "../DownloadProgressModals"
import CreateFolderModal from "../CreateFolderModal/CreateFolderModal"
import UploadFileModule from "../UploadFileModal/UploadFileModal"
import UploadFileModal from "../UploadFileModal/UploadFileModal"
import MoveFileModal from "../MoveFileModal/MoveFileModal"
import { CONTENT_TYPES } from "../../../Utils/Constants"
import { CSSTheme } from "../../../Themes/types"
Expand Down Expand Up @@ -990,7 +990,7 @@ const FilesList = () => {
modalOpen={createFolderModalOpen}
close={() => setCreateFolderModalOpen(false)}
/>
<UploadFileModule
<UploadFileModal
modalOpen={isUploadModalOpen}
close={() => setIsUploadModalOpen(false)}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ interface IUploadFileModuleProps {
close: () => void
}

const UploadFileModule = ({ modalOpen, close }: IUploadFileModuleProps) => {
const UploadFileModal = ({ modalOpen, close }: IUploadFileModuleProps) => {
const classes = useStyles()
const [isDoneDisabled, setIsDoneDisabled] = useState(true)
const { uploadFiles } = useStorage()
Expand Down Expand Up @@ -166,4 +166,4 @@ const UploadFileModule = ({ modalOpen, close }: IUploadFileModuleProps) => {
)
}

export default UploadFileModule
export default UploadFileModal
17 changes: 17 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5829,6 +5829,18 @@
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==

"@types/filesystem@^0.0.32":
version "0.0.32"
resolved "https://registry.yarnpkg.com/@types/filesystem/-/filesystem-0.0.32.tgz#307df7cc084a2293c3c1a31151b178063e0a8edf"
integrity sha512-Yuf4jR5YYMR2DVgwuCiP11s0xuVRyPKmz8vo6HBY3CGdeMj8af93CFZX+T82+VD1+UqHOxTq31lO7MI7lepBtQ==
dependencies:
"@types/filewriter" "*"

"@types/filewriter@*":
version "0.0.29"
resolved "https://registry.yarnpkg.com/@types/filewriter/-/filewriter-0.0.29.tgz#a48795ecadf957f6c0d10e0c34af86c098fa5bee"
integrity sha512-BsPXH/irW0ht0Ji6iw/jJaK8Lj3FJemon2gvEqHKpCdDCeemHa+rI3WBGq5z7cDMZgoLjY40oninGxqk+8NzNQ==

"@types/fs-extra@^8.0.1":
version "8.1.1"
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-8.1.1.tgz#1e49f22d09aa46e19b51c0b013cb63d0d923a068"
Expand Down Expand Up @@ -10731,6 +10743,11 @@ data-urls@^1.0.0, data-urls@^1.1.0:
whatwg-mimetype "^2.2.0"
whatwg-url "^7.0.0"

datatransfer-files-promise@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/datatransfer-files-promise/-/datatransfer-files-promise-1.3.1.tgz#8996214c76925b57fe4c03b25a214ceaca76f063"
integrity sha512-xwh/RYaCTzUVyZr+rDKO28ew3Nbe4aP1HmEa1rNta9Uq1VNI2+ME0s1JKwsnUKXEpCWL+puGXkpJ6j+3vm/T8g==

date-fns@^2.16.1:
version "2.16.1"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.16.1.tgz#05775792c3f3331da812af253e1a935851d3834b"
Expand Down