@file-viewer/renderer-archive
Advanced tools
| import { type ArchiveEntryView } from './archiveShared.js'; | ||
| /** | ||
| * Finds the files that form the selected Shapefile dataset. Shapefile | ||
| * sidecars are matched case-insensitively by directory and basename so an | ||
| * archive containing multiple datasets never leaks attributes or projection | ||
| * metadata from a neighbouring dataset into the preview. | ||
| */ | ||
| export declare const getShapefileBundleEntries: (entries: readonly ArchiveEntryView[], selectedEntry: ArchiveEntryView) => ArchiveEntryView[]; | ||
| /** | ||
| * shpjs accepts an ArrayBuffer ZIP for a complete Shapefile dataset. Archive | ||
| * previews normally extract one entry at a time, so rebuild a small STORE-only | ||
| * ZIP from the selected .shp and its sidecars before delegating to the existing | ||
| * geospatial renderer. STORE avoids wasting CPU recompressing already | ||
| * compressed archive data. | ||
| */ | ||
| export declare const createShapefileBundleArchive: (entries: readonly ArchiveEntryView[], readEntry: (entry: ArchiveEntryView) => Promise<ArrayBuffer>) => Promise<ArrayBuffer>; |
| import { getArchiveEntryExtension, } from './archiveShared.js'; | ||
| const SHAPEFILE_COMPONENT_EXTENSIONS = new Set([ | ||
| 'shp', | ||
| 'shx', | ||
| 'dbf', | ||
| 'prj', | ||
| 'cpg', | ||
| ]); | ||
| const SHAPEFILE_COMPONENT_ORDER = new Map([ | ||
| ['shp', 0], | ||
| ['dbf', 1], | ||
| ['shx', 2], | ||
| ['prj', 3], | ||
| ['cpg', 4], | ||
| ]); | ||
| const normalizeArchivePath = (path) => path | ||
| .replace(/^\/+/, '') | ||
| .replace(/\\/g, '/'); | ||
| const getArchivePathParts = (path) => { | ||
| const normalized = normalizeArchivePath(path); | ||
| const slash = normalized.lastIndexOf('/'); | ||
| const directory = slash === -1 ? '' : normalized.slice(0, slash); | ||
| const filename = slash === -1 ? normalized : normalized.slice(slash + 1); | ||
| const extension = getArchiveEntryExtension(filename); | ||
| const stem = extension | ||
| ? filename.slice(0, -(extension.length + 1)) | ||
| : filename; | ||
| return { | ||
| directory: directory.toLowerCase(), | ||
| filename, | ||
| stem: stem.toLowerCase(), | ||
| extension, | ||
| }; | ||
| }; | ||
| const resolveJSZipWriter = (module) => { | ||
| const record = module; | ||
| const defaultRecord = record === null || record === void 0 ? void 0 : record.default; | ||
| const candidates = [ | ||
| record === null || record === void 0 ? void 0 : record.default, | ||
| defaultRecord === null || defaultRecord === void 0 ? void 0 : defaultRecord.default, | ||
| record === null || record === void 0 ? void 0 : record.JSZip, | ||
| module, | ||
| ]; | ||
| const constructor = candidates.find(candidate => typeof candidate === 'function'); | ||
| if (!constructor) { | ||
| throw new Error('JSZip module does not expose a constructor.'); | ||
| } | ||
| return constructor; | ||
| }; | ||
| /** | ||
| * Finds the files that form the selected Shapefile dataset. Shapefile | ||
| * sidecars are matched case-insensitively by directory and basename so an | ||
| * archive containing multiple datasets never leaks attributes or projection | ||
| * metadata from a neighbouring dataset into the preview. | ||
| */ | ||
| export const getShapefileBundleEntries = (entries, selectedEntry) => { | ||
| const selected = getArchivePathParts(selectedEntry.path); | ||
| if (selected.extension !== 'shp') { | ||
| return []; | ||
| } | ||
| return entries | ||
| .filter(entry => { | ||
| const candidate = getArchivePathParts(entry.path); | ||
| return candidate.directory === selected.directory && | ||
| candidate.stem === selected.stem && | ||
| SHAPEFILE_COMPONENT_EXTENSIONS.has(candidate.extension); | ||
| }) | ||
| .sort((left, right) => { | ||
| var _a, _b; | ||
| const leftExtension = getArchivePathParts(left.path).extension; | ||
| const rightExtension = getArchivePathParts(right.path).extension; | ||
| return ((_a = SHAPEFILE_COMPONENT_ORDER.get(leftExtension)) !== null && _a !== void 0 ? _a : Number.MAX_SAFE_INTEGER) - | ||
| ((_b = SHAPEFILE_COMPONENT_ORDER.get(rightExtension)) !== null && _b !== void 0 ? _b : Number.MAX_SAFE_INTEGER); | ||
| }); | ||
| }; | ||
| /** | ||
| * shpjs accepts an ArrayBuffer ZIP for a complete Shapefile dataset. Archive | ||
| * previews normally extract one entry at a time, so rebuild a small STORE-only | ||
| * ZIP from the selected .shp and its sidecars before delegating to the existing | ||
| * geospatial renderer. STORE avoids wasting CPU recompressing already | ||
| * compressed archive data. | ||
| */ | ||
| export const createShapefileBundleArchive = async (entries, readEntry) => { | ||
| const JSZip = resolveJSZipWriter(await import('jszip')); | ||
| const zip = new JSZip(); | ||
| for (const entry of entries) { | ||
| const { filename } = getArchivePathParts(entry.path); | ||
| zip.file(filename || entry.name, await readEntry(entry)); | ||
| } | ||
| return zip.generateAsync({ | ||
| type: 'arraybuffer', | ||
| compression: 'STORE', | ||
| }); | ||
| }; |
+10
-3
@@ -7,2 +7,3 @@ import { resolveFileViewerArchiveWasmUrl, resolveFileViewerArchiveWorkerUrl, resolveFileViewerRuntimeAssetBaseUrl, } from '@file-viewer/core/assets'; | ||
| import { comicBookStyle, createComicBookController, } from './comicBook.js'; | ||
| import { createShapefileBundleArchive, getShapefileBundleEntries, } from './shapefileBundle.js'; | ||
| const DEFAULT_MAX_ARCHIVE_SIZE = 320 * 1024 * 1024; | ||
@@ -833,6 +834,10 @@ const DEFAULT_MAX_ENTRY_PREVIEW_SIZE = 64 * 1024 * 1024; | ||
| syncState(); | ||
| if (entry.size > maxEntryPreviewSize) { | ||
| const shapefileEntries = getShapefileBundleEntries(entries, entry); | ||
| const previewSize = shapefileEntries.length | ||
| ? shapefileEntries.reduce((sum, component) => sum + component.size, 0) | ||
| : entry.size; | ||
| if (previewSize > maxEntryPreviewSize) { | ||
| setError(t('archive.error.entryTooLarge', { | ||
| name: entry.name, | ||
| size: formatArchiveBytes(entry.size), | ||
| size: formatArchiveBytes(previewSize), | ||
| limit: formatArchiveBytes(maxEntryPreviewSize), | ||
@@ -845,3 +850,5 @@ })); | ||
| try { | ||
| const entryBuffer = await extractEntryBuffer(entry); | ||
| const entryBuffer = shapefileEntries.length | ||
| ? await createShapefileBundleArchive(shapefileEntries, extractEntryBuffer) | ||
| : await extractEntryBuffer(entry); | ||
| if (requestId !== previewSequence) { | ||
@@ -848,0 +855,0 @@ return; |
+4
-3
| { | ||
| "name": "@file-viewer/renderer-archive", | ||
| "version": "2.2.3", | ||
| "version": "2.2.4", | ||
| "private": false, | ||
@@ -59,3 +59,3 @@ "type": "module", | ||
| "dependencies": { | ||
| "@file-viewer/core": "2.2.3", | ||
| "@file-viewer/core": "2.2.4", | ||
| "jszip": "^3.10.1", | ||
@@ -71,4 +71,5 @@ "libarchive.js": "^2.0.2" | ||
| "type-check": "tsc -b tsconfig.json", | ||
| "verify:github-101": "pnpm build && node scripts/verify-github-101.mjs" | ||
| "verify:github-101": "pnpm build && node scripts/verify-github-101.mjs", | ||
| "verify:github-148": "pnpm build && node scripts/verify-github-148.mjs" | ||
| } | ||
| } |
110087
4.97%18
12.5%1833
6.82%+ Added
- Removed
Updated