]> source.dussan.org Git - nextcloud-server.git/commitdiff
feat: Use viewer to open and compare versions
authorLouis Chemineau <louis@chmn.me>
Thu, 6 Jul 2023 15:01:20 +0000 (17:01 +0200)
committerJulius Härtl <jus@bitgrid.net>
Tue, 8 Aug 2023 19:19:03 +0000 (21:19 +0200)
Signed-off-by: Louis Chemineau <louis@chmn.me>
apps/files_versions/src/components/Version.vue
apps/files_versions/src/utils/davRequest.js
apps/files_versions/src/utils/versions.js
apps/files_versions/src/views/VersionTab.vue

index 3d7edc7784197189b2d7c86ed4b2f0c84b5fb7a0..5ae389e15d33ee02ed6f423c873719ffdeb99c4e 100644 (file)
        <div>
                <NcListItem class="version"
                        :name="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>
                                        </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">
 <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'
@@ -130,7 +139,7 @@ import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip.js'
 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 {
@@ -144,6 +153,7 @@ export default {
                NcTextField,
                BackupRestore,
                Download,
+               FileCompare,
                Pencil,
                Check,
                Delete,
@@ -190,6 +200,14 @@ export default {
                        type: Boolean,
                        default: false,
                },
+               canView: {
+                       type: Boolean,
+                       default: false,
+               },
+               canCompare: {
+                       type: Boolean,
+                       default: false,
+               },
        },
        data() {
                return {
@@ -232,20 +250,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')
@@ -259,7 +263,7 @@ export default {
                /** @return {boolean} */
                enableDeletion() {
                        return this.capabilities.files.version_deletion === true && this.fileInfo.mountType !== 'group'
-               }
+               },
        },
        methods: {
                openVersionLabelModal() {
@@ -282,6 +286,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>
index fec64caa2d5dea92ba17cb73cadf9734f53bce12..d3fe729aaabd310668aaec2b2ed2fdfe9c6199eb 100644 (file)
@@ -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>
index 71593dd0ce80c7c7634ba057ceb07530949768ff..7f5ccfc0e6917ad09648fbc328ff04b0ad62c399 100644 (file)
  */
 
 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)
 }
index 6d3b07c2f88bb3391d60f0630bf1b39b4258736a..a8b7ce100926ea044869a56d472dd57923db4afa 100644 (file)
        <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>