119 lines
3.5 KiB
TypeScript
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,
|
|
}))
|
|
}
|