Skip to content

Commit

Permalink
Merge pull request #6529 from owncloud/space-images-as-thumbnails
Browse files Browse the repository at this point in the history
Load thumbnails for space images instead of the whole file
  • Loading branch information
Jan authored Mar 8, 2022
2 parents 26500e1 + 3e33ef5 commit 2002a93
Show file tree
Hide file tree
Showing 15 changed files with 223 additions and 44 deletions.
5 changes: 5 additions & 0 deletions changelog/unreleased/enhancement-load-space-images-as-preview
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Enhancement: Load space images as preview

We've added a new logic which renders space images as preview to minimize data traffic

https://github.com/owncloud/web/pull/6529
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@
ref="spaceImageInput"
type="file"
name="file"
multiple
tabindex="-1"
accept="image/*"
:accept="supportedSpaceImageMimeTypes"
@change="$_uploadImage_uploadImageSpace"
/>
<oc-list id="oc-spaces-actions-sidebar" class-name="oc-mt-s">
Expand Down Expand Up @@ -41,6 +40,7 @@ import UploadImage from '../../../mixins/spaces/actions/uploadImage'
import EditQuota from '../../../mixins/spaces/actions/editQuota'
import QuotaModal from '../../Spaces/QuotaModal.vue'
import ReadmeContentModal from '../../../components/Spaces/ReadmeContentModal.vue'
import { thumbnailService } from '../../../services'
export default {
name: 'SpaceActions',
Expand Down Expand Up @@ -80,6 +80,9 @@ export default {
},
quotaModalIsOpen() {
return this.$data.$_editQuota_modalOpen
},
supportedSpaceImageMimeTypes() {
return thumbnailService.getSupportedMimeTypes('image/').join(',')
}
},
methods: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<div class="oc-space-details-sidebar-image oc-text-center">
<oc-spinner v-if="loadImageTask.isRunning" />
<div v-else-if="spaceImage" class="oc-position-relative">
<img :src="'data:image/jpeg;base64,' + spaceImage" alt="" class="oc-mb-m" />
<img :src="spaceImage" alt="" class="oc-mb-m" />
</div>
<oc-icon
v-else
Expand Down Expand Up @@ -71,9 +71,11 @@ import Mixins from '../../../mixins'
import MixinResources from '../../../mixins/resources'
import { mapActions, mapGetters } from 'vuex'
import { useTask } from 'vue-concurrency'
import { buildWebDavSpacesPath } from '../../../helpers/resources'
import { buildResource, buildWebDavSpacesPath } from '../../../helpers/resources'
import { spaceRoleManager } from '../../../helpers/share'
import SpaceQuota from '../../SpaceQuota.vue'
import { loadPreview } from '../../../helpers/resource'
import { ImageDimension } from '../../../constants'
export default {
name: 'SpaceDetails',
Expand All @@ -96,12 +98,17 @@ export default {
.slice(webDavPathComponents.indexOf(ref.space.id) + 1)
.join('/')
const fileContents = yield ref.$client.files.getFileContents(
buildWebDavSpacesPath(ref.space.id, path),
{ responseType: 'arrayBuffer' }
)
const fileInfo = yield ref.$client.files.fileInfo(buildWebDavSpacesPath(ref.space.id, path))
const resource = buildResource(fileInfo)
spaceImage.value = Buffer.from(fileContents).toString('base64')
spaceImage.value = yield loadPreview({
resource,
isPublic: false,
dimensions: ImageDimension.Preview,
server: ref.configuration.server,
userId: ref.user.id,
token: ref.getToken
})
})
return { loadImageTask, spaceImage }
Expand All @@ -112,7 +119,7 @@ export default {
'currentFileOutgoingCollaborators',
'currentFileOutgoingLinks'
]),
...mapGetters(['user']),
...mapGetters(['user', 'getToken']),
space() {
return this.displayedItem.value
Expand Down Expand Up @@ -174,6 +181,17 @@ export default {
)
}
},
watch: {
'space.spaceImageData': {
handler(val) {
if (!val) {
return
}
this.loadImageTask.perform(this)
},
deep: true
}
},
mounted() {
this.loadImageTask.perform(this)
},
Expand Down
10 changes: 9 additions & 1 deletion packages/web-app-files/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import quickActions from './quickActions'
import store from './store'
import { FilterSearch, SDKSearch } from './search'
import { bus } from 'web-pkg/src/instance'
import { archiverService, Registry } from './services'
import { archiverService, thumbnailService, Registry } from './services'
import fileSideBars from './fileSideBars'
import { buildRoutes } from './router'
import get from 'lodash-es/get'
Expand Down Expand Up @@ -138,5 +138,13 @@ export default {
store.getters.configuration.server || window.location.origin,
get(store, 'getters.capabilities.files.archivers', [])
)
// FIXME: Remove mock data
thumbnailService.initialize(
get(store, 'getters.capabilities.files.thumbnail', {
enabled: true,
version: 'v0.1',
supportedMimeTypes: ['image/png', 'image/jpg', 'image/jpeg', 'image/gif', 'text/plain']
})
)
}
}
6 changes: 5 additions & 1 deletion packages/web-app-files/src/mixins/spaces/actions/setImage.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { clientService } from 'web-pkg/src/services'
import { mapMutations, mapActions, mapGetters } from 'vuex'
import { buildResource } from '../../../helpers/resources'
import { bus } from 'web-pkg/src/instance'
import { thumbnailService } from '../../../services'

export default {
computed: {
Expand All @@ -20,7 +21,10 @@ export default {
if (resources.length !== 1) {
return false
}
if (!resources[0].mimeType?.startsWith('image/')) {
if (!resources[0].mimeType) {
return false
}
if (!thumbnailService.isMimetypeSupported(resources[0].mimeType, true)) {
return false
}
return isLocationSpacesActive(this.$router, 'files-spaces-project')
Expand Down
12 changes: 12 additions & 0 deletions packages/web-app-files/src/mixins/spaces/actions/uploadImage.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { mapActions, mapGetters, mapMutations, mapState } from 'vuex'
import { clientService } from 'web-pkg/src/services'
import { bus } from 'web-pkg/src/instance'
import { thumbnailService } from '../../../services'

export default {
data: function () {
Expand Down Expand Up @@ -42,6 +43,17 @@ export default {
const graphClient = clientService.graphAuthenticated(this.configuration.server, this.getToken)
const file = ev.currentTarget.files[0]

if (!file) {
return
}

if (!thumbnailService.isMimetypeSupported(file.type, true)) {
return this.showMessage({
title: this.$gettext('The file type is unsupported'),
status: 'danger'
})
}

const extraHeaders = {}
if (file.lastModifiedDate) {
extraHeaders['X-OC-Mtime'] = '' + file.lastModifiedDate.getTime() / 1000
Expand Down
1 change: 1 addition & 0 deletions packages/web-app-files/src/services/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './archiver'
export * from './thumbnail'
export * from './cache'
export { default as Registry } from './registry'
33 changes: 33 additions & 0 deletions packages/web-app-files/src/services/thumbnail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
interface ThumbnailCapability {
enabled: boolean
version: string // version is just a major version, e.g. `v2`
supportedMimeTypes: string[]
}

export class ThumbnailService {
serverUrl: string
capability?: ThumbnailCapability

public initialize(thumbnailCapability: ThumbnailCapability = null): void {
this.capability = thumbnailCapability
}

public get available(): boolean {
return !!this.capability?.version
}

public isMimetypeSupported(mimeType: string, onlyImages = false) {
return onlyImages
? mimeType.startsWith('image/') && this.capability.supportedMimeTypes.includes(mimeType)
: this.capability.supportedMimeTypes.includes(mimeType)
}

public getSupportedMimeTypes(filter?: string) {
if (!filter) {
return this.capability.supportedMimeTypes
}
return this.capability.supportedMimeTypes.filter((mimeType) => mimeType.startsWith(filter))
}
}

export const thumbnailService = new ThumbnailService()
34 changes: 23 additions & 11 deletions packages/web-app-files/src/views/spaces/Project.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
:class="{ expanded: imageExpanded }"
class="space-overview-image oc-cursor-pointer"
alt=""
:src="'data:' + imageContent.mimeType + ';base64,' + imageContent.data"
:src="imageContent"
@click="toggleImageExpanded"
/>
</div>
Expand Down Expand Up @@ -49,7 +49,7 @@
name="file"
multiple
tabindex="-1"
accept="image/*"
:accept="supportedSpaceImageMimeTypes"
@change="$_uploadImage_uploadImageSpace"
/>
<ul class="oc-list oc-files-context-actions">
Expand Down Expand Up @@ -85,11 +85,13 @@
</div>
<p v-if="space.description" class="oc-mt-rm">{{ space.description }}</p>
<div>
<!-- eslint-disable vue/no-v-html -->
<div
ref="markdownContainer"
class="markdown-container"
v-html="markdownContent"
></div>
<!-- eslint-enable -->
<div v-if="showMarkdownCollapse" class="markdown-collapse oc-text-center oc-mt-s">
<oc-button appearance="raw" @click="toggleCollapseMarkdown">
<oc-icon :name="markdownCollapseIcon" />
Expand Down Expand Up @@ -149,6 +151,7 @@ import sanitizeHtml from 'sanitize-html'
import MixinAccessibleBreadcrumb from '../../mixins/accessibleBreadcrumb'
import { bus } from 'web-pkg/src/instance'
import { buildResource, buildSpace, buildWebDavSpacesPath } from '../../helpers/resources'
import { loadPreview } from '../../helpers/resource'
import ResourceTable, { determineSortFields } from '../../components/FilesList/ResourceTable.vue'
import { createLocationSpaces } from '../../router'
import { usePagination, useSort } from '../../composables'
Expand All @@ -173,6 +176,7 @@ import EditQuota from '../../mixins/spaces/actions/editQuota'
import EditReadmeContent from '../../mixins/spaces/actions/editReadmeContent'
import QuotaModal from '../../components/Spaces/QuotaModal.vue'
import ReadmeContentModal from '../../components/Spaces/ReadmeContentModal.vue'
import { thumbnailService } from '../../services'
const visibilityObserver = new VisibilityObserver()
Expand Down Expand Up @@ -312,6 +316,7 @@ export default {
'totalFilesSize',
'currentFileOutgoingCollaborators'
]),
...mapGetters(['user', 'getToken']),
selected: {
get() {
Expand Down Expand Up @@ -355,6 +360,9 @@ export default {
},
readmeContentModalIsOpen() {
return this.$data.$_editReadmeContent_modalOpen
},
supportedSpaceImageMimeTypes() {
return thumbnailService.getSupportedMimeTypes('image/').join(',')
}
},
watch: {
Expand All @@ -376,16 +384,20 @@ export default {
const path = webDavPathComponents
.slice(webDavPathComponents.indexOf(this.space.id) + 1)
.join('/')
this.$client.files
.getFileContents(buildWebDavSpacesPath(this.space.id, path), {
responseType: 'arrayBuffer'
})
.then((fileContents) => {
this.imageContent = {
data: Buffer.from(fileContents).toString('base64'),
mimeType: this.space.spaceImageData.file.mimeType
}
this.$client.files.fileInfo(buildWebDavSpacesPath(this.space.id, path)).then((data) => {
const resource = buildResource(data)
loadPreview({
resource,
isPublic: false,
dimensions: ImageDimension.Preview,
server: this.configuration.server,
userId: this.user.id,
token: this.getToken
}).then((imageBlob) => {
this.imageContent = imageBlob
})
})
},
deep: true
},
Expand Down
34 changes: 19 additions & 15 deletions packages/web-app-files/src/views/spaces/Projects.vue
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,7 @@
<img
v-if="imageContentObject[space.id]"
class="space-image oc-rounded-top"
:src="
'data:' +
imageContentObject[space.id]['mimeType'] +
';base64,' +
imageContentObject[space.id]['data']
"
:src="imageContentObject[space.id]['data']"
alt=""
/>
<oc-icon
Expand Down Expand Up @@ -168,16 +163,18 @@ import { computed } from '@vue/composition-api'
import { useStore } from 'web-pkg/src/composables'
import { useTask } from 'vue-concurrency'
import { createLocationSpaces } from '../../router'
import { mapMutations, mapActions } from 'vuex'
import { mapMutations, mapActions, mapGetters } from 'vuex'
import Rename from '../../mixins/spaces/actions/rename'
import Delete from '../../mixins/spaces/actions/delete'
import Disable from '../../mixins/spaces/actions/disable'
import Restore from '../../mixins/spaces/actions/restore'
import EditDescription from '../../mixins/spaces/actions/editDescription'
import ShowDetails from '../../mixins/spaces/actions/showDetails'
import UploadImage from '../../mixins/spaces/actions/uploadImage'
import { buildSpace, buildWebDavSpacesPath } from '../../helpers/resources'
import { buildResource, buildSpace, buildWebDavSpacesPath } from '../../helpers/resources'
import { clientService } from 'web-pkg/src/services'
import { loadPreview } from '../../helpers/resource'
import { ImageDimension } from '../../constants'
export default {
components: {
Expand Down Expand Up @@ -215,6 +212,7 @@ export default {
}
},
computed: {
...mapGetters(['user', 'getToken']),
hasCreatePermission() {
// @TODO
return true
Expand All @@ -238,17 +236,23 @@ export default {
const path = webDavPathComponents
.slice(webDavPathComponents.indexOf(space.id) + 1)
.join('/')
this.$client.files
.getFileContents(buildWebDavSpacesPath(space.id, path), {
responseType: 'arrayBuffer'
})
.then((fileContents) => {
this.$client.files.fileInfo(buildWebDavSpacesPath(space.id, path)).then((fileInfo) => {
const resource = buildResource(fileInfo)
loadPreview({
resource,
isPublic: false,
dimensions: ImageDimension.Preview,
server: this.configuration.server,
userId: this.user.id,
token: this.getToken
}).then((imageBlob) => {
this.$set(this.imageContentObject, space.id, {
fileId: space.spaceImageData.id,
data: Buffer.from(fileContents).toString('base64'),
mimeType: space.spaceImageData.file.mimeType
data: imageBlob
})
})
})
}
},
deep: true
Expand Down
Loading

0 comments on commit 2002a93

Please sign in to comment.