diff --git a/packages/files-ui/package.json b/packages/files-ui/package.json index 74413f7d1d..dbe13d718f 100644 --- a/packages/files-ui/package.json +++ b/packages/files-ui/package.json @@ -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", diff --git a/packages/files-ui/src/Components/Modules/FileBrowsers/views/DragPreviewLayer.tsx b/packages/files-ui/src/Components/Modules/FileBrowsers/views/DragPreviewLayer.tsx new file mode 100644 index 0000000000..95722994b4 --- /dev/null +++ b/packages/files-ui/src/Components/Modules/FileBrowsers/views/DragPreviewLayer.tsx @@ -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 ( +
+
+ {icon} +
+
+ {name} +
+
+ ) +} + +const DragPreviewGridItem: React.FC<{item: FileSystemItem; icon: React.ReactNode}> = ({ + item: { name, isFolder }, + icon +}) => { + const classes = useStyles() + return ( +
+
+
+ {icon} +
+
{name}
+
+
+ ) +} + +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 + :
+ +
+} diff --git a/packages/files-ui/src/Components/Modules/FileBrowsers/views/FileSystemItem/FileSystemItem.tsx b/packages/files-ui/src/Components/Modules/FileBrowsers/views/FileSystemItem/FileSystemItem.tsx index 07af776323..b40892d13b 100644 --- a/packages/files-ui/src/Components/Modules/FileBrowsers/views/FileSystemItem/FileSystemItem.tsx +++ b/packages/files-ui/src/Components/Modules/FileBrowsers/views/FileSystemItem/FileSystemItem.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useRef } from "react" +import React, { useCallback, useEffect, useRef } from "react" import { FormikTextInput, Typography, @@ -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" @@ -131,7 +131,6 @@ const FileSystemItemRow = ({ setEditing, renameSchema, handleRename, - handleMove, deleteFile, recoverFile, viewFolder, @@ -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) { @@ -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() diff --git a/packages/files-ui/src/Components/Modules/FileBrowsers/views/FilesTable.view.tsx b/packages/files-ui/src/Components/Modules/FileBrowsers/views/FilesTable.view.tsx index 6a2854be9b..3774edc756 100644 --- a/packages/files-ui/src/Components/Modules/FileBrowsers/views/FilesTable.view.tsx +++ b/packages/files-ui/src/Components/Modules/FileBrowsers/views/FilesTable.view.tsx @@ -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 { @@ -311,6 +312,8 @@ const FilesTableView = () => { const [selectedCids, setSelectedCids] = useState([]) const [previewFileIndex, setPreviewFileIndex] = useState() const { selectedLocale } = useLanguageContext() + const { redirect } = useHistory() + const items: FileSystemItem[] = useMemo(() => { let temp = [] @@ -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( @@ -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({ @@ -466,6 +467,7 @@ const FilesTableView = () => { // Bulk operations const [validBulkOps, setValidBulkOps] = useState([]) + useEffect(() => { if (bulkOperations) { let filteredList: FileOperation[] = [ @@ -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 (
{ Drop to upload files +
{crumbs && moduleRootPath ? ( { - const manager = useRef(RNDContext) - if (manager.current?.dragDropManager) { - return ( - - {children} - - ) - } - return <>{children} -} +const DragAndDrop: React.FC = ({ children }) => ( + + {children} + +) export default DragAndDrop diff --git a/packages/files-ui/src/Utils/pathUtils.ts b/packages/files-ui/src/Utils/pathUtils.ts index 3554703e36..ef4174ac5a 100644 --- a/packages/files-ui/src/Utils/pathUtils.ts +++ b/packages/files-ui/src/Utils/pathUtils.ts @@ -45,7 +45,6 @@ export function getParentPathFromFilePath(filePath: string) { else return parentPath } - export function extractDrivePath(pathname: string) { return pathname.split("/").slice(2).join("/").concat("/") } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 7cb9a92c00..49393b31e2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4758,7 +4758,7 @@ resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.7.tgz#613957d900fab9ff84c8dfb24fa3eef0c2a40896" integrity sha512-2xtoL22/3Mv6a70i4+4RB7VgbDDORoWwjcqeNysojZA0R7NK17RbY5Gof/2QiFfJgX+KkWghbwJ+d/2SB8Ndzg== -"@types/hoist-non-react-statics@*", "@types/hoist-non-react-statics@^3.3.1": +"@types/hoist-non-react-statics@*": version "3.3.1" resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== @@ -9510,14 +9510,14 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" -dnd-core@^11.1.3: - version "11.1.3" - resolved "https://registry.yarnpkg.com/dnd-core/-/dnd-core-11.1.3.tgz#f92099ba7245e49729d2433157031a6267afcc98" - integrity sha512-QugF55dNW+h+vzxVJ/LSJeTeUw9MCJ2cllhmVThVPEtF16ooBkxj0WBE5RB+AceFxMFo1rO6bJKXtqKl+JNnyA== +dnd-core@14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/dnd-core/-/dnd-core-14.0.0.tgz#973ab3470d0a9ac5a0fa9021c4feba93ad12347d" + integrity sha512-wTDYKyjSqWuYw3ZG0GJ7k+UIfzxTNoZLjDrut37PbcPGNfwhlKYlPUqjAKUjOOv80izshUiqusaKgJPItXSevA== dependencies: "@react-dnd/asap" "^4.0.0" "@react-dnd/invariant" "^2.0.0" - redux "^4.0.4" + redux "^4.0.5" dns-equal@^1.0.0: version "1.0.0" @@ -17933,22 +17933,23 @@ react-dev-utils@^9.0.0: strip-ansi "5.2.0" text-table "0.2.0" -react-dnd-html5-backend@^11.1.3: - version "11.1.3" - resolved "https://registry.yarnpkg.com/react-dnd-html5-backend/-/react-dnd-html5-backend-11.1.3.tgz#2749f04f416ec230ea193f5c1fbea2de7dffb8f7" - integrity sha512-/1FjNlJbW/ivkUxlxQd7o3trA5DE33QiRZgxent3zKme8DwF4Nbw3OFVhTRFGaYhHFNL1rZt6Rdj1D78BjnNLw== +react-dnd-html5-backend@14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/react-dnd-html5-backend/-/react-dnd-html5-backend-14.0.0.tgz#28d660a2ad1e07447c34a65cd25f7de8f1657194" + integrity sha512-2wAQqRFC1hbRGmk6+dKhOXsyQQOn3cN8PSZyOUeOun9J8t3tjZ7PS2+aFu7CVu2ujMDwTJR3VTwZh8pj2kCv7g== dependencies: - dnd-core "^11.1.3" + dnd-core "14.0.0" -react-dnd@^11.1.3: - version "11.1.3" - resolved "https://registry.yarnpkg.com/react-dnd/-/react-dnd-11.1.3.tgz#f9844f5699ccc55dfc81462c2c19f726e670c1af" - integrity sha512-8rtzzT8iwHgdSC89VktwhqdKKtfXaAyC4wiqp0SywpHG12TTLvfOoL6xNEIUWXwIEWu+CFfDn4GZJyynCEuHIQ== +react-dnd@14.0.2: + version "14.0.2" + resolved "https://registry.yarnpkg.com/react-dnd/-/react-dnd-14.0.2.tgz#57266baec92b887301f81fa3b77f87168d159733" + integrity sha512-JoEL78sBCg8SzjOKMlkR70GWaPORudhWuTNqJ56lb2P8Vq0eM2+er3ZrMGiSDhOmzaRPuA9SNBz46nHCrjn11A== dependencies: + "@react-dnd/invariant" "^2.0.0" "@react-dnd/shallowequal" "^2.0.0" - "@types/hoist-non-react-statics" "^3.3.1" - dnd-core "^11.1.3" - hoist-non-react-statics "^3.3.0" + dnd-core "14.0.0" + fast-deep-equal "^3.1.3" + hoist-non-react-statics "^3.3.2" react-docgen@^5.0.0: version "5.3.0" @@ -18513,13 +18514,12 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" -redux@^4.0.4: - version "4.0.5" - resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f" - integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w== +redux@^4.0.5: + version "4.1.0" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.0.tgz#eb049679f2f523c379f1aff345c8612f294c88d4" + integrity sha512-uI2dQN43zqLWCt6B/BMGRMY6db7TTY4qeHHfGeKb3EOhmOKjU3KdWvNLJyqaHRksv/ErdNH7cFZWg9jXtewy4g== dependencies: - loose-envify "^1.4.0" - symbol-observable "^1.2.0" + "@babel/runtime" "^7.9.2" refractor@^2.4.1: version "2.10.1" @@ -20292,7 +20292,7 @@ svgo@^1.0.0, svgo@^1.2.2: unquote "~1.1.1" util.promisify "~1.0.0" -symbol-observable@^1.1.0, symbol-observable@^1.2.0: +symbol-observable@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==