Merge pull request #12549 from nextcloud/fix/file-preview-binding

Fix: replace v-bind with object prop passing
This commit is contained in:
Dorra 2024-06-20 11:24:33 +02:00 committed by GitHub
commit 38bfcad791
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 93 additions and 188 deletions

View file

@ -395,7 +395,7 @@ describe('Message.vue', () => {
},
file: {
component: FilePreview,
props: params.file,
props: { file: params.file },
},
}
)
@ -423,7 +423,7 @@ describe('Message.vue', () => {
},
file: {
component: FilePreview,
props: params.file,
props: { file: params.file },
},
}
)

View file

@ -268,11 +268,12 @@ export default {
} else if (type === 'file' && mimetype !== 'text/vcard') {
richParameters[p] = {
component: FilePreview,
props: Object.assign({
props: {
token: this.message.token,
itemType,
referenceId: this.message.referenceId,
}, this.message.messageParameters[p]),
file: this.message.messageParameters[p],
},
}
} else if (type === 'deck-card') {
richParameters[p] = {

View file

@ -51,14 +51,16 @@ describe('FilePreview.vue', () => {
propsData = {
token: 'TOKEN',
id: '123',
name: 'test.jpg',
path: 'path/to/test.jpg',
size: '128',
etag: '1872ade88f3013edeb33decd74a4f947',
permissions: '15',
mimetype: 'image/jpeg',
previewAvailable: 'yes',
file: {
id: '123',
name: 'test.jpg',
path: 'path/to/test.jpg',
size: '128',
etag: '1872ade88f3013edeb33decd74a4f947',
permissions: '15',
mimetype: 'image/jpeg',
'preview-available': 'yes',
},
}
})
@ -96,7 +98,7 @@ describe('FilePreview.vue', () => {
})
test('renders file preview for guests', async () => {
propsData.link = 'https://localhost/nc-webroot/s/xtokenx'
propsData.file.link = 'https://localhost/nc-webroot/s/xtokenx'
getUserIdMock.mockClear().mockReturnValue(null)
const wrapper = shallowMount(FilePreview, {
@ -172,10 +174,10 @@ describe('FilePreview.vue', () => {
}],
}))
propsData.id = 'temp-123'
propsData.index = 'index-1'
propsData.uploadId = 1000
propsData.localUrl = 'blob:XYZ'
propsData.file.id = 'temp-123'
propsData.file.index = 'index-1'
propsData.file.uploadId = 1000
propsData.file.localUrl = 'blob:XYZ'
const wrapper = shallowMount(FilePreview, {
localVue,
@ -224,7 +226,7 @@ describe('FilePreview.vue', () => {
})
test('renders generic mime type icon for unknown mime types', async () => {
propsData.previewAvailable = 'no'
propsData.file['preview-available'] = 'no'
OC.MimeType.getIconUrl.mockReturnValueOnce(imagePath('core', 'image/jpeg'))
const wrapper = shallowMount(FilePreview, {
@ -244,12 +246,12 @@ describe('FilePreview.vue', () => {
describe('gif rendering', () => {
beforeEach(() => {
propsData.mimetype = 'image/gif'
propsData.name = 'test %20.gif'
propsData.path = 'path/to/test %20.gif'
propsData.file.mimetype = 'image/gif'
propsData.file.name = 'test %20.gif'
propsData.file.path = 'path/to/test %20.gif'
})
test('directly renders small GIF files', async () => {
propsData.size = '128'
propsData.file.size = '128'
const wrapper = shallowMount(FilePreview, {
localVue,
@ -265,8 +267,8 @@ describe('FilePreview.vue', () => {
})
test('directly renders small GIF files (absolute path)', async () => {
propsData.size = '128'
propsData.path = '/path/to/test %20.gif'
propsData.file.size = '128'
propsData.file.path = '/path/to/test %20.gif'
const wrapper = shallowMount(FilePreview, {
localVue,
@ -282,8 +284,8 @@ describe('FilePreview.vue', () => {
})
test('directly renders small GIF files for guests', async () => {
propsData.size = '128'
propsData.link = 'https://localhost/nc-webroot/s/xtokenx'
propsData.file.size = '128'
propsData.file.link = 'https://localhost/nc-webroot/s/xtokenx'
getUserIdMock.mockClear().mockReturnValue(null)
const wrapper = shallowMount(FilePreview, {
@ -296,12 +298,12 @@ describe('FilePreview.vue', () => {
expect(wrapper.element.tagName).toBe('A')
expect(wrapper.find('img').attributes('src'))
.toBe(propsData.link + '/download/test%20%2520.gif')
.toBe(propsData.file.link + '/download/test%20%2520.gif')
})
test('renders static preview for big GIF files', async () => {
// 4 MB, bigger than max from capability (3 MB)
propsData.size = '4194304'
propsData.file.size = '4194304'
const wrapper = shallowMount(FilePreview, {
localVue,
@ -425,9 +427,9 @@ describe('FilePreview.vue', () => {
describe('play icon for video', () => {
beforeEach(() => {
propsData.mimetype = 'video/mp4'
propsData.name = 'test.mp4'
propsData.path = 'path/to/test.mp4'
propsData.file.mimetype = 'video/mp4'
propsData.file.name = 'test.mp4'
propsData.file.path = 'path/to/test.mp4'
// viewer needs to be available
OCA.Viewer = {
@ -460,19 +462,19 @@ describe('FilePreview.vue', () => {
test('does not render play icon for direct renders', async () => {
// gif is directly rendered
propsData.mimetype = 'image/gif'
propsData.name = 'test.gif'
propsData.path = 'path/to/test.gif'
propsData.file.mimetype = 'image/gif'
propsData.file.name = 'test.gif'
propsData.file.path = 'path/to/test.gif'
await testPlayButtonVisible(false)
})
test('render play icon gif previews with big size', async () => {
// gif is directly rendered
propsData.mimetype = 'image/gif'
propsData.name = 'test.gif'
propsData.path = 'path/to/test.gif'
propsData.size = '10000000' // bigger than default max
propsData.file.mimetype = 'image/gif'
propsData.file.name = 'test.gif'
propsData.file.path = 'path/to/test.gif'
propsData.file.size = '10000000' // bigger than default max
await testPlayButtonVisible(true)
})
@ -502,9 +504,9 @@ describe('FilePreview.vue', () => {
test('does not render play icon for non-videos', async () => {
// viewer supported, but not a video
propsData.mimetype = 'image/png'
propsData.name = 'test.png'
propsData.path = 'path/to/test.png'
propsData.file.mimetype = 'image/png'
propsData.file.name = 'test.png'
propsData.file.path = 'path/to/test.png'
await testPlayButtonVisible(false)
})
})

View file

@ -46,7 +46,7 @@
tabindex="1"
type="primary"
:aria-label="removeAriaLabel"
@click="$emit('remove-file', id)">
@click="$emit('remove-file', file.id)">
<template #icon>
<Close />
</template>
@ -103,13 +103,15 @@ export default {
type: String,
required: true,
},
/**
* File id
* File object
*/
id: {
type: String,
file: {
type: Object,
required: true,
},
/**
* Reference id from the message
*/
@ -117,81 +119,6 @@ export default {
type: String,
default: '',
},
/**
* File name
*/
name: {
type: String,
required: true,
},
/**
* File path relative to the user's home storage,
* or link share root, includes the file name.
*/
path: {
type: String,
default: '',
},
/**
* File size in bytes
*/
size: {
type: String,
default: '-1',
},
/**
* Download link
*/
link: {
type: String,
default: '',
},
/**
* Mime type
*/
mimetype: {
type: String,
default: '',
},
/**
* File ETag
*/
etag: {
type: String,
default: '',
},
/**
* File ETag
*/
permissions: {
type: String,
default: '0',
},
/**
* Whether a preview is available, string "yes" for yes
* otherwise the string "no"
*/
// FIXME: use booleans here
previewAvailable: {
type: String,
default: 'no',
},
/**
* If preview and metadata are available, return width
*/
width: {
type: String,
default: null,
},
/**
* If preview and metadata are available, return height
*/
height: {
type: String,
default: null,
},
/**
* Whether to render a small preview to embed in replies
@ -200,26 +127,7 @@ export default {
type: Boolean,
default: false,
},
/**
* Upload id from the file upload store.
*
* In case this component is used to display a file that is being uploaded
* this parameter is used to access the file upload status in the store
*/
uploadId: {
type: Number,
default: null,
},
/**
* File upload index from the file upload store.
*
* In case this component is used to display a file that is being uploaded
* this parameter is used to access the file upload status in the store
*/
index: {
type: String,
default: '',
},
/**
* Whether the container is the upload editor.
* True if this component is used in the upload editor.
@ -229,13 +137,6 @@ export default {
type: Boolean,
default: false,
},
/**
* The link to the file for displaying it in the preview
*/
localUrl: {
type: String,
default: '',
},
rowLayout: {
type: Boolean,
@ -281,9 +182,9 @@ export default {
// is not easily recognizable, when:
return (
// the file is not an image
!this.mimetype.startsWith('image/')
!this.file.mimetype.startsWith('image/')
// the image has no preview (ex: disabled on server)
|| (this.previewAvailable !== 'yes' && !this.localUrl)
|| (this.file['preview-available'] !== 'yes' && !this.file.localUrl)
// the preview failed loading
|| this.failed
// always show in upload editor
@ -292,11 +193,11 @@ export default {
},
fileDetail() {
return this.name
return this.file.name
},
fallbackLocalUrl() {
if (!this.mimetype.startsWith('image/') && !this.mimetype.startsWith('video/')) {
if (!this.file.mimetype.startsWith('image/') && !this.file.mimetype.startsWith('video/')) {
return undefined
}
return this.$store.getters.getLocalUrl(this.referenceId)
@ -308,7 +209,7 @@ export default {
return null
}
return {
content: this.name,
content: this.file.name,
delay: { show: 500 },
placement: 'left',
}
@ -329,24 +230,24 @@ export default {
return
} else if (this.isVoiceMessage && !this.isSharedItems) {
return {
name: this.name,
path: this.path,
link: this.link,
name: this.file.name,
path: this.file.path,
link: this.file.link,
}
}
return {
href: this.link,
href: this.file.link,
target: '_blank',
rel: 'noopener noreferrer',
}
},
defaultIconUrl() {
return OC.MimeType.getIconUrl(this.mimetype) || imagePath('core', 'filetypes/file')
return OC.MimeType.getIconUrl(this.file.mimetype) || imagePath('core', 'filetypes/file')
},
mediumPreview() {
return !this.mimetype.startsWith('image/') && !this.mimetype.startsWith('video/')
return !this.file.mimetype.startsWith('image/') && !this.file.mimetype.startsWith('video/')
},
previewImageClass() {
@ -361,7 +262,7 @@ export default {
if (this.failed || this.previewType === PREVIEW_TYPE.MIME_ICON || this.rowLayout) {
classes += 'mimeicon'
} else if (this.previewAvailable === 'yes') {
} else if (this.file['preview-available'] === 'yes') {
classes += 'media'
}
@ -370,7 +271,7 @@ export default {
imageContainerStyle() {
// Fallback for loading mimeicons (preview for audio files is not provided)
if (this.previewAvailable !== 'yes' || this.mimetype.startsWith('audio/')) {
if (this.file['preview-available'] !== 'yes' || this.file.mimetype.startsWith('audio/')) {
return {
width: this.smallPreview ? '32px' : '128px',
height: this.smallPreview ? '32px' : '128px',
@ -381,7 +282,7 @@ export default {
const heightConstraint = this.smallPreview ? 32 : (this.mediumPreview ? 192 : 384)
// Fallback when no metadata available
if (!this.width || !this.height) {
if (!this.file.width || !this.file.height) {
return {
width: widthConstraint + 'px',
height: heightConstraint + 'px',
@ -389,13 +290,13 @@ export default {
}
const sizeMultiplicator = Math.min(
(heightConstraint > parseInt(this.height, 10) ? 1 : (heightConstraint / parseInt(this.height, 10))),
(widthConstraint > parseInt(this.width, 10) ? 1 : (widthConstraint / parseInt(this.width, 10)))
(heightConstraint > parseInt(this.file.height, 10) ? 1 : (heightConstraint / parseInt(this.file.height, 10))),
(widthConstraint > parseInt(this.file.width, 10) ? 1 : (widthConstraint / parseInt(this.file.width, 10)))
)
return {
width: parseInt(this.width, 10) * sizeMultiplicator + 'px',
aspectRatio: this.width + '/' + this.height,
width: parseInt(this.file.width, 10) * sizeMultiplicator + 'px',
aspectRatio: this.file.width + '/' + this.file.height,
}
},
@ -408,10 +309,10 @@ export default {
return PREVIEW_TYPE.TEMPORARY
}
if (this.previewAvailable !== 'yes') {
if (this.file['preview-available'] !== 'yes') {
return PREVIEW_TYPE.MIME_ICON
}
if (this.mimetype === 'image/gif' && parseInt(this.size, 10) <= this.maxGifSize) {
if (this.file.mimetype === 'image/gif' && parseInt(this.file.size, 10) <= this.maxGifSize) {
return PREVIEW_TYPE.DIRECT
}
@ -422,20 +323,20 @@ export default {
const userId = this.$store.getters.getUserId()
if (this.previewType === PREVIEW_TYPE.TEMPORARY) {
return this.localUrl
return this.file.localUrl
}
if (this.fallbackLocalUrl) {
return this.fallbackLocalUrl
}
if (this.previewType === PREVIEW_TYPE.MIME_ICON || this.rowLayout) {
return OC.MimeType.getIconUrl(this.mimetype)
return OC.MimeType.getIconUrl(this.file.mimetype)
}
// whether to embed/render the file directly
if (this.previewType === PREVIEW_TYPE.DIRECT) {
// return direct image
if (userId === null) {
// guest mode, use public link download URL
return this.link + '/download/' + encodePath(this.name)
return this.file.link + '/download/' + encodePath(this.file.name)
} else {
// use direct DAV URL
return generateRemoteUrl(`dav/files/${userId}`) + encodePath(this.internalAbsolutePath)
@ -451,14 +352,14 @@ export default {
if (userId === null) {
// guest mode: grab token from the link URL
// FIXME: use a cleaner way...
const token = this.link.slice(this.link.lastIndexOf('/') + 1)
const token = this.file.link.slice(this.file.link.lastIndexOf('/') + 1)
return generateUrl('/apps/files_sharing/publicpreview/{token}?x=-1&y={height}&a=1', {
token,
height: previewSize,
})
} else {
return generateUrl('/core/preview?fileId={fileId}&x=-1&y={height}&a=1', {
fileId: this.id,
fileId: this.file.id,
height: previewSize,
})
}
@ -471,7 +372,7 @@ export default {
const availableHandlers = OCA.Viewer.availableHandlers
for (let i = 0; i < availableHandlers.length; i++) {
if (availableHandlers[i]?.mimes?.includes && availableHandlers[i].mimes.includes(this.mimetype)) {
if (availableHandlers[i]?.mimes?.includes && availableHandlers[i].mimes.includes(this.file.mimetype)) {
return true
}
}
@ -490,23 +391,23 @@ export default {
}
// videos only display a preview, so always show a button if playable
return this.mimetype === 'image/gif' || this.mimetype.startsWith('video/')
return this.file.mimetype === 'image/gif' || this.file.mimetype.startsWith('video/')
},
internalAbsolutePath() {
return this.path.startsWith('/') ? this.path : '/' + this.path
return this.file.path.startsWith('/') ? this.file.path : '/' + this.file.path
},
isTemporaryUpload() {
return this.id.startsWith('temp') && this.index && this.uploadId
return this.file.id.startsWith('temp') && this.file.index && this.file.uploadId
},
uploadFile() {
return this.$store.getters.getUploadFile(this.uploadId, this.index)
return this.$store.getters.getUploadFile(this.file.uploadId, this.file.index)
},
upload() {
return this.uploadManager?.queue.find(item => item._source.includes(this.uploadFile.sharePath))
return this.uploadManager?.queue.find(item => item._source.includes(this.uploadFile?.sharePath))
},
uploadProgress() {
@ -528,11 +429,11 @@ export default {
showUploadProgress() {
return this.isTemporaryUpload && !this.isUploadEditor
&& ['shared', 'sharing', 'successUpload', 'uploading'].includes(this.uploadFile?.status)
&& ['shared', 'sharing', 'successUpload', 'uploading', 'failedUpload'].includes(this.uploadFile?.status)
},
hasTemporaryImageUrl() {
return this.mimetype.startsWith('image/') && this.localUrl
return this.file.mimetype.startsWith('image/') && this.file.localUrl
},
wrapperTabIndex() {
@ -540,7 +441,7 @@ export default {
},
removeAriaLabel() {
return t('spreed', 'Remove {fileName}', { fileName: this.name })
return t('spreed', 'Remove {fileName}', { fileName: this.file.name })
},
},
@ -576,7 +477,7 @@ export default {
t,
handleClick(event) {
if (this.isUploadEditor) {
this.$emit('remove-file', this.id)
this.$emit('remove-file', this.file.id)
return
}
@ -600,9 +501,9 @@ export default {
return getRevertedList(messages)
}
this.openViewer(this.internalAbsolutePath, list, this, loadMore)
this.openViewer(this.internalAbsolutePath, list, this.file, loadMore)
} else {
this.openViewer(this.internalAbsolutePath, [this], this)
this.openViewer(this.internalAbsolutePath, [this.file], this.file)
}
},

View file

@ -31,7 +31,7 @@
<FilePreview :key="file[1].temporaryMessage.id"
:token="token"
is-upload-editor
v-bind="file[1].temporaryMessage.messageParameters.file"
:file="file[1].temporaryMessage.messageParameters.file"
@remove-file="handleRemoveFileFromSelection" />
</template>
<div :key="'addMore'"

View file

@ -151,11 +151,12 @@ export default {
if (type === 'file') {
richParameters[p] = {
component: FilePreview,
props: Object.assign({
props: {
token: this.message.token,
smallPreview: true,
rowLayout: !this.message.messageParameters[p].mimetype.startsWith('image/'),
}, this.message.messageParameters[p]),
file: this.message.messageParameters[p],
},
}
} else {
richParameters[p] = {

View file

@ -40,7 +40,7 @@
:row-layout="!isMedia"
:item-type="type"
is-shared-items
v-bind="item.messageParameters.file" />
:file="item.messageParameters.file" />
</template>
</div>
</template>