@composy/log-viewer-core 0.0.1 → 0.0.2
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/CHANGELOG.md +52 -0
- package/README.md +175 -37
- package/dist/index.js +614 -299
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +548 -1
- package/dist/index.min.js.map +1 -0
- package/es/.dts-output-manifest.json +1 -0
- package/es/.dts.tsbuildinfo +1 -0
- package/es/core/EventEmitter.d.ts +0 -1
- package/es/core/EventEmitter.js +13 -9
- package/es/core/EventEmitter.js.map +1 -1
- package/es/core/LogViewer.d.ts +14 -11
- package/es/core/LogViewer.js +243 -64
- package/es/core/LogViewer.js.map +1 -1
- package/es/filters/FilterEngine.d.ts +0 -1
- package/es/filters/FilterEngine.js +31 -26
- package/es/filters/FilterEngine.js.map +1 -1
- package/es/index.d.ts +7 -7
- package/es/index.js +3 -3
- package/es/index.js.map +1 -1
- package/es/renderers/DiffRenderer.d.ts +0 -1
- package/es/renderers/DiffRenderer.js +13 -3
- package/es/renderers/DiffRenderer.js.map +1 -1
- package/es/renderers/TimelineRenderer.d.ts +7 -3
- package/es/renderers/TimelineRenderer.js +56 -22
- package/es/renderers/TimelineRenderer.js.map +1 -1
- package/es/styles/index.d.ts +1 -5
- package/es/styles/index.js +167 -131
- package/es/styles/index.js.map +1 -1
- package/es/types/index.d.ts +4 -1
- package/es/types/index.js +1 -1
- package/es/types/index.js.map +1 -1
- package/es/utils/index.d.ts +1 -2
- package/es/utils/index.js +54 -6
- package/es/utils/index.js.map +1 -1
- package/esm/core/EventEmitter.js +13 -9
- package/esm/core/EventEmitter.js.map +1 -1
- package/esm/core/LogViewer.js +243 -64
- package/esm/core/LogViewer.js.map +1 -1
- package/esm/filters/FilterEngine.js +31 -26
- package/esm/filters/FilterEngine.js.map +1 -1
- package/esm/index.js +2 -2
- package/esm/index.js.map +1 -1
- package/esm/renderers/DiffRenderer.js +13 -3
- package/esm/renderers/DiffRenderer.js.map +1 -1
- package/esm/renderers/TimelineRenderer.js +56 -22
- package/esm/renderers/TimelineRenderer.js.map +1 -1
- package/esm/styles/index.js +167 -131
- package/esm/styles/index.js.map +1 -1
- package/esm/types/index.js +1 -1
- package/esm/types/index.js.map +1 -1
- package/esm/utils/index.js +54 -6
- package/esm/utils/index.js.map +1 -1
- package/lib/core/EventEmitter.cjs +13 -9
- package/lib/core/EventEmitter.cjs.map +1 -1
- package/{esm/core/EventEmitter.d.ts → lib/core/EventEmitter.d.cts} +0 -1
- package/lib/core/LogViewer.cjs +247 -68
- package/lib/core/LogViewer.cjs.map +1 -1
- package/{esm/core/LogViewer.d.ts → lib/core/LogViewer.d.cts} +14 -11
- package/lib/filters/FilterEngine.cjs +31 -26
- package/lib/filters/FilterEngine.cjs.map +1 -1
- package/lib/filters/{FilterEngine.d.ts → FilterEngine.d.cts} +0 -1
- package/lib/index.cjs +3 -3
- package/lib/index.cjs.map +1 -1
- package/{esm/index.d.ts → lib/index.d.cts} +7 -7
- package/lib/renderers/DiffRenderer.cjs +13 -3
- package/lib/renderers/DiffRenderer.cjs.map +1 -1
- package/lib/renderers/{DiffRenderer.d.ts → DiffRenderer.d.cts} +0 -1
- package/lib/renderers/TimelineRenderer.cjs +55 -21
- package/lib/renderers/TimelineRenderer.cjs.map +1 -1
- package/lib/renderers/{TimelineRenderer.d.ts → TimelineRenderer.d.cts} +7 -3
- package/lib/styles/index.cjs +167 -131
- package/lib/styles/index.cjs.map +1 -1
- package/{esm/styles/index.d.ts → lib/styles/index.d.cts} +1 -5
- package/lib/types/index.cjs +1 -1
- package/lib/types/index.cjs.map +1 -1
- package/{esm/types/index.d.ts → lib/types/index.d.cts} +4 -1
- package/lib/utils/index.cjs +54 -6
- package/lib/utils/index.cjs.map +1 -1
- package/{esm/utils/index.d.ts → lib/utils/index.d.cts} +1 -2
- package/package.json +15 -9
- package/es/core/EventEmitter.d.ts.map +0 -1
- package/es/core/LogViewer.d.ts.map +0 -1
- package/es/filters/FilterEngine.d.ts.map +0 -1
- package/es/index.d.ts.map +0 -1
- package/es/renderers/DiffRenderer.d.ts.map +0 -1
- package/es/renderers/TimelineRenderer.d.ts.map +0 -1
- package/es/styles/index.d.ts.map +0 -1
- package/es/types/index.d.ts.map +0 -1
- package/es/utils/index.d.ts.map +0 -1
- package/esm/core/EventEmitter.d.ts.map +0 -1
- package/esm/core/LogViewer.d.ts.map +0 -1
- package/esm/filters/FilterEngine.d.ts +0 -26
- package/esm/filters/FilterEngine.d.ts.map +0 -1
- package/esm/index.d.ts.map +0 -1
- package/esm/renderers/DiffRenderer.d.ts +0 -18
- package/esm/renderers/DiffRenderer.d.ts.map +0 -1
- package/esm/renderers/TimelineRenderer.d.ts +0 -22
- package/esm/renderers/TimelineRenderer.d.ts.map +0 -1
- package/esm/styles/index.d.ts.map +0 -1
- package/esm/types/index.d.ts.map +0 -1
- package/esm/utils/index.d.ts.map +0 -1
- package/lib/core/EventEmitter.d.ts +0 -11
- package/lib/core/LogViewer.d.ts +0 -46
- package/lib/index.d.ts +0 -26
- package/lib/styles/index.d.ts +0 -9
- package/lib/types/index.d.ts +0 -113
- package/lib/utils/index.d.ts +0 -18
package/es/utils/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* ***********************************
|
|
3
3
|
* @composy/log-viewer-core v0.0.1 *
|
|
4
4
|
* Built with rollup *
|
|
5
|
-
* Build time: 2024-
|
|
5
|
+
* Build time: 2024-10-12 17:29:20 *
|
|
6
6
|
* Build mode: production *
|
|
7
7
|
* Minified: No *
|
|
8
8
|
* ***********************************
|
|
@@ -10,11 +10,12 @@
|
|
|
10
10
|
//#region src/utils/index.ts
|
|
11
11
|
let _uid = 0;
|
|
12
12
|
function uid(prefix = "lv") {
|
|
13
|
-
|
|
13
|
+
const random = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID().slice(0, 8) : Math.random().toString(36).slice(2, 8);
|
|
14
|
+
return `${prefix}-${++_uid}-${random}`;
|
|
14
15
|
}
|
|
15
16
|
function formatDate(value, fmt = "YYYY-MM-DD HH:mm:ss") {
|
|
16
17
|
const d = value instanceof Date ? value : new Date(value);
|
|
17
|
-
if (isNaN(d.getTime())) return String(value);
|
|
18
|
+
if (Number.isNaN(d.getTime())) return String(value);
|
|
18
19
|
const pad = (n) => String(n).padStart(2, "0");
|
|
19
20
|
const year = String(d.getFullYear());
|
|
20
21
|
const month = pad(d.getMonth() + 1);
|
|
@@ -27,6 +28,7 @@ function formatDate(value, fmt = "YYYY-MM-DD HH:mm:ss") {
|
|
|
27
28
|
function formatRelativeTime(value) {
|
|
28
29
|
const d = value instanceof Date ? value : new Date(value);
|
|
29
30
|
const diff = Date.now() - d.getTime();
|
|
31
|
+
if (!Number.isFinite(diff) || diff < 0) return formatDate(d);
|
|
30
32
|
if (diff < 6e4) return "刚刚";
|
|
31
33
|
if (diff < 36e5) return `${Math.floor(diff / 6e4)} \u5206\u949F\u524D`;
|
|
32
34
|
if (diff < 864e5) return `${Math.floor(diff / 36e5)} \u5C0F\u65F6\u524D`;
|
|
@@ -45,15 +47,58 @@ function escapeHtml(str) {
|
|
|
45
47
|
}
|
|
46
48
|
function debounce(fn, delay) {
|
|
47
49
|
let timer = null;
|
|
50
|
+
const wait = Number.isFinite(delay) && delay > 0 ? delay : 0;
|
|
48
51
|
return ((...args) => {
|
|
49
52
|
if (timer) clearTimeout(timer);
|
|
50
|
-
timer = setTimeout(() =>
|
|
53
|
+
timer = setTimeout(() => {
|
|
54
|
+
timer = null;
|
|
55
|
+
fn(...args);
|
|
56
|
+
}, wait);
|
|
51
57
|
});
|
|
52
58
|
}
|
|
53
59
|
function deepClone(obj) {
|
|
54
60
|
if (obj === null || typeof obj !== "object") return obj;
|
|
61
|
+
if (typeof structuredClone === "function") try {
|
|
62
|
+
return structuredClone(obj);
|
|
63
|
+
} catch {}
|
|
64
|
+
const seen = /* @__PURE__ */ new WeakMap();
|
|
65
|
+
const clone = (value) => {
|
|
66
|
+
if (value === null || typeof value !== "object") return value;
|
|
67
|
+
if (seen.has(value)) return seen.get(value);
|
|
68
|
+
if (value instanceof Date) return new Date(value.getTime());
|
|
69
|
+
if (value instanceof RegExp) return new RegExp(value.source, value.flags);
|
|
70
|
+
if (value instanceof Map) {
|
|
71
|
+
const result2 = /* @__PURE__ */ new Map();
|
|
72
|
+
seen.set(value, result2);
|
|
73
|
+
value.forEach((mapValue, key) => result2.set(clone(key), clone(mapValue)));
|
|
74
|
+
return result2;
|
|
75
|
+
}
|
|
76
|
+
if (value instanceof Set) {
|
|
77
|
+
const result2 = /* @__PURE__ */ new Set();
|
|
78
|
+
seen.set(value, result2);
|
|
79
|
+
value.forEach((setValue) => result2.add(clone(setValue)));
|
|
80
|
+
return result2;
|
|
81
|
+
}
|
|
82
|
+
if (Array.isArray(value)) {
|
|
83
|
+
const result2 = [];
|
|
84
|
+
seen.set(value, result2);
|
|
85
|
+
value.forEach((item) => result2.push(clone(item)));
|
|
86
|
+
return result2;
|
|
87
|
+
}
|
|
88
|
+
const result = Object.create(Object.getPrototypeOf(value));
|
|
89
|
+
seen.set(value, result);
|
|
90
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
91
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
92
|
+
if (!descriptor || !("value" in descriptor)) continue;
|
|
93
|
+
Object.defineProperty(result, key, {
|
|
94
|
+
...descriptor,
|
|
95
|
+
value: clone(descriptor.value)
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return result;
|
|
99
|
+
};
|
|
55
100
|
try {
|
|
56
|
-
return
|
|
101
|
+
return clone(obj);
|
|
57
102
|
} catch {
|
|
58
103
|
return obj;
|
|
59
104
|
}
|
|
@@ -61,7 +106,10 @@ function deepClone(obj) {
|
|
|
61
106
|
function formatValue(val) {
|
|
62
107
|
if (val === null || val === void 0) return "-";
|
|
63
108
|
if (typeof val === "object") try {
|
|
64
|
-
return JSON.stringify(val,
|
|
109
|
+
return JSON.stringify(val, (_key, value) => {
|
|
110
|
+
if (typeof value === "bigint") return `${value}n`;
|
|
111
|
+
return value;
|
|
112
|
+
}, 2);
|
|
65
113
|
} catch {
|
|
66
114
|
return String(val);
|
|
67
115
|
}
|
package/es/utils/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/utils/index.ts"],"sourcesContent":["/**\n * 工具函数\n */\n\nlet _uid = 0\n\n/** 生成唯一 ID */\nexport function uid(prefix = 'lv'): string {\n
|
|
1
|
+
{"version":3,"file":"index.js","names":["result"],"sources":["../../src/utils/index.ts"],"sourcesContent":["/**\r\n * 工具函数\r\n */\r\n\r\nlet _uid = 0\r\n\r\n/** 生成唯一 ID */\r\nexport function uid(prefix = 'lv'): string {\r\n const random =\r\n typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'\r\n ? crypto.randomUUID().slice(0, 8)\r\n : Math.random().toString(36).slice(2, 8)\r\n return `${prefix}-${++_uid}-${random}`\r\n}\r\n\r\n/** 格式化日期 */\r\nexport function formatDate(\r\n value: string | number | Date,\r\n fmt = 'YYYY-MM-DD HH:mm:ss'\r\n): string {\r\n const d = value instanceof Date ? value : new Date(value)\r\n if (Number.isNaN(d.getTime())) return String(value)\r\n\r\n const pad = (n: number) => String(n).padStart(2, '0')\r\n const year = String(d.getFullYear())\r\n const month = pad(d.getMonth() + 1)\r\n const day = pad(d.getDate())\r\n const hours = pad(d.getHours())\r\n const minutes = pad(d.getMinutes())\r\n const seconds = pad(d.getSeconds())\r\n\r\n return fmt\r\n .replace('YYYY', year)\r\n .replace('MM', month)\r\n .replace('DD', day)\r\n .replace('HH', hours)\r\n .replace('mm', minutes)\r\n .replace('ss', seconds)\r\n}\r\n\r\n/** 格式化相对时间 */\r\nexport function formatRelativeTime(value: string | number | Date): string {\r\n const d = value instanceof Date ? value : new Date(value)\r\n const now = Date.now()\r\n const diff = now - d.getTime()\r\n\r\n if (!Number.isFinite(diff) || diff < 0) return formatDate(d)\r\n if (diff < 60_000) return '刚刚'\r\n if (diff < 3_600_000) return `${Math.floor(diff / 60_000)} 分钟前`\r\n if (diff < 86_400_000) return `${Math.floor(diff / 3_600_000)} 小时前`\r\n if (diff < 604_800_000) return `${Math.floor(diff / 86_400_000)} 天前`\r\n return formatDate(d)\r\n}\r\n\r\n/** HTML 转义 */\r\nexport function escapeHtml(str: string): string {\r\n const map: Record<string, string> = {\r\n '&': '&',\r\n '<': '<',\r\n '>': '>',\r\n '\"': '"',\r\n \"'\": ''',\r\n }\r\n return str.replace(/[&<>\"']/g, c => map[c] || c)\r\n}\r\n\r\n/** 防抖 */\r\nexport function debounce<T extends (...args: unknown[]) => void>(\r\n fn: T,\r\n delay: number\r\n): T {\r\n let timer: ReturnType<typeof setTimeout> | null = null\r\n const wait = Number.isFinite(delay) && delay > 0 ? delay : 0\r\n return ((...args: unknown[]) => {\r\n if (timer) clearTimeout(timer)\r\n timer = setTimeout(() => {\r\n timer = null\r\n fn(...args)\r\n }, wait)\r\n }) as unknown as T\r\n}\r\n\r\n/** 深拷贝值,同时保留 Date、Map、Set 和循环引用。 */\r\nexport function deepClone<T>(obj: T): T {\r\n if (obj === null || typeof obj !== 'object') return obj\r\n\r\n if (typeof structuredClone === 'function') {\r\n try {\r\n return structuredClone(obj)\r\n } catch {\r\n // structuredClone rejects functions and a few host objects; use the\r\n // compatibility clone below so configuration callbacks stay intact.\r\n }\r\n }\r\n\r\n const seen = new WeakMap<object, unknown>()\r\n\r\n const clone = (value: unknown): unknown => {\r\n if (value === null || typeof value !== 'object') return value\r\n if (seen.has(value)) return seen.get(value)\r\n\r\n if (value instanceof Date) return new Date(value.getTime())\r\n if (value instanceof RegExp) return new RegExp(value.source, value.flags)\r\n\r\n if (value instanceof Map) {\r\n const result = new Map<unknown, unknown>()\r\n seen.set(value, result)\r\n value.forEach((mapValue, key) => result.set(clone(key), clone(mapValue)))\r\n return result\r\n }\r\n\r\n if (value instanceof Set) {\r\n const result = new Set<unknown>()\r\n seen.set(value, result)\r\n value.forEach(setValue => result.add(clone(setValue)))\r\n return result\r\n }\r\n\r\n if (Array.isArray(value)) {\r\n const result: unknown[] = []\r\n seen.set(value, result)\r\n value.forEach(item => result.push(clone(item)))\r\n return result\r\n }\r\n\r\n const result = Object.create(Object.getPrototypeOf(value)) as Record<\r\n string,\r\n unknown\r\n >\r\n seen.set(value, result)\r\n for (const key of Reflect.ownKeys(value)) {\r\n const descriptor = Object.getOwnPropertyDescriptor(value, key)\r\n if (!descriptor || !('value' in descriptor)) continue\r\n Object.defineProperty(result, key, {\r\n ...descriptor,\r\n value: clone(descriptor.value),\r\n })\r\n }\r\n return result\r\n }\r\n\r\n try {\r\n return clone(obj) as T\r\n } catch {\r\n return obj\r\n }\r\n}\r\n\r\n/** 将值格式化为可展示的字符串 */\r\nexport function formatValue(val: unknown): string {\r\n if (val === null || val === undefined) return '-'\r\n if (typeof val === 'object') {\r\n try {\r\n return JSON.stringify(\r\n val,\r\n (_key, value) => {\r\n if (typeof value === 'bigint') return `${value}n`\r\n return value\r\n },\r\n 2\r\n )\r\n } catch {\r\n return String(val)\r\n }\r\n }\r\n return String(val)\r\n}\r\n"],"mappings":";;;;;;;;;;AAIA,IAAI,OAAO;AAGJ,SAAS,IAAI,SAAS,MAAc;CACzC,MAAM,SACJ,OAAO,WAAW,eAAe,OAAO,OAAO,eAAe,aAC1D,OAAO,WAAW,CAAA,CAAE,MAAM,GAAG,CAAC,IAC9B,KAAK,OAAO,CAAA,CAAE,SAAS,EAAE,CAAA,CAAE,MAAM,GAAG,CAAC;CAC3C,OAAO,GAAG,OAAM,GAAI,EAAE,KAAI,GAAI;AAChC;AAGO,SAAS,WACd,OACA,MAAM,uBACE;CACR,MAAM,IAAI,iBAAiB,OAAO,QAAQ,IAAI,KAAK,KAAK;CACxD,IAAI,OAAO,MAAM,EAAE,QAAQ,CAAC,GAAG,OAAO,OAAO,KAAK;CAElD,MAAM,OAAO,MAAc,OAAO,CAAC,CAAA,CAAE,SAAS,GAAG,GAAG;CACpD,MAAM,OAAO,OAAO,EAAE,YAAY,CAAC;CACnC,MAAM,QAAQ,IAAI,EAAE,SAAS,IAAI,CAAC;CAClC,MAAM,MAAM,IAAI,EAAE,QAAQ,CAAC;CAC3B,MAAM,QAAQ,IAAI,EAAE,SAAS,CAAC;CAC9B,MAAM,UAAU,IAAI,EAAE,WAAW,CAAC;CAClC,MAAM,UAAU,IAAI,EAAE,WAAW,CAAC;CAElC,OAAO,IACJ,QAAQ,QAAQ,IAAI,CAAA,CACpB,QAAQ,MAAM,KAAK,CAAA,CACnB,QAAQ,MAAM,GAAG,CAAA,CACjB,QAAQ,MAAM,KAAK,CAAA,CACnB,QAAQ,MAAM,OAAO,CAAA,CACrB,QAAQ,MAAM,OAAO;AAC1B;AAGO,SAAS,mBAAmB,OAAuC;CACxE,MAAM,IAAI,iBAAiB,OAAO,QAAQ,IAAI,KAAK,KAAK;CAExD,MAAM,OADM,KAAK,IACJ,IAAM,EAAE,QAAQ;CAE7B,IAAI,CAAC,OAAO,SAAS,IAAI,KAAK,OAAO,GAAG,OAAO,WAAW,CAAC;CAC3D,IAAI,OAAO,KAAQ,OAAO;CAC1B,IAAI,OAAO,MAAW,OAAO,GAAG,KAAK,MAAM,OAAO,GAAM,EAAC;CACzD,IAAI,OAAO,OAAY,OAAO,GAAG,KAAK,MAAM,OAAO,IAAS,EAAC;CAC7D,IAAI,OAAO,QAAa,OAAO,GAAG,KAAK,MAAM,OAAO,KAAU,EAAC;CAC/D,OAAO,WAAW,CAAC;AACrB;AAGO,SAAS,WAAW,KAAqB;CAC9C,MAAM,MAA8B;EAClC,KAAK;EACL,KAAK;EACL,KAAK;EACL,MAAK;EACL,KAAK;CACP;CACA,OAAO,IAAI,QAAQ,aAAY,MAAK,IAAI,MAAM,CAAC;AACjD;AAGO,SAAS,SACd,IACA,OACG;CACH,IAAI,QAA8C;CAClD,MAAM,OAAO,OAAO,SAAS,KAAK,KAAK,QAAQ,IAAI,QAAQ;CAC3D,SAAQ,GAAI,SAAoB;EAC9B,IAAI,OAAO,aAAa,KAAK;EAC7B,QAAQ,iBAAiB;GACvB,QAAQ;GACR,GAAG,GAAG,IAAI;EACZ,GAAG,IAAI;CACT;AACF;AAGO,SAAS,UAAa,KAAW;CACtC,IAAI,QAAQ,QAAQ,OAAO,QAAQ,UAAU,OAAO;CAEpD,IAAI,OAAO,oBAAoB,YAC7B,IAAI;EACF,OAAO,gBAAgB,GAAG;CAC5B,QAAQ,CAGR;CAGF,MAAM,uBAAO,IAAI,QAAyB;CAE1C,MAAM,SAAS,UAA4B;EACzC,IAAI,UAAU,QAAQ,OAAO,UAAU,UAAU,OAAO;EACxD,IAAI,KAAK,IAAI,KAAK,GAAG,OAAO,KAAK,IAAI,KAAK;EAE1C,IAAI,iBAAiB,MAAM,OAAO,IAAI,KAAK,MAAM,QAAQ,CAAC;EAC1D,IAAI,iBAAiB,QAAQ,OAAO,IAAI,OAAO,MAAM,QAAQ,MAAM,KAAK;EAExE,IAAI,iBAAiB,KAAK;GACxB,MAAMA,0BAAS,IAAI,IAAsB;GACzC,KAAK,IAAI,OAAOA,OAAM;GACtB,MAAM,SAAS,UAAU,QAAQA,QAAO,IAAI,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,CAAC;GACxE,OAAOA;EACT;EAEA,IAAI,iBAAiB,KAAK;GACxB,MAAMA,0BAAS,IAAI,IAAa;GAChC,KAAK,IAAI,OAAOA,OAAM;GACtB,MAAM,SAAQ,aAAYA,QAAO,IAAI,MAAM,QAAQ,CAAC,CAAC;GACrD,OAAOA;EACT;EAEA,IAAI,MAAM,QAAQ,KAAK,GAAG;GACxB,MAAMA,UAAoB,CAAC;GAC3B,KAAK,IAAI,OAAOA,OAAM;GACtB,MAAM,SAAQ,SAAQA,QAAO,KAAK,MAAM,IAAI,CAAC,CAAC;GAC9C,OAAOA;EACT;EAEA,MAAM,SAAS,OAAO,OAAO,OAAO,eAAe,KAAK,CAAC;EAIzD,KAAK,IAAI,OAAO,MAAM;EACtB,KAAA,MAAW,OAAO,QAAQ,QAAQ,KAAK,GAAG;GACxC,MAAM,aAAa,OAAO,yBAAyB,OAAO,GAAG;GAC7D,IAAI,CAAC,cAAc,EAAE,WAAW,aAAa;GAC7C,OAAO,eAAe,QAAQ,KAAK;IACjC,GAAG;IACH,OAAO,MAAM,WAAW,KAAK;GAC/B,CAAC;EACH;EACA,OAAO;CACT;CAEA,IAAI;EACF,OAAO,MAAM,GAAG;CAClB,QAAQ;EACN,OAAO;CACT;AACF;AAGO,SAAS,YAAY,KAAsB;CAChD,IAAI,QAAQ,QAAQ,QAAQ,KAAA,GAAW,OAAO;CAC9C,IAAI,OAAO,QAAQ,UACjB,IAAI;EACF,OAAO,KAAK,UACV,MACC,MAAM,UAAU;GACf,IAAI,OAAO,UAAU,UAAU,OAAO,GAAG,MAAK;GAC9C,OAAO;EACT,GACA,CACF;CACF,QAAQ;EACN,OAAO,OAAO,GAAG;CACnB;CAEF,OAAO,OAAO,GAAG;AACnB"}
|
package/esm/core/EventEmitter.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* ***********************************
|
|
3
3
|
* @composy/log-viewer-core v0.0.1 *
|
|
4
4
|
* Built with rollup *
|
|
5
|
-
* Build time: 2024-
|
|
5
|
+
* Build time: 2024-10-12 17:29:20 *
|
|
6
6
|
* Build mode: production *
|
|
7
7
|
* Minified: No *
|
|
8
8
|
* ***********************************
|
|
@@ -13,21 +13,25 @@ var EventEmitter = class {
|
|
|
13
13
|
this.listeners = /* @__PURE__ */ new Map();
|
|
14
14
|
}
|
|
15
15
|
on(event, callback) {
|
|
16
|
+
if (!event || typeof callback !== "function") return () => void 0;
|
|
16
17
|
if (!this.listeners.has(event)) this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
17
18
|
this.listeners.get(event).add(callback);
|
|
18
19
|
return () => this.off(event, callback);
|
|
19
20
|
}
|
|
20
21
|
off(event, callback) {
|
|
21
|
-
this.listeners.get(event)
|
|
22
|
+
const listeners = this.listeners.get(event);
|
|
23
|
+
if (!listeners) return;
|
|
24
|
+
listeners.delete(callback);
|
|
25
|
+
if (listeners.size === 0) this.listeners.delete(event);
|
|
22
26
|
}
|
|
23
27
|
emit(event, ...args) {
|
|
24
|
-
this.listeners.get(event)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
}
|
|
28
|
+
const listeners = this.listeners.get(event);
|
|
29
|
+
if (!listeners) return;
|
|
30
|
+
for (const cb of [...listeners]) try {
|
|
31
|
+
cb(...args);
|
|
32
|
+
} catch (e) {
|
|
33
|
+
console.error(`[LogViewer] Event handler error (${event}):`, e);
|
|
34
|
+
}
|
|
31
35
|
}
|
|
32
36
|
removeAllListeners(event) {
|
|
33
37
|
if (event) this.listeners.delete(event);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EventEmitter.js","names":[],"sources":["../../src/core/EventEmitter.ts"],"sourcesContent":["/**\n * 简单的事件发射器\n */\nexport class EventEmitter {\n private listeners = new Map<string, Set<(...args: unknown[]) => void>>()\n\n on(event: string, callback: (...args: unknown[]) => void): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set())\n }\n this.listeners.get(event)
|
|
1
|
+
{"version":3,"file":"EventEmitter.js","names":[],"sources":["../../src/core/EventEmitter.ts"],"sourcesContent":["/**\r\n * 简单的事件发射器\r\n */\r\nexport class EventEmitter {\r\n private listeners = new Map<string, Set<(...args: unknown[]) => void>>()\r\n\r\n on(event: string, callback: (...args: unknown[]) => void): () => void {\r\n if (!event || typeof callback !== 'function') {\r\n return () => undefined\r\n }\r\n if (!this.listeners.has(event)) {\r\n this.listeners.set(event, new Set())\r\n }\r\n const listeners = this.listeners.get(event)!\r\n listeners.add(callback)\r\n return () => this.off(event, callback)\r\n }\r\n\r\n off(event: string, callback: (...args: unknown[]) => void): void {\r\n const listeners = this.listeners.get(event)\r\n if (!listeners) return\r\n listeners.delete(callback)\r\n if (listeners.size === 0) this.listeners.delete(event)\r\n }\r\n\r\n emit(event: string, ...args: unknown[]): void {\r\n const listeners = this.listeners.get(event)\r\n if (!listeners) return\r\n\r\n // Snapshot the set so listeners can unsubscribe or subscribe while the\r\n // current event is being delivered without changing this dispatch cycle.\r\n for (const cb of [...listeners]) {\r\n try {\r\n cb(...args)\r\n } catch (e) {\r\n console.error(`[LogViewer] Event handler error (${event}):`, e)\r\n }\r\n }\r\n }\r\n\r\n removeAllListeners(event?: string): void {\r\n if (event) {\r\n this.listeners.delete(event)\r\n } else {\r\n this.listeners.clear()\r\n }\r\n }\r\n}\r\n"],"mappings":";;;;;;;;;;AAGO,IAAM,eAAN,MAAmB;CAAnB,cAAA;EACL,KAAQ,4BAAY,IAAI,IAA+C;CAAA;CAEvE,GAAG,OAAe,UAAoD;EACpE,IAAI,CAAC,SAAS,OAAO,aAAa,YAChC,aAAa,KAAA;EAEf,IAAI,CAAC,KAAK,UAAU,IAAI,KAAK,GAC3B,KAAK,UAAU,IAAI,uBAAO,IAAI,IAAI,CAAC;EAGrC,KADuB,UAAU,IAAI,KACrC,CAAA,CAAU,IAAI,QAAQ;EACtB,aAAa,KAAK,IAAI,OAAO,QAAQ;CACvC;CAEA,IAAI,OAAe,UAA8C;EAC/D,MAAM,YAAY,KAAK,UAAU,IAAI,KAAK;EAC1C,IAAI,CAAC,WAAW;EAChB,UAAU,OAAO,QAAQ;EACzB,IAAI,UAAU,SAAS,GAAG,KAAK,UAAU,OAAO,KAAK;CACvD;CAEA,KAAK,OAAA,GAAkB,MAAuB;EAC5C,MAAM,YAAY,KAAK,UAAU,IAAI,KAAK;EAC1C,IAAI,CAAC,WAAW;EAIhB,KAAA,MAAW,MAAM,CAAC,GAAG,SAAS,GAC5B,IAAI;GACF,GAAG,GAAG,IAAI;EACZ,SAAS,GAAG;GACV,QAAQ,MAAM,oCAAoC,MAAK,KAAM,CAAC;EAChE;CAEJ;CAEA,mBAAmB,OAAsB;EACvC,IAAI,OACF,KAAK,UAAU,OAAO,KAAK;OAE3B,KAAK,UAAU,MAAM;CAEzB;AACF"}
|
package/esm/core/LogViewer.js
CHANGED
|
@@ -2,18 +2,63 @@
|
|
|
2
2
|
* ***********************************
|
|
3
3
|
* @composy/log-viewer-core v0.0.1 *
|
|
4
4
|
* Built with rollup *
|
|
5
|
-
* Build time: 2024-
|
|
5
|
+
* Build time: 2024-10-12 17:29:20 *
|
|
6
6
|
* Build mode: production *
|
|
7
7
|
* Minified: No *
|
|
8
8
|
* ***********************************
|
|
9
9
|
*/
|
|
10
|
+
import { ACTION_LABELS } from "../types/index.js";
|
|
10
11
|
import { EventEmitter } from "./EventEmitter.js";
|
|
11
12
|
import { FilterEngine } from "../filters/FilterEngine.js";
|
|
12
13
|
import { deepClone } from "../utils/index.js";
|
|
13
|
-
import { TimelineRenderer } from "../renderers/TimelineRenderer.js";
|
|
14
14
|
import { DiffRenderer } from "../renderers/DiffRenderer.js";
|
|
15
|
+
import { TimelineRenderer } from "../renderers/TimelineRenderer.js";
|
|
15
16
|
import { injectStyles } from "../styles/index.js";
|
|
16
17
|
//#region src/core/LogViewer.ts
|
|
18
|
+
const DEFAULT_PAGE_SIZE = 20;
|
|
19
|
+
const MAX_PAGE_SIZE = 1e3;
|
|
20
|
+
function normalizePageSize(value, fallback = DEFAULT_PAGE_SIZE) {
|
|
21
|
+
const pageSize = Number(value);
|
|
22
|
+
if (!Number.isFinite(pageSize) || pageSize <= 0) return fallback;
|
|
23
|
+
return Math.min(MAX_PAGE_SIZE, Math.max(1, Math.floor(pageSize)));
|
|
24
|
+
}
|
|
25
|
+
function normalizePage(value) {
|
|
26
|
+
const page = Number(value);
|
|
27
|
+
if (!Number.isFinite(page) || page < 1) return 1;
|
|
28
|
+
return Math.floor(page);
|
|
29
|
+
}
|
|
30
|
+
function cloneEntries(entries) {
|
|
31
|
+
if (entries === void 0) return [];
|
|
32
|
+
if (!Array.isArray(entries)) throw new TypeError("[LogViewer] data must be an array");
|
|
33
|
+
return entries.map((entry, index) => {
|
|
34
|
+
if (!entry || typeof entry !== "object") throw new TypeError(`[LogViewer] data[${index}] must be an object`);
|
|
35
|
+
const cloned = deepClone(entry);
|
|
36
|
+
const id = String(cloned.id ?? "").trim();
|
|
37
|
+
if (!id) throw new TypeError(`[LogViewer] data[${index}].id must be a non-empty string`);
|
|
38
|
+
return {
|
|
39
|
+
...cloned,
|
|
40
|
+
id,
|
|
41
|
+
user: String(cloned.user ?? ""),
|
|
42
|
+
module: String(cloned.module ?? ""),
|
|
43
|
+
changes: Array.isArray(cloned.changes) ? cloned.changes : void 0
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
function isActionType(value) {
|
|
48
|
+
return Object.keys(ACTION_LABELS).includes(value);
|
|
49
|
+
}
|
|
50
|
+
function sourceEntries(payload) {
|
|
51
|
+
if (Array.isArray(payload)) return payload;
|
|
52
|
+
if (!payload || typeof payload !== "object") throw new TypeError("[LogViewer] dataSource response must be an array");
|
|
53
|
+
const record = payload;
|
|
54
|
+
for (const key of [
|
|
55
|
+
"data",
|
|
56
|
+
"logs",
|
|
57
|
+
"items",
|
|
58
|
+
"entries"
|
|
59
|
+
]) if (Array.isArray(record[key])) return record[key];
|
|
60
|
+
throw new TypeError("[LogViewer] dataSource response must contain an array");
|
|
61
|
+
}
|
|
17
62
|
var LogViewer = class {
|
|
18
63
|
constructor(config) {
|
|
19
64
|
this.emitter = new EventEmitter();
|
|
@@ -25,89 +70,107 @@ var LogViewer = class {
|
|
|
25
70
|
this.filter = {};
|
|
26
71
|
this.pagination = {
|
|
27
72
|
page: 1,
|
|
28
|
-
pageSize:
|
|
73
|
+
pageSize: DEFAULT_PAGE_SIZE
|
|
29
74
|
};
|
|
30
75
|
this.theme = "light";
|
|
31
76
|
this.expandedIds = /* @__PURE__ */ new Set();
|
|
77
|
+
this.filterEl = null;
|
|
32
78
|
this.timelineEl = null;
|
|
33
79
|
this.paginationEl = null;
|
|
34
80
|
this.destroyed = false;
|
|
35
|
-
this.
|
|
81
|
+
this.requestId = 0;
|
|
82
|
+
this.abortController = null;
|
|
83
|
+
if (typeof document === "undefined") throw new Error("[LogViewer] A browser document is required");
|
|
36
84
|
const container = typeof config.container === "string" ? document.querySelector(config.container) : config.container;
|
|
37
|
-
if (!container) throw new Error(`[LogViewer] Container not found: ${config.container}`);
|
|
85
|
+
if (!container) throw new Error(`[LogViewer] Container not found: ${String(config.container)}`);
|
|
86
|
+
this.config = { ...config };
|
|
87
|
+
this.theme = config.theme === "dark" ? "dark" : "light";
|
|
88
|
+
this.pagination = {
|
|
89
|
+
page: normalizePage(config.pagination?.page),
|
|
90
|
+
pageSize: normalizePageSize(config.pageSize ?? config.pagination?.pageSize)
|
|
91
|
+
};
|
|
92
|
+
this.filter = deepClone(config.filter || {});
|
|
93
|
+
this.data = cloneEntries(config.data);
|
|
38
94
|
injectStyles();
|
|
39
95
|
this.root = document.createElement("div");
|
|
40
96
|
this.root.className = "lv-root";
|
|
41
|
-
container.appendChild(this.root);
|
|
42
|
-
this.theme = config.theme || "light";
|
|
43
97
|
this.root.setAttribute("data-theme", this.theme);
|
|
44
|
-
|
|
45
|
-
if (config.pagination) Object.assign(this.pagination, config.pagination);
|
|
46
|
-
if (config.filter) this.filter = deepClone(config.filter);
|
|
47
|
-
if (config.data) this.data = [...config.data];
|
|
48
|
-
this.applyFilterAndRender();
|
|
98
|
+
container.appendChild(this.root);
|
|
49
99
|
this.bindEvents();
|
|
100
|
+
this.applyFilterAndRender();
|
|
101
|
+
if (config.dataSource) this.loadData().catch(() => void 0);
|
|
50
102
|
}
|
|
51
103
|
getData() {
|
|
52
|
-
return
|
|
104
|
+
return deepClone(this.data);
|
|
53
105
|
}
|
|
54
106
|
setData(entries) {
|
|
55
|
-
this.
|
|
107
|
+
if (this.destroyed) return;
|
|
108
|
+
this.data = cloneEntries(entries);
|
|
56
109
|
this.pagination.page = 1;
|
|
110
|
+
this.expandedIds.clear();
|
|
57
111
|
this.applyFilterAndRender();
|
|
58
|
-
this.emitter.emit("data:change", this.
|
|
112
|
+
this.emitter.emit("data:change", this.getData());
|
|
59
113
|
}
|
|
60
114
|
appendData(entries) {
|
|
61
|
-
this.
|
|
115
|
+
if (this.destroyed) return;
|
|
116
|
+
this.data.push(...cloneEntries(entries));
|
|
62
117
|
this.applyFilterAndRender();
|
|
63
|
-
this.emitter.emit("data:change", this.
|
|
118
|
+
this.emitter.emit("data:change", this.getData());
|
|
64
119
|
}
|
|
65
120
|
getFilter() {
|
|
66
121
|
return deepClone(this.filter);
|
|
67
122
|
}
|
|
68
123
|
setFilter(filter) {
|
|
69
|
-
this.
|
|
124
|
+
if (this.destroyed) return;
|
|
125
|
+
this.filter = deepClone(filter || {});
|
|
70
126
|
this.pagination.page = 1;
|
|
71
127
|
this.applyFilterAndRender();
|
|
72
|
-
this.
|
|
73
|
-
this.
|
|
128
|
+
const current = this.getFilter();
|
|
129
|
+
this.emitter.emit("filter:change", current);
|
|
130
|
+
this.config.onFilterChange?.(current);
|
|
74
131
|
}
|
|
75
132
|
resetFilter() {
|
|
76
|
-
this.
|
|
77
|
-
this.
|
|
78
|
-
this.applyFilterAndRender();
|
|
79
|
-
this.emitter.emit("filter:change", this.filter);
|
|
133
|
+
if (this.destroyed) return;
|
|
134
|
+
this.setFilter({});
|
|
80
135
|
}
|
|
81
136
|
getTheme() {
|
|
82
137
|
return this.theme;
|
|
83
138
|
}
|
|
84
139
|
setTheme(theme) {
|
|
140
|
+
if (this.destroyed) return;
|
|
141
|
+
if (theme !== "light" && theme !== "dark") throw new TypeError("[LogViewer] theme must be light or dark");
|
|
142
|
+
if (this.theme === theme) return;
|
|
85
143
|
this.theme = theme;
|
|
86
144
|
this.root.setAttribute("data-theme", theme);
|
|
87
145
|
this.emitter.emit("theme:change", theme);
|
|
88
146
|
this.config.onThemeChange?.(theme);
|
|
89
147
|
}
|
|
90
148
|
expandEntry(id) {
|
|
149
|
+
if (this.destroyed || this.expandedIds.has(id)) return;
|
|
91
150
|
this.expandedIds.add(id);
|
|
92
|
-
const entry = this.data.find((
|
|
93
|
-
if (entry) this.emitter.emit("entry:expand", entry);
|
|
151
|
+
const entry = this.data.find((item) => item.id === id);
|
|
152
|
+
if (entry) this.emitter.emit("entry:expand", deepClone(entry));
|
|
94
153
|
this.renderTimeline();
|
|
95
154
|
}
|
|
96
155
|
collapseEntry(id) {
|
|
156
|
+
if (this.destroyed || !this.expandedIds.has(id)) return;
|
|
97
157
|
this.expandedIds.delete(id);
|
|
98
|
-
const entry = this.data.find((
|
|
99
|
-
if (entry) this.emitter.emit("entry:collapse", entry);
|
|
158
|
+
const entry = this.data.find((item) => item.id === id);
|
|
159
|
+
if (entry) this.emitter.emit("entry:collapse", deepClone(entry));
|
|
100
160
|
this.renderTimeline();
|
|
101
161
|
}
|
|
102
162
|
collapseAll() {
|
|
163
|
+
if (this.destroyed || this.expandedIds.size === 0) return;
|
|
103
164
|
this.expandedIds.clear();
|
|
104
165
|
this.renderTimeline();
|
|
105
166
|
}
|
|
106
167
|
goToPage(page) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
this.
|
|
168
|
+
if (this.destroyed) return;
|
|
169
|
+
const totalPages = Math.max(1, Math.ceil(this.filteredData.length / this.pagination.pageSize));
|
|
170
|
+
const nextPage = Math.min(totalPages, Math.max(1, normalizePage(page)));
|
|
171
|
+
if (nextPage === this.pagination.page) return;
|
|
172
|
+
this.pagination.page = nextPage;
|
|
173
|
+
this.emitter.emit("page:change", nextPage);
|
|
111
174
|
this.renderTimeline();
|
|
112
175
|
}
|
|
113
176
|
getPagination() {
|
|
@@ -120,17 +183,55 @@ var LogViewer = class {
|
|
|
120
183
|
return this.emitter.on(event, callback);
|
|
121
184
|
}
|
|
122
185
|
refresh() {
|
|
123
|
-
this.applyFilterAndRender();
|
|
186
|
+
if (!this.destroyed) this.applyFilterAndRender();
|
|
187
|
+
}
|
|
188
|
+
async loadData() {
|
|
189
|
+
if (this.destroyed || !this.config.dataSource) return;
|
|
190
|
+
const currentRequest = ++this.requestId;
|
|
191
|
+
this.abortController?.abort();
|
|
192
|
+
this.abortController = typeof AbortController !== "undefined" ? new AbortController() : null;
|
|
193
|
+
try {
|
|
194
|
+
let payload;
|
|
195
|
+
if (typeof this.config.dataSource === "function") payload = await this.config.dataSource();
|
|
196
|
+
else {
|
|
197
|
+
if (typeof fetch !== "function") throw new Error("[LogViewer] fetch is unavailable");
|
|
198
|
+
const response = await fetch(this.config.dataSource, { signal: this.abortController?.signal });
|
|
199
|
+
if (!response.ok) throw new Error(`[LogViewer] dataSource request failed: ${response.status}`);
|
|
200
|
+
payload = await response.json();
|
|
201
|
+
}
|
|
202
|
+
if (this.destroyed || currentRequest !== this.requestId) return;
|
|
203
|
+
this.setData(sourceEntries(payload));
|
|
204
|
+
} catch (error) {
|
|
205
|
+
if (this.destroyed || typeof DOMException !== "undefined" && error instanceof DOMException && error.name === "AbortError") return;
|
|
206
|
+
this.config.onError?.(error);
|
|
207
|
+
this.emitter.emit("error", error);
|
|
208
|
+
throw error;
|
|
209
|
+
}
|
|
124
210
|
}
|
|
125
211
|
destroy() {
|
|
126
212
|
if (this.destroyed) return;
|
|
127
213
|
this.destroyed = true;
|
|
214
|
+
this.requestId++;
|
|
215
|
+
this.abortController?.abort();
|
|
216
|
+
this.abortController = null;
|
|
128
217
|
this.emitter.removeAllListeners();
|
|
218
|
+
this.expandedIds.clear();
|
|
219
|
+
this.root.replaceChildren();
|
|
129
220
|
this.root.remove();
|
|
130
221
|
}
|
|
131
222
|
applyFilterAndRender() {
|
|
132
223
|
this.filteredData = this.filterEngine.filter(this.data, this.filter);
|
|
133
|
-
this.filteredData.
|
|
224
|
+
this.filteredData = this.filteredData.map((entry, index) => ({
|
|
225
|
+
entry,
|
|
226
|
+
index
|
|
227
|
+
})).sort((a, b) => {
|
|
228
|
+
const left = new Date(a.entry.timestamp).getTime();
|
|
229
|
+
const right = new Date(b.entry.timestamp).getTime();
|
|
230
|
+
const leftTime = Number.isFinite(left) ? left : Number.MIN_SAFE_INTEGER;
|
|
231
|
+
return (Number.isFinite(right) ? right : Number.MIN_SAFE_INTEGER) - leftTime || a.index - b.index;
|
|
232
|
+
}).map(({ entry }) => entry);
|
|
233
|
+
const totalPages = Math.max(1, Math.ceil(this.filteredData.length / this.pagination.pageSize));
|
|
234
|
+
this.pagination.page = Math.min(this.pagination.page, totalPages);
|
|
134
235
|
this.pagination.total = this.filteredData.length;
|
|
135
236
|
this.renderTimeline();
|
|
136
237
|
}
|
|
@@ -139,43 +240,121 @@ var LogViewer = class {
|
|
|
139
240
|
const start = (page - 1) * pageSize;
|
|
140
241
|
return this.filteredData.slice(start, start + pageSize);
|
|
141
242
|
}
|
|
243
|
+
renderFilterBar() {
|
|
244
|
+
if (this.config.showFilter === false) return null;
|
|
245
|
+
const filter = document.createElement("div");
|
|
246
|
+
filter.className = "lv-filter";
|
|
247
|
+
filter.setAttribute("role", "search");
|
|
248
|
+
const addInput = (field, label) => {
|
|
249
|
+
const item = document.createElement("label");
|
|
250
|
+
item.className = "lv-filter-item";
|
|
251
|
+
const text = document.createElement("span");
|
|
252
|
+
text.className = "lv-filter-label";
|
|
253
|
+
text.textContent = label;
|
|
254
|
+
const input = document.createElement("input");
|
|
255
|
+
input.className = "lv-filter-input";
|
|
256
|
+
input.type = "search";
|
|
257
|
+
input.value = String(this.filter[field] || "");
|
|
258
|
+
input.setAttribute("data-filter-field", field);
|
|
259
|
+
input.setAttribute("aria-label", label);
|
|
260
|
+
item.append(text, input);
|
|
261
|
+
filter.appendChild(item);
|
|
262
|
+
};
|
|
263
|
+
addInput("keyword", this.config.locale === "en-US" ? "Keyword" : "关键词");
|
|
264
|
+
addInput("user", this.config.locale === "en-US" ? "User" : "用户");
|
|
265
|
+
addInput("module", this.config.locale === "en-US" ? "Module" : "模块");
|
|
266
|
+
addInput("ip", "IP");
|
|
267
|
+
const actionItem = document.createElement("label");
|
|
268
|
+
actionItem.className = "lv-filter-item";
|
|
269
|
+
const actionLabel = document.createElement("span");
|
|
270
|
+
actionLabel.className = "lv-filter-label";
|
|
271
|
+
actionLabel.textContent = this.config.locale === "en-US" ? "Action" : "操作";
|
|
272
|
+
const action = document.createElement("select");
|
|
273
|
+
action.className = "lv-filter-select";
|
|
274
|
+
action.setAttribute("data-filter-field", "action");
|
|
275
|
+
action.setAttribute("aria-label", actionLabel.textContent);
|
|
276
|
+
const allOption = document.createElement("option");
|
|
277
|
+
allOption.value = "";
|
|
278
|
+
allOption.textContent = this.config.locale === "en-US" ? "All" : "全部";
|
|
279
|
+
action.appendChild(allOption);
|
|
280
|
+
for (const [value, label] of Object.entries(ACTION_LABELS)) {
|
|
281
|
+
const option = document.createElement("option");
|
|
282
|
+
option.value = value;
|
|
283
|
+
option.textContent = label;
|
|
284
|
+
action.appendChild(option);
|
|
285
|
+
}
|
|
286
|
+
action.value = Array.isArray(this.filter.action) ? "" : this.filter.action || "";
|
|
287
|
+
actionItem.append(actionLabel, action);
|
|
288
|
+
filter.appendChild(actionItem);
|
|
289
|
+
const reset = document.createElement("button");
|
|
290
|
+
reset.type = "button";
|
|
291
|
+
reset.className = "lv-filter-btn";
|
|
292
|
+
reset.setAttribute("data-filter-reset", "true");
|
|
293
|
+
reset.textContent = this.config.locale === "en-US" ? "Reset" : "重置";
|
|
294
|
+
filter.appendChild(reset);
|
|
295
|
+
return filter;
|
|
296
|
+
}
|
|
297
|
+
updateFilterField(field, value) {
|
|
298
|
+
const next = this.getFilter();
|
|
299
|
+
if (!value.trim()) delete next[field];
|
|
300
|
+
else if (field === "action" && isActionType(value)) next.action = value;
|
|
301
|
+
else if (field !== "action") next[field] = value;
|
|
302
|
+
this.setFilter(next);
|
|
303
|
+
}
|
|
142
304
|
renderTimeline() {
|
|
143
305
|
if (this.destroyed) return;
|
|
144
306
|
const pageData = this.getPageData();
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
307
|
+
const timeline = this.timelineRenderer.render(pageData, this.expandedIds, {
|
|
308
|
+
showDiff: this.config.showDiff,
|
|
309
|
+
showAnomalyBadge: this.config.showAnomalyBadge,
|
|
310
|
+
locale: this.config.locale
|
|
311
|
+
});
|
|
312
|
+
if (this.config.showDiff !== false) for (const entry of pageData) {
|
|
313
|
+
if (!this.expandedIds.has(entry.id)) continue;
|
|
314
|
+
([...timeline.children].find((child) => child.getAttribute("data-id") === entry.id)?.querySelector(".lv-timeline-card"))?.appendChild(this.diffRenderer.renderDetail(entry));
|
|
149
315
|
}
|
|
150
|
-
|
|
151
|
-
this.timelineEl
|
|
152
|
-
this.
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
this.paginationEl =
|
|
156
|
-
this.root.appendChild(this.
|
|
316
|
+
this.filterEl?.remove();
|
|
317
|
+
this.timelineEl?.remove();
|
|
318
|
+
this.paginationEl?.remove();
|
|
319
|
+
this.filterEl = this.renderFilterBar();
|
|
320
|
+
this.timelineEl = timeline;
|
|
321
|
+
this.paginationEl = this.timelineRenderer.renderPagination(this.pagination.page, this.pagination.pageSize, this.filteredData.length);
|
|
322
|
+
if (this.filterEl) this.root.appendChild(this.filterEl);
|
|
323
|
+
this.root.append(this.timelineEl, this.paginationEl);
|
|
157
324
|
}
|
|
158
325
|
bindEvents() {
|
|
159
|
-
this.root.addEventListener("
|
|
160
|
-
const target =
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
326
|
+
this.root.addEventListener("change", (event) => {
|
|
327
|
+
const target = event.target;
|
|
328
|
+
if (!(target instanceof HTMLInputElement) && !(target instanceof HTMLSelectElement)) return;
|
|
329
|
+
const field = target.getAttribute("data-filter-field");
|
|
330
|
+
if (field) this.updateFilterField(field, target.value);
|
|
331
|
+
});
|
|
332
|
+
this.root.addEventListener("click", (event) => {
|
|
333
|
+
const target = event.target;
|
|
334
|
+
if (!(target instanceof Element)) return;
|
|
335
|
+
if (target.closest("[data-filter-reset]")) {
|
|
336
|
+
this.resetFilter();
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
const hint = target.closest(".lv-timeline-expand-hint");
|
|
340
|
+
const id = (hint?.closest(".lv-timeline-item") || target.closest(".lv-timeline-item"))?.getAttribute("data-id");
|
|
341
|
+
if (hint && id) {
|
|
342
|
+
if (this.expandedIds.has(id)) this.collapseEntry(id);
|
|
343
|
+
else this.expandEntry(id);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
const pageButton = target.closest(".lv-pagination-btn");
|
|
347
|
+
if (pageButton && !pageButton.disabled) {
|
|
348
|
+
const page = Number(pageButton.getAttribute("data-page"));
|
|
349
|
+
if (Number.isFinite(page)) this.goToPage(page);
|
|
350
|
+
return;
|
|
174
351
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
352
|
+
if (!target.closest(".lv-timeline-card") || !id) return;
|
|
353
|
+
const entry = this.filteredData.find((candidate) => candidate.id === id);
|
|
354
|
+
if (entry) {
|
|
355
|
+
const snapshot = deepClone(entry);
|
|
356
|
+
this.emitter.emit("entry:click", snapshot);
|
|
357
|
+
this.config.onEntryClick?.(snapshot);
|
|
179
358
|
}
|
|
180
359
|
});
|
|
181
360
|
}
|