From 567fd3b4dc8cecadd1df56d09118a15fe6825829 Mon Sep 17 00:00:00 2001 From: Alexander Onnikov Date: Wed, 3 Sep 2025 01:08:37 +0700 Subject: [PATCH] UBERF-11387 Preview service (#9738) * UBERF-11387 Preview service Signed-off-by: Alexander Onnikov * code review fixes Signed-off-by: Alexander Onnikov * optimize build Signed-off-by: Alexander Onnikov * use base preview image Signed-off-by: Alexander Onnikov * free some disk space in github runner Signed-off-by: Alexander Onnikov * fix: try to clean builder cache Signed-off-by: Alexander Onnikov * fix: update sharp version for preview Signed-off-by: Alexander Onnikov * some cache fixes Signed-off-by: Alexander Onnikov --------- Signed-off-by: Alexander Onnikov --- .github/workflows/main.yml | 15 +- .vscode/launch.json | 16 ++ common/config/rush/pnpm-lock.yaml | 72 ++++- common/scripts/docker.sh | 1 + desktop/src/ui/platform.ts | 1 + desktop/src/ui/types.ts | 1 + dev/docker-compose.yaml | 16 +- dev/prod/public/config-dev.json | 4 +- dev/prod/public/config.json | 1 + dev/prod/src/platform.ts | 2 + packages/presentation/package.json | 1 + .../presentation/src/components/Image.svelte | 74 ++++- packages/presentation/src/filetypes.ts | 17 +- packages/presentation/src/plugin.ts | 3 +- packages/presentation/src/preview.ts | 70 ++++- packages/ui/package.json | 3 +- packages/ui/src/components/Blurhash.svelte | 54 ++++ packages/ui/src/components/Video.svelte | 13 +- packages/ui/src/index.ts | 1 + .../src/components/AttachmentActions.svelte | 8 +- .../components/AttachmentImagePreview.svelte | 14 +- .../src/components/AttachmentList.svelte | 6 +- .../src/components/AttachmentPresenter.svelte | 8 +- .../src/components/AttachmentPreview.svelte | 38 +-- .../src/components/AttachmentRefInput.svelte | 1 + .../components/AttachmentVideoPreview.svelte | 5 +- .../components/message/MessageFooter.svelte | 6 +- .../src/components/Thumbnail.svelte | 4 +- plugins/drive-resources/src/utils.ts | 9 +- .../extension/shortcuts/imageUpload.ts | 12 +- plugins/view-resources/src/blob.ts | 8 +- pods/preview/.eslintrc.js | 7 + pods/preview/.npmignore | 4 + pods/preview/Dockerfile | 9 + pods/preview/config/rig.json | 4 + pods/preview/jest.config.js | 7 + pods/preview/package.json | 74 +++++ pods/preview/src/cache.ts | 229 +++++++++++++++ pods/preview/src/config.ts | 55 ++++ pods/preview/src/error.ts | 59 ++++ pods/preview/src/index.ts | 84 ++++++ pods/preview/src/metadata/image.ts | 44 +++ pods/preview/src/metadata/index.ts | 17 ++ pods/preview/src/metadata/video.ts | 112 ++++++++ pods/preview/src/middleware.ts | 88 ++++++ pods/preview/src/providers/doc.ts | 123 ++++++++ pods/preview/src/providers/image.ts | 66 +++++ pods/preview/src/providers/index.ts | 20 ++ pods/preview/src/providers/noop.ts | 37 +++ pods/preview/src/providers/pdf.ts | 82 ++++++ pods/preview/src/providers/video.ts | 68 +++++ pods/preview/src/server.ts | 262 ++++++++++++++++++ pods/preview/src/service.ts | 161 +++++++++++ pods/preview/src/tempdir.ts | 88 ++++++ pods/preview/src/types.ts | 44 +++ pods/preview/src/utils/blurhash.ts | 27 ++ pods/preview/src/utils/ffmpeg.ts | 40 +++ pods/preview/src/utils/index.ts | 19 ++ pods/preview/src/utils/libreoffice.ts | 96 +++++++ pods/preview/src/utils/pdf.ts | 31 +++ pods/preview/src/utils/sharp.ts | 104 +++++++ pods/preview/src/utils/utils.ts | 21 ++ pods/preview/tsconfig.json | 12 + rush.json | 5 + server/front/src/index.ts | 2 + server/front/src/starter.ts | 6 + server/server-storage/src/fallback.ts | 11 +- services/backup/backup-api-pod/package.json | 2 +- services/billing/pod-billing/package.json | 2 +- 69 files changed, 2520 insertions(+), 86 deletions(-) create mode 100644 packages/ui/src/components/Blurhash.svelte create mode 100644 pods/preview/.eslintrc.js create mode 100644 pods/preview/.npmignore create mode 100644 pods/preview/Dockerfile create mode 100644 pods/preview/config/rig.json create mode 100644 pods/preview/jest.config.js create mode 100644 pods/preview/package.json create mode 100644 pods/preview/src/cache.ts create mode 100644 pods/preview/src/config.ts create mode 100644 pods/preview/src/error.ts create mode 100644 pods/preview/src/index.ts create mode 100644 pods/preview/src/metadata/image.ts create mode 100644 pods/preview/src/metadata/index.ts create mode 100644 pods/preview/src/metadata/video.ts create mode 100644 pods/preview/src/middleware.ts create mode 100644 pods/preview/src/providers/doc.ts create mode 100644 pods/preview/src/providers/image.ts create mode 100644 pods/preview/src/providers/index.ts create mode 100644 pods/preview/src/providers/noop.ts create mode 100644 pods/preview/src/providers/pdf.ts create mode 100644 pods/preview/src/providers/video.ts create mode 100644 pods/preview/src/server.ts create mode 100644 pods/preview/src/service.ts create mode 100644 pods/preview/src/tempdir.ts create mode 100644 pods/preview/src/types.ts create mode 100644 pods/preview/src/utils/blurhash.ts create mode 100644 pods/preview/src/utils/ffmpeg.ts create mode 100644 pods/preview/src/utils/index.ts create mode 100644 pods/preview/src/utils/libreoffice.ts create mode 100644 pods/preview/src/utils/pdf.ts create mode 100644 pods/preview/src/utils/sharp.ts create mode 100644 pods/preview/src/utils/utils.ts create mode 100644 pods/preview/tsconfig.json diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index de7f175fdb..70ec96b9db 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -612,6 +612,17 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 steps: + # https://github.com/actions/runner-images/issues/2840#issuecomment-790492173 + - name: Free disk space + run: | + echo "Disk space before cleanup:" + df -h + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf "/usr/local/share/boost" + sudo rm -rf "$AGENT_TOOLSDIRECTORY" + echo "Disk space after cleanup:" + df -h - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Configure docker @@ -671,7 +682,9 @@ jobs: password: ${{ secrets.DOCKER_ACCESS_TOKEN }} - name: Docker build - run: node common/scripts/install-run-rush.js docker:build -v + run: | + node common/scripts/install-run-rush.js docker:build -v + docker builder prune -a -f env: DOCKER_CLI_HINTS: false DOCKER_EXTRA: --platform=linux/amd64,linux/arm64 diff --git a/.vscode/launch.json b/.vscode/launch.json index cb59de7312..e7ad3953d3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -854,6 +854,22 @@ "sourceMaps": true, "cwd": "${workspaceRoot}/pods/media" }, + { + "name": "Debug preview", + "type": "node", + "request": "launch", + "args": ["src/index.ts"], + "env": { + "ACCOUNTS_URL": "http://huly.local:3000", + "STORAGE_CONFIG": "datalake|http://huly.local:4030", + "SECRET": "secret", + "CACHE_PATH": "/tmp/preview" + }, + "runtimeArgs": ["--nolazy", "-r", "ts-node/register"], + "runtimeVersion": "20", + "sourceMaps": true, + "cwd": "${workspaceRoot}/pods/preview" + }, { "type": "chrome", "name": "Attach to Browser", diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index f2d73961e0..8835f4eb1a 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -928,6 +928,9 @@ importers: '@rush-temp/pod-notification': specifier: file:./projects/pod-notification.tgz version: file:projects/pod-notification.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(@swc/core@1.13.5)(babel-jest@29.7.0(@babel/core@7.23.9)) + '@rush-temp/pod-preview': + specifier: file:./projects/pod-preview.tgz + version: file:projects/pod-preview.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(@swc/core@1.13.5)(babel-jest@29.7.0(@babel/core@7.23.9)) '@rush-temp/pod-print': specifier: file:./projects/pod-print.tgz version: file:projects/pod-print.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(@swc/core@1.13.5)(babel-jest@29.7.0(@babel/core@7.23.9))(bufferutil@4.0.8)(utf-8-validate@6.0.4) @@ -1810,6 +1813,9 @@ importers: big-integer: specifier: ^1.6.51 version: 1.6.52 + blurhash: + specifier: ^2.0.5 + version: 2.0.5 body-parser: specifier: ^1.20.3 version: 1.20.3 @@ -2146,6 +2152,9 @@ importers: node-loader: specifier: ~2.0.0 version: 2.0.0(webpack@5.97.1) + node-poppler: + specifier: ^8.0.4 + version: 8.0.4 nodemailer: specifier: ^6.10.0 version: 6.10.0 @@ -5562,6 +5571,10 @@ packages: resolution: {integrity: sha512-0niSiaoYcTSTPS35rJb9UkCUfYq4j9Bovia2g/6rTqBU8mjaGattwDKxXptIC8CUGRoNtwxeXrQHpQzmFIqjBg==, tarball: file:projects/pod-notification.tgz} version: 0.0.0 + '@rush-temp/pod-preview@file:projects/pod-preview.tgz': + resolution: {integrity: sha512-APb+wu9FGvuc2FCTz9nGyrGZwA4+o5uL6WWPdRhg1zeC9UniDdSA2InqNP03CxK8fayAwTMkTN9BF3C2g3w2lQ==, tarball: file:projects/pod-preview.tgz} + version: 0.0.0 + '@rush-temp/pod-print@file:projects/pod-print.tgz': resolution: {integrity: sha512-8tndpe/4V1VSqIisntC9lxmfP8oJd+6z3gd06BQHBertjGBezb/FE4+Qmxuj8CVVK/2Wqr4EZe3zYWB/YbErOA==, tarball: file:projects/pod-print.tgz} version: 0.0.0 @@ -5623,7 +5636,7 @@ packages: version: 0.0.0 '@rush-temp/presentation@file:projects/presentation.tgz': - resolution: {integrity: sha512-j00+daHWZ+3GFc+1dXsCeABWAuTZbIG8mvvvPIiLGIfqVSmkH4trxbsP0appWm+/bkPfwtP0wJjhsQ4d7kgzMQ==, tarball: file:projects/presentation.tgz} + resolution: {integrity: sha512-64kvJ00DZEan7BMLQD44sU6vdctIqLG560Vd5eBzrV8ulkZ6i0/Dv2AHOOmJAJVVl7Bu6CZ03Rsbzb4xMbbeNw==, tarball: file:projects/presentation.tgz} version: 0.0.0 '@rush-temp/print-assets@file:projects/print-assets.tgz': @@ -6243,7 +6256,7 @@ packages: version: 0.0.0 '@rush-temp/ui@file:projects/ui.tgz': - resolution: {integrity: sha512-iGJVYhTpkK5rTm+K+Qlnwg/6A+spZqjxNIDODNpmdUPjMO8lIvnYj4BaX2dvUZuLmXy4L2VErS4mgok+cjNrbw==, tarball: file:projects/ui.tgz} + resolution: {integrity: sha512-3ynpo7rTSlzh7CnYosyNUuGKd+FyQ2AQdFKiGst51IdBo+9qxGY1D/t4r6Zf8hYcNGPPTWLaLrQH5a8+5C90bA==, tarball: file:projects/ui.tgz} version: 0.0.0 '@rush-temp/uploader-assets@file:projects/uploader-assets.tgz': @@ -8070,6 +8083,9 @@ packages: bluebird@3.7.2: resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} + blurhash@2.0.5: + resolution: {integrity: sha512-cRygWd7kGBQO3VEhPiTgq4Wc43ctsM+o46urrmPOiuAe+07fzlSB9OJVdpgDL0jPqXUVQ9ht7aq7kxOeJHRK+w==} + bmp-js@0.1.0: resolution: {integrity: sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==} @@ -11951,6 +11967,10 @@ packages: resolution: {integrity: sha512-vv8fJuOUCCvSPjDjBLlMqYMHob4aGjkmrkaE42/mZr0VT+ZAU10jRF8oTnX9+pgU9/vYJ8P7YT3Vd6ajkmzSCw==} engines: {node: '>=0.12'} + node-poppler@8.0.4: + resolution: {integrity: sha512-8bp200rELOX72mHv0cfnuDs7LWmd3jr3Y9pxgaysjeSxnUiZxhTA0Pg+SaXkTplIK+r4CfIVAfrla4omNq6UtA==} + engines: {node: '>=20'} + node-releases@2.0.19: resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} @@ -24719,6 +24739,46 @@ snapshots: - node-notifier - supports-color + '@rush-temp/pod-preview@file:projects/pod-preview.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(@swc/core@1.13.5)(babel-jest@29.7.0(@babel/core@7.23.9))': + dependencies: + '@types/cors': 2.8.17 + '@types/express': 4.17.21 + '@types/jest': 29.5.12 + '@types/morgan': 1.9.9 + '@types/node': 22.15.29 + '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.8.3))(eslint@8.56.0)(typescript@5.8.3) + '@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.8.3) + blurhash: 2.0.5 + cors: 2.8.5 + cross-env: 7.0.3 + dotenv: 16.0.3 + esbuild: 0.24.2 + eslint: 8.56.0 + eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.8.3))(eslint@8.56.0)(typescript@5.8.3))(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint-plugin-n@15.7.0(eslint@8.56.0))(eslint-plugin-promise@6.1.1(eslint@8.56.0))(eslint@8.56.0)(typescript@5.8.3) + eslint-plugin-import: 2.29.1(eslint@8.56.0) + eslint-plugin-n: 15.7.0(eslint@8.56.0) + eslint-plugin-node: 11.1.0(eslint@8.56.0) + eslint-plugin-promise: 6.1.1(eslint@8.56.0) + express: 4.21.2 + jest: 29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.8.3)) + lru-cache: 11.1.0 + morgan: 1.10.0 + node-poppler: 8.0.4 + prettier: 3.2.5 + sharp: 0.34.3 + ts-jest: 29.1.2(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(esbuild@0.24.2)(jest@29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.8.3)))(typescript@5.8.3) + ts-node: 10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - '@babel/core' + - '@jest/types' + - '@swc/core' + - '@swc/wasm' + - babel-jest + - babel-plugin-macros + - node-notifier + - supports-color + '@rush-temp/pod-print@file:projects/pod-print.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(@swc/core@1.13.5)(babel-jest@29.7.0(@babel/core@7.23.9))(bufferutil@4.0.8)(utf-8-validate@6.0.4)': dependencies: '@tsconfig/node16': 1.0.4 @@ -29741,6 +29801,7 @@ snapshots: '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.8.3))(eslint@8.56.0)(typescript@5.8.3) '@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.8.3) autolinker: 4.0.0 + blurhash: 2.0.5 date-fns: 2.30.0 date-fns-tz: 2.0.0(date-fns@2.30.0) dompurify: 3.1.6 @@ -32292,6 +32353,8 @@ snapshots: bluebird@3.7.2: {} + blurhash@2.0.5: {} + bmp-js@0.1.0: {} bn.js@4.12.0: {} @@ -36951,6 +37014,11 @@ snapshots: dependencies: write-file-atomic: 1.3.4 + node-poppler@8.0.4: + dependencies: + camelcase: 6.3.0 + semver: 7.7.2 + node-releases@2.0.19: {} nodemailer@6.10.0: {} diff --git a/common/scripts/docker.sh b/common/scripts/docker.sh index 16c2cb9c86..fc5085a4a7 100755 --- a/common/scripts/docker.sh +++ b/common/scripts/docker.sh @@ -21,6 +21,7 @@ rush docker:build -p 20 \ --to @hcengineering/pod-export \ --to @hcengineering/pod-msg2file \ --to @hcengineering/pod-media \ +--to @hcengineering/pod-preview \ --to @hcengineering/pod-external \ --to @hcengineering/pod-backup \ --to @hcengineering/backup-api-pod \ diff --git a/desktop/src/ui/platform.ts b/desktop/src/ui/platform.ts index 924e30777b..713edf3974 100644 --- a/desktop/src/ui/platform.ts +++ b/desktop/src/ui/platform.ts @@ -290,6 +290,7 @@ export async function configurePlatform (onWorkbenchConnect?: () => Promise - +
+ {#if !loaded} + {#if blurhash !== undefined} +
+ +
+ {:else if showLoading} +
+ +
+ {/if} + {/if} + + +
+ + diff --git a/packages/presentation/src/filetypes.ts b/packages/presentation/src/filetypes.ts index 44a3196b7f..72705fbbf7 100644 --- a/packages/presentation/src/filetypes.ts +++ b/packages/presentation/src/filetypes.ts @@ -15,11 +15,12 @@ import { Analytics } from '@hcengineering/analytics' import { type BlobMetadata, type Blob, type Ref } from '@hcengineering/core' -import { getResource } from '@hcengineering/platform' +import { getMetadata, getResource } from '@hcengineering/platform' import { type PopupAlignment } from '@hcengineering/ui' import { writable } from 'svelte/store' import plugin from './plugin' +import { getPreviewMetadata } from './preview' import type { FileOrBlob, FilePreviewExtension } from './types' import { createQuery } from './utils' @@ -27,23 +28,29 @@ import { createQuery } from './utils' * @public */ export async function getFileMetadata (file: FileOrBlob, uuid: Ref): Promise { + const workspace = getMetadata(plugin.metadata.WorkspaceUuid) ?? '' + + const metadata = await getPreviewMetadata(workspace, uuid) + const previewType = await getPreviewType(file.type, $previewTypes) if (previewType?.metadataProvider === undefined) { - return undefined + return metadata } const metadataProvider = await getResource(previewType.metadataProvider) if (metadataProvider === undefined) { - return undefined + return metadata } try { - return await metadataProvider(file, uuid) + const customMetadata = await metadataProvider(file, uuid) + return { ...metadata, ...customMetadata } } catch (err) { console.error(err) Analytics.handleError(err as Error) - return undefined } + + return metadata } /** diff --git a/packages/presentation/src/plugin.ts b/packages/presentation/src/plugin.ts index b51c50d5be..3539e83fbb 100644 --- a/packages/presentation/src/plugin.ts +++ b/packages/presentation/src/plugin.ts @@ -178,7 +178,8 @@ export default plugin(presentationId, { ClientHook: '' as Metadata, SessionId: '' as Metadata, StatsUrl: '' as Metadata, - MailUrl: '' as Metadata + MailUrl: '' as Metadata, + PreviewUrl: '' as Metadata }, status: { FileTooLarge: '' as StatusCode diff --git a/packages/presentation/src/preview.ts b/packages/presentation/src/preview.ts index d9b7d7da71..2c0e9ca17a 100644 --- a/packages/presentation/src/preview.ts +++ b/packages/presentation/src/preview.ts @@ -1,10 +1,19 @@ import type { Blob, Ref } from '@hcengineering/core' import { concatLink } from '@hcengineering/core' import { getMetadata } from '@hcengineering/platform' +import { withRetry } from '@hcengineering/retry' import { getFileUrl, getCurrentWorkspaceUuid } from './file' import presentation from './plugin' +export interface PreviewMetadata { + thumbnail?: { + width: number + height: number + blurhash: string + } +} + export interface PreviewConfig { image: string video: string @@ -96,7 +105,26 @@ function blobToSrcSet ( return '' } - let url = cfg.image.replaceAll(':workspace', encodeURIComponent(getCurrentWorkspaceUuid())) + const workspace = encodeURIComponent(getCurrentWorkspaceUuid()) + const name = encodeURIComponent(blob) + + const previewUrl = getMetadata(presentation.metadata.PreviewUrl) ?? '' + if (previewUrl !== '') { + if (width !== undefined) { + return ( + getImagePreviewUrl(workspace, name, width, height ?? width, 1) + + ' 1x , ' + + getImagePreviewUrl(workspace, name, width, height ?? width, 2) + + ' 2x, ' + + getImagePreviewUrl(workspace, name, width, height ?? width, 3) + + ' 3x' + ) + } else { + return '' + } + } + + let url = cfg.image.replaceAll(':workspace', workspace) const downloadUrl = getFileUrl(blob) const frontUrl = getMetadata(presentation.metadata.FrontUrl) ?? window.location.origin @@ -104,7 +132,7 @@ function blobToSrcSet ( url = concatLink(frontUrl ?? '', url) } url = url.replaceAll(':downloadFile', encodeURIComponent(downloadUrl)) - url = url.replaceAll(':blobId', encodeURIComponent(blob)) + url = url.replaceAll(':blobId', name) let result = '' if (width !== undefined) { @@ -120,6 +148,44 @@ function blobToSrcSet ( return result } +export function getPreviewThumbnail (file: string, width: number, height: number, dpr?: number): string { + return getImagePreviewUrl( + encodeURIComponent(getCurrentWorkspaceUuid()), + encodeURIComponent(file), + width, + height, + dpr ?? window.devicePixelRatio + ) +} + +export async function getPreviewMetadata (workspace: string, name: string): Promise { + const previewUrl = getMetadata(presentation.metadata.PreviewUrl) ?? '' + + if (previewUrl !== '') { + const token = getMetadata(presentation.metadata.Token) ?? '' + const url = concatLink(previewUrl, `/metadata/${workspace}/${name}`) + + try { + const response = await withRetry(async () => { + return await fetch(url, { headers: { Authorization: `Bearer ${token}` } }) + }) + if (response.ok) { + const json = await response.json() + return json as PreviewMetadata + } + } catch (err) { + console.warn('Failed to fetch preview metadata', err) + } + } + return {} +} + +function getImagePreviewUrl (workspace: string, name: string, width: number, height: number, dpr: number): string { + const previewUrl = getMetadata(presentation.metadata.PreviewUrl) ?? '' + const url = `/image/fit=cover,width=${width},height=${height},dpr=${dpr}/${workspace}/${name}` + return concatLink(previewUrl, url) +} + function formatImageSize (url: string, width: number, height: number, dpr: number): string { return url .replaceAll(':size', `${width * dpr}`) diff --git a/packages/ui/package.json b/packages/ui/package.json index c1398f209f..b73b7a0e33 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -51,7 +51,8 @@ "emojibase-regex": "^16.0.0", "fast-equals": "^5.2.2", "svelte": "^4.2.19", - "plyr": "^3.7.8" + "plyr": "^3.7.8", + "blurhash": "^2.0.5" }, "repository": "https://github.com/hcengineering/platform", "publishConfig": { diff --git a/packages/ui/src/components/Blurhash.svelte b/packages/ui/src/components/Blurhash.svelte new file mode 100644 index 0000000000..7d052efad6 --- /dev/null +++ b/packages/ui/src/components/Blurhash.svelte @@ -0,0 +1,54 @@ + + + + + + diff --git a/packages/ui/src/components/Video.svelte b/packages/ui/src/components/Video.svelte index 1ace8bb6de..6e1bde5b65 100644 --- a/packages/ui/src/components/Video.svelte +++ b/packages/ui/src/components/Video.svelte @@ -18,6 +18,7 @@ export let src: string export let name: string | undefined = undefined + export let poster: string | undefined = undefined export let preload = true let video: HTMLVideoElement @@ -40,7 +41,15 @@ }) -