( imageNames: string[], fetchMask: MaskFetchFunction, onProgress?: MaskZipProgressCallback )
| 18 | } |
| 19 | |
| 20 | export async function exportMasksZip( |
| 21 | imageNames: string[], |
| 22 | fetchMask: MaskFetchFunction, |
| 23 | onProgress?: MaskZipProgressCallback |
| 24 | ): Promise<Blob> { |
| 25 | const { zipSync } = await import('fflate'); |
| 26 | |
| 27 | const totalImages = imageNames.length; |
| 28 | const zipData: Record<string, Uint8Array> = {}; |
| 29 | let processed = 0; |
| 30 | let failed = 0; |
| 31 | |
| 32 | for (const imageName of imageNames) { |
| 33 | try { |
| 34 | const file = await fetchMask(imageName); |
| 35 | if (!file) { |
| 36 | failed++; |
| 37 | processed++; |
| 38 | onProgress?.(Math.round((processed / totalImages) * 100), `Skipped: ${imageName}`); |
| 39 | continue; |
| 40 | } |
| 41 | |
| 42 | const arrayBuffer = await file.arrayBuffer(); |
| 43 | zipData[normalizeMaskPath(imageName)] = new Uint8Array(arrayBuffer); |
| 44 | } catch (err) { |
| 45 | appLogger.warn(`[Mask Export] Failed to process ${imageName}:`, err); |
| 46 | failed++; |
| 47 | } |
| 48 | |
| 49 | processed++; |
| 50 | onProgress?.(Math.round((processed / totalImages) * 100)); |
| 51 | } |
| 52 | |
| 53 | if (failed > 0) { |
| 54 | appLogger.warn(`[Mask Export] ${failed}/${totalImages} masks failed to export`); |
| 55 | } |
| 56 | |
| 57 | const zipped = zipSync(zipData, { level: 6 }); |
| 58 | return createZipBlob(zipped); |
| 59 | } |
| 60 | |
| 61 | export async function downloadMasksZip( |
| 62 | imageNames: string[], |
no test coverage detected