From 77a7e6b75d0114d0840fda9498306a9576438671 Mon Sep 17 00:00:00 2001 From: Alex Ackermann Date: Fri, 27 Jun 2025 13:12:29 +0200 Subject: [PATCH 01/13] feat: only request thumnail if server support is guaranteed --- .../src/helpers/resource/functions.ts | 1 + .../web-client/src/helpers/resource/types.ts | 1 + .../web-client/src/webdav/constants/dav.ts | 4 +- .../src/services/preview/previewService.ts | 55 ++----------------- .../unit/services/previewService.spec.ts | 3 +- 5 files changed, 12 insertions(+), 52 deletions(-) diff --git a/packages/web-client/src/helpers/resource/functions.ts b/packages/web-client/src/helpers/resource/functions.ts index d55f0a39f5..e0682aae5d 100644 --- a/packages/web-client/src/helpers/resource/functions.ts +++ b/packages/web-client/src/helpers/resource/functions.ts @@ -173,6 +173,7 @@ export function buildResource( image: convertObjectToCamelCaseKeys(resource.props[DavProperty.Image]), photo: convertObjectToCamelCaseKeys(resource.props[DavProperty.Photo]), extraProps, + hasPreview: () => resource.props[DavProperty.HasPreview] === '1', canUpload: function () { return this.permissions.indexOf(DavPermission.FolderCreateable) >= 0 }, diff --git a/packages/web-client/src/helpers/resource/types.ts b/packages/web-client/src/helpers/resource/types.ts index 8246839e14..1927a26fad 100644 --- a/packages/web-client/src/helpers/resource/types.ts +++ b/packages/web-client/src/helpers/resource/types.ts @@ -70,6 +70,7 @@ export interface Resource { remoteItemId?: string remoteItemPath?: string + hasPreview?(): boolean canCreate?(): boolean canUpload?({ user }: { user?: User }): boolean canDownload?(): boolean diff --git a/packages/web-client/src/webdav/constants/dav.ts b/packages/web-client/src/webdav/constants/dav.ts index eac88f5e97..425165e6f6 100644 --- a/packages/web-client/src/webdav/constants/dav.ts +++ b/packages/web-client/src/webdav/constants/dav.ts @@ -91,6 +91,7 @@ const DavPropertyMapping = { Highlights: defString('highlights' as const), MetaPathForUser: defString('meta-path-for-user' as const), RemoteItemId: defString('remote-item-id' as const), + HasPreview: defString('has-preview' as const), ShareId: defString('shareid' as const), ShareRoot: defString('shareroot' as const), @@ -148,7 +149,8 @@ export abstract class DavProperties { DavProperty.Audio, DavProperty.Location, DavProperty.Image, - DavProperty.Photo + DavProperty.Photo, + DavProperty.HasPreview ] static readonly PublicLink: DavPropertyValue[] = DavProperties.Default.concat([ diff --git a/packages/web-pkg/src/services/preview/previewService.ts b/packages/web-pkg/src/services/preview/previewService.ts index f29b19aff2..ded3acf90f 100644 --- a/packages/web-pkg/src/services/preview/previewService.ts +++ b/packages/web-pkg/src/services/preview/previewService.ts @@ -4,9 +4,7 @@ import { ClientService } from '../client' import { encodePath } from '../../utils' import { isPublicSpaceResource } from '@opencloud-eu/web-client' import { BuildQueryStringOptions, LoadPreviewOptions } from '.' -import { AuthStore, CapabilityStore, ConfigStore, UserStore } from '../../composables' - -// @ts-ignore +import { AuthStore, ConfigStore, UserStore } from '../../composables' // @ts-ignore import { stringify } from 'qs' export class PreviewService { @@ -14,71 +12,28 @@ export class PreviewService { configStore: ConfigStore userStore: UserStore authStore: AuthStore - capabilityStore: CapabilityStore - - capability?: CapabilityStore['capabilities']['files']['thumbnail'] constructor({ clientService, userStore, authStore, - capabilityStore, configStore }: { clientService: ClientService userStore: UserStore authStore: AuthStore - capabilityStore: CapabilityStore configStore: ConfigStore }) { this.clientService = clientService this.userStore = userStore this.authStore = authStore this.configStore = configStore - - this.capability = capabilityStore.filesThumbnail || { - enabled: true, - version: 'v0.1', - supportedMimeTypes: [ - 'image/gif', - 'image/png', - 'image/jpeg', - 'image/tiff', - 'image/bmp', - 'image/x-ms-bmp', - 'application/vnd.geogebra.slides', - 'application/vnd.geogebra.pinboard' - ] - } - } - - private get available(): boolean { - return !!this.capability?.version - } - - private get supportedMimeTypes() { - return this.capability?.supportedMimeTypes || [] } private get user() { return this.userStore.user } - public isMimetypeSupported(mimeType: string, onlyImages = false) { - if (!this.supportedMimeTypes.length) { - return true - } - const mimeTypes = this.getSupportedMimeTypes(onlyImages ? 'image/' : null) - return mimeTypes.includes(mimeType) - } - - public getSupportedMimeTypes(filter?: string) { - if (!filter) { - return this.supportedMimeTypes - } - return this.supportedMimeTypes.filter((mimeType) => mimeType.startsWith(filter)) - } - public async loadPreview( options: LoadPreviewOptions, cached = false, @@ -86,10 +41,12 @@ export class PreviewService { signal?: AbortSignal ): Promise { const { space, resource } = options - const serverSupportsPreview = this.available && this.isMimetypeSupported(resource.mimeType) const resourceSupportsPreview = - resource.type !== 'folder' && resource.extension && resource.canDownload() - if (!serverSupportsPreview || !resourceSupportsPreview) { + resource.type !== 'folder' && + resource.extension && + resource.canDownload() && + resource.hasPreview() + if (!resourceSupportsPreview) { return undefined } diff --git a/packages/web-pkg/tests/unit/services/previewService.spec.ts b/packages/web-pkg/tests/unit/services/previewService.spec.ts index a7b91cb0ac..045606e8d9 100644 --- a/packages/web-pkg/tests/unit/services/previewService.spec.ts +++ b/packages/web-pkg/tests/unit/services/previewService.spec.ts @@ -208,8 +208,7 @@ const getWrapper = ({ configStore, clientService, userStore, - authStore, - capabilityStore + authStore }), clientService } From 7cca898a2fa3f4a9773a3b07963726e21b5ffa3a Mon Sep 17 00:00:00 2001 From: Alex Ackermann Date: Fri, 27 Jun 2025 13:17:12 +0200 Subject: [PATCH 02/13] remove leftovers # Conflicts: # tests/e2e/support/api/graph/userManagement.ts --- .../Spaces/SpaceContextActions.spec.ts | 2 +- .../actions/files/useFileActionsSetImage.ts | 3 -- .../composables/piniaStores/capabilities.ts | 2 -- .../files/useFileActionsSetImage.spec.ts | 1 - .../unit/services/previewService.spec.ts | 28 ------------------- .../web-runtime/src/container/bootstrap.ts | 1 - 6 files changed, 1 insertion(+), 36 deletions(-) diff --git a/packages/web-app-files/tests/unit/components/Spaces/SpaceContextActions.spec.ts b/packages/web-app-files/tests/unit/components/Spaces/SpaceContextActions.spec.ts index a1489aff91..ca253921f0 100644 --- a/packages/web-app-files/tests/unit/components/Spaces/SpaceContextActions.spec.ts +++ b/packages/web-app-files/tests/unit/components/Spaces/SpaceContextActions.spec.ts @@ -40,7 +40,7 @@ function getWrapper(space: SpaceResource) { const mocks = defaultComponentMocks({ currentRoute: mock({ path: '/files', name: '' }) }) - mocks.$previewService.getSupportedMimeTypes.mockReturnValue([]) + return { wrapper: mount(SpaceContextActions, { props: { diff --git a/packages/web-pkg/src/composables/actions/files/useFileActionsSetImage.ts b/packages/web-pkg/src/composables/actions/files/useFileActionsSetImage.ts index e3d74fc7cc..aa686d56ab 100644 --- a/packages/web-pkg/src/composables/actions/files/useFileActionsSetImage.ts +++ b/packages/web-pkg/src/composables/actions/files/useFileActionsSetImage.ts @@ -49,9 +49,6 @@ export const useFileActionsSetImage = () => { if (!resources[0].mimeType) { return false } - if (!previewService.isMimetypeSupported(resources[0].mimeType, true)) { - return false - } if (!isLocationSpacesActive(router, 'files-spaces-generic')) { return false diff --git a/packages/web-pkg/src/composables/piniaStores/capabilities.ts b/packages/web-pkg/src/composables/piniaStores/capabilities.ts index db152c87dc..e639d372f5 100644 --- a/packages/web-pkg/src/composables/piniaStores/capabilities.ts +++ b/packages/web-pkg/src/composables/piniaStores/capabilities.ts @@ -95,7 +95,6 @@ export const useCapabilityStore = defineStore('capabilities', () => { const filesAppProviders = computed(() => unref(capabilities).files.app_providers) const filesFavorites = computed(() => unref(capabilities).files.favorites) - const filesThumbnail = computed(() => unref(capabilities).files.thumbnail) const filesArchivers = computed(() => unref(capabilities).files.archivers) const filesPrivateLinks = computed(() => unref(capabilities).files.privateLinks) const filesPermanentDeletion = computed(() => unref(capabilities).files.permanent_deletion) @@ -159,7 +158,6 @@ export const useCapabilityStore = defineStore('capabilities', () => { graphUsersReadOnlyAttributes, filesAppProviders, filesFavorites, - filesThumbnail, filesArchivers, filesPrivateLinks, filesPermanentDeletion, diff --git a/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsSetImage.spec.ts b/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsSetImage.spec.ts index c2685a1e21..2958826cdd 100644 --- a/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsSetImage.spec.ts +++ b/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsSetImage.spec.ts @@ -112,7 +112,6 @@ function getWrapper({ currentRoute: mock({ name: 'files-spaces-generic' }) }) } - mocks.$previewService.isMimetypeSupported.mockReturnValue(isMimetypeSupported) mocks.$clientService.webdav.getFileInfo.mockResolvedValue(mock()) return { diff --git a/packages/web-pkg/tests/unit/services/previewService.spec.ts b/packages/web-pkg/tests/unit/services/previewService.spec.ts index 045606e8d9..a978a818bc 100644 --- a/packages/web-pkg/tests/unit/services/previewService.spec.ts +++ b/packages/web-pkg/tests/unit/services/previewService.spec.ts @@ -12,34 +12,6 @@ import { import { User } from '@opencloud-eu/web-client/graph/generated' describe('PreviewService', () => { - describe('method "isMimetypeSupported"', () => { - it('should return true if mimeType is supported', () => { - const supportedMimeTypes = ['image/png'] - const { previewService } = getWrapper({ supportedMimeTypes }) - expect(previewService.isMimetypeSupported(supportedMimeTypes[0])).toBe(true) - }) - it('should return true if no specific supported mimeTypes given', () => { - const { previewService } = getWrapper() - expect(previewService.isMimetypeSupported('image/png')).toBe(true) - }) - it('should return false if mimeType is not supported', () => { - const supportedMimeTypes = ['image/png'] - const { previewService } = getWrapper({ supportedMimeTypes }) - expect(previewService.isMimetypeSupported('image/jpeg')).toBe(false) - }) - }) - describe('method "getSupportedMimeTypes"', () => { - it('reads the supported mime types from the capabilities', () => { - const supportedMimeTypes = ['image/png'] - const { previewService } = getWrapper({ supportedMimeTypes }) - expect(previewService.getSupportedMimeTypes()).toEqual(supportedMimeTypes) - }) - it('filters the supported mime types from the capabilities', () => { - const supportedMimeTypes = ['image/png', 'text/plain'] - const { previewService } = getWrapper({ supportedMimeTypes }) - expect(previewService.getSupportedMimeTypes('image')).toEqual([supportedMimeTypes[0]]) - }) - }) describe('method "loadPreview"', () => { it('does not load preview if no version specified', async () => { const supportedMimeTypes = ['image/png'] diff --git a/packages/web-runtime/src/container/bootstrap.ts b/packages/web-runtime/src/container/bootstrap.ts index fff7976fe7..7ffc630fe3 100644 --- a/packages/web-runtime/src/container/bootstrap.ts +++ b/packages/web-runtime/src/container/bootstrap.ts @@ -508,7 +508,6 @@ export const announcePreviewService = ({ clientService, userStore, authStore, - capabilityStore, configStore }) app.config.globalProperties.$previewService = previewService From 1f9b4fdb8200df467176f7927056488fedb6ba59 Mon Sep 17 00:00:00 2001 From: Alex Ackermann Date: Fri, 27 Jun 2025 13:23:22 +0200 Subject: [PATCH 03/13] add unit test --- .../unit/services/previewService.spec.ts | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/packages/web-pkg/tests/unit/services/previewService.spec.ts b/packages/web-pkg/tests/unit/services/previewService.spec.ts index a978a818bc..f16acfdac8 100644 --- a/packages/web-pkg/tests/unit/services/previewService.spec.ts +++ b/packages/web-pkg/tests/unit/services/previewService.spec.ts @@ -5,9 +5,9 @@ import { Resource, SpaceResource } from '@opencloud-eu/web-client' import { AxiosResponse } from 'axios' import { useAuthStore, - useUserStore, useCapabilityStore, - useConfigStore + useConfigStore, + useUserStore } from '../../../src/composables/piniaStores' import { User } from '@opencloud-eu/web-client/graph/generated' @@ -59,6 +59,26 @@ describe('PreviewService', () => { }) expect(preview).toEqual(undefined) }) + it('does not load preview if "hasPreview" is false', async () => { + const objectUrl = 'objectUrl' + const supportedMimeTypes = ['image/png'] + const { previewService } = getWrapper({ + supportedMimeTypes, + version: '1' + }) + window.URL.createObjectURL = vi.fn().mockImplementation(() => objectUrl) + const preview = await previewService.loadPreview({ + space: mock(), + resource: mock({ + mimeType: supportedMimeTypes[0], + webDavPath: '/', + etag: '', + canDownload: () => true, + hasPreview: () => false + }) + }) + expect(preview).toEqual(undefined) + }) it.each([425, 429])('retries when the server returns a %s status code', async (status) => { const supportedMimeTypes = ['image/png'] const { previewService, clientService } = getWrapper({ @@ -78,6 +98,7 @@ describe('PreviewService', () => { mimeType: supportedMimeTypes[0], webDavPath: '/', etag: '', + hasPreview: () => true, canDownload: () => true }) }) @@ -98,6 +119,7 @@ describe('PreviewService', () => { mimeType: supportedMimeTypes[0], webDavPath: '/', etag: '', + hasPreview: () => true, canDownload: () => true }) }) @@ -115,6 +137,7 @@ describe('PreviewService', () => { mimeType: supportedMimeTypes[0], webDavPath: '/', etag: '', + hasPreview: () => true, canDownload: () => true }) window.URL.createObjectURL = vi.fn().mockImplementation(() => objectUrl) @@ -146,6 +169,7 @@ describe('PreviewService', () => { mimeType: supportedMimeTypes[0], downloadURL, etag: '', + hasPreview: () => true, canDownload: () => true }) }) From a6c609e1857d24cc260f67cf1ddbf1de07fa31c3 Mon Sep 17 00:00:00 2001 From: Alex Ackermann Date: Fri, 27 Jun 2025 13:27:49 +0200 Subject: [PATCH 04/13] remove unit test, as allowed mimetypes are hardcoded anyway and controlled by the browser --- .../files/useFileActionsSetImage.spec.ts | 31 +------------------ 1 file changed, 1 insertion(+), 30 deletions(-) diff --git a/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsSetImage.spec.ts b/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsSetImage.spec.ts index 2958826cdd..d8db1caae1 100644 --- a/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsSetImage.spec.ts +++ b/packages/web-pkg/tests/unit/composables/actions/files/useFileActionsSetImage.spec.ts @@ -25,33 +25,6 @@ describe('setImage', () => { } }) }) - it('should be false when mimeType is not image', () => { - const space = mock({ canEditImage: () => true }) - getWrapper({ - setup: ({ actions }) => { - expect( - unref(actions)[0].isVisible({ - space, - resources: [{ id: '1', mimeType: 'text/plain' }] as Resource[] - }) - ).toBe(false) - }, - isMimetypeSupported: false - }) - }) - it('should be true when the mimeType is image', () => { - const space = mock({ canEditImage: () => true }) - getWrapper({ - setup: ({ actions }) => { - expect( - unref(actions)[0].isVisible({ - space, - resources: [{ id: '1', mimeType: 'image/png' }] as Resource[] - }) - ).toBe(true) - } - }) - }) it('should be false when canEditImage is false', () => { const space = mock({ canEditImage: () => false }) getWrapper({ @@ -92,8 +65,7 @@ describe('setImage', () => { }) function getWrapper({ - setup, - isMimetypeSupported = true + setup }: { setup: ( instance: ReturnType, @@ -101,7 +73,6 @@ function getWrapper({ clientService: ReturnType['$clientService'] } ) => void - isMimetypeSupported?: boolean }) { vi.mocked(useSpaceHelpers).mockReturnValue({ getDefaultMetaFolder: () => new Promise(() => mock()) From eea4e19d930491966ec39f882b803ed528aa37fc Mon Sep 17 00:00:00 2001 From: Alex Ackermann Date: Fri, 27 Jun 2025 16:45:25 +0200 Subject: [PATCH 05/13] enhance --- packages/web-client/src/helpers/resource/functions.ts | 2 +- packages/web-client/src/helpers/share/functions.ts | 2 ++ packages/web-client/src/helpers/space/functions.ts | 1 + packages/web-client/src/webdav/constants/dav.ts | 2 +- packages/web-pkg/src/services/preview/previewService.ts | 1 + 5 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/web-client/src/helpers/resource/functions.ts b/packages/web-client/src/helpers/resource/functions.ts index e0682aae5d..d47a51cdf0 100644 --- a/packages/web-client/src/helpers/resource/functions.ts +++ b/packages/web-client/src/helpers/resource/functions.ts @@ -173,7 +173,7 @@ export function buildResource( image: convertObjectToCamelCaseKeys(resource.props[DavProperty.Image]), photo: convertObjectToCamelCaseKeys(resource.props[DavProperty.Photo]), extraProps, - hasPreview: () => resource.props[DavProperty.HasPreview] === '1', + hasPreview: () => resource.props[DavProperty.HasPreview] === 1, canUpload: function () { return this.permissions.indexOf(DavPermission.FolderCreateable) >= 0 }, diff --git a/packages/web-client/src/helpers/share/functions.ts b/packages/web-client/src/helpers/share/functions.ts index 3c33092a3f..560e5f9ccb 100644 --- a/packages/web-client/src/helpers/share/functions.ts +++ b/packages/web-client/src/helpers/share/functions.ts @@ -217,6 +217,8 @@ export function buildOutgoingShareResource({ mimeType: driveItem.file?.mimeType || 'httpd/unix-directory', outgoing: true, privateLink: urlJoin(serverUrl, 'f', driveItem.id), + //TODO: this check is not correct, we should check item has a preview, needs graph implementation + hasPreview: () => !!driveItem.image, canRename: () => true, canDownload: () => true, canUpload: () => true, diff --git a/packages/web-client/src/helpers/space/functions.ts b/packages/web-client/src/helpers/space/functions.ts index 4430bec693..1551cb1c7b 100644 --- a/packages/web-client/src/helpers/space/functions.ts +++ b/packages/web-client/src/helpers/space/functions.ts @@ -319,6 +319,7 @@ export function buildSpaceImageResource(space: SpaceResource): Resource { mimeType: space.spaceImageData.file.mimeType, type: 'file', webDavPath: urlJoin(space.webDavPath, '.space', space.spaceImageData.name), + hasPreview: () => true, canDownload: () => true } as Resource } diff --git a/packages/web-client/src/webdav/constants/dav.ts b/packages/web-client/src/webdav/constants/dav.ts index 425165e6f6..ff75a0ce6c 100644 --- a/packages/web-client/src/webdav/constants/dav.ts +++ b/packages/web-client/src/webdav/constants/dav.ts @@ -91,7 +91,7 @@ const DavPropertyMapping = { Highlights: defString('highlights' as const), MetaPathForUser: defString('meta-path-for-user' as const), RemoteItemId: defString('remote-item-id' as const), - HasPreview: defString('has-preview' as const), + HasPreview: defNumber('has-preview' as const), ShareId: defString('shareid' as const), ShareRoot: defString('shareroot' as const), diff --git a/packages/web-pkg/src/services/preview/previewService.ts b/packages/web-pkg/src/services/preview/previewService.ts index ded3acf90f..9f23e28da3 100644 --- a/packages/web-pkg/src/services/preview/previewService.ts +++ b/packages/web-pkg/src/services/preview/previewService.ts @@ -41,6 +41,7 @@ export class PreviewService { signal?: AbortSignal ): Promise { const { space, resource } = options + console.log(resource) const resourceSupportsPreview = resource.type !== 'folder' && resource.extension && From b7dc1fbffe84a97cdfa9f2e8b087559168d7a85f Mon Sep 17 00:00:00 2001 From: Alex Ackermann Date: Fri, 27 Jun 2025 23:06:26 +0200 Subject: [PATCH 06/13] fix setting default space image --- .../src/composables/resources/useLoadPreview.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/web-pkg/src/composables/resources/useLoadPreview.ts b/packages/web-pkg/src/composables/resources/useLoadPreview.ts index d2d9a51290..59f1673591 100644 --- a/packages/web-pkg/src/composables/resources/useLoadPreview.ts +++ b/packages/web-pkg/src/composables/resources/useLoadPreview.ts @@ -97,8 +97,11 @@ export const useLoadPreview = (viewMode?: Ref) => { if (isProjectSpaceResource(resource) && (!resource.spaceImageData || resource.disabled)) { if (unref(defaultSpaceImageBlobURL)) { - resource.thumbnail = unref(defaultSpaceImageBlobURL) - return unref(defaultSpaceImageBlobURL) + spacesStore.updateSpaceField({ + id: resource.id, + field: 'thumbnail', + value: unref(defaultSpaceImageBlobURL) + }) } try { @@ -112,7 +115,11 @@ export const useLoadPreview = (viewMode?: Ref) => { spacesStore.setDefaultSpaceImageBlobURL( URL.createObjectURL(defaultSpaceImageBlobURLResponse.data) ) - resource.thumbnail = unref(defaultSpaceImageBlobURL) + spacesStore.updateSpaceField({ + id: resource.id, + field: 'thumbnail', + value: unref(defaultSpaceImageBlobURL) + }) return unref(defaultSpaceImageBlobURL) } catch { return null From 0486d13c1d3a0e804ad646420e988a64587bbf49 Mon Sep 17 00:00:00 2001 From: Alex Ackermann Date: Sat, 28 Jun 2025 12:06:44 +0200 Subject: [PATCH 07/13] simplify check --- packages/web-pkg/src/services/preview/previewService.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/web-pkg/src/services/preview/previewService.ts b/packages/web-pkg/src/services/preview/previewService.ts index 9f23e28da3..b714249680 100644 --- a/packages/web-pkg/src/services/preview/previewService.ts +++ b/packages/web-pkg/src/services/preview/previewService.ts @@ -42,12 +42,8 @@ export class PreviewService { ): Promise { const { space, resource } = options console.log(resource) - const resourceSupportsPreview = - resource.type !== 'folder' && - resource.extension && - resource.canDownload() && - resource.hasPreview() - if (!resourceSupportsPreview) { + + if (!resource.canDownload() || !resource.hasPreview()) { return undefined } From cf478a640c77915d7dfac5528dfd582395af070f Mon Sep 17 00:00:00 2001 From: Alex Ackermann Date: Wed, 23 Jul 2025 12:07:23 +0200 Subject: [PATCH 08/13] adapt for shares --- .../src/graph/driveItems/driveItems.ts | 8 +-- .../web-client/src/graph/driveItems/types.ts | 14 ++++- .../web-client/src/graph/generated/api.ts | 58 ++++++++++++++----- .../web-client/src/helpers/share/functions.ts | 3 +- .../folder/loaders/loaderSharedViaLink.ts | 5 +- .../folder/loaders/loaderSharedWithMe.ts | 5 +- .../folder/loaders/loaderSharedWithOthers.ts | 5 +- .../src/services/preview/previewService.ts | 1 - .../web-runtime/src/container/sse/shares.ts | 16 +++-- 9 files changed, 85 insertions(+), 30 deletions(-) diff --git a/packages/web-client/src/graph/driveItems/driveItems.ts b/packages/web-client/src/graph/driveItems/driveItems.ts index b24df26adb..c94f2c5572 100644 --- a/packages/web-client/src/graph/driveItems/driveItems.ts +++ b/packages/web-client/src/graph/driveItems/driveItems.ts @@ -39,13 +39,13 @@ export const DriveItemsFactory = ({ await driveItemApiFactory.deleteDriveItem(driveId, itemId, requestOptions) }, - async listSharedByMe(requestOptions) { - const { data } = await meDriveApiFactory.listSharedByMe(requestOptions) + async listSharedByMe(options, requestOptions) { + const { data } = await meDriveApiFactory.listSharedByMe(options?.expand, requestOptions) return data?.value || [] }, - async listSharedWithMe(requestOptions) { - const { data } = await meDriveApiFactory.listSharedWithMe(requestOptions) + async listSharedWithMe(options, requestOptions) { + const { data } = await meDriveApiFactory.listSharedWithMe(options?.expand, requestOptions) return data?.value || [] } } diff --git a/packages/web-client/src/graph/driveItems/types.ts b/packages/web-client/src/graph/driveItems/types.ts index 1ec55066bd..a865afb4e8 100644 --- a/packages/web-client/src/graph/driveItems/types.ts +++ b/packages/web-client/src/graph/driveItems/types.ts @@ -23,6 +23,16 @@ export interface GraphDriveItems { itemId: string, requestOptions?: GraphRequestOptions ) => Promise - listSharedByMe: (requestOptions?: GraphRequestOptions) => Promise - listSharedWithMe: (requestOptions?: GraphRequestOptions) => Promise + listSharedByMe: ( + options?: { + expand?: Set<'thumbnails'> + }, + requestOptions?: GraphRequestOptions + ) => Promise + listSharedWithMe: ( + options?: { + expand?: Set<'thumbnails'> + }, + requestOptions?: GraphRequestOptions + ) => Promise } diff --git a/packages/web-client/src/graph/generated/api.ts b/packages/web-client/src/graph/generated/api.ts index 0c9be17481..ad1a95b77a 100644 --- a/packages/web-client/src/graph/generated/api.ts +++ b/packages/web-client/src/graph/generated/api.ts @@ -8628,10 +8628,11 @@ export const MeDriveApiAxiosParamCreator = function (configuration?: Configurati /** * The `driveItems` returned from the `sharedByMe` method always include the `permissions` relation that indicates they are shared items. * @summary Get a list of driveItem objects shared by the current user. + * @param {Set} [$expand] Expand related entities * @param {*} [options] Override http request option. * @throws {RequiredError} */ - listSharedByMe: async (options: RawAxiosRequestConfig = {}): Promise => { + listSharedByMe: async ($expand?: Set, options: RawAxiosRequestConfig = {}): Promise => { const localVarPath = `/v1beta1/me/drive/sharedByMe`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -8650,6 +8651,10 @@ export const MeDriveApiAxiosParamCreator = function (configuration?: Configurati // http basic authentication required setBasicAuthToObject(localVarRequestOptions, configuration) + if ($expand) { + localVarQueryParameter['$expand'] = Array.from($expand).join(COLLECTION_FORMATS.csv); + } + setSearchParams(localVarUrlObj, localVarQueryParameter); @@ -8664,10 +8669,11 @@ export const MeDriveApiAxiosParamCreator = function (configuration?: Configurati /** * The `driveItems` returned from the `sharedWithMe` method always include the `remoteItem` facet that indicates they are items from a different drive. * @summary Get a list of driveItem objects shared with the owner of a drive. + * @param {Set} [$expand] Expand related entities * @param {*} [options] Override http request option. * @throws {RequiredError} */ - listSharedWithMe: async (options: RawAxiosRequestConfig = {}): Promise => { + listSharedWithMe: async ($expand?: Set, options: RawAxiosRequestConfig = {}): Promise => { const localVarPath = `/v1beta1/me/drive/sharedWithMe`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -8686,6 +8692,10 @@ export const MeDriveApiAxiosParamCreator = function (configuration?: Configurati // http basic authentication required setBasicAuthToObject(localVarRequestOptions, configuration) + if ($expand) { + localVarQueryParameter['$expand'] = Array.from($expand).join(COLLECTION_FORMATS.csv); + } + setSearchParams(localVarUrlObj, localVarQueryParameter); @@ -8722,11 +8732,12 @@ export const MeDriveApiFp = function(configuration?: Configuration) { /** * The `driveItems` returned from the `sharedByMe` method always include the `permissions` relation that indicates they are shared items. * @summary Get a list of driveItem objects shared by the current user. + * @param {Set} [$expand] Expand related entities * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async listSharedByMe(options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.listSharedByMe(options); + async listSharedByMe($expand?: Set, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.listSharedByMe($expand, options); const localVarOperationServerIndex = configuration?.serverIndex ?? 0; const localVarOperationServerBasePath = operationServerMap['MeDriveApi.listSharedByMe']?.[localVarOperationServerIndex]?.url; return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); @@ -8734,11 +8745,12 @@ export const MeDriveApiFp = function(configuration?: Configuration) { /** * The `driveItems` returned from the `sharedWithMe` method always include the `remoteItem` facet that indicates they are items from a different drive. * @summary Get a list of driveItem objects shared with the owner of a drive. + * @param {Set} [$expand] Expand related entities * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async listSharedWithMe(options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.listSharedWithMe(options); + async listSharedWithMe($expand?: Set, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.listSharedWithMe($expand, options); const localVarOperationServerIndex = configuration?.serverIndex ?? 0; const localVarOperationServerBasePath = operationServerMap['MeDriveApi.listSharedWithMe']?.[localVarOperationServerIndex]?.url; return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); @@ -8765,20 +8777,22 @@ export const MeDriveApiFactory = function (configuration?: Configuration, basePa /** * The `driveItems` returned from the `sharedByMe` method always include the `permissions` relation that indicates they are shared items. * @summary Get a list of driveItem objects shared by the current user. + * @param {Set} [$expand] Expand related entities * @param {*} [options] Override http request option. * @throws {RequiredError} */ - listSharedByMe(options?: RawAxiosRequestConfig): AxiosPromise { - return localVarFp.listSharedByMe(options).then((request) => request(axios, basePath)); + listSharedByMe($expand?: Set, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.listSharedByMe($expand, options).then((request) => request(axios, basePath)); }, /** * The `driveItems` returned from the `sharedWithMe` method always include the `remoteItem` facet that indicates they are items from a different drive. * @summary Get a list of driveItem objects shared with the owner of a drive. + * @param {Set} [$expand] Expand related entities * @param {*} [options] Override http request option. * @throws {RequiredError} */ - listSharedWithMe(options?: RawAxiosRequestConfig): AxiosPromise { - return localVarFp.listSharedWithMe(options).then((request) => request(axios, basePath)); + listSharedWithMe($expand?: Set, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.listSharedWithMe($expand, options).then((request) => request(axios, basePath)); }, }; }; @@ -8804,26 +8818,42 @@ export class MeDriveApi extends BaseAPI { /** * The `driveItems` returned from the `sharedByMe` method always include the `permissions` relation that indicates they are shared items. * @summary Get a list of driveItem objects shared by the current user. + * @param {Set} [$expand] Expand related entities * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof MeDriveApi */ - public listSharedByMe(options?: RawAxiosRequestConfig) { - return MeDriveApiFp(this.configuration).listSharedByMe(options).then((request) => request(this.axios, this.basePath)); + public listSharedByMe($expand?: Set, options?: RawAxiosRequestConfig) { + return MeDriveApiFp(this.configuration).listSharedByMe($expand, options).then((request) => request(this.axios, this.basePath)); } /** * The `driveItems` returned from the `sharedWithMe` method always include the `remoteItem` facet that indicates they are items from a different drive. * @summary Get a list of driveItem objects shared with the owner of a drive. + * @param {Set} [$expand] Expand related entities * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof MeDriveApi */ - public listSharedWithMe(options?: RawAxiosRequestConfig) { - return MeDriveApiFp(this.configuration).listSharedWithMe(options).then((request) => request(this.axios, this.basePath)); + public listSharedWithMe($expand?: Set, options?: RawAxiosRequestConfig) { + return MeDriveApiFp(this.configuration).listSharedWithMe($expand, options).then((request) => request(this.axios, this.basePath)); } } +/** + * @export + */ +export const ListSharedByMeExpandEnum = { + Thumbnails: 'thumbnails' +} as const; +export type ListSharedByMeExpandEnum = typeof ListSharedByMeExpandEnum[keyof typeof ListSharedByMeExpandEnum]; +/** + * @export + */ +export const ListSharedWithMeExpandEnum = { + Thumbnails: 'thumbnails' +} as const; +export type ListSharedWithMeExpandEnum = typeof ListSharedWithMeExpandEnum[keyof typeof ListSharedWithMeExpandEnum]; /** diff --git a/packages/web-client/src/helpers/share/functions.ts b/packages/web-client/src/helpers/share/functions.ts index 560e5f9ccb..ac5de83d50 100644 --- a/packages/web-client/src/helpers/share/functions.ts +++ b/packages/web-client/src/helpers/share/functions.ts @@ -217,8 +217,7 @@ export function buildOutgoingShareResource({ mimeType: driveItem.file?.mimeType || 'httpd/unix-directory', outgoing: true, privateLink: urlJoin(serverUrl, 'f', driveItem.id), - //TODO: this check is not correct, we should check item has a preview, needs graph implementation - hasPreview: () => !!driveItem.image, + hasPreview: () => !!driveItem.thumbnails, canRename: () => true, canDownload: () => true, canUpload: () => true, diff --git a/packages/web-pkg/src/services/folder/loaders/loaderSharedViaLink.ts b/packages/web-pkg/src/services/folder/loaders/loaderSharedViaLink.ts index 4d30d0b603..f20728cb2b 100644 --- a/packages/web-pkg/src/services/folder/loaders/loaderSharedViaLink.ts +++ b/packages/web-pkg/src/services/folder/loaders/loaderSharedViaLink.ts @@ -34,7 +34,10 @@ export class FolderLoaderSharedViaLink implements FolderLoader { } const value = yield* call( - clientService.graphAuthenticated.driveItems.listSharedByMe({ signal: signal1 }) + clientService.graphAuthenticated.driveItems.listSharedByMe( + { expand: new Set(['thumbnails']) }, + { signal: signal1 } + ) ) const resources = value diff --git a/packages/web-pkg/src/services/folder/loaders/loaderSharedWithMe.ts b/packages/web-pkg/src/services/folder/loaders/loaderSharedWithMe.ts index 238b299de3..bb0ecc13dd 100644 --- a/packages/web-pkg/src/services/folder/loaders/loaderSharedWithMe.ts +++ b/packages/web-pkg/src/services/folder/loaders/loaderSharedWithMe.ts @@ -34,7 +34,10 @@ export class FolderLoaderSharedWithMe implements FolderLoader { } const value = yield* call( - clientService.graphAuthenticated.driveItems.listSharedWithMe({ signal: signal1 }) + clientService.graphAuthenticated.driveItems.listSharedWithMe( + { expand: new Set(['thumbnails']) }, + { signal: signal1 } + ) ) const resources = value.map((driveItem) => diff --git a/packages/web-pkg/src/services/folder/loaders/loaderSharedWithOthers.ts b/packages/web-pkg/src/services/folder/loaders/loaderSharedWithOthers.ts index 65fa585f14..8b96491af0 100644 --- a/packages/web-pkg/src/services/folder/loaders/loaderSharedWithOthers.ts +++ b/packages/web-pkg/src/services/folder/loaders/loaderSharedWithOthers.ts @@ -34,7 +34,10 @@ export class FolderLoaderSharedWithOthers implements FolderLoader { } const value = yield* call( - clientService.graphAuthenticated.driveItems.listSharedByMe({ signal: signal1 }) + clientService.graphAuthenticated.driveItems.listSharedByMe( + { expand: new Set(['thumbnails']) }, + { signal: signal1 } + ) ) const resources = value diff --git a/packages/web-pkg/src/services/preview/previewService.ts b/packages/web-pkg/src/services/preview/previewService.ts index b714249680..4f610410a6 100644 --- a/packages/web-pkg/src/services/preview/previewService.ts +++ b/packages/web-pkg/src/services/preview/previewService.ts @@ -41,7 +41,6 @@ export class PreviewService { signal?: AbortSignal ): Promise { const { space, resource } = options - console.log(resource) if (!resource.canDownload() || !resource.hasPreview()) { return undefined diff --git a/packages/web-runtime/src/container/sse/shares.ts b/packages/web-runtime/src/container/sse/shares.ts index 1d612a8d84..6901ebd86d 100644 --- a/packages/web-runtime/src/container/sse/shares.ts +++ b/packages/web-runtime/src/container/sse/shares.ts @@ -153,7 +153,9 @@ export const onSSEShareCreatedEvent = async ({ if (isLocationSharesActive(router, 'files-shares-with-me')) { // FIXME: get drive item by id as soon as server supports it - const driveItems = await clientService.graphAuthenticated.driveItems.listSharedWithMe() + const driveItems = await clientService.graphAuthenticated.driveItems.listSharedWithMe({ + expand: new Set(['thumbnails']) + }) const driveItem = driveItems.find(({ remoteItem }) => remoteItem.id === sseData.itemid) if (!driveItem) { return @@ -168,7 +170,9 @@ export const onSSEShareCreatedEvent = async ({ if (isLocationSharesActive(router, 'files-shares-with-others')) { // FIXME: get drive item by id as soon as server supports it - const driveItems = await clientService.graphAuthenticated.driveItems.listSharedByMe() + const driveItems = await clientService.graphAuthenticated.driveItems.listSharedByMe({ + expand: new Set(['thumbnails']) + }) const driveItem = driveItems.find(({ id }) => id === sseData.itemid) if (!driveItem) { return @@ -205,7 +209,9 @@ export const onSSEShareUpdatedEvent = async ({ if (isLocationSharesActive(router, 'files-shares-with-me')) { // FIXME: get drive item by id as soon as server supports it - const driveItems = await clientService.graphAuthenticated.driveItems.listSharedWithMe() + const driveItems = await clientService.graphAuthenticated.driveItems.listSharedWithMe({ + expand: new Set(['thumbnails']) + }) const driveItem = driveItems.find(({ remoteItem }) => remoteItem.id === sseData.itemid) if (!driveItem) { return @@ -332,7 +338,9 @@ export const onSSELinkCreatedEvent = async ({ if (isLocationSharesActive(router, 'files-shares-via-link')) { // FIXME: get drive item by id as soon as server supports it - const driveItems = await clientService.graphAuthenticated.driveItems.listSharedByMe() + const driveItems = await clientService.graphAuthenticated.driveItems.listSharedByMe({ + expand: new Set(['thumbnails']) + }) const driveItem = driveItems.find(({ id }) => id === sseData.itemid) if (!driveItem) { return From cbf908d7192fd5b222049e5bcb389e7a3e8f269c Mon Sep 17 00:00:00 2001 From: Alexander Ackermann Date: Fri, 25 Jul 2025 10:48:25 +0200 Subject: [PATCH 09/13] bump opencloud commitid --- .woodpecker.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.woodpecker.env b/.woodpecker.env index 158b0062ca..c6497da4fc 100644 --- a/.woodpecker.env +++ b/.woodpecker.env @@ -1,3 +1,3 @@ # The version of OpenCloud to use in pipelines -OPENCLOUD_COMMITID=6e909bf3d7f1fd309bda787d48dd9e89ebd35501 +OPENCLOUD_COMMITID=2670f24605dd0f97b89329f5a2f2f99d5107bc68 OPENCLOUD_BRANCH=main From 7f8e364589818f1b752c63b2b878f6a2189a6e31 Mon Sep 17 00:00:00 2001 From: Alexander Ackermann Date: Fri, 25 Jul 2025 11:16:53 +0200 Subject: [PATCH 10/13] adjust for incoming shares --- packages/web-client/src/helpers/share/functions.ts | 1 + packages/web-pkg/src/services/preview/previewService.ts | 3 +++ 2 files changed, 4 insertions(+) diff --git a/packages/web-client/src/helpers/share/functions.ts b/packages/web-client/src/helpers/share/functions.ts index ac5de83d50..131d9faae3 100644 --- a/packages/web-client/src/helpers/share/functions.ts +++ b/packages/web-client/src/helpers/share/functions.ts @@ -156,6 +156,7 @@ export function buildIncomingShareResource({ sharePermissions, outgoing: false, privateLink: urlJoin(serverUrl, 'f', driveItem.remoteItem.id), + hasPreview: () => !!driveItem.thumbnails, canRename: () => driveItem['@client.synchronize'], canDownload: () => sharePermissions.includes(GraphSharePermission.readContent), canUpload: () => sharePermissions.includes(GraphSharePermission.createUpload), diff --git a/packages/web-pkg/src/services/preview/previewService.ts b/packages/web-pkg/src/services/preview/previewService.ts index 4f610410a6..125ab48db2 100644 --- a/packages/web-pkg/src/services/preview/previewService.ts +++ b/packages/web-pkg/src/services/preview/previewService.ts @@ -42,6 +42,9 @@ export class PreviewService { ): Promise { const { space, resource } = options + console.log(resource) + console.log(typeof resource) + if (!resource.canDownload() || !resource.hasPreview()) { return undefined } From 5b08f40b818f3e6b97e03837ac68b9d129758f94 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 25 Jul 2025 11:53:41 +0200 Subject: [PATCH 11/13] Update previewService.ts Co-authored-by: Jannik Stehle <50302941+JammingBen@users.noreply.github.com> --- packages/web-pkg/src/services/preview/previewService.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/web-pkg/src/services/preview/previewService.ts b/packages/web-pkg/src/services/preview/previewService.ts index 125ab48db2..4f610410a6 100644 --- a/packages/web-pkg/src/services/preview/previewService.ts +++ b/packages/web-pkg/src/services/preview/previewService.ts @@ -42,9 +42,6 @@ export class PreviewService { ): Promise { const { space, resource } = options - console.log(resource) - console.log(typeof resource) - if (!resource.canDownload() || !resource.hasPreview()) { return undefined } From 79091fc0ed4e74fcf34936465da18d3886a239ff Mon Sep 17 00:00:00 2001 From: Alexander Ackermann Date: Fri, 25 Jul 2025 12:32:47 +0200 Subject: [PATCH 12/13] rm unused var --- packages/web-runtime/src/container/bootstrap.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/web-runtime/src/container/bootstrap.ts b/packages/web-runtime/src/container/bootstrap.ts index 7ffc630fe3..5ad5609cc2 100644 --- a/packages/web-runtime/src/container/bootstrap.ts +++ b/packages/web-runtime/src/container/bootstrap.ts @@ -494,14 +494,12 @@ export const announcePreviewService = ({ app, configStore, userStore, - authStore, - capabilityStore + authStore }: { app: App configStore: ConfigStore userStore: UserStore authStore: AuthStore - capabilityStore: CapabilityStore }): void => { const clientService = app.config.globalProperties.$clientService const previewService = new PreviewService({ From 4a49a599805fd5e726e56f694d5928da77d7ad8a Mon Sep 17 00:00:00 2001 From: Alexander Ackermann Date: Fri, 25 Jul 2025 12:53:03 +0200 Subject: [PATCH 13/13] fix types --- packages/web-runtime/src/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/web-runtime/src/index.ts b/packages/web-runtime/src/index.ts index ee7e82b6e0..24160740f8 100644 --- a/packages/web-runtime/src/index.ts +++ b/packages/web-runtime/src/index.ts @@ -105,8 +105,7 @@ export const bootstrapApp = async (configurationPath: string, appsReadyCallback: app, configStore, userStore, - authStore, - capabilityStore + authStore }) announcePasswordPolicyService({ app }) await announceAuthClient(configStore)