mirror of
https://github.com/nextcloud/server.git
synced 2026-05-28 04:32:30 -04:00
feat: Use viewer to open and compare versions
Signed-off-by: Louis Chemineau <louis@chmn.me>
This commit is contained in:
parent
b2589c8f90
commit
bb791a895d
4 changed files with 147 additions and 38 deletions
|
|
@ -19,13 +19,13 @@
|
|||
<div>
|
||||
<NcListItem class="version"
|
||||
:title="versionLabel"
|
||||
:href="downloadURL"
|
||||
:force-display-actions="true"
|
||||
data-files-versions-version>
|
||||
data-files-versions-version
|
||||
@click="click">
|
||||
<template #icon>
|
||||
<div v-if="!(loadPreview || previewLoaded)" class="version__image" />
|
||||
<img v-else-if="isCurrent || version.hasPreview"
|
||||
:src="previewURL"
|
||||
:src="version.previewUrl"
|
||||
alt=""
|
||||
decoding="async"
|
||||
fetchpriority="low"
|
||||
|
|
@ -46,7 +46,7 @@
|
|||
</div>
|
||||
</template>
|
||||
<template #actions>
|
||||
<NcActionButton v-if="enableLabeling"
|
||||
<NcActionButton v-if="enableLabeling"
|
||||
:close-after-click="true"
|
||||
@click="openVersionLabelModal">
|
||||
<template #icon>
|
||||
|
|
@ -54,6 +54,14 @@
|
|||
</template>
|
||||
{{ version.label === '' ? t('files_versions', 'Name this version') : t('files_versions', 'Edit version name') }}
|
||||
</NcActionButton>
|
||||
<NcActionButton v-if="!isCurrent && canView && canCompare"
|
||||
:close-after-click="true"
|
||||
@click="compareVersion">
|
||||
<template #icon>
|
||||
<FileCompare :size="22" />
|
||||
</template>
|
||||
{{ t('files_versions', 'Compare to current version') }}
|
||||
</NcActionButton>
|
||||
<NcActionButton v-if="!isCurrent"
|
||||
:close-after-click="true"
|
||||
@click="restoreVersion">
|
||||
|
|
@ -116,6 +124,7 @@
|
|||
<script>
|
||||
import BackupRestore from 'vue-material-design-icons/BackupRestore.vue'
|
||||
import Download from 'vue-material-design-icons/Download.vue'
|
||||
import FileCompare from 'vue-material-design-icons/FileCompare.vue'
|
||||
import Pencil from 'vue-material-design-icons/Pencil.vue'
|
||||
import Check from 'vue-material-design-icons/Check.vue'
|
||||
import Delete from 'vue-material-design-icons/Delete.vue'
|
||||
|
|
@ -124,7 +133,7 @@ import { NcActionButton, NcActionLink, NcListItem, NcModal, NcButton, NcTextFiel
|
|||
import moment from '@nextcloud/moment'
|
||||
import { translate } from '@nextcloud/l10n'
|
||||
import { joinPaths } from '@nextcloud/paths'
|
||||
import { generateUrl, getRootUrl } from '@nextcloud/router'
|
||||
import { getRootUrl } from '@nextcloud/router'
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
|
||||
export default {
|
||||
|
|
@ -138,6 +147,7 @@ export default {
|
|||
NcTextField,
|
||||
BackupRestore,
|
||||
Download,
|
||||
FileCompare,
|
||||
Pencil,
|
||||
Check,
|
||||
Delete,
|
||||
|
|
@ -184,6 +194,14 @@ export default {
|
|||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
canView: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
canCompare: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -226,20 +244,6 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
previewURL() {
|
||||
if (this.isCurrent) {
|
||||
return generateUrl('/core/preview?fileId={fileId}&c={fileEtag}&x=250&y=250&forceIcon=0&a=0', {
|
||||
fileId: this.fileInfo.id,
|
||||
fileEtag: this.fileInfo.etag,
|
||||
})
|
||||
} else {
|
||||
return this.version.preview
|
||||
}
|
||||
},
|
||||
|
||||
/** @return {string} */
|
||||
formattedDate() {
|
||||
return moment(this.version.mtime).format('LLL')
|
||||
|
|
@ -253,7 +257,7 @@ export default {
|
|||
/** @return {boolean} */
|
||||
enableDeletion() {
|
||||
return this.capabilities.files.version_deletion === true && this.fileInfo.mountType !== 'group'
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openVersionLabelModal() {
|
||||
|
|
@ -276,6 +280,21 @@ export default {
|
|||
deleteVersion() {
|
||||
this.$emit('delete', this.version)
|
||||
},
|
||||
|
||||
click() {
|
||||
if (!this.canView) {
|
||||
window.location = this.downloadURL
|
||||
return
|
||||
}
|
||||
this.$emit('click', { version: this.version })
|
||||
},
|
||||
|
||||
compareVersion() {
|
||||
if (!this.canView) {
|
||||
throw new Error('Cannot compare version of this file')
|
||||
}
|
||||
this.$emit('compare', { version: this.version })
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ export default `<?xml version="1.0"?>
|
|||
<d:getcontentlength />
|
||||
<d:getcontenttype />
|
||||
<d:getlastmodified />
|
||||
<d:getetag />
|
||||
<nc:version-label />
|
||||
<nc:has-preview />
|
||||
</d:prop>
|
||||
|
|
|
|||
|
|
@ -20,25 +20,33 @@
|
|||
*/
|
||||
|
||||
import { getCurrentUser } from '@nextcloud/auth'
|
||||
import { joinPaths } from '@nextcloud/paths'
|
||||
import { generateRemoteUrl, generateUrl } from '@nextcloud/router'
|
||||
import moment from '@nextcloud/moment'
|
||||
|
||||
import { encodeFilePath } from '../../../files/src/utils/fileUtils.js'
|
||||
|
||||
import client from '../utils/davClient.js'
|
||||
import davRequest from '../utils/davRequest.js'
|
||||
import logger from '../utils/logger.js'
|
||||
import { joinPaths } from '@nextcloud/paths'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import moment from '@nextcloud/moment'
|
||||
import path from 'path'
|
||||
|
||||
/**
|
||||
* @typedef {object} Version
|
||||
* @property {string} fileId - The id of the file associated to the version.
|
||||
* @property {string} label - 'Current version' or ''
|
||||
* @property {string} fileName - File name relative to the version DAV endpoint
|
||||
* @property {string} mimeType - Empty for the current version, else the actual mime type of the version
|
||||
* @property {string} filename - File name relative to the version DAV endpoint
|
||||
* @property {string} basename - A base name generated from the mtime
|
||||
* @property {string} mime - Empty for the current version, else the actual mime type of the version
|
||||
* @property {string} etag - Empty for the current version, else the actual mime type of the version
|
||||
* @property {string} size - Human readable size
|
||||
* @property {string} type - 'file'
|
||||
* @property {number} mtime - Version creation date as a timestamp
|
||||
* @property {string} permissions - Only readable: 'R'
|
||||
* @property {boolean} hasPreview - Whether the version has a preview
|
||||
* @property {string} preview - Preview URL of the version
|
||||
* @property {string} previewUrl - Preview URL of the version
|
||||
* @property {string} url - Download URL of the version
|
||||
* @property {string} source - The WebDAV endpoint of the ressource
|
||||
* @property {string|null} fileVersion - The version id, null for the current version
|
||||
*/
|
||||
|
||||
|
|
@ -75,7 +83,7 @@ export async function restoreVersion(version) {
|
|||
logger.debug('Restoring version', { url: version.url })
|
||||
await client.moveFile(
|
||||
`/versions/${getCurrentUser()?.uid}/versions/${version.fileId}/${version.fileVersion}`,
|
||||
`/versions/${getCurrentUser()?.uid}/restore/target`
|
||||
`/versions/${getCurrentUser()?.uid}/restore/target`,
|
||||
)
|
||||
} catch (exception) {
|
||||
logger.error('Could not restore version', { exception })
|
||||
|
|
@ -91,20 +99,39 @@ export async function restoreVersion(version) {
|
|||
* @return {Version}
|
||||
*/
|
||||
function formatVersion(version, fileInfo) {
|
||||
const mtime = moment(version.lastmod).unix() * 1000
|
||||
let previewUrl = ''
|
||||
let filename = ''
|
||||
|
||||
if (mtime === fileInfo.mtime) { // Version is the current one
|
||||
filename = path.join('files', getCurrentUser()?.uid ?? '', fileInfo.path, fileInfo.name)
|
||||
previewUrl = generateUrl('/core/preview?fileId={fileId}&c={fileEtag}&x=250&y=250&forceIcon=0&a=0', {
|
||||
fileId: fileInfo.id,
|
||||
fileEtag: fileInfo.etag,
|
||||
})
|
||||
} else {
|
||||
filename = version.filename
|
||||
previewUrl = generateUrl('/apps/files_versions/preview?file={file}&version={fileVersion}', {
|
||||
file: joinPaths(fileInfo.path, fileInfo.name),
|
||||
fileVersion: version.basename,
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
fileId: fileInfo.id,
|
||||
label: version.props['version-label'],
|
||||
fileName: version.filename,
|
||||
mimeType: version.mime,
|
||||
filename,
|
||||
basename: moment(mtime).format('LLL'),
|
||||
mime: version.mime,
|
||||
etag: `${version.props.getetag}`,
|
||||
size: version.size,
|
||||
type: version.type,
|
||||
mtime: moment(version.lastmod).unix() * 1000,
|
||||
mtime,
|
||||
permissions: 'R',
|
||||
hasPreview: version.props['has-preview'] === 1,
|
||||
preview: generateUrl('/apps/files_versions/preview?file={file}&version={fileVersion}', {
|
||||
file: joinPaths(fileInfo.path, fileInfo.name),
|
||||
fileVersion: version.basename,
|
||||
}),
|
||||
url: joinPaths('/remote.php/dav', version.filename),
|
||||
previewUrl,
|
||||
url: joinPaths('/remote.php/dav', filename),
|
||||
source: generateRemoteUrl('dav') + encodeFilePath(filename),
|
||||
fileVersion: version.basename,
|
||||
}
|
||||
}
|
||||
|
|
@ -115,7 +142,7 @@ function formatVersion(version, fileInfo) {
|
|||
*/
|
||||
export async function setVersionLabel(version, newLabel) {
|
||||
return await client.customRequest(
|
||||
version.fileName,
|
||||
version.filename,
|
||||
{
|
||||
method: 'PROPPATCH',
|
||||
data: `<?xml version="1.0"?>
|
||||
|
|
@ -129,7 +156,7 @@ export async function setVersionLabel(version, newLabel) {
|
|||
</d:prop>
|
||||
</d:set>
|
||||
</d:propertyupdate>`,
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -137,5 +164,5 @@ export async function setVersionLabel(version, newLabel) {
|
|||
* @param {Version} version
|
||||
*/
|
||||
export async function deleteVersion(version) {
|
||||
await client.deleteFile(version.fileName)
|
||||
await client.deleteFile(version.filename)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,11 +19,15 @@
|
|||
<ul data-files-versions-versions-list>
|
||||
<Version v-for="version in orderedVersions"
|
||||
:key="version.mtime"
|
||||
:can-view="canView"
|
||||
:can-compare="canCompare"
|
||||
:load-preview="isActive"
|
||||
:version="version"
|
||||
:file-info="fileInfo"
|
||||
:is-current="version.mtime === fileInfo.mtime"
|
||||
:is-first-version="version.mtime === initialVersionMtime"
|
||||
@click="openVersion"
|
||||
@compare="compareVersion"
|
||||
@restore="handleRestore"
|
||||
@label-update="handleLabelUpdate"
|
||||
@delete="handleDelete" />
|
||||
|
|
@ -32,6 +36,7 @@
|
|||
|
||||
<script>
|
||||
import { showError, showSuccess } from '@nextcloud/dialogs'
|
||||
import isMobile from '@nextcloud/vue/dist/Mixins/isMobile.js'
|
||||
import { fetchVersions, deleteVersion, restoreVersion, setVersionLabel } from '../utils/versions.js'
|
||||
import Version from '../components/Version.vue'
|
||||
|
||||
|
|
@ -40,6 +45,9 @@ export default {
|
|||
components: {
|
||||
Version,
|
||||
},
|
||||
mixins: [
|
||||
isMobile,
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
fileInfo: null,
|
||||
|
|
@ -78,6 +86,37 @@ export default {
|
|||
.map(version => version.mtime)
|
||||
.reduce((a, b) => Math.min(a, b))
|
||||
},
|
||||
|
||||
viewerFileInfo() {
|
||||
// We need to remap bitmask to dav permissions as the file info we have is converted through client.js
|
||||
let davPermissions = ''
|
||||
if (this.fileInfo.permissions & 1) {
|
||||
davPermissions += 'R'
|
||||
}
|
||||
if (this.fileInfo.permissions & 2) {
|
||||
davPermissions += 'W'
|
||||
}
|
||||
if (this.fileInfo.permissions & 8) {
|
||||
davPermissions += 'D'
|
||||
}
|
||||
return {
|
||||
...this.fileInfo,
|
||||
mime: this.fileInfo.mimetype,
|
||||
basename: this.fileInfo.name,
|
||||
filename: this.fileInfo.path + this.fileInfo.name,
|
||||
permissions: davPermissions,
|
||||
fileid: this.fileInfo.id,
|
||||
}
|
||||
},
|
||||
|
||||
/** @return {boolean} */
|
||||
canView() {
|
||||
return window.OCA.Viewer?.mimetypesCompare?.includes(this.fileInfo.mimetype)
|
||||
},
|
||||
|
||||
canCompare() {
|
||||
return !this.isMobile
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
|
|
@ -182,6 +221,29 @@ export default {
|
|||
resetState() {
|
||||
this.$set(this, 'versions', [])
|
||||
},
|
||||
|
||||
openVersion({ version }) {
|
||||
// Open current file view instead of read only
|
||||
if (version.mtime === this.fileInfo.mtime) {
|
||||
OCA.Viewer.open({ fileInfo: this.viewerFileInfo })
|
||||
return
|
||||
}
|
||||
|
||||
// Versions previews are too small for our use case, so we override hasPreview and previewUrl
|
||||
// which makes the viewer render the original file.
|
||||
const versions = this.versions.map(version => ({ ...version, hasPreview: false, previewUrl: undefined }))
|
||||
|
||||
OCA.Viewer.open({
|
||||
fileInfo: versions.find(v => v.source === version.source),
|
||||
enableSidebar: false,
|
||||
})
|
||||
},
|
||||
|
||||
compareVersion({ version }) {
|
||||
const versions = this.versions.map(version => ({ ...version, hasPreview: false, previewUrl: undefined }))
|
||||
|
||||
OCA.Viewer.compare(this.viewerFileInfo, versions.find(v => v.source === version.source))
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Reference in a new issue