@file-viewer/renderer-archive 2.1.1 → 2.1.3
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 +45 -40
- package/dist/archiveShared.d.ts +2 -0
- package/dist/archiveShared.js +14 -0
- package/package.json +2 -2
package/dist/archive.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { resolveFileViewerArchiveWasmUrl, resolveFileViewerArchiveWorkerUrl, } from '@file-viewer/core/assets';
|
|
2
|
-
import { disposeFileViewerRendered, } from '@file-viewer/core';
|
|
3
|
-
import { createArchiveCacheKey, flattenArchiveObject, formatArchiveBytes, getArchiveEntryExtension, } from './archiveShared.js';
|
|
2
|
+
import { createFileViewerTranslator, disposeFileViewerRendered, } from '@file-viewer/core';
|
|
3
|
+
import { buildArchiveNestedRenderContext, createArchiveCacheKey, flattenArchiveObject, formatArchiveBytes, getArchiveEntryExtension, } from './archiveShared.js';
|
|
4
4
|
import { readArchiveCache, writeArchiveCache } from './archiveCache.js';
|
|
5
5
|
import { loadArchiveEntriesWithoutWorker } from './archiveFallback.js';
|
|
6
6
|
const DEFAULT_MAX_ARCHIVE_SIZE = 320 * 1024 * 1024;
|
|
@@ -98,7 +98,7 @@ const getWorkerConstructor = (documentRef) => {
|
|
|
98
98
|
const WorkerCtor = ((_a = documentRef.defaultView) === null || _a === void 0 ? void 0 : _a.Worker) ||
|
|
99
99
|
(typeof Worker !== 'undefined' ? Worker : undefined);
|
|
100
100
|
if (!WorkerCtor) {
|
|
101
|
-
throw new Error('
|
|
101
|
+
throw new Error('Web Worker is not supported by this browser.');
|
|
102
102
|
}
|
|
103
103
|
return WorkerCtor;
|
|
104
104
|
};
|
|
@@ -157,7 +157,7 @@ const prepareWorkerUrl = async (candidate, objectUrls) => {
|
|
|
157
157
|
}
|
|
158
158
|
const response = await fetch(candidate.workerUrl, { cache: 'no-cache' });
|
|
159
159
|
if (!response.ok) {
|
|
160
|
-
throw new Error(
|
|
160
|
+
throw new Error(`Unable to read libarchive Worker: ${response.status}`);
|
|
161
161
|
}
|
|
162
162
|
const source = await response.text();
|
|
163
163
|
const workerUrl = URL.createObjectURL(new Blob([
|
|
@@ -174,7 +174,7 @@ const resolveWorkerCandidates = async (documentRef, options) => {
|
|
|
174
174
|
: undefined;
|
|
175
175
|
if (options === null || options === void 0 ? void 0 : options.workerUrl) {
|
|
176
176
|
candidates.push({
|
|
177
|
-
label: '
|
|
177
|
+
label: 'custom libarchive Worker',
|
|
178
178
|
workerUrl: resolveFileViewerArchiveWorkerUrl(options, baseUrl),
|
|
179
179
|
wasmUrl,
|
|
180
180
|
});
|
|
@@ -183,22 +183,19 @@ const resolveWorkerCandidates = async (documentRef, options) => {
|
|
|
183
183
|
const publicWorkerUrl = resolveFileViewerArchiveWorkerUrl(undefined, baseUrl);
|
|
184
184
|
if (await probeWorkerUrl(publicWorkerUrl)) {
|
|
185
185
|
candidates.push({
|
|
186
|
-
label: '
|
|
186
|
+
label: 'static libarchive Worker',
|
|
187
187
|
workerUrl: publicWorkerUrl,
|
|
188
188
|
wasmUrl,
|
|
189
189
|
});
|
|
190
190
|
}
|
|
191
191
|
return candidates;
|
|
192
192
|
};
|
|
193
|
-
const buildNestedOptions = (context, archiveOptions) => ({
|
|
194
|
-
...((context === null || context === void 0 ? void 0 : context.options) || {}),
|
|
195
|
-
archive: archiveOptions,
|
|
196
|
-
});
|
|
197
193
|
const renderNestedWithCoreFallback = async (buffer, type, target, context) => {
|
|
194
|
+
const t = createFileViewerTranslator(context === null || context === void 0 ? void 0 : context.options);
|
|
198
195
|
const { fileViewerCoreRendererDispatcher } = await import('@file-viewer/core');
|
|
199
196
|
const handler = fileViewerCoreRendererDispatcher.resolve(type);
|
|
200
197
|
if (!handler) {
|
|
201
|
-
target.textContent =
|
|
198
|
+
target.textContent = t('archive.error.nestedUnsupported', { type });
|
|
202
199
|
return undefined;
|
|
203
200
|
}
|
|
204
201
|
return handler(buffer, target, type, {
|
|
@@ -223,8 +220,9 @@ export default async function renderArchive(buffer, target, _type, context) {
|
|
|
223
220
|
let selectedEntry = null;
|
|
224
221
|
let nestedRendered;
|
|
225
222
|
let loading = false;
|
|
226
|
-
|
|
227
|
-
let
|
|
223
|
+
const t = createFileViewerTranslator(context === null || context === void 0 ? void 0 : context.options);
|
|
224
|
+
let loadingText = t('archive.loading.readingDirectory');
|
|
225
|
+
let loadingHint = t('archive.loading.readingDirectoryHint');
|
|
228
226
|
let errorText = '';
|
|
229
227
|
let archiveNotice = '';
|
|
230
228
|
let encrypted = null;
|
|
@@ -241,15 +239,15 @@ export default async function renderArchive(buffer, target, _type, context) {
|
|
|
241
239
|
const info = createElement(documentRef, 'div', 'archive-info');
|
|
242
240
|
const search = createElement(documentRef, 'input', 'archive-search');
|
|
243
241
|
search.type = 'search';
|
|
244
|
-
search.placeholder = '
|
|
242
|
+
search.placeholder = t('archive.search.placeholder');
|
|
245
243
|
const list = createElement(documentRef, 'div', 'archive-list');
|
|
246
244
|
list.setAttribute('role', 'list');
|
|
247
245
|
sidebar.append(head, warning, info, search, list);
|
|
248
246
|
const preview = createElement(documentRef, 'main', 'archive-preview');
|
|
249
247
|
const toolbar = createElement(documentRef, 'div', 'archive-preview-toolbar');
|
|
250
248
|
const toolbarTitle = createElement(documentRef, 'div');
|
|
251
|
-
toolbarTitle.append(createElement(documentRef, 'span', undefined, '
|
|
252
|
-
const downloadButton = createElement(documentRef, 'button', undefined, '
|
|
249
|
+
toolbarTitle.append(createElement(documentRef, 'span', undefined, t('archive.preview.title')), createElement(documentRef, 'strong', undefined, t('archive.preview.chooseFile')));
|
|
250
|
+
const downloadButton = createElement(documentRef, 'button', undefined, t('archive.preview.downloadFile'));
|
|
253
251
|
downloadButton.type = 'button';
|
|
254
252
|
toolbar.append(toolbarTitle, downloadButton);
|
|
255
253
|
const nestedTarget = createElement(documentRef, 'div', 'archive-nested-target');
|
|
@@ -266,7 +264,7 @@ export default async function renderArchive(buffer, target, _type, context) {
|
|
|
266
264
|
state.append(stateContent);
|
|
267
265
|
root.append(state);
|
|
268
266
|
const error = createElement(documentRef, 'div', 'archive-error');
|
|
269
|
-
const errorTitle = createElement(documentRef, 'strong', undefined, '
|
|
267
|
+
const errorTitle = createElement(documentRef, 'strong', undefined, t('archive.error.title'));
|
|
270
268
|
const errorMessage = createElement(documentRef, 'p');
|
|
271
269
|
error.append(errorTitle, errorMessage);
|
|
272
270
|
root.append(error);
|
|
@@ -303,8 +301,12 @@ export default async function renderArchive(buffer, target, _type, context) {
|
|
|
303
301
|
};
|
|
304
302
|
const syncState = () => {
|
|
305
303
|
const archiveStats = getArchiveStats();
|
|
306
|
-
stats.textContent =
|
|
307
|
-
|
|
304
|
+
stats.textContent = t('archive.stats.summary', {
|
|
305
|
+
count: archiveStats.count,
|
|
306
|
+
size: formatArchiveBytes(archiveStats.totalSize),
|
|
307
|
+
previewable: archiveStats.previewableCount,
|
|
308
|
+
});
|
|
309
|
+
warning.textContent = t('archive.warning.encrypted');
|
|
308
310
|
warning.classList.toggle('archive-hidden', !encrypted);
|
|
309
311
|
info.textContent = archiveNotice;
|
|
310
312
|
info.classList.toggle('archive-hidden', !archiveNotice);
|
|
@@ -316,7 +318,7 @@ export default async function renderArchive(buffer, target, _type, context) {
|
|
|
316
318
|
downloadButton.hidden = !selectedEntry;
|
|
317
319
|
const activeTitle = toolbarTitle.querySelector('strong');
|
|
318
320
|
if (activeTitle) {
|
|
319
|
-
activeTitle.textContent = (selectedEntry === null || selectedEntry === void 0 ? void 0 : selectedEntry.name) || '
|
|
321
|
+
activeTitle.textContent = (selectedEntry === null || selectedEntry === void 0 ? void 0 : selectedEntry.name) || t('archive.preview.chooseFile');
|
|
320
322
|
}
|
|
321
323
|
};
|
|
322
324
|
const renderEmptyState = () => {
|
|
@@ -324,7 +326,7 @@ export default async function renderArchive(buffer, target, _type, context) {
|
|
|
324
326
|
return;
|
|
325
327
|
}
|
|
326
328
|
const empty = createElement(documentRef, 'div', 'archive-empty');
|
|
327
|
-
empty.append(createElement(documentRef, 'strong', undefined, '
|
|
329
|
+
empty.append(createElement(documentRef, 'strong', undefined, t('archive.empty.title')), createElement(documentRef, 'p', undefined, t('archive.empty.message')));
|
|
328
330
|
nestedTarget.replaceChildren(empty);
|
|
329
331
|
};
|
|
330
332
|
const renderEntryList = () => {
|
|
@@ -375,13 +377,13 @@ export default async function renderArchive(buffer, target, _type, context) {
|
|
|
375
377
|
return worker;
|
|
376
378
|
},
|
|
377
379
|
});
|
|
378
|
-
setLoading(true,
|
|
380
|
+
setLoading(true, t('archive.loading.initializingCandidate', { label: candidate.label }), t('archive.loading.initializingCandidateHint'));
|
|
379
381
|
const archiveFile = createArchiveFile(documentRef, buffer, filename);
|
|
380
|
-
const archive = await withTimeout(Archive.open(archiveFile), workerTimeoutMs,
|
|
382
|
+
const archive = await withTimeout(Archive.open(archiveFile), workerTimeoutMs, t('archive.error.candidateInitTimeout', { label: candidate.label }), targetWindow);
|
|
381
383
|
archiveReader = archive;
|
|
382
|
-
encrypted = await withTimeout(archive.hasEncryptedData(), workerTimeoutMs,
|
|
383
|
-
setLoading(true, '
|
|
384
|
-
const fileTree = await withTimeout(archive.getFilesObject(), workerTimeoutMs,
|
|
384
|
+
encrypted = await withTimeout(archive.hasEncryptedData(), workerTimeoutMs, t('archive.error.encryptedCheckTimeout', { label: candidate.label }), targetWindow).catch(() => null);
|
|
385
|
+
setLoading(true, t('archive.loading.readingDirectory'), t('archive.loading.directoryReadyHint'));
|
|
386
|
+
const fileTree = await withTimeout(archive.getFilesObject(), workerTimeoutMs, t('archive.error.candidateReadTimeout', { label: candidate.label }), targetWindow);
|
|
385
387
|
entries = flattenArchiveObject(fileTree)
|
|
386
388
|
.sort((left, right) => left.path.localeCompare(right.path));
|
|
387
389
|
syncState();
|
|
@@ -397,14 +399,14 @@ export default async function renderArchive(buffer, target, _type, context) {
|
|
|
397
399
|
}
|
|
398
400
|
};
|
|
399
401
|
const tryOpenArchiveWithFallback = async () => {
|
|
400
|
-
setLoading(true, '
|
|
402
|
+
setLoading(true, t('archive.loading.workerFallback'), t('archive.loading.workerFallbackHint'));
|
|
401
403
|
const fallbackEntries = await loadArchiveEntriesWithoutWorker(buffer, filename);
|
|
402
404
|
if (!fallbackEntries) {
|
|
403
405
|
return false;
|
|
404
406
|
}
|
|
405
407
|
entries = fallbackEntries.sort((left, right) => left.path.localeCompare(right.path));
|
|
406
408
|
encrypted = null;
|
|
407
|
-
archiveNotice = '
|
|
409
|
+
archiveNotice = t('archive.notice.workerFallback');
|
|
408
410
|
syncState();
|
|
409
411
|
renderEntryList();
|
|
410
412
|
renderEmptyState();
|
|
@@ -412,10 +414,13 @@ export default async function renderArchive(buffer, target, _type, context) {
|
|
|
412
414
|
};
|
|
413
415
|
const openArchive = async () => {
|
|
414
416
|
if (buffer.byteLength > maxArchiveSize) {
|
|
415
|
-
setError(
|
|
417
|
+
setError(t('archive.error.tooLarge', {
|
|
418
|
+
size: formatArchiveBytes(buffer.byteLength),
|
|
419
|
+
limit: formatArchiveBytes(maxArchiveSize),
|
|
420
|
+
}));
|
|
416
421
|
return;
|
|
417
422
|
}
|
|
418
|
-
setLoading(true, '
|
|
423
|
+
setLoading(true, t('archive.loading.initializingWorker'), t('archive.loading.initializingWorkerHint'));
|
|
419
424
|
setError('');
|
|
420
425
|
archiveNotice = '';
|
|
421
426
|
try {
|
|
@@ -438,7 +443,7 @@ export default async function renderArchive(buffer, target, _type, context) {
|
|
|
438
443
|
if (await tryOpenArchiveWithFallback()) {
|
|
439
444
|
return;
|
|
440
445
|
}
|
|
441
|
-
throw new Error(errors.join('
|
|
446
|
+
throw new Error(errors.join('; ') || t('archive.error.workerInitFailed'));
|
|
442
447
|
}
|
|
443
448
|
catch (nextError) {
|
|
444
449
|
console.error(nextError);
|
|
@@ -452,11 +457,7 @@ export default async function renderArchive(buffer, target, _type, context) {
|
|
|
452
457
|
await clearNestedPreview();
|
|
453
458
|
const child = createElement(documentRef, 'div', 'archive-nested-content');
|
|
454
459
|
nestedTarget.append(child);
|
|
455
|
-
const nestedContext =
|
|
456
|
-
...context,
|
|
457
|
-
filename: entry.name,
|
|
458
|
-
options: buildNestedOptions(context, archiveOptions),
|
|
459
|
-
};
|
|
460
|
+
const nestedContext = buildArchiveNestedRenderContext(context, entry, archiveOptions);
|
|
460
461
|
nestedRendered = (context === null || context === void 0 ? void 0 : context.renderNestedBuffer)
|
|
461
462
|
? await context.renderNestedBuffer(entryBuffer, entry.extension, child, nestedContext)
|
|
462
463
|
: await renderNestedWithCoreFallback(entryBuffer, entry.extension, child, nestedContext);
|
|
@@ -487,14 +488,18 @@ export default async function renderArchive(buffer, target, _type, context) {
|
|
|
487
488
|
renderEntryList();
|
|
488
489
|
syncState();
|
|
489
490
|
if (entry.size > maxEntryPreviewSize) {
|
|
490
|
-
setError(
|
|
491
|
+
setError(t('archive.error.entryTooLarge', {
|
|
492
|
+
name: entry.name,
|
|
493
|
+
size: formatArchiveBytes(entry.size),
|
|
494
|
+
limit: formatArchiveBytes(maxEntryPreviewSize),
|
|
495
|
+
}));
|
|
491
496
|
return;
|
|
492
497
|
}
|
|
493
|
-
setLoading(true,
|
|
498
|
+
setLoading(true, t('archive.loading.extracting', { name: entry.name }));
|
|
494
499
|
setError('');
|
|
495
500
|
try {
|
|
496
501
|
const entryBuffer = await extractEntryBuffer(entry);
|
|
497
|
-
setLoading(true,
|
|
502
|
+
setLoading(true, t('archive.loading.rendering', { name: entry.name }));
|
|
498
503
|
await renderEntryBuffer(entry, entryBuffer);
|
|
499
504
|
}
|
|
500
505
|
catch (nextError) {
|
|
@@ -506,7 +511,7 @@ export default async function renderArchive(buffer, target, _type, context) {
|
|
|
506
511
|
}
|
|
507
512
|
}
|
|
508
513
|
const downloadEntry = async (entry) => {
|
|
509
|
-
setLoading(true,
|
|
514
|
+
setLoading(true, t('archive.loading.exporting', { name: entry.name }));
|
|
510
515
|
try {
|
|
511
516
|
const entryBuffer = await extractEntryBuffer(entry);
|
|
512
517
|
const url = URL.createObjectURL(new Blob([entryBuffer]));
|
package/dist/archiveShared.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { FileRenderContext, FileViewerArchiveOptions } from '@file-viewer/core';
|
|
1
2
|
export declare const ARCHIVE_PREVIEWABLE_EXTENSIONS: readonly string[];
|
|
2
3
|
export interface ArchiveEntryView {
|
|
3
4
|
id: string;
|
|
@@ -22,3 +23,4 @@ export declare const isPreviewableArchiveEntry: (name: string) => boolean;
|
|
|
22
23
|
export declare const formatArchiveBytes: (value: number) => string;
|
|
23
24
|
export declare const flattenArchiveObject: (input: Record<string, unknown>, prefix?: string) => ArchiveEntryView[];
|
|
24
25
|
export declare const createArchiveCacheKey: (archiveName: string, archiveSize: number, entry: ArchiveEntryView) => string;
|
|
26
|
+
export declare const buildArchiveNestedRenderContext: (context: FileRenderContext | undefined, entry: Pick<ArchiveEntryView, "name">, archiveOptions: FileViewerArchiveOptions | undefined) => FileRenderContext;
|
package/dist/archiveShared.js
CHANGED
|
@@ -85,3 +85,17 @@ export const createArchiveCacheKey = (archiveName, archiveSize, entry) => {
|
|
|
85
85
|
entry.lastModified || 0,
|
|
86
86
|
].join(':');
|
|
87
87
|
};
|
|
88
|
+
const buildNestedOptions = (context, archiveOptions) => ({
|
|
89
|
+
...((context === null || context === void 0 ? void 0 : context.options) || {}),
|
|
90
|
+
archive: archiveOptions,
|
|
91
|
+
});
|
|
92
|
+
export const buildArchiveNestedRenderContext = (context, entry, archiveOptions) => ({
|
|
93
|
+
...context,
|
|
94
|
+
filename: entry.name,
|
|
95
|
+
// Archive children are rendered from the extracted bytes. Never inherit
|
|
96
|
+
// the parent archive URL, otherwise streaming renderers such as PDF.js
|
|
97
|
+
// would try to parse the .zip/.rar source as the nested file.
|
|
98
|
+
url: undefined,
|
|
99
|
+
streamUrl: undefined,
|
|
100
|
+
options: buildNestedOptions(context, archiveOptions),
|
|
101
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@file-viewer/renderer-archive",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Standalone archive renderer plugin for Flyfish 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.1.
|
|
59
|
+
"@file-viewer/core": "^2.1.3",
|
|
60
60
|
"jszip": "^3.10.1",
|
|
61
61
|
"libarchive.js": "^2.0.2"
|
|
62
62
|
},
|