gongxiangp2/lib/performance-survey-image-browser.ts

119 lines
3.5 KiB
TypeScript

import { sortPerformanceSurveyImagesByName } from '@/lib/performance-survey-image-order'
export type PerformanceSurveyProcessedImageKind = 'slice' | 'preview'
export type PerformanceSurveyBrowserProcessedImage = {
file: File
mimeType: 'image/webp'
width: number
height: number
kind: PerformanceSurveyProcessedImageKind
sourceName: string
sourceIndex: number
sliceIndex: number
sliceCount: number
}
type WorkerProcessedImage = Omit<PerformanceSurveyBrowserProcessedImage, 'file'> & {
blob: Blob
fileName: string
}
type WorkerSuccessMessage = {
id: string
ok: true
images: WorkerProcessedImage[]
}
type WorkerErrorMessage = {
id: string
ok: false
error: string
}
const DESKTOP_BROWSER_REQUIRED_MESSAGE = '图片需要在桌面版 Chrome 或 Edge 中处理,请更换浏览器后重新上传'
function assertPerformanceSurveyBrowserImageSupport() {
if (
typeof window === 'undefined'
|| typeof Worker === 'undefined'
|| typeof OffscreenCanvas === 'undefined'
|| typeof createImageBitmap === 'undefined'
) {
throw new Error(DESKTOP_BROWSER_REQUIRED_MESSAGE)
}
}
function runImageWorker(
files: File[],
profile: 'performance_survey' | 'work_review',
): Promise<WorkerProcessedImage[]> {
assertPerformanceSurveyBrowserImageSupport()
return new Promise((resolve, reject) => {
let worker: Worker
try {
worker = new Worker(new URL('./performance-survey-image.worker.ts', import.meta.url), { type: 'module' })
} catch {
reject(new Error('浏览器图片处理线程启动失败,请使用桌面版 Chrome 或 Edge 重试'))
return
}
const requestId = `${Date.now()}-${Math.random().toString(36).slice(2)}`
let settled = false
const finish = (callback: () => void) => {
if (settled) return
settled = true
worker.terminate()
callback()
}
worker.onmessage = (event: MessageEvent<WorkerSuccessMessage | WorkerErrorMessage>) => {
const data = event.data
if (data?.id !== requestId) return
if (data.ok === true) {
finish(() => resolve(data.images))
} else {
finish(() => reject(new Error(data.error || '浏览器图片处理失败')))
}
}
worker.onerror = () => {
finish(() => reject(new Error('浏览器图片处理线程启动失败,请使用桌面版 Chrome 或 Edge 重试')))
}
worker.postMessage({ id: requestId, files, profile })
})
}
export async function preparePerformanceSurveyImagesInBrowser(
files: File[],
onProgress?: (label: string) => void,
options?: {
profile?: 'performance_survey' | 'work_review'
},
): Promise<PerformanceSurveyBrowserProcessedImage[]> {
if (files.length === 0) throw new Error('请至少选择 1 张图片')
const orderedFiles = sortPerformanceSurveyImagesByName(files, (file) => file.name)
onProgress?.(
orderedFiles.length === 1
? '正在浏览器后台生成预览和 WebP 切片'
: `正在浏览器后台转换 ${orderedFiles.length} 张有序切片为 WebP`,
)
const processed = await runImageWorker(orderedFiles, options?.profile || 'performance_survey')
return processed.map((item) => ({
file: new File([item.blob], item.fileName, {
type: item.mimeType,
lastModified: Date.now(),
}),
mimeType: item.mimeType,
width: item.width,
height: item.height,
kind: item.kind,
sourceName: item.sourceName,
sourceIndex: item.sourceIndex,
sliceIndex: item.sliceIndex,
sliceCount: item.sliceCount,
}))
}