Skip to content

Commit

Permalink
Use native html logic to prioritize loading
Browse files Browse the repository at this point in the history
Signed-off-by: Louis Chemineau <louis@chmn.me>
  • Loading branch information
artonge committed May 4, 2023
1 parent d17a77c commit 7d9a053
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 55 deletions.
82 changes: 28 additions & 54 deletions src/components/File.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,26 +33,35 @@
<div class="file__images">
<VideoIcon v-if="file.mime.includes('video')" class="video-icon" :size="64" />

<template v-if="distance < 1 && canLoad">
<!-- Alway load the small preview, unless the large on is already loaded -->
<img v-if="!loadedLarge && !errorSmall"
<!-- We have two img elements to load the small and large preview -->
<!-- Do not show the small preview if the larger one is loaded -->
<!-- Prioritize visible files -->
<!-- Load small preview first, then the larger one -->
<!-- Preload large preview for near visible files -->
<!-- Preload small preview for further away files -->
<template v-if="initialized">
<img v-if="!loadedLarge && (loadedSmall || (distance < 5 && !errorSmall))"
ref="imgSmall"
:key="`${file.basename}-small`"
:src="srcSmall"
:alt="file.basename"
:aria-describedby="ariaDescription"
:decoding="loadedSmall || isVisible ? 'sync' : 'async'"
:fetchpriority="loadedSmall || isVisible ? 'high' : 'low'"
:loading="loadedSmall || isVisible ? 'eager' : distance < 2 ? 'auto' : 'lazy'"
@load="onLoadSmall"
@error="onErrorSmall">

<!-- Load the large preview if the small on is loaded and if the image is visible by the user. -->
<img v-if="loadedLarge || ((loadedSmall || errorSmall) && distance === 0) && !errorLarge"
<img v-if="loadedLarge || ((isVisible || (distance < 2 && (loadedSmall || errorSmall))) && !errorLarge)"
ref="imgLarge"
:key="`${file.basename}-large`"
:src="srcLarge"
:alt="file.basename"
:decoding="loadedLarge || isVisible ? 'sync' : 'async'"
:fetchpriority="loadedLarge || isVisible ? 'high' : 'low'"
:loading="loadedLarge || isVisible ? 'auto' : 'lazy'"
:aria-describedby="ariaDescription"
@load="onLoadLarge"
@loadstart="onLoadStartLarge"
@error="onErrorLarge">
</template>
</div>
Expand Down Expand Up @@ -80,7 +89,7 @@ import { generateUrl } from '@nextcloud/router'
import { NcCheckboxRadioSwitch } from '@nextcloud/vue'
import UserConfig from '../mixins/UserConfig.js'
import Semaphore from '../utils/semaphoreWithPriority.js'
import { isCachedPreview } from '../services/PreviewService.js'
export default {
name: 'File',
Expand Down Expand Up @@ -108,22 +117,16 @@ export default {
type: Number,
required: true,
},
semaphore: {
type: Semaphore,
required: true,
},
},
data() {
return {
initialized: false,
isDestroyed: false,
loadedSmall: false,
loadedLarge: false,
loadingLarge: false,
errorSmall: false,
loadedLarge: false,
errorLarge: false,
canLoad: false,
semaphoreSymbol: null,
isDestroyed: false,
}
},
Expand Down Expand Up @@ -152,32 +155,23 @@ export default {
srcSmall() {
return this.getItemURL(64)
},
/** @return {boolean} */
isVisible() {
return this.distance === 0
},
},
async mounted() {
// Wait some time before starting the image loading logic.
// This prevent loading a lot of images when the user is simply scrolling.
setTimeout(async () => {
this.semaphoreSymbol = await this.semaphore.acquire(() => this.distance, this.file.fileid)
if (this.isDestroyed) {
this.releaseSemaphore()
return
}
[this.loadedSmall, this.loadedLarge] = await Promise.all([
await isCachedPreview(this.srcSmall),
await isCachedPreview(this.srcLarge),
])
if (this.distance === 0) {
this.canLoad = true
} else {
// Delay even more non visible images
setTimeout(async () => (this.canLoad = true), 2000)
}
}, 1000)
this.initialized = true
},
beforeDestroy() {
this.isDestroyed = true
this.releaseSemaphore()
// cancel any pending load
if (this.$refs.imgSmall !== undefined) {
Expand All @@ -195,30 +189,18 @@ export default {
onLoadSmall() {
this.loadedSmall = true
if (!this.loadingLarge) {
this.releaseSemaphore()
}
},
onLoadStartLarge() {
this.loadingLarge = true
},
onLoadLarge() {
this.loadedLarge = true
this.releaseSemaphore()
},
onErrorSmall() {
this.errorSmall = true
if (!this.loadingLarge) {
this.releaseSemaphore()
}
},
onErrorLarge() {
this.errorLarge = true
this.releaseSemaphore()
},
onToggle(value) {
Expand All @@ -233,14 +215,6 @@ export default {
return generateUrl(`/apps/photos/api/v1/preview/${this.file.fileid}?etag=${this.decodedEtag}&x=${size}&y=${size}`)
}
},
releaseSemaphore() {
if (this.semaphoreSymbol === null) {
return
}
this.semaphore.release(this.semaphoreSymbol)
this.semaphoreSymbol = null
},
},
}
Expand Down
110 changes: 110 additions & 0 deletions src/components/TimelineFile.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<!--
- @copyright Copyright (c) 2019 Louis Chmn <louis@chmn.me>
-
- @author Louis Chemineau <louis@chmn.me>
-
- @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/>.
-
-->

<template>
<h3 v-if="file.sectionHeader"
:id="`file-picker-section-header-${file.id}`"
class="section-header">
<b>{{ dateMonth }}</b>
{{ dateYear }}
</h3>
<File v-else
:file="actualFile"
:allow-selection="true"
:selected="isSelected"
:distance="distance"
@click="openViewer"
@select-toggled="onFileSelectToggle" />
</template>

<script>
import { mapGetters } from 'vuex'
import { isMobile } from '@nextcloud/vue'
import moment from '@nextcloud/moment'
import FilesSelectionMixin from '../mixins/FilesSelectionMixin.js'
import File from './File.vue'
export default {
name: 'TimelineFile',
components: {
File,
},
filters: {
},
mixins: [
FilesSelectionMixin,
isMobile,
],
props: {
file: {
type: Object,
required: true,
},
distance: {
type: Number,
required: true,
},
},
computed: {
...mapGetters([
'files',
]),
/** @return {boolean} */
isSelected() {
return this.selection[this.file.id] === true
},
/** @return {object} */
actualFile() {
return this.files[this.file.id]
},
/** @return {string} */
dateMonth() {
return moment(this.file.id, 'YYYYMM').format('MMMM')
},
/** @return {string} */
dateYear() {
return moment(this.file.id, 'YYYYMM').format('YYYY')
},
},
methods: {
openViewer(fileId) {
const file = this.files[fileId]
OCA.Viewer.open({
fileInfo: file,
list: Object.values(this.fileIdsByMonth).flat().map(fileId => this.files[fileId]),
loadMore: file.loadMore ? async () => await file.loadMore(true) : () => [],
canLoop: file.canLoop,
})
},
},
}
</script>
2 changes: 1 addition & 1 deletion src/components/VirtualScrolling.vue
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export default {
renderDistance: {
type: Number,
default: 5,
default: 10,
},
bottomBufferRatio: {
type: Number,
Expand Down
34 changes: 34 additions & 0 deletions src/services/PreviewService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* @copyright Copyright (c) 2023 Louis Chmn <louis@chmn.me>
*
* @author Louis Chmn <louis@chmn.me>
*
* @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/>.
*
*/

// The preview service worker cache name (see webpack config)
const SWCacheName = 'images'

/**
* Check if the preview is already cached by the service worker
* @param {string} previewUrl
*/
export const isCachedPreview = async function (previewUrl) {
const cache = await caches.open(SWCacheName)
const response = await cache.match(previewUrl)
return response !== undefined
}
3 changes: 3 additions & 0 deletions src/views/Timeline.vue
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ import AlbumPicker from '../components/Albums/AlbumPicker.vue'
import ActionFavorite from '../components/Actions/ActionFavorite.vue'
import ActionDownload from '../components/Actions/ActionDownload.vue'
import HeaderNavigation from '../components/HeaderNavigation.vue'
import { translate } from '@nextcloud/l10n'
export default {
name: 'Timeline',
Expand Down Expand Up @@ -267,6 +268,8 @@ export default {
this.fetchedFileIds = this.fetchedFileIds.filter(fileid => !fileIds.includes(fileid))
await this.deleteFiles(fileIds)
},
t: translate,
},
}
</script>
Expand Down

0 comments on commit 7d9a053

Please sign in to comment.