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

[VO-740] feat(nextcloud): Show trash #3187

Merged
merged 9 commits into from
Jul 22, 2024
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
"classnames": "2.3.1",
"cozy-bar": "^12.3.0",
"cozy-ci": "0.5.2",
"cozy-client": "^48.9.0",
"cozy-client": "^48.11.0",
"cozy-client-js": "0.20.0",
"cozy-device-helper": "^2.7.0",
"cozy-doctypes": "1.85.4",
Expand Down
19 changes: 13 additions & 6 deletions src/components/MoreMenu.jsx → src/components/MoreMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import React, { useState, useCallback, useRef } from 'react'
import React, { useState, useCallback, useRef, RefObject, FC } from 'react'

import ActionsMenu from 'cozy-ui/transpiled/react/ActionsMenu'
import { Action } from 'cozy-ui/transpiled/react/ActionsMenu/Actions'

import { BarRightOnMobile } from 'components/Bar'
import { File } from './FolderPicker/types'
import MoreButton from 'components/Button/MoreButton'

interface MoreMenuProps {
actions: Record<string, Action>[]
docs?: File[]
disabled?: boolean
}

/**
* Renders a MoreMenu component.
*
Expand All @@ -13,14 +20,14 @@ import MoreButton from 'components/Button/MoreButton'
* @param disabled - Indicates whether the menu is disabled.
* @returns The rendered MoreMenu component.
*/
const MoreMenu = ({ actions, docs, disabled }) => {
const MoreMenu: FC<MoreMenuProps> = ({ actions, docs = [], disabled }) => {
const [isMenuOpened, setMenuOpened] = useState(false)
const moreButtonRef = useRef(null)
const moreButtonRef: RefObject<HTMLDivElement> = useRef(null)
const openMenu = useCallback(() => setMenuOpened(true), [setMenuOpened])
const closeMenu = useCallback(() => setMenuOpened(false), [setMenuOpened])

return (
<BarRightOnMobile>
<>
<div ref={moreButtonRef}>
<MoreButton onClick={openMenu} disabled={disabled} />
</div>
Expand All @@ -38,7 +45,7 @@ const MoreMenu = ({ actions, docs, disabled }) => {
}}
/>
) : null}
</BarRightOnMobile>
</>
)
}

Expand Down
3 changes: 2 additions & 1 deletion src/components/TrashedBanner.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ const TrashedBanner = ({ fileId, isPublic }) => {
setDestroyConfirmationDisplayed(false)
}

const handleDestroyConfirm = () => {
const handleDestroyConfirm = async () => {
await client?.collection('io.cozy.files').deleteFilePermanently(fileId)
showAlert({
message: t('TrashedBanner.destroySuccess'),
severity: 'secondary'
Expand Down
5 changes: 3 additions & 2 deletions src/declarations.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,11 @@ declare module 'cozy-ui/transpiled/react/ActionsMenu/Actions' {
displayCondition?: (
docs: import('cozy-client/types/types').IOCozyFile[]
) => boolean
disabled?: (docs: import('cozy-client/types/types').IOCozyFile[]) => boolean
action?: (
docs: import('cozy-client/types/types').IOCozyFile[],
opts: { handleAction: HandleActionCallback }
) => Promise<void>
) => Promise<void> | void
Component: ForwardRefExoticComponent<RefAttributes<React.ComponentType>>
}

Expand All @@ -99,7 +100,7 @@ declare module 'cozy-ui/transpiled/react/ActionsMenu/Actions' {
export function makeActions(
arg1: ((props?: T) => Action)[],
T
): Record<string, () => Action>
): Record<string, Action>[]
}

declare module 'cozy-sharing' {
Expand Down
25 changes: 18 additions & 7 deletions src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@
"menu_open_cozy": "Open in my Cozy",
"menu_create_note": "Note",
"menu_create_shortcut": "Shortcut",
"empty_trash": "Empty trash",
"share": "Share",
"trash": "Remove",
"leave": "Leave shared folder & delete it",
Expand Down Expand Up @@ -279,19 +278,24 @@
"cancel": "Cancel",
"delete": "Remove"
},
"emptytrashconfirmation": {
"EmptyTrashConfirm": {
"title": "Permanently delete?",
"forbidden": "You won't be able to access these files anymore.",
"restore": "You won't be able to restore these files if you didn't make a backup.",
"cancel": "Cancel",
"delete": "Delete all"
"delete": "Delete all",
"processing": "Your trash is being emptied. This might take a few moments.",
"success": "The trash has been emptied.",
"error": "An error occurred, please try again."
},
"DestroyConfirm": {
"title": "Delete %{filename}? |||| Delete %{smart_count} %{type}?",
"forbidden": "You won't be able to access this %{type} anymore. |||| You won't be able to access these %{type} anymore.",
"restore": "You won't be able to restore this %{type} if you didn't make a backup. |||| You won't be able to restore these %{type} if you didn't make a backup.",
"cancel": "Cancel",
"delete": "Delete permanently"
"delete": "Delete permanently",
"success": "The %{type} has been deleted permanently. |||| %{smart_count} %{type} have been deleted permanently.",
"error": "An error occurred, please try again."
},
"quotaalert": {
"title": "Your disk space is full :(",
Expand Down Expand Up @@ -332,8 +336,6 @@
"restore_file_success": "The selection has been successfully restored.",
"trash_file_success": "The selection has been moved to the Trash.",
"destroy_file_success": "The selection has been deleted permanently.",
"empty_trash_progress": "Your trash is being emptied. This might take a few moments.",
"empty_trash_success": "The trash has been emptied.",
"folder_name": "The element %{folderName} already exists, please choose a new name.",
"file_name": "The element %{fileName} already exists, please choose a new name.",
"file_name_missing": "The file name is missing, please choose a new name.",
Expand Down Expand Up @@ -792,7 +794,8 @@
"text": "We have not found anything at this address. This may be a typing error."
},
"NextcloudBreadcrumb": {
"root": "Shared Drives"
"root": "Shared Drives",
"trash": "Trash"
},
"NextcloudToolbar": {
"share": "Share"
Expand Down Expand Up @@ -828,5 +831,13 @@
"add": "%{filename} has been added to favorites |||| These items have been added to favorites",
"remove": "%{filename} has been removed from favorites |||| These items have been removed from favorites"
}
},
"TrashToolbar": {
"emptyTrash": "Empty trash"
},
"RestoreNextcloudFile": {
"label": "Restore",
"success": "The item has been restored",
"error": "An error occurred, please try again."
}
}
25 changes: 18 additions & 7 deletions src/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@
"menu_open_cozy": "Ouvrir dans mon Cozy",
"menu_create_note": "Note",
"menu_create_shortcut": "Raccourci",
"empty_trash": "Vider la corbeille",
"share": "Partager",
"trash": "Supprimer",
"leave": "Quitter le dossier partagé et le supprimer",
Expand Down Expand Up @@ -280,19 +279,24 @@
"cancel": "Annuler",
"delete": "Supprimer"
},
"emptytrashconfirmation": {
"EmptyTrashConfirm": {
"title": "Supprimer définitivement ?",
"forbidden": "Vous ne pourrez plus accéder à ces fichiers.",
"restore": "Vous ne pourrez pas restaurer ces fichiers.",
"cancel": "Annuler",
"delete": "Tout supprimer"
"delete": "Tout supprimer",
"processing": "Votre corbeille est en train de se vider. Cela peut prendre quelques instants.",
"success": "La corbeille a été vidée.",
"error": "Une erreur est survenue, merci de réessayer."
},
"DestroyConfirm": {
"title": "Supprimer %{filename} ? |||| Supprimer %{smart_count} %{type} ?",
"forbidden": "Vous ne pourrez plus accéder à ce %{type}. |||| Vous ne pourrez plus accéder à ces %{type}.",
"restore": "Vous ne pourrez pas restaurer ce %{type}. |||| Vous ne pourrez pas restaurer ces %{type}.",
"cancel": "Annuler",
"delete": "Supprimer définitivement"
"delete": "Supprimer définitivement",
"success": "Le %{type} a été supprimé définitivement. |||| %{smart_count} %{type} ont été supprimés définitivement.",
"error": "Une erreur est survenue, merci de réessayer."
},
"quotaalert": {
"title": "Votre espace disque est plein :(",
Expand Down Expand Up @@ -333,8 +337,6 @@
"restore_file_success": "La sélection a été restaurée avec succès.",
"trash_file_success": "La sélection a été déplacée dans la Corbeille.",
"destroy_file_success": "La sélection a été supprimée définitivement.",
"empty_trash_progress": " Votre corbeille est en train de se vider. Cela peut prendre quelques instants.",
"empty_trash_success": "La corbeille a été vidée.",
"folder_name": "L'élément %{folderName} existe déjà, merci de choisir un nouveau nom.",
"file_name": "L'élément %{fileName} existe déjà, utilisez un nouveau nom",
"file_name_missing": "Le nom du fichier est manquant, veuillez choisir un nouveau nom.",
Expand Down Expand Up @@ -793,7 +795,8 @@
"text": "Nous n’avons trouvé aucun élément à cette adresse. Il s’agit peut-être d’une erreur de frappe."
},
"NextcloudBreadcrumb": {
"root": "Drive partagés"
"root": "Drive partagés",
"trash": "Corbeille"
},
"NextcloudToolbar": {
"share": "Partager"
Expand Down Expand Up @@ -829,5 +832,13 @@
"add": "%{filename} a été ajouté aux favoris |||| Ces éléments ont été ajoutés aux favoris",
"remove": "%{filename} a été retiré des favoris |||| Ces éléments ont été retirés des favoris"
}
},
"TrashToolbar": {
"emptyTrash": "Vider la corbeille"
},
"RestoreNextcloudFile": {
"label": "Restaurer",
"success": "L'élément a bien été restauré",
"error": "Une erreur est survenue, merci de réessayer."
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
import React, { forwardRef } from 'react'

import { Action } from 'cozy-ui/transpiled/react/ActionsMenu/Actions'
import ActionsMenuItem from 'cozy-ui/transpiled/react/ActionsMenu/ActionsMenuItem'
import Icon from 'cozy-ui/transpiled/react/Icon'
import CheckSquareIcon from 'cozy-ui/transpiled/react/Icons/CheckSquare'
import ListItemIcon from 'cozy-ui/transpiled/react/ListItemIcon'
import ListItemText from 'cozy-ui/transpiled/react/ListItemText'

export const selectable = ({ t, showSelectionBar }) => {
interface selectableProps {
t: (key: string, options?: Record<string, unknown>) => string
showSelectionBar: () => void
}

export const selectable = ({
t,
showSelectionBar
}: selectableProps): Action => {
const label = t('toolbar.menu_select')
const icon = CheckSquareIcon

return {
name: 'selectable',
label,
icon,
action: () => {
action: (): void => {
showSelectionBar()
},
Component: forwardRef(function Selectable(props, ref) {
Expand Down
33 changes: 0 additions & 33 deletions src/modules/actions/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import { isEncryptedFolder, isEncryptedFile } from 'lib/encryption'
import { navigateToModal } from 'modules/actions/helpers'
import DeleteConfirm from 'modules/drive/DeleteConfirm'
import { startRenamingAsync } from 'modules/drive/rename'
import DestroyConfirm from 'modules/trash/components/DestroyConfirm'

export { share } from './share'

Expand Down Expand Up @@ -240,35 +239,3 @@ export const restore = ({ t, refresh, client }) => {
})
}
}

export const destroy = ({ t, pushModal, popModal }) => {
const label = t('SelectionBar.destroy')
const icon = TrashIcon

return {
name: 'destroy',
label,
icon,
action: files =>
pushModal(
<DestroyConfirm
files={files}
onConfirm={popModal}
onCancel={popModal}
/>
),
Component: forwardRef(function Destroy(props, ref) {
return (
<ActionsMenuItem {...props} ref={ref}>
<ListItemIcon>
<Icon icon={icon} color="var(--errorColor)" />
</ListItemIcon>
<ListItemText
primary={label}
primaryTypographyProps={{ color: 'error' }}
/>
</ActionsMenuItem>
)
})
}
}
22 changes: 0 additions & 22 deletions src/modules/actions/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,25 +120,3 @@ export const restoreFiles = async (client, files) => {
await client.collection(DOCTYPE_FILES).restore(file.id)
}
}

export const deleteFilesPermanently = async (client, files) => {
for (const file of files) {
await client.collection(DOCTYPE_FILES).deleteFilePermanently(file.id)
}
}

export const emptyTrash = async (client, { showAlert, t }) => {
showAlert({
message: t('alert.empty_trash_progress'),
severity: 'secondary'
})
try {
await client.collection(DOCTYPE_FILES).emptyTrash()
} catch (err) {
showAlert({ message: t('alert.try_again'), severity: 'error' })
}
showAlert({
message: t('alert.empty_trash_success'),
severity: 'secondary'
})
}
22 changes: 0 additions & 22 deletions src/modules/confirm/Message.jsx

This file was deleted.

3 changes: 1 addition & 2 deletions src/modules/filelist/File.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import {
FileAction,
SharingShortcutBadge
} from './cells'
import { TRASH_DIR_ID } from 'constants/config'
import { ActionMenuWithHeader } from 'modules/actionmenu/ActionMenuWithHeader'
import { extraColumnsPropTypes } from 'modules/certifications'
import { isRenaming, getRenamingFile } from 'modules/drive/rename'
Expand Down Expand Up @@ -118,7 +117,7 @@ const File = ({
// because they are magic folder created by the stack
const canInteractWithFile =
attributes._id !== 'io.cozy.files.shared-drives-dir' &&
attributes._id !== TRASH_DIR_ID
!attributes._id.endsWith('.trash-dir')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why use a literal string instead of a constant here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allows me to manage all the trash from both doctype io.cozy.files and io.cozy.remote.nextcloud.files in one line. You would have prefer something like that :

const canInteractWithFile =
    attributes._id !== SHARED_DRIVES_DIR_ID &&
    attributes._id !== TRASH_DIR_ID &&
    attributes._id !== NEXTCLOUD_TRASH_DIR_ID

Maybe this constant should be provided by cozy-client or cozy-stack-client as they are already set as type here.

Note: I tend to put the doctype directly because I find them quicker to search globally in a project.


return (
<TableRow className={filContentRowSelected}>
Expand Down
3 changes: 1 addition & 2 deletions src/modules/filelist/FileThumbnail.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import Spinner from 'cozy-ui/transpiled/react/Spinner'
import useBreakpoints from 'cozy-ui/transpiled/react/providers/Breakpoints'

import IconServer from 'assets/icons/icon-type-server.svg'
import { TRASH_DIR_ID } from 'constants/config'
import FileIcon from 'modules/filelist/FileIcon'
import FileIconMime from 'modules/filelist/FileIconMime'
import { SharingShortcutIcon } from 'modules/filelist/SharingShortcutIcon'
Expand All @@ -32,7 +31,7 @@ const FileThumbnail = ({ file, size, isInSyncFromSharing, isEncrypted }) => {
file.cozyMetadata?.createdByApp === 'nextcloud' &&
flag('drive.show-nextcloud-dev')

if (file._id === TRASH_DIR_ID) {
if (file?._id?.endsWith('.trash-dir')) {
return <Icon icon={TrashDuotoneIcon} size={size ?? 32} />
}

Expand Down
Loading
Loading