From 43fe5efe79e5788bf005582dba5e6d715f395cf5 Mon Sep 17 00:00:00 2001 From: kim Date: Mon, 22 Jul 2024 13:18:39 +0200 Subject: [PATCH] feat: allow drag move many items --- package.json | 2 +- src/components/item/FolderContent.tsx | 4 +- src/components/main/list/ItemsTable.tsx | 60 ++++++++++++++++++------ src/components/pages/home/HomeScreen.tsx | 6 ++- src/langs/en.json | 5 +- yarn.lock | 10 ++-- 6 files changed, 61 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index e36670f10..5bc16a070 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "@graasp/chatbox": "3.1.0", "@graasp/map": "1.16.0", "@graasp/query-client": "3.16.0", - "@graasp/sdk": "4.19.0", + "@graasp/sdk": "4.20.0", "@graasp/translations": "1.32.0", "@graasp/ui": "github:graasp/graasp-ui#card-selection", "@mui/icons-material": "5.16.4", diff --git a/src/components/item/FolderContent.tsx b/src/components/item/FolderContent.tsx index 31a347d69..39aab8c73 100644 --- a/src/components/item/FolderContent.tsx +++ b/src/components/item/FolderContent.tsx @@ -47,7 +47,8 @@ type Props = { const Content = ({ item, searchText, items, sortBy }: Props) => { const { mode } = useLayoutContext(); const { itemTypes } = useFilterItemsContext(); - const { selectedIds, toggleSelection } = useSelectionContext(); + const { selectedIds, clearSelection, toggleSelection } = + useSelectionContext(); const enableEditing = item.permission ? PermissionLevelCompare.lte(PermissionLevel.Write, item.permission) @@ -73,6 +74,7 @@ const Content = ({ item, searchText, items, sortBy }: Props) => { id={buildItemsTableId(item.id)} items={items ?? []} onCardClick={(id) => toggleSelection(id)} + onMove={clearSelection} /> {Boolean(enableEditing && !searchText && !itemTypes?.length) && ( diff --git a/src/components/main/list/ItemsTable.tsx b/src/components/main/list/ItemsTable.tsx index 539758541..5318234f2 100644 --- a/src/components/main/list/ItemsTable.tsx +++ b/src/components/main/list/ItemsTable.tsx @@ -8,13 +8,14 @@ import Dialog from '@mui/material/Dialog'; import DialogTitle from '@mui/material/DialogTitle'; import { ItemType, PackedItem } from '@graasp/sdk'; -import { COMMON } from '@graasp/translations'; +import { COMMON, FAILURE_MESSAGES } from '@graasp/translations'; import { Button, DraggingWrapper } from '@graasp/ui'; import { useBuilderTranslation, useCommonTranslation, useEnumsTranslation, + useMessagesTranslation, } from '@/config/i18n'; import { BUILDER } from '@/langs/constants'; @@ -33,6 +34,7 @@ export type ItemsTableProps = { enableMoveInBetween?: boolean; onCardClick?: (el: string) => void; selectedIds?: string[]; + onMove?: () => void; }; const ItemsTable = ({ @@ -43,10 +45,12 @@ const ItemsTable = ({ enableMoveInBetween = true, selectedIds, onCardClick, + onMove, }: ItemsTableProps): JSX.Element => { const [open, setOpen] = useState(false); const { t: translateCommon } = useCommonTranslation(); const { t: translateBuilder } = useBuilderTranslation(); + const { t: translateMessage } = useMessagesTranslation(); const { t: translateEnums } = useEnumsTranslation(); const { itemId } = useParams(); @@ -59,7 +63,7 @@ const ItemsTable = ({ const { mutate: moveItems } = mutations.useMoveItems(); const { mutateAsync: uploadItems } = mutations.useUploadFiles(); const [moveData, setMoveData] = useState<{ - movedItem: PackedItem; + movedItems: PackedItem[]; to: PackedItem; }>(); @@ -71,7 +75,10 @@ const ItemsTable = ({ items: rows, }); - const onDropInRow = (movedItem: PackedItem | any, targetItem: PackedItem) => { + const onDropInRow = ( + movedItem: PackedItem | { files: File[] }, + targetItem: PackedItem, + ) => { // prevent drop in non-folder item if (targetItem.type !== ItemType.FOLDER) { toast.error( @@ -83,7 +90,7 @@ const ItemsTable = ({ } // upload files in item - if (movedItem.files) { + if ('files' in movedItem) { uploadItems({ files: movedItem.files, id: targetItem.id, @@ -95,21 +102,39 @@ const ItemsTable = ({ .catch((e) => { close(e); }); - } else if (movedItem.id !== targetItem.id) { - setOpen(true); - setMoveData({ movedItem, to: targetItem }); + return; + } + + // cannot move item into itself, or target cannot be part of selection if moving selection + if ( + movedItem.id === targetItem.id || + (selectedIds?.includes(movedItem?.id) && + selectedIds?.includes(targetItem.id)) + ) { + toast.error(translateMessage(FAILURE_MESSAGES.INVALID_MOVE_TARGET)); + return; + } + + let movedItems: PackedItem[] = []; + // use selected ids on drag move if moved item is part of selected ids + if (selectedIds?.includes(movedItem?.id)) { + movedItems = rows.filter(({ id }) => selectedIds.includes(id)); + } else if (movedItem) { + movedItems = [movedItem]; } + setMoveData({ movedItems, to: targetItem }); + setOpen(true); }; // warning: this won't work anymore with pagination! const onDropBetweenRow = ( - { files, id }: PackedItem | any, + el: PackedItem | { files: File[] }, previousItem?: PackedItem, ) => { // upload files at row - if (files) { + if ('files' in el) { uploadItems({ - files, + files: el.files, id: parentItem?.id, previousItemId: previousItem?.id, onUploadProgress: update, @@ -124,6 +149,7 @@ const ItemsTable = ({ console.error('cannot move in root'); toast.error(BUILDER.ERROR_MESSAGE); } else { + const { id } = el; setMovingId(id); reorder({ id, @@ -137,9 +163,10 @@ const ItemsTable = ({ const handleMoveItems = () => { if (moveData) { - moveItems({ items: [moveData.movedItem], to: moveData.to.id }); + moveItems({ items: moveData.movedItems, to: moveData.to.id }); setMoveData(undefined); handleClose(); + onMove?.(); } }; @@ -181,7 +208,7 @@ const ItemsTable = ({ t={translateBuilder} i18nKey={BUILDER.MOVE_CONFIRM_TITLE} values={{ - name: moveData.movedItem.name, + count: moveData.movedItems.length, targetName: moveData.to.name, }} components={{ 1: }} @@ -189,9 +216,12 @@ const ItemsTable = ({ - {translateBuilder(BUILDER.MOVE_WARNING, { - name: moveData.movedItem.name, - })} + {translateBuilder(BUILDER.MOVE_WARNING)} +
    + {moveData.movedItems.map(({ name, id }) => ( +
  • {name}
  • + ))} +
) : ( diff --git a/src/components/pages/home/HomeScreen.tsx b/src/components/pages/home/HomeScreen.tsx index 3501b16a4..ee3b9b53c 100644 --- a/src/components/pages/home/HomeScreen.tsx +++ b/src/components/pages/home/HomeScreen.tsx @@ -45,7 +45,8 @@ const HomeScreenContent = ({ searchText }: { searchText: string }) => { const { itemTypes } = useFilterItemsContext(); const [showOnlyMe, setShowOnlyMe] = useState(false); - const { selectedIds, toggleSelection } = useSelectionContext(); + const { selectedIds, toggleSelection, clearSelection } = + useSelectionContext(); const { mode } = useLayoutContext(); const { sortBy, setSortBy, ordering, setOrdering } = useSorting({ @@ -81,7 +82,7 @@ const HomeScreenContent = ({ searchText }: { searchText: string }) => { ); } - if (data && data.pages.length) { + if (data?.pages?.length) { // default show upload zone let content = ( @@ -101,6 +102,7 @@ const HomeScreenContent = ({ searchText }: { searchText: string }) => { enableMoveInBetween={false} onCardClick={toggleSelection} selectedIds={selectedIds} + onMove={clearSelection} /> {!isFetching && data.pages[0].totalCount > totalFetchedItems && ( diff --git a/src/langs/en.json b/src/langs/en.json index dba682813..52780d823 100644 --- a/src/langs/en.json +++ b/src/langs/en.json @@ -463,9 +463,10 @@ "BOOKMARKS_NO_ITEM": "No bookmarked item", "BOOKMARKS_NO_ITEM_SEARCH": "No bookmarked item for {{search}}", "UPLOAD_BETWEEN_FILES": "Upload your file(s) here", - "MOVE_WARNING": "This operation might give access to {{name}} to more persons than previously. Do you want to proceed?", + "MOVE_WARNING": "This operation might give access to more persons than previously to the items below. Do you want to proceed?", "MOVE_IN_NON_FOLDER_ERROR_MESSAGE": "Cannot add items in {{type}}", - "MOVE_CONFIRM_TITLE": "Confirm moving <1>{{name}} inside <1>{{targetName}}", + "MOVE_CONFIRM_TITLE_one": "Confirm moving one item inside <1>{{targetName}}?", + "MOVE_CONFIRM_TITLE": "Confirm moving <1>{{count}} items inside <1>{{targetName}}?", "ITEM_SEARCH_NOTHING_FOUND": "No item found with these parameters", "ITEM_SEARCH_NOTHING_FOUND_QUERY_TITLE": "search", "ITEM_SEARCH_NOTHING_FOUND_TYPES_TITLE": "types", diff --git a/yarn.lock b/yarn.lock index 6d7a89aa4..dc6c82843 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1734,9 +1734,9 @@ __metadata: languageName: node linkType: hard -"@graasp/sdk@npm:4.19.0": - version: 4.19.0 - resolution: "@graasp/sdk@npm:4.19.0" +"@graasp/sdk@npm:4.20.0": + version: 4.20.0 + resolution: "@graasp/sdk@npm:4.20.0" dependencies: "@faker-js/faker": "npm:8.4.1" filesize: "npm:10.1.4" @@ -1745,7 +1745,7 @@ __metadata: peerDependencies: date-fns: ^3 uuid: ^9 || ^10.0.0 - checksum: 10/d2fb382f5669ab1b8e265612208d6766672be8b6fa833fe1af8e4f17d2c3f7f384e4bf3f5fd004ec76c8c1ab1ad382c37730477e90ec1ae85b3079ca6a8e68aa + checksum: 10/81e336d68094bcd234b4983217ce9f05d41501032fac00810bf927b8289ef933e18cdfb0e1b0275df6af5a7683b54a8ebf509134aa95b4c7ecfce4a5f313752d languageName: node linkType: hard @@ -7959,7 +7959,7 @@ __metadata: "@graasp/chatbox": "npm:3.1.0" "@graasp/map": "npm:1.16.0" "@graasp/query-client": "npm:3.16.0" - "@graasp/sdk": "npm:4.19.0" + "@graasp/sdk": "npm:4.20.0" "@graasp/translations": "npm:1.32.0" "@graasp/ui": "github:graasp/graasp-ui#card-selection" "@mui/icons-material": "npm:5.16.4"