@djangocfg/debuger 2.1.219
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 +345 -0
- package/dist/chunk-ESIQODSI.mjs +115 -0
- package/dist/chunk-ESIQODSI.mjs.map +1 -0
- package/dist/chunk-PAWJFY3S.mjs +6 -0
- package/dist/chunk-PAWJFY3S.mjs.map +1 -0
- package/dist/chunk-YQE3KTBG.mjs +64 -0
- package/dist/chunk-YQE3KTBG.mjs.map +1 -0
- package/dist/customEmitter-BO-1IWxm.d.ts +57 -0
- package/dist/emitters/index.cjs +74 -0
- package/dist/emitters/index.cjs.map +1 -0
- package/dist/emitters/index.d.ts +33 -0
- package/dist/emitters/index.mjs +4 -0
- package/dist/emitters/index.mjs.map +1 -0
- package/dist/index.cjs +887 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +162 -0
- package/dist/index.mjs +694 -0
- package/dist/index.mjs.map +1 -0
- package/dist/logger/index.cjs +123 -0
- package/dist/logger/index.cjs.map +1 -0
- package/dist/logger/index.d.ts +50 -0
- package/dist/logger/index.mjs +4 -0
- package/dist/logger/index.mjs.map +1 -0
- package/package.json +67 -0
- package/src/DebugButton.tsx +143 -0
- package/src/DebugPanel.tsx +171 -0
- package/src/bridges/index.ts +1 -0
- package/src/bridges/monitorBridge.ts +63 -0
- package/src/emitters/Emitter.ts +51 -0
- package/src/emitters/audioEmitter.ts +74 -0
- package/src/emitters/customEmitter.ts +42 -0
- package/src/emitters/index.ts +10 -0
- package/src/hooks/useAudioEventLog.ts +147 -0
- package/src/hooks/useCustomEventLog.ts +39 -0
- package/src/hooks/useDebugShortcut.ts +27 -0
- package/src/hooks/useStoreSnapshot.ts +70 -0
- package/src/index.ts +62 -0
- package/src/logger/index.ts +3 -0
- package/src/logger/logStore.ts +89 -0
- package/src/logger/logger.ts +95 -0
- package/src/logger/types.ts +39 -0
- package/src/panels/AudioDebugPanel.tsx +157 -0
- package/src/panels/LogsPanel.tsx +267 -0
- package/src/panels/StorePanel.tsx +71 -0
- package/src/store/debugStore.ts +42 -0
- package/src/styles/index.css +5 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,887 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var lucideReact = require('lucide-react');
|
|
5
|
+
var components = require('@djangocfg/ui-core/components');
|
|
6
|
+
var lib = require('@djangocfg/ui-core/lib');
|
|
7
|
+
var zustand = require('zustand');
|
|
8
|
+
var hooks = require('@djangocfg/ui-core/hooks');
|
|
9
|
+
var client = require('@djangocfg/monitor/client');
|
|
10
|
+
var uiTools = require('@djangocfg/ui-tools');
|
|
11
|
+
var reactVirtual = require('@tanstack/react-virtual');
|
|
12
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
13
|
+
|
|
14
|
+
var __defProp = Object.defineProperty;
|
|
15
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
16
|
+
var useDebugStore = zustand.create((set) => ({
|
|
17
|
+
isOpen: false,
|
|
18
|
+
tab: "logs",
|
|
19
|
+
isUnlocked: false,
|
|
20
|
+
open: /* @__PURE__ */ __name(() => set({ isOpen: true }), "open"),
|
|
21
|
+
close: /* @__PURE__ */ __name(() => set({ isOpen: false }), "close"),
|
|
22
|
+
toggle: /* @__PURE__ */ __name(() => set((s) => ({ isOpen: !s.isOpen })), "toggle"),
|
|
23
|
+
setTab: /* @__PURE__ */ __name((tab) => set({ tab }), "setTab"),
|
|
24
|
+
unlock: /* @__PURE__ */ __name((_key) => {
|
|
25
|
+
const valid = true;
|
|
26
|
+
set({ isUnlocked: true });
|
|
27
|
+
return valid;
|
|
28
|
+
}, "unlock")
|
|
29
|
+
}));
|
|
30
|
+
function useDebugShortcut() {
|
|
31
|
+
const toggle = useDebugStore((s) => s.toggle);
|
|
32
|
+
const isUnlocked = useDebugStore((s) => s.isUnlocked);
|
|
33
|
+
const isDev2 = typeof process !== "undefined" && true;
|
|
34
|
+
hooks.useHotkey(
|
|
35
|
+
"meta+d",
|
|
36
|
+
(e) => {
|
|
37
|
+
e.preventDefault();
|
|
38
|
+
if (!isDev2 && !isUnlocked) return;
|
|
39
|
+
toggle();
|
|
40
|
+
},
|
|
41
|
+
{ preventDefault: true }
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
__name(useDebugShortcut, "useDebugShortcut");
|
|
45
|
+
var MAX_LOGS = 1e3;
|
|
46
|
+
var DEFAULT_FILTER = {
|
|
47
|
+
levels: ["debug", "info", "warn", "error", "success"]
|
|
48
|
+
};
|
|
49
|
+
var _counter = 0;
|
|
50
|
+
function generateId() {
|
|
51
|
+
return `log-${Date.now()}-${++_counter}`;
|
|
52
|
+
}
|
|
53
|
+
__name(generateId, "generateId");
|
|
54
|
+
function matchesFilter(entry, filter) {
|
|
55
|
+
if (!filter.levels.includes(entry.level)) return false;
|
|
56
|
+
if (filter.component) {
|
|
57
|
+
if (!entry.component.toLowerCase().includes(filter.component.toLowerCase())) return false;
|
|
58
|
+
}
|
|
59
|
+
if (filter.search) {
|
|
60
|
+
const s = filter.search.toLowerCase();
|
|
61
|
+
const inMsg = entry.message.toLowerCase().includes(s);
|
|
62
|
+
const inData = entry.data ? JSON.stringify(entry.data).toLowerCase().includes(s) : false;
|
|
63
|
+
if (!inMsg && !inData) return false;
|
|
64
|
+
}
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
__name(matchesFilter, "matchesFilter");
|
|
68
|
+
var useDebugLogStore = zustand.create((set, get) => ({
|
|
69
|
+
logs: [],
|
|
70
|
+
filter: DEFAULT_FILTER,
|
|
71
|
+
addLog: /* @__PURE__ */ __name((entry) => {
|
|
72
|
+
const newEntry = {
|
|
73
|
+
...entry,
|
|
74
|
+
id: generateId(),
|
|
75
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
76
|
+
};
|
|
77
|
+
set((state) => {
|
|
78
|
+
const next = [...state.logs, newEntry];
|
|
79
|
+
return { logs: next.length > MAX_LOGS ? next.slice(-MAX_LOGS) : next };
|
|
80
|
+
});
|
|
81
|
+
}, "addLog"),
|
|
82
|
+
clearLogs: /* @__PURE__ */ __name(() => set({ logs: [] }), "clearLogs"),
|
|
83
|
+
setFilter: /* @__PURE__ */ __name((filter) => set((state) => ({ filter: { ...state.filter, ...filter } })), "setFilter"),
|
|
84
|
+
getFilteredLogs: /* @__PURE__ */ __name(() => {
|
|
85
|
+
const { logs, filter } = get();
|
|
86
|
+
return logs.filter((e) => matchesFilter(e, filter));
|
|
87
|
+
}, "getFilteredLogs"),
|
|
88
|
+
exportLogs: /* @__PURE__ */ __name(() => JSON.stringify(get().logs, null, 2), "exportLogs")
|
|
89
|
+
}));
|
|
90
|
+
var useDebugFilteredLogs = /* @__PURE__ */ __name(() => {
|
|
91
|
+
const logs = useDebugLogStore((s) => s.logs);
|
|
92
|
+
const filter = useDebugLogStore((s) => s.filter);
|
|
93
|
+
return logs.filter((e) => matchesFilter(e, filter));
|
|
94
|
+
}, "useDebugFilteredLogs");
|
|
95
|
+
var useDebugLogCount = /* @__PURE__ */ __name(() => useDebugLogStore((s) => s.logs.length), "useDebugLogCount");
|
|
96
|
+
var useDebugErrorCount = /* @__PURE__ */ __name(() => useDebugLogStore((s) => s.logs.filter((l) => l.level === "error").length), "useDebugErrorCount");
|
|
97
|
+
var isBrowser = typeof window !== "undefined";
|
|
98
|
+
function extractStack(data) {
|
|
99
|
+
if (!data) return { cleanData: void 0, stack: void 0 };
|
|
100
|
+
const cleanData = { ...data };
|
|
101
|
+
let stack;
|
|
102
|
+
if (data.error instanceof Error) {
|
|
103
|
+
stack = data.error.stack;
|
|
104
|
+
cleanData.error = { name: data.error.name, message: data.error.message };
|
|
105
|
+
} else if (typeof data.error === "object" && data.error !== null) {
|
|
106
|
+
const e = data.error;
|
|
107
|
+
if (typeof e.stack === "string") stack = e.stack;
|
|
108
|
+
if (typeof e.message === "string") cleanData.error = e.message;
|
|
109
|
+
}
|
|
110
|
+
return { cleanData, stack };
|
|
111
|
+
}
|
|
112
|
+
__name(extractStack, "extractStack");
|
|
113
|
+
function createDebugLogger(component) {
|
|
114
|
+
const write = /* @__PURE__ */ __name((level, message, data) => {
|
|
115
|
+
const { cleanData, stack } = extractStack(data);
|
|
116
|
+
if (isBrowser) {
|
|
117
|
+
useDebugLogStore.getState().addLog({ level, component, message, data: cleanData, stack });
|
|
118
|
+
}
|
|
119
|
+
{
|
|
120
|
+
const method = level === "error" ? "error" : level === "warn" ? "warn" : "log";
|
|
121
|
+
const prefix = `[${component}]`;
|
|
122
|
+
if (cleanData) {
|
|
123
|
+
console[method](prefix, message, cleanData);
|
|
124
|
+
} else {
|
|
125
|
+
console[method](prefix, message);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}, "write");
|
|
129
|
+
return {
|
|
130
|
+
debug: /* @__PURE__ */ __name((msg, data) => write("debug", msg, data), "debug"),
|
|
131
|
+
info: /* @__PURE__ */ __name((msg, data) => write("info", msg, data), "info"),
|
|
132
|
+
warn: /* @__PURE__ */ __name((msg, data) => write("warn", msg, data), "warn"),
|
|
133
|
+
error: /* @__PURE__ */ __name((msg, data) => write("error", msg, data), "error"),
|
|
134
|
+
success: /* @__PURE__ */ __name((msg, data) => write("success", msg, data), "success")
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
__name(createDebugLogger, "createDebugLogger");
|
|
138
|
+
function debugLog(component, level, message, data) {
|
|
139
|
+
const { cleanData, stack } = extractStack(data);
|
|
140
|
+
if (isBrowser) {
|
|
141
|
+
useDebugLogStore.getState().addLog({ level, component, message, data: cleanData, stack });
|
|
142
|
+
}
|
|
143
|
+
{
|
|
144
|
+
const method = level === "error" ? "error" : level === "warn" ? "warn" : "log";
|
|
145
|
+
if (cleanData) {
|
|
146
|
+
console[method](`[${component}]`, message, cleanData);
|
|
147
|
+
} else {
|
|
148
|
+
console[method](`[${component}]`, message);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
__name(debugLog, "debugLog");
|
|
153
|
+
var _installed = false;
|
|
154
|
+
function installMonitorBridge() {
|
|
155
|
+
if (typeof window === "undefined") return;
|
|
156
|
+
if (_installed) return;
|
|
157
|
+
let prevLen = client.monitorStore.getState().buffer.length;
|
|
158
|
+
const unsub = client.monitorStore.subscribe((state) => {
|
|
159
|
+
const buffer = state.buffer;
|
|
160
|
+
if (buffer.length <= prevLen) {
|
|
161
|
+
prevLen = buffer.length;
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const newEvents = buffer.slice(prevLen);
|
|
165
|
+
prevLen = buffer.length;
|
|
166
|
+
for (const event of newEvents) {
|
|
167
|
+
const level = event.level === "error" ? "error" : event.level === "warn" ? "warn" : "info";
|
|
168
|
+
useDebugLogStore.getState().addLog({
|
|
169
|
+
level,
|
|
170
|
+
component: `monitor:${event.event_type ?? "event"}`,
|
|
171
|
+
message: event.message ?? "",
|
|
172
|
+
data: {
|
|
173
|
+
...event.url && { url: event.url },
|
|
174
|
+
...event.session_id && { session_id: event.session_id },
|
|
175
|
+
...event.http_status !== void 0 && {
|
|
176
|
+
http_status: event.http_status,
|
|
177
|
+
http_method: event.http_method,
|
|
178
|
+
http_url: event.http_url
|
|
179
|
+
},
|
|
180
|
+
...event.extra ? { extra: event.extra } : {}
|
|
181
|
+
},
|
|
182
|
+
stack: event.stack_trace
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
_installed = true;
|
|
187
|
+
return () => {
|
|
188
|
+
unsub();
|
|
189
|
+
_installed = false;
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
__name(installMonitorBridge, "installMonitorBridge");
|
|
193
|
+
var ROW_ESTIMATE_PX = 32;
|
|
194
|
+
var OVERSCAN = 5;
|
|
195
|
+
var LOG_LEVEL_CONFIG = {
|
|
196
|
+
debug: { icon: lucideReact.Bug, color: "text-muted-foreground" },
|
|
197
|
+
info: { icon: lucideReact.Info, color: "text-blue-500" },
|
|
198
|
+
warn: { icon: lucideReact.AlertTriangle, color: "text-yellow-500" },
|
|
199
|
+
error: { icon: lucideReact.AlertCircle, color: "text-red-500" },
|
|
200
|
+
success: { icon: lucideReact.CheckCircle, color: "text-green-500" }
|
|
201
|
+
};
|
|
202
|
+
var LOG_LEVELS = Object.keys(LOG_LEVEL_CONFIG);
|
|
203
|
+
function LogEntryRow({ entry, expanded, onToggle }) {
|
|
204
|
+
const config = LOG_LEVEL_CONFIG[entry.level];
|
|
205
|
+
const Icon = config.icon;
|
|
206
|
+
const pad = /* @__PURE__ */ __name((n, len = 2) => String(n).padStart(len, "0"), "pad");
|
|
207
|
+
const d = entry.timestamp;
|
|
208
|
+
const time = `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${pad(d.getMilliseconds(), 3)}`;
|
|
209
|
+
const hasData = entry.data && Object.keys(entry.data).length > 0;
|
|
210
|
+
const hasStack = !!entry.stack;
|
|
211
|
+
const isExpandable = hasData || hasStack;
|
|
212
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-b border-border/50 last:border-0", children: [
|
|
213
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
214
|
+
"button",
|
|
215
|
+
{
|
|
216
|
+
type: "button",
|
|
217
|
+
onClick: isExpandable ? onToggle : void 0,
|
|
218
|
+
disabled: !isExpandable,
|
|
219
|
+
className: lib.cn(
|
|
220
|
+
"flex w-full items-start gap-2 px-3 py-1.5 text-left text-xs",
|
|
221
|
+
isExpandable ? "hover:bg-muted/50 cursor-pointer" : "cursor-default"
|
|
222
|
+
),
|
|
223
|
+
children: [
|
|
224
|
+
/* @__PURE__ */ jsxRuntime.jsx(Icon, { className: lib.cn("h-3.5 w-3.5 mt-0.5 shrink-0", config.color) }),
|
|
225
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground font-mono shrink-0", children: time }),
|
|
226
|
+
/* @__PURE__ */ jsxRuntime.jsx(components.Badge, { variant: "outline", className: "shrink-0 text-[10px] px-1 py-0", children: entry.component }),
|
|
227
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: entry.message }),
|
|
228
|
+
isExpandable && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-muted-foreground", children: expanded ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronUp, { className: "h-3 w-3" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-3 w-3" }) })
|
|
229
|
+
]
|
|
230
|
+
}
|
|
231
|
+
),
|
|
232
|
+
expanded && isExpandable && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-3 pb-2 pl-8", children: [
|
|
233
|
+
hasData && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1", children: /* @__PURE__ */ jsxRuntime.jsx(uiTools.LazyJsonTree, { data: entry.data, mode: "compact" }) }),
|
|
234
|
+
hasStack && /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "mt-2 text-[10px] text-red-400 whitespace-pre-wrap font-mono bg-red-950/20 p-2 rounded", children: entry.stack })
|
|
235
|
+
] })
|
|
236
|
+
] });
|
|
237
|
+
}
|
|
238
|
+
__name(LogEntryRow, "LogEntryRow");
|
|
239
|
+
function LogsPanel({ isActive }) {
|
|
240
|
+
const [expandedLogs, setExpandedLogs] = react.useState(/* @__PURE__ */ new Set());
|
|
241
|
+
const [searchQuery, setSearchQuery] = react.useState("");
|
|
242
|
+
const [selectedLevels, setSelectedLevels] = react.useState(
|
|
243
|
+
/* @__PURE__ */ new Set(["debug", "info", "warn", "error", "success"])
|
|
244
|
+
);
|
|
245
|
+
const [componentFilter, setComponentFilter] = react.useState("");
|
|
246
|
+
const logs = useDebugFilteredLogs();
|
|
247
|
+
const logCount = useDebugLogCount();
|
|
248
|
+
const errorCount = useDebugErrorCount();
|
|
249
|
+
const clearLogs = useDebugLogStore((s) => s.clearLogs);
|
|
250
|
+
const setFilter = useDebugLogStore((s) => s.setFilter);
|
|
251
|
+
const exportLogs = useDebugLogStore((s) => s.exportLogs);
|
|
252
|
+
const scrollRef = react.useRef(null);
|
|
253
|
+
const virtualizer = reactVirtual.useVirtualizer({
|
|
254
|
+
count: logs.length,
|
|
255
|
+
getScrollElement: /* @__PURE__ */ __name(() => scrollRef.current, "getScrollElement"),
|
|
256
|
+
estimateSize: /* @__PURE__ */ __name(() => ROW_ESTIMATE_PX, "estimateSize"),
|
|
257
|
+
overscan: OVERSCAN
|
|
258
|
+
});
|
|
259
|
+
react.useEffect(() => {
|
|
260
|
+
if (!isActive) return;
|
|
261
|
+
setFilter({
|
|
262
|
+
levels: Array.from(selectedLevels),
|
|
263
|
+
component: componentFilter || void 0,
|
|
264
|
+
search: searchQuery || void 0
|
|
265
|
+
});
|
|
266
|
+
}, [isActive, selectedLevels, componentFilter, searchQuery, setFilter]);
|
|
267
|
+
const handleToggleExpand = react.useCallback((id) => {
|
|
268
|
+
setExpandedLogs((prev) => {
|
|
269
|
+
const next = new Set(prev);
|
|
270
|
+
next.has(id) ? next.delete(id) : next.add(id);
|
|
271
|
+
return next;
|
|
272
|
+
});
|
|
273
|
+
}, []);
|
|
274
|
+
const handleToggleLevel = react.useCallback((level) => {
|
|
275
|
+
setSelectedLevels((prev) => {
|
|
276
|
+
const next = new Set(prev);
|
|
277
|
+
next.has(level) ? next.delete(level) : next.add(level);
|
|
278
|
+
return next;
|
|
279
|
+
});
|
|
280
|
+
}, []);
|
|
281
|
+
const handleExport = react.useCallback(() => {
|
|
282
|
+
const json = exportLogs();
|
|
283
|
+
const blob = new Blob([json], { type: "application/json" });
|
|
284
|
+
const url = URL.createObjectURL(blob);
|
|
285
|
+
const a = document.createElement("a");
|
|
286
|
+
a.href = url;
|
|
287
|
+
a.download = `debug-logs-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace(/:/g, "-")}.json`;
|
|
288
|
+
a.click();
|
|
289
|
+
URL.revokeObjectURL(url);
|
|
290
|
+
}, [exportLogs]);
|
|
291
|
+
const handleClear = react.useCallback(() => {
|
|
292
|
+
clearLogs();
|
|
293
|
+
setExpandedLogs(/* @__PURE__ */ new Set());
|
|
294
|
+
}, [clearLogs]);
|
|
295
|
+
const logsJson = exportLogs();
|
|
296
|
+
const virtualItems = virtualizer.getVirtualItems();
|
|
297
|
+
const totalSize = virtualizer.getTotalSize();
|
|
298
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col h-full", children: [
|
|
299
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 border-b border-border px-3 py-1.5 shrink-0", children: [
|
|
300
|
+
/* @__PURE__ */ jsxRuntime.jsxs(components.Badge, { variant: "secondary", className: "text-[10px]", children: [
|
|
301
|
+
logCount,
|
|
302
|
+
" logs"
|
|
303
|
+
] }),
|
|
304
|
+
errorCount > 0 && /* @__PURE__ */ jsxRuntime.jsxs(components.Badge, { variant: "destructive", className: "text-[10px]", children: [
|
|
305
|
+
errorCount,
|
|
306
|
+
" errors"
|
|
307
|
+
] })
|
|
308
|
+
] }),
|
|
309
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2 border-b border-border px-3 py-2 shrink-0", children: [
|
|
310
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex-1 min-w-[140px]", children: [
|
|
311
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { className: "absolute left-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" }),
|
|
312
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
313
|
+
components.Input,
|
|
314
|
+
{
|
|
315
|
+
placeholder: "Search...",
|
|
316
|
+
value: searchQuery,
|
|
317
|
+
onChange: (e) => setSearchQuery(e.target.value),
|
|
318
|
+
className: "h-7 pl-7 text-xs"
|
|
319
|
+
}
|
|
320
|
+
)
|
|
321
|
+
] }),
|
|
322
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
323
|
+
components.Input,
|
|
324
|
+
{
|
|
325
|
+
placeholder: "Component...",
|
|
326
|
+
value: componentFilter,
|
|
327
|
+
onChange: (e) => setComponentFilter(e.target.value),
|
|
328
|
+
className: "h-7 w-24 text-xs"
|
|
329
|
+
}
|
|
330
|
+
),
|
|
331
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-0.5", children: LOG_LEVELS.map((level) => {
|
|
332
|
+
const { icon: Icon, color } = LOG_LEVEL_CONFIG[level];
|
|
333
|
+
const active = selectedLevels.has(level);
|
|
334
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(components.Tooltip, { children: [
|
|
335
|
+
/* @__PURE__ */ jsxRuntime.jsx(components.TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(components.Button, { variant: active ? "secondary" : "ghost", size: "icon", className: "h-6 w-6", onClick: () => handleToggleLevel(level), children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: lib.cn("h-3.5 w-3.5", active && color) }) }) }),
|
|
336
|
+
/* @__PURE__ */ jsxRuntime.jsx(components.TooltipContent, { children: level })
|
|
337
|
+
] }, level);
|
|
338
|
+
}) }),
|
|
339
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5 ml-auto", children: [
|
|
340
|
+
/* @__PURE__ */ jsxRuntime.jsx(components.CopyButton, { value: logsJson, size: "icon", className: "h-6 w-6" }),
|
|
341
|
+
/* @__PURE__ */ jsxRuntime.jsxs(components.Tooltip, { children: [
|
|
342
|
+
/* @__PURE__ */ jsxRuntime.jsx(components.TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(components.Button, { variant: "ghost", size: "icon", className: "h-6 w-6", onClick: handleExport, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "h-3.5 w-3.5" }) }) }),
|
|
343
|
+
/* @__PURE__ */ jsxRuntime.jsx(components.TooltipContent, { children: "Export JSON" })
|
|
344
|
+
] }),
|
|
345
|
+
/* @__PURE__ */ jsxRuntime.jsxs(components.Tooltip, { children: [
|
|
346
|
+
/* @__PURE__ */ jsxRuntime.jsx(components.TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(components.Button, { variant: "ghost", size: "icon", className: "h-6 w-6", onClick: handleClear, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-3.5 w-3.5" }) }) }),
|
|
347
|
+
/* @__PURE__ */ jsxRuntime.jsx(components.TooltipContent, { children: "Clear" })
|
|
348
|
+
] })
|
|
349
|
+
] })
|
|
350
|
+
] }),
|
|
351
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { ref: scrollRef, className: "flex-1 overflow-auto", children: logs.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center py-10 text-muted-foreground", children: [
|
|
352
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Bug, { className: "h-8 w-8 mb-2 opacity-40" }),
|
|
353
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm", children: "No logs yet" })
|
|
354
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: `${totalSize}px`, position: "relative" }, children: virtualItems.map((vItem) => {
|
|
355
|
+
const entry = logs[vItem.index];
|
|
356
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
357
|
+
"div",
|
|
358
|
+
{
|
|
359
|
+
"data-index": vItem.index,
|
|
360
|
+
ref: virtualizer.measureElement,
|
|
361
|
+
style: { position: "absolute", top: 0, left: 0, width: "100%", transform: `translateY(${vItem.start}px)` },
|
|
362
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
363
|
+
LogEntryRow,
|
|
364
|
+
{
|
|
365
|
+
entry,
|
|
366
|
+
expanded: expandedLogs.has(entry.id),
|
|
367
|
+
onToggle: () => handleToggleExpand(entry.id)
|
|
368
|
+
}
|
|
369
|
+
)
|
|
370
|
+
},
|
|
371
|
+
entry.id
|
|
372
|
+
);
|
|
373
|
+
}) }) })
|
|
374
|
+
] });
|
|
375
|
+
}
|
|
376
|
+
__name(LogsPanel, "LogsPanel");
|
|
377
|
+
|
|
378
|
+
// src/emitters/Emitter.ts
|
|
379
|
+
var _Emitter = class _Emitter {
|
|
380
|
+
constructor() {
|
|
381
|
+
this.listeners = /* @__PURE__ */ new Map();
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Emit an event. No-op if no listeners are registered.
|
|
385
|
+
*/
|
|
386
|
+
emit(event, data) {
|
|
387
|
+
const handlers = this.listeners.get(event);
|
|
388
|
+
if (!handlers || handlers.size === 0) return;
|
|
389
|
+
handlers.forEach((h) => h(data));
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Subscribe to an event. Returns an unsubscribe function.
|
|
393
|
+
*/
|
|
394
|
+
on(event, handler) {
|
|
395
|
+
if (!this.listeners.has(event)) {
|
|
396
|
+
this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
397
|
+
}
|
|
398
|
+
this.listeners.get(event).add(handler);
|
|
399
|
+
return () => this.listeners.get(event)?.delete(handler);
|
|
400
|
+
}
|
|
401
|
+
/** True when at least one listener is registered for this event (hot-path guard) */
|
|
402
|
+
hasListeners(event) {
|
|
403
|
+
return (this.listeners.get(event)?.size ?? 0) > 0;
|
|
404
|
+
}
|
|
405
|
+
/** Remove all listeners (useful for testing / cleanup) */
|
|
406
|
+
clear() {
|
|
407
|
+
this.listeners.clear();
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
__name(_Emitter, "Emitter");
|
|
411
|
+
var Emitter = _Emitter;
|
|
412
|
+
|
|
413
|
+
// src/emitters/audioEmitter.ts
|
|
414
|
+
var _emitter = new Emitter();
|
|
415
|
+
var BUFFER_SIZE = 200;
|
|
416
|
+
var _buffer = [];
|
|
417
|
+
var emitAudioEvent = /* @__PURE__ */ __name((event) => {
|
|
418
|
+
if (_buffer.length >= BUFFER_SIZE) _buffer.shift();
|
|
419
|
+
_buffer.push(event);
|
|
420
|
+
_emitter.emit("event", event);
|
|
421
|
+
}, "emitAudioEvent");
|
|
422
|
+
var subscribeAudioEvents = /* @__PURE__ */ __name((fn) => {
|
|
423
|
+
for (const e of _buffer) fn(e);
|
|
424
|
+
return _emitter.on("event", fn);
|
|
425
|
+
}, "subscribeAudioEvents");
|
|
426
|
+
var clearAudioEventBuffer = /* @__PURE__ */ __name(() => {
|
|
427
|
+
_buffer.length = 0;
|
|
428
|
+
}, "clearAudioEventBuffer");
|
|
429
|
+
var hasAudioListeners = /* @__PURE__ */ __name(() => _emitter.hasListeners("event"), "hasAudioListeners");
|
|
430
|
+
|
|
431
|
+
// src/hooks/useAudioEventLog.ts
|
|
432
|
+
var MAX_EVENTS = 200;
|
|
433
|
+
function useAudioEventLog(active) {
|
|
434
|
+
const [events, setEvents] = react.useState([]);
|
|
435
|
+
const [syncIntervalMs, setSyncIntervalMs] = react.useState(null);
|
|
436
|
+
const [kindCounts, setKindCounts] = react.useState({});
|
|
437
|
+
const [seekRate, setSeekRate] = react.useState(0);
|
|
438
|
+
const queueRef = react.useRef([]);
|
|
439
|
+
const counterRef = react.useRef(0);
|
|
440
|
+
const lastSyncTs = react.useRef(null);
|
|
441
|
+
const seekTimestamps = react.useRef([]);
|
|
442
|
+
const kindCountsRef = react.useRef({});
|
|
443
|
+
const clear = react.useCallback(() => {
|
|
444
|
+
clearAudioEventBuffer();
|
|
445
|
+
queueRef.current = [];
|
|
446
|
+
counterRef.current = 0;
|
|
447
|
+
kindCountsRef.current = {};
|
|
448
|
+
seekTimestamps.current = [];
|
|
449
|
+
lastSyncTs.current = null;
|
|
450
|
+
setEvents([]);
|
|
451
|
+
setSyncIntervalMs(null);
|
|
452
|
+
setKindCounts({});
|
|
453
|
+
setSeekRate(0);
|
|
454
|
+
}, []);
|
|
455
|
+
react.useEffect(() => {
|
|
456
|
+
if (!active) return;
|
|
457
|
+
const unsubscribe = subscribeAudioEvents((event) => {
|
|
458
|
+
const entry = { ...event, id: String(++counterRef.current) };
|
|
459
|
+
queueRef.current.push(entry);
|
|
460
|
+
if (event.kind === "sync") {
|
|
461
|
+
if (lastSyncTs.current !== null) {
|
|
462
|
+
const interval = event.ts - lastSyncTs.current;
|
|
463
|
+
entry._interval = interval;
|
|
464
|
+
}
|
|
465
|
+
lastSyncTs.current = event.ts;
|
|
466
|
+
}
|
|
467
|
+
if (event.kind === "seek") {
|
|
468
|
+
seekTimestamps.current.push(event.ts);
|
|
469
|
+
}
|
|
470
|
+
kindCountsRef.current = {
|
|
471
|
+
...kindCountsRef.current,
|
|
472
|
+
[event.kind]: (kindCountsRef.current[event.kind] ?? 0) + 1
|
|
473
|
+
};
|
|
474
|
+
});
|
|
475
|
+
return unsubscribe;
|
|
476
|
+
}, [active]);
|
|
477
|
+
react.useEffect(() => {
|
|
478
|
+
if (!active) return;
|
|
479
|
+
let rafId;
|
|
480
|
+
const flush = /* @__PURE__ */ __name(() => {
|
|
481
|
+
if (queueRef.current.length > 0) {
|
|
482
|
+
const incoming = queueRef.current.splice(0);
|
|
483
|
+
const lastSync = [...incoming].reverse().find((e) => e.kind === "sync");
|
|
484
|
+
if (lastSync?._interval !== void 0) {
|
|
485
|
+
setSyncIntervalMs(lastSync._interval);
|
|
486
|
+
}
|
|
487
|
+
setEvents((prev) => {
|
|
488
|
+
const next = [...prev, ...incoming];
|
|
489
|
+
return next.length > MAX_EVENTS ? next.slice(-MAX_EVENTS) : next;
|
|
490
|
+
});
|
|
491
|
+
setKindCounts({ ...kindCountsRef.current });
|
|
492
|
+
}
|
|
493
|
+
rafId = requestAnimationFrame(flush);
|
|
494
|
+
}, "flush");
|
|
495
|
+
rafId = requestAnimationFrame(flush);
|
|
496
|
+
return () => cancelAnimationFrame(rafId);
|
|
497
|
+
}, [active]);
|
|
498
|
+
react.useEffect(() => {
|
|
499
|
+
if (!active) return;
|
|
500
|
+
const id = setInterval(() => {
|
|
501
|
+
const now = Date.now();
|
|
502
|
+
seekTimestamps.current = seekTimestamps.current.filter((t) => now - t < 5e3);
|
|
503
|
+
setSeekRate(Math.round(seekTimestamps.current.length / 5 * 10) / 10);
|
|
504
|
+
}, 1e3);
|
|
505
|
+
return () => clearInterval(id);
|
|
506
|
+
}, [active]);
|
|
507
|
+
return { events, clear, seekRate, syncIntervalMs, kindCounts };
|
|
508
|
+
}
|
|
509
|
+
__name(useAudioEventLog, "useAudioEventLog");
|
|
510
|
+
var DEBUG_MODES = ["off", "on", "verbose"];
|
|
511
|
+
var KIND_STYLES = {
|
|
512
|
+
play: { label: "play", className: "text-green-400" },
|
|
513
|
+
pause: { label: "pause", className: "text-yellow-400" },
|
|
514
|
+
seek: { label: "seek", className: "text-blue-400" },
|
|
515
|
+
ended: { label: "ended", className: "text-muted-foreground" },
|
|
516
|
+
sync: { label: "sync", className: "text-muted-foreground/50" },
|
|
517
|
+
load: { label: "load", className: "text-purple-400" },
|
|
518
|
+
error: { label: "error", className: "text-red-400" },
|
|
519
|
+
engine: { label: "eng", className: "text-cyan-400" },
|
|
520
|
+
custom: { label: "custom", className: "text-orange-400" }
|
|
521
|
+
};
|
|
522
|
+
function getDebugAudioMode() {
|
|
523
|
+
try {
|
|
524
|
+
const v = localStorage.getItem("DEBUG_AUDIO");
|
|
525
|
+
if (v === "verbose") return "verbose";
|
|
526
|
+
if (v) return "on";
|
|
527
|
+
} catch {
|
|
528
|
+
}
|
|
529
|
+
return "off";
|
|
530
|
+
}
|
|
531
|
+
__name(getDebugAudioMode, "getDebugAudioMode");
|
|
532
|
+
function formatTs(ts) {
|
|
533
|
+
const d = new Date(ts);
|
|
534
|
+
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}:${String(d.getSeconds()).padStart(2, "0")}.${String(d.getMilliseconds()).padStart(3, "0")}`;
|
|
535
|
+
}
|
|
536
|
+
__name(formatTs, "formatTs");
|
|
537
|
+
function AudioDebugPanel({ isActive }) {
|
|
538
|
+
const { events, clear, seekRate, syncIntervalMs, kindCounts } = useAudioEventLog(isActive);
|
|
539
|
+
const [debugMode, setDebugMode] = react.useState(getDebugAudioMode);
|
|
540
|
+
const handleSetDebugAudio = react.useCallback((mode) => {
|
|
541
|
+
if (mode === "off") {
|
|
542
|
+
localStorage.removeItem("DEBUG_AUDIO");
|
|
543
|
+
} else {
|
|
544
|
+
localStorage.setItem("DEBUG_AUDIO", mode === "verbose" ? "verbose" : "1");
|
|
545
|
+
}
|
|
546
|
+
setDebugMode(mode);
|
|
547
|
+
}, []);
|
|
548
|
+
const kindEntries = Object.entries(kindCounts);
|
|
549
|
+
const activeKinds = kindEntries.filter(([, count]) => count > 0);
|
|
550
|
+
const reversedEvents = [...events].reverse();
|
|
551
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col h-full", children: [
|
|
552
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2 border-b border-border px-3 py-2 text-xs", children: [
|
|
553
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-muted-foreground", children: [
|
|
554
|
+
"Sync interval:",
|
|
555
|
+
" ",
|
|
556
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground font-mono", children: syncIntervalMs !== null ? `${syncIntervalMs.toFixed(1)}ms` : "\u2014" })
|
|
557
|
+
] }),
|
|
558
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-muted-foreground", children: [
|
|
559
|
+
"Seeks/s: ",
|
|
560
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground font-mono", children: seekRate })
|
|
561
|
+
] }),
|
|
562
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-muted-foreground", children: [
|
|
563
|
+
"Total: ",
|
|
564
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground font-mono", children: events.length })
|
|
565
|
+
] }),
|
|
566
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-1 flex-wrap ml-auto", children: activeKinds.map(([kind, count]) => /* @__PURE__ */ jsxRuntime.jsxs(components.Badge, { variant: "outline", className: lib.cn("text-[10px] px-1 py-0", KIND_STYLES[kind]?.className), children: [
|
|
567
|
+
KIND_STYLES[kind]?.label ?? kind,
|
|
568
|
+
" ",
|
|
569
|
+
count
|
|
570
|
+
] }, kind)) }),
|
|
571
|
+
/* @__PURE__ */ jsxRuntime.jsx(components.Button, { variant: "ghost", size: "icon", className: "h-6 w-6 ml-1", onClick: clear, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-3.5 w-3.5" }) })
|
|
572
|
+
] }),
|
|
573
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 border-b border-border px-3 py-1.5", children: [
|
|
574
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-muted-foreground mr-1", children: "DEBUG_AUDIO:" }),
|
|
575
|
+
DEBUG_MODES.map((mode) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
576
|
+
"button",
|
|
577
|
+
{
|
|
578
|
+
type: "button",
|
|
579
|
+
className: lib.cn(
|
|
580
|
+
"text-[10px] px-2 py-0.5 rounded border transition-colors",
|
|
581
|
+
debugMode === mode ? "border-primary bg-primary/10 text-primary" : "border-border hover:bg-muted text-muted-foreground"
|
|
582
|
+
),
|
|
583
|
+
onClick: () => handleSetDebugAudio(mode),
|
|
584
|
+
children: mode
|
|
585
|
+
},
|
|
586
|
+
mode
|
|
587
|
+
))
|
|
588
|
+
] }),
|
|
589
|
+
/* @__PURE__ */ jsxRuntime.jsx(components.ScrollArea, { className: "flex-1", children: reversedEvents.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center py-10 text-muted-foreground", children: [
|
|
590
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Radio, { className: "h-8 w-8 mb-2 opacity-40" }),
|
|
591
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm", children: "No audio events yet" }),
|
|
592
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs mt-1", children: [
|
|
593
|
+
"Wire ",
|
|
594
|
+
/* @__PURE__ */ jsxRuntime.jsx("code", { className: "text-xs", children: "emitAudioEvent()" }),
|
|
595
|
+
" into your audio engine"
|
|
596
|
+
] })
|
|
597
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y divide-border/30", children: reversedEvents.map((entry) => {
|
|
598
|
+
const style = KIND_STYLES[entry.kind] ?? { label: entry.kind, className: "text-foreground" };
|
|
599
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2 px-3 py-1 text-xs", children: [
|
|
600
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono text-muted-foreground shrink-0 w-[88px]", children: formatTs(entry.ts) }),
|
|
601
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: lib.cn("shrink-0 w-12 font-medium", style.className), children: style.label }),
|
|
602
|
+
entry.trackId && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 font-mono text-muted-foreground", children: entry.trackId.slice(0, 6) }),
|
|
603
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-foreground/80", children: entry.msg })
|
|
604
|
+
] }, entry.id);
|
|
605
|
+
}) }) })
|
|
606
|
+
] });
|
|
607
|
+
}
|
|
608
|
+
__name(AudioDebugPanel, "AudioDebugPanel");
|
|
609
|
+
var BUILTIN_TABS = [
|
|
610
|
+
{ id: "logs", label: "Logs", icon: lucideReact.ScrollText },
|
|
611
|
+
{ id: "audio", label: "Audio", icon: lucideReact.Radio }
|
|
612
|
+
];
|
|
613
|
+
var POSITION_CLASSES = {
|
|
614
|
+
"bottom-right": "bottom-4 right-4",
|
|
615
|
+
"bottom-left": "bottom-4 left-4",
|
|
616
|
+
"top-right": "top-4 right-4",
|
|
617
|
+
"top-left": "top-4 left-4"
|
|
618
|
+
};
|
|
619
|
+
function DebugPanel({
|
|
620
|
+
tabs: customTabs = [],
|
|
621
|
+
position = "bottom-left",
|
|
622
|
+
defaultHeight = 480,
|
|
623
|
+
defaultWidth = 560
|
|
624
|
+
}) {
|
|
625
|
+
const isOpen = useDebugStore((s) => s.isOpen);
|
|
626
|
+
const activeTab = useDebugStore((s) => s.tab);
|
|
627
|
+
const setTab = useDebugStore((s) => s.setTab);
|
|
628
|
+
const close = useDebugStore((s) => s.close);
|
|
629
|
+
const errorCount = useDebugErrorCount();
|
|
630
|
+
const [isMinimized, setIsMinimized] = react.useState(false);
|
|
631
|
+
react.useEffect(() => {
|
|
632
|
+
const unsub = installMonitorBridge();
|
|
633
|
+
return () => unsub?.();
|
|
634
|
+
}, []);
|
|
635
|
+
const allTabs = react.useMemo(() => [
|
|
636
|
+
...BUILTIN_TABS,
|
|
637
|
+
...customTabs.map((t) => ({ id: t.id, label: t.label, icon: t.icon }))
|
|
638
|
+
], [customTabs]);
|
|
639
|
+
const panelHeight = isMinimized ? 48 : defaultHeight;
|
|
640
|
+
if (!isOpen) return null;
|
|
641
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
642
|
+
"div",
|
|
643
|
+
{
|
|
644
|
+
className: lib.cn(
|
|
645
|
+
"fixed z-[100] flex flex-col overflow-hidden rounded-lg border border-border bg-background shadow-xl",
|
|
646
|
+
POSITION_CLASSES[position]
|
|
647
|
+
),
|
|
648
|
+
style: { width: defaultWidth, height: panelHeight },
|
|
649
|
+
children: [
|
|
650
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 border-b border-border bg-muted/50 px-3 py-2 shrink-0", children: [
|
|
651
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Bug, { className: "h-4 w-4 text-primary" }),
|
|
652
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium", children: "Debug" }),
|
|
653
|
+
errorCount > 0 && /* @__PURE__ */ jsxRuntime.jsxs(components.Badge, { variant: "destructive", className: "ml-1 text-[10px]", children: [
|
|
654
|
+
errorCount,
|
|
655
|
+
" errors"
|
|
656
|
+
] }),
|
|
657
|
+
!isMinimized && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-0.5 ml-3", children: allTabs.map((tab) => {
|
|
658
|
+
const Icon = tab.icon;
|
|
659
|
+
const isActive = activeTab === tab.id;
|
|
660
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
661
|
+
"button",
|
|
662
|
+
{
|
|
663
|
+
type: "button",
|
|
664
|
+
onClick: () => setTab(tab.id),
|
|
665
|
+
className: lib.cn(
|
|
666
|
+
"flex items-center gap-1.5 px-2 py-0.5 rounded text-xs transition-colors",
|
|
667
|
+
isActive ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground hover:bg-muted"
|
|
668
|
+
),
|
|
669
|
+
children: [
|
|
670
|
+
/* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-3 w-3" }),
|
|
671
|
+
tab.label
|
|
672
|
+
]
|
|
673
|
+
},
|
|
674
|
+
tab.id
|
|
675
|
+
);
|
|
676
|
+
}) }),
|
|
677
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 ml-auto", children: [
|
|
678
|
+
/* @__PURE__ */ jsxRuntime.jsxs(components.Tooltip, { children: [
|
|
679
|
+
/* @__PURE__ */ jsxRuntime.jsx(components.TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(components.Button, { variant: "ghost", size: "icon", className: "h-6 w-6", onClick: () => setIsMinimized((v) => !v), children: isMinimized ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Maximize2, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Minimize2, { className: "h-3.5 w-3.5" }) }) }),
|
|
680
|
+
/* @__PURE__ */ jsxRuntime.jsx(components.TooltipContent, { children: isMinimized ? "Expand" : "Minimize" })
|
|
681
|
+
] }),
|
|
682
|
+
/* @__PURE__ */ jsxRuntime.jsxs(components.Tooltip, { children: [
|
|
683
|
+
/* @__PURE__ */ jsxRuntime.jsx(components.TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(components.Button, { variant: "ghost", size: "icon", className: "h-6 w-6", onClick: close, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-3.5 w-3.5" }) }) }),
|
|
684
|
+
/* @__PURE__ */ jsxRuntime.jsx(components.TooltipContent, { children: "Close" })
|
|
685
|
+
] })
|
|
686
|
+
] })
|
|
687
|
+
] }),
|
|
688
|
+
!isMinimized && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 overflow-hidden", children: [
|
|
689
|
+
activeTab === "logs" && /* @__PURE__ */ jsxRuntime.jsx(LogsPanel, { isActive: activeTab === "logs" }),
|
|
690
|
+
activeTab === "audio" && /* @__PURE__ */ jsxRuntime.jsx(AudioDebugPanel, { isActive: activeTab === "audio" }),
|
|
691
|
+
customTabs.map((tab) => {
|
|
692
|
+
const Panel = tab.panel;
|
|
693
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: lib.cn("h-full", activeTab !== tab.id && "hidden"), children: /* @__PURE__ */ jsxRuntime.jsx(Panel, { isActive: activeTab === tab.id }) }, tab.id);
|
|
694
|
+
})
|
|
695
|
+
] })
|
|
696
|
+
]
|
|
697
|
+
}
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
__name(DebugPanel, "DebugPanel");
|
|
701
|
+
function useEasterEgg(onTriggered) {
|
|
702
|
+
const clicks = react.useRef([]);
|
|
703
|
+
return react.useCallback(() => {
|
|
704
|
+
const now = Date.now();
|
|
705
|
+
clicks.current = [...clicks.current.filter((t) => now - t < 2e3), now];
|
|
706
|
+
if (clicks.current.length >= 5) {
|
|
707
|
+
clicks.current = [];
|
|
708
|
+
onTriggered();
|
|
709
|
+
}
|
|
710
|
+
}, [onTriggered]);
|
|
711
|
+
}
|
|
712
|
+
__name(useEasterEgg, "useEasterEgg");
|
|
713
|
+
function DebugButton({ className, panel = {} }) {
|
|
714
|
+
const isOpen = useDebugStore((s) => s.isOpen);
|
|
715
|
+
const isUnlocked = useDebugStore((s) => s.isUnlocked);
|
|
716
|
+
const unlock = useDebugStore((s) => s.unlock);
|
|
717
|
+
const toggle = useDebugStore((s) => s.toggle);
|
|
718
|
+
const errorCount = useDebugErrorCount();
|
|
719
|
+
const isDev2 = typeof process !== "undefined" && true;
|
|
720
|
+
useDebugShortcut();
|
|
721
|
+
react.useEffect(() => {
|
|
722
|
+
if (typeof document === "undefined") return;
|
|
723
|
+
const apply = /* @__PURE__ */ __name(() => {
|
|
724
|
+
document.querySelectorAll("nextjs-portal").forEach((el) => {
|
|
725
|
+
el.style.display = isOpen ? "none" : "";
|
|
726
|
+
});
|
|
727
|
+
}, "apply");
|
|
728
|
+
apply();
|
|
729
|
+
const observer = new MutationObserver(apply);
|
|
730
|
+
observer.observe(document.body, { childList: true });
|
|
731
|
+
return () => observer.disconnect();
|
|
732
|
+
}, [isOpen]);
|
|
733
|
+
react.useEffect(() => {
|
|
734
|
+
if (typeof window === "undefined") return;
|
|
735
|
+
const params = new URLSearchParams(window.location.search);
|
|
736
|
+
const key = params.get("debug");
|
|
737
|
+
if (key) {
|
|
738
|
+
unlock(key);
|
|
739
|
+
params.delete("debug");
|
|
740
|
+
const newUrl = `${window.location.pathname}${params.toString() ? `?${params.toString()}` : ""}${window.location.hash}`;
|
|
741
|
+
window.history.replaceState(null, "", newUrl);
|
|
742
|
+
}
|
|
743
|
+
}, [unlock]);
|
|
744
|
+
const handleEasterEggClick = react.useCallback(() => {
|
|
745
|
+
unlock("1");
|
|
746
|
+
toggle();
|
|
747
|
+
}, [unlock, toggle]);
|
|
748
|
+
const handleEasterEggTrigger = useEasterEgg(handleEasterEggClick);
|
|
749
|
+
if (!isDev2 && !isUnlocked) {
|
|
750
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
751
|
+
"div",
|
|
752
|
+
{
|
|
753
|
+
className: "fixed bottom-5 left-5 w-9 h-9 z-[99999] opacity-0",
|
|
754
|
+
onClick: handleEasterEggTrigger
|
|
755
|
+
}
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
759
|
+
!isOpen && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed bottom-5 left-16 z-[99999]", children: /* @__PURE__ */ jsxRuntime.jsxs(components.Tooltip, { children: [
|
|
760
|
+
/* @__PURE__ */ jsxRuntime.jsx(components.TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
761
|
+
"button",
|
|
762
|
+
{
|
|
763
|
+
type: "button",
|
|
764
|
+
className: lib.cn(
|
|
765
|
+
"relative w-9 h-9 flex items-center justify-center rounded-full",
|
|
766
|
+
"bg-black/80 backdrop-blur-xl",
|
|
767
|
+
"shadow-[0_0_0_1px_#171717,inset_0_0_0_1px_hsla(0,0%,100%,0.14),0px_16px_32px_-8px_rgba(0,0,0,0.24)]",
|
|
768
|
+
"transition-all duration-150 hover:scale-105",
|
|
769
|
+
"focus:outline-none focus-visible:ring-2 focus-visible:ring-white/50",
|
|
770
|
+
errorCount > 0 && "bg-red-600/90",
|
|
771
|
+
className
|
|
772
|
+
),
|
|
773
|
+
onClick: toggle,
|
|
774
|
+
children: [
|
|
775
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Bug, { className: lib.cn("h-4 w-4", errorCount > 0 ? "text-white" : "text-white/80") }),
|
|
776
|
+
errorCount > 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute -right-1 -top-1 flex h-4 min-w-4 items-center justify-center rounded-full bg-white text-[10px] font-semibold text-red-600 shadow-sm", children: errorCount })
|
|
777
|
+
]
|
|
778
|
+
}
|
|
779
|
+
) }),
|
|
780
|
+
/* @__PURE__ */ jsxRuntime.jsxs(components.TooltipContent, { side: "top", children: [
|
|
781
|
+
"Debug Panel ",
|
|
782
|
+
/* @__PURE__ */ jsxRuntime.jsx("kbd", { className: "ml-1 text-[10px] opacity-60", children: "\u2318D" })
|
|
783
|
+
] })
|
|
784
|
+
] }) }),
|
|
785
|
+
/* @__PURE__ */ jsxRuntime.jsx(DebugPanel, { ...panel })
|
|
786
|
+
] });
|
|
787
|
+
}
|
|
788
|
+
__name(DebugButton, "DebugButton");
|
|
789
|
+
function shallowEqual(a, b) {
|
|
790
|
+
if (a === b) return true;
|
|
791
|
+
if (typeof a !== "object" || typeof b !== "object") return false;
|
|
792
|
+
if (a === null || b === null) return false;
|
|
793
|
+
const keysA = Object.keys(a);
|
|
794
|
+
const keysB = Object.keys(b);
|
|
795
|
+
if (keysA.length !== keysB.length) return false;
|
|
796
|
+
for (const key of keysA) {
|
|
797
|
+
if (a[key] !== b[key]) return false;
|
|
798
|
+
}
|
|
799
|
+
return true;
|
|
800
|
+
}
|
|
801
|
+
__name(shallowEqual, "shallowEqual");
|
|
802
|
+
function useStoreSnapshot(getState, intervalMs = 200, active = true) {
|
|
803
|
+
const getStateRef = react.useRef(getState);
|
|
804
|
+
getStateRef.current = getState;
|
|
805
|
+
const [snapshot, setSnapshot] = react.useState(() => getState());
|
|
806
|
+
react.useEffect(() => {
|
|
807
|
+
if (!active) return;
|
|
808
|
+
const id = setInterval(() => {
|
|
809
|
+
const next = getStateRef.current();
|
|
810
|
+
setSnapshot((prev) => shallowEqual(prev, next) ? prev : next);
|
|
811
|
+
}, intervalMs);
|
|
812
|
+
return () => clearInterval(id);
|
|
813
|
+
}, [active, intervalMs]);
|
|
814
|
+
return snapshot;
|
|
815
|
+
}
|
|
816
|
+
__name(useStoreSnapshot, "useStoreSnapshot");
|
|
817
|
+
function StorePanel({ label, getState, intervalMs = 200, isActive }) {
|
|
818
|
+
const snapshot = useStoreSnapshot(getState, intervalMs, isActive);
|
|
819
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col h-full", children: [
|
|
820
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 border-b border-border px-3 py-2 shrink-0", children: [
|
|
821
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Database, { className: "h-3.5 w-3.5 text-muted-foreground" }),
|
|
822
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-muted-foreground", children: label }),
|
|
823
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ml-auto text-[10px] text-muted-foreground/60", children: [
|
|
824
|
+
"polling ",
|
|
825
|
+
intervalMs,
|
|
826
|
+
"ms"
|
|
827
|
+
] })
|
|
828
|
+
] }),
|
|
829
|
+
/* @__PURE__ */ jsxRuntime.jsx(components.ScrollArea, { className: "flex-1", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-2", children: /* @__PURE__ */ jsxRuntime.jsx(uiTools.LazyJsonTree, { data: snapshot, mode: "full" }) }) })
|
|
830
|
+
] });
|
|
831
|
+
}
|
|
832
|
+
__name(StorePanel, "StorePanel");
|
|
833
|
+
|
|
834
|
+
// src/emitters/customEmitter.ts
|
|
835
|
+
var _emitter2 = new Emitter();
|
|
836
|
+
var emitDebugEvent = /* @__PURE__ */ __name((event) => _emitter2.emit("event", event), "emitDebugEvent");
|
|
837
|
+
var subscribeCustomEvents = /* @__PURE__ */ __name((fn) => _emitter2.on("event", fn), "subscribeCustomEvents");
|
|
838
|
+
var hasCustomListeners = /* @__PURE__ */ __name(() => _emitter2.hasListeners("event"), "hasCustomListeners");
|
|
839
|
+
|
|
840
|
+
// src/hooks/useCustomEventLog.ts
|
|
841
|
+
var MAX_EVENTS2 = 300;
|
|
842
|
+
function useCustomEventLog(active, channel) {
|
|
843
|
+
const [events, setEvents] = react.useState([]);
|
|
844
|
+
const counterRef = react.useRef(0);
|
|
845
|
+
const clear = react.useCallback(() => setEvents([]), []);
|
|
846
|
+
react.useEffect(() => {
|
|
847
|
+
if (!active) return;
|
|
848
|
+
const unsubscribe = subscribeCustomEvents((event) => {
|
|
849
|
+
if (channel && event.channel !== channel) return;
|
|
850
|
+
const entry = { ...event, id: String(++counterRef.current) };
|
|
851
|
+
setEvents((prev) => {
|
|
852
|
+
const next = [...prev, entry];
|
|
853
|
+
return next.length > MAX_EVENTS2 ? next.slice(-MAX_EVENTS2) : next;
|
|
854
|
+
});
|
|
855
|
+
});
|
|
856
|
+
return unsubscribe;
|
|
857
|
+
}, [active, channel]);
|
|
858
|
+
return { events, clear };
|
|
859
|
+
}
|
|
860
|
+
__name(useCustomEventLog, "useCustomEventLog");
|
|
861
|
+
|
|
862
|
+
exports.AudioDebugPanel = AudioDebugPanel;
|
|
863
|
+
exports.DebugButton = DebugButton;
|
|
864
|
+
exports.DebugPanel = DebugPanel;
|
|
865
|
+
exports.LogsPanel = LogsPanel;
|
|
866
|
+
exports.StorePanel = StorePanel;
|
|
867
|
+
exports.clearAudioEventBuffer = clearAudioEventBuffer;
|
|
868
|
+
exports.createDebugLogger = createDebugLogger;
|
|
869
|
+
exports.debugLog = debugLog;
|
|
870
|
+
exports.emitAudioEvent = emitAudioEvent;
|
|
871
|
+
exports.emitDebugEvent = emitDebugEvent;
|
|
872
|
+
exports.hasAudioListeners = hasAudioListeners;
|
|
873
|
+
exports.hasCustomListeners = hasCustomListeners;
|
|
874
|
+
exports.installMonitorBridge = installMonitorBridge;
|
|
875
|
+
exports.subscribeAudioEvents = subscribeAudioEvents;
|
|
876
|
+
exports.subscribeCustomEvents = subscribeCustomEvents;
|
|
877
|
+
exports.useAudioEventLog = useAudioEventLog;
|
|
878
|
+
exports.useCustomEventLog = useCustomEventLog;
|
|
879
|
+
exports.useDebugErrorCount = useDebugErrorCount;
|
|
880
|
+
exports.useDebugFilteredLogs = useDebugFilteredLogs;
|
|
881
|
+
exports.useDebugLogCount = useDebugLogCount;
|
|
882
|
+
exports.useDebugLogStore = useDebugLogStore;
|
|
883
|
+
exports.useDebugShortcut = useDebugShortcut;
|
|
884
|
+
exports.useDebugStore = useDebugStore;
|
|
885
|
+
exports.useStoreSnapshot = useStoreSnapshot;
|
|
886
|
+
//# sourceMappingURL=index.cjs.map
|
|
887
|
+
//# sourceMappingURL=index.cjs.map
|