@basic-genomics/hivtrace-viz 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.
Files changed (53) hide show
  1. package/README.md +161 -0
  2. package/dist/060b2710bdbbe3dfe48b.svg +288 -0
  3. package/dist/1815e00441357e01619e.ttf +0 -0
  4. package/dist/2463b90d9a316e4e5294.woff2 +0 -0
  5. package/dist/2582b0e4bcf85eceead0.ttf +0 -0
  6. package/dist/4692b9ec53fd5972caa2.ttf +0 -0
  7. package/dist/5be1347c682810f199c7.eot +0 -0
  8. package/dist/82b1212e45a2bc35dd73.woff +0 -0
  9. package/dist/89999bdf5d835c012025.woff2 +0 -0
  10. package/dist/914997e1bdfc990d0897.ttf +0 -0
  11. package/dist/be810be3a3e14c682a25.woff2 +0 -0
  12. package/dist/c210719e60948b211a12.woff2 +0 -0
  13. package/dist/da94ef451f4969af06e6.ttf +0 -0
  14. package/dist/ea8f94e1d22e0d35ccd4.woff2 +0 -0
  15. package/dist/embed/060b2710bdbbe3dfe48b.svg +288 -0
  16. package/dist/embed/1815e00441357e01619e.ttf +0 -0
  17. package/dist/embed/2463b90d9a316e4e5294.woff2 +0 -0
  18. package/dist/embed/2582b0e4bcf85eceead0.ttf +0 -0
  19. package/dist/embed/4692b9ec53fd5972caa2.ttf +0 -0
  20. package/dist/embed/5be1347c682810f199c7.eot +0 -0
  21. package/dist/embed/82b1212e45a2bc35dd73.woff +0 -0
  22. package/dist/embed/89999bdf5d835c012025.woff2 +0 -0
  23. package/dist/embed/914997e1bdfc990d0897.ttf +0 -0
  24. package/dist/embed/be810be3a3e14c682a25.woff2 +0 -0
  25. package/dist/embed/c210719e60948b211a12.woff2 +0 -0
  26. package/dist/embed/da94ef451f4969af06e6.ttf +0 -0
  27. package/dist/embed/ea8f94e1d22e0d35ccd4.woff2 +0 -0
  28. package/dist/embed/hivtrace.css +19152 -0
  29. package/dist/embed/hivtrace.css.map +1 -0
  30. package/dist/embed/hivtrace.js +3 -0
  31. package/dist/embed/hivtrace.js.LICENSE.txt +38 -0
  32. package/dist/embed/hivtrace.js.map +1 -0
  33. package/dist/embed/index.html +1318 -0
  34. package/dist/embed/locales/en-US.json +168 -0
  35. package/dist/embed/locales/zh-CN.json +168 -0
  36. package/dist/hivtrace.css +19152 -0
  37. package/dist/hivtrace.css.map +1 -0
  38. package/dist/hivtrace.js +3 -0
  39. package/dist/hivtrace.js.LICENSE.txt +38 -0
  40. package/dist/hivtrace.js.map +1 -0
  41. package/dist/react/HivtraceViz.d.ts +8 -0
  42. package/dist/react/HivtraceViz.d.ts.map +1 -0
  43. package/dist/react/HivtraceViz.js +169 -0
  44. package/dist/react/index.d.ts +3 -0
  45. package/dist/react/index.d.ts.map +1 -0
  46. package/dist/react/index.js +1 -0
  47. package/dist/react/types.d.ts +82 -0
  48. package/dist/react/types.d.ts.map +1 -0
  49. package/dist/react/types.js +5 -0
  50. package/dist/vite-plugin/index.d.ts +13 -0
  51. package/dist/vite-plugin/index.d.ts.map +1 -0
  52. package/dist/vite-plugin/index.js +83 -0
  53. package/package.json +106 -0
@@ -0,0 +1,8 @@
1
+ /**
2
+ * HivtraceViz React 组件
3
+ * 封装 iframe 和 postMessage 通信,提供简洁的 React 接口
4
+ */
5
+ import type { HivtraceVizProps } from './types';
6
+ export declare function HivtraceViz({ data, options, isDataFetched, showLoadingOnUpdate, onTrackCluster, onFullscreenChange, onReady, onError, error, className, style, emptyState, loadingState, }: HivtraceVizProps): import("react/jsx-runtime").JSX.Element;
7
+ export default HivtraceViz;
8
+ //# sourceMappingURL=HivtraceViz.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HivtraceViz.d.ts","sourceRoot":"","sources":["../../src/react/HivtraceViz.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAA6C,MAAM,SAAS,CAAC;AA2F3F,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,OAAO,EACP,aAAa,EACb,mBAA0B,EAC1B,cAAc,EACd,kBAAkB,EAClB,OAAO,EACP,OAAO,EACP,KAAK,EACL,SAAS,EACT,KAAK,EACL,UAAU,EACV,YAAY,GACb,EAAE,gBAAgB,2CAiKlB;AAED,eAAe,WAAW,CAAC"}
@@ -0,0 +1,169 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * HivtraceViz React 组件
4
+ * 封装 iframe 和 postMessage 通信,提供简洁的 React 接口
5
+ */
6
+ import { useEffect, useRef, useState, useCallback } from 'react';
7
+ // iframe 静态资源路径(由 Vite 插件提供)
8
+ const IFRAME_SRC = '/hivtrace-viz/index.html';
9
+ // 检查网络数据是否有效
10
+ function hasValidNetworkData(data) {
11
+ return Boolean(data?.trace_results?.Nodes &&
12
+ Object.keys(data.trace_results.Nodes).length > 0);
13
+ }
14
+ // 默认空状态组件
15
+ function DefaultEmptyState() {
16
+ return (_jsxs("div", { style: {
17
+ display: 'flex',
18
+ flexDirection: 'column',
19
+ alignItems: 'center',
20
+ justifyContent: 'center',
21
+ height: '100%',
22
+ gap: 16,
23
+ padding: 32,
24
+ textAlign: 'center',
25
+ }, children: [_jsxs("svg", { width: "48", height: "48", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", style: { color: '#9ca3af' }, children: [_jsx("circle", { cx: "12", cy: "12", r: "10" }), _jsx("line", { x1: "12", y1: "8", x2: "12", y2: "12" }), _jsx("line", { x1: "12", y1: "16", x2: "12.01", y2: "16" })] }), _jsxs("div", { children: [_jsx("h3", { style: { fontSize: 18, fontWeight: 500, margin: 0 }, children: "\u5206\u6790\u7ED3\u679C\u4E0D\u5305\u542B\u6709\u6548\u5206\u5B50\u7F51\u7EDC" }), _jsx("p", { style: { fontSize: 14, color: '#6b7280', margin: '8px 0 0' }, children: "\u65E0\u6CD5\u8FDB\u884C\u53EF\u89C6\u5316" })] })] }));
26
+ }
27
+ // 默认加载状态组件
28
+ function DefaultLoadingState({ message }) {
29
+ return (_jsx("div", { style: {
30
+ position: 'absolute',
31
+ inset: 0,
32
+ zIndex: 10,
33
+ display: 'flex',
34
+ alignItems: 'center',
35
+ justifyContent: 'center',
36
+ backgroundColor: 'rgba(255, 255, 255, 0.5)',
37
+ backdropFilter: 'blur(4px)',
38
+ }, children: _jsxs("div", { style: { textAlign: 'center' }, children: [_jsx("div", { style: {
39
+ width: 48,
40
+ height: 48,
41
+ borderRadius: '50%',
42
+ backgroundColor: '#e5e7eb',
43
+ margin: '0 auto 16px',
44
+ animation: 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite',
45
+ } }), _jsx("p", { style: { color: '#6b7280' }, children: message })] }) }));
46
+ }
47
+ export function HivtraceViz({ data, options, isDataFetched, showLoadingOnUpdate = true, onTrackCluster, onFullscreenChange, onReady, onError, error, className, style, emptyState, loadingState, }) {
48
+ const iframeRef = useRef(null);
49
+ const [internalLoadingState, setInternalLoadingState] = useState('initial');
50
+ const [isIframeReady, setIsIframeReady] = useState(false);
51
+ const previousDataRef = useRef(data);
52
+ // 智能推断数据获取状态
53
+ const effectiveIsDataFetched = isDataFetched !== undefined
54
+ ? isDataFetched
55
+ : data !== undefined;
56
+ // 发送数据到 iframe
57
+ const sendData = useCallback((type) => {
58
+ const iframe = iframeRef.current;
59
+ if (!iframe?.contentWindow || !data)
60
+ return;
61
+ iframe.contentWindow.postMessage({
62
+ type,
63
+ graphData: data,
64
+ options,
65
+ }, '*');
66
+ }, [data, options]);
67
+ // 发送错误到 iframe
68
+ const sendError = useCallback((message) => {
69
+ const iframe = iframeRef.current;
70
+ if (!iframe?.contentWindow)
71
+ return;
72
+ iframe.contentWindow.postMessage({
73
+ type: 'HIVTRACE_ERROR',
74
+ message,
75
+ }, '*');
76
+ }, []);
77
+ // 处理 iframe 消息
78
+ useEffect(() => {
79
+ const handleMessage = (event) => {
80
+ const iframe = iframeRef.current;
81
+ if (!iframe || event.source !== iframe.contentWindow)
82
+ return;
83
+ const msg = event.data;
84
+ if (!msg?.type)
85
+ return;
86
+ switch (msg.type) {
87
+ case 'HIVTRACE_INIT_READY':
88
+ setInternalLoadingState('frameReady');
89
+ setIsIframeReady(true);
90
+ break;
91
+ case 'HIVTRACE_READY':
92
+ setInternalLoadingState('rendered');
93
+ onReady?.();
94
+ break;
95
+ case 'TRACK_CLUSTER':
96
+ if (msg.clusterInfo && msg.allData) {
97
+ onTrackCluster?.(msg.clusterInfo, msg.allData);
98
+ }
99
+ break;
100
+ case 'FULLSCREEN_CHANGE':
101
+ if (typeof msg.isFullscreen === 'boolean') {
102
+ onFullscreenChange?.(msg.isFullscreen);
103
+ }
104
+ break;
105
+ case 'ERROR':
106
+ onError?.(msg.message || 'Unknown error');
107
+ break;
108
+ }
109
+ };
110
+ window.addEventListener('message', handleMessage);
111
+ return () => window.removeEventListener('message', handleMessage);
112
+ }, [onTrackCluster, onFullscreenChange, onReady, onError]);
113
+ // iframe 加载完成时发送初始数据或错误
114
+ useEffect(() => {
115
+ if (isIframeReady && internalLoadingState === 'frameReady') {
116
+ if (error) {
117
+ // 如果有错误,发送错误到 iframe
118
+ sendError(error);
119
+ }
120
+ else if (data) {
121
+ // 否则发送数据
122
+ sendData('HIVTRACE_DATA');
123
+ }
124
+ }
125
+ }, [isIframeReady, data, error, internalLoadingState, sendData, sendError]);
126
+ // 数据变化时发送更新
127
+ useEffect(() => {
128
+ const dataChanged = previousDataRef.current !== data;
129
+ previousDataRef.current = data;
130
+ if (!isIframeReady || internalLoadingState !== 'rendered')
131
+ return;
132
+ if (dataChanged) {
133
+ if (showLoadingOnUpdate) {
134
+ // 显示 loading,完全重新渲染
135
+ setInternalLoadingState('frameReady');
136
+ sendData('HIVTRACE_DATA');
137
+ }
138
+ else {
139
+ // 静默更新,保持用户设置(展开状态等)
140
+ sendData('HIVTRACE_UPDATE_DATA');
141
+ }
142
+ }
143
+ }, [data, isIframeReady, internalLoadingState, showLoadingOnUpdate, sendData]);
144
+ // 数据获取状态重置时,重置内部状态
145
+ useEffect(() => {
146
+ if (!effectiveIsDataFetched) {
147
+ previousDataRef.current = undefined;
148
+ }
149
+ }, [effectiveIsDataFetched]);
150
+ // 如果数据已获取但无有效网络数据,显示空状态
151
+ if (effectiveIsDataFetched && !hasValidNetworkData(data)) {
152
+ return (_jsx("div", { className: className, style: style, children: emptyState ?? _jsx(DefaultEmptyState, {}) }));
153
+ }
154
+ // 计算是否显示加载状态
155
+ const shouldShowLoading = !effectiveIsDataFetched ||
156
+ (effectiveIsDataFetched && hasValidNetworkData(data) && internalLoadingState !== 'rendered');
157
+ // 获取加载消息
158
+ const getLoadingMessage = () => {
159
+ if (!effectiveIsDataFetched) {
160
+ return '正在获取分析数据...';
161
+ }
162
+ if (internalLoadingState === 'initial') {
163
+ return '正在加载可视化框架...';
164
+ }
165
+ return '正在处理数据并渲染可视化...';
166
+ };
167
+ return (_jsxs("div", { className: className, style: { position: 'relative', width: '100%', height: '100%', ...style }, children: [loadingState !== null && shouldShowLoading && (loadingState ?? _jsx(DefaultLoadingState, { message: getLoadingMessage() })), _jsx("iframe", { ref: iframeRef, src: IFRAME_SRC, style: { width: '100%', height: '100%', border: 'none' }, title: "HIV-TRACE \u5206\u5B50\u7F51\u7EDC\u53EF\u89C6\u5316", allow: "fullscreen" })] }));
168
+ }
169
+ export default HivtraceViz;
@@ -0,0 +1,3 @@
1
+ export { HivtraceViz, default } from './HivtraceViz';
2
+ export type { HivtraceVizProps, HivtraceVizData, HivtraceVizOptions, ClusterInfo, NetworkInfo, } from './types';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACrD,YAAY,EACV,gBAAgB,EAChB,eAAe,EACf,kBAAkB,EAClB,WAAW,EACX,WAAW,GACZ,MAAM,SAAS,CAAC"}
@@ -0,0 +1 @@
1
+ export { HivtraceViz, default } from './HivtraceViz';
@@ -0,0 +1,82 @@
1
+ /**
2
+ * HivtraceViz 组件类型定义
3
+ * 覆盖原有 HivtraceViz.tsx 和 ClusterTrackingHivtraceViz.tsx 的所有功能
4
+ */
5
+ export interface HivtraceVizData {
6
+ trace_results?: {
7
+ Nodes: Record<string, unknown>;
8
+ Edges?: unknown[];
9
+ [key: string]: unknown;
10
+ };
11
+ [key: string]: unknown;
12
+ }
13
+ export interface HivtraceVizOptions {
14
+ /** 是否启用簇追踪功能 */
15
+ enableClusterTracking?: boolean;
16
+ /** 指定要展开的簇ID数组 */
17
+ expand?: string[];
18
+ /** 基因距离阈值(如 0.015 表示 1.5%) */
19
+ threshold?: number;
20
+ }
21
+ export interface ClusterInfo {
22
+ cluster_id: string | number;
23
+ node_count: number;
24
+ cluster_size?: number;
25
+ position: {
26
+ x?: number;
27
+ y?: number;
28
+ };
29
+ state: {
30
+ expanded: boolean;
31
+ fixed: boolean;
32
+ };
33
+ node_ids: string[];
34
+ }
35
+ export interface NetworkInfo {
36
+ network_info: {
37
+ total_nodes: number;
38
+ total_edges: number;
39
+ total_clusters: number;
40
+ };
41
+ }
42
+ export interface HivtraceVizProps {
43
+ /** 分析数据 */
44
+ data?: HivtraceVizData;
45
+ /** 配置选项 */
46
+ options?: HivtraceVizOptions;
47
+ /**
48
+ * 显式指定数据获取状态
49
+ * - 如果传入,使用此值决定是否显示"正在获取数据"加载状态
50
+ * - 如果不传,根据 data !== undefined 自动推断
51
+ */
52
+ isDataFetched?: boolean;
53
+ /**
54
+ * 数据更新时是否显示 loading
55
+ * - true: 数据变化时显示 loading(默认)
56
+ * - false: 静默更新,不显示 loading
57
+ */
58
+ showLoadingOnUpdate?: boolean;
59
+ /** 簇追踪回调 */
60
+ onTrackCluster?: (clusterInfo: ClusterInfo, networkInfo: NetworkInfo) => void;
61
+ /** 全屏状态变化回调 */
62
+ onFullscreenChange?: (isFullscreen: boolean) => void;
63
+ /** 可视化渲染完成回调 */
64
+ onReady?: () => void;
65
+ /** 错误回调 */
66
+ onError?: (message: string) => void;
67
+ /**
68
+ * 错误信息
69
+ * - 传入错误消息时,组件会显示错误状态而不是加载态
70
+ * - 可用于任何错误场景(API 失败、数据解析错误、权限错误等)
71
+ */
72
+ error?: string;
73
+ /** 自定义类名 */
74
+ className?: string;
75
+ /** 自定义样式 */
76
+ style?: React.CSSProperties;
77
+ /** 自定义空状态渲染 */
78
+ emptyState?: React.ReactNode;
79
+ /** 自定义加载状态渲染 */
80
+ loadingState?: React.ReactNode;
81
+ }
82
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/react/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,eAAe;IAC9B,aAAa,CAAC,EAAE;QACd,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC/B,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;QAClB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;IACF,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,kBAAkB;IACjC,gBAAgB;IAChB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,kBAAkB;IAClB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,8BAA8B;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE;QAAE,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACrC,KAAK,EAAE;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC;IAC7C,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE;QACZ,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;QACpB,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;CACH;AAED,MAAM,WAAW,gBAAgB;IAC/B,WAAW;IACX,IAAI,CAAC,EAAE,eAAe,CAAC;IAEvB,WAAW;IACX,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAE7B;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B,YAAY;IACZ,cAAc,CAAC,EAAE,CAAC,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,KAAK,IAAI,CAAC;IAE9E,eAAe;IACf,kBAAkB,CAAC,EAAE,CAAC,YAAY,EAAE,OAAO,KAAK,IAAI,CAAC;IAErD,gBAAgB;IAChB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAErB,WAAW;IACX,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAEpC;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,YAAY;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,YAAY;IACZ,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAE5B,eAAe;IACf,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAE7B,gBAAgB;IAChB,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAChC"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * HivtraceViz 组件类型定义
3
+ * 覆盖原有 HivtraceViz.tsx 和 ClusterTrackingHivtraceViz.tsx 的所有功能
4
+ */
5
+ export {};
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Vite 插件 - 为宿主应用提供 hivtrace-viz 静态资源服务
3
+ */
4
+ import type { Plugin } from 'vite';
5
+ export interface HivtraceVizPluginOptions {
6
+ /**
7
+ * 自定义 URL 前缀,默认 '/hivtrace-viz'
8
+ */
9
+ base?: string;
10
+ }
11
+ export declare function hivtraceVizPlugin(options?: HivtraceVizPluginOptions): Plugin;
12
+ export default hivtraceVizPlugin;
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vite-plugin/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAiB,MAAM,MAAM,CAAC;AASlD,MAAM,WAAW,wBAAwB;IACvC;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,wBAA6B,GAAG,MAAM,CAsFhF;AAED,eAAe,iBAAiB,CAAC"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Vite 插件 - 为宿主应用提供 hivtrace-viz 静态资源服务
3
+ */
4
+ import { resolve, join, dirname } from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ import { existsSync, readFileSync, mkdirSync, cpSync } from 'fs';
7
+ // ESM 兼容的 __dirname
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = dirname(__filename);
10
+ export function hivtraceVizPlugin(options = {}) {
11
+ const base = options.base || '/hivtrace-viz';
12
+ // 定位包的 embed 目录(iframe 静态资源)
13
+ const packageRoot = resolve(__dirname, '../..');
14
+ const embedDir = resolve(packageRoot, 'dist/embed');
15
+ return {
16
+ name: 'vite-plugin-hivtrace-viz',
17
+ /**
18
+ * 开发服务器:提供静态文件服务
19
+ */
20
+ configureServer(server) {
21
+ if (!existsSync(embedDir)) {
22
+ console.warn(`[hivtrace-viz] embed directory not found at ${embedDir}. ` +
23
+ `Run 'npm run build' in the @basic-genomics/hivtrace-viz package first.`);
24
+ return;
25
+ }
26
+ // 添加中间件处理 /hivtrace-viz/* 请求
27
+ server.middlewares.use((req, res, next) => {
28
+ if (!req.url?.startsWith(base)) {
29
+ return next();
30
+ }
31
+ // 移除 base 前缀,获取实际文件路径
32
+ const relativePath = req.url.slice(base.length) || '/index.html';
33
+ const filePath = join(embedDir, relativePath);
34
+ if (existsSync(filePath)) {
35
+ // 设置正确的 Content-Type
36
+ const ext = filePath.split('.').pop()?.toLowerCase();
37
+ const mimeTypes = {
38
+ 'html': 'text/html',
39
+ 'js': 'application/javascript',
40
+ 'css': 'text/css',
41
+ 'json': 'application/json',
42
+ 'woff': 'font/woff',
43
+ 'woff2': 'font/woff2',
44
+ 'ttf': 'font/ttf',
45
+ 'svg': 'image/svg+xml',
46
+ 'png': 'image/png',
47
+ 'eot': 'application/vnd.ms-fontobject',
48
+ };
49
+ if (ext && mimeTypes[ext]) {
50
+ res.setHeader('Content-Type', mimeTypes[ext]);
51
+ }
52
+ // 读取并返回文件
53
+ const content = readFileSync(filePath);
54
+ res.end(content);
55
+ }
56
+ else {
57
+ next();
58
+ }
59
+ });
60
+ console.log(`[hivtrace-viz] Serving embed resources at ${base}`);
61
+ },
62
+ /**
63
+ * 生产构建:复制静态资源到 dist 目录
64
+ */
65
+ closeBundle() {
66
+ if (!existsSync(embedDir)) {
67
+ console.warn(`[hivtrace-viz] embed directory not found. Skipping asset copy.`);
68
+ return;
69
+ }
70
+ // 复制到宿主应用的 dist 目录
71
+ const outputDir = resolve(process.cwd(), 'dist', base.slice(1));
72
+ try {
73
+ mkdirSync(outputDir, { recursive: true });
74
+ cpSync(embedDir, outputDir, { recursive: true, force: true });
75
+ console.log(`[hivtrace-viz] Copied embed assets to ${outputDir}`);
76
+ }
77
+ catch (e) {
78
+ console.error('[hivtrace-viz] Failed to copy assets:', e);
79
+ }
80
+ },
81
+ };
82
+ }
83
+ export default hivtraceVizPlugin;
package/package.json ADDED
@@ -0,0 +1,106 @@
1
+ {
2
+ "name": "@basic-genomics/hivtrace-viz",
3
+ "version": "1.0.0",
4
+ "description": "HIV-TRACE molecular transmission network visualization with React integration",
5
+ "engines": {
6
+ "node": ">=18"
7
+ },
8
+ "main": "dist/hivtrace.js",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/hivtrace.js",
12
+ "require": "./dist/hivtrace.js"
13
+ },
14
+ "./react": {
15
+ "import": "./dist/react/index.js",
16
+ "types": "./dist/react/index.d.ts"
17
+ },
18
+ "./vite": {
19
+ "import": "./dist/vite-plugin/index.js",
20
+ "types": "./dist/vite-plugin/index.d.ts"
21
+ },
22
+ "./embed/*": "./dist/embed/*"
23
+ },
24
+ "files": [
25
+ "dist"
26
+ ],
27
+ "browserslist": "> 0.25%",
28
+ "dependencies": {
29
+ "@fortawesome/fontawesome-free": "^6.2.1",
30
+ "autocomplete.js": "^0.38.1",
31
+ "bootstrap": "^3.4.1",
32
+ "bootstrap-datepicker": "^1.10.x",
33
+ "d3": "3.x",
34
+ "downloadjs": "^1.4.7",
35
+ "jquery": "^3.6.1",
36
+ "jQuery-QueryBuilder": "^2.7.0",
37
+ "js-convert-case": "^4.2.0",
38
+ "jspanel4": "4.16.1",
39
+ "taffydb": "^2.7.3",
40
+ "topojson": "3.x",
41
+ "underscore": "1.x"
42
+ },
43
+ "devDependencies": {
44
+ "@babel/core": "^7.20.2",
45
+ "@babel/preset-env": "^7.20.2",
46
+ "@types/fs-extra": "^11.0.4",
47
+ "@types/node": "^20.12.5",
48
+ "@types/react": "^18.3.27",
49
+ "@zainulbr/i18n-webpack-plugin": "^2.0.3",
50
+ "babel-loader": "^9.1.0",
51
+ "concurrently": "^7.6.0",
52
+ "core-js": "^3.47.0",
53
+ "css-loader": "^6.7.2",
54
+ "exports-loader": "^5.0.0",
55
+ "expose-loader": "^5.0.1",
56
+ "file-loader": "^6.2.0",
57
+ "fs-extra": "^11.3.3",
58
+ "http-server": "^14.1.1",
59
+ "imports-loader": "^5.0.0",
60
+ "mini-css-extract-plugin": "^2.7.0",
61
+ "process": "^0.11.10",
62
+ "react": "^18.2.0",
63
+ "sirv": "^2.0.4",
64
+ "style-loader": "^3.3.1",
65
+ "typescript": "^5.9.3",
66
+ "url-loader": "^4.1.1",
67
+ "vite": "^7.3.0",
68
+ "webpack": "5.76.0",
69
+ "webpack-cli": "^5.0.0"
70
+ },
71
+ "peerDependencies": {
72
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
73
+ "vite": "^4.0.0 || ^5.0.0 || ^6.0.0"
74
+ },
75
+ "peerDependenciesMeta": {
76
+ "react": {
77
+ "optional": true
78
+ },
79
+ "vite": {
80
+ "optional": true
81
+ }
82
+ },
83
+ "scripts": {
84
+ "dev": "npm run build:embed && export NODE_OPTIONS=--openssl-legacy-provider; concurrently \"webpack --config webpack.config.cjs --watch --mode=development\" \"node scripts/watch-embed.cjs\" \"http-server -o preview.html\"",
85
+ "build": "npm run build:core && npm run build:react && npm run build:vite && npm run build:embed",
86
+ "build:core": "export NODE_OPTIONS=--openssl-legacy-provider; webpack --config webpack.config.cjs --mode=production",
87
+ "build:react": "tsc -p tsconfig.react.json",
88
+ "build:vite": "tsc -p tsconfig.vite.json",
89
+ "build:embed": "node scripts/build-embed.cjs",
90
+ "prepublishOnly": "npm run build"
91
+ },
92
+ "repository": {
93
+ "type": "git",
94
+ "url": "https://github.com/aspect-genomics/hivtrace-viz.git"
95
+ },
96
+ "keywords": [
97
+ "hivtrace",
98
+ "visualization",
99
+ "molecular-network",
100
+ "d3",
101
+ "hiv",
102
+ "react",
103
+ "vite"
104
+ ],
105
+ "packageManager": "yarn@1.22.19+sha1.4ba7fc5c6e704fce2066ecbfb0b0d8976fe62447"
106
+ }