@file-viewer/core 2.0.10 → 2.1.0

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.
Files changed (114) hide show
  1. package/README.en.md +2 -2
  2. package/README.md +2 -2
  3. package/dist/config/options.d.ts +1 -1
  4. package/dist/config/options.js +1 -1
  5. package/dist/contracts/types.d.ts +71 -1
  6. package/dist/headless.d.ts +2 -2
  7. package/dist/headless.js +1 -1
  8. package/dist/index.d.ts +9 -6
  9. package/dist/index.js +105 -48
  10. package/dist/platform/assets.d.ts +3 -1
  11. package/dist/platform/assets.js +18 -2
  12. package/dist/registry/capabilities.d.ts +2 -2
  13. package/dist/registry/capabilities.js +2 -1
  14. package/dist/registry/formats.d.ts +20 -7
  15. package/dist/registry/formats.js +14 -5
  16. package/dist/registry/registry.d.ts +8 -1
  17. package/dist/registry/registry.js +29 -0
  18. package/dist/renderers/image.js +1 -10
  19. package/dist/renderers/index.d.ts +320 -2
  20. package/dist/renderers/index.js +27 -157
  21. package/dist/viewer/createViewer.js +86 -3
  22. package/package.json +17 -44
  23. package/dist/renderers/archive.d.ts +0 -2
  24. package/dist/renderers/archive.js +0 -547
  25. package/dist/renderers/archiveCache.d.ts +0 -10
  26. package/dist/renderers/archiveCache.js +0 -96
  27. package/dist/renderers/archiveFallback.d.ts +0 -7
  28. package/dist/renderers/archiveFallback.js +0 -166
  29. package/dist/renderers/archiveShared.d.ts +0 -23
  30. package/dist/renderers/archiveShared.js +0 -71
  31. package/dist/renderers/audio.d.ts +0 -8
  32. package/dist/renderers/audio.js +0 -219
  33. package/dist/renderers/cad.d.ts +0 -2
  34. package/dist/renderers/cad.js +0 -446
  35. package/dist/renderers/code.d.ts +0 -11
  36. package/dist/renderers/code.js +0 -233
  37. package/dist/renderers/data.d.ts +0 -7
  38. package/dist/renderers/data.js +0 -370
  39. package/dist/renderers/drawing.d.ts +0 -10
  40. package/dist/renderers/drawing.js +0 -882
  41. package/dist/renderers/eda.d.ts +0 -2
  42. package/dist/renderers/eda.js +0 -434
  43. package/dist/renderers/edaParser.d.ts +0 -77
  44. package/dist/renderers/edaParser.js +0 -569
  45. package/dist/renderers/email.d.ts +0 -2
  46. package/dist/renderers/email.js +0 -463
  47. package/dist/renderers/epub.d.ts +0 -2
  48. package/dist/renderers/epub.js +0 -331
  49. package/dist/renderers/geo.d.ts +0 -2
  50. package/dist/renderers/geo.js +0 -284
  51. package/dist/renderers/markdown.d.ts +0 -2
  52. package/dist/renderers/markdown.js +0 -83
  53. package/dist/renderers/model.d.ts +0 -2
  54. package/dist/renderers/model.js +0 -567
  55. package/dist/renderers/ofd.d.ts +0 -2
  56. package/dist/renderers/ofd.js +0 -256
  57. package/dist/renderers/openDocument.d.ts +0 -2
  58. package/dist/renderers/openDocument.js +0 -122
  59. package/dist/renderers/pdf.d.ts +0 -3
  60. package/dist/renderers/pdf.js +0 -1001
  61. package/dist/renderers/pdfStyles.d.ts +0 -1
  62. package/dist/renderers/pdfStyles.js +0 -1
  63. package/dist/renderers/pptx.d.ts +0 -2
  64. package/dist/renderers/pptx.js +0 -217
  65. package/dist/renderers/spreadsheet/state.d.ts +0 -80
  66. package/dist/renderers/spreadsheet/state.js +0 -96
  67. package/dist/renderers/spreadsheet/view.d.ts +0 -25
  68. package/dist/renderers/spreadsheet/view.js +0 -833
  69. package/dist/renderers/spreadsheet/worker/index.d.ts +0 -2
  70. package/dist/renderers/spreadsheet/worker/index.js +0 -1
  71. package/dist/renderers/spreadsheet/worker/sheetjs/SheetJsModel.d.ts +0 -73
  72. package/dist/renderers/spreadsheet/worker/sheetjs/SheetJsModel.js +0 -623
  73. package/dist/renderers/spreadsheet/worker/sheetjs/color.d.ts +0 -2
  74. package/dist/renderers/spreadsheet/worker/sheetjs/color.js +0 -73
  75. package/dist/renderers/spreadsheet/worker/sheetjs/index.d.ts +0 -1
  76. package/dist/renderers/spreadsheet/worker/sheetjs/index.js +0 -1
  77. package/dist/renderers/spreadsheet/worker/sheetjs/parser.d.ts +0 -18
  78. package/dist/renderers/spreadsheet/worker/sheetjs/parser.js +0 -106
  79. package/dist/renderers/spreadsheet/worker/sheetjs/sheet.worker.d.ts +0 -1
  80. package/dist/renderers/spreadsheet/worker/sheetjs/sheet.worker.js +0 -11
  81. package/dist/renderers/spreadsheet/worker/type.d.ts +0 -57
  82. package/dist/renderers/spreadsheet/worker/type.js +0 -1
  83. package/dist/renderers/spreadsheet.d.ts +0 -3
  84. package/dist/renderers/spreadsheet.js +0 -929
  85. package/dist/renderers/typst.d.ts +0 -8
  86. package/dist/renderers/typst.js +0 -547
  87. package/dist/renderers/umd/parser.d.ts +0 -30
  88. package/dist/renderers/umd/parser.js +0 -408
  89. package/dist/renderers/umd.d.ts +0 -2
  90. package/dist/renderers/umd.js +0 -297
  91. package/dist/renderers/video.d.ts +0 -8
  92. package/dist/renderers/video.js +0 -108
  93. package/dist/renderers/wordDoc.d.ts +0 -5
  94. package/dist/renderers/wordDoc.js +0 -284
  95. package/dist/renderers/wordDocx.d.ts +0 -5
  96. package/dist/renderers/wordDocx.js +0 -985
  97. package/dist/renderers/wordDocx.worker.d.ts +0 -1
  98. package/dist/renderers/wordDocx.worker.js +0 -96
  99. package/vendor/ofd/dltech/jbig2/arithmetic_decoder.js +0 -183
  100. package/vendor/ofd/dltech/jbig2/ccitt.js +0 -1070
  101. package/vendor/ofd/dltech/jbig2/compatibility.js +0 -12
  102. package/vendor/ofd/dltech/jbig2/core_utils.js +0 -180
  103. package/vendor/ofd/dltech/jbig2/is_node.js +0 -27
  104. package/vendor/ofd/dltech/jbig2/jbig2.js +0 -2589
  105. package/vendor/ofd/dltech/jbig2/jbig2_stream.js +0 -81
  106. package/vendor/ofd/dltech/jbig2/primitives.js +0 -387
  107. package/vendor/ofd/dltech/jbig2/stream.js +0 -1348
  108. package/vendor/ofd/dltech/jbig2/util.js +0 -972
  109. package/vendor/ofd/dltech/ofd/ofd.d.ts +0 -11
  110. package/vendor/ofd/dltech/ofd/ofd.js +0 -100
  111. package/vendor/ofd/dltech/ofd/ofd_parser.js +0 -395
  112. package/vendor/ofd/dltech/ofd/ofd_render.js +0 -473
  113. package/vendor/ofd/dltech/ofd/ofd_util.js +0 -350
  114. package/vendor/ofd/dltech/ofd/pipeline.js +0 -26
@@ -1,166 +0,0 @@
1
- import { getArchiveEntryExtension, isPreviewableArchiveEntry, } from './archiveShared.js';
2
- const ZIP_LIKE_EXTENSIONS = new Set(['zip', 'zipx', 'jar', 'war', 'ear', 'apk', 'cbz']);
3
- const TAR_LIKE_EXTENSIONS = new Set(['tar', 'tgz', 'gz', 'gzip']);
4
- const TAR_BLOCK_SIZE = 512;
5
- const toArrayBuffer = (bytes) => {
6
- const output = new Uint8Array(bytes.byteLength);
7
- output.set(bytes);
8
- return output.buffer;
9
- };
10
- const decompressBytes = async (bytes, format) => {
11
- if (typeof DecompressionStream === 'undefined') {
12
- return null;
13
- }
14
- const stream = new Blob([toArrayBuffer(bytes)])
15
- .stream()
16
- .pipeThrough(new DecompressionStream(format));
17
- return new Uint8Array(await new Response(stream).arrayBuffer());
18
- };
19
- const normalizeArchivePath = (path) => {
20
- return path.replace(/^\/+/, '').replace(/\\/g, '/');
21
- };
22
- const getPathName = (path) => {
23
- const parts = normalizeArchivePath(path).split('/');
24
- return parts[parts.length - 1] || path;
25
- };
26
- const getPathDepth = (path) => {
27
- return Math.max(0, normalizeArchivePath(path).split('/').length - 1);
28
- };
29
- const createEntryView = (source) => {
30
- const path = normalizeArchivePath(source.path);
31
- const name = getPathName(path);
32
- return {
33
- id: path,
34
- path,
35
- name,
36
- extension: getArchiveEntryExtension(name),
37
- size: source.size,
38
- lastModified: source.lastModified,
39
- depth: getPathDepth(path),
40
- previewable: isPreviewableArchiveEntry(name),
41
- compressedFile: {
42
- name,
43
- size: source.size,
44
- lastModified: source.lastModified,
45
- async extract() {
46
- const buffer = await source.load();
47
- return new File([buffer], name, {
48
- type: 'application/octet-stream',
49
- lastModified: source.lastModified || Date.now(),
50
- });
51
- },
52
- },
53
- };
54
- };
55
- const parseOctal = (bytes, start, length) => {
56
- const text = new TextDecoder('ascii')
57
- .decode(bytes.slice(start, start + length))
58
- .replace(/\0.*$/, '')
59
- .trim();
60
- return text ? Number.parseInt(text, 8) || 0 : 0;
61
- };
62
- const readTarName = (bytes, offset) => {
63
- const decoder = new TextDecoder('utf-8');
64
- const name = decoder.decode(bytes.slice(offset, offset + 100)).replace(/\0.*$/, '');
65
- const prefix = decoder.decode(bytes.slice(offset + 345, offset + 500)).replace(/\0.*$/, '');
66
- return normalizeArchivePath(prefix ? `${prefix}/${name}` : name);
67
- };
68
- const parseTarEntries = (bytes) => {
69
- const entries = [];
70
- let offset = 0;
71
- while (offset + TAR_BLOCK_SIZE <= bytes.length) {
72
- const header = bytes.slice(offset, offset + TAR_BLOCK_SIZE);
73
- if (header.every(value => value === 0)) {
74
- break;
75
- }
76
- const path = readTarName(bytes, offset);
77
- const size = parseOctal(bytes, offset + 124, 12);
78
- const typeFlag = String.fromCharCode(bytes[offset + 156] || 0);
79
- const dataOffset = offset + TAR_BLOCK_SIZE;
80
- const nextOffset = dataOffset + Math.ceil(size / TAR_BLOCK_SIZE) * TAR_BLOCK_SIZE;
81
- if (path && typeFlag !== '5') {
82
- const fileBytes = bytes.slice(dataOffset, dataOffset + size);
83
- entries.push(createEntryView({
84
- path,
85
- size,
86
- load: async () => toArrayBuffer(fileBytes),
87
- }));
88
- }
89
- offset = nextOffset;
90
- }
91
- return entries;
92
- };
93
- const getArchiveExtension = (filename) => {
94
- const lower = filename.toLowerCase();
95
- if (lower.endsWith('.tar.gz') || lower.endsWith('.tgz')) {
96
- return 'tgz';
97
- }
98
- return getArchiveEntryExtension(filename);
99
- };
100
- const getGzipEntryName = (filename) => {
101
- const lower = filename.toLowerCase();
102
- if (lower.endsWith('.gzip')) {
103
- return filename.slice(0, -5) || 'archive';
104
- }
105
- if (lower.endsWith('.gz')) {
106
- return filename.slice(0, -3) || 'archive';
107
- }
108
- return `${filename || 'archive'}.bin`;
109
- };
110
- const loadZipEntries = async (data) => {
111
- const { default: JSZip } = await import('jszip');
112
- const zip = await JSZip.loadAsync(data);
113
- const entries = [];
114
- zip.forEach((relativePath, file) => {
115
- var _a, _b;
116
- if (file.dir) {
117
- return;
118
- }
119
- const metadata = file;
120
- const normalizedPath = normalizeArchivePath(relativePath);
121
- entries.push(createEntryView({
122
- path: normalizedPath,
123
- size: ((_a = metadata._data) === null || _a === void 0 ? void 0 : _a.uncompressedSize) || 0,
124
- lastModified: (_b = file.date) === null || _b === void 0 ? void 0 : _b.getTime(),
125
- load: async () => file.async('arraybuffer'),
126
- }));
127
- });
128
- return entries;
129
- };
130
- const loadTarEntries = async (data, filename, extension) => {
131
- const source = new Uint8Array(data);
132
- const bytes = extension === 'tar'
133
- ? source
134
- : await decompressBytes(source, 'gzip');
135
- if (!bytes) {
136
- return null;
137
- }
138
- if (extension === 'gz' || extension === 'gzip') {
139
- const lower = filename.toLowerCase();
140
- const isTarGz = lower.endsWith('.tar.gz') || lower.endsWith('.tgz');
141
- if (!isTarGz) {
142
- const name = getGzipEntryName(filename);
143
- return [createEntryView({
144
- path: name,
145
- size: bytes.byteLength,
146
- load: async () => toArrayBuffer(bytes),
147
- })];
148
- }
149
- }
150
- return parseTarEntries(bytes);
151
- };
152
- /**
153
- * Worker fallback for constrained browsers, temporary local servers, and
154
- * mobile WebViews. The main libarchive path still covers broader formats;
155
- * this covers common ZIP/TAR/GZIP archives without an extra static Worker.
156
- */
157
- export const loadArchiveEntriesWithoutWorker = async (data, filename) => {
158
- const extension = getArchiveExtension(filename);
159
- if (ZIP_LIKE_EXTENSIONS.has(extension)) {
160
- return loadZipEntries(data);
161
- }
162
- if (TAR_LIKE_EXTENSIONS.has(extension)) {
163
- return loadTarEntries(data, filename, extension);
164
- }
165
- return null;
166
- };
@@ -1,23 +0,0 @@
1
- export declare const ARCHIVE_PREVIEWABLE_EXTENSIONS: readonly string[];
2
- export interface ArchiveEntryView {
3
- id: string;
4
- path: string;
5
- name: string;
6
- extension: string;
7
- size: number;
8
- lastModified?: number;
9
- depth: number;
10
- previewable: boolean;
11
- compressedFile: {
12
- name: string;
13
- size: number;
14
- lastModified?: number;
15
- extract(): Promise<File>;
16
- };
17
- }
18
- export declare const getArchiveEntryExtension: (name: string) => string;
19
- export declare const isArchiveExtension: (extension: string) => boolean;
20
- export declare const isPreviewableArchiveEntry: (name: string) => boolean;
21
- export declare const formatArchiveBytes: (value: number) => string;
22
- export declare const flattenArchiveObject: (input: Record<string, unknown>, prefix?: string) => ArchiveEntryView[];
23
- export declare const createArchiveCacheKey: (archiveName: string, archiveSize: number, entry: ArchiveEntryView) => string;
@@ -1,71 +0,0 @@
1
- import { ARCHIVE_EXTENSIONS, DEFAULT_SUPPORTED_EXTENSIONS, } from '../registry/formats.js';
2
- export const ARCHIVE_PREVIEWABLE_EXTENSIONS = DEFAULT_SUPPORTED_EXTENSIONS;
3
- export const getArchiveEntryExtension = (name) => {
4
- const clean = name.split(/[?#]/)[0] || name;
5
- const dot = clean.lastIndexOf('.');
6
- return dot === -1 ? '' : clean.slice(dot + 1).toLowerCase();
7
- };
8
- export const isArchiveExtension = (extension) => (ARCHIVE_EXTENSIONS.includes(extension.toLowerCase()));
9
- export const isPreviewableArchiveEntry = (name) => {
10
- const extension = getArchiveEntryExtension(name);
11
- return ARCHIVE_PREVIEWABLE_EXTENSIONS.includes(extension);
12
- };
13
- export const formatArchiveBytes = (value) => {
14
- if (!Number.isFinite(value) || value < 0) {
15
- return '-';
16
- }
17
- if (value < 1024) {
18
- return `${value} B`;
19
- }
20
- const units = ['KB', 'MB', 'GB'];
21
- let next = value / 1024;
22
- for (const unit of units) {
23
- if (next < 1024 || unit === units[units.length - 1]) {
24
- return `${next.toFixed(next < 10 ? 1 : 0)} ${unit}`;
25
- }
26
- next /= 1024;
27
- }
28
- return `${value} B`;
29
- };
30
- const isCompressedFile = (value) => {
31
- return typeof value === 'object' &&
32
- value !== null &&
33
- 'extract' in value &&
34
- typeof value.extract === 'function';
35
- };
36
- export const flattenArchiveObject = (input, prefix = '') => {
37
- const entries = [];
38
- Object.entries(input).forEach(([key, value]) => {
39
- const path = prefix ? `${prefix}/${key}` : key;
40
- if (isCompressedFile(value)) {
41
- const name = value.name || key;
42
- const extension = getArchiveEntryExtension(name);
43
- entries.push({
44
- id: path,
45
- path,
46
- name,
47
- extension,
48
- size: value.size || 0,
49
- lastModified: value.lastModified,
50
- depth: path.split('/').length - 1,
51
- previewable: isPreviewableArchiveEntry(name),
52
- compressedFile: value,
53
- });
54
- return;
55
- }
56
- if (value && typeof value === 'object') {
57
- entries.push(...flattenArchiveObject(value, path));
58
- }
59
- });
60
- return entries;
61
- };
62
- export const createArchiveCacheKey = (archiveName, archiveSize, entry) => {
63
- return [
64
- 'archive-entry',
65
- archiveName || 'archive',
66
- archiveSize,
67
- entry.path,
68
- entry.size,
69
- entry.lastModified || 0,
70
- ].join(':');
71
- };
@@ -1,8 +0,0 @@
1
- import type { FileViewerRenderedInstance } from '../contracts/types';
2
- /**
3
- * Pure TypeScript audio renderer.
4
- *
5
- * Regular audio files use the browser's native `<audio>` element. MIDI stays in
6
- * the same async chunk and lazily imports `@tonejs/midi` only for `.mid/.midi`.
7
- */
8
- export default function renderAudio(buffer: ArrayBuffer, target: HTMLDivElement, type?: string): Promise<FileViewerRenderedInstance>;
@@ -1,219 +0,0 @@
1
- const AUDIO_MIME_MAP = {
2
- aac: 'audio/aac',
3
- flac: 'audio/flac',
4
- m4a: 'audio/mp4',
5
- mp3: 'audio/mpeg',
6
- mpeg: 'audio/mpeg',
7
- oga: 'audio/ogg',
8
- ogg: 'audio/ogg',
9
- opus: 'audio/ogg; codecs=opus',
10
- wav: 'audio/wav',
11
- weba: 'audio/webm'
12
- };
13
- const audioStyle = `
14
- .fv-audio-viewer{width:100%;min-height:100%;display:flex;align-items:center;justify-content:center;padding:28px;background:linear-gradient(135deg,rgba(14,116,144,.1),transparent 34%),linear-gradient(180deg,#f5f8fb 0%,#edf2f7 100%);box-sizing:border-box}
15
- .fv-audio-card{width:min(100%,640px);display:grid;grid-template-columns:86px minmax(0,1fr);gap:18px;align-items:center;padding:24px;border-radius:8px;border:1px solid rgba(15,23,42,.08);background:rgba(255,255,255,.92);box-shadow:0 20px 52px rgba(15,23,42,.13);box-sizing:border-box}
16
- .fv-audio-art{position:relative;width:86px;height:86px;border-radius:8px;background:linear-gradient(135deg,#0f766e,#2dd4bf);box-shadow:inset 0 0 0 1px rgba(255,255,255,.24)}
17
- .fv-audio-art span{position:absolute;inset:18px;border-radius:999px;border:8px solid rgba(255,255,255,.88)}
18
- .fv-audio-art i{position:absolute;right:18px;bottom:20px;width:18px;height:36px;border-radius:10px 10px 4px 4px;background:rgba(255,255,255,.9)}
19
- .fv-audio-copy{min-width:0}
20
- .fv-audio-kicker{color:#0f766e;font-size:12px;font-weight:800;letter-spacing:0}
21
- .fv-audio-copy strong{display:block;margin-top:5px;color:#132235;font-size:23px;line-height:1.15}
22
- .fv-audio-copy p{margin:8px 0 0;color:#64748b;font-size:13px;line-height:1.7}
23
- .fv-audio-meter{grid-column:1/-1;display:grid;grid-template-columns:48px minmax(0,1fr) 48px;align-items:center;gap:10px;color:#64748b;font-size:12px;font-variant-numeric:tabular-nums}
24
- .fv-audio-progress{height:6px;overflow:hidden;border-radius:999px;background:rgba(15,118,110,.12)}
25
- .fv-audio-progress i{display:block;height:100%;border-radius:inherit;background:linear-gradient(90deg,#0f766e,#2dd4bf);transition:width .18s ease}
26
- .fv-audio-control{grid-column:1/-1;width:100%;height:42px}
27
- .fv-midi-viewer{min-height:100%;padding:28px;background:#eef1f4;box-sizing:border-box}
28
- .fv-midi-card{max-width:960px;margin:0 auto;border-radius:8px;border:1px solid rgba(15,23,42,.08);background:#fff;box-shadow:0 18px 48px rgba(15,23,42,.12);overflow:hidden}
29
- .fv-midi-card header{padding:18px 22px;border-bottom:1px solid rgba(15,23,42,.08)}
30
- .fv-midi-card header span{display:block;color:#0f766e;font-size:12px;font-weight:800}
31
- .fv-midi-card header strong{display:block;margin-top:6px;color:#132235;font-size:22px}
32
- .fv-midi-state{padding:28px 22px;color:#64748b}
33
- .fv-midi-error{color:#b42318}
34
- .fv-midi-stats{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:1px;background:rgba(15,23,42,.08)}
35
- .fv-midi-stats div{padding:16px;background:#f8fafc}
36
- .fv-midi-stats span{display:block;color:#64748b;font-size:12px}
37
- .fv-midi-stats strong{display:block;margin-top:4px;color:#132235;font-size:20px}
38
- .fv-midi-table-wrap{overflow:auto}
39
- .fv-midi-table{width:100%;border-collapse:collapse;color:#132235;font-size:14px}
40
- .fv-midi-table th,.fv-midi-table td{padding:12px 16px;border-top:1px solid rgba(15,23,42,.08);text-align:left}
41
- .fv-midi-table th{color:#64748b;background:#f8fafc;font-weight:700}
42
- @media (max-width:700px){.fv-midi-stats{grid-template-columns:repeat(2,minmax(0,1fr))}}
43
- `;
44
- const createElement = (tagName, className, text) => {
45
- const element = document.createElement(tagName);
46
- if (className) {
47
- element.className = className;
48
- }
49
- if (typeof text === 'string') {
50
- element.textContent = text;
51
- }
52
- return element;
53
- };
54
- const formatDuration = (seconds) => {
55
- if (!Number.isFinite(seconds) || seconds <= 0) {
56
- return '00:00';
57
- }
58
- const minutes = Math.floor(seconds / 60);
59
- const rest = Math.round(seconds % 60);
60
- return `${String(minutes).padStart(2, '0')}:${String(rest).padStart(2, '0')}`;
61
- };
62
- const createStyle = () => {
63
- const style = document.createElement('style');
64
- style.textContent = audioStyle;
65
- return style;
66
- };
67
- const clearTarget = (target) => {
68
- target.replaceChildren();
69
- };
70
- const createRenderedInstance = (target, cleanup) => ({
71
- $el: target,
72
- unmount() {
73
- cleanup();
74
- clearTarget(target);
75
- }
76
- });
77
- const renderAudioElement = (buffer, target, type) => {
78
- const normalizedType = type.trim().toLowerCase() || 'mp3';
79
- const mimeType = AUDIO_MIME_MAP[normalizedType] || 'audio/*';
80
- const sourceUrl = URL.createObjectURL(new Blob([buffer], { type: mimeType }));
81
- const root = createElement('div', 'fv-audio-viewer');
82
- const card = createElement('section', 'fv-audio-card');
83
- const art = createElement('div', 'fv-audio-art');
84
- art.append(createElement('span'), createElement('i'));
85
- const copy = createElement('div', 'fv-audio-copy');
86
- copy.append(createElement('span', 'fv-audio-kicker', normalizedType.toUpperCase() || 'AUDIO'), createElement('strong', '', '音频预览'), createElement('p', '', '使用浏览器原生播放器打开,兼容性取决于当前浏览器支持的音频编码。'));
87
- const currentTimeText = createElement('span', '', '00:00');
88
- const durationText = createElement('span', '', '--:--');
89
- const progressFill = createElement('i');
90
- const progress = createElement('div', 'fv-audio-progress');
91
- progress.setAttribute('aria-hidden', 'true');
92
- progress.append(progressFill);
93
- const meter = createElement('div', 'fv-audio-meter');
94
- meter.append(currentTimeText, progress, durationText);
95
- const audio = createElement('audio', 'fv-audio-control');
96
- audio.src = sourceUrl;
97
- audio.controls = true;
98
- audio.preload = 'metadata';
99
- audio.textContent = '当前浏览器不支持音频播放。';
100
- const handleLoadedMetadata = () => {
101
- durationText.textContent = Number.isFinite(audio.duration) && audio.duration > 0
102
- ? formatDuration(audio.duration)
103
- : '--:--';
104
- };
105
- const handleTimeUpdate = () => {
106
- currentTimeText.textContent = formatDuration(audio.currentTime);
107
- const percent = Number.isFinite(audio.duration) && audio.duration > 0
108
- ? Math.min(100, Math.max(0, audio.currentTime / audio.duration * 100))
109
- : 0;
110
- progressFill.style.width = `${percent}%`;
111
- };
112
- audio.addEventListener('loadedmetadata', handleLoadedMetadata);
113
- audio.addEventListener('timeupdate', handleTimeUpdate);
114
- card.append(art, copy, meter, audio);
115
- root.append(card);
116
- target.replaceChildren(createStyle(), root);
117
- return createRenderedInstance(target, () => {
118
- audio.pause();
119
- audio.removeEventListener('loadedmetadata', handleLoadedMetadata);
120
- audio.removeEventListener('timeupdate', handleTimeUpdate);
121
- URL.revokeObjectURL(sourceUrl);
122
- });
123
- };
124
- const renderMidiElement = (buffer, target) => {
125
- let disposed = false;
126
- const root = createElement('div', 'fv-midi-viewer');
127
- const card = createElement('section', 'fv-midi-card');
128
- const header = document.createElement('header');
129
- const title = createElement('strong', '', 'MIDI 文件');
130
- header.append(createElement('span', '', 'MIDI'), title);
131
- const body = createElement('div', 'fv-midi-state', '正在解析 MIDI 轨道...');
132
- card.append(header, body);
133
- root.append(card);
134
- target.replaceChildren(createStyle(), root);
135
- const renderError = (message) => {
136
- body.className = 'fv-midi-state fv-midi-error';
137
- body.textContent = message;
138
- };
139
- const renderTrackTable = (tracks) => {
140
- const wrap = createElement('div', 'fv-midi-table-wrap');
141
- const table = createElement('table', 'fv-midi-table');
142
- const thead = document.createElement('thead');
143
- const headerRow = document.createElement('tr');
144
- for (const label of ['轨道', '乐器', '通道', '音符数', '时长']) {
145
- headerRow.append(createElement('th', '', label));
146
- }
147
- thead.append(headerRow);
148
- const tbody = document.createElement('tbody');
149
- for (const track of tracks) {
150
- const row = document.createElement('tr');
151
- row.append(createElement('td', '', track.name), createElement('td', '', track.instrument), createElement('td', '', String(track.channel + 1)), createElement('td', '', String(track.notes)), createElement('td', '', formatDuration(track.duration)));
152
- tbody.append(row);
153
- }
154
- table.append(thead, tbody);
155
- wrap.append(table);
156
- return wrap;
157
- };
158
- const renderSummary = (input) => {
159
- title.textContent = input.name;
160
- const totalNotes = input.tracks.reduce((sum, track) => sum + track.notes, 0);
161
- const stats = createElement('div', 'fv-midi-stats');
162
- for (const [label, value] of [
163
- ['时长', formatDuration(input.duration)],
164
- ['PPQ', String(input.ppq)],
165
- ['轨道', String(input.tracks.length)],
166
- ['音符', String(totalNotes)]
167
- ]) {
168
- const stat = document.createElement('div');
169
- stat.append(createElement('span', '', label), createElement('strong', '', value));
170
- stats.append(stat);
171
- }
172
- body.replaceWith(stats, renderTrackTable(input.tracks));
173
- };
174
- void (async () => {
175
- try {
176
- const { Midi } = await import('@tonejs/midi');
177
- const midi = new Midi(buffer);
178
- if (disposed) {
179
- return;
180
- }
181
- renderSummary({
182
- name: midi.name || 'MIDI 文件',
183
- duration: midi.duration,
184
- ppq: midi.header.ppq,
185
- tracks: midi.tracks.map((track, index) => {
186
- var _a, _b;
187
- return ({
188
- name: track.name || `Track ${index + 1}`,
189
- instrument: ((_a = track.instrument) === null || _a === void 0 ? void 0 : _a.name) || ((_b = track.instrument) === null || _b === void 0 ? void 0 : _b.family) || 'Unknown',
190
- channel: track.channel,
191
- notes: track.notes.length,
192
- duration: track.duration
193
- });
194
- })
195
- });
196
- }
197
- catch (error) {
198
- if (!disposed) {
199
- renderError(error instanceof Error ? error.message : 'MIDI 解析失败');
200
- }
201
- }
202
- })();
203
- return createRenderedInstance(target, () => {
204
- disposed = true;
205
- });
206
- };
207
- /**
208
- * Pure TypeScript audio renderer.
209
- *
210
- * Regular audio files use the browser's native `<audio>` element. MIDI stays in
211
- * the same async chunk and lazily imports `@tonejs/midi` only for `.mid/.midi`.
212
- */
213
- export default async function renderAudio(buffer, target, type) {
214
- const normalizedType = (type || 'mp3').toLowerCase();
215
- if (normalizedType === 'midi' || normalizedType === 'mid') {
216
- return renderMidiElement(buffer, target);
217
- }
218
- return renderAudioElement(buffer, target, normalizedType);
219
- }
@@ -1,2 +0,0 @@
1
- import type { FileRenderContext, FileViewerRenderedInstance } from '../contracts/types';
2
- export default function renderCad(buffer: ArrayBuffer, target: HTMLDivElement, type?: string, context?: FileRenderContext): Promise<FileViewerRenderedInstance>;