From 376c56ae9187967e64c16a6839efaabcb4b824b0 Mon Sep 17 00:00:00 2001 From: Louise Wang Date: Thu, 17 Mar 2022 20:48:35 +0100 Subject: [PATCH] feat: add download button --- package.json | 2 +- src/components/main/Item.js | 33 ++- src/components/table/ActionsCellRenderer.js | 31 ++- src/config/constants.js | 2 +- src/config/messages.js | 2 + src/middlewares/notifier.js | 280 ++++++++++++++++++++ yarn.lock | 8 +- 7 files changed, 348 insertions(+), 10 deletions(-) create mode 100644 src/middlewares/notifier.js diff --git a/package.json b/package.json index 52c56d3a9..76f7875cd 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "@graasp/chatbox": "github:graasp/graasp-chatbox.git", "@graasp/query-client": "github:graasp/graasp-query-client.git", "@graasp/translations": "github:graasp/graasp-translations.git", - "@graasp/ui": "github:graasp/graasp-ui.git", + "@graasp/ui": "github:graasp/graasp-ui.git#105/downloadButton", "@graasp/utils": "github:graasp/graasp-utils.git", "@material-ui/core": "4.12.3", "@material-ui/icons": "5.0.0-beta.4", diff --git a/src/components/main/Item.js b/src/components/main/Item.js index ebc0044bf..b62a9cfc8 100644 --- a/src/components/main/Item.js +++ b/src/components/main/Item.js @@ -1,6 +1,7 @@ -import React, { useContext } from 'react'; +import React, { useContext, useEffect } from 'react'; import PropTypes from 'prop-types'; -import { Card as GraaspCard, Thumbnail } from '@graasp/ui'; +import { Card as GraaspCard, Thumbnail, DownloadButton } from '@graasp/ui'; +import { MUTATION_KEYS } from '@graasp/query-client'; import truncate from 'lodash.truncate'; import { makeStyles } from '@material-ui/core/styles'; import { useTranslation } from 'react-i18next'; @@ -18,7 +19,7 @@ import FavoriteButton from '../common/FavoriteButton'; import PinButton from '../common/PinButton'; import { CurrentUserContext } from '../context/CurrentUserContext'; import { buildItemPath } from '../../config/paths'; -import { hooks } from '../../config/queryClient'; +import { hooks, useMutation } from '../../config/queryClient'; import HideButton from '../common/HideButton'; const NameWrapper = @@ -65,6 +66,28 @@ const Item = ({ item, memberships }) => { memberId: member?.get('id'), }); + const { + mutate: downloadItem, + data, + isSuccess, + isLoading: isDownloading, + } = useMutation(MUTATION_KEYS.EXPORT_ZIP); + + useEffect(() => { + if (isSuccess) { + const url = window.URL.createObjectURL(new Blob([data])); + const link = document.createElement('a'); + link.href = url; + link.setAttribute('download', `${item.id}.zip`); + document.body.appendChild(link); + link.click(); + } + }, [data, isSuccess, item]); + + const handleDownload = () => { + downloadItem(item.id); + }; + const Actions = ( <> {!member.isEmpty() && } @@ -73,6 +96,10 @@ const Item = ({ item, memberships }) => { + )} diff --git a/src/components/table/ActionsCellRenderer.js b/src/components/table/ActionsCellRenderer.js index e6af0faa1..4b0005ea6 100644 --- a/src/components/table/ActionsCellRenderer.js +++ b/src/components/table/ActionsCellRenderer.js @@ -1,6 +1,8 @@ import PropTypes from 'prop-types'; import { List } from 'immutable'; import React, { useEffect, useState } from 'react'; +import { DownloadButton } from '@graasp/ui'; +import { MUTATION_KEYS } from '@graasp/query-client'; import EditButton from '../common/EditButton'; import ItemMenu from '../main/ItemMenu'; import FavoriteButton from '../common/FavoriteButton'; @@ -10,12 +12,31 @@ import { isItemUpdateAllowedForUser, } from '../../utils/membership'; import HideButton from '../common/HideButton'; +import { useMutation } from '../../config/queryClient'; // items and memberships match by index const ActionsCellRenderer = ({ memberships, items, member }) => { const ChildComponent = ({ data: item }) => { const [canEdit, setCanEdit] = useState(false); + const { + mutate: downloadItem, + content, + isSuccess, + isLoading: isDownloading, + } = useMutation(MUTATION_KEYS.EXPORT_ZIP); + + useEffect(() => { + if (isSuccess) { + const url = window.URL.createObjectURL(new Blob([content])); + const link = document.createElement('a'); + link.href = url; + link.setAttribute('download', `${item.id}.zip`); + document.body.appendChild(link); + link.click(); + } + }, [content, isSuccess, item]); + useEffect(() => { if (items && memberships && !memberships.isEmpty() && !items.isEmpty()) { setCanEdit( @@ -40,6 +61,10 @@ const ActionsCellRenderer = ({ memberships, items, member }) => { return ; }; + const handleDownload = () => { + downloadItem(item.id); + }; + const renderEditorActions = () => { if (!canEdit) { return null; @@ -50,6 +75,10 @@ const ActionsCellRenderer = ({ memberships, items, member }) => { + ); }; @@ -63,7 +92,7 @@ const ActionsCellRenderer = ({ memberships, items, member }) => { ); }; ChildComponent.propTypes = { - data: PropTypes.shape({}).isRequired, + data: PropTypes.shape({ id: PropTypes.string.isRequired }).isRequired, }; return ChildComponent; }; diff --git a/src/config/constants.js b/src/config/constants.js index 9cc97dc5e..f43baeed0 100644 --- a/src/config/constants.js +++ b/src/config/constants.js @@ -171,7 +171,7 @@ export const ITEMS_TABLE_CONTAINER_HEIGHT = '60vh'; export const DRAG_ICON_SIZE = 18; -export const ACTION_CELL_WIDTH = 230; +export const ACTION_CELL_WIDTH = 300; export const THUMBNAIL_ASPECT = 1; export const THUMBNAIL_EXTENSION = 'image/jpeg'; export const THUMBNAIL_SETTING_MAX_WIDTH = 300; diff --git a/src/config/messages.js b/src/config/messages.js index 989140a69..3f59b0528 100644 --- a/src/config/messages.js +++ b/src/config/messages.js @@ -47,3 +47,5 @@ export const IMPORT_ZIP_FAILURE_MESSAGE = 'An error occurred while importing The ZIP archive.'; export const IMPORT_ZIP_PROGRESS_MESSAGE = 'The ZIP is being processed. Please wait a moment.'; +export const EXPORT_ZIP_FAILURE_MESSAGE = + 'An error occurred while downloading the item as ZIP archive. Please try again later.'; diff --git a/src/middlewares/notifier.js b/src/middlewares/notifier.js new file mode 100644 index 000000000..54120fa11 --- /dev/null +++ b/src/middlewares/notifier.js @@ -0,0 +1,280 @@ +import { toast } from 'react-toastify'; +import { routines } from '@graasp/query-client'; +import i18n from '../config/i18n'; +import { + COPY_ITEMS_ERROR_MESSAGE, + COPY_ITEMS_SUCCESS_MESSAGE, + CREATE_ITEM_ERROR_MESSAGE, + CREATE_ITEM_SUCCESS_MESSAGE, + DELETE_ITEMS_ERROR_MESSAGE, + DELETE_ITEMS_SUCCESS_MESSAGE, + EDIT_ITEM_ERROR_MESSAGE, + EDIT_ITEM_SUCCESS_MESSAGE, + MOVE_ITEMS_ERROR_MESSAGE, + MOVE_ITEMS_SUCCESS_MESSAGE, + SHARE_ITEM_ERROR_MESSAGE, + SHARE_ITEM_SUCCESS_MESSAGE, + UPLOAD_FILES_ERROR_MESSAGE, + UPLOAD_FILES_SUCCESS_MESSAGE, + UPLOAD_FILES_PROGRESS_MESSAGE, + SIGN_OUT_ERROR_MESSAGE, + SIGN_OUT_SUCCESS_MESSAGE, + POST_ITEM_TAG_ERROR_MESSAGE, + DELETE_ITEM_TAG_ERROR_MESSAGE, + ITEM_LOGIN_SIGN_IN_ERROR_MESSAGE, + EDIT_MEMBER_ERROR_MESSAGE, + EDIT_MEMBER_SUCCESS_MESSAGE, + COPY_MEMBER_ID_TO_CLIPBOARD_SUCCESS_MESSAGE, + COPY_MEMBER_ID_TO_CLIPBOARD_ERROR_MESSAGE, + EDIT_ITEM_MEMBERSHIP_ERROR_MESSAGE, + DELETE_ITEM_MEMBERSHIP_ERROR_MESSAGE, + EDIT_ITEM_MEMBERSHIP_SUCCESS_MESSAGE, + DELETE_ITEM_MEMBERSHIP_SUCCESS_MESSAGE, + POST_ITEM_FLAG_ERROR_MESSAGE, + POST_ITEM_FLAG_SUCCESS_MESSAGE, + COPY_ITEM_LINK_TO_CLIPBOARD_SUCCESS_MESSAGE, + COPY_ITEM_LINK_TO_CLIPBOARD_ERROR_MESSAGE, + RECYCLE_ITEMS_SUCCESS_MESSAGE, + RECYCLE_ITEMS_ERROR_MESSAGE, + RESTORE_ITEMS_ERROR_MESSAGE, + RESTORE_ITEMS_SUCCESS_MESSAGE, + UPLOAD_ITEM_THUMBNAIL_SUCCESS_MESSAGE, + UPLOAD_ITEM_THUMBNAIL_FAILURE_MESSAGE, + UPLOAD_AVATAR_FAILURE_MESSAGE, + UPLOAD_AVATAR_SUCCESS_MESSAGE, + IMPORT_ZIP_SUCCESS_MESSAGE, + IMPORT_ZIP_FAILURE_MESSAGE, + IMPORT_ZIP_PROGRESS_MESSAGE, + EXPORT_ZIP_FAILURE_MESSAGE, +} from '../config/messages'; +import { + COPY_ITEM_LINK_TO_CLIPBOARD, + COPY_MEMBER_ID_TO_CLIPBOARD, +} from '../types/clipboard'; + +const { + createItemRoutine, + deleteItemsRoutine, + deleteItemRoutine, + moveItemsRoutine, + copyItemsRoutine, + editItemRoutine, + shareItemRoutine, + uploadFileRoutine, + signOutRoutine, + postItemTagRoutine, + deleteItemTagRoutine, + postItemLoginRoutine, + editMemberRoutine, + editItemMembershipRoutine, + deleteItemMembershipRoutine, + postItemFlagRoutine, + recycleItemsRoutine, + restoreItemsRoutine, + uploadItemThumbnailRoutine, + uploadAvatarRoutine, + importZipRoutine, + downloadItemRoutine, +} = routines; + +export default ({ type, payload }) => { + let message = null; + switch (type) { + // error messages + case editItemMembershipRoutine.FAILURE: { + message = EDIT_ITEM_MEMBERSHIP_ERROR_MESSAGE; + break; + } + case deleteItemMembershipRoutine.FAILURE: { + message = DELETE_ITEM_MEMBERSHIP_ERROR_MESSAGE; + break; + } + case COPY_MEMBER_ID_TO_CLIPBOARD.FAILURE: { + message = COPY_MEMBER_ID_TO_CLIPBOARD_ERROR_MESSAGE; + break; + } + case editMemberRoutine.FAILURE: { + message = EDIT_MEMBER_ERROR_MESSAGE; + break; + } + case createItemRoutine.FAILURE: { + message = CREATE_ITEM_ERROR_MESSAGE; + break; + } + case deleteItemsRoutine.FAILURE: + case deleteItemRoutine.FAILURE: { + message = DELETE_ITEMS_ERROR_MESSAGE; + break; + } + case moveItemsRoutine.FAILURE: { + message = MOVE_ITEMS_ERROR_MESSAGE; + break; + } + case copyItemsRoutine.FAILURE: { + message = COPY_ITEMS_ERROR_MESSAGE; + break; + } + case editItemRoutine.FAILURE: { + message = EDIT_ITEM_ERROR_MESSAGE; + break; + } + case shareItemRoutine.FAILURE: { + message = SHARE_ITEM_ERROR_MESSAGE; + break; + } + case uploadFileRoutine.FAILURE: { + message = UPLOAD_FILES_ERROR_MESSAGE; + break; + } + case signOutRoutine.FAILURE: { + message = SIGN_OUT_ERROR_MESSAGE; + break; + } + case postItemTagRoutine.FAILURE: { + message = POST_ITEM_TAG_ERROR_MESSAGE; + break; + } + case deleteItemTagRoutine.FAILURE: { + message = DELETE_ITEM_TAG_ERROR_MESSAGE; + break; + } + case postItemLoginRoutine.FAILURE: { + message = ITEM_LOGIN_SIGN_IN_ERROR_MESSAGE; + break; + } + case postItemFlagRoutine.FAILURE: { + message = POST_ITEM_FLAG_ERROR_MESSAGE; + break; + } + case COPY_ITEM_LINK_TO_CLIPBOARD.FAILURE: { + message = COPY_ITEM_LINK_TO_CLIPBOARD_ERROR_MESSAGE; + break; + } + case recycleItemsRoutine.FAILURE: { + message = RECYCLE_ITEMS_ERROR_MESSAGE; + break; + } + case restoreItemsRoutine.FAILURE: { + message = RESTORE_ITEMS_ERROR_MESSAGE; + break; + } + case uploadItemThumbnailRoutine.FAILURE: { + message = UPLOAD_ITEM_THUMBNAIL_FAILURE_MESSAGE; + break; + } + case uploadAvatarRoutine.FAILURE: { + message = UPLOAD_AVATAR_FAILURE_MESSAGE; + break; + } + case importZipRoutine.FAILURE: { + message = IMPORT_ZIP_FAILURE_MESSAGE; + break; + } + case downloadItemRoutine.FAILURE: { + message = EXPORT_ZIP_FAILURE_MESSAGE; + break; + } + // success messages + case editMemberRoutine.SUCCESS: { + message = EDIT_MEMBER_SUCCESS_MESSAGE; + break; + } + case createItemRoutine.SUCCESS: { + message = CREATE_ITEM_SUCCESS_MESSAGE; + break; + } + case deleteItemsRoutine.SUCCESS: + case deleteItemRoutine.SUCCESS: { + message = DELETE_ITEMS_SUCCESS_MESSAGE; + break; + } + case moveItemsRoutine.SUCCESS: { + message = MOVE_ITEMS_SUCCESS_MESSAGE; + break; + } + case copyItemsRoutine.SUCCESS: { + message = COPY_ITEMS_SUCCESS_MESSAGE; + break; + } + case editItemRoutine.SUCCESS: { + message = EDIT_ITEM_SUCCESS_MESSAGE; + break; + } + case shareItemRoutine.SUCCESS: { + message = SHARE_ITEM_SUCCESS_MESSAGE; + break; + } + case uploadFileRoutine.SUCCESS: { + message = UPLOAD_FILES_SUCCESS_MESSAGE; + break; + } + case signOutRoutine.SUCCESS: { + message = SIGN_OUT_SUCCESS_MESSAGE; + break; + } + case COPY_MEMBER_ID_TO_CLIPBOARD.SUCCESS: { + message = COPY_MEMBER_ID_TO_CLIPBOARD_SUCCESS_MESSAGE; + break; + } + case editItemMembershipRoutine.SUCCESS: { + message = EDIT_ITEM_MEMBERSHIP_SUCCESS_MESSAGE; + break; + } + case deleteItemMembershipRoutine.SUCCESS: { + message = DELETE_ITEM_MEMBERSHIP_SUCCESS_MESSAGE; + break; + } + case postItemFlagRoutine.SUCCESS: { + message = POST_ITEM_FLAG_SUCCESS_MESSAGE; + break; + } + case COPY_ITEM_LINK_TO_CLIPBOARD.SUCCESS: { + message = COPY_ITEM_LINK_TO_CLIPBOARD_SUCCESS_MESSAGE; + break; + } + case recycleItemsRoutine.SUCCESS: { + message = RECYCLE_ITEMS_SUCCESS_MESSAGE; + break; + } + case restoreItemsRoutine.SUCCESS: { + message = RESTORE_ITEMS_SUCCESS_MESSAGE; + break; + } + case uploadItemThumbnailRoutine.SUCCESS: { + message = UPLOAD_ITEM_THUMBNAIL_SUCCESS_MESSAGE; + break; + } + case uploadAvatarRoutine.SUCCESS: { + message = UPLOAD_AVATAR_SUCCESS_MESSAGE; + break; + } + case importZipRoutine.SUCCESS: { + message = IMPORT_ZIP_SUCCESS_MESSAGE; + break; + } + + // progress messages + // todo: this might be handled differently + case uploadFileRoutine.REQUEST: { + toast.info(i18n.t(UPLOAD_FILES_PROGRESS_MESSAGE)); + break; + } + case uploadItemThumbnailRoutine.REQUEST: { + toast.info(i18n.t(UPLOAD_FILES_PROGRESS_MESSAGE)); + break; + } + case importZipRoutine.REQUEST: { + toast.info(i18n.t(IMPORT_ZIP_PROGRESS_MESSAGE)); + break; + } + default: + } + // error notification + if (payload?.error && message) { + toast.error(i18n.t(message)); + } + // success notification + else if (message) { + toast.success(i18n.t(message)); + } +}; diff --git a/yarn.lock b/yarn.lock index 847ddbdcb..44ede463e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1924,9 +1924,9 @@ __metadata: languageName: node linkType: hard -"@graasp/ui@github:graasp/graasp-ui.git": +"@graasp/ui@github:graasp/graasp-ui.git#105/downloadButton": version: 0.2.0 - resolution: "@graasp/ui@https://github.com/graasp/graasp-ui.git#commit=24d2a4becb962236a768b9fb8e9a42f78580aee1" + resolution: "@graasp/ui@https://github.com/graasp/graasp-ui.git#commit=833577607913ed642c57bba03f0a01ec979f81b3" dependencies: "@graasp/utils": "github:graasp/graasp-utils.git" clsx: 1.1.1 @@ -1946,7 +1946,7 @@ __metadata: i18next: 21.3.1 react: ^16.13.1 react-dom: 16.13.1 - checksum: 03db9261ae6a180410dd32d63189eeff8dc394f183290b2ce46aecf85bf382424384c70c729fda7a4a17d160a3ab1a6db7b506fb0f913d677871c004af272359 + checksum: 91f18b4fb37abba4c9d605e02adeb72c9a9dc9f4f7f1a48a4f7a297789047ea8c59ea4fa88a9b15b915e687b9ba9bb8a26888114bc0cf75c6c9e60751fce28ef languageName: node linkType: hard @@ -9875,7 +9875,7 @@ __metadata: "@graasp/chatbox": "github:graasp/graasp-chatbox.git" "@graasp/query-client": "github:graasp/graasp-query-client.git" "@graasp/translations": "github:graasp/graasp-translations.git" - "@graasp/ui": "github:graasp/graasp-ui.git" + "@graasp/ui": "github:graasp/graasp-ui.git#105/downloadButton" "@graasp/utils": "github:graasp/graasp-utils.git" "@graasp/websockets": "github:graasp/graasp-websockets.git#master" "@material-ui/core": 4.12.3