office-viewer-react 1.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/README.md +390 -0
- package/dist/client/assets/DocxViewer-NgAiZAEg.css +1 -0
- package/dist/client/assets/DocxViewer-gwdjm0mw.js +60 -0
- package/dist/client/assets/LogoIcon-BcnkueZW.js +1 -0
- package/dist/client/assets/PptxViewer-CLNaZa_4.js +59 -0
- package/dist/client/assets/PptxViewer-CYMXzyIj.css +1 -0
- package/dist/client/assets/XlsxViewer-BNso6L-X.css +1 -0
- package/dist/client/assets/XlsxViewer-C2ErMokS.js +64 -0
- package/dist/client/assets/_commonjs-dynamic-modules-DaXrHM_S.js +1 -0
- package/dist/client/assets/form-C1byQJR4.js +1 -0
- package/dist/client/assets/index-BDMLGHcR.js +2 -0
- package/dist/client/assets/index-CKjGwz9R.js +12 -0
- package/dist/client/assets/jszip.min-BwIaN_vk.js +2 -0
- package/dist/client/assets/login-DEy3R1iD.js +1 -0
- package/dist/client/assets/register-CUUVGLJE.js +1 -0
- package/dist/client/assets/styles-3a3CPFIV.css +1 -0
- package/dist/client/robots.txt +2 -0
- package/dist/index.cjs +1806 -0
- package/dist/index.d.cts +16 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +1769 -0
- package/dist/server/assets/DocxViewer-Bm8UJY-7.js +469 -0
- package/dist/server/assets/LogoIcon-Dx0LU3or.js +26 -0
- package/dist/server/assets/PptxViewer-DS7Atucw.js +213 -0
- package/dist/server/assets/XlsxViewer-jzIgKmN2.js +841 -0
- package/dist/server/assets/_tanstack-start-manifest_v-CpFqMvFH.js +4 -0
- package/dist/server/assets/empty-plugin-adapters-BFgPZ6_d.js +6 -0
- package/dist/server/assets/form-CD9otjw-.js +236 -0
- package/dist/server/assets/index-gQHSGxNv.js +365 -0
- package/dist/server/assets/login-DvbAXNSQ.js +81 -0
- package/dist/server/assets/register-C2G9K9kP.js +102 -0
- package/dist/server/assets/router-F5YKPXkV.js +229 -0
- package/dist/server/assets/server-6Sfy37dh.js +1523 -0
- package/dist/server/assets/start-dMGD6DUy.js +56 -0
- package/dist/server/server.js +94 -0
- package/package.json +120 -0
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useState, useEffect, useCallback } from "react";
|
|
3
|
+
import { renderAsync } from "docx-preview";
|
|
4
|
+
import { Loader2, ChevronLeft, ChevronRight } from "lucide-react";
|
|
5
|
+
import JSZip from "jszip";
|
|
6
|
+
import * as echarts from "echarts";
|
|
7
|
+
const NS = {
|
|
8
|
+
c: "http://schemas.openxmlformats.org/drawingml/2006/chart",
|
|
9
|
+
a: "http://schemas.openxmlformats.org/drawingml/2006/main",
|
|
10
|
+
r: "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
|
|
11
|
+
pic: "http://schemas.openxmlformats.org/drawingml/2006/picture",
|
|
12
|
+
rel: "http://schemas.openxmlformats.org/package/2006/relationships",
|
|
13
|
+
ct: "http://schemas.openxmlformats.org/package/2006/content-types"
|
|
14
|
+
};
|
|
15
|
+
const EMU_PER_PX = 9525;
|
|
16
|
+
const PALETTE = ["#4472c4", "#ed7d31", "#a5a5a5", "#ffc000", "#5b9bd5", "#70ad47", "#264478", "#9e480e"];
|
|
17
|
+
function localElements(parent, name) {
|
|
18
|
+
if (!parent) return [];
|
|
19
|
+
return Array.from(parent.children).filter((c) => c.localName === name);
|
|
20
|
+
}
|
|
21
|
+
function firstLocal(parent, name) {
|
|
22
|
+
if (!parent) return null;
|
|
23
|
+
return Array.from(parent.children).find((c) => c.localName === name) ?? null;
|
|
24
|
+
}
|
|
25
|
+
function deepFirst(parent, name) {
|
|
26
|
+
if (!parent) return null;
|
|
27
|
+
const list = parent.getElementsByTagNameNS(NS.c, name);
|
|
28
|
+
return list.length ? list[0] : null;
|
|
29
|
+
}
|
|
30
|
+
function readCache(ref) {
|
|
31
|
+
if (!ref) return [];
|
|
32
|
+
const cache = firstLocal(ref, "numCache") || firstLocal(ref, "strCache") || ref;
|
|
33
|
+
const pts = Array.from(cache.getElementsByTagNameNS(NS.c, "pt"));
|
|
34
|
+
const out = [];
|
|
35
|
+
for (const pt of pts) {
|
|
36
|
+
const idx = parseInt(pt.getAttribute("idx") || "0", 10);
|
|
37
|
+
const v = pt.getElementsByTagNameNS(NS.c, "v")[0];
|
|
38
|
+
out[idx] = v ? v.textContent || "" : "";
|
|
39
|
+
}
|
|
40
|
+
for (let i = 0; i < out.length; i++) if (out[i] === void 0) out[i] = "";
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
function parseSeries(ser) {
|
|
44
|
+
const tx = firstLocal(ser, "tx");
|
|
45
|
+
let name = "";
|
|
46
|
+
if (tx) {
|
|
47
|
+
const strRef = firstLocal(tx, "strRef");
|
|
48
|
+
if (strRef) name = readCache(strRef).find((x) => x) || "";
|
|
49
|
+
else {
|
|
50
|
+
const v = tx.getElementsByTagNameNS(NS.c, "v")[0];
|
|
51
|
+
name = v ? v.textContent || "" : "";
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const catRef = firstLocal(firstLocal(ser, "cat"), "strRef") || firstLocal(firstLocal(ser, "cat"), "numRef");
|
|
55
|
+
const valRef = firstLocal(firstLocal(ser, "val"), "numRef");
|
|
56
|
+
const cats = readCache(catRef);
|
|
57
|
+
const vals = readCache(valRef).map((v) => {
|
|
58
|
+
const n = parseFloat(v);
|
|
59
|
+
return isNaN(n) ? 0 : n;
|
|
60
|
+
});
|
|
61
|
+
return { name, cats, vals };
|
|
62
|
+
}
|
|
63
|
+
function chartTitle(chartEl) {
|
|
64
|
+
const title = deepFirst(chartEl, "title");
|
|
65
|
+
if (!title) return "";
|
|
66
|
+
return Array.from(title.getElementsByTagNameNS(NS.a, "t")).map((t) => t.textContent || "").join("");
|
|
67
|
+
}
|
|
68
|
+
function buildOption(chartSpace) {
|
|
69
|
+
const chartEl = firstLocal(chartSpace, "chart");
|
|
70
|
+
const plotArea = firstLocal(chartEl, "plotArea");
|
|
71
|
+
if (!plotArea) return null;
|
|
72
|
+
const typeEl = Array.from(plotArea.children).find((c) => /Chart$/.test(c.localName));
|
|
73
|
+
if (!typeEl) return null;
|
|
74
|
+
const kind = typeEl.localName;
|
|
75
|
+
const seriesEls = localElements(typeEl, "ser");
|
|
76
|
+
if (!seriesEls.length) return null;
|
|
77
|
+
const series = seriesEls.map(parseSeries);
|
|
78
|
+
const categories = series[0]?.cats ?? [];
|
|
79
|
+
const title = chartTitle(chartEl);
|
|
80
|
+
const baseTitle = title ? { title: { text: title, left: "center", textStyle: { fontSize: 14 } } } : {};
|
|
81
|
+
const legend = series.length > 1 || /pie|doughnut/i.test(kind) ? { legend: { bottom: 0, type: "scroll" } } : {};
|
|
82
|
+
if (/pie|doughnut/i.test(kind)) {
|
|
83
|
+
const s = series[0];
|
|
84
|
+
const radius = /doughnut/i.test(kind) ? ["40%", "70%"] : "70%";
|
|
85
|
+
return {
|
|
86
|
+
color: PALETTE,
|
|
87
|
+
...baseTitle,
|
|
88
|
+
tooltip: { trigger: "item" },
|
|
89
|
+
legend: { bottom: 0, type: "scroll" },
|
|
90
|
+
series: [{ type: "pie", radius, center: ["50%", "48%"], data: s.cats.map((c, i) => ({ name: c || `#${i + 1}`, value: s.vals[i] ?? 0 })), label: { fontSize: 11 } }]
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
if (/scatter/i.test(kind)) {
|
|
94
|
+
return {
|
|
95
|
+
color: PALETTE,
|
|
96
|
+
...baseTitle,
|
|
97
|
+
...legend,
|
|
98
|
+
tooltip: { trigger: "item" },
|
|
99
|
+
xAxis: { type: "value" },
|
|
100
|
+
yAxis: { type: "value" },
|
|
101
|
+
series: series.map((s) => ({ name: s.name, type: "scatter", data: s.vals.map((v, i) => [parseFloat(s.cats[i]) || i, v]) }))
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
const isBarHorizontal = /barChart/i.test(kind) && firstLocal(typeEl, "barDir")?.getAttribute("val") === "bar";
|
|
105
|
+
const isArea = /areaChart/i.test(kind);
|
|
106
|
+
const isLine = /lineChart/i.test(kind);
|
|
107
|
+
const catAxis = { type: "category", data: categories };
|
|
108
|
+
const valAxis = { type: "value" };
|
|
109
|
+
return {
|
|
110
|
+
color: PALETTE,
|
|
111
|
+
...baseTitle,
|
|
112
|
+
...legend,
|
|
113
|
+
grid: { left: 48, right: 24, top: title ? 48 : 24, bottom: series.length > 1 ? 48 : 32, containLabel: true },
|
|
114
|
+
tooltip: { trigger: "axis" },
|
|
115
|
+
xAxis: isBarHorizontal ? valAxis : catAxis,
|
|
116
|
+
yAxis: isBarHorizontal ? catAxis : valAxis,
|
|
117
|
+
series: series.map((s) => ({ name: s.name, type: isLine || isArea ? "line" : "bar", areaStyle: isArea ? {} : void 0, smooth: false, data: s.vals }))
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
async function renderChartPng(option, wPx, hPx) {
|
|
121
|
+
const w = Math.max(240, Math.min(1600, Math.round(wPx)));
|
|
122
|
+
const h = Math.max(160, Math.min(1200, Math.round(hPx)));
|
|
123
|
+
const div = document.createElement("div");
|
|
124
|
+
div.style.cssText = `position:absolute;left:-99999px;top:0;width:${w}px;height:${h}px;`;
|
|
125
|
+
document.body.appendChild(div);
|
|
126
|
+
const chart = echarts.init(div, void 0, { renderer: "canvas", width: w, height: h });
|
|
127
|
+
chart.setOption({ animation: false, ...option });
|
|
128
|
+
const url = chart.getDataURL({ type: "png", pixelRatio: 2, backgroundColor: "#ffffff" });
|
|
129
|
+
chart.dispose();
|
|
130
|
+
div.remove();
|
|
131
|
+
return url;
|
|
132
|
+
}
|
|
133
|
+
function dataUrlToBytes(dataUrl) {
|
|
134
|
+
const b64 = dataUrl.split(",")[1];
|
|
135
|
+
const bin = atob(b64);
|
|
136
|
+
const bytes = new Uint8Array(bin.length);
|
|
137
|
+
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
|
|
138
|
+
return bytes;
|
|
139
|
+
}
|
|
140
|
+
function resolvePath(baseDir, target) {
|
|
141
|
+
const parts = (baseDir + target).split("/");
|
|
142
|
+
const stack = [];
|
|
143
|
+
for (const p of parts) {
|
|
144
|
+
if (p === "..") stack.pop();
|
|
145
|
+
else if (p !== "." && p !== "") stack.push(p);
|
|
146
|
+
}
|
|
147
|
+
return stack.join("/");
|
|
148
|
+
}
|
|
149
|
+
function buildPicNode(doc, relId, cx, cy) {
|
|
150
|
+
const pic = doc.createElementNS(NS.pic, "pic:pic");
|
|
151
|
+
const nvPicPr = doc.createElementNS(NS.pic, "pic:nvPicPr");
|
|
152
|
+
const cNvPr = doc.createElementNS(NS.pic, "pic:cNvPr");
|
|
153
|
+
cNvPr.setAttribute("id", "0");
|
|
154
|
+
cNvPr.setAttribute("name", "chart");
|
|
155
|
+
const cNvPicPr = doc.createElementNS(NS.pic, "pic:cNvPicPr");
|
|
156
|
+
nvPicPr.append(cNvPr, cNvPicPr);
|
|
157
|
+
const blipFill = doc.createElementNS(NS.pic, "pic:blipFill");
|
|
158
|
+
const blip = doc.createElementNS(NS.a, "a:blip");
|
|
159
|
+
blip.setAttributeNS(NS.r, "r:embed", relId);
|
|
160
|
+
const stretch = doc.createElementNS(NS.a, "a:stretch");
|
|
161
|
+
stretch.appendChild(doc.createElementNS(NS.a, "a:fillRect"));
|
|
162
|
+
blipFill.append(blip, stretch);
|
|
163
|
+
const spPr = doc.createElementNS(NS.pic, "pic:spPr");
|
|
164
|
+
const xfrm = doc.createElementNS(NS.a, "a:xfrm");
|
|
165
|
+
const off = doc.createElementNS(NS.a, "a:off");
|
|
166
|
+
off.setAttribute("x", "0");
|
|
167
|
+
off.setAttribute("y", "0");
|
|
168
|
+
const ext = doc.createElementNS(NS.a, "a:ext");
|
|
169
|
+
ext.setAttribute("cx", cx);
|
|
170
|
+
ext.setAttribute("cy", cy);
|
|
171
|
+
xfrm.append(off, ext);
|
|
172
|
+
const prstGeom = doc.createElementNS(NS.a, "a:prstGeom");
|
|
173
|
+
prstGeom.setAttribute("prst", "rect");
|
|
174
|
+
prstGeom.appendChild(doc.createElementNS(NS.a, "a:avLst"));
|
|
175
|
+
spPr.append(xfrm, prstGeom);
|
|
176
|
+
pic.append(nvPicPr, blipFill, spPr);
|
|
177
|
+
return pic;
|
|
178
|
+
}
|
|
179
|
+
function getExtent(chartNode) {
|
|
180
|
+
let el = chartNode;
|
|
181
|
+
while (el && el.localName !== "inline" && el.localName !== "anchor") el = el.parentElement;
|
|
182
|
+
if (el) {
|
|
183
|
+
const ext = firstLocal(el, "extent");
|
|
184
|
+
if (ext) return { cx: ext.getAttribute("cx") || "5486400", cy: ext.getAttribute("cy") || "3200400" };
|
|
185
|
+
}
|
|
186
|
+
return { cx: "5486400", cy: "3200400" };
|
|
187
|
+
}
|
|
188
|
+
async function preprocessDocxCharts(data) {
|
|
189
|
+
let zip;
|
|
190
|
+
try {
|
|
191
|
+
zip = await JSZip.loadAsync(data);
|
|
192
|
+
} catch {
|
|
193
|
+
return data;
|
|
194
|
+
}
|
|
195
|
+
const partNames = Object.keys(zip.files).filter((n) => /^word\/(document|header\d*|footer\d*)\.xml$/.test(n));
|
|
196
|
+
let imgCounter = 0, touched = false;
|
|
197
|
+
const parser = new DOMParser(), serializer = new XMLSerializer();
|
|
198
|
+
for (const partName of partNames) {
|
|
199
|
+
const xmlText = await zip.file(partName).async("string");
|
|
200
|
+
if (!xmlText.includes("drawingml/2006/chart")) continue;
|
|
201
|
+
const doc = parser.parseFromString(xmlText, "application/xml");
|
|
202
|
+
const chartNodes = Array.from(doc.getElementsByTagNameNS(NS.c, "chart")).filter(
|
|
203
|
+
(n) => n.localName === "chart" && n.getAttributeNS(NS.r, "id")
|
|
204
|
+
);
|
|
205
|
+
if (!chartNodes.length) continue;
|
|
206
|
+
const dir = partName.substring(0, partName.lastIndexOf("/") + 1);
|
|
207
|
+
const relsPath = `${dir}_rels/${partName.substring(dir.length)}.rels`;
|
|
208
|
+
const relsFile = zip.file(relsPath);
|
|
209
|
+
if (!relsFile) continue;
|
|
210
|
+
const relsDoc = parser.parseFromString(await relsFile.async("string"), "application/xml");
|
|
211
|
+
const relsRoot = relsDoc.documentElement;
|
|
212
|
+
const relTarget = (id) => {
|
|
213
|
+
const rel = Array.from(relsRoot.children).find((r) => r.getAttribute("Id") === id);
|
|
214
|
+
return rel ? rel.getAttribute("Target") : null;
|
|
215
|
+
};
|
|
216
|
+
const existingIds = new Set(Array.from(relsRoot.children).map((r) => r.getAttribute("Id") || ""));
|
|
217
|
+
const newRelId = () => {
|
|
218
|
+
let i = 1, id = `rIdChart${i}`;
|
|
219
|
+
while (existingIds.has(id)) id = `rIdChart${++i}`;
|
|
220
|
+
existingIds.add(id);
|
|
221
|
+
return id;
|
|
222
|
+
};
|
|
223
|
+
for (const chartNode of chartNodes) {
|
|
224
|
+
const relId = chartNode.getAttributeNS(NS.r, "id");
|
|
225
|
+
const target = relTarget(relId);
|
|
226
|
+
if (!target) continue;
|
|
227
|
+
const chartPath = resolvePath(dir, target);
|
|
228
|
+
const chartFile = zip.file(chartPath);
|
|
229
|
+
if (!chartFile) continue;
|
|
230
|
+
let option = null;
|
|
231
|
+
try {
|
|
232
|
+
const chartDoc = parser.parseFromString(await chartFile.async("string"), "application/xml");
|
|
233
|
+
option = buildOption(chartDoc.documentElement);
|
|
234
|
+
} catch {
|
|
235
|
+
option = null;
|
|
236
|
+
}
|
|
237
|
+
if (!option) continue;
|
|
238
|
+
const { cx, cy } = getExtent(chartNode);
|
|
239
|
+
const wPx = parseInt(cx, 10) / EMU_PER_PX, hPx = parseInt(cy, 10) / EMU_PER_PX;
|
|
240
|
+
let png;
|
|
241
|
+
try {
|
|
242
|
+
const url = await renderChartPng(option, wPx, hPx);
|
|
243
|
+
png = dataUrlToBytes(url);
|
|
244
|
+
} catch {
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
imgCounter++;
|
|
248
|
+
const imgName = `media/chartimg${imgCounter}.png`;
|
|
249
|
+
zip.file(`word/${imgName}`, png);
|
|
250
|
+
const rId = newRelId();
|
|
251
|
+
const rel = relsDoc.createElementNS(NS.rel, "Relationship");
|
|
252
|
+
rel.setAttribute("Id", rId);
|
|
253
|
+
rel.setAttribute("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image");
|
|
254
|
+
rel.setAttribute("Target", imgName);
|
|
255
|
+
relsRoot.appendChild(rel);
|
|
256
|
+
const graphicData = chartNode.parentElement;
|
|
257
|
+
if (!graphicData) continue;
|
|
258
|
+
graphicData.setAttribute("uri", NS.pic);
|
|
259
|
+
while (graphicData.firstChild) graphicData.removeChild(graphicData.firstChild);
|
|
260
|
+
graphicData.appendChild(buildPicNode(doc, rId, cx, cy));
|
|
261
|
+
touched = true;
|
|
262
|
+
}
|
|
263
|
+
zip.file(partName, serializer.serializeToString(doc));
|
|
264
|
+
zip.file(relsPath, serializer.serializeToString(relsDoc));
|
|
265
|
+
}
|
|
266
|
+
if (!touched) return data;
|
|
267
|
+
const ctFile = zip.file("[Content_Types].xml");
|
|
268
|
+
if (ctFile) {
|
|
269
|
+
const ctDoc = parser.parseFromString(await ctFile.async("string"), "application/xml");
|
|
270
|
+
const hasPng = Array.from(ctDoc.documentElement.children).some(
|
|
271
|
+
(e) => e.localName === "Default" && e.getAttribute("Extension")?.toLowerCase() === "png"
|
|
272
|
+
);
|
|
273
|
+
if (!hasPng) {
|
|
274
|
+
const def = ctDoc.createElementNS(NS.ct, "Default");
|
|
275
|
+
def.setAttribute("Extension", "png");
|
|
276
|
+
def.setAttribute("ContentType", "image/png");
|
|
277
|
+
ctDoc.documentElement.appendChild(def);
|
|
278
|
+
zip.file("[Content_Types].xml", serializer.serializeToString(ctDoc));
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return zip.generateAsync({ type: "blob" });
|
|
282
|
+
}
|
|
283
|
+
function stampPageNumbers(container) {
|
|
284
|
+
const sections = Array.from(container.querySelectorAll("section"));
|
|
285
|
+
if (sections.length === 0) return { sections: [], labels: [] };
|
|
286
|
+
const total = sections.length;
|
|
287
|
+
const labels = [];
|
|
288
|
+
sections.forEach((section, i) => {
|
|
289
|
+
section.style.boxShadow = "0 1px 4px 0 rgba(15,23,42,0.08), 0 0 0 1px rgba(15,23,42,0.04)";
|
|
290
|
+
const label = document.createElement("div");
|
|
291
|
+
label.setAttribute("aria-hidden", "true");
|
|
292
|
+
label.style.display = "flex";
|
|
293
|
+
label.style.justifyContent = "center";
|
|
294
|
+
label.style.alignItems = "center";
|
|
295
|
+
label.style.padding = "10px 0 26px";
|
|
296
|
+
label.style.fontSize = "12px";
|
|
297
|
+
label.style.color = "#94a3b8";
|
|
298
|
+
label.style.userSelect = "none";
|
|
299
|
+
label.style.letterSpacing = "0.04em";
|
|
300
|
+
label.textContent = `Page ${i + 1} of ${total}`;
|
|
301
|
+
section.insertAdjacentElement("afterend", label);
|
|
302
|
+
labels.push(label);
|
|
303
|
+
});
|
|
304
|
+
return { sections, labels };
|
|
305
|
+
}
|
|
306
|
+
function stylePages(sections, labels, mode, activeIndex) {
|
|
307
|
+
sections.forEach((s, i) => {
|
|
308
|
+
s.style.display = mode === "navigation" && i !== activeIndex ? "none" : "";
|
|
309
|
+
});
|
|
310
|
+
labels.forEach((l) => {
|
|
311
|
+
l.style.display = mode === "navigation" ? "none" : "";
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
function DocxViewer({
|
|
315
|
+
data,
|
|
316
|
+
mode = "scroll",
|
|
317
|
+
className,
|
|
318
|
+
onError
|
|
319
|
+
}) {
|
|
320
|
+
const containerRef = useRef(null);
|
|
321
|
+
const sectionsRef = useRef([]);
|
|
322
|
+
const labelsRef = useRef([]);
|
|
323
|
+
const currentPageRef = useRef(0);
|
|
324
|
+
const modeRef = useRef(mode);
|
|
325
|
+
const [loading, setLoading] = useState(true);
|
|
326
|
+
const [error, setError] = useState(null);
|
|
327
|
+
const [currentPage, setCurrentPage] = useState(0);
|
|
328
|
+
const [totalPages, setTotalPages] = useState(0);
|
|
329
|
+
useEffect(() => {
|
|
330
|
+
modeRef.current = mode;
|
|
331
|
+
}, [mode]);
|
|
332
|
+
const isNavMode = mode === "navigation";
|
|
333
|
+
const canPrev = currentPage > 0;
|
|
334
|
+
const canNext = currentPage < totalPages - 1;
|
|
335
|
+
const goToPage = useCallback((index) => {
|
|
336
|
+
const sections = sectionsRef.current;
|
|
337
|
+
if (!sections.length || index < 0 || index >= sections.length) return;
|
|
338
|
+
stylePages(sections, labelsRef.current, "navigation", index);
|
|
339
|
+
currentPageRef.current = index;
|
|
340
|
+
setCurrentPage(index);
|
|
341
|
+
containerRef.current?.scrollIntoView({ block: "start", behavior: "smooth" });
|
|
342
|
+
}, []);
|
|
343
|
+
const applyMode = useCallback((m) => {
|
|
344
|
+
const sections = sectionsRef.current;
|
|
345
|
+
if (!sections.length) return;
|
|
346
|
+
const idx = Math.min(currentPageRef.current, sections.length - 1);
|
|
347
|
+
stylePages(sections, labelsRef.current, m, idx);
|
|
348
|
+
if (m === "navigation") {
|
|
349
|
+
currentPageRef.current = idx;
|
|
350
|
+
setCurrentPage(idx);
|
|
351
|
+
}
|
|
352
|
+
}, []);
|
|
353
|
+
useEffect(() => {
|
|
354
|
+
applyMode(mode);
|
|
355
|
+
}, [mode, applyMode]);
|
|
356
|
+
useEffect(() => {
|
|
357
|
+
if (mode !== "navigation") return;
|
|
358
|
+
const handleKey = (e) => {
|
|
359
|
+
if (e.target instanceof HTMLSelectElement) return;
|
|
360
|
+
if (e.key === "ArrowRight" || e.key === "ArrowDown") {
|
|
361
|
+
e.preventDefault();
|
|
362
|
+
goToPage(currentPageRef.current + 1);
|
|
363
|
+
} else if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
|
|
364
|
+
e.preventDefault();
|
|
365
|
+
goToPage(currentPageRef.current - 1);
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
window.addEventListener("keydown", handleKey);
|
|
369
|
+
return () => window.removeEventListener("keydown", handleKey);
|
|
370
|
+
}, [mode, goToPage]);
|
|
371
|
+
useEffect(() => {
|
|
372
|
+
let cancelled = false;
|
|
373
|
+
setLoading(true);
|
|
374
|
+
setError(null);
|
|
375
|
+
(async () => {
|
|
376
|
+
try {
|
|
377
|
+
const prepared = await preprocessDocxCharts(data.slice(0));
|
|
378
|
+
if (cancelled || !containerRef.current) return;
|
|
379
|
+
containerRef.current.innerHTML = "";
|
|
380
|
+
sectionsRef.current = [];
|
|
381
|
+
labelsRef.current = [];
|
|
382
|
+
await renderAsync(prepared, containerRef.current, void 0, {
|
|
383
|
+
className: "docx",
|
|
384
|
+
inWrapper: true,
|
|
385
|
+
ignoreWidth: false,
|
|
386
|
+
ignoreHeight: false,
|
|
387
|
+
breakPages: true,
|
|
388
|
+
experimental: true,
|
|
389
|
+
renderHeaders: true,
|
|
390
|
+
renderFooters: true,
|
|
391
|
+
renderFootnotes: true,
|
|
392
|
+
renderEndnotes: true,
|
|
393
|
+
useBase64URL: true
|
|
394
|
+
});
|
|
395
|
+
if (!cancelled && containerRef.current) {
|
|
396
|
+
const { sections, labels } = stampPageNumbers(containerRef.current);
|
|
397
|
+
sectionsRef.current = sections;
|
|
398
|
+
labelsRef.current = labels;
|
|
399
|
+
setTotalPages(sections.length);
|
|
400
|
+
const m = modeRef.current;
|
|
401
|
+
const idx = Math.min(currentPageRef.current, sections.length - 1);
|
|
402
|
+
stylePages(sections, labels, m, idx);
|
|
403
|
+
if (m === "navigation") {
|
|
404
|
+
currentPageRef.current = idx;
|
|
405
|
+
setCurrentPage(idx);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
} catch (e) {
|
|
409
|
+
const err = e instanceof Error ? e : new Error("Failed to render document");
|
|
410
|
+
if (!cancelled) {
|
|
411
|
+
setError(err.message);
|
|
412
|
+
onError?.(err);
|
|
413
|
+
}
|
|
414
|
+
} finally {
|
|
415
|
+
if (!cancelled) setLoading(false);
|
|
416
|
+
}
|
|
417
|
+
})();
|
|
418
|
+
return () => {
|
|
419
|
+
cancelled = true;
|
|
420
|
+
};
|
|
421
|
+
}, [data, onError]);
|
|
422
|
+
return /* @__PURE__ */ jsxs("div", { className: `ov-docx${className ? ` ${className}` : ""}`, children: [
|
|
423
|
+
loading && /* @__PURE__ */ jsxs("div", { className: "ov-docx__loading", children: [
|
|
424
|
+
/* @__PURE__ */ jsx(Loader2, { className: "ov-docx__loading-icon" }),
|
|
425
|
+
/* @__PURE__ */ jsx("span", { className: "ov-docx__loading-text", children: "Rendering document…" })
|
|
426
|
+
] }),
|
|
427
|
+
error && /* @__PURE__ */ jsx("div", { className: "ov-docx__error", children: error }),
|
|
428
|
+
/* @__PURE__ */ jsx(
|
|
429
|
+
"div",
|
|
430
|
+
{
|
|
431
|
+
className: "ov-docx__body",
|
|
432
|
+
style: { visibility: loading ? "hidden" : "visible" },
|
|
433
|
+
children: /* @__PURE__ */ jsx("div", { ref: containerRef, className: "ov-docx__render" })
|
|
434
|
+
}
|
|
435
|
+
),
|
|
436
|
+
isNavMode && !loading && totalPages > 0 && /* @__PURE__ */ jsxs("div", { className: "ov-docx__nav", children: [
|
|
437
|
+
/* @__PURE__ */ jsx(
|
|
438
|
+
"button",
|
|
439
|
+
{
|
|
440
|
+
onClick: () => goToPage(currentPageRef.current - 1),
|
|
441
|
+
disabled: !canPrev,
|
|
442
|
+
"aria-label": "Previous page",
|
|
443
|
+
className: "ov-docx__nav-btn",
|
|
444
|
+
children: /* @__PURE__ */ jsx(ChevronLeft, { className: "ov-docx__nav-btn-icon", strokeWidth: 2 })
|
|
445
|
+
}
|
|
446
|
+
),
|
|
447
|
+
/* @__PURE__ */ jsxs("div", { className: "ov-docx__nav-counter", children: [
|
|
448
|
+
/* @__PURE__ */ jsx("span", { className: "ov-docx__nav-current", children: currentPage + 1 }),
|
|
449
|
+
/* @__PURE__ */ jsxs("span", { className: "ov-docx__nav-total", children: [
|
|
450
|
+
" of ",
|
|
451
|
+
totalPages
|
|
452
|
+
] })
|
|
453
|
+
] }),
|
|
454
|
+
/* @__PURE__ */ jsx(
|
|
455
|
+
"button",
|
|
456
|
+
{
|
|
457
|
+
onClick: () => goToPage(currentPageRef.current + 1),
|
|
458
|
+
disabled: !canNext,
|
|
459
|
+
"aria-label": "Next page",
|
|
460
|
+
className: "ov-docx__nav-btn",
|
|
461
|
+
children: /* @__PURE__ */ jsx(ChevronRight, { className: "ov-docx__nav-btn-icon", strokeWidth: 2 })
|
|
462
|
+
}
|
|
463
|
+
)
|
|
464
|
+
] })
|
|
465
|
+
] });
|
|
466
|
+
}
|
|
467
|
+
export {
|
|
468
|
+
DocxViewer
|
|
469
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
function LogoIcon({ size = 24 }) {
|
|
3
|
+
return /* @__PURE__ */ jsxs(
|
|
4
|
+
"svg",
|
|
5
|
+
{
|
|
6
|
+
width: size,
|
|
7
|
+
height: size,
|
|
8
|
+
viewBox: "0 0 24 24",
|
|
9
|
+
fill: "none",
|
|
10
|
+
"aria-hidden": "true",
|
|
11
|
+
children: [
|
|
12
|
+
/* @__PURE__ */ jsx("rect", { x: "2", y: "2", width: "20", height: "3", rx: "1.5", fill: "white" }),
|
|
13
|
+
/* @__PURE__ */ jsx("rect", { x: "2", y: "7", width: "14", height: "2", rx: "1", fill: "white", fillOpacity: "0.6" }),
|
|
14
|
+
/* @__PURE__ */ jsx("rect", { x: "2", y: "12", width: "6", height: "4", rx: "1", fill: "white" }),
|
|
15
|
+
/* @__PURE__ */ jsx("rect", { x: "9", y: "12", width: "6", height: "4", rx: "1", fill: "white" }),
|
|
16
|
+
/* @__PURE__ */ jsx("rect", { x: "16", y: "12", width: "6", height: "4", rx: "1", fill: "white" }),
|
|
17
|
+
/* @__PURE__ */ jsx("rect", { x: "2", y: "18", width: "6", height: "4", rx: "1", fill: "white", fillOpacity: "0.6" }),
|
|
18
|
+
/* @__PURE__ */ jsx("rect", { x: "9", y: "18", width: "6", height: "4", rx: "1", fill: "white", fillOpacity: "0.6" }),
|
|
19
|
+
/* @__PURE__ */ jsx("rect", { x: "16", y: "18", width: "6", height: "4", rx: "1", fill: "white", fillOpacity: "0.6" })
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
export {
|
|
25
|
+
LogoIcon as L
|
|
26
|
+
};
|