mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-23 19:44:59 +03:00
UBERF-7597: Get rid of formats in preview.ts (#6077)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
7fb8737959
commit
d6eaaa9f01
@ -4,9 +4,6 @@ import { getMetadata } from '@hcengineering/platform'
|
|||||||
import { getBlobHref, getClient, getCurrentWorkspaceUrl, getFileUrl } from '.'
|
import { getBlobHref, getClient, getCurrentWorkspaceUrl, getFileUrl } from '.'
|
||||||
import presentation from './plugin'
|
import presentation from './plugin'
|
||||||
|
|
||||||
type SupportedFormat = string
|
|
||||||
const defaultSupportedFormats = 'avif,webp,heif,jpeg'
|
|
||||||
|
|
||||||
export interface ProviderPreviewConfig {
|
export interface ProviderPreviewConfig {
|
||||||
// Identifier of provider
|
// Identifier of provider
|
||||||
// If set to '' could be applied to any provider, for example to exclude some 'image/gif' etc from being processing with providers.
|
// If set to '' could be applied to any provider, for example to exclude some 'image/gif' etc from being processing with providers.
|
||||||
@ -14,8 +11,6 @@ export interface ProviderPreviewConfig {
|
|||||||
// Preview url
|
// Preview url
|
||||||
// If '' preview is disabled for config.
|
// If '' preview is disabled for config.
|
||||||
previewUrl: string
|
previewUrl: string
|
||||||
// A supported file formats
|
|
||||||
formats: SupportedFormat[]
|
|
||||||
|
|
||||||
// Content type markers, will check by containts, if passed, only allow to be used with matched content types.
|
// Content type markers, will check by containts, if passed, only allow to be used with matched content types.
|
||||||
contentTypes?: string[]
|
contentTypes?: string[]
|
||||||
@ -28,8 +23,7 @@ export interface PreviewConfig {
|
|||||||
|
|
||||||
const defaultPreview = (): ProviderPreviewConfig => ({
|
const defaultPreview = (): ProviderPreviewConfig => ({
|
||||||
providerId: '',
|
providerId: '',
|
||||||
formats: ['avif', 'webp', 'jpg'],
|
previewUrl: `/files/${getCurrentWorkspaceUrl()}?file=:blobId&size=:size`
|
||||||
previewUrl: `/files/${getCurrentWorkspaceUrl()}?file=:blobId.:format&size=:size`
|
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,7 +33,7 @@ const defaultPreview = (): ProviderPreviewConfig => ({
|
|||||||
|
|
||||||
- providerName - a provider name should be same as in Storage configuration.
|
- providerName - a provider name should be same as in Storage configuration.
|
||||||
It coult be empty and it will match by content types.
|
It coult be empty and it will match by content types.
|
||||||
- previewUrl - an Url with :workspace, :blobId, :downloadFile, :size, :format placeholders, they will be replaced in UI with an appropriate blob values.
|
- previewUrl - an Url with :workspace, :blobId, :downloadFile, :size placeholders, they will be replaced in UI with an appropriate blob values.
|
||||||
- supportedFormats - a `,` separated list of file extensions.
|
- supportedFormats - a `,` separated list of file extensions.
|
||||||
- contentTypes - a ',' separated list of content type patterns.
|
- contentTypes - a ',' separated list of content type patterns.
|
||||||
|
|
||||||
@ -58,14 +52,14 @@ export function parsePreviewConfig (config?: string): PreviewConfig | undefined
|
|||||||
if (c === '') {
|
if (c === '') {
|
||||||
continue // Skip empty lines
|
continue // Skip empty lines
|
||||||
}
|
}
|
||||||
|
const vars = c.split('|')
|
||||||
let [provider, url, formats, contentTypes] = c.split('|').map((it) => it.trim())
|
let [provider, url, formats, contentTypes] = c.split('|').map((it) => it.trim())
|
||||||
if (formats === undefined) {
|
if (vars.length === 3) {
|
||||||
formats = defaultSupportedFormats
|
contentTypes = formats // Backward compatibility, since formats are obsolete
|
||||||
}
|
}
|
||||||
const p: ProviderPreviewConfig = {
|
const p: ProviderPreviewConfig = {
|
||||||
providerId: provider,
|
providerId: provider,
|
||||||
previewUrl: url,
|
previewUrl: url,
|
||||||
formats: formats.split(',').map((it) => it.trim()),
|
|
||||||
// Allow preview only for images by default
|
// Allow preview only for images by default
|
||||||
contentTypes:
|
contentTypes:
|
||||||
contentTypes !== undefined
|
contentTypes !== undefined
|
||||||
@ -94,7 +88,6 @@ export function getPreviewConfig (): PreviewConfig {
|
|||||||
{
|
{
|
||||||
providerId: '',
|
providerId: '',
|
||||||
contentTypes: ['image/gif', 'image/apng', 'image/svg'], // Disable gif and apng format preview.
|
contentTypes: ['image/gif', 'image/apng', 'image/svg'], // Disable gif and apng format preview.
|
||||||
formats: [],
|
|
||||||
previewUrl: ''
|
previewUrl: ''
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -182,13 +175,7 @@ function blobToSrcSet (
|
|||||||
url = url.replaceAll(':blobId', encodeURIComponent(blob._id))
|
url = url.replaceAll(':blobId', encodeURIComponent(blob._id))
|
||||||
|
|
||||||
let result = ''
|
let result = ''
|
||||||
for (const f of cfg.formats ?? []) {
|
const fu = url
|
||||||
if (result.length > 0) {
|
|
||||||
result += ', '
|
|
||||||
}
|
|
||||||
|
|
||||||
const fu = url.replaceAll(':format', f)
|
|
||||||
|
|
||||||
if (width !== undefined) {
|
if (width !== undefined) {
|
||||||
result +=
|
result +=
|
||||||
fu.replaceAll(':size', `${width}`) +
|
fu.replaceAll(':size', `${width}`) +
|
||||||
@ -200,7 +187,7 @@ function blobToSrcSet (
|
|||||||
} else {
|
} else {
|
||||||
result += fu.replaceAll(':size', `${-1}`)
|
result += fu.replaceAll(':size', `${-1}`)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,11 +28,11 @@ A `;` separated list of triples, providerName|previewUrl|supportedFormats.
|
|||||||
|
|
||||||
- providerName - a provider name should be same as in Storage configuration.
|
- providerName - a provider name should be same as in Storage configuration.
|
||||||
It coult be empty and it will match by content types.
|
It coult be empty and it will match by content types.
|
||||||
- previewUrl - an Url with :workspace, :blobId, :downloadFile, :size, :format placeholders, they will be replaced in UI with an appropriate blob values.
|
- previewUrl - an Url with :workspace, :blobId, :downloadFile, :size placeholders, they will be replaced in UI with an appropriate blob values.
|
||||||
- supportedFormats - a `,` separated list of file extensions.
|
- supportedFormats - a `,` separated list of file extensions.
|
||||||
- contentTypes - a ',' separated list of content type patterns.
|
- contentTypes - a ',' separated list of content type patterns.
|
||||||
|
|
||||||
PREVIEW_CONFIG=*|https://front.hc.engineering/files/:workspace/api/preview/?format=:format&width=:size&image=:downloadFile
|
PREVIEW_CONFIG=*|https://front.hc.engineering/files/:workspace/api/preview/?width=:size&image=:downloadFile
|
||||||
|
|
||||||
## Variables
|
## Variables
|
||||||
|
|
||||||
@ -40,7 +40,6 @@ PREVIEW_CONFIG=*|https://front.hc.engineering/files/:workspace/api/preview/?form
|
|||||||
- :blobId - an uniq blob _id identifier.
|
- :blobId - an uniq blob _id identifier.
|
||||||
- :size - a numeric value to determine required size of the image, image will not be upscaled, only downscaled. If -1 is passed, original image size value will be used.
|
- :size - a numeric value to determine required size of the image, image will not be upscaled, only downscaled. If -1 is passed, original image size value will be used.
|
||||||
- :downloadFile - an URI encoded component value of full download URI, could be presigned uri to S3 storage.
|
- :downloadFile - an URI encoded component value of full download URI, could be presigned uri to S3 storage.
|
||||||
- :format - an a conversion file format, `avif`,`webp` etc.
|
|
||||||
|
|
||||||
## Passing default variant.
|
## Passing default variant.
|
||||||
|
|
||||||
@ -50,7 +49,7 @@ providerName could be set to `*` in this case it will be default preview provide
|
|||||||
|
|
||||||
If no preview config are specified, a default one targating a front service preview/resize functionality will be used.
|
If no preview config are specified, a default one targating a front service preview/resize functionality will be used.
|
||||||
|
|
||||||
`/files/${getCurrentWorkspaceUrl()}?file=:blobId.:format&size=:size`
|
`/files/${getCurrentWorkspaceUrl()}?file=:blobId&size=:size`
|
||||||
|
|
||||||
## Testing with dev-production/etc.
|
## Testing with dev-production/etc.
|
||||||
|
|
||||||
|
@ -32,13 +32,11 @@ import { v4 as uuid } from 'uuid'
|
|||||||
import { preConditions } from './utils'
|
import { preConditions } from './utils'
|
||||||
|
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
|
import { Readable } from 'stream'
|
||||||
|
|
||||||
const cacheControlValue = 'public, max-age=365d'
|
const cacheControlValue = 'public, max-age=365d'
|
||||||
const cacheControlNoCache = 'public, no-store, no-cache, must-revalidate, max-age=0'
|
const cacheControlNoCache = 'public, no-store, no-cache, must-revalidate, max-age=0'
|
||||||
|
|
||||||
type SupportedFormat = 'jpeg' | 'avif' | 'heif' | 'webp' | 'png'
|
|
||||||
const supportedFormats: SupportedFormat[] = ['avif', 'webp', 'heif', 'jpeg', 'png']
|
|
||||||
|
|
||||||
async function storageUpload (
|
async function storageUpload (
|
||||||
ctx: MeasureContext,
|
ctx: MeasureContext,
|
||||||
storageAdapter: StorageAdapter,
|
storageAdapter: StorageAdapter,
|
||||||
@ -118,6 +116,8 @@ async function getFileRange (
|
|||||||
'Last-Modified': new Date(stat.modifiedOn).toISOString()
|
'Last-Modified': new Date(stat.modifiedOn).toISOString()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
res.send(Readable.toWeb(dataStream))
|
||||||
|
|
||||||
dataStream.pipe(res)
|
dataStream.pipe(res)
|
||||||
|
|
||||||
await new Promise<void>((resolve, reject) => {
|
await new Promise<void>((resolve, reject) => {
|
||||||
@ -179,7 +179,12 @@ async function getFile (
|
|||||||
}
|
}
|
||||||
if (preConditions.IfUnmodifiedSince(req.headers, { lastModified: new Date(stat.modifiedOn) }) === 'failed') {
|
if (preConditions.IfUnmodifiedSince(req.headers, { lastModified: new Date(stat.modifiedOn) }) === 'failed') {
|
||||||
// Send 412 (Precondition Failed)
|
// Send 412 (Precondition Failed)
|
||||||
res.statusCode = 412
|
res.writeHead(412, {
|
||||||
|
'content-type': stat.contentType,
|
||||||
|
etag: stat.etag,
|
||||||
|
'last-modified': new Date(stat.modifiedOn).toISOString(),
|
||||||
|
'cache-control': cacheControlValue
|
||||||
|
})
|
||||||
res.end()
|
res.end()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -360,17 +365,12 @@ export function start (
|
|||||||
(req.query.token as string | undefined)
|
(req.query.token as string | undefined)
|
||||||
payload = token !== undefined ? decodeToken(token) : payload
|
payload = token !== undefined ? decodeToken(token) : payload
|
||||||
|
|
||||||
let uuid = req.params.file ?? req.query.file
|
const uuid = req.params.file ?? req.query.file
|
||||||
if (uuid === undefined) {
|
if (uuid === undefined) {
|
||||||
res.status(404).send()
|
res.status(404).send()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const format: SupportedFormat | undefined = supportedFormats.find((it) => uuid.endsWith(it))
|
|
||||||
if (format !== undefined) {
|
|
||||||
uuid = uuid.slice(0, uuid.length - format.length - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
let blobInfo = await ctx.with(
|
let blobInfo = await ctx.with(
|
||||||
'notoken-stat',
|
'notoken-stat',
|
||||||
{ workspace: payload.workspace.name },
|
{ workspace: payload.workspace.name },
|
||||||
@ -411,12 +411,13 @@ export function start (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const size = req.query.size !== undefined ? parseInt(req.query.size as string) : undefined
|
const size = req.query.size !== undefined ? parseInt(req.query.size as string) : undefined
|
||||||
if (format !== undefined && isImage && blobInfo.contentType !== 'image/gif') {
|
const accept = req.headers.accept
|
||||||
|
if (accept !== undefined && isImage && blobInfo.contentType !== 'image/gif') {
|
||||||
blobInfo = await ctx.with(
|
blobInfo = await ctx.with(
|
||||||
'resize',
|
'resize',
|
||||||
{},
|
{},
|
||||||
async (ctx) =>
|
async (ctx) =>
|
||||||
await getGeneratePreview(ctx, blobInfo as PlatformBlob, size ?? -1, uuid, config, payload, format)
|
await getGeneratePreview(ctx, blobInfo as PlatformBlob, size ?? -1, uuid, config, payload, accept)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -731,6 +732,8 @@ export function start (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const supportedFormats = ['avif', 'webp', 'heif', 'jpeg', 'png']
|
||||||
|
|
||||||
async function getGeneratePreview (
|
async function getGeneratePreview (
|
||||||
ctx: MeasureContext,
|
ctx: MeasureContext,
|
||||||
blob: PlatformBlob,
|
blob: PlatformBlob,
|
||||||
@ -738,11 +741,29 @@ async function getGeneratePreview (
|
|||||||
uuid: string,
|
uuid: string,
|
||||||
config: { storageAdapter: StorageAdapter },
|
config: { storageAdapter: StorageAdapter },
|
||||||
payload: Token,
|
payload: Token,
|
||||||
format: SupportedFormat = 'jpeg'
|
accept: string
|
||||||
): Promise<PlatformBlob> {
|
): Promise<PlatformBlob> {
|
||||||
if (size === undefined) {
|
if (size === undefined) {
|
||||||
return blob
|
return blob
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const formats = accept.split(',').map((it) => it.trim())
|
||||||
|
|
||||||
|
// Select appropriate format
|
||||||
|
let format: string | undefined
|
||||||
|
|
||||||
|
for (const f of formats) {
|
||||||
|
const [type] = f.split(';')
|
||||||
|
const [clazz, kind] = type.split('/')
|
||||||
|
if (clazz === 'image' && supportedFormats.includes(kind)) {
|
||||||
|
format = kind
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (format === undefined) {
|
||||||
|
return blob
|
||||||
|
}
|
||||||
|
|
||||||
const sizeId = uuid + `%preview%${size}${format !== 'jpeg' ? format : ''}`
|
const sizeId = uuid + `%preview%${size}${format !== 'jpeg' ? format : ''}`
|
||||||
|
|
||||||
const d = await config.storageAdapter.stat(ctx, payload.workspace, sizeId)
|
const d = await config.storageAdapter.stat(ctx, payload.workspace, sizeId)
|
||||||
@ -823,7 +844,7 @@ async function getGeneratePreview (
|
|||||||
Analytics.handleError(err)
|
Analytics.handleError(err)
|
||||||
ctx.error('failed to resize image', {
|
ctx.error('failed to resize image', {
|
||||||
err,
|
err,
|
||||||
format,
|
format: accept,
|
||||||
contentType: blob.contentType,
|
contentType: blob.contentType,
|
||||||
uuid,
|
uuid,
|
||||||
size: blob.size,
|
size: blob.size,
|
||||||
|
@ -103,7 +103,7 @@ export function startFront (ctx: MeasureContext, extraConfig?: Record<string, st
|
|||||||
|
|
||||||
if (previewConfig === undefined) {
|
if (previewConfig === undefined) {
|
||||||
// Use universal preview config
|
// Use universal preview config
|
||||||
previewConfig = `*|${uploadUrl}/:workspace?file=:blobId.:format&size=:size`
|
previewConfig = `*|${uploadUrl}/:workspace?file=:blobId&size=:size`
|
||||||
}
|
}
|
||||||
|
|
||||||
const pushPublicKey = process.env.PUSH_PUBLIC_KEY
|
const pushPublicKey = process.env.PUSH_PUBLIC_KEY
|
||||||
|
Loading…
Reference in New Issue
Block a user