mirror of
https://github.com/nextcloud/server.git
synced 2026-06-09 00:32:29 -04:00
feat(trashbin): Allow emptying trash
Signed-off-by: Christopher Ng <chrng8@gmail.com>
This commit is contained in:
parent
0af875d713
commit
943023a3f4
3 changed files with 125 additions and 1 deletions
109
apps/files_trashbin/src/fileListActions/emptyTrashAction.ts
Normal file
109
apps/files_trashbin/src/fileListActions/emptyTrashAction.ts
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { Node } from '@nextcloud/files'
|
||||
|
||||
import PQueue from 'p-queue'
|
||||
import { FileListAction } from '@nextcloud/files'
|
||||
import {
|
||||
DialogSeverity,
|
||||
getDialogBuilder,
|
||||
showError,
|
||||
showInfo,
|
||||
showSuccess,
|
||||
TOAST_PERMANENT_TIMEOUT,
|
||||
} from '@nextcloud/dialogs'
|
||||
|
||||
import { deleteNode } from '../../../files/src/actions/deleteUtils.ts'
|
||||
import { logger } from '../logger.ts'
|
||||
|
||||
type Toast = ReturnType<typeof showInfo>
|
||||
|
||||
const queue = new PQueue({ concurrency: 5 })
|
||||
|
||||
const showLoadingToast = (): null | Toast => {
|
||||
const message = t('files_trashbin', 'Deleting files…')
|
||||
let toast: null | Toast = null
|
||||
toast = showInfo(
|
||||
`<span class="icon icon-loading-small toast-loading-icon"></span> ${message}`,
|
||||
{
|
||||
isHTML: true,
|
||||
timeout: TOAST_PERMANENT_TIMEOUT,
|
||||
onRemove: () => {
|
||||
toast?.hideToast()
|
||||
toast = null
|
||||
},
|
||||
},
|
||||
)
|
||||
return toast
|
||||
}
|
||||
|
||||
const emptyTrash = async (nodes: Node[]) => {
|
||||
const promises = nodes.map((node) => {
|
||||
const { promise, resolve, reject } = Promise.withResolvers<void>()
|
||||
queue.add(async () => {
|
||||
try {
|
||||
await deleteNode(node)
|
||||
resolve()
|
||||
} catch (error) {
|
||||
logger.error('Failed to delete node', { error, node })
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
return promise
|
||||
})
|
||||
|
||||
const toast = showLoadingToast()
|
||||
const results = await Promise.allSettled(promises)
|
||||
if (results.some((result) => result.status === 'rejected')) {
|
||||
toast?.hideToast()
|
||||
showError(t('files_trashbin', 'Failed to delete all previously deleted files'))
|
||||
return
|
||||
}
|
||||
toast?.hideToast()
|
||||
showSuccess(t('files_trashbin', 'Permanently deleted all previously deleted files'))
|
||||
}
|
||||
|
||||
export const emptyTrashAction = new FileListAction({
|
||||
id: 'empty-trash',
|
||||
|
||||
displayName: () => t('files_trashbin', 'Empty deleted files'),
|
||||
order: 0,
|
||||
|
||||
enabled: (view, nodes, { folder }) => {
|
||||
if (view.id !== 'trashbin') {
|
||||
return false
|
||||
}
|
||||
return nodes.length > 0 && folder.path === '/'
|
||||
},
|
||||
|
||||
exec: async (view, nodes) => {
|
||||
const dialog = getDialogBuilder(t('files_trashbin', 'Confirm permanent deletion'))
|
||||
.setSeverity(DialogSeverity.Warning)
|
||||
// TODO Add note for groupfolders
|
||||
.setText(t('files_trashbin', 'Are you sure you want to permanently delete all previously deleted files? This cannot be undone.'))
|
||||
.setButtons([
|
||||
{
|
||||
label: t('files_trashbin', 'Cancel'),
|
||||
type: 'secondary',
|
||||
callback: () => {},
|
||||
},
|
||||
{
|
||||
label: t('files_trashbin', 'Empty deleted files'),
|
||||
type: 'error',
|
||||
callback: () => {
|
||||
emptyTrash(nodes)
|
||||
},
|
||||
},
|
||||
])
|
||||
.build()
|
||||
|
||||
try {
|
||||
await dialog.show()
|
||||
} catch (error) {
|
||||
// Allow throw on dialog close
|
||||
}
|
||||
},
|
||||
})
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
import './trashbin.scss'
|
||||
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import { View, getNavigation, registerFileListAction } from '@nextcloud/files'
|
||||
import DeleteSvg from '@mdi/svg/svg/delete.svg?raw'
|
||||
|
||||
import { getContents } from './services/trashbin'
|
||||
|
|
@ -13,7 +14,8 @@ import { columns } from './columns.ts'
|
|||
|
||||
// Register restore action
|
||||
import './actions/restoreAction'
|
||||
import { View, getNavigation } from '@nextcloud/files'
|
||||
|
||||
import { emptyTrashAction } from './fileListActions/emptyTrashAction.ts'
|
||||
|
||||
const Navigation = getNavigation()
|
||||
Navigation.register(new View({
|
||||
|
|
@ -34,3 +36,5 @@ Navigation.register(new View({
|
|||
|
||||
getContents,
|
||||
}))
|
||||
|
||||
registerFileListAction(emptyTrashAction)
|
||||
|
|
|
|||
11
apps/files_trashbin/src/logger.ts
Normal file
11
apps/files_trashbin/src/logger.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { getLoggerBuilder } from '@nextcloud/logger'
|
||||
|
||||
export const logger = getLoggerBuilder()
|
||||
.setApp('files_trashbin')
|
||||
.detectUser()
|
||||
.build()
|
||||
Loading…
Reference in a new issue