@file-viewer/renderer-archive 2.2.3 → 2.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/archive.js CHANGED
@@ -4,6 +4,7 @@ import { buildArchiveNestedRenderContext, createArchiveCacheKey, flattenArchiveO
4
4
  import { readArchiveCache, writeArchiveCache } from './archiveCache.js';
5
5
  import { hasLikelyGbkZipFilenames, isLikelyEncryptedArchive, loadArchiveEntriesWithoutWorker, } from './archiveFallback.js';
6
6
  import { comicBookStyle, createComicBookController, } from './comicBook.js';
7
+ import { createShapefileBundleArchive, getShapefileBundleEntries, } from './shapefileBundle.js';
7
8
  const DEFAULT_MAX_ARCHIVE_SIZE = 320 * 1024 * 1024;
8
9
  const DEFAULT_MAX_ENTRY_PREVIEW_SIZE = 64 * 1024 * 1024;
9
10
  const DEFAULT_WORKER_TIMEOUT_MS = 30000;
@@ -829,10 +830,14 @@ export default async function renderArchive(buffer, target, _type, context) {
829
830
  comicBook.onEntrySelected(entry);
830
831
  renderEntryList();
831
832
  syncState();
832
- if (entry.size > maxEntryPreviewSize) {
833
+ const shapefileEntries = getShapefileBundleEntries(entries, entry);
834
+ const previewSize = shapefileEntries.length
835
+ ? shapefileEntries.reduce((sum, component) => sum + component.size, 0)
836
+ : entry.size;
837
+ if (previewSize > maxEntryPreviewSize) {
833
838
  setError(t('archive.error.entryTooLarge', {
834
839
  name: entry.name,
835
- size: formatArchiveBytes(entry.size),
840
+ size: formatArchiveBytes(previewSize),
836
841
  limit: formatArchiveBytes(maxEntryPreviewSize),
837
842
  }));
838
843
  return;
@@ -840,7 +845,9 @@ export default async function renderArchive(buffer, target, _type, context) {
840
845
  setLoading(true, t('archive.loading.extracting', { name: entry.name }));
841
846
  setError('');
842
847
  try {
843
- const entryBuffer = await extractEntryBuffer(entry);
848
+ const entryBuffer = shapefileEntries.length
849
+ ? await createShapefileBundleArchive(shapefileEntries, extractEntryBuffer)
850
+ : await extractEntryBuffer(entry);
844
851
  if (requestId !== previewSequence) {
845
852
  return;
846
853
  }
@@ -0,0 +1,16 @@
1
+ import { type ArchiveEntryView } from './archiveShared.js';
2
+ /**
3
+ * Finds the files that form the selected Shapefile dataset. Shapefile
4
+ * sidecars are matched case-insensitively by directory and basename so an
5
+ * archive containing multiple datasets never leaks attributes or projection
6
+ * metadata from a neighbouring dataset into the preview.
7
+ */
8
+ export declare const getShapefileBundleEntries: (entries: readonly ArchiveEntryView[], selectedEntry: ArchiveEntryView) => ArchiveEntryView[];
9
+ /**
10
+ * shpjs accepts an ArrayBuffer ZIP for a complete Shapefile dataset. Archive
11
+ * previews normally extract one entry at a time, so rebuild a small STORE-only
12
+ * ZIP from the selected .shp and its sidecars before delegating to the existing
13
+ * geospatial renderer. STORE avoids wasting CPU recompressing already
14
+ * compressed archive data.
15
+ */
16
+ export declare const createShapefileBundleArchive: (entries: readonly ArchiveEntryView[], readEntry: (entry: ArchiveEntryView) => Promise<ArrayBuffer>) => Promise<ArrayBuffer>;
@@ -0,0 +1,94 @@
1
+ import { getArchiveEntryExtension, } from './archiveShared.js';
2
+ const SHAPEFILE_COMPONENT_EXTENSIONS = new Set([
3
+ 'shp',
4
+ 'shx',
5
+ 'dbf',
6
+ 'prj',
7
+ 'cpg',
8
+ ]);
9
+ const SHAPEFILE_COMPONENT_ORDER = new Map([
10
+ ['shp', 0],
11
+ ['dbf', 1],
12
+ ['shx', 2],
13
+ ['prj', 3],
14
+ ['cpg', 4],
15
+ ]);
16
+ const normalizeArchivePath = (path) => path
17
+ .replace(/^\/+/, '')
18
+ .replace(/\\/g, '/');
19
+ const getArchivePathParts = (path) => {
20
+ const normalized = normalizeArchivePath(path);
21
+ const slash = normalized.lastIndexOf('/');
22
+ const directory = slash === -1 ? '' : normalized.slice(0, slash);
23
+ const filename = slash === -1 ? normalized : normalized.slice(slash + 1);
24
+ const extension = getArchiveEntryExtension(filename);
25
+ const stem = extension
26
+ ? filename.slice(0, -(extension.length + 1))
27
+ : filename;
28
+ return {
29
+ directory: directory.toLowerCase(),
30
+ filename,
31
+ stem: stem.toLowerCase(),
32
+ extension,
33
+ };
34
+ };
35
+ const resolveJSZipWriter = (module) => {
36
+ const record = module;
37
+ const defaultRecord = record === null || record === void 0 ? void 0 : record.default;
38
+ const candidates = [
39
+ record === null || record === void 0 ? void 0 : record.default,
40
+ defaultRecord === null || defaultRecord === void 0 ? void 0 : defaultRecord.default,
41
+ record === null || record === void 0 ? void 0 : record.JSZip,
42
+ module,
43
+ ];
44
+ const constructor = candidates.find(candidate => typeof candidate === 'function');
45
+ if (!constructor) {
46
+ throw new Error('JSZip module does not expose a constructor.');
47
+ }
48
+ return constructor;
49
+ };
50
+ /**
51
+ * Finds the files that form the selected Shapefile dataset. Shapefile
52
+ * sidecars are matched case-insensitively by directory and basename so an
53
+ * archive containing multiple datasets never leaks attributes or projection
54
+ * metadata from a neighbouring dataset into the preview.
55
+ */
56
+ export const getShapefileBundleEntries = (entries, selectedEntry) => {
57
+ const selected = getArchivePathParts(selectedEntry.path);
58
+ if (selected.extension !== 'shp') {
59
+ return [];
60
+ }
61
+ return entries
62
+ .filter(entry => {
63
+ const candidate = getArchivePathParts(entry.path);
64
+ return candidate.directory === selected.directory &&
65
+ candidate.stem === selected.stem &&
66
+ SHAPEFILE_COMPONENT_EXTENSIONS.has(candidate.extension);
67
+ })
68
+ .sort((left, right) => {
69
+ var _a, _b;
70
+ const leftExtension = getArchivePathParts(left.path).extension;
71
+ const rightExtension = getArchivePathParts(right.path).extension;
72
+ return ((_a = SHAPEFILE_COMPONENT_ORDER.get(leftExtension)) !== null && _a !== void 0 ? _a : Number.MAX_SAFE_INTEGER) -
73
+ ((_b = SHAPEFILE_COMPONENT_ORDER.get(rightExtension)) !== null && _b !== void 0 ? _b : Number.MAX_SAFE_INTEGER);
74
+ });
75
+ };
76
+ /**
77
+ * shpjs accepts an ArrayBuffer ZIP for a complete Shapefile dataset. Archive
78
+ * previews normally extract one entry at a time, so rebuild a small STORE-only
79
+ * ZIP from the selected .shp and its sidecars before delegating to the existing
80
+ * geospatial renderer. STORE avoids wasting CPU recompressing already
81
+ * compressed archive data.
82
+ */
83
+ export const createShapefileBundleArchive = async (entries, readEntry) => {
84
+ const JSZip = resolveJSZipWriter(await import('jszip'));
85
+ const zip = new JSZip();
86
+ for (const entry of entries) {
87
+ const { filename } = getArchivePathParts(entry.path);
88
+ zip.file(filename || entry.name, await readEntry(entry));
89
+ }
90
+ return zip.generateAsync({
91
+ type: 'arraybuffer',
92
+ compression: 'STORE',
93
+ });
94
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@file-viewer/renderer-archive",
3
- "version": "2.2.3",
3
+ "version": "2.2.4",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Standalone archive renderer plugin for File Viewer with libarchive worker, ZIP/TAR fallback, IndexedDB cache, and nested previews.",
@@ -56,7 +56,7 @@
56
56
  "LICENSE"
57
57
  ],
58
58
  "dependencies": {
59
- "@file-viewer/core": "2.2.3",
59
+ "@file-viewer/core": "2.2.4",
60
60
  "jszip": "^3.10.1",
61
61
  "libarchive.js": "^2.0.2"
62
62
  },
@@ -67,6 +67,7 @@
67
67
  "scripts": {
68
68
  "build": "tsc -b tsconfig.json",
69
69
  "type-check": "tsc -b tsconfig.json",
70
- "verify:github-101": "pnpm build && node scripts/verify-github-101.mjs"
70
+ "verify:github-101": "pnpm build && node scripts/verify-github-101.mjs",
71
+ "verify:github-148": "pnpm build && node scripts/verify-github-148.mjs"
71
72
  }
72
73
  }