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

Bulk DND Move files #1028

Merged
merged 20 commits into from
May 19, 2021
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
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
4 changes: 2 additions & 2 deletions packages/files-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@
"mime-matcher": "^1.0.5",
"react": "^16.14.0",
"react-beforeunload": "^2.4.0",
"react-dnd": "^11.1.3",
"react-dnd-html5-backend": "^11.1.3",
"react-dnd": "14.0.2",
"react-dnd-html5-backend": "14.0.0",
"react-dom": "^16.14.0",
"react-h5-audio-player": "^3.5.0",
"react-hotkeys-hook": "^2.4.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import { FolderFilledSvg, FileImageSvg, FilePdfSvg, FileTextSvg, Typography } from "@chainsafe/common-components"
import { makeStyles } from "@chainsafe/common-theme"
import clsx from "clsx"
import React from "react"
import { useDragLayer, XYCoord } from "react-dnd"
import { FileSystemItem } from "../../../../Contexts/DriveContext"
import { CSFTheme } from "../../../../Themes/types"
import { DragTypes } from "../DragConstants"
import { BrowserView } from "../types"

const useStyles = makeStyles(({ breakpoints, constants, palette }: CSFTheme) => {
return ({
rowItem: {
display: "flex",
height: 70,
border: "2px solid transparent"
},
fileIcon: {
"& svg": {
width: constants.generalUnit * 2.5,
fill: constants.fileSystemItemRow.icon
},
[breakpoints.up("md")]: {
paddingLeft: constants.generalUnit * 8.5
},
paddingRight: constants.generalUnit * 6
},
folderIcon: {
"& svg": {
fill: palette.additional.gray[9]
}
},
filename: {
whiteSpace: "nowrap",
textOverflow: "ellipsis",
overflow: "hidden"
},
previewDragLayer: {
position: "fixed",
pointerEvents: "none",
zIndex: 100,
left: 0,
top: 0,
bottom: 0,
right: 0
},
dropdownIcon: {
"& svg": {
fill: constants.fileSystemItemRow.dropdownIcon
}
},
gridViewContainer: {
display: "flex",
flex: 1,
maxWidth: constants.generalUnit * 24
},
gridFolderName: {
textAlign: "center",
wordBreak: "break-all",
overflowWrap: "break-word",
padding: constants.generalUnit
},
gridViewIconNameBox: {
display: "flex",
flexDirection: "column",
width: "100%",
cursor: "pointer"
},
gridIcon: {
display: "flex",
justifyContent: "center",
alignItems: "center",
height: constants.generalUnit * 16,
maxWidth: constants.generalUnit * 24,
border: `1px solid ${palette.additional["gray"][6]}`,
boxShadow: constants.filesTable.gridItemShadow,
"& svg": {
width: "30%"
},
[breakpoints.down("lg")]: {
height: constants.generalUnit * 16
},
[breakpoints.down("sm")]: {
height: constants.generalUnit * 16
}
},
menuTitleGrid: {
padding: `0 ${constants.generalUnit * 0.5}px`,
[breakpoints.down("md")]: {
padding: 0
}
}
})})

const DragPreviewRowItem: React.FC<{item: FileSystemItem; icon: React.ReactNode}> = ({
item: { name, isFolder },
icon
}) => {
const classes = useStyles()
return (
<div className={classes.rowItem}>
<div className={clsx(classes.fileIcon, isFolder && classes.folderIcon)}>
{icon}
</div>
<div className={classes.filename}>
<Typography>{name}</Typography>
</div>
</div>
)
}

const DragPreviewGridItem: React.FC<{item: FileSystemItem; icon: React.ReactNode}> = ({
item: { name, isFolder },
icon
}) => {
const classes = useStyles()
return (
<div className={classes.gridViewContainer}>
<div
className={clsx(classes.gridViewIconNameBox)}
>
<div
className={clsx(
classes.fileIcon,
isFolder && classes.folderIcon,
classes.gridIcon
)}
>
{icon}
</div>
<div className={classes.gridFolderName}>{name}</div>
</div>
</div>
)
}

export const DragPreviewLayer: React.FC<{items: FileSystemItem[]; previewType: BrowserView} > = ({ items, previewType }) => {
const classes = useStyles()
const { isDragging, dragItems, itemType, currentOffset } = useDragLayer(monitor => ({
itemType: monitor.getItemType(),
dragItems: monitor.getItem() as {ids: string[]},
isDragging: monitor.isDragging(),
initialOffset: monitor.getInitialSourceClientOffset(),
currentOffset: monitor.getSourceClientOffset()
}))

const getItemStyles = (currentOffset: XYCoord | null) => {
if (!currentOffset) {
return {
display: "none"
}
}
const { x, y } = currentOffset

const transform = `translate(${x}px, ${y}px)`
return {
transform,
WebkitTransform: transform
}
}

return (!isDragging || itemType !== DragTypes.MOVABLE_FILE)
? null
: <div className={classes.previewDragLayer}>
<ul style={getItemStyles(currentOffset)}>
{dragItems.ids.map(di => {
const previewItem = items.find(i => i.cid === di)

if (previewItem) {
let Icon
if (previewItem.isFolder) {
Icon = FolderFilledSvg
} else if (previewItem.content_type.includes("image")) {
Icon = FileImageSvg
} else if (previewItem.content_type.includes("pdf")) {
Icon = FilePdfSvg
} else {
Icon = FileTextSvg
}

return (previewType === "table")
? <DragPreviewRowItem
item={previewItem}
icon={<Icon />}
key={previewItem.cid}
/>
: <DragPreviewGridItem
item={previewItem}
icon={<Icon />}
key={previewItem.cid}
/>
} else {
return null
}})}
</ul>
</div>
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback, useRef } from "react"
import React, { useCallback, useEffect, useRef } from "react"
import {
FormikTextInput,
Typography,
Expand All @@ -25,7 +25,7 @@ import CustomModal from "../../../../Elements/CustomModal"
import { Trans } from "@lingui/macro"
import { useDrag, useDrop } from "react-dnd"
import { DragTypes } from "../../DragConstants"
import { NativeTypes } from "react-dnd-html5-backend"
import { getEmptyImage, NativeTypes } from "react-dnd-html5-backend"
import { BrowserView, FileOperation } from "../../types"
import { CSFTheme } from "../../../../../Themes/types"
import FileItemTableItem from "./FileSystemTableItem"
Expand Down Expand Up @@ -131,7 +131,6 @@ const FileSystemItemRow = ({
setEditing,
renameSchema,
handleRename,
handleMove,
deleteFile,
recoverFile,
viewFolder,
Expand All @@ -144,7 +143,7 @@ const FileSystemItemRow = ({
browserView,
resetSelectedFiles
}: IFileSystemItemRowProps) => {
const { downloadFile, currentPath, handleUploadOnDrop, moduleRootPath } = useFileBrowser()
const { downloadFile, currentPath, handleUploadOnDrop, moduleRootPath, handleMove } = useFileBrowser()
const { cid, name, isFolder, content_type } = file
let Icon
if (isFolder) {
Expand Down Expand Up @@ -268,22 +267,41 @@ const FileSystemItemRow = ({
(itemOperation) => allMenuItems[itemOperation]
)

const [, dragMoveRef, preview] = useDrag({
item: { type: DragTypes.MOVABLE_FILE, payload: file }
const [, dragMoveRef, preview] = useDrag(() =>
({ type: DragTypes.MOVABLE_FILE,
item: () => {
if (selected.includes(file.cid)) {
return { ids: selected }
} else {
return { ids: [...selected, file.cid] }
}
}
}), [selected])

useEffect(() => {
// This gets called after every render, by default

// Use empty image as a drag preview so browsers don't draw it
// and we can draw whatever we want on the custom drag layer instead.
preview(getEmptyImage(), {
// IE fallback: specify that we'd rather screenshot the node
// when it already knows it's being dragged so we can hide it with CSS.
captureDraggingState: true
})
})

const [{ isOverMove }, dropMoveRef] = useDrop({
accept: DragTypes.MOVABLE_FILE,
canDrop: () => isFolder,
drop: async (item: {
type: typeof DragTypes.MOVABLE_FILE
payload: FileSystemItem
}) => {
handleMove &&
(await handleMove(
`${currentPath}${item.payload.name}`,
`${currentPath}${name}/${item.payload.name}`
))
drop: (item: {ids: string[]}) => {
item.ids.forEach((cid) => {
const fileToMove = files.find(f => f.cid === cid)
handleMove && fileToMove &&
handleMove(
`${currentPath}${fileToMove.name}`,
`${currentPath}${name}/${fileToMove.name}`
)
})
},
collect: (monitor) => ({
isOverMove: monitor.isOver()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import MimeMatcher from "../../../../Utils/MimeMatcher"
import { useLanguageContext } from "../../../../Contexts/LanguageContext"
import { getPathWithFile } from "../../../../Utils/pathUtils"
import SurveyBanner from "../../../SurveyBanner"
import { DragPreviewLayer } from "./DragPreviewLayer"
import { useFileBrowser } from "../../../../Contexts/FileBrowserContext"

interface IStyleProps {
Expand Down Expand Up @@ -311,6 +312,8 @@ const FilesTableView = () => {
const [selectedCids, setSelectedCids] = useState<string[]>([])
const [previewFileIndex, setPreviewFileIndex] = useState<number | undefined>()
const { selectedLocale } = useLanguageContext()
const { redirect } = useHistory()

const items: FileSystemItem[] = useMemo(() => {
let temp = []

Expand Down Expand Up @@ -342,8 +345,6 @@ const FilesTableView = () => {
: temp.sort(sortFoldersFirst)
}, [sourceFiles, direction, column, selectedLocale])

const { redirect } = useHistory()

const files = useMemo(() => items.filter((i) => !i.isFolder), [items])

const selectedFiles = useMemo(
Expand Down Expand Up @@ -412,13 +413,13 @@ const FilesTableView = () => {
[selectedCids]
)

const toggleAll = () => {
const toggleAll = useCallback(() => {
if (selectedCids.length === items.length) {
setSelectedCids([])
} else {
setSelectedCids([...items.map((file: FileSystemItem) => file.cid)])
}
}
}, [setSelectedCids, items, selectedCids])

const invalidFilenameRegex = new RegExp("/")
const renameSchema = object().shape({
Expand Down Expand Up @@ -466,6 +467,7 @@ const FilesTableView = () => {

// Bulk operations
const [validBulkOps, setValidBulkOps] = useState<FileOperation[]>([])

useEffect(() => {
if (bulkOperations) {
let filteredList: FileOperation[] = [
Expand Down Expand Up @@ -575,14 +577,17 @@ const FilesTableView = () => {
setSelectedCids([])
}, [])

useEffect(() => {
setSelectedCids([])
}, [currentPath])

const onHideSurveyBanner = useCallback(() => {
setIsSurveyBannerVisible(false)
}, [setIsSurveyBannerVisible])

const handleViewFolder = useCallback((cid: string) => {
resetSelectedCids()
viewFolder && viewFolder(cid)
}, [viewFolder, resetSelectedCids])
}, [viewFolder])

return (
<article
Expand All @@ -599,6 +604,10 @@ const FilesTableView = () => {
<Trans>Drop to upload files</Trans>
</Typography>
</div>
<DragPreviewLayer
items={sourceFiles}
previewType={browserView}
/>
<div className={classes.breadCrumbContainer}>
{crumbs && moduleRootPath ? (
<Breadcrumb
Expand Down
Loading