Skip to content

Commit

Permalink
feat(files): add download action
Browse files Browse the repository at this point in the history
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
  • Loading branch information
skjnldsv committed Apr 19, 2023
1 parent 66da58f commit a017988
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 9 deletions.
7 changes: 4 additions & 3 deletions apps/files/src/actions/deleteAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ import TrashCan from '@mdi/svg/svg/trash-can.svg?raw'

import { registerFileAction, FileAction } from '../services/FileAction.ts'
import logger from '../logger.js'
import type { Navigation } from '../services/Navigation.ts'

registerFileAction(new FileAction({
id: 'delete',
displayName(nodes: Node[], view) {
displayName(nodes: Node[], view: Navigation) {
return view.id === 'trashbin'
? t('files_trashbin', 'Delete permanently')
: t('files', 'Delete')
Expand All @@ -57,8 +58,8 @@ registerFileAction(new FileAction({
return false
}
},
async execBatch(nodes: Node[], view) {
return Promise.all(nodes.map(node => this.exec(node, view)))
async execBatch(nodes: Node[], view: Navigation, dir: string) {
return Promise.all(nodes.map(node => this.exec(node, view, dir)))
},

order: 100,
Expand Down
70 changes: 70 additions & 0 deletions apps/files/src/actions/downloadAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* @copyright Copyright (c) 2023 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import { emit } from '@nextcloud/event-bus'
import { Permission, Node } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
import ArrowDown from '@mdi/svg/svg/arrow-down.svg?raw'

import { registerFileAction, FileAction } from '../services/FileAction.ts'
import { generateUrl } from '@nextcloud/router'
import type { Navigation } from '../services/Navigation.ts'

const triggerDownload = function(url: string) {
const hiddenElement = document.createElement('a')
hiddenElement.download = ''
hiddenElement.href = url
hiddenElement.click()
}

registerFileAction(new FileAction({
id: 'download',
displayName: () => t('files', 'Download'),
iconSvgInline: () => ArrowDown,

enabled(nodes: Node[]) {
return nodes.length > 0 && nodes
.map(node => node.permissions)
.every(permission => (permission & Permission.READ) !== 0)
},

async exec(node: Node) {
triggerDownload(node.source)
return null
},
async execBatch(nodes: Node[], view: Navigation, dir: string) {
if (nodes.length === 1) {
this.exec(nodes[0], view, dir)
return [null]
}

const secret = Math.random().toString(36).substring(2)
const url = generateUrl('/apps/files/ajax/download.php?dir={dir}&files={files}&downloadStartSecret={secret}', {
dir,
secret,
files: JSON.stringify(nodes.map(node => node.basename)),
})
triggerDownload(url)
return new Array(nodes.length).fill(null)
},

order: 20,
}))
2 changes: 1 addition & 1 deletion apps/files/src/components/FileEntry.vue
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@ export default Vue.extend({
this.loading = action.id
Vue.set(this.source, '_loading', true)
const success = await action.exec(this.source, this.currentView)
const success = await action.exec(this.source, this.currentView, this.dir)
// If the action returns null, we stay silent
if (success === null) {
Expand Down
6 changes: 5 additions & 1 deletion apps/files/src/components/FilesListHeaderActions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ export default Vue.extend({
},
computed: {
dir() {
// Remove any trailing slash but leave root slash
return (this.$route?.query?.dir || '/').replace(/^(.+)\/$/, '$1')
},
enabledActions() {
return actions
.filter(action => action.execBatch)
Expand Down Expand Up @@ -165,7 +169,7 @@ export default Vue.extend({
})
// Dispatch action execution
const results = await action.execBatch(this.nodes, this.currentView)
const results = await action.execBatch(this.nodes, this.currentView, this.dir)
// Check if all actions returned null
if (results.filter(result => result !== null).length === 0) {
Expand Down
1 change: 1 addition & 0 deletions apps/files/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import './legacy/filelistSearch.js'
import './actions/deleteAction.ts'
import './actions/favoriteAction.ts'
import './actions/sidebarAction.ts'
import './actions/downloadAction.ts'

import processLegacyFilesViews from './legacy/navigationMapper.js'
import registerFavoritesView from './views/favorites.ts'
Expand Down
5 changes: 3 additions & 2 deletions apps/files/src/services/FileAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

import { Node } from '@nextcloud/files'
import logger from '../logger'
import type { Navigation } from './Navigation'

declare global {
interface Window {
Expand All @@ -48,14 +49,14 @@ interface FileActionData {
* @returns true if the action was executed, false otherwise
* @throws Error if the action failed
*/
exec: (file: Node, view) => Promise<boolean|null>,
exec: (file: Node, view: Navigation, dir: string) => Promise<boolean|null>,
/**
* Function executed on multiple files action
* @returns true if the action was executed successfully,
* false otherwise and null if the action is silent/undefined.
* @throws Error if the action failed
*/
execBatch?: (files: Node[], view) => Promise<(boolean|null)[]>
execBatch?: (files: Node[], view: Navigation, dir: string) => Promise<(boolean|null)[]>
/** This action order in the list */
order?: number,
/** Make this action the default */
Expand Down
5 changes: 3 additions & 2 deletions apps/files_trashbin/src/actions/restoreAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import History from '@mdi/svg/svg/history.svg?raw'

import { registerFileAction, FileAction } from '../../../files/src/services/FileAction.ts'
import logger from '../../../files/src/logger.js'
import type { Navigation } from '../../../files/src/services/Navigation.ts'

registerFileAction(new FileAction({
id: 'restore',
Expand Down Expand Up @@ -69,8 +70,8 @@ registerFileAction(new FileAction({
return false
}
},
async execBatch(nodes: Node[], view) {
return Promise.all(nodes.map(node => this.exec(node, view)))
async execBatch(nodes: Node[], view: Navigation, dir: string) {
return Promise.all(nodes.map(node => this.exec(node, view, dir)))
},

order: 1,
Expand Down

0 comments on commit a017988

Please sign in to comment.