diff options
Diffstat (limited to 'apps/files_versions/src/views/VersionTab.vue')
-rw-r--r-- | apps/files_versions/src/views/VersionTab.vue | 218 |
1 files changed, 174 insertions, 44 deletions
diff --git a/apps/files_versions/src/views/VersionTab.vue b/apps/files_versions/src/views/VersionTab.vue index f2e9576abd0..a643aef439d 100644 --- a/apps/files_versions/src/views/VersionTab.vue +++ b/apps/files_versions/src/views/VersionTab.vue @@ -1,58 +1,90 @@ <!-- - - @copyright 2022 Carl Schwan <carl@carlschwan.eu> - - @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/>. - --> + - SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later +--> <template> - <ul data-files-versions-versions-list> - <Version v-for="version in orderedVersions" - :key="version.mtime" - :version="version" - :file-info="fileInfo" - :is-current="version.mtime === fileInfo.mtime" - :is-first-version="version.mtime === initialVersionMtime" - @restore="handleRestore" - @label-update="handleLabelUpdate" - @delete="handleDelete" /> - </ul> + <div class="versions-tab__container"> + <VirtualScrolling v-slot="{ visibleSections }" + :sections="sections" + :header-height="0"> + <ul :aria-label="t('files_versions', 'File versions')" data-files-versions-versions-list> + <template v-if="visibleSections.length === 1"> + <Version v-for="(row) of visibleSections[0].rows" + :key="row.items[0].mtime" + :can-view="canView" + :can-compare="canCompare" + :load-preview="isActive" + :version="row.items[0]" + :file-info="fileInfo" + :is-current="row.items[0].mtime === fileInfo.mtime" + :is-first-version="row.items[0].mtime === initialVersionMtime" + @click="openVersion" + @compare="compareVersion" + @restore="handleRestore" + @label-update-request="handleLabelUpdateRequest(row.items[0])" + @delete="handleDelete" /> + </template> + </ul> + <NcLoadingIcon v-if="loading" slot="loader" class="files-list-viewer__loader" /> + </VirtualScrolling> + <VersionLabelDialog v-if="editedVersion" + :open.sync="showVersionLabelForm" + :version-label="editedVersion.label" + @label-update="handleLabelUpdate" /> + </div> </template> <script> +import path from 'path' + +import { getCurrentUser } from '@nextcloud/auth' import { showError, showSuccess } from '@nextcloud/dialogs' -import { fetchVersions, deleteVersion, restoreVersion, setVersionLabel } from '../utils/versions.js' +import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus' +import { useIsMobile } from '@nextcloud/vue/composables/useIsMobile' +import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon' + +import { fetchVersions, deleteVersion, restoreVersion, setVersionLabel } from '../utils/versions.ts' import Version from '../components/Version.vue' +import VirtualScrolling from '../components/VirtualScrolling.vue' +import VersionLabelDialog from '../components/VersionLabelDialog.vue' export default { name: 'VersionTab', components: { Version, + VirtualScrolling, + VersionLabelDialog, + NcLoadingIcon, + }, + + setup() { + return { + isMobile: useIsMobile(), + } }, + data() { return { fileInfo: null, - /** @type {import('../utils/versions.js').Version[]} */ + isActive: false, + /** @type {import('../utils/versions.ts').Version[]} */ versions: [], loading: false, + showVersionLabelForm: false, + editedVersion: null, } }, computed: { + sections() { + const rows = this.orderedVersions.map(version => ({ key: version.mtime, height: 68, sectionKey: 'versions', items: [version] })) + return [{ key: 'versions', rows, height: 68 * this.orderedVersions.length }] + }, + /** * Order versions by mtime. * Put the current version at the top. * - * @return {import('../utils/versions.js').Version[]} + * @return {import('../utils/versions.ts').Version[]} */ orderedVersions() { return [...this.versions].sort((a, b) => { @@ -68,6 +100,7 @@ export default { /** * Return the mtime of the first version to display "Initial version" label + * * @return {number} */ initialVersionMtime() { @@ -75,6 +108,43 @@ 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 + }, + }, + mounted() { + subscribe('files_versions:restore:restored', this.fetchVersions) + }, + beforeUnmount() { + unsubscribe('files_versions:restore:restored', this.fetchVersions) }, methods: { /** @@ -89,6 +159,13 @@ export default { }, /** + * @param {boolean} isActive whether the tab is active + */ + async setIsActive(isActive) { + this.isActive = isActive + }, + + /** * Get the existing versions infos */ async fetchVersions() { @@ -103,7 +180,7 @@ export default { /** * Handle restored event from Version.vue * - * @param {import('../utils/versions.js').Version} version + * @param {import('../utils/versions.ts').Version} version The version to restore */ async handleRestore(version) { // Update local copy of fileInfo as rendering depends on it. @@ -114,45 +191,65 @@ export default { mtime: version.mtime, } + const restoreStartedEventState = { + preventDefault: false, + fileInfo: this.fileInfo, + version, + } + emit('files_versions:restore:requested', restoreStartedEventState) + if (restoreStartedEventState.preventDefault) { + return + } + try { await restoreVersion(version) - if (version.label !== '') { + if (version.label) { showSuccess(t('files_versions', `${version.label} restored`)) } else if (version.mtime === this.initialVersionMtime) { showSuccess(t('files_versions', 'Initial version restored')) } else { showSuccess(t('files_versions', 'Version restored')) } - await this.fetchVersions() + emit('files_versions:restore:restored', version) } catch (exception) { this.fileInfo = oldFileInfo showError(t('files_versions', 'Could not restore version')) + emit('files_versions:restore:failed', version) } }, /** * Handle label-updated event from Version.vue - * - * @param {import('../utils/versions.js').Version} version - * @param {string} newName + * @param {import('../utils/versions.ts').Version} version The version to update */ - async handleLabelUpdate(version, newName) { - const oldLabel = version.label - version.label = newName + handleLabelUpdateRequest(version) { + this.showVersionLabelForm = true + this.editedVersion = version + }, + + /** + * Handle label-updated event from Version.vue + * @param {string} newLabel The new label + */ + async handleLabelUpdate(newLabel) { + const oldLabel = this.editedVersion.label + this.editedVersion.label = newLabel + this.showVersionLabelForm = false try { - await setVersionLabel(version, newName) + await setVersionLabel(this.editedVersion, newLabel) + this.editedVersion = null } catch (exception) { - version.label = oldLabel - showError(t('files_versions', 'Could not set version name')) + this.editedVersion.label = oldLabel + showError(this.t('files_versions', 'Could not set version label')) + logger.error('Could not set version label', { exception }) } }, /** * Handle deleted event from Version.vue * - * @param {import('../utils/versions.js').Version} version - * @param {string} newName + * @param {import('../utils/versions.ts').Version} version The version to delete */ async handleDelete(version) { const index = this.versions.indexOf(version) @@ -172,6 +269,39 @@ 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 previewUrl + // which makes the viewer render the original file. + // We also point to the original filename if the version is the current one. + const versions = this.versions.map(version => ({ + ...version, + filename: version.mtime === this.fileInfo.mtime ? path.join('files', getCurrentUser()?.uid ?? '', this.fileInfo.path, this.fileInfo.name) : version.filename, + previewUrl: undefined, + })) + + OCA.Viewer.open({ + fileInfo: versions.find(v => v.source === version.source), + enableSidebar: false, + }) + }, + + compareVersion({ version }) { + const versions = this.versions.map(version => ({ ...version, previewUrl: undefined })) + + OCA.Viewer.compare(this.viewerFileInfo, versions.find(v => v.source === version.source)) + }, }, } </script> +<style lang="scss"> +.versions-tab__container { + height: 100%; +} +</style> |