logbubble 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/ui/logUI.ts ADDED
@@ -0,0 +1,395 @@
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();