From b8efe772fe246dea0e55a4b9f31035a251e8c179 Mon Sep 17 00:00:00 2001 From: Saumya Pandey Date: Mon, 20 Sep 2021 07:30:54 +0530 Subject: [PATCH] fix: Warning when dragging document between collections with different user permissions (#2516) --- .../Sidebar/components/CollectionLink.js | 37 ++++++- .../Sidebar/components/DocumentLink.js | 7 +- app/scenes/DocumentReparent.js | 98 +++++++++++++++++++ shared/i18n/locales/en_US/translation.json | 7 ++ 4 files changed, 147 insertions(+), 2 deletions(-) create mode 100644 app/scenes/DocumentReparent.js diff --git a/app/components/Sidebar/components/CollectionLink.js b/app/components/Sidebar/components/CollectionLink.js index 74e564d5..6b67473d 100644 --- a/app/components/Sidebar/components/CollectionLink.js +++ b/app/components/Sidebar/components/CollectionLink.js @@ -3,11 +3,14 @@ import fractionalIndex from "fractional-index"; import { observer } from "mobx-react"; import * as React from "react"; import { useDrop, useDrag } from "react-dnd"; +import { useTranslation } from "react-i18next"; import { useLocation } from "react-router-dom"; import styled from "styled-components"; import Collection from "models/Collection"; import Document from "models/Document"; +import DocumentReparent from "scenes/DocumentReparent"; import CollectionIcon from "components/CollectionIcon"; +import Modal from "components/Modal"; import DocumentLink from "./DocumentLink"; import DropCursor from "./DropCursor"; import DropToImport from "./DropToImport"; @@ -37,8 +40,15 @@ function CollectionLink({ isDraggingAnyCollection, onChangeDragging, }: Props) { + const { t } = useTranslation(); const { search } = useLocation(); const [menuOpen, handleMenuOpen, handleMenuClose] = useBoolean(); + const [ + permissionOpen, + handlePermissionOpen, + handlePermissionClose, + ] = useBoolean(); + const itemRef = React.useRef(); const handleTitleChange = React.useCallback( async (name: string) => { @@ -74,9 +84,22 @@ function CollectionLink({ const [{ isOver, canDrop }, drop] = useDrop({ accept: "document", drop: (item, monitor) => { + const { id, collectionId } = item; if (monitor.didDrop()) return; if (!collection) return; - documents.move(item.id, collection.id); + if (collection.id === collectionId) return; + const prevCollection = collections.get(collectionId); + + if ( + prevCollection && + prevCollection.permission === null && + prevCollection.permission !== collection.permission + ) { + itemRef.current = item; + handlePermissionOpen(); + } else { + documents.move(id, collection.id); + } }, canDrop: (item, monitor) => { return policies.abilities(collection.id).update; @@ -210,6 +233,18 @@ function CollectionLink({ index={index} /> ))} + + + ); } diff --git a/app/components/Sidebar/components/DocumentLink.js b/app/components/Sidebar/components/DocumentLink.js index 58dcebdb..b4c5b464 100644 --- a/app/components/Sidebar/components/DocumentLink.js +++ b/app/components/Sidebar/components/DocumentLink.js @@ -128,7 +128,12 @@ function DocumentLink( // Draggable const [{ isDragging }, drag] = useDrag({ type: "document", - item: () => ({ ...node, depth, active: isActiveDocument }), + item: () => ({ + ...node, + depth, + active: isActiveDocument, + collectionId: collection?.id || "", + }), collect: (monitor) => ({ isDragging: !!monitor.isDragging(), }), diff --git a/app/scenes/DocumentReparent.js b/app/scenes/DocumentReparent.js new file mode 100644 index 00000000..56adbcb2 --- /dev/null +++ b/app/scenes/DocumentReparent.js @@ -0,0 +1,98 @@ +// @flow +import { observer } from "mobx-react"; +import * as React from "react"; +import { useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import Collection from "models/Collection"; +import Document from "models/Document"; +import Button from "components/Button"; +import Flex from "components/Flex"; +import HelpText from "components/HelpText"; +import useStores from "hooks/useStores"; +import useToasts from "hooks/useToasts"; +import { type NavigationNode } from "types"; + +type Props = {| + document: Document, + item: {| + active: ?boolean, + children: Array, + collectionId: string, + depth: number, + id: string, + title: string, + url: string, + |}, + collection: Collection, + onCancel: () => void, + onSubmit: () => void, +|}; + +function DocumentReparent({ + document, + collection, + item, + onSubmit, + onCancel, +}: Props) { + const [isSaving, setIsSaving] = useState(); + const { showToast } = useToasts(); + const { documents, collections } = useStores(); + const { t } = useTranslation(); + const prevCollection = collections.get(item.collectionId); + + const accessMapping = { + read_write: t("view and edit access"), + read: t("view only access"), + null: t("no access"), + }; + + const handleSubmit = React.useCallback( + async (ev: SyntheticEvent<>) => { + ev.preventDefault(); + setIsSaving(true); + + try { + await documents.move(item.id, collection.id); + showToast(t("Document moved"), { + type: "info", + }); + onSubmit(); + } catch (err) { + showToast(err.message, { type: "error" }); + } finally { + setIsSaving(false); + } + }, + [documents, item.id, collection.id, showToast, t, onSubmit] + ); + + return ( + +
+ + }} + /> + + {" "} + +
+
+ ); +} + +export default observer(DocumentReparent); diff --git a/shared/i18n/locales/en_US/translation.json b/shared/i18n/locales/en_US/translation.json index e4d66d36..a80e689f 100644 --- a/shared/i18n/locales/en_US/translation.json +++ b/shared/i18n/locales/en_US/translation.json @@ -142,6 +142,7 @@ "Keyboard shortcuts": "Keyboard shortcuts", "Back": "Back", "Document archived": "Document archived", + "Move document": "Move document", "Collections could not be loaded, please reload the app": "Collections could not be loaded, please reload the app", "New collection": "New collection", "Collections": "Collections", @@ -378,6 +379,12 @@ "Couldn’t create the document, try again?": "Couldn’t create the document, try again?", "Document permanently deleted": "Document permanently deleted", "Are you sure you want to permanently delete the {{ documentTitle }} document? This action is immediate and cannot be undone.": "Are you sure you want to permanently delete the {{ documentTitle }} document? This action is immediate and cannot be undone.", + "view and edit access": "view and edit access", + "view only access": "view only access", + "no access": "no access", + "Heads up – moving the document {{ title }} to the {{ newCollectionName }} collection will grant all team members {{ newPermission }}, they currently have {{ prevPermission }}.": "Heads up – moving the document {{ title }} to the {{ newCollectionName }} collection will grant all team members {{ newPermission }}, they currently have {{ prevPermission }}.", + "Moving": "Moving", + "Cancel": "Cancel", "Template created, go ahead and customize it": "Template created, go ahead and customize it", "Creating a template from {{titleWithDefault}} is a non-destructive action – we'll make a copy of the document and turn it into a template that can be used as a starting point for new documents.": "Creating a template from {{titleWithDefault}} is a non-destructive action – we'll make a copy of the document and turn it into a template that can be used as a starting point for new documents.", "Search documents": "Search documents",