@file-viewer/core 2.0.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.
- package/LICENSE +160 -0
- package/README.en.md +78 -0
- package/README.md +83 -0
- package/dist/assets.d.ts +62 -0
- package/dist/assets.js +260 -0
- package/dist/browser.d.ts +1 -0
- package/dist/browser.js +1 -0
- package/dist/capabilities.d.ts +24 -0
- package/dist/capabilities.js +95 -0
- package/dist/document.d.ts +9 -0
- package/dist/document.js +86 -0
- package/dist/documentDom/anchors.d.ts +7 -0
- package/dist/documentDom/anchors.js +196 -0
- package/dist/documentDom/index.d.ts +5 -0
- package/dist/documentDom/index.js +3 -0
- package/dist/documentDom/providers.d.ts +13 -0
- package/dist/documentDom/providers.js +52 -0
- package/dist/documentDom/scroll.d.ts +12 -0
- package/dist/documentDom/scroll.js +42 -0
- package/dist/documentDom.d.ts +5 -0
- package/dist/documentDom.js +3 -0
- package/dist/documentEvents.d.ts +73 -0
- package/dist/documentEvents.js +150 -0
- package/dist/documentSearch.d.ts +50 -0
- package/dist/documentSearch.js +459 -0
- package/dist/documentZoom.d.ts +47 -0
- package/dist/documentZoom.js +184 -0
- package/dist/export.d.ts +26 -0
- package/dist/export.js +466 -0
- package/dist/formats.d.ts +305 -0
- package/dist/formats.js +207 -0
- package/dist/headless.d.ts +25 -0
- package/dist/headless.js +14 -0
- package/dist/index.d.ts +78 -0
- package/dist/index.js +118 -0
- package/dist/lifecycleFacade.d.ts +46 -0
- package/dist/lifecycleFacade.js +68 -0
- package/dist/loading.d.ts +59 -0
- package/dist/loading.js +489 -0
- package/dist/operations.d.ts +287 -0
- package/dist/operations.js +485 -0
- package/dist/options.d.ts +16 -0
- package/dist/options.js +92 -0
- package/dist/presentation.d.ts +14 -0
- package/dist/presentation.js +16 -0
- package/dist/printLayout.d.ts +19 -0
- package/dist/printLayout.js +83 -0
- package/dist/registry.d.ts +2 -0
- package/dist/registry.js +63 -0
- package/dist/rendererDispatcher.d.ts +21 -0
- package/dist/rendererDispatcher.js +42 -0
- package/dist/rendererHandler.d.ts +165 -0
- package/dist/rendererHandler.js +354 -0
- package/dist/renderers/archive.d.ts +2 -0
- package/dist/renderers/archive.js +547 -0
- package/dist/renderers/archiveCache.d.ts +10 -0
- package/dist/renderers/archiveCache.js +96 -0
- package/dist/renderers/archiveFallback.d.ts +7 -0
- package/dist/renderers/archiveFallback.js +166 -0
- package/dist/renderers/archiveShared.d.ts +23 -0
- package/dist/renderers/archiveShared.js +71 -0
- package/dist/renderers/audio.d.ts +8 -0
- package/dist/renderers/audio.js +219 -0
- package/dist/renderers/cad.d.ts +2 -0
- package/dist/renderers/cad.js +445 -0
- package/dist/renderers/code.d.ts +11 -0
- package/dist/renderers/code.js +233 -0
- package/dist/renderers/data.d.ts +7 -0
- package/dist/renderers/data.js +370 -0
- package/dist/renderers/drawing.d.ts +10 -0
- package/dist/renderers/drawing.js +537 -0
- package/dist/renderers/eda.d.ts +2 -0
- package/dist/renderers/eda.js +434 -0
- package/dist/renderers/edaParser.d.ts +77 -0
- package/dist/renderers/edaParser.js +569 -0
- package/dist/renderers/email.d.ts +2 -0
- package/dist/renderers/email.js +463 -0
- package/dist/renderers/epub.d.ts +2 -0
- package/dist/renderers/epub.js +330 -0
- package/dist/renderers/geo.d.ts +2 -0
- package/dist/renderers/geo.js +284 -0
- package/dist/renderers/image.d.ts +2 -0
- package/dist/renderers/image.js +179 -0
- package/dist/renderers/index.d.ts +21 -0
- package/dist/renderers/index.js +207 -0
- package/dist/renderers/markdown.d.ts +2 -0
- package/dist/renderers/markdown.js +83 -0
- package/dist/renderers/model.d.ts +2 -0
- package/dist/renderers/model.js +566 -0
- package/dist/renderers/ofd.d.ts +2 -0
- package/dist/renderers/ofd.js +255 -0
- package/dist/renderers/openDocument.d.ts +2 -0
- package/dist/renderers/openDocument.js +122 -0
- package/dist/renderers/pdf.d.ts +3 -0
- package/dist/renderers/pdf.js +846 -0
- package/dist/renderers/pdfStyles.d.ts +1 -0
- package/dist/renderers/pdfStyles.js +1 -0
- package/dist/renderers/pptx.d.ts +2 -0
- package/dist/renderers/pptx.js +202 -0
- package/dist/renderers/spreadsheet/state.d.ts +80 -0
- package/dist/renderers/spreadsheet/state.js +96 -0
- package/dist/renderers/spreadsheet/view.d.ts +25 -0
- package/dist/renderers/spreadsheet/view.js +833 -0
- package/dist/renderers/spreadsheet/worker/index.d.ts +2 -0
- package/dist/renderers/spreadsheet/worker/index.js +1 -0
- package/dist/renderers/spreadsheet/worker/sheetjs/SheetJsModel.d.ts +73 -0
- package/dist/renderers/spreadsheet/worker/sheetjs/SheetJsModel.js +623 -0
- package/dist/renderers/spreadsheet/worker/sheetjs/color.d.ts +2 -0
- package/dist/renderers/spreadsheet/worker/sheetjs/color.js +73 -0
- package/dist/renderers/spreadsheet/worker/sheetjs/index.d.ts +1 -0
- package/dist/renderers/spreadsheet/worker/sheetjs/index.js +1 -0
- package/dist/renderers/spreadsheet/worker/sheetjs/parser.d.ts +18 -0
- package/dist/renderers/spreadsheet/worker/sheetjs/parser.js +106 -0
- package/dist/renderers/spreadsheet/worker/sheetjs/sheet.worker.d.ts +1 -0
- package/dist/renderers/spreadsheet/worker/sheetjs/sheet.worker.js +11 -0
- package/dist/renderers/spreadsheet/worker/type.d.ts +57 -0
- package/dist/renderers/spreadsheet/worker/type.js +1 -0
- package/dist/renderers/spreadsheet.d.ts +3 -0
- package/dist/renderers/spreadsheet.js +929 -0
- package/dist/renderers/typst.d.ts +8 -0
- package/dist/renderers/typst.js +415 -0
- package/dist/renderers/umd/parser.d.ts +30 -0
- package/dist/renderers/umd/parser.js +408 -0
- package/dist/renderers/umd.d.ts +2 -0
- package/dist/renderers/umd.js +297 -0
- package/dist/renderers/video.d.ts +8 -0
- package/dist/renderers/video.js +108 -0
- package/dist/renderers/wordDoc.d.ts +5 -0
- package/dist/renderers/wordDoc.js +284 -0
- package/dist/renderers/wordDocx.d.ts +5 -0
- package/dist/renderers/wordDocx.js +501 -0
- package/dist/renderers/wordDocx.worker.d.ts +1 -0
- package/dist/renderers/wordDocx.worker.js +96 -0
- package/dist/source.d.ts +18 -0
- package/dist/source.js +152 -0
- package/dist/sourceLoading.d.ts +566 -0
- package/dist/sourceLoading.js +918 -0
- package/dist/state.d.ts +16 -0
- package/dist/state.js +81 -0
- package/dist/types.d.ts +446 -0
- package/dist/types.js +1 -0
- package/dist/viewer.d.ts +8 -0
- package/dist/viewer.js +285 -0
- package/dist/viewerOperations.d.ts +88 -0
- package/dist/viewerOperations.js +242 -0
- package/dist/watermark.d.ts +15 -0
- package/dist/watermark.js +81 -0
- package/dist/worker.d.ts +34 -0
- package/dist/worker.js +101 -0
- package/package.json +109 -0
- package/vendor/ofd/dltech/jbig2/arithmetic_decoder.js +183 -0
- package/vendor/ofd/dltech/jbig2/ccitt.js +1070 -0
- package/vendor/ofd/dltech/jbig2/compatibility.js +12 -0
- package/vendor/ofd/dltech/jbig2/core_utils.js +180 -0
- package/vendor/ofd/dltech/jbig2/is_node.js +27 -0
- package/vendor/ofd/dltech/jbig2/jbig2.js +2589 -0
- package/vendor/ofd/dltech/jbig2/jbig2_stream.js +81 -0
- package/vendor/ofd/dltech/jbig2/primitives.js +387 -0
- package/vendor/ofd/dltech/jbig2/stream.js +1348 -0
- package/vendor/ofd/dltech/jbig2/util.js +972 -0
- package/vendor/ofd/dltech/ofd/ofd.d.ts +11 -0
- package/vendor/ofd/dltech/ofd/ofd.js +100 -0
- package/vendor/ofd/dltech/ofd/ofd_parser.js +395 -0
- package/vendor/ofd/dltech/ofd/ofd_render.js +473 -0
- package/vendor/ofd/dltech/ofd/ofd_util.js +350 -0
- package/vendor/ofd/dltech/ofd/pipeline.js +26 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
const epubStyle = `
|
|
2
|
+
.epub-viewer{width:100%;height:100%;display:flex;flex-direction:column;overflow:hidden;background:#eef1f4;color:#172033;box-sizing:border-box}
|
|
3
|
+
.epub-viewer *{box-sizing:border-box}
|
|
4
|
+
.epub-toolbar{flex-shrink:0;display:grid;grid-template-columns:40px minmax(0,1fr) auto;align-items:center;gap:12px;padding:12px 14px;border-bottom:1px solid rgba(15,23,42,.08);background:rgba(255,255,255,.92)}
|
|
5
|
+
.epub-title{min-width:0;display:flex;flex-direction:column;gap:3px}
|
|
6
|
+
.epub-title strong,.epub-title span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
7
|
+
.epub-title strong{font-size:14px}
|
|
8
|
+
.epub-title span{color:#64748b;font-size:12px}
|
|
9
|
+
.epub-icon-button,.epub-button{height:36px;border:1px solid rgba(15,23,42,.08);background:#fff;color:#172033;font:inherit;cursor:pointer}
|
|
10
|
+
.epub-icon-button{width:40px;display:inline-flex;align-items:center;justify-content:center;border-radius:8px}
|
|
11
|
+
.epub-icon-button span,.epub-icon-button span::before,.epub-icon-button span::after{width:16px;height:2px;display:block;border-radius:999px;background:currentColor}
|
|
12
|
+
.epub-icon-button span{position:relative}
|
|
13
|
+
.epub-icon-button span::before,.epub-icon-button span::after{content:'';position:absolute;left:0}
|
|
14
|
+
.epub-icon-button span::before{top:-5px}
|
|
15
|
+
.epub-icon-button span::after{top:5px}
|
|
16
|
+
.epub-icon-button.active{border-color:rgba(37,99,235,.24);background:rgba(37,99,235,.08);color:#1d4ed8}
|
|
17
|
+
.epub-actions{display:flex;align-items:center;gap:8px}
|
|
18
|
+
.epub-button{min-width:68px;padding:0 12px;border-radius:8px;font-size:13px;font-weight:700}
|
|
19
|
+
.epub-button:disabled{color:#94a3b8;cursor:not-allowed}
|
|
20
|
+
.epub-progress{min-width:58px;color:#64748b;font-size:12px;text-align:center}
|
|
21
|
+
.epub-body{flex:1;min-height:0;display:grid;grid-template-columns:minmax(180px,240px) minmax(0,1fr)}
|
|
22
|
+
.epub-viewer--toc-hidden .epub-body{grid-template-columns:minmax(0,1fr)}
|
|
23
|
+
.epub-toc{min-width:0;min-height:0;display:flex;flex-direction:column;border-right:1px solid rgba(15,23,42,.08);background:rgba(255,255,255,.8)}
|
|
24
|
+
.epub-toc[hidden]{display:none}
|
|
25
|
+
.epub-toc-head{flex-shrink:0;display:flex;justify-content:space-between;gap:8px;padding:12px;color:#172033;font-size:13px}
|
|
26
|
+
.epub-toc-head span{color:#64748b}
|
|
27
|
+
.epub-toc-list{flex:1;min-height:0;overflow:auto;padding:0 8px 10px}
|
|
28
|
+
.epub-toc-item{width:100%;min-height:34px;border:0;border-radius:8px;background:transparent;color:#475569;font:inherit;font-size:12px;text-align:left;cursor:pointer}
|
|
29
|
+
.epub-toc-item:hover,.epub-toc-item.active{background:rgba(37,99,235,.08);color:#1d4ed8}
|
|
30
|
+
.epub-stage-wrap{position:relative;min-width:0;min-height:0;padding:18px;overflow:hidden}
|
|
31
|
+
.epub-stage{width:100%;height:100%;overflow-x:hidden;overflow-y:auto;border-radius:8px;background:#fff;box-shadow:0 18px 45px rgba(15,23,42,.12),inset 0 0 0 1px rgba(15,23,42,.06)}
|
|
32
|
+
.epub-stage .epub-container{width:100%!important;max-width:100%;overflow-x:hidden!important;overflow-y:auto!important}
|
|
33
|
+
.epub-stage iframe{max-width:100%}
|
|
34
|
+
.epub-state{position:absolute;inset:18px;display:flex;align-items:center;justify-content:center;border-radius:8px;background:rgba(255,255,255,.92);color:#64748b;font-size:14px}
|
|
35
|
+
.epub-state.error{color:#b42318}
|
|
36
|
+
.file-viewer[data-viewer-theme='dark'] .epub-viewer{background:#172033;color:#e5eef8}
|
|
37
|
+
.file-viewer[data-viewer-theme='dark'] .epub-toolbar,.file-viewer[data-viewer-theme='dark'] .epub-toc,.file-viewer[data-viewer-theme='dark'] .epub-stage{background:#fff;color:#172033}
|
|
38
|
+
@media (prefers-color-scheme:dark){.file-viewer[data-viewer-theme='system'] .epub-viewer{background:#172033;color:#e5eef8}.file-viewer[data-viewer-theme='system'] .epub-toolbar,.file-viewer[data-viewer-theme='system'] .epub-toc,.file-viewer[data-viewer-theme='system'] .epub-stage{background:#fff;color:#172033}}
|
|
39
|
+
@media (max-width:720px){.epub-toolbar{grid-template-columns:40px minmax(0,1fr)}.epub-actions{grid-column:1/-1;justify-content:space-between}.epub-body{position:relative;grid-template-columns:minmax(0,1fr)}.epub-toc{position:absolute;z-index:5;top:0;bottom:0;left:0;width:min(82vw,280px);box-shadow:18px 0 40px rgba(15,23,42,.16)}.epub-stage-wrap{padding:12px}}
|
|
40
|
+
`;
|
|
41
|
+
const createStyle = () => {
|
|
42
|
+
const style = document.createElement('style');
|
|
43
|
+
style.textContent = epubStyle;
|
|
44
|
+
return style;
|
|
45
|
+
};
|
|
46
|
+
const createElement = (tagName, className, text) => {
|
|
47
|
+
const element = document.createElement(tagName);
|
|
48
|
+
if (className) {
|
|
49
|
+
element.className = className;
|
|
50
|
+
}
|
|
51
|
+
if (text !== undefined) {
|
|
52
|
+
element.textContent = text;
|
|
53
|
+
}
|
|
54
|
+
return element;
|
|
55
|
+
};
|
|
56
|
+
const normalizeLabel = (value, fallback) => {
|
|
57
|
+
if (typeof value === 'string' && value.trim()) {
|
|
58
|
+
return value.trim();
|
|
59
|
+
}
|
|
60
|
+
return fallback;
|
|
61
|
+
};
|
|
62
|
+
const flattenToc = (items, depth = 0) => {
|
|
63
|
+
if (!Array.isArray(items)) {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
return items.flatMap((item, index) => {
|
|
67
|
+
const node = item;
|
|
68
|
+
const href = typeof node.href === 'string' ? node.href : '';
|
|
69
|
+
const label = normalizeLabel(node.label || node.title, `章节 ${index + 1}`);
|
|
70
|
+
const subitems = flattenToc(node.subitems || node.children, depth + 1);
|
|
71
|
+
if (!href) {
|
|
72
|
+
return subitems;
|
|
73
|
+
}
|
|
74
|
+
return [{
|
|
75
|
+
depth,
|
|
76
|
+
href,
|
|
77
|
+
id: `${depth}-${index}-${href}`,
|
|
78
|
+
label,
|
|
79
|
+
}, ...subitems];
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
const pickInitialHref = (items) => {
|
|
83
|
+
var _a;
|
|
84
|
+
const chapterLike = items.find(item => {
|
|
85
|
+
const label = item.label.toLowerCase();
|
|
86
|
+
return /(^|\s)(chapter|part|book|prologue|preface|introduction)\b/.test(label)
|
|
87
|
+
|| /第[一二三四五六七八九十百千0-9]+[章节回部卷篇]/.test(item.label);
|
|
88
|
+
});
|
|
89
|
+
if (chapterLike) {
|
|
90
|
+
return chapterLike.href;
|
|
91
|
+
}
|
|
92
|
+
const readable = items.find(item => {
|
|
93
|
+
const text = `${item.label} ${item.href}`.toLowerCase();
|
|
94
|
+
return !/(cover|titlepage|title-page|copyright|license|toc|contents|nav|table-of-contents|wrap0000)/.test(text);
|
|
95
|
+
});
|
|
96
|
+
return (readable === null || readable === void 0 ? void 0 : readable.href) || ((_a = items[0]) === null || _a === void 0 ? void 0 : _a.href);
|
|
97
|
+
};
|
|
98
|
+
export default async function renderEpub(buffer, target) {
|
|
99
|
+
let book;
|
|
100
|
+
let rendition;
|
|
101
|
+
let disposed = false;
|
|
102
|
+
let tocOpen = true;
|
|
103
|
+
let status = 'loading';
|
|
104
|
+
let title = 'EPUB 电子书';
|
|
105
|
+
let author = '';
|
|
106
|
+
let tocItems = [];
|
|
107
|
+
let currentHref = '';
|
|
108
|
+
let progress = null;
|
|
109
|
+
let atStart = true;
|
|
110
|
+
let atEnd = false;
|
|
111
|
+
const timers = new Set();
|
|
112
|
+
const cleanups = [];
|
|
113
|
+
const root = createElement('div', 'epub-viewer');
|
|
114
|
+
const toolbar = createElement('div', 'epub-toolbar');
|
|
115
|
+
const tocButton = createElement('button', 'epub-icon-button');
|
|
116
|
+
tocButton.type = 'button';
|
|
117
|
+
tocButton.title = '目录';
|
|
118
|
+
tocButton.append(createElement('span'));
|
|
119
|
+
const titleRoot = createElement('div', 'epub-title');
|
|
120
|
+
const titleText = createElement('strong', undefined, title);
|
|
121
|
+
const subtitleText = createElement('span', undefined, '阅读中');
|
|
122
|
+
titleRoot.append(titleText, subtitleText);
|
|
123
|
+
const actions = createElement('div', 'epub-actions');
|
|
124
|
+
const prevButton = createElement('button', 'epub-button', '上一页');
|
|
125
|
+
const progressText = createElement('span', 'epub-progress', '阅读中');
|
|
126
|
+
const nextButton = createElement('button', 'epub-button', '下一页');
|
|
127
|
+
prevButton.type = 'button';
|
|
128
|
+
nextButton.type = 'button';
|
|
129
|
+
actions.append(prevButton, progressText, nextButton);
|
|
130
|
+
toolbar.append(tocButton, titleRoot, actions);
|
|
131
|
+
const body = createElement('div', 'epub-body');
|
|
132
|
+
const toc = createElement('aside', 'epub-toc');
|
|
133
|
+
const tocHead = createElement('div', 'epub-toc-head');
|
|
134
|
+
const tocCount = createElement('span', undefined, '0 项');
|
|
135
|
+
tocHead.append(createElement('strong', undefined, '目录'), tocCount);
|
|
136
|
+
const tocList = createElement('div', 'epub-toc-list');
|
|
137
|
+
toc.append(tocHead, tocList);
|
|
138
|
+
const stageWrap = createElement('main', 'epub-stage-wrap');
|
|
139
|
+
const stage = createElement('div', 'epub-stage');
|
|
140
|
+
const state = createElement('div', 'epub-state', '正在解析 EPUB...');
|
|
141
|
+
stageWrap.append(stage, state);
|
|
142
|
+
body.append(toc, stageWrap);
|
|
143
|
+
root.append(toolbar, body);
|
|
144
|
+
target.replaceChildren(createStyle(), root);
|
|
145
|
+
const listen = (element, event, listener) => {
|
|
146
|
+
element.addEventListener(event, listener);
|
|
147
|
+
cleanups.push(() => element.removeEventListener(event, listener));
|
|
148
|
+
};
|
|
149
|
+
const currentChapter = () => {
|
|
150
|
+
var _a;
|
|
151
|
+
if (!currentHref) {
|
|
152
|
+
return '';
|
|
153
|
+
}
|
|
154
|
+
const exact = tocItems.find(item => item.href === currentHref);
|
|
155
|
+
if (exact) {
|
|
156
|
+
return exact.label;
|
|
157
|
+
}
|
|
158
|
+
return ((_a = tocItems.find(item => currentHref.includes(item.href.split('#')[0]))) === null || _a === void 0 ? void 0 : _a.label) || '';
|
|
159
|
+
};
|
|
160
|
+
const progressLabel = () => {
|
|
161
|
+
if (typeof progress === 'number') {
|
|
162
|
+
return `${progress}%`;
|
|
163
|
+
}
|
|
164
|
+
return currentChapter() || '阅读中';
|
|
165
|
+
};
|
|
166
|
+
const syncUi = () => {
|
|
167
|
+
root.classList.toggle('epub-viewer--toc-hidden', !tocOpen);
|
|
168
|
+
toc.hidden = !tocOpen;
|
|
169
|
+
tocButton.classList.toggle('active', tocOpen);
|
|
170
|
+
titleText.textContent = title;
|
|
171
|
+
subtitleText.textContent = author || progressLabel();
|
|
172
|
+
progressText.textContent = progressLabel();
|
|
173
|
+
prevButton.disabled = status !== 'ready' || atStart;
|
|
174
|
+
nextButton.disabled = status !== 'ready' || atEnd;
|
|
175
|
+
state.hidden = status === 'ready';
|
|
176
|
+
state.classList.toggle('error', status === 'error');
|
|
177
|
+
tocCount.textContent = `${tocItems.length} 项`;
|
|
178
|
+
Array.from(tocList.querySelectorAll('.epub-toc-item')).forEach(button => {
|
|
179
|
+
button.classList.toggle('active', button.dataset.href === currentHref);
|
|
180
|
+
});
|
|
181
|
+
};
|
|
182
|
+
const renderToc = () => {
|
|
183
|
+
tocList.replaceChildren();
|
|
184
|
+
tocItems.forEach(item => {
|
|
185
|
+
const button = createElement('button', 'epub-toc-item', item.label);
|
|
186
|
+
button.type = 'button';
|
|
187
|
+
button.dataset.href = item.href;
|
|
188
|
+
button.style.paddingLeft = `${12 + item.depth * 14}px`;
|
|
189
|
+
listen(button, 'click', () => {
|
|
190
|
+
void (rendition === null || rendition === void 0 ? void 0 : rendition.display(item.href));
|
|
191
|
+
tocOpen = false;
|
|
192
|
+
syncUi();
|
|
193
|
+
});
|
|
194
|
+
tocList.append(button);
|
|
195
|
+
});
|
|
196
|
+
syncUi();
|
|
197
|
+
};
|
|
198
|
+
const updateLocation = (epubLocation) => {
|
|
199
|
+
var _a, _b;
|
|
200
|
+
atStart = Boolean(epubLocation === null || epubLocation === void 0 ? void 0 : epubLocation.atStart);
|
|
201
|
+
atEnd = Boolean(epubLocation === null || epubLocation === void 0 ? void 0 : epubLocation.atEnd);
|
|
202
|
+
currentHref = ((_a = epubLocation === null || epubLocation === void 0 ? void 0 : epubLocation.start) === null || _a === void 0 ? void 0 : _a.href) || '';
|
|
203
|
+
if (typeof ((_b = epubLocation === null || epubLocation === void 0 ? void 0 : epubLocation.start) === null || _b === void 0 ? void 0 : _b.percentage) === 'number') {
|
|
204
|
+
progress = Math.round(epubLocation.start.percentage * 100);
|
|
205
|
+
}
|
|
206
|
+
syncUi();
|
|
207
|
+
};
|
|
208
|
+
const wait = (ms) => new Promise(resolve => {
|
|
209
|
+
const timer = window.setTimeout(() => {
|
|
210
|
+
timers.delete(timer);
|
|
211
|
+
resolve(undefined);
|
|
212
|
+
}, ms);
|
|
213
|
+
timers.add(timer);
|
|
214
|
+
});
|
|
215
|
+
const hasReadableFrame = () => {
|
|
216
|
+
var _a;
|
|
217
|
+
try {
|
|
218
|
+
const iframe = stage.querySelector('iframe');
|
|
219
|
+
const frameBody = (_a = iframe === null || iframe === void 0 ? void 0 : iframe.contentDocument) === null || _a === void 0 ? void 0 : _a.body;
|
|
220
|
+
if (!frameBody) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
return Boolean(frameBody.innerText.trim() || frameBody.querySelector('img, svg, canvas'));
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
const waitForReadableFrame = async () => {
|
|
230
|
+
for (let index = 0; index < 20; index += 1) {
|
|
231
|
+
if (disposed || hasReadableFrame()) {
|
|
232
|
+
return hasReadableFrame();
|
|
233
|
+
}
|
|
234
|
+
await wait(100);
|
|
235
|
+
}
|
|
236
|
+
return hasReadableFrame();
|
|
237
|
+
};
|
|
238
|
+
const openBook = async () => {
|
|
239
|
+
status = 'loading';
|
|
240
|
+
state.textContent = '正在解析 EPUB...';
|
|
241
|
+
syncUi();
|
|
242
|
+
try {
|
|
243
|
+
const { default: ePub } = await import('epubjs');
|
|
244
|
+
if (disposed) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
book = ePub(buffer.slice(0), {
|
|
248
|
+
openAs: 'binary',
|
|
249
|
+
replacements: 'blobUrl',
|
|
250
|
+
});
|
|
251
|
+
rendition = book.renderTo(stage, {
|
|
252
|
+
allowScriptedContent: false,
|
|
253
|
+
flow: 'scrolled',
|
|
254
|
+
height: '100%',
|
|
255
|
+
manager: 'continuous',
|
|
256
|
+
resizeOnOrientationChange: true,
|
|
257
|
+
spread: 'none',
|
|
258
|
+
width: '100%',
|
|
259
|
+
});
|
|
260
|
+
rendition.themes.default({
|
|
261
|
+
body: {
|
|
262
|
+
color: '#172033',
|
|
263
|
+
fontFamily: 'Georgia, "Times New Roman", serif',
|
|
264
|
+
lineHeight: '1.72',
|
|
265
|
+
padding: '0 8px',
|
|
266
|
+
},
|
|
267
|
+
img: {
|
|
268
|
+
maxWidth: '100%',
|
|
269
|
+
},
|
|
270
|
+
html: {
|
|
271
|
+
height: 'auto',
|
|
272
|
+
overflow: 'auto',
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
rendition.on('relocated', updateLocation);
|
|
276
|
+
await book.ready;
|
|
277
|
+
const metadata = await book.loaded.metadata.catch(() => undefined);
|
|
278
|
+
title = normalizeLabel(metadata === null || metadata === void 0 ? void 0 : metadata.title, title);
|
|
279
|
+
author = normalizeLabel(metadata === null || metadata === void 0 ? void 0 : metadata.creator, '');
|
|
280
|
+
const navigation = await book.loaded.navigation.catch(() => undefined);
|
|
281
|
+
tocItems = flattenToc(navigation === null || navigation === void 0 ? void 0 : navigation.toc);
|
|
282
|
+
renderToc();
|
|
283
|
+
await rendition.display(pickInitialHref(tocItems));
|
|
284
|
+
if (!await waitForReadableFrame()) {
|
|
285
|
+
throw new Error('EPUB 正文渲染未完成,请刷新后重试');
|
|
286
|
+
}
|
|
287
|
+
if (disposed) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
status = 'ready';
|
|
291
|
+
syncUi();
|
|
292
|
+
void book.locations.generate(1200).catch(() => undefined);
|
|
293
|
+
}
|
|
294
|
+
catch (error) {
|
|
295
|
+
console.error(error);
|
|
296
|
+
status = 'error';
|
|
297
|
+
state.textContent = error instanceof Error ? error.message : String(error);
|
|
298
|
+
syncUi();
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
listen(tocButton, 'click', () => {
|
|
302
|
+
tocOpen = !tocOpen;
|
|
303
|
+
syncUi();
|
|
304
|
+
});
|
|
305
|
+
listen(prevButton, 'click', () => {
|
|
306
|
+
void (rendition === null || rendition === void 0 ? void 0 : rendition.prev());
|
|
307
|
+
});
|
|
308
|
+
listen(nextButton, 'click', () => {
|
|
309
|
+
void (rendition === null || rendition === void 0 ? void 0 : rendition.next());
|
|
310
|
+
});
|
|
311
|
+
syncUi();
|
|
312
|
+
void openBook();
|
|
313
|
+
return {
|
|
314
|
+
$el: root,
|
|
315
|
+
unmount() {
|
|
316
|
+
disposed = true;
|
|
317
|
+
timers.forEach(timer => window.clearTimeout(timer));
|
|
318
|
+
timers.clear();
|
|
319
|
+
if (rendition) {
|
|
320
|
+
rendition.off('relocated', updateLocation);
|
|
321
|
+
rendition.destroy();
|
|
322
|
+
rendition = undefined;
|
|
323
|
+
}
|
|
324
|
+
book === null || book === void 0 ? void 0 : book.destroy();
|
|
325
|
+
book = undefined;
|
|
326
|
+
cleanups.splice(0).forEach(cleanup => cleanup());
|
|
327
|
+
target.replaceChildren();
|
|
328
|
+
},
|
|
329
|
+
};
|
|
330
|
+
}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { readFileViewerText } from '../source.js';
|
|
2
|
+
const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
3
|
+
const GEO_WIDTH = 960;
|
|
4
|
+
const GEO_HEIGHT = 620;
|
|
5
|
+
const GEO_PADDING = 36;
|
|
6
|
+
const geoStyle = `
|
|
7
|
+
.geo-viewer{min-height:100%;display:grid;grid-template-columns:minmax(240px,320px) minmax(0,1fr);background:#eef1f4;color:#132235}
|
|
8
|
+
.geo-panel{padding:24px;border-right:1px solid rgba(15,23,42,.08);background:#fff;box-sizing:border-box}
|
|
9
|
+
.geo-panel>span{color:#0f766e;font-size:12px;font-weight:800}.geo-panel h2{margin:8px 0 22px;font-size:24px}
|
|
10
|
+
.geo-panel dl{display:grid;gap:12px;margin:0}.geo-panel dt,.geo-counts strong{color:#64748b;font-size:12px;font-weight:700}.geo-panel dd{margin:4px 0 0;word-break:break-all;font-size:14px}
|
|
11
|
+
.geo-counts{margin-top:24px}.geo-counts p{margin:8px 0 0;font-size:14px}
|
|
12
|
+
.geo-map{padding:28px;overflow:auto}.geo-map svg{display:block;width:min(100%,1200px);min-width:640px;height:auto;margin:0 auto;overflow:visible}
|
|
13
|
+
.geo-map rect{fill:#f8fafc;stroke:rgba(15,23,42,.08)}
|
|
14
|
+
.geo-map path{fill:none;stroke:#0f766e;stroke-width:2.2;vector-effect:non-scaling-stroke}.geo-map .geo-polygon{fill:rgba(45,212,191,.18)}
|
|
15
|
+
.geo-map circle{fill:#2563eb;stroke:#fff;stroke-width:1.5;vector-effect:non-scaling-stroke}
|
|
16
|
+
.geo-state{display:flex;align-items:center;justify-content:center;min-height:280px;border-radius:8px;background:rgba(255,255,255,.82);color:#64748b}
|
|
17
|
+
.geo-state.error{color:#b42318}
|
|
18
|
+
.file-viewer[data-viewer-theme='dark'] .geo-viewer{background:#101820;color:#e5edf6}.file-viewer[data-viewer-theme='dark'] .geo-panel{background:#111827;border-color:rgba(148,163,184,.18)}.file-viewer[data-viewer-theme='dark'] .geo-panel dt,.file-viewer[data-viewer-theme='dark'] .geo-counts strong{color:#94a3b8}.file-viewer[data-viewer-theme='dark'] .geo-map rect{fill:#111827;stroke:rgba(148,163,184,.18)}
|
|
19
|
+
@media (prefers-color-scheme:dark){.file-viewer[data-viewer-theme='system'] .geo-viewer{background:#101820;color:#e5edf6}.file-viewer[data-viewer-theme='system'] .geo-panel{background:#111827;border-color:rgba(148,163,184,.18)}.file-viewer[data-viewer-theme='system'] .geo-panel dt,.file-viewer[data-viewer-theme='system'] .geo-counts strong{color:#94a3b8}.file-viewer[data-viewer-theme='system'] .geo-map rect{fill:#111827;stroke:rgba(148,163,184,.18)}}
|
|
20
|
+
@media (max-width:860px){.geo-viewer{grid-template-columns:1fr}.geo-panel{border-right:0;border-bottom:1px solid rgba(15,23,42,.08)}}
|
|
21
|
+
`;
|
|
22
|
+
const createStyle = () => {
|
|
23
|
+
const style = document.createElement('style');
|
|
24
|
+
style.textContent = geoStyle;
|
|
25
|
+
return style;
|
|
26
|
+
};
|
|
27
|
+
const createSvgElement = (tag) => {
|
|
28
|
+
return document.createElementNS(SVG_NS, tag);
|
|
29
|
+
};
|
|
30
|
+
const normalizeGeoJson = (value) => {
|
|
31
|
+
const candidate = value;
|
|
32
|
+
if ((candidate === null || candidate === void 0 ? void 0 : candidate.type) === 'FeatureCollection' && Array.isArray(candidate.features)) {
|
|
33
|
+
return candidate;
|
|
34
|
+
}
|
|
35
|
+
if ((candidate === null || candidate === void 0 ? void 0 : candidate.type) === 'Feature') {
|
|
36
|
+
return { type: 'FeatureCollection', features: [candidate] };
|
|
37
|
+
}
|
|
38
|
+
if ((candidate === null || candidate === void 0 ? void 0 : candidate.type) && candidate.coordinates) {
|
|
39
|
+
return {
|
|
40
|
+
type: 'FeatureCollection',
|
|
41
|
+
features: [{ type: 'Feature', geometry: candidate, properties: {} }],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
throw new Error('无法识别的 GeoJSON 数据');
|
|
45
|
+
};
|
|
46
|
+
const parseXml = (text) => {
|
|
47
|
+
const doc = new DOMParser().parseFromString(text, 'application/xml');
|
|
48
|
+
const error = doc.querySelector('parsererror');
|
|
49
|
+
if (error) {
|
|
50
|
+
throw new Error(error.textContent || 'XML 解析失败');
|
|
51
|
+
}
|
|
52
|
+
return doc;
|
|
53
|
+
};
|
|
54
|
+
const normalizeGeoType = (type) => {
|
|
55
|
+
const normalized = (type || 'geojson').trim().toLowerCase();
|
|
56
|
+
if (normalized === 'json') {
|
|
57
|
+
return 'geojson';
|
|
58
|
+
}
|
|
59
|
+
if (normalized === 'shapefile') {
|
|
60
|
+
return 'shp';
|
|
61
|
+
}
|
|
62
|
+
return normalized;
|
|
63
|
+
};
|
|
64
|
+
const parseGeo = async (buffer, type) => {
|
|
65
|
+
if (type === 'geojson') {
|
|
66
|
+
return normalizeGeoJson(JSON.parse(await readFileViewerText(buffer)));
|
|
67
|
+
}
|
|
68
|
+
if (type === 'kml' || type === 'gpx') {
|
|
69
|
+
const toGeoJSON = await import('@tmcw/togeojson');
|
|
70
|
+
const doc = parseXml(await readFileViewerText(buffer));
|
|
71
|
+
return normalizeGeoJson(type === 'kml' ? toGeoJSON.kml(doc) : toGeoJSON.gpx(doc));
|
|
72
|
+
}
|
|
73
|
+
if (type === 'shp') {
|
|
74
|
+
const { default: shp } = await import('shpjs');
|
|
75
|
+
return normalizeGeoJson(await shp(buffer));
|
|
76
|
+
}
|
|
77
|
+
throw new Error(`不支持 .${type} 地理格式`);
|
|
78
|
+
};
|
|
79
|
+
const walkPositions = (geometry, visit) => {
|
|
80
|
+
if (!geometry) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
switch (geometry.type) {
|
|
84
|
+
case 'Point':
|
|
85
|
+
visit(geometry.coordinates);
|
|
86
|
+
break;
|
|
87
|
+
case 'MultiPoint':
|
|
88
|
+
case 'LineString':
|
|
89
|
+
geometry.coordinates.forEach(visit);
|
|
90
|
+
break;
|
|
91
|
+
case 'MultiLineString':
|
|
92
|
+
case 'Polygon':
|
|
93
|
+
geometry.coordinates.forEach(line => line.forEach(visit));
|
|
94
|
+
break;
|
|
95
|
+
case 'MultiPolygon':
|
|
96
|
+
geometry.coordinates.forEach(polygon => polygon.forEach(line => line.forEach(visit)));
|
|
97
|
+
break;
|
|
98
|
+
case 'GeometryCollection':
|
|
99
|
+
geometry.geometries.forEach(item => walkPositions(item, visit));
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
const collectBounds = (features) => {
|
|
104
|
+
let minX = Infinity;
|
|
105
|
+
let minY = Infinity;
|
|
106
|
+
let maxX = -Infinity;
|
|
107
|
+
let maxY = -Infinity;
|
|
108
|
+
features.forEach(feature => {
|
|
109
|
+
walkPositions(feature.geometry, position => {
|
|
110
|
+
const [x, y] = position;
|
|
111
|
+
if (!Number.isFinite(x) || !Number.isFinite(y)) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
minX = Math.min(minX, x);
|
|
115
|
+
minY = Math.min(minY, y);
|
|
116
|
+
maxX = Math.max(maxX, x);
|
|
117
|
+
maxY = Math.max(maxY, y);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
if (!Number.isFinite(minX) || !Number.isFinite(minY)) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
return { minX, minY, maxX, maxY };
|
|
124
|
+
};
|
|
125
|
+
const projectPosition = (position, bounds) => {
|
|
126
|
+
if (!bounds) {
|
|
127
|
+
return [GEO_WIDTH / 2, GEO_HEIGHT / 2];
|
|
128
|
+
}
|
|
129
|
+
const xRange = Math.max(1e-9, bounds.maxX - bounds.minX);
|
|
130
|
+
const yRange = Math.max(1e-9, bounds.maxY - bounds.minY);
|
|
131
|
+
const scale = Math.min((GEO_WIDTH - GEO_PADDING * 2) / xRange, (GEO_HEIGHT - GEO_PADDING * 2) / yRange);
|
|
132
|
+
const xOffset = (GEO_WIDTH - xRange * scale) / 2;
|
|
133
|
+
const yOffset = (GEO_HEIGHT - yRange * scale) / 2;
|
|
134
|
+
const x = xOffset + (position[0] - bounds.minX) * scale;
|
|
135
|
+
const y = GEO_HEIGHT - (yOffset + (position[1] - bounds.minY) * scale);
|
|
136
|
+
return [Number(x.toFixed(2)), Number(y.toFixed(2))];
|
|
137
|
+
};
|
|
138
|
+
const pathLine = (positions, bounds, close = false) => {
|
|
139
|
+
const points = positions
|
|
140
|
+
.filter(position => Number.isFinite(position[0]) && Number.isFinite(position[1]))
|
|
141
|
+
.map(position => projectPosition(position, bounds));
|
|
142
|
+
if (!points.length) {
|
|
143
|
+
return '';
|
|
144
|
+
}
|
|
145
|
+
const [first, ...rest] = points;
|
|
146
|
+
const body = [`M${first[0]} ${first[1]}`, ...rest.map(point => `L${point[0]} ${point[1]}`)];
|
|
147
|
+
if (close) {
|
|
148
|
+
body.push('Z');
|
|
149
|
+
}
|
|
150
|
+
return body.join(' ');
|
|
151
|
+
};
|
|
152
|
+
const appendGeometry = (parent, geometry, bounds) => {
|
|
153
|
+
if (!geometry) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
switch (geometry.type) {
|
|
157
|
+
case 'Point': {
|
|
158
|
+
const [x, y] = projectPosition(geometry.coordinates, bounds);
|
|
159
|
+
const circle = createSvgElement('circle');
|
|
160
|
+
circle.setAttribute('cx', String(x));
|
|
161
|
+
circle.setAttribute('cy', String(y));
|
|
162
|
+
circle.setAttribute('r', '4');
|
|
163
|
+
parent.appendChild(circle);
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
case 'MultiPoint':
|
|
167
|
+
geometry.coordinates.forEach(position => appendGeometry(parent, { type: 'Point', coordinates: position }, bounds));
|
|
168
|
+
break;
|
|
169
|
+
case 'LineString': {
|
|
170
|
+
const path = createSvgElement('path');
|
|
171
|
+
path.setAttribute('d', pathLine(geometry.coordinates, bounds));
|
|
172
|
+
parent.appendChild(path);
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
case 'MultiLineString':
|
|
176
|
+
geometry.coordinates.forEach(line => appendGeometry(parent, { type: 'LineString', coordinates: line }, bounds));
|
|
177
|
+
break;
|
|
178
|
+
case 'Polygon':
|
|
179
|
+
geometry.coordinates.forEach(line => {
|
|
180
|
+
const path = createSvgElement('path');
|
|
181
|
+
path.classList.add('geo-polygon');
|
|
182
|
+
path.setAttribute('d', pathLine(line, bounds, true));
|
|
183
|
+
parent.appendChild(path);
|
|
184
|
+
});
|
|
185
|
+
break;
|
|
186
|
+
case 'MultiPolygon':
|
|
187
|
+
geometry.coordinates.forEach(polygon => {
|
|
188
|
+
appendGeometry(parent, { type: 'Polygon', coordinates: polygon }, bounds);
|
|
189
|
+
});
|
|
190
|
+
break;
|
|
191
|
+
case 'GeometryCollection':
|
|
192
|
+
geometry.geometries.forEach(item => appendGeometry(parent, item, bounds));
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
const buildGeometryCounts = (features) => {
|
|
197
|
+
const counts = new Map();
|
|
198
|
+
features.forEach(feature => {
|
|
199
|
+
var _a;
|
|
200
|
+
const key = ((_a = feature.geometry) === null || _a === void 0 ? void 0 : _a.type) || 'Null';
|
|
201
|
+
counts.set(key, (counts.get(key) || 0) + 1);
|
|
202
|
+
});
|
|
203
|
+
return [...counts.entries()];
|
|
204
|
+
};
|
|
205
|
+
const formatBounds = (bounds) => {
|
|
206
|
+
if (!bounds) {
|
|
207
|
+
return '-';
|
|
208
|
+
}
|
|
209
|
+
return `${bounds.minX.toFixed(5)}, ${bounds.minY.toFixed(5)} -> ${bounds.maxX.toFixed(5)}, ${bounds.maxY.toFixed(5)}`;
|
|
210
|
+
};
|
|
211
|
+
const appendDescriptionItem = (list, label, value) => {
|
|
212
|
+
const row = document.createElement('div');
|
|
213
|
+
const term = document.createElement('dt');
|
|
214
|
+
term.textContent = label;
|
|
215
|
+
const detail = document.createElement('dd');
|
|
216
|
+
detail.textContent = String(value);
|
|
217
|
+
row.append(term, detail);
|
|
218
|
+
list.appendChild(row);
|
|
219
|
+
};
|
|
220
|
+
const renderCollection = (collection, type) => {
|
|
221
|
+
const root = document.createElement('div');
|
|
222
|
+
root.className = 'geo-viewer';
|
|
223
|
+
const features = collection.features || [];
|
|
224
|
+
const bounds = collectBounds(features);
|
|
225
|
+
const panel = document.createElement('aside');
|
|
226
|
+
panel.className = 'geo-panel';
|
|
227
|
+
const badge = document.createElement('span');
|
|
228
|
+
badge.textContent = type.toUpperCase();
|
|
229
|
+
const heading = document.createElement('h2');
|
|
230
|
+
heading.textContent = '地理数据预览';
|
|
231
|
+
const description = document.createElement('dl');
|
|
232
|
+
appendDescriptionItem(description, '要素数', features.length);
|
|
233
|
+
appendDescriptionItem(description, '范围', formatBounds(bounds));
|
|
234
|
+
const counts = document.createElement('div');
|
|
235
|
+
counts.className = 'geo-counts';
|
|
236
|
+
const countsHeading = document.createElement('strong');
|
|
237
|
+
countsHeading.textContent = '几何类型';
|
|
238
|
+
counts.appendChild(countsHeading);
|
|
239
|
+
buildGeometryCounts(features).forEach(([name, count]) => {
|
|
240
|
+
const row = document.createElement('p');
|
|
241
|
+
row.textContent = `${name}: ${count}`;
|
|
242
|
+
counts.appendChild(row);
|
|
243
|
+
});
|
|
244
|
+
panel.append(badge, heading, description, counts);
|
|
245
|
+
const map = document.createElement('main');
|
|
246
|
+
map.className = 'geo-map';
|
|
247
|
+
const svg = createSvgElement('svg');
|
|
248
|
+
svg.setAttribute('viewBox', `0 0 ${GEO_WIDTH} ${GEO_HEIGHT}`);
|
|
249
|
+
svg.setAttribute('role', 'img');
|
|
250
|
+
svg.setAttribute('aria-label', '地理数据 SVG 预览');
|
|
251
|
+
const rect = createSvgElement('rect');
|
|
252
|
+
rect.setAttribute('width', String(GEO_WIDTH));
|
|
253
|
+
rect.setAttribute('height', String(GEO_HEIGHT));
|
|
254
|
+
rect.setAttribute('rx', '8');
|
|
255
|
+
const group = createSvgElement('g');
|
|
256
|
+
features.forEach(feature => appendGeometry(group, feature.geometry, bounds));
|
|
257
|
+
svg.append(rect, group);
|
|
258
|
+
map.appendChild(svg);
|
|
259
|
+
root.append(panel, map);
|
|
260
|
+
return root;
|
|
261
|
+
};
|
|
262
|
+
export default async function renderGeo(buffer, target, type) {
|
|
263
|
+
const normalizedType = normalizeGeoType(type);
|
|
264
|
+
const style = createStyle();
|
|
265
|
+
const state = document.createElement('div');
|
|
266
|
+
state.className = 'geo-state';
|
|
267
|
+
state.textContent = '正在解析地理数据...';
|
|
268
|
+
target.replaceChildren(style, state);
|
|
269
|
+
try {
|
|
270
|
+
const collection = await parseGeo(buffer, normalizedType);
|
|
271
|
+
target.replaceChildren(style, renderCollection(collection, normalizedType));
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
console.error(error);
|
|
275
|
+
state.classList.add('error');
|
|
276
|
+
state.textContent = error instanceof Error ? error.message : String(error);
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
$el: target,
|
|
280
|
+
unmount() {
|
|
281
|
+
target.replaceChildren();
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
}
|