@wapri/preview 0.0.0-stage → 0.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.
- package/LICENSE +21 -0
- package/README.md +78 -2
- package/dist/constant/index.d.ts +51 -0
- package/dist/constant/index.js +58 -0
- package/dist/core/context.d.ts +21 -0
- package/dist/core/context.js +78 -0
- package/dist/core/lazy.d.ts +43 -0
- package/dist/core/lazy.js +175 -0
- package/dist/core/source.d.ts +20 -0
- package/dist/core/source.js +118 -0
- package/dist/core/style.d.ts +8 -0
- package/dist/core/style.js +20 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +17 -0
- package/dist/pdf/index.d.ts +8 -0
- package/dist/pdf/index.js +364 -0
- package/dist/pdf/load.d.ts +15 -0
- package/dist/pdf/load.js +78 -0
- package/dist/previewFile.d.ts +18 -0
- package/dist/previewFile.js +54 -0
- package/dist/text/index.d.ts +8 -0
- package/dist/text/index.js +118 -0
- package/dist/types/common.d.ts +68 -0
- package/dist/types/common.js +1 -0
- package/dist/types/pdf.d.ts +23 -0
- package/dist/types/pdf.js +1 -0
- package/dist/types/text.d.ts +14 -0
- package/dist/types/text.js +1 -0
- package/dist/types/word.d.ts +57 -0
- package/dist/types/word.js +1 -0
- package/dist/types/xlsx.d.ts +45 -0
- package/dist/types/xlsx.js +1 -0
- package/dist/vendor/pdf.worker.min.mjs +21 -0
- package/dist/vendor/xlsx.full.min.js +22 -0
- package/dist/word/font.d.ts +20 -0
- package/dist/word/font.js +132 -0
- package/dist/word/index.d.ts +8 -0
- package/dist/word/index.js +237 -0
- package/dist/word/layout.d.ts +56 -0
- package/dist/word/layout.js +365 -0
- package/dist/word/parse.d.ts +6 -0
- package/dist/word/parse.js +315 -0
- package/dist/word/render.d.ts +17 -0
- package/dist/word/render.js +210 -0
- package/dist/xlsx/index.d.ts +8 -0
- package/dist/xlsx/index.js +195 -0
- package/dist/xlsx/parse.d.ts +14 -0
- package/dist/xlsx/parse.js +177 -0
- package/dist/xlsx/render.d.ts +17 -0
- package/dist/xlsx/render.js +144 -0
- package/dist/xlsx/workerSource.d.ts +7 -0
- package/dist/xlsx/workerSource.js +8 -0
- package/package.json +28 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 wapri contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,79 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @wapri/preview
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
面向 H5 的文件预览包:文本、PDF、Word(docx)、Excel(xlsx / xls / csv)。解析与渲染分离,按可视区域懒渲染,支持本地文件与远程地址。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @wapri/preview
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 快速开始
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { previewFile } from '@wapri/preview'
|
|
15
|
+
|
|
16
|
+
const handle = await previewFile(file, {
|
|
17
|
+
container: document.querySelector('#viewer')!,
|
|
18
|
+
pdf: { thumbnails: true },
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
handle.refresh() // 容器尺寸变化后重排
|
|
22
|
+
handle.destroy() // 释放 canvas、worker、objectURL
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
远程文件与自定义请求头:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
const handle = await previewFile(
|
|
29
|
+
{ url: '/files/report.pdf', headers: { Authorization: 'Bearer token' } },
|
|
30
|
+
{ container: document.querySelector('#viewer')! },
|
|
31
|
+
)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`previewFile` 会读取文件头识别类型,再分发到 `previewText` / `previewPdf` / `previewWord` / `previewXlsx`。
|
|
35
|
+
|
|
36
|
+
## API
|
|
37
|
+
|
|
38
|
+
| 方法 | 说明 |
|
|
39
|
+
| --- | --- |
|
|
40
|
+
| `previewFile(input, options)` | 自动识别类型并预览 |
|
|
41
|
+
| `previewText(input, options)` | 文本预览,按行分块懒渲染 |
|
|
42
|
+
| `previewPdf(input, options)` | PDF 预览,pdf.js + Canvas,支持缩略图 |
|
|
43
|
+
| `previewWord(input, options)` | Word 预览,mammoth 解析 + opentype 排版 + Canvas 分页 |
|
|
44
|
+
| `previewXlsx(input, options)` | Excel 预览,SheetJS 解析 + 虚拟表格 |
|
|
45
|
+
| `configurePdfjs(config)` | 配置 pdf.js worker、CMap、标准字体地址 |
|
|
46
|
+
|
|
47
|
+
`input` 支持 `File` / `Blob` / `ArrayBuffer` / `ArrayBufferView` / URL 字符串 / `{ url, fileName?, headers? }`。
|
|
48
|
+
|
|
49
|
+
通用 options:`container`、`className`、`lazy`、`overscan`、`theme`、`onProgress`。
|
|
50
|
+
|
|
51
|
+
## 渲染策略
|
|
52
|
+
|
|
53
|
+
| 类型 | 解析 | 渲染 | 懒渲染粒度 |
|
|
54
|
+
| --- | --- | --- | --- |
|
|
55
|
+
| 文本 | 主线程按行分块 | DOM | 80 行一块 |
|
|
56
|
+
| PDF | pdf.js worker | Canvas | 每页 |
|
|
57
|
+
| Word | mammoth + opentype 排版 | Canvas | 每页 |
|
|
58
|
+
| XLSX | SheetJS(worker 优先) | DOM 绝对定位单元格 | 可视行列 |
|
|
59
|
+
|
|
60
|
+
## 资源与配置
|
|
61
|
+
|
|
62
|
+
- PDF:必须调用 `configurePdfjs({ workerSrc })` 指定可访问的 `pdf.worker.min.mjs` 地址。打包器会把 `import.meta.url` 改写成构建路径(webpack 下是 `file://`),包内相对路径无法可靠定位 worker;文档站 demo 使用 `/pdf.worker.min.mjs`,业务按自己的部署位置配置。中文 PDF 建议同时配置 `cMapUrl`。
|
|
63
|
+
- XLSX:默认尝试用包内 `dist/vendor/xlsx.full.min.js` 在 worker 中解析,地址不可用时自动回退主线程;也可以用 `workerScript` 显式指定自己的 UMD 地址。
|
|
64
|
+
- Word:传入 `fonts`(TTF / OTF 的 `ArrayBuffer[]`)可用 opentype.js 精确测量并绘制,不传时回退 Canvas 字体。
|
|
65
|
+
|
|
66
|
+
## 依赖
|
|
67
|
+
|
|
68
|
+
- `@wapri/gbase`:Worker 通道、Object URL 管理、文件读取、类型嗅探、可视区域观察
|
|
69
|
+
- `pdfjs-dist`、`mammoth`、`xlsx`、`opentype.js`
|
|
70
|
+
|
|
71
|
+
## 注意
|
|
72
|
+
|
|
73
|
+
- 预览只读取用户选择的文件,不上传、不落盘。
|
|
74
|
+
- 大文件解析会占用主线程内存,`maxCells`(XLSX)等上限参数可按业务调整。
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
## 在线文档
|
|
78
|
+
|
|
79
|
+
https://sdmu-gaoqi.github.io/wapri/previews
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 预览包常量:class 前缀、默认尺寸与分块参数。
|
|
3
|
+
*/
|
|
4
|
+
export declare const PREVIEW_PACKAGE_NAME = "@wapri/preview";
|
|
5
|
+
/** 所有节点 class 统一用它做前缀,便于业务方覆盖样式 */
|
|
6
|
+
export declare const PREVIEW_PREFIX = "wa-preview";
|
|
7
|
+
export declare const PREVIEW_CLASS: {
|
|
8
|
+
readonly root: "wa-preview";
|
|
9
|
+
readonly viewport: "wa-preview__viewport";
|
|
10
|
+
readonly page: "wa-preview__page";
|
|
11
|
+
readonly pageCanvas: "wa-preview__page-canvas";
|
|
12
|
+
readonly pageLabel: "wa-preview__page-label";
|
|
13
|
+
readonly thumbnails: "wa-preview__thumbnails";
|
|
14
|
+
readonly thumbnail: "wa-preview__thumbnail";
|
|
15
|
+
readonly text: "wa-preview__text";
|
|
16
|
+
readonly textChunk: "wa-preview__text-chunk";
|
|
17
|
+
readonly textLine: "wa-preview__text-line";
|
|
18
|
+
readonly sheetTabs: "wa-preview__sheet-tabs";
|
|
19
|
+
readonly sheetTab: "wa-preview__sheet-tab";
|
|
20
|
+
readonly sheetTabActive: "wa-preview__sheet-tab--active";
|
|
21
|
+
readonly grid: "wa-preview__grid";
|
|
22
|
+
readonly gridHeader: "wa-preview__grid-header";
|
|
23
|
+
readonly gridCell: "wa-preview__grid-cell";
|
|
24
|
+
readonly gridRow: "wa-preview__grid-row";
|
|
25
|
+
readonly empty: "wa-preview__empty";
|
|
26
|
+
readonly error: "wa-preview__error";
|
|
27
|
+
};
|
|
28
|
+
/** 文本预览默认按 80 行分一块 */
|
|
29
|
+
export declare const TEXT_CHUNK_LINES = 80;
|
|
30
|
+
/** XLSX 默认行列尺寸,单位 px */
|
|
31
|
+
export declare const XLSX_DEFAULT_COL_WIDTH = 100;
|
|
32
|
+
export declare const XLSX_DEFAULT_ROW_HEIGHT = 24;
|
|
33
|
+
/** 单表最多解析单元格数量,避免超大表把内存打爆 */
|
|
34
|
+
export declare const XLSX_MAX_CELLS = 200000;
|
|
35
|
+
/** Word 默认页面为 A4 @96dpi */
|
|
36
|
+
export declare const WORD_PAGE_WIDTH = 794;
|
|
37
|
+
export declare const WORD_PAGE_HEIGHT = 1123;
|
|
38
|
+
export declare const WORD_PAGE_PADDING = 72;
|
|
39
|
+
/** PDF 缩略图宽度 */
|
|
40
|
+
export declare const PDF_THUMBNAIL_WIDTH = 96;
|
|
41
|
+
/** 默认主题 */
|
|
42
|
+
export declare const DEFAULT_THEME: {
|
|
43
|
+
readonly color: "#1f2329";
|
|
44
|
+
readonly mutedColor: "#8f959e";
|
|
45
|
+
readonly background: "#f2f3f5";
|
|
46
|
+
readonly paper: "#ffffff";
|
|
47
|
+
readonly accent: "#3370ff";
|
|
48
|
+
readonly fontFamily: "-apple-system, BlinkMacSystemFont, \"PingFang SC\", \"Hiragino Sans GB\", \"Microsoft YaHei\", sans-serif";
|
|
49
|
+
readonly fontSize: 14;
|
|
50
|
+
readonly lineHeight: 1.7;
|
|
51
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 预览包常量:class 前缀、默认尺寸与分块参数。
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export var PREVIEW_PACKAGE_NAME = '@wapri/preview';
|
|
6
|
+
|
|
7
|
+
/** 所有节点 class 统一用它做前缀,便于业务方覆盖样式 */
|
|
8
|
+
export var PREVIEW_PREFIX = 'wa-preview';
|
|
9
|
+
export var PREVIEW_CLASS = {
|
|
10
|
+
root: PREVIEW_PREFIX,
|
|
11
|
+
viewport: "".concat(PREVIEW_PREFIX, "__viewport"),
|
|
12
|
+
page: "".concat(PREVIEW_PREFIX, "__page"),
|
|
13
|
+
pageCanvas: "".concat(PREVIEW_PREFIX, "__page-canvas"),
|
|
14
|
+
pageLabel: "".concat(PREVIEW_PREFIX, "__page-label"),
|
|
15
|
+
thumbnails: "".concat(PREVIEW_PREFIX, "__thumbnails"),
|
|
16
|
+
thumbnail: "".concat(PREVIEW_PREFIX, "__thumbnail"),
|
|
17
|
+
text: "".concat(PREVIEW_PREFIX, "__text"),
|
|
18
|
+
textChunk: "".concat(PREVIEW_PREFIX, "__text-chunk"),
|
|
19
|
+
textLine: "".concat(PREVIEW_PREFIX, "__text-line"),
|
|
20
|
+
sheetTabs: "".concat(PREVIEW_PREFIX, "__sheet-tabs"),
|
|
21
|
+
sheetTab: "".concat(PREVIEW_PREFIX, "__sheet-tab"),
|
|
22
|
+
sheetTabActive: "".concat(PREVIEW_PREFIX, "__sheet-tab--active"),
|
|
23
|
+
grid: "".concat(PREVIEW_PREFIX, "__grid"),
|
|
24
|
+
gridHeader: "".concat(PREVIEW_PREFIX, "__grid-header"),
|
|
25
|
+
gridCell: "".concat(PREVIEW_PREFIX, "__grid-cell"),
|
|
26
|
+
gridRow: "".concat(PREVIEW_PREFIX, "__grid-row"),
|
|
27
|
+
empty: "".concat(PREVIEW_PREFIX, "__empty"),
|
|
28
|
+
error: "".concat(PREVIEW_PREFIX, "__error")
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/** 文本预览默认按 80 行分一块 */
|
|
32
|
+
export var TEXT_CHUNK_LINES = 80;
|
|
33
|
+
|
|
34
|
+
/** XLSX 默认行列尺寸,单位 px */
|
|
35
|
+
export var XLSX_DEFAULT_COL_WIDTH = 100;
|
|
36
|
+
export var XLSX_DEFAULT_ROW_HEIGHT = 24;
|
|
37
|
+
/** 单表最多解析单元格数量,避免超大表把内存打爆 */
|
|
38
|
+
export var XLSX_MAX_CELLS = 200000;
|
|
39
|
+
|
|
40
|
+
/** Word 默认页面为 A4 @96dpi */
|
|
41
|
+
export var WORD_PAGE_WIDTH = 794;
|
|
42
|
+
export var WORD_PAGE_HEIGHT = 1123;
|
|
43
|
+
export var WORD_PAGE_PADDING = 72;
|
|
44
|
+
|
|
45
|
+
/** PDF 缩略图宽度 */
|
|
46
|
+
export var PDF_THUMBNAIL_WIDTH = 96;
|
|
47
|
+
|
|
48
|
+
/** 默认主题 */
|
|
49
|
+
export var DEFAULT_THEME = {
|
|
50
|
+
color: '#1f2329',
|
|
51
|
+
mutedColor: '#8f959e',
|
|
52
|
+
background: '#f2f3f5',
|
|
53
|
+
paper: '#ffffff',
|
|
54
|
+
accent: '#3370ff',
|
|
55
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif',
|
|
56
|
+
fontSize: 14,
|
|
57
|
+
lineHeight: 1.7
|
|
58
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 预览上下文:解析容器、合并主题、注入样式、统一销毁。
|
|
3
|
+
*/
|
|
4
|
+
import type { BasePreviewOptions, PreviewProgress, PreviewTheme } from '../types/common';
|
|
5
|
+
export interface PreviewContext {
|
|
6
|
+
readonly container: HTMLElement;
|
|
7
|
+
readonly root: HTMLElement;
|
|
8
|
+
readonly viewport: HTMLElement;
|
|
9
|
+
readonly theme: Required<PreviewTheme>;
|
|
10
|
+
readonly options: BasePreviewOptions;
|
|
11
|
+
/** 注册销毁回调,按注册顺序逆序执行 */
|
|
12
|
+
onDestroy(callback: () => void): void;
|
|
13
|
+
/** 汇报进度 */
|
|
14
|
+
emitProgress(progress: PreviewProgress): void;
|
|
15
|
+
/** 渲染错误态 */
|
|
16
|
+
fail(error: unknown): void;
|
|
17
|
+
/** 释放容器与全部副作用 */
|
|
18
|
+
destroy(): void;
|
|
19
|
+
}
|
|
20
|
+
export declare const resolveContainer: (target: HTMLElement | string) => HTMLElement;
|
|
21
|
+
export declare const createPreviewContext: (options: BasePreviewOptions) => PreviewContext;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
|
|
2
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
3
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
4
|
+
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
5
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
|
|
6
|
+
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
7
|
+
/**
|
|
8
|
+
* 预览上下文:解析容器、合并主题、注入样式、统一销毁。
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { DEFAULT_THEME, PREVIEW_CLASS } from "../constant";
|
|
12
|
+
import { ensurePreviewStyles } from "./style";
|
|
13
|
+
export var resolveContainer = function resolveContainer(target) {
|
|
14
|
+
if (typeof target !== 'string') return target;
|
|
15
|
+
var element = document.querySelector(target);
|
|
16
|
+
if (!element) {
|
|
17
|
+
throw new Error("[preview] \u627E\u4E0D\u5230\u5BB9\u5668\uFF1A".concat(target));
|
|
18
|
+
}
|
|
19
|
+
return element;
|
|
20
|
+
};
|
|
21
|
+
var toErrorMessage = function toErrorMessage(error) {
|
|
22
|
+
if (error instanceof Error) return error.message;
|
|
23
|
+
if (typeof error === 'string') return error;
|
|
24
|
+
return '预览失败';
|
|
25
|
+
};
|
|
26
|
+
export var createPreviewContext = function createPreviewContext(options) {
|
|
27
|
+
ensurePreviewStyles();
|
|
28
|
+
var container = resolveContainer(options.container);
|
|
29
|
+
var theme = _objectSpread(_objectSpread({}, DEFAULT_THEME), options.theme);
|
|
30
|
+
var callbacks = [];
|
|
31
|
+
var root = document.createElement('div');
|
|
32
|
+
root.className = [PREVIEW_CLASS.root, options.className].filter(Boolean).join(' ');
|
|
33
|
+
root.style.setProperty('--wa-preview-color', theme.color);
|
|
34
|
+
root.style.setProperty('--wa-preview-muted', theme.mutedColor);
|
|
35
|
+
root.style.setProperty('--wa-preview-background', theme.background);
|
|
36
|
+
root.style.setProperty('--wa-preview-paper', theme.paper);
|
|
37
|
+
root.style.setProperty('--wa-preview-accent', theme.accent);
|
|
38
|
+
root.style.setProperty('--wa-preview-font-family', theme.fontFamily);
|
|
39
|
+
root.style.setProperty('--wa-preview-font-size', "".concat(theme.fontSize, "px"));
|
|
40
|
+
root.style.setProperty('--wa-preview-line-height', String(theme.lineHeight));
|
|
41
|
+
var viewport = document.createElement('div');
|
|
42
|
+
viewport.className = PREVIEW_CLASS.viewport;
|
|
43
|
+
root.appendChild(viewport);
|
|
44
|
+
container.appendChild(root);
|
|
45
|
+
var destroyed = false;
|
|
46
|
+
var context = {
|
|
47
|
+
container: container,
|
|
48
|
+
root: root,
|
|
49
|
+
viewport: viewport,
|
|
50
|
+
theme: theme,
|
|
51
|
+
options: options,
|
|
52
|
+
onDestroy: function onDestroy(callback) {
|
|
53
|
+
callbacks.push(callback);
|
|
54
|
+
},
|
|
55
|
+
emitProgress: function emitProgress(progress) {
|
|
56
|
+
var _options$onProgress;
|
|
57
|
+
(_options$onProgress = options.onProgress) === null || _options$onProgress === void 0 || _options$onProgress.call(options, progress);
|
|
58
|
+
},
|
|
59
|
+
fail: function fail(error) {
|
|
60
|
+
var message = toErrorMessage(error);
|
|
61
|
+
var box = document.createElement('div');
|
|
62
|
+
box.className = PREVIEW_CLASS.error;
|
|
63
|
+
box.textContent = message;
|
|
64
|
+
viewport.innerHTML = '';
|
|
65
|
+
viewport.appendChild(box);
|
|
66
|
+
},
|
|
67
|
+
destroy: function destroy() {
|
|
68
|
+
if (destroyed) return;
|
|
69
|
+
destroyed = true;
|
|
70
|
+
while (callbacks.length > 0) {
|
|
71
|
+
var _callbacks$pop;
|
|
72
|
+
(_callbacks$pop = callbacks.pop()) === null || _callbacks$pop === void 0 || _callbacks$pop();
|
|
73
|
+
}
|
|
74
|
+
root.remove();
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
return context;
|
|
78
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 可视区域懒渲染池。
|
|
3
|
+
*
|
|
4
|
+
* 每个可渲染单元(文本块、PDF 页、表格行、Word 页)注册进来,
|
|
5
|
+
* 进入视口才执行 render,离开视口按 keepAlive 决定是否调用 release 释放位图。
|
|
6
|
+
*/
|
|
7
|
+
export interface LazyTarget {
|
|
8
|
+
element: HTMLElement;
|
|
9
|
+
render: () => void | Promise<void>;
|
|
10
|
+
/** 离开视口时释放,例如把 canvas 宽高置 0 */
|
|
11
|
+
release?: () => void;
|
|
12
|
+
}
|
|
13
|
+
export interface LazyPoolOptions {
|
|
14
|
+
/** IntersectionObserver 的 root,通常是滚动容器 */
|
|
15
|
+
root?: HTMLElement | null;
|
|
16
|
+
/** 提前渲染的边距,默认 1 屏 */
|
|
17
|
+
rootMargin?: string;
|
|
18
|
+
/** 离开视口是否保留渲染结果,默认 true */
|
|
19
|
+
keepAlive?: boolean;
|
|
20
|
+
/** 同时渲染的最大数量,默认 2 */
|
|
21
|
+
maxConcurrent?: number;
|
|
22
|
+
}
|
|
23
|
+
export declare class LazyRenderPool {
|
|
24
|
+
private readonly observer?;
|
|
25
|
+
private readonly entries;
|
|
26
|
+
private readonly queue;
|
|
27
|
+
private readonly keepAlive;
|
|
28
|
+
private readonly maxConcurrent;
|
|
29
|
+
private running;
|
|
30
|
+
private destroyed;
|
|
31
|
+
constructor(options?: LazyPoolOptions);
|
|
32
|
+
get renderedCount(): number;
|
|
33
|
+
/** 注册一个懒渲染单元,返回取消注册函数 */
|
|
34
|
+
register(target: LazyTarget): () => void;
|
|
35
|
+
/** 立即渲染全部单元,用于打印、导出或关闭懒加载 */
|
|
36
|
+
renderAll(): void;
|
|
37
|
+
/** 丢弃已渲染结果并重新观察,用于容器尺寸变化后重排 */
|
|
38
|
+
reset(): void;
|
|
39
|
+
destroy(): void;
|
|
40
|
+
private readonly handleIntersect;
|
|
41
|
+
private enqueue;
|
|
42
|
+
private pump;
|
|
43
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
|
|
2
|
+
function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
|
|
3
|
+
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
|
|
4
|
+
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
|
|
5
|
+
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
6
|
+
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
|
|
7
|
+
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
|
|
8
|
+
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
9
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
|
|
10
|
+
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
11
|
+
/**
|
|
12
|
+
* 可视区域懒渲染池。
|
|
13
|
+
*
|
|
14
|
+
* 每个可渲染单元(文本块、PDF 页、表格行、Word 页)注册进来,
|
|
15
|
+
* 进入视口才执行 render,离开视口按 keepAlive 决定是否调用 release 释放位图。
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export var LazyRenderPool = /*#__PURE__*/function () {
|
|
19
|
+
function LazyRenderPool() {
|
|
20
|
+
var _this = this,
|
|
21
|
+
_options$keepAlive,
|
|
22
|
+
_options$maxConcurren;
|
|
23
|
+
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
24
|
+
_classCallCheck(this, LazyRenderPool);
|
|
25
|
+
_defineProperty(this, "observer", void 0);
|
|
26
|
+
_defineProperty(this, "entries", new Map());
|
|
27
|
+
_defineProperty(this, "queue", []);
|
|
28
|
+
_defineProperty(this, "keepAlive", void 0);
|
|
29
|
+
_defineProperty(this, "maxConcurrent", void 0);
|
|
30
|
+
_defineProperty(this, "running", 0);
|
|
31
|
+
_defineProperty(this, "destroyed", false);
|
|
32
|
+
_defineProperty(this, "handleIntersect", function (records) {
|
|
33
|
+
var _iterator = _createForOfIteratorHelper(records),
|
|
34
|
+
_step;
|
|
35
|
+
try {
|
|
36
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
37
|
+
var record = _step.value;
|
|
38
|
+
var entry = _this.entries.get(record.target);
|
|
39
|
+
if (!entry) continue;
|
|
40
|
+
if (record.isIntersecting) {
|
|
41
|
+
_this.enqueue(entry);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (!_this.keepAlive && entry.rendered) {
|
|
45
|
+
var _entry$target$release, _entry$target;
|
|
46
|
+
(_entry$target$release = (_entry$target = entry.target).release) === null || _entry$target$release === void 0 || _entry$target$release.call(_entry$target);
|
|
47
|
+
entry.rendered = false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
} catch (err) {
|
|
51
|
+
_iterator.e(err);
|
|
52
|
+
} finally {
|
|
53
|
+
_iterator.f();
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
this.keepAlive = (_options$keepAlive = options.keepAlive) !== null && _options$keepAlive !== void 0 ? _options$keepAlive : true;
|
|
57
|
+
this.maxConcurrent = Math.max(1, (_options$maxConcurren = options.maxConcurrent) !== null && _options$maxConcurren !== void 0 ? _options$maxConcurren : 2);
|
|
58
|
+
if (typeof IntersectionObserver !== 'undefined') {
|
|
59
|
+
var _options$root, _options$rootMargin;
|
|
60
|
+
this.observer = new IntersectionObserver(this.handleIntersect, {
|
|
61
|
+
root: (_options$root = options.root) !== null && _options$root !== void 0 ? _options$root : null,
|
|
62
|
+
rootMargin: (_options$rootMargin = options.rootMargin) !== null && _options$rootMargin !== void 0 ? _options$rootMargin : '100% 0px',
|
|
63
|
+
threshold: 0.01
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
_createClass(LazyRenderPool, [{
|
|
68
|
+
key: "renderedCount",
|
|
69
|
+
get: function get() {
|
|
70
|
+
var count = 0;
|
|
71
|
+
this.entries.forEach(function (entry) {
|
|
72
|
+
if (entry.rendered) count += 1;
|
|
73
|
+
});
|
|
74
|
+
return count;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** 注册一个懒渲染单元,返回取消注册函数 */
|
|
78
|
+
}, {
|
|
79
|
+
key: "register",
|
|
80
|
+
value: function register(target) {
|
|
81
|
+
var _this2 = this;
|
|
82
|
+
var entry = {
|
|
83
|
+
target: target,
|
|
84
|
+
rendered: false,
|
|
85
|
+
queued: false
|
|
86
|
+
};
|
|
87
|
+
this.entries.set(target.element, entry);
|
|
88
|
+
if (!this.observer) {
|
|
89
|
+
// 没有 IntersectionObserver 时直接全量渲染
|
|
90
|
+
this.enqueue(entry);
|
|
91
|
+
return function () {
|
|
92
|
+
return _this2.entries.delete(target.element);
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
this.observer.observe(target.element);
|
|
96
|
+
return function () {
|
|
97
|
+
var _this2$observer;
|
|
98
|
+
(_this2$observer = _this2.observer) === null || _this2$observer === void 0 || _this2$observer.unobserve(target.element);
|
|
99
|
+
_this2.entries.delete(target.element);
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** 立即渲染全部单元,用于打印、导出或关闭懒加载 */
|
|
104
|
+
}, {
|
|
105
|
+
key: "renderAll",
|
|
106
|
+
value: function renderAll() {
|
|
107
|
+
var _this3 = this;
|
|
108
|
+
this.entries.forEach(function (entry) {
|
|
109
|
+
return _this3.enqueue(entry);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** 丢弃已渲染结果并重新观察,用于容器尺寸变化后重排 */
|
|
114
|
+
}, {
|
|
115
|
+
key: "reset",
|
|
116
|
+
value: function reset() {
|
|
117
|
+
var _this4 = this;
|
|
118
|
+
this.queue.length = 0;
|
|
119
|
+
this.entries.forEach(function (entry) {
|
|
120
|
+
var _entry$target$release2, _entry$target2;
|
|
121
|
+
entry.rendered = false;
|
|
122
|
+
entry.queued = false;
|
|
123
|
+
(_entry$target$release2 = (_entry$target2 = entry.target).release) === null || _entry$target$release2 === void 0 || _entry$target$release2.call(_entry$target2);
|
|
124
|
+
if (!_this4.observer) {
|
|
125
|
+
_this4.enqueue(entry);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 重新 observe 会重新触发一次 intersect 回调
|
|
130
|
+
_this4.observer.unobserve(entry.target.element);
|
|
131
|
+
_this4.observer.observe(entry.target.element);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}, {
|
|
135
|
+
key: "destroy",
|
|
136
|
+
value: function destroy() {
|
|
137
|
+
var _this$observer;
|
|
138
|
+
this.destroyed = true;
|
|
139
|
+
(_this$observer = this.observer) === null || _this$observer === void 0 || _this$observer.disconnect();
|
|
140
|
+
this.entries.clear();
|
|
141
|
+
this.queue.length = 0;
|
|
142
|
+
}
|
|
143
|
+
}, {
|
|
144
|
+
key: "enqueue",
|
|
145
|
+
value: function enqueue(entry) {
|
|
146
|
+
if (entry.rendered || entry.queued) return;
|
|
147
|
+
entry.queued = true;
|
|
148
|
+
this.queue.push(entry);
|
|
149
|
+
this.pump();
|
|
150
|
+
}
|
|
151
|
+
}, {
|
|
152
|
+
key: "pump",
|
|
153
|
+
value: function pump() {
|
|
154
|
+
var _this5 = this;
|
|
155
|
+
var _loop = function _loop() {
|
|
156
|
+
var entry = _this5.queue.shift();
|
|
157
|
+
if (!entry || entry.rendered) return 1; // continue
|
|
158
|
+
_this5.running += 1;
|
|
159
|
+
Promise.resolve(entry.target.render()).then(function () {
|
|
160
|
+
entry.rendered = true;
|
|
161
|
+
}).catch(function (error) {
|
|
162
|
+
console.error('[preview] 渲染单元失败', error);
|
|
163
|
+
}).finally(function () {
|
|
164
|
+
entry.queued = false;
|
|
165
|
+
_this5.running -= 1;
|
|
166
|
+
_this5.pump();
|
|
167
|
+
});
|
|
168
|
+
};
|
|
169
|
+
while (!this.destroyed && this.running < this.maxConcurrent && this.queue.length > 0) {
|
|
170
|
+
if (_loop()) continue;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}]);
|
|
174
|
+
return LazyRenderPool;
|
|
175
|
+
}();
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 输入来源解析:把 File / Blob / ArrayBuffer / URL 统一成可识别的字节流。
|
|
3
|
+
*/
|
|
4
|
+
import type { PreviewInput } from '../types/common';
|
|
5
|
+
import type { FileCategory } from '@wapri/gbase';
|
|
6
|
+
export interface ResolvedSource {
|
|
7
|
+
/** 预览能力分类 */
|
|
8
|
+
kind: 'text' | 'pdf' | 'word' | 'xlsx';
|
|
9
|
+
/** 原始识别分类,便于调用方判断 doc/ppt 等不支持的类型 */
|
|
10
|
+
category: FileCategory;
|
|
11
|
+
name: string;
|
|
12
|
+
extension: string;
|
|
13
|
+
mime: string;
|
|
14
|
+
buffer: ArrayBuffer;
|
|
15
|
+
}
|
|
16
|
+
export declare class PreviewUnsupportedError extends Error {
|
|
17
|
+
constructor(message: string);
|
|
18
|
+
}
|
|
19
|
+
/** 解析来源并读取整个文件;远程文件支持自定义请求头 */
|
|
20
|
+
export declare const resolvePreviewSource: (input: PreviewInput) => Promise<ResolvedSource>;
|