Skip to content

Commit 168166d

Browse files
authored
Merge pull request #3001 from nextcloud/save-file-before-download
feat: Save current state of a file before downloading it
2 parents 242c4ba + 8734670 commit 168166d

3 files changed

Lines changed: 67 additions & 12 deletions

File tree

viewer/README.md

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,16 @@ If you want to make your app compatible with this app, you can use the methods p
175175
],
176176

177177
// your vue component view
178-
component: VideoView
178+
component: VideoView,
179+
180+
// optional: callback to be called before download
181+
// useful for saving unsaved changes, validation, logging, etc.
182+
// if not provided, defaults to an empty function
183+
downloadCallback: async (fileInfo) => {
184+
// perform any pre-download operations
185+
// e.g., save current editor state
186+
await saveCurrentDocument(fileInfo)
187+
}
179188
})
180189
```
181190
3. Make sure your script is loaded with `\OCP\Util::addInitScript` so that the handler is registered **before** the viewer is loaded.
@@ -213,7 +222,16 @@ If you want to make your app compatible with this app, you can use the `OCA.View
213222
],
214223

215224
// your vue component view
216-
component: VideoView
225+
component: VideoView,
226+
227+
// optional: callback to be called before download
228+
// useful for saving unsaved changes, validation, logging, etc.
229+
// if not provided, defaults to an empty function
230+
downloadCallback: async (fileInfo) => {
231+
// perform any pre-download operations
232+
// e.g., save current editor state
233+
await saveCurrentDocument(fileInfo)
234+
}
217235
})
218236
```
219237
3. Make sure your script is loaded with `\OCP\Util::addScript` (in contrast to using the API package)!

viewer/src/services/Viewer.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import logger from './logger.js'
1818
* @property {string} group group identifier to combine for navigating to the next/previous files
1919
* @property {?string} theme viewer modal theme (one of 'dark', 'light', 'default')
2020
* @property {boolean} canCompare Indicate support for comparing two files
21+
* @property {?Function} downloadCallback Optional callback to be called before download
2122
*/
2223

2324
/**
@@ -89,6 +90,11 @@ export default class Viewer {
8990
return
9091
}
9192

93+
// Set default empty function for downloadCallback if not provided
94+
if (!handler.downloadCallback) {
95+
handler.downloadCallback = () => {}
96+
}
97+
9298
this._state.handlers.push(handler)
9399
const handledMimes = [
94100
...handler.mimes,

viewer/src/views/Viewer.vue

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -76,15 +76,14 @@
7676
@click="showSidebar">
7777
{{ t('viewer', 'Open sidebar') }}
7878
</NcActionButton>
79-
<NcActionLink v-if="canDownload"
80-
:download="currentFile.basename"
79+
<NcActionButton v-if="canDownload"
8180
:close-after-click="true"
82-
:href="downloadPath">
81+
@click="onDownload">
8382
<template #icon>
8483
<Download :size="20" />
8584
</template>
8685
{{ t('viewer', 'Download') }}
87-
</NcActionLink>
86+
</NcActionButton>
8887
<NcActionButton v-if="canDelete"
8988
:close-after-click="true"
9089
@click="onDelete">
@@ -1006,12 +1005,7 @@ export default defineComponent({
10061005
if (event.key === 's' && event.ctrlKey === true) {
10071006
event.preventDefault()
10081007
if (this.canDownload) {
1009-
const a = document.createElement('a')
1010-
a.href = this.currentFile.source ?? this.currentFile.davPath
1011-
a.download = this.currentFile.basename
1012-
document.body.appendChild(a)
1013-
a.click()
1014-
document.body.removeChild(a)
1008+
this.onDownload()
10151009
}
10161010
}
10171011
},
@@ -1196,6 +1190,43 @@ export default defineComponent({
11961190
this.toggleEditor(true)
11971191
},
11981192
1193+
/**
1194+
* Call handler's downloadCallback before downloading
1195+
*/
1196+
async onDownload() {
1197+
if (!this.canDownload) {
1198+
return
1199+
}
1200+
1201+
// Get the current handler for this file
1202+
const mime = this.currentFile.mime
1203+
const alias = mime?.split('/')[0]
1204+
const handler = this.registeredHandlers[mime] ?? this.registeredHandlers[alias]
1205+
1206+
if (handler?.downloadCallback && typeof handler.downloadCallback === 'function') {
1207+
try {
1208+
logger.debug('Calling handler downloadCallback before download')
1209+
await handler.downloadCallback(this.currentFile)
1210+
} catch (error) {
1211+
logger.error('Failed to execute downloadCallback', { error })
1212+
showError(t('viewer', 'Failed to save file before download'))
1213+
return
1214+
}
1215+
}
1216+
1217+
this.performDownload()
1218+
},
1219+
1220+
performDownload() {
1221+
logger.debug('Performing download', { file: this.currentFile })
1222+
const a = document.createElement('a')
1223+
a.href = this.currentFile.source ?? this.currentFile.davPath
1224+
a.download = this.currentFile.basename
1225+
document.body.appendChild(a)
1226+
a.click()
1227+
document.body.removeChild(a)
1228+
},
1229+
11991230
handleTrapElementsChange(element) {
12001231
this.trapElements.push(element)
12011232
},

0 commit comments

Comments
 (0)