mirror of
https://github.com/nextcloud/server.git
synced 2026-04-21 06:08:46 -04:00
refactor(files_versions): adjust frontend for new files sidebar API
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
This commit is contained in:
parent
4a9cdeb01f
commit
493c371a22
8 changed files with 176 additions and 212 deletions
|
|
@ -24,6 +24,6 @@ class LoadAdditionalListener implements IEventListener {
|
|||
// TODO: make sure to only include the sidebar script when
|
||||
// we properly split it between files list and sidebar
|
||||
Util::addStyle(Application::APP_ID, 'sidebar-tab');
|
||||
Util::addScript(Application::APP_ID, 'sidebar-tab');
|
||||
Util::addInitScript(Application::APP_ID, 'sidebar-tab');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,6 @@ class LoadSidebarListener implements IEventListener {
|
|||
// TODO: make sure to only include the sidebar script when
|
||||
// we properly split it between files list and sidebar
|
||||
Util::addStyle(Application::APP_ID, 'sidebar-tab');
|
||||
Util::addScript(Application::APP_ID, 'sidebar-tab');
|
||||
Util::addInitScript(Application::APP_ID, 'sidebar-tab');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
:force-display-actions="true"
|
||||
:actions-aria-label="t('files_versions', 'Actions for version from {versionHumanExplicitDate}', { versionHumanExplicitDate })"
|
||||
:data-files-versions-version="version.fileVersion"
|
||||
:href="downloadURL"
|
||||
@click="click">
|
||||
<!-- Icon -->
|
||||
<template #icon>
|
||||
|
|
@ -131,8 +132,8 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { INode } from '@nextcloud/files'
|
||||
import type { PropType } from 'vue'
|
||||
import type { LegacyFileInfo } from '../../../files/src/services/FileInfo.ts'
|
||||
import type { Version } from '../utils/versions.ts'
|
||||
|
||||
import { getCurrentUser } from '@nextcloud/auth'
|
||||
|
|
@ -140,7 +141,6 @@ import { formatFileSize, Permission } from '@nextcloud/files'
|
|||
import { loadState } from '@nextcloud/initial-state'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import moment from '@nextcloud/moment'
|
||||
import { join } from '@nextcloud/paths'
|
||||
import { getRootUrl } from '@nextcloud/router'
|
||||
import { computed, nextTick, ref } from 'vue'
|
||||
import NcActionButton from '@nextcloud/vue/components/NcActionButton'
|
||||
|
|
@ -161,8 +161,8 @@ const props = defineProps({
|
|||
required: true,
|
||||
},
|
||||
|
||||
fileInfo: {
|
||||
type: Object as PropType<LegacyFileInfo>,
|
||||
node: {
|
||||
type: Object as PropType<INode>,
|
||||
required: true,
|
||||
},
|
||||
|
||||
|
|
@ -194,8 +194,6 @@ const props = defineProps({
|
|||
|
||||
const emit = defineEmits(['click', 'compare', 'restore', 'delete', 'label-update-request'])
|
||||
|
||||
const hasPermission = (permissions: number, permission: number): boolean => (permissions & permission) !== 0
|
||||
|
||||
const previewLoaded = ref(false)
|
||||
const previewErrored = ref(false)
|
||||
const capabilities = ref(loadState('core', 'capabilities', { files: { version_labeling: false, version_deletion: false } }))
|
||||
|
|
@ -240,7 +238,7 @@ const versionHumanExplicitDate = computed(() => {
|
|||
|
||||
const downloadURL = computed(() => {
|
||||
if (props.isCurrent) {
|
||||
return getRootUrl() + join('/remote.php/webdav', props.fileInfo.path, props.fileInfo.name)
|
||||
return props.node.source
|
||||
} else {
|
||||
return getRootUrl() + props.version.url
|
||||
}
|
||||
|
|
@ -255,21 +253,21 @@ const enableDeletion = computed(() => {
|
|||
})
|
||||
|
||||
const hasDeletePermissions = computed(() => {
|
||||
return hasPermission(props.fileInfo.permissions, Permission.DELETE)
|
||||
return hasPermission(props.node, Permission.DELETE)
|
||||
})
|
||||
|
||||
const hasUpdatePermissions = computed(() => {
|
||||
return hasPermission(props.fileInfo.permissions, Permission.UPDATE)
|
||||
return hasPermission(props.node, Permission.UPDATE)
|
||||
})
|
||||
|
||||
const isDownloadable = computed(() => {
|
||||
if ((props.fileInfo.permissions & Permission.READ) === 0) {
|
||||
if ((props.node.permissions & Permission.READ) === 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
// If the mount type is a share, ensure it got download permissions.
|
||||
if (props.fileInfo.mountType === 'shared') {
|
||||
const downloadAttribute = props.fileInfo.shareAttributes
|
||||
if (props.node.attributes['mount-type'] === 'shared' && props.node.attributes['share-attributes']) {
|
||||
const downloadAttribute = JSON.parse(props.node.attributes['share-attributes'])
|
||||
.find((attribute) => attribute.scope === 'permissions' && attribute.key === 'download') || {}
|
||||
// If the download attribute is set to false, the file is not downloadable
|
||||
if (downloadAttribute?.value === false) {
|
||||
|
|
@ -281,21 +279,21 @@ const isDownloadable = computed(() => {
|
|||
})
|
||||
|
||||
/**
|
||||
*
|
||||
* Label update request
|
||||
*/
|
||||
function labelUpdate() {
|
||||
emit('label-update-request')
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Restore version
|
||||
*/
|
||||
function restoreVersion() {
|
||||
emit('restore', props.version)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Delete version
|
||||
*/
|
||||
async function deleteVersion() {
|
||||
// Let @nc-vue properly remove the popover before we delete the version.
|
||||
|
|
@ -306,18 +304,20 @@ async function deleteVersion() {
|
|||
}
|
||||
|
||||
/**
|
||||
* Handle click on the version entry
|
||||
*
|
||||
* @param event - The click event
|
||||
*/
|
||||
function click() {
|
||||
if (!props.canView) {
|
||||
window.location.href = downloadURL.value
|
||||
return
|
||||
function click(event: MouseEvent) {
|
||||
if (props.canView) {
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
emit('click', { version: props.version })
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* If the user can compare, emit the compare event
|
||||
*/
|
||||
function compareVersion() {
|
||||
if (!props.canView) {
|
||||
|
|
@ -325,6 +325,16 @@ function compareVersion() {
|
|||
}
|
||||
emit('compare', { version: props.version })
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current user has the given permission on the node
|
||||
*
|
||||
* @param node - The node to check
|
||||
* @param permission - The permission to check
|
||||
*/
|
||||
function hasPermission(node: INode, permission: number): boolean {
|
||||
return (node.permissions & permission) !== 0
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
|
|
|||
|
|
@ -1,50 +1,46 @@
|
|||
/**
|
||||
/*!
|
||||
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { App, ComponentPublicInstance } from 'vue'
|
||||
|
||||
import BackupRestore from '@mdi/svg/svg/backup-restore.svg?raw'
|
||||
import { FileType, registerSidebarTab } from '@nextcloud/files'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import { createApp } from 'vue'
|
||||
import FilesVersionsSidebarTab from './views/FilesVersionsSidebarTab.vue'
|
||||
import { isPublicShare } from '@nextcloud/sharing/public'
|
||||
import { defineAsyncComponent, defineCustomElement } from 'vue'
|
||||
|
||||
// Init FilesVersions tab component
|
||||
let filesVersionsTabApp: App<Element> | null = null
|
||||
let filesVersionsTabInstance: ComponentPublicInstance<typeof FilesVersionsSidebarTab> | null = null
|
||||
const tagName = 'files-versions_sidebar-tab'
|
||||
const FilesVersionsSidebarTab = defineAsyncComponent(() => import('./views/FilesVersionsSidebarTab.vue'))
|
||||
|
||||
window.addEventListener('DOMContentLoaded', function() {
|
||||
if (window.OCA.Files?.Sidebar === undefined) {
|
||||
registerSidebarTab({
|
||||
id: 'files_versions',
|
||||
order: 90,
|
||||
displayName: t('files_versions', 'Versions'),
|
||||
iconSvgInline: BackupRestore,
|
||||
enabled({ node }) {
|
||||
if (isPublicShare()) {
|
||||
return false
|
||||
}
|
||||
if (node.type !== FileType.File) {
|
||||
return false
|
||||
}
|
||||
// setup tab
|
||||
setupTab()
|
||||
return true
|
||||
},
|
||||
tagName,
|
||||
})
|
||||
|
||||
/**
|
||||
* Setup the custom element for the Files Versions sidebar tab.
|
||||
*/
|
||||
function setupTab() {
|
||||
if (window.customElements.get(tagName)) {
|
||||
// already defined
|
||||
return
|
||||
}
|
||||
|
||||
window.OCA.Files.Sidebar.registerTab(new window.OCA.Files.Sidebar.Tab({
|
||||
id: 'files_versions',
|
||||
name: t('files_versions', 'Versions'),
|
||||
iconSvg: BackupRestore,
|
||||
|
||||
async mount(el, fileInfo) {
|
||||
// destroy previous instance if available
|
||||
if (filesVersionsTabApp) {
|
||||
filesVersionsTabApp.unmount()
|
||||
}
|
||||
filesVersionsTabApp = createApp(FilesVersionsSidebarTab)
|
||||
filesVersionsTabInstance = filesVersionsTabApp.mount(el)
|
||||
filesVersionsTabInstance.update(fileInfo)
|
||||
},
|
||||
update(fileInfo) {
|
||||
filesVersionsTabInstance!.update(fileInfo)
|
||||
},
|
||||
setIsActive(isActive) {
|
||||
filesVersionsTabInstance?.setIsActive(isActive)
|
||||
},
|
||||
destroy() {
|
||||
filesVersionsTabApp?.unmount()
|
||||
filesVersionsTabApp = null
|
||||
},
|
||||
enabled(fileInfo) {
|
||||
return !(fileInfo?.isDirectory() ?? true)
|
||||
},
|
||||
window.customElements.define(tagName, defineCustomElement(FilesVersionsSidebarTab, {
|
||||
shadowRoot: false,
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { getRequestToken, onRequestTokenUpdate } from '@nextcloud/auth'
|
||||
import { generateRemoteUrl } from '@nextcloud/router'
|
||||
import { createClient } from 'webdav'
|
||||
|
||||
// init webdav client
|
||||
const rootPath = 'dav'
|
||||
const remote = generateRemoteUrl(rootPath)
|
||||
const client = createClient(remote)
|
||||
|
||||
/**
|
||||
* set CSRF token header
|
||||
*
|
||||
* @param token - CSRF token
|
||||
*/
|
||||
function setHeaders(token) {
|
||||
client.setHeaders({
|
||||
// Add this so the server knows it is an request from the browser
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
// Inject user auth
|
||||
requesttoken: token ?? '',
|
||||
})
|
||||
}
|
||||
|
||||
// refresh headers when request token changes
|
||||
onRequestTokenUpdate(setHeaders)
|
||||
setHeaders(getRequestToken())
|
||||
|
||||
export default client
|
||||
|
|
@ -1,18 +1,17 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable jsdoc/require-param */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
/**
|
||||
/*!
|
||||
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { INode } from '@nextcloud/files'
|
||||
import type { FileStat, ResponseDataDetailed } from 'webdav'
|
||||
|
||||
import { getCurrentUser } from '@nextcloud/auth'
|
||||
import axios from '@nextcloud/axios'
|
||||
import { getClient } from '@nextcloud/files/dav'
|
||||
import moment from '@nextcloud/moment'
|
||||
import { encodePath, join } from '@nextcloud/paths'
|
||||
import { generateRemoteUrl, generateUrl } from '@nextcloud/router'
|
||||
import client from '../utils/davClient.ts'
|
||||
import davRequest from '../utils/davRequest.ts'
|
||||
import logger from '../utils/logger.ts'
|
||||
|
||||
|
|
@ -25,7 +24,7 @@ export interface Version {
|
|||
basename: string // A base name generated from the mtime
|
||||
mime: string // Empty for the current version, else the actual mime type of the version
|
||||
etag: string // Empty for the current version, else the actual mime type of the version
|
||||
size: string // Human readable size
|
||||
size: number // File size in bytes
|
||||
type: string // 'file'
|
||||
mtime: number // Version creation date as a timestamp
|
||||
permissions: string // Only readable: 'R'
|
||||
|
|
@ -35,8 +34,15 @@ export interface Version {
|
|||
fileVersion: string | null // The version id, null for the current version
|
||||
}
|
||||
|
||||
export async function fetchVersions(fileInfo: any): Promise<Version[]> {
|
||||
const path = `/versions/${getCurrentUser()?.uid}/versions/${fileInfo.id}`
|
||||
const client = getClient()
|
||||
|
||||
/**
|
||||
* Get file versions for a given node
|
||||
*
|
||||
* @param node - The node to fetch versions for
|
||||
*/
|
||||
export async function fetchVersions(node: INode): Promise<Version[]> {
|
||||
const path = `/versions/${getCurrentUser()?.uid}/versions/${node.fileid}`
|
||||
|
||||
try {
|
||||
const response = await client.getDirectoryContents(path, {
|
||||
|
|
@ -47,7 +53,7 @@ export async function fetchVersions(fileInfo: any): Promise<Version[]> {
|
|||
const versions = response.data
|
||||
// Filter out root
|
||||
.filter(({ mime }) => mime !== '')
|
||||
.map((version) => formatVersion(version, fileInfo))
|
||||
.map((version) => formatVersion(version as Required<FileStat>, node))
|
||||
|
||||
const authorIds = new Set(versions.map((version) => String(version.author)))
|
||||
const authors = await axios.post(generateUrl('/displaynames'), { users: [...authorIds] })
|
||||
|
|
@ -68,6 +74,8 @@ export async function fetchVersions(fileInfo: any): Promise<Version[]> {
|
|||
|
||||
/**
|
||||
* Restore the given version
|
||||
*
|
||||
* @param version - The version to restore
|
||||
*/
|
||||
export async function restoreVersion(version: Version) {
|
||||
try {
|
||||
|
|
@ -84,25 +92,28 @@ export async function restoreVersion(version: Version) {
|
|||
|
||||
/**
|
||||
* Format version
|
||||
*
|
||||
* @param version - The version data from WebDAV
|
||||
* @param node - The original node
|
||||
*/
|
||||
function formatVersion(version: any, fileInfo: any): Version {
|
||||
function formatVersion(version: Required<FileStat>, node: INode): Version {
|
||||
const mtime = moment(version.lastmod).unix() * 1000
|
||||
let previewUrl = ''
|
||||
|
||||
if (mtime === fileInfo.mtime) { // Version is the current one
|
||||
if (mtime === node.mtime?.getTime()) { // Version is the current one
|
||||
previewUrl = generateUrl('/core/preview?fileId={fileId}&c={fileEtag}&x=250&y=250&forceIcon=0&a=0&forceIcon=1&mimeFallback=1', {
|
||||
fileId: fileInfo.id,
|
||||
fileEtag: fileInfo.etag,
|
||||
fileId: node.fileid,
|
||||
fileEtag: node.attributes.etag,
|
||||
})
|
||||
} else {
|
||||
previewUrl = generateUrl('/apps/files_versions/preview?file={file}&version={fileVersion}&mimeFallback=1', {
|
||||
file: join(fileInfo.path, fileInfo.name),
|
||||
file: node.path,
|
||||
fileVersion: version.basename,
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
fileId: fileInfo.id,
|
||||
fileId: node.fileid!.toString(),
|
||||
// If version-label is defined make sure it is a string (prevent issue if the label is a number an PHP returns a number then)
|
||||
label: version.props['version-label'] ? String(version.props['version-label']) : '',
|
||||
author: version.props['version-author'] ? String(version.props['version-author']) : null,
|
||||
|
|
@ -122,6 +133,12 @@ function formatVersion(version: any, fileInfo: any): Version {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set version label
|
||||
*
|
||||
* @param version - The version to set the label for
|
||||
* @param newLabel - The new label
|
||||
*/
|
||||
export async function setVersionLabel(version: Version, newLabel: string) {
|
||||
return await client.customRequest(
|
||||
version.filename,
|
||||
|
|
@ -142,6 +159,11 @@ export async function setVersionLabel(version: Version, newLabel: string) {
|
|||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete version
|
||||
*
|
||||
* @param version - The version to delete
|
||||
*/
|
||||
export async function deleteVersion(version: Version) {
|
||||
await client.deleteFile(version.filename)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
<template>
|
||||
<div v-if="fileInfo !== null" class="versions-tab__container">
|
||||
<div v-if="node" class="versions-tab__container">
|
||||
<VirtualScrolling
|
||||
:sections="sections"
|
||||
:header-height="0">
|
||||
|
|
@ -17,8 +17,8 @@
|
|||
:can-compare="canCompare"
|
||||
:load-preview="isActive"
|
||||
:version="row.items[0].version"
|
||||
:file-info="fileInfo"
|
||||
:is-current="row.items[0].version.mtime === fileInfo.mtime"
|
||||
:node="node"
|
||||
:is-current="row.items[0].version.mtime === currentVersionMtime"
|
||||
:is-first-version="row.items[0].version.mtime === initialVersionMtime"
|
||||
@click="openVersion"
|
||||
@compare="compareVersion"
|
||||
|
|
@ -41,16 +41,14 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { LegacyFileInfo } from '../../../files/src/services/FileInfo.ts'
|
||||
import type { IFolder, INode, IView } from '@nextcloud/files'
|
||||
import type { Version } from '../utils/versions.ts'
|
||||
|
||||
import { getCurrentUser } from '@nextcloud/auth'
|
||||
import { showError, showSuccess } from '@nextcloud/dialogs'
|
||||
import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus'
|
||||
import { emit } from '@nextcloud/event-bus'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import { useIsMobile } from '@nextcloud/vue/composables/useIsMobile'
|
||||
import path from 'path'
|
||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import { computed, ref, toRef, watch } from 'vue'
|
||||
import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
|
||||
import VersionEntry from '../components/VersionEntry.vue'
|
||||
import VersionLabelDialog from '../components/VersionLabelDialog.vue'
|
||||
|
|
@ -58,28 +56,49 @@ import VirtualScrolling from '../components/VirtualScrolling.vue'
|
|||
import logger from '../utils/logger.ts'
|
||||
import { deleteVersion, fetchVersions, restoreVersion, setVersionLabel } from '../utils/versions.ts'
|
||||
|
||||
const isMobile = useIsMobile()
|
||||
const props = defineProps<{
|
||||
node?: INode
|
||||
folder?: IFolder
|
||||
view?: IView
|
||||
}>()
|
||||
|
||||
const fileInfo = ref<LegacyFileInfo | null>(null)
|
||||
defineExpose({ setActive })
|
||||
|
||||
const isMobile = useIsMobile()
|
||||
const isActive = ref<boolean>(false)
|
||||
const versions = ref<Version[]>([])
|
||||
const loading = ref(false)
|
||||
const showVersionLabelForm = ref(false)
|
||||
const editedVersion = ref<Version | null>(null)
|
||||
|
||||
watch(toRef(() => props.node), async () => {
|
||||
if (!props.node) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
loading.value = true
|
||||
versions.value = await fetchVersions(props.node)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
const currentVersionMtime = computed(() => props.node?.mtime?.getTime() ?? 0)
|
||||
|
||||
/**
|
||||
* Order versions by mtime.
|
||||
* Put the current version at the top.
|
||||
*/
|
||||
const orderedVersions = computed(() => {
|
||||
return [...versions.value].sort((a, b) => {
|
||||
if (fileInfo.value === null) {
|
||||
if (!props.node) {
|
||||
return 0
|
||||
}
|
||||
|
||||
if (a.mtime === fileInfo.value.mtime) {
|
||||
if (a.mtime === props.node.mtime?.getTime()) {
|
||||
return -1
|
||||
} else if (b.mtime === fileInfo.value.mtime) {
|
||||
} else if (b.mtime === props.node.mtime?.getTime()) {
|
||||
return 1
|
||||
} else {
|
||||
return b.mtime - a.mtime
|
||||
|
|
@ -88,7 +107,12 @@ const orderedVersions = computed(() => {
|
|||
})
|
||||
|
||||
const sections = computed(() => {
|
||||
const rows = orderedVersions.value.map((version) => ({ key: version.mtime.toString(), height: 68, sectionKey: 'versions', items: [{ id: version.mtime.toString(), version }] }))
|
||||
const rows = orderedVersions.value.map((version) => ({
|
||||
key: version.mtime.toString(),
|
||||
height: 68,
|
||||
sectionKey: 'versions',
|
||||
items: [{ id: version.mtime.toString(), version }],
|
||||
}))
|
||||
return [{ key: 'versions', rows, height: 68 * orderedVersions.value.length }]
|
||||
})
|
||||
|
||||
|
|
@ -101,82 +125,26 @@ const initialVersionMtime = computed(() => {
|
|||
.reduce((a, b) => Math.min(a, b))
|
||||
})
|
||||
|
||||
const viewerFileInfo = computed(() => {
|
||||
if (fileInfo.value === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
// We need to remap bitmask to dav permissions as the file info we have is converted through client.js
|
||||
let davPermissions = ''
|
||||
if (fileInfo.value.permissions & 1) {
|
||||
davPermissions += 'R'
|
||||
}
|
||||
if (fileInfo.value.permissions & 2) {
|
||||
davPermissions += 'W'
|
||||
}
|
||||
if (fileInfo.value.permissions & 8) {
|
||||
davPermissions += 'D'
|
||||
}
|
||||
return {
|
||||
...fileInfo.value,
|
||||
mime: fileInfo.value.mimetype,
|
||||
basename: fileInfo.value.name,
|
||||
filename: fileInfo.value.path + '/' + fileInfo.value.name,
|
||||
permissions: davPermissions,
|
||||
fileid: fileInfo.value.id,
|
||||
}
|
||||
})
|
||||
|
||||
const canView = computed(() => {
|
||||
if (fileInfo.value === null) {
|
||||
if (!props.node) {
|
||||
return false
|
||||
}
|
||||
|
||||
return window.OCA.Viewer?.mimetypesCompare?.includes(fileInfo.value.mimetype)
|
||||
return window.OCA.Viewer?.mimetypes?.includes(props.node?.mime)
|
||||
})
|
||||
|
||||
const canCompare = computed(() => {
|
||||
return !isMobile.value
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
subscribe('files_versions:restore:restored', fetchVersions)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
unsubscribe('files_versions:restore:restored', fetchVersions)
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
/**
|
||||
* Update current fileInfo and fetch new data
|
||||
*
|
||||
* @param _fileInfo the current file FileInfo
|
||||
*/
|
||||
async update(_fileInfo: LegacyFileInfo) {
|
||||
fileInfo.value = _fileInfo
|
||||
resetState()
|
||||
internalFetchVersions()
|
||||
},
|
||||
|
||||
/**
|
||||
* @param _isActive whether the tab is active
|
||||
*/
|
||||
async setIsActive(_isActive: boolean) {
|
||||
isActive.value = _isActive
|
||||
},
|
||||
&& window.OCA.Viewer?.mimetypesCompare?.includes(props.node?.mime)
|
||||
})
|
||||
|
||||
/**
|
||||
* Get the existing versions infos
|
||||
* This method is called by the files app if the sidebar tab state changes.
|
||||
*
|
||||
* @param active - The new active state
|
||||
*/
|
||||
async function internalFetchVersions() {
|
||||
try {
|
||||
loading.value = true
|
||||
versions.value = await fetchVersions(fileInfo.value)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
function setActive(active: boolean) {
|
||||
isActive.value = active
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -185,17 +153,19 @@ async function internalFetchVersions() {
|
|||
* @param version The version to restore
|
||||
*/
|
||||
async function handleRestore(version: Version) {
|
||||
// Update local copy of fileInfo as rendering depends on it.
|
||||
const oldFileInfo = fileInfo.value
|
||||
fileInfo.value = {
|
||||
...fileInfo.value,
|
||||
size: version.size,
|
||||
mtime: version.mtime,
|
||||
if (!props.node) {
|
||||
return
|
||||
}
|
||||
|
||||
// Update local copy of fileInfo as rendering depends on it.
|
||||
const restoredNode = props.node.clone()
|
||||
restoredNode.attributes.etag = version.etag
|
||||
restoredNode.size = version.size
|
||||
restoredNode.mtime = new Date(version.mtime)
|
||||
|
||||
const restoreStartedEventState = {
|
||||
preventDefault: false,
|
||||
fileInfo: fileInfo.value,
|
||||
node: restoredNode,
|
||||
version,
|
||||
}
|
||||
emit('files_versions:restore:requested', restoreStartedEventState)
|
||||
|
|
@ -212,9 +182,9 @@ async function handleRestore(version: Version) {
|
|||
} else {
|
||||
showSuccess(t('files_versions', 'Version restored'))
|
||||
}
|
||||
emit('files_versions:restore:restored', version)
|
||||
emit('files:node:updated', restoredNode)
|
||||
emit('files_versions:restore:restored', { node: restoredNode, version })
|
||||
} catch {
|
||||
fileInfo.value = oldFileInfo
|
||||
showError(t('files_versions', 'Could not restore version'))
|
||||
emit('files_versions:restore:failed', version)
|
||||
}
|
||||
|
|
@ -271,25 +241,18 @@ async function handleDelete(version: Version) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the current view to its default state
|
||||
*/
|
||||
function resetState() {
|
||||
versions.value = []
|
||||
}
|
||||
|
||||
/**
|
||||
* @param payload - The event payload
|
||||
* @param payload.version - The version to open
|
||||
*/
|
||||
function openVersion({ version }: { version: Version }) {
|
||||
if (fileInfo.value === null) {
|
||||
if (props.node === null) {
|
||||
return
|
||||
}
|
||||
|
||||
// Open current file view instead of read only
|
||||
if (version.mtime === fileInfo.value.mtime) {
|
||||
window.OCA.Viewer.open({ fileInfo: viewerFileInfo.value })
|
||||
if (version.mtime === props.node?.mtime?.getTime()) {
|
||||
window.OCA.Viewer.open({ path: props.node.path })
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -298,7 +261,7 @@ function openVersion({ version }: { version: Version }) {
|
|||
...version,
|
||||
// Versions previews are too small for our use case, so we override previewUrl
|
||||
// to either point to the original file or original version.
|
||||
filename: version.mtime === fileInfo.value.mtime ? path.join('files', getCurrentUser()?.uid ?? '', fileInfo.value.path, fileInfo.value.name) : version.filename,
|
||||
filename: version.filename,
|
||||
previewUrl: undefined,
|
||||
},
|
||||
enableSidebar: false,
|
||||
|
|
@ -312,7 +275,10 @@ function openVersion({ version }: { version: Version }) {
|
|||
function compareVersion({ version }: { version: Version }) {
|
||||
const _versions = versions.value.map((version) => ({ ...version, previewUrl: undefined }))
|
||||
|
||||
window.OCA.Viewer.compare(viewerFileInfo.value, _versions.find((v) => v.source === version.source))
|
||||
window.OCA.Viewer.compare(
|
||||
{ path: props.node!.path },
|
||||
_versions.find((v) => v.source === version.source),
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
import type { User } from '@nextcloud/e2e-test-server/cypress'
|
||||
import type { ShareSetting } from '../files_sharing/FilesSharingUtils.ts'
|
||||
|
||||
import { basename } from '@nextcloud/paths'
|
||||
import { triggerActionForFile } from '../files/FilesUtils.ts'
|
||||
import { createShare } from '../files_sharing/FilesSharingUtils.ts'
|
||||
|
||||
export function uploadThreeVersions(user: User, fileName: string) {
|
||||
|
|
@ -24,11 +26,10 @@ export function openVersionsPanel(fileName: string) {
|
|||
// Detect the versions list fetch
|
||||
cy.intercept('PROPFIND', '**/dav/versions/*/versions/**').as('getVersions')
|
||||
|
||||
// Open the versions tab
|
||||
cy.window().then((win) => {
|
||||
win.OCA.Files.Sidebar.setActiveTab('files_versions')
|
||||
win.OCA.Files.Sidebar.open(`/${fileName}`)
|
||||
})
|
||||
triggerActionForFile(basename(fileName), 'details')
|
||||
cy.get('[data-cy-sidebar]')
|
||||
.find('[aria-controls="tab-files_versions"]')
|
||||
.click()
|
||||
|
||||
// Wait for the versions list to be fetched
|
||||
cy.wait('@getVersions')
|
||||
|
|
@ -85,6 +86,8 @@ export function setupTestSharedFileFromUser(owner: User, randomFileName: string,
|
|||
cy.login(owner)
|
||||
cy.visit('/apps/files')
|
||||
createShare(randomFileName, recipient.userId, shareOptions)
|
||||
cy.logout()
|
||||
|
||||
cy.login(recipient)
|
||||
cy.visit('/apps/files')
|
||||
return cy.wrap(recipient)
|
||||
|
|
|
|||
Loading…
Reference in a new issue