From 014a57e54174ce9a8f2c55beafad2dd8d1c6a9d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6?= Date: Tue, 4 Apr 2023 08:10:43 +0200 Subject: [PATCH] fix: improved preview handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: John Molakvoæ --- apps/files/src/components/BreadCrumbs.vue | 1 + apps/files/src/components/FileEntry.vue | 102 +++++------ apps/files/src/components/FilesListHeader.vue | 16 ++ .../src/components/FilesListHeaderButton.vue | 12 +- .../src/components/FilesListNotVirtual.vue | 167 ++++++++++++++++++ .../files/src/components/FilesListVirtual.vue | 15 +- apps/files/src/services/PreviewService.ts | 37 ++++ apps/files_trashbin/src/services/trashbin.ts | 2 +- 8 files changed, 287 insertions(+), 65 deletions(-) create mode 100644 apps/files/src/components/FilesListNotVirtual.vue create mode 100644 apps/files/src/services/PreviewService.ts diff --git a/apps/files/src/components/BreadCrumbs.vue b/apps/files/src/components/BreadCrumbs.vue index dcedeab01729e..d2f8610e9ca87 100644 --- a/apps/files/src/components/BreadCrumbs.vue +++ b/apps/files/src/components/BreadCrumbs.vue @@ -40,6 +40,7 @@ export default Vue.extend({ computed: { dirs() { const cumulativePath = (acc) => (value) => (acc += `${value}/`) + // Generate a cumulative path for each path segment: ['/', '/foo', '/foo/bar', ...] etc const paths = this.path.split('/').filter(Boolean).map(cumulativePath('/')) // Strip away trailing slash return ['/', ...paths.map(path => path.replace(/^(.+)\/$/, '$1'))] diff --git a/apps/files/src/components/FileEntry.vue b/apps/files/src/components/FileEntry.vue index 71a0c4e2a6572..a4b373a7d9df1 100644 --- a/apps/files/src/components/FileEntry.vue +++ b/apps/files/src/components/FileEntry.vue @@ -32,7 +32,7 @@ - + @@ -61,7 +61,8 @@ - import { debounce } from 'debounce' -import { Folder, File, getFileActions, formatFileSize } from '@nextcloud/files' +import { Folder, File, formatFileSize } from '@nextcloud/files' import { Fragment } from 'vue-fragment' import { join } from 'path' -import { mapState } from 'pinia' import { showError } from '@nextcloud/dialogs' import { translate } from '@nextcloud/l10n' import FileIcon from 'vue-material-design-icons/File.vue' @@ -113,17 +113,15 @@ import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadi import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js' import Vue from 'vue' +import { isCachedPreview } from '../services/PreviewService' +import { getFileActions } from '../services/FileAction' import { useFilesStore } from '../store/files' +import { UserConfig } from '../types' import { useSelectionStore } from '../store/selection' import { useUserConfigStore } from '../store/userconfig' import CustomElementRender from './CustomElementRender.vue' import CustomSvgIconRender from './CustomSvgIconRender.vue' import logger from '../logger.js' -import { UserConfig } from '../types' - - -// The preview service worker cache name (see webpack config) -const SWCacheName = 'previews' // The registered actions list const actions = getFileActions() @@ -156,6 +154,10 @@ export default Vue.extend({ type: Object, required: true, }, + index: { + type: Number, + required: true, + }, }, setup() { @@ -314,6 +316,7 @@ export default Vue.extend({ // Restore default tabindex this.$el.parentNode.style.display = '' }, + /** * When the source changes, reset the preview * and fetch the new one. @@ -335,11 +338,7 @@ export default Vue.extend({ this.fetchAndApplyPreview() }, 150, false) - // ⚠ Init img on mount and - // not when the module is imported to - // avoid sharing between recycled components - this.img = null - + // Fetch the preview on init this.debounceIfNotCached() }, @@ -354,7 +353,7 @@ export default Vue.extend({ } // Check if we already have this preview cached - const isCached = await this.isCachedPreview(this.previewUrl) + const isCached = await isCachedPreview(this.previewUrl) if (isCached) { this.backgroundImage = `url(${this.previewUrl})` this.backgroundFailed = false @@ -372,19 +371,37 @@ export default Vue.extend({ } // If any image is being processed, reset it - if (this.img) { + if (this.previewPromise) { this.clearImg() } - this.img = new Image() - this.img.fetchpriority = this.active ? 'high' : 'auto' - this.img.onload = () => { - this.backgroundImage = `url(${this.previewUrl})` - } - this.img.onerror = () => { - this.backgroundFailed = true - } - this.img.src = this.previewUrl + // Ensure max 5 previews are being fetched at the same time + const controller = new AbortController() + + // Store the promise to be able to cancel it + this.previewPromise = new CancelablePromise((resolve, reject, onCancel) => { + const img = new Image() + // If active, load the preview with higher priority + img.fetchpriority = this.active ? 'high' : 'auto' + img.onload = () => { + this.backgroundImage = `url(${this.previewUrl})` + this.backgroundFailed = false + resolve(img) + } + img.onerror = () => { + this.backgroundFailed = true + reject(img) + } + img.src = this.previewUrl + + // Image loading has been canceled + onCancel(() => { + img.onerror = null + img.onload = null + img.src = '' + controller.abort() + }) + }) }, resetState() { @@ -402,23 +419,10 @@ export default Vue.extend({ this.backgroundImage = '' this.backgroundFailed = false - if (this.img) { - // Do not fail on cancel - this.img.onerror = null - this.img.src = '' + if (this.previewPromise) { + this.previewPromise.cancel() + this.previewPromise = null } - - this.img = null - }, - - isCachedPreview(previewUrl) { - return caches.open(SWCacheName) - .then(function(cache) { - return cache.match(previewUrl) - .then(function(response) { - return !!response // or `return response ? true : false`, or similar. - }) - }) }, hashCode(str) { @@ -464,23 +468,21 @@ tr { /* Preview not loaded animation effect */ .files-list__row-icon-preview:not([style*='background']) { - background: linear-gradient(110deg, var(--color-loading-dark) 0%, var(--color-loading-dark) 25%, var(--color-loading-light) 50%, var(--color-loading-dark) 75%, var(--color-loading-dark) 100%); - background-size: 400%; - animation: preview-gradient-slide 1.2s ease-in-out infinite; + background: var(--color-loading-dark); + // animation: preview-gradient-fade 1.2s ease-in-out infinite; } diff --git a/apps/files/src/components/FilesListHeader.vue b/apps/files/src/components/FilesListHeader.vue index 184ca7aa30ee2..f0af8c531dc64 100644 --- a/apps/files/src/components/FilesListHeader.vue +++ b/apps/files/src/components/FilesListHeader.vue @@ -88,6 +88,12 @@ export default Vue.extend({ FilesListHeaderActions, }, + provide() { + return { + toggleSortBy: this.toggleSortBy, + } + }, + props: { isSizeAvailable: { type: Boolean, @@ -186,6 +192,16 @@ export default Vue.extend({ } }, + toggleSortBy(key) { + // If we're already sorting by this key, flip the direction + if (this.sortingMode === key) { + this.sortingStore.toggleSortingDirection(this.currentView.id) + return + } + // else sort ASC by this new key + this.sortingStore.setSortingBy(key, this.currentView.id) + }, + t: translate, }, }) diff --git a/apps/files/src/components/FilesListHeaderButton.vue b/apps/files/src/components/FilesListHeaderButton.vue index cde77ff21fea3..fc9b7330956f2 100644 --- a/apps/files/src/components/FilesListHeaderButton.vue +++ b/apps/files/src/components/FilesListHeaderButton.vue @@ -51,6 +51,8 @@ export default Vue.extend({ NcButton, }, + inject: ['toggleSortBy'], + props: { name: { type: String, @@ -97,16 +99,6 @@ export default Vue.extend({ }) }, - toggleSortBy(key) { - // If we're already sorting by this key, flip the direction - if (this.sortingMode === key) { - this.sortingStore.toggleSortingDirection(this.currentView.id) - return - } - // else sort ASC by this new key - this.sortingStore.setSortingBy(key, this.currentView.id) - }, - t: translate, }, }) diff --git a/apps/files/src/components/FilesListNotVirtual.vue b/apps/files/src/components/FilesListNotVirtual.vue new file mode 100644 index 0000000000000..edfb2bd820a34 --- /dev/null +++ b/apps/files/src/components/FilesListNotVirtual.vue @@ -0,0 +1,167 @@ + + + + + + diff --git a/apps/files/src/components/FilesListVirtual.vue b/apps/files/src/components/FilesListVirtual.vue index e6cd60c2cadad..7891128a1eb0f 100644 --- a/apps/files/src/components/FilesListVirtual.vue +++ b/apps/files/src/components/FilesListVirtual.vue @@ -31,8 +31,12 @@ list-class="files-list__body" list-tag="tbody" role="table"> -