logbubble 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -0
- package/dist/auto.js +0 -2
- package/dist/core/console.js +19 -10
- package/dist/core/dom.js +9 -2
- package/dist/core/fetch.js +12 -2
- package/dist/core/xhr.js +11 -2
- package/dist/init.js +12 -7
- package/dist/ui/dragHandler.d.ts +22 -0
- package/dist/ui/dragHandler.js +127 -0
- package/dist/ui/filterManager.d.ts +15 -0
- package/dist/ui/filterManager.js +61 -0
- package/dist/ui/logStore.d.ts +16 -3
- package/dist/ui/logStore.js +152 -8
- package/dist/ui/logUI.d.ts +28 -1
- package/dist/ui/logUI.js +306 -218
- package/dist/ui/styles.d.ts +1 -0
- package/dist/ui/styles.js +384 -0
- package/dist/ui/virtualScroller.d.ts +24 -0
- package/dist/ui/virtualScroller.js +112 -0
- package/dist/vite-logbubble-plugin.d.ts +3 -0
- package/dist/vite-logbubble-plugin.js +20 -0
- package/package.json +24 -9
- package/auto.ts +0 -4
- package/core/console.ts +0 -79
- package/core/dom.ts +0 -33
- package/core/fetch.ts +0 -41
- package/core/xhr.ts +0 -38
- package/init.ts +0 -41
- package/ui/logStore.ts +0 -49
- package/ui/logUI.ts +0 -395
package/core/dom.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
// dom.ts
|
|
2
|
-
// (Optional) Patch dynamic <script> and <link> loading for network logging
|
|
3
|
-
|
|
4
|
-
import { logStore } from "../ui/logStore";
|
|
5
|
-
|
|
6
|
-
export function patchDOM(logFn: (msg: string) => void) {
|
|
7
|
-
if (typeof window === "undefined") return;
|
|
8
|
-
const origCreateElement = document.createElement;
|
|
9
|
-
document.createElement = function (
|
|
10
|
-
this: Document,
|
|
11
|
-
tag: string,
|
|
12
|
-
options?: ElementCreationOptions,
|
|
13
|
-
) {
|
|
14
|
-
const el = origCreateElement.call(this, tag, options);
|
|
15
|
-
if (tag === "script" || tag === "link") {
|
|
16
|
-
el.addEventListener("load", function () {
|
|
17
|
-
const src =
|
|
18
|
-
(el as HTMLScriptElement).src || (el as HTMLLinkElement).href;
|
|
19
|
-
const message = `[NET] ${tag.toUpperCase()} ${src} LOADED`;
|
|
20
|
-
logFn(message);
|
|
21
|
-
logStore.addLog(message, "dom");
|
|
22
|
-
});
|
|
23
|
-
el.addEventListener("error", function () {
|
|
24
|
-
const src =
|
|
25
|
-
(el as HTMLScriptElement).src || (el as HTMLLinkElement).href;
|
|
26
|
-
const message = `[NET] ${tag.toUpperCase()} ${src} ERROR`;
|
|
27
|
-
logFn(message);
|
|
28
|
-
logStore.addLog(message, "dom");
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
return el;
|
|
32
|
-
} as typeof document.createElement;
|
|
33
|
-
}
|
package/core/fetch.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
// fetch.ts
|
|
2
|
-
// Patches window.fetch to log network requests in dev mode (browser/WebView only)
|
|
3
|
-
|
|
4
|
-
import { logStore } from "../ui/logStore";
|
|
5
|
-
|
|
6
|
-
export function patchFetch(logFn: (msg: string) => void) {
|
|
7
|
-
if (typeof window === "undefined" || typeof window.fetch !== "function")
|
|
8
|
-
return;
|
|
9
|
-
const originalFetch = window.fetch;
|
|
10
|
-
window.fetch = async function (
|
|
11
|
-
this: typeof globalThis,
|
|
12
|
-
input: RequestInfo | URL,
|
|
13
|
-
init?: RequestInit,
|
|
14
|
-
) {
|
|
15
|
-
const method =
|
|
16
|
-
(init && init.method) ||
|
|
17
|
-
(typeof input === "object" && "method" in input && input.method) ||
|
|
18
|
-
"GET";
|
|
19
|
-
const url =
|
|
20
|
-
typeof input === "string"
|
|
21
|
-
? input
|
|
22
|
-
: input instanceof URL
|
|
23
|
-
? input.href
|
|
24
|
-
: input.url;
|
|
25
|
-
const start = Date.now();
|
|
26
|
-
try {
|
|
27
|
-
const response = await originalFetch.call(this, input, init);
|
|
28
|
-
const ms = Date.now() - start;
|
|
29
|
-
const message = `[NET] ${method} ${url} ${response.status} ${ms}ms`;
|
|
30
|
-
logFn(message);
|
|
31
|
-
logStore.addLog(message, "fetch");
|
|
32
|
-
return response;
|
|
33
|
-
} catch (err) {
|
|
34
|
-
const ms = Date.now() - start;
|
|
35
|
-
const message = `[NET] ${method} ${url} ERROR ${ms}ms`;
|
|
36
|
-
logFn(message);
|
|
37
|
-
logStore.addLog(message, "fetch");
|
|
38
|
-
throw err;
|
|
39
|
-
}
|
|
40
|
-
} as typeof window.fetch;
|
|
41
|
-
}
|
package/core/xhr.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
// xhr.ts
|
|
2
|
-
// Patches XMLHttpRequest to log network requests in dev mode (browser/WebView only)
|
|
3
|
-
|
|
4
|
-
import { logStore } from "../ui/logStore";
|
|
5
|
-
|
|
6
|
-
export function patchXHR(logFn: (msg: string) => void) {
|
|
7
|
-
if (
|
|
8
|
-
typeof window === "undefined" ||
|
|
9
|
-
typeof window.XMLHttpRequest !== "function"
|
|
10
|
-
)
|
|
11
|
-
return;
|
|
12
|
-
const OriginalXHR = window.XMLHttpRequest;
|
|
13
|
-
function PatchedXHR(this: XMLHttpRequest) {
|
|
14
|
-
const xhr = new OriginalXHR();
|
|
15
|
-
let url = "";
|
|
16
|
-
let method = "";
|
|
17
|
-
let start = 0;
|
|
18
|
-
xhr.open = new Proxy(xhr.open, {
|
|
19
|
-
apply(target, thisArg, args: [string, string | URL, ...any[]]) {
|
|
20
|
-
method = args[0];
|
|
21
|
-
url = typeof args[1] === "string" ? args[1] : args[1].href;
|
|
22
|
-
return Reflect.apply(target, thisArg, args);
|
|
23
|
-
},
|
|
24
|
-
});
|
|
25
|
-
xhr.addEventListener("loadstart", () => {
|
|
26
|
-
start = Date.now();
|
|
27
|
-
});
|
|
28
|
-
xhr.addEventListener("loadend", () => {
|
|
29
|
-
const ms = Date.now() - start;
|
|
30
|
-
const message = `[NET] ${method} ${url} ${xhr.status} ${ms}ms`;
|
|
31
|
-
logFn(message);
|
|
32
|
-
logStore.addLog(message, "xhr");
|
|
33
|
-
});
|
|
34
|
-
return xhr;
|
|
35
|
-
}
|
|
36
|
-
PatchedXHR.prototype = OriginalXHR.prototype;
|
|
37
|
-
window.XMLHttpRequest = PatchedXHR as any;
|
|
38
|
-
}
|
package/init.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
// init.ts
|
|
2
|
-
// Entry point for web-only dev-network-logger
|
|
3
|
-
import { patchFetch } from "./core/fetch";
|
|
4
|
-
import { patchXHR } from "./core/xhr";
|
|
5
|
-
import { patchDOM } from "./core/dom";
|
|
6
|
-
import { patchConsole } from "./core/console";
|
|
7
|
-
import { logUI } from "./ui/logUI";
|
|
8
|
-
|
|
9
|
-
let isInitialized = false;
|
|
10
|
-
|
|
11
|
-
function isDev() {
|
|
12
|
-
return (
|
|
13
|
-
typeof process !== "undefined" &&
|
|
14
|
-
process.env &&
|
|
15
|
-
(process.env.NODE_ENV === "development" || process.env.NODE_ENV === "dev")
|
|
16
|
-
);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function initNetworkLogger() {
|
|
20
|
-
if (isInitialized) {
|
|
21
|
-
console.warn("[LogBubble] Already initialized, skipping duplicate init");
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
isInitialized = true;
|
|
25
|
-
|
|
26
|
-
logUI.init();
|
|
27
|
-
// Patch fetch/XHR/DOM/Console in dev mode
|
|
28
|
-
patchFetch(() => {});
|
|
29
|
-
patchXHR(() => {});
|
|
30
|
-
patchDOM(() => {});
|
|
31
|
-
patchConsole();
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Auto-init for browser
|
|
35
|
-
if (typeof window !== "undefined") {
|
|
36
|
-
(window as any).initNetworkLogger = initNetworkLogger;
|
|
37
|
-
// Optionally auto-init in dev
|
|
38
|
-
// if (isDev()) {
|
|
39
|
-
initNetworkLogger();
|
|
40
|
-
// }
|
|
41
|
-
}
|
package/ui/logStore.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
// logStore.ts
|
|
2
|
-
// Global log store and API for collecting logs from JS
|
|
3
|
-
|
|
4
|
-
export interface LogEntry {
|
|
5
|
-
timestamp: number;
|
|
6
|
-
message: string;
|
|
7
|
-
type: "fetch" | "xhr" | "dom" | "plugin";
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
class LogStore {
|
|
11
|
-
private logs: LogEntry[] = [];
|
|
12
|
-
private listeners: Array<(log: LogEntry) => void> = [];
|
|
13
|
-
private maxLogs = 500;
|
|
14
|
-
|
|
15
|
-
addLog(message: string, type: LogEntry["type"] = "fetch") {
|
|
16
|
-
const log: LogEntry = {
|
|
17
|
-
timestamp: Date.now(),
|
|
18
|
-
message,
|
|
19
|
-
type,
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
this.logs.push(log);
|
|
23
|
-
|
|
24
|
-
// Keep only the last maxLogs entries
|
|
25
|
-
if (this.logs.length > this.maxLogs) {
|
|
26
|
-
this.logs.shift();
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Notify all listeners
|
|
30
|
-
this.listeners.forEach((listener) => listener(log));
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
getLogs(): LogEntry[] {
|
|
34
|
-
return [...this.logs];
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
clearLogs() {
|
|
38
|
-
this.logs = [];
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
subscribe(listener: (log: LogEntry) => void) {
|
|
42
|
-
this.listeners.push(listener);
|
|
43
|
-
return () => {
|
|
44
|
-
this.listeners = this.listeners.filter((l) => l !== listener);
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export const logStore = new LogStore();
|
package/ui/logUI.ts
DELETED
|
@@ -1,395 +0,0 @@
|
|
|
1
|
-
// logUI.ts
|
|
2
|
-
// Vanilla JS floating log UI
|
|
3
|
-
|
|
4
|
-
import { logStore, LogEntry } from "./logStore";
|
|
5
|
-
|
|
6
|
-
const UI_STYLES = `
|
|
7
|
-
#dev-net-logger-root * {
|
|
8
|
-
box-sizing: border-box;
|
|
9
|
-
margin: 0;
|
|
10
|
-
padding: 0;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
#dev-net-logger-button {
|
|
14
|
-
position: fixed;
|
|
15
|
-
top: 10px;
|
|
16
|
-
right: 10px;
|
|
17
|
-
width: 48px;
|
|
18
|
-
height: 48px;
|
|
19
|
-
border-radius: 50%;
|
|
20
|
-
background: #2563eb;
|
|
21
|
-
color: white;
|
|
22
|
-
border: none;
|
|
23
|
-
font-size: 18px;
|
|
24
|
-
font-weight: bold;
|
|
25
|
-
cursor: pointer;
|
|
26
|
-
z-index: 999999;
|
|
27
|
-
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
|
|
28
|
-
display: flex;
|
|
29
|
-
align-items: center;
|
|
30
|
-
justify-content: center;
|
|
31
|
-
font-family: monospace;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
#dev-net-logger-button:active {
|
|
35
|
-
transform: scale(0.95);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
#dev-net-logger-badge {
|
|
39
|
-
position: absolute;
|
|
40
|
-
top: -4px;
|
|
41
|
-
right: -4px;
|
|
42
|
-
min-width: 18px;
|
|
43
|
-
height: 18px;
|
|
44
|
-
border-radius: 9px;
|
|
45
|
-
background: #ef4444;
|
|
46
|
-
color: white;
|
|
47
|
-
font-size: 10px;
|
|
48
|
-
font-weight: bold;
|
|
49
|
-
display: none;
|
|
50
|
-
align-items: center;
|
|
51
|
-
justify-content: center;
|
|
52
|
-
padding: 0 4px;
|
|
53
|
-
font-family: monospace;
|
|
54
|
-
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
#dev-net-logger-badge.visible {
|
|
58
|
-
display: flex;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
#dev-net-logger-backdrop {
|
|
62
|
-
position: fixed;
|
|
63
|
-
top: 0;
|
|
64
|
-
left: 0;
|
|
65
|
-
right: 0;
|
|
66
|
-
bottom: 0;
|
|
67
|
-
background: rgba(0, 0, 0, 0.4);
|
|
68
|
-
z-index: 999997;
|
|
69
|
-
display: none;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
#dev-net-logger-backdrop.visible {
|
|
73
|
-
display: block;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
#dev-net-logger-window {
|
|
77
|
-
position: fixed;
|
|
78
|
-
top: 70px;
|
|
79
|
-
right: 10px;
|
|
80
|
-
width: calc(100vw - 20px);
|
|
81
|
-
max-width: 500px;
|
|
82
|
-
height: 60vh;
|
|
83
|
-
background: rgba(0, 0, 0, 0.95);
|
|
84
|
-
border-radius: 8px;
|
|
85
|
-
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5);
|
|
86
|
-
z-index: 999998;
|
|
87
|
-
display: none;
|
|
88
|
-
flex-direction: column;
|
|
89
|
-
overflow: hidden;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
#dev-net-logger-window.visible {
|
|
93
|
-
display: flex;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
#dev-net-logger-header {
|
|
97
|
-
display: flex;
|
|
98
|
-
justify-content: space-between;
|
|
99
|
-
align-items: center;
|
|
100
|
-
padding: 12px 16px;
|
|
101
|
-
background: #1e293b;
|
|
102
|
-
border-bottom: 1px solid #334155;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
#dev-net-logger-title {
|
|
106
|
-
color: #f1f5f9;
|
|
107
|
-
font-size: 14px;
|
|
108
|
-
font-weight: bold;
|
|
109
|
-
font-family: monospace;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
#dev-net-logger-controls {
|
|
113
|
-
display: flex;
|
|
114
|
-
gap: 8px;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
#dev-net-logger-controls button {
|
|
118
|
-
padding: 4px 8px;
|
|
119
|
-
font-size: 11px;
|
|
120
|
-
background: #334155;
|
|
121
|
-
color: #f1f5f9;
|
|
122
|
-
border: none;
|
|
123
|
-
border-radius: 4px;
|
|
124
|
-
cursor: pointer;
|
|
125
|
-
font-family: monospace;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
#dev-net-logger-controls button:active {
|
|
129
|
-
background: #475569;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
#dev-net-logger-content {
|
|
133
|
-
flex: 1;
|
|
134
|
-
overflow-y: auto;
|
|
135
|
-
padding: 8px;
|
|
136
|
-
font-family: monospace;
|
|
137
|
-
font-size: 11px;
|
|
138
|
-
line-height: 1.4;
|
|
139
|
-
color: #e2e8f0;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
.log-entry {
|
|
143
|
-
padding: 6px 8px;
|
|
144
|
-
margin-bottom: 4px;
|
|
145
|
-
border-radius: 4px;
|
|
146
|
-
background: #0f172a;
|
|
147
|
-
border-left: 3px solid #64748b;
|
|
148
|
-
word-break: break-word;
|
|
149
|
-
overflow-wrap: break-word;
|
|
150
|
-
display: flex;
|
|
151
|
-
flex-wrap: wrap;
|
|
152
|
-
align-items: baseline;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
.log-entry.type-fetch {
|
|
156
|
-
border-left-color: #3b82f6;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
.log-entry.type-xhr {
|
|
160
|
-
border-left-color: #8b5cf6;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
.log-entry.type-dom {
|
|
164
|
-
border-left-color: #f59e0b;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
.log-entry.type-plugin {
|
|
168
|
-
border-left-color: #ec4899;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
.log-timestamp {
|
|
172
|
-
color: #64748b;
|
|
173
|
-
font-size: 10px;
|
|
174
|
-
margin-right: 8px;
|
|
175
|
-
flex-shrink: 0;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
.log-message {
|
|
179
|
-
color: #e2e8f0;
|
|
180
|
-
word-break: break-word;
|
|
181
|
-
overflow-wrap: break-word;
|
|
182
|
-
flex: 1;
|
|
183
|
-
min-width: 0;
|
|
184
|
-
}
|
|
185
|
-
`;
|
|
186
|
-
|
|
187
|
-
export class LogUI {
|
|
188
|
-
private rootEl: HTMLDivElement | null = null;
|
|
189
|
-
private buttonEl: HTMLDivElement | null = null;
|
|
190
|
-
private badgeEl: HTMLDivElement | null = null;
|
|
191
|
-
private backdropEl: HTMLDivElement | null = null;
|
|
192
|
-
private windowEl: HTMLDivElement | null = null;
|
|
193
|
-
private contentEl: HTMLDivElement | null = null;
|
|
194
|
-
private isVisible = false;
|
|
195
|
-
private unreadCount = 0;
|
|
196
|
-
private unsubscribe: (() => void) | null = null;
|
|
197
|
-
|
|
198
|
-
init() {
|
|
199
|
-
if (typeof window === "undefined" || typeof document === "undefined")
|
|
200
|
-
return;
|
|
201
|
-
if (this.rootEl) return; // Already initialized
|
|
202
|
-
|
|
203
|
-
this.createUI();
|
|
204
|
-
this.attachEventListeners();
|
|
205
|
-
this.subscribeToLogs();
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
private createUI() {
|
|
209
|
-
// Inject styles
|
|
210
|
-
const styleEl = document.createElement("style");
|
|
211
|
-
styleEl.textContent = UI_STYLES;
|
|
212
|
-
document.head.appendChild(styleEl);
|
|
213
|
-
|
|
214
|
-
// Create root container
|
|
215
|
-
this.rootEl = document.createElement("div");
|
|
216
|
-
this.rootEl.id = "dev-net-logger-root";
|
|
217
|
-
|
|
218
|
-
// Create button
|
|
219
|
-
this.buttonEl = document.createElement("div");
|
|
220
|
-
this.buttonEl.id = "dev-net-logger-button";
|
|
221
|
-
this.buttonEl.textContent = "📡";
|
|
222
|
-
this.buttonEl.title = "Network Logger";
|
|
223
|
-
|
|
224
|
-
// Create badge
|
|
225
|
-
this.badgeEl = document.createElement("div");
|
|
226
|
-
this.badgeEl.id = "dev-net-logger-badge";
|
|
227
|
-
this.buttonEl.appendChild(this.badgeEl);
|
|
228
|
-
|
|
229
|
-
// Create backdrop
|
|
230
|
-
this.backdropEl = document.createElement("div");
|
|
231
|
-
this.backdropEl.id = "dev-net-logger-backdrop";
|
|
232
|
-
|
|
233
|
-
// Create window
|
|
234
|
-
this.windowEl = document.createElement("div");
|
|
235
|
-
this.windowEl.id = "dev-net-logger-window";
|
|
236
|
-
|
|
237
|
-
// Create header
|
|
238
|
-
const headerEl = document.createElement("div");
|
|
239
|
-
headerEl.id = "dev-net-logger-header";
|
|
240
|
-
|
|
241
|
-
const titleEl = document.createElement("div");
|
|
242
|
-
titleEl.id = "dev-net-logger-title";
|
|
243
|
-
titleEl.textContent = "Network Logs";
|
|
244
|
-
|
|
245
|
-
const controlsEl = document.createElement("div");
|
|
246
|
-
controlsEl.id = "dev-net-logger-controls";
|
|
247
|
-
|
|
248
|
-
const clearBtn = document.createElement("button");
|
|
249
|
-
clearBtn.textContent = "Clear";
|
|
250
|
-
clearBtn.onclick = () => this.clearLogs();
|
|
251
|
-
|
|
252
|
-
const copyBtn = document.createElement("button");
|
|
253
|
-
copyBtn.textContent = "Copy";
|
|
254
|
-
copyBtn.onclick = () => this.copyLogs();
|
|
255
|
-
|
|
256
|
-
controlsEl.appendChild(clearBtn);
|
|
257
|
-
controlsEl.appendChild(copyBtn);
|
|
258
|
-
|
|
259
|
-
headerEl.appendChild(titleEl);
|
|
260
|
-
headerEl.appendChild(controlsEl);
|
|
261
|
-
|
|
262
|
-
// Create content area
|
|
263
|
-
this.contentEl = document.createElement("div");
|
|
264
|
-
this.contentEl.id = "dev-net-logger-content";
|
|
265
|
-
|
|
266
|
-
this.windowEl.appendChild(headerEl);
|
|
267
|
-
this.windowEl.appendChild(this.contentEl);
|
|
268
|
-
|
|
269
|
-
this.rootEl.appendChild(this.buttonEl);
|
|
270
|
-
this.rootEl.appendChild(this.backdropEl);
|
|
271
|
-
this.rootEl.appendChild(this.windowEl);
|
|
272
|
-
|
|
273
|
-
document.body.appendChild(this.rootEl);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
private attachEventListeners() {
|
|
277
|
-
if (!this.buttonEl || !this.windowEl || !this.backdropEl) return;
|
|
278
|
-
|
|
279
|
-
this.buttonEl.addEventListener("click", () => {
|
|
280
|
-
this.toggleWindow();
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
// Close window when clicking backdrop
|
|
284
|
-
this.backdropEl.addEventListener("click", () => {
|
|
285
|
-
this.toggleWindow();
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
private subscribeToLogs() {
|
|
290
|
-
// Load existing logs
|
|
291
|
-
const existingLogs = logStore.getLogs();
|
|
292
|
-
existingLogs.forEach((log) => this.addLogToUI(log));
|
|
293
|
-
|
|
294
|
-
// Subscribe to new logs
|
|
295
|
-
this.unsubscribe = logStore.subscribe((log) => {
|
|
296
|
-
this.addLogToUI(log);
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
private addLogToUI(log: LogEntry) {
|
|
301
|
-
if (!this.contentEl) return;
|
|
302
|
-
|
|
303
|
-
// Increment unread count if window is closed
|
|
304
|
-
if (!this.isVisible) {
|
|
305
|
-
this.unreadCount++;
|
|
306
|
-
this.updateBadge();
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
const logEl = document.createElement("div");
|
|
310
|
-
logEl.className = `log-entry type-${log.type}`;
|
|
311
|
-
|
|
312
|
-
const timestampEl = document.createElement("span");
|
|
313
|
-
timestampEl.className = "log-timestamp";
|
|
314
|
-
const date = new Date(log.timestamp);
|
|
315
|
-
timestampEl.textContent = `${date.getHours().toString().padStart(2, "0")}:${date.getMinutes().toString().padStart(2, "0")}:${date.getSeconds().toString().padStart(2, "0")}`;
|
|
316
|
-
|
|
317
|
-
const messageEl = document.createElement("span");
|
|
318
|
-
messageEl.className = "log-message";
|
|
319
|
-
messageEl.textContent = log.message;
|
|
320
|
-
|
|
321
|
-
logEl.appendChild(timestampEl);
|
|
322
|
-
logEl.appendChild(messageEl);
|
|
323
|
-
|
|
324
|
-
this.contentEl.appendChild(logEl);
|
|
325
|
-
|
|
326
|
-
// Auto-scroll to bottom
|
|
327
|
-
this.contentEl.scrollTop = this.contentEl.scrollHeight;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
private toggleWindow() {
|
|
331
|
-
if (!this.windowEl || !this.backdropEl) return;
|
|
332
|
-
this.isVisible = !this.isVisible;
|
|
333
|
-
if (this.isVisible) {
|
|
334
|
-
this.windowEl.classList.add("visible");
|
|
335
|
-
this.backdropEl.classList.add("visible");
|
|
336
|
-
// Prevent body scroll
|
|
337
|
-
document.body.style.overflow = "hidden";
|
|
338
|
-
// Reset unread count when opening
|
|
339
|
-
this.unreadCount = 0;
|
|
340
|
-
this.updateBadge();
|
|
341
|
-
} else {
|
|
342
|
-
this.windowEl.classList.remove("visible");
|
|
343
|
-
this.backdropEl.classList.remove("visible");
|
|
344
|
-
// Restore body scroll
|
|
345
|
-
document.body.style.overflow = "";
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
private updateBadge() {
|
|
350
|
-
if (!this.badgeEl) return;
|
|
351
|
-
if (this.unreadCount > 0) {
|
|
352
|
-
this.badgeEl.textContent =
|
|
353
|
-
this.unreadCount > 99 ? "99+" : String(this.unreadCount);
|
|
354
|
-
this.badgeEl.classList.add("visible");
|
|
355
|
-
} else {
|
|
356
|
-
this.badgeEl.classList.remove("visible");
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
private clearLogs() {
|
|
361
|
-
if (!this.contentEl) return;
|
|
362
|
-
logStore.clearLogs();
|
|
363
|
-
this.contentEl.innerHTML = "";
|
|
364
|
-
this.unreadCount = 0;
|
|
365
|
-
this.updateBadge();
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
private copyLogs() {
|
|
369
|
-
const logs = logStore.getLogs();
|
|
370
|
-
const text = logs
|
|
371
|
-
.map((log) => {
|
|
372
|
-
const date = new Date(log.timestamp);
|
|
373
|
-
const time = `${date.getHours().toString().padStart(2, "0")}:${date.getMinutes().toString().padStart(2, "0")}:${date.getSeconds().toString().padStart(2, "0")}`;
|
|
374
|
-
return `[${time}] ${log.message}`;
|
|
375
|
-
})
|
|
376
|
-
.join("\n");
|
|
377
|
-
|
|
378
|
-
if (navigator.clipboard) {
|
|
379
|
-
navigator.clipboard.writeText(text).then(() => {
|
|
380
|
-
alert("Logs copied to clipboard!");
|
|
381
|
-
});
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
destroy() {
|
|
386
|
-
if (this.unsubscribe) {
|
|
387
|
-
this.unsubscribe();
|
|
388
|
-
}
|
|
389
|
-
if (this.rootEl) {
|
|
390
|
-
this.rootEl.remove();
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
export const logUI = new LogUI();
|