@pie-players/pie-section-player-tools-event-debugger 0.3.42 → 0.3.44
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/dist/index.d.ts +0 -1
- package/dist/section-player-tools-event-debugger.js +1676 -2330
- package/dist/vite.config.d.ts +0 -1
- package/package.json +4 -7
- package/EventPanel.svelte +0 -766
- package/dist/index.d.ts.map +0 -1
- package/dist/vite.config.d.ts.map +0 -1
package/EventPanel.svelte
DELETED
|
@@ -1,766 +0,0 @@
|
|
|
1
|
-
<svelte:options
|
|
2
|
-
customElement={{
|
|
3
|
-
tag: "pie-section-player-tools-event-debugger",
|
|
4
|
-
shadow: "open",
|
|
5
|
-
props: {
|
|
6
|
-
maxEvents: { type: "Number", attribute: "max-events" },
|
|
7
|
-
maxEventsByLevel: {
|
|
8
|
-
type: "Object",
|
|
9
|
-
attribute: "max-events-by-level",
|
|
10
|
-
},
|
|
11
|
-
toolkitCoordinator: { type: "Object", attribute: "toolkit-coordinator" },
|
|
12
|
-
sectionId: { type: "String", attribute: "section-id" },
|
|
13
|
-
attemptId: { type: "String", attribute: "attempt-id" },
|
|
14
|
-
persistenceScope: { type: "String", attribute: "persistence-scope" },
|
|
15
|
-
persistencePanelId: { type: "String", attribute: "persistence-panel-id" },
|
|
16
|
-
},
|
|
17
|
-
}}
|
|
18
|
-
/>
|
|
19
|
-
|
|
20
|
-
<script lang="ts">
|
|
21
|
-
import "@pie-players/pie-theme/components.css";
|
|
22
|
-
import SharedFloatingPanel from "@pie-players/pie-section-player-tools-shared/SharedFloatingPanel.svelte";
|
|
23
|
-
import {
|
|
24
|
-
getSectionControllerFromCoordinator,
|
|
25
|
-
isMatchingSectionControllerLifecycleEvent,
|
|
26
|
-
} from "@pie-players/pie-section-player-tools-shared";
|
|
27
|
-
import { createEventDispatcher, onDestroy, untrack } from "svelte";
|
|
28
|
-
|
|
29
|
-
type ControllerEvent = {
|
|
30
|
-
type?: string;
|
|
31
|
-
timestamp?: number;
|
|
32
|
-
itemId?: string;
|
|
33
|
-
canonicalItemId?: string;
|
|
34
|
-
intent?: string;
|
|
35
|
-
[key: string]: unknown;
|
|
36
|
-
};
|
|
37
|
-
type ControllerRuntimeState = {
|
|
38
|
-
loadingComplete?: boolean;
|
|
39
|
-
totalRegistered?: number;
|
|
40
|
-
totalLoaded?: number;
|
|
41
|
-
itemsComplete?: boolean;
|
|
42
|
-
completedCount?: number;
|
|
43
|
-
totalItems?: number;
|
|
44
|
-
} | null;
|
|
45
|
-
type ToolkitCoordinatorLike = {
|
|
46
|
-
// Phase D (>=0.3.35): subscribe* helpers follow the toolkit's
|
|
47
|
-
// active section cohort automatically and do not accept
|
|
48
|
-
// sectionId / attemptId arguments. The debugger keeps `sectionId`
|
|
49
|
-
// + `attemptId` in `subscriptions` purely to gate same-target
|
|
50
|
-
// re-subscribe — it does not pass them to the coordinator.
|
|
51
|
-
subscribeItemEvents?: (args: {
|
|
52
|
-
listener: (event: ControllerEvent) => void;
|
|
53
|
-
}) => () => void;
|
|
54
|
-
subscribeSectionLifecycleEvents?: (args: {
|
|
55
|
-
listener: (event: ControllerEvent) => void;
|
|
56
|
-
}) => () => void;
|
|
57
|
-
getSectionController?: (args: {
|
|
58
|
-
sectionId: string;
|
|
59
|
-
attemptId?: string;
|
|
60
|
-
}) => unknown;
|
|
61
|
-
onSectionControllerLifecycle?: (
|
|
62
|
-
listener: (event: {
|
|
63
|
-
key?: { sectionId?: string; attemptId?: string };
|
|
64
|
-
}) => void,
|
|
65
|
-
) => () => void;
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
type EventType =
|
|
69
|
-
| "item-session-data-changed"
|
|
70
|
-
| "item-session-meta-changed"
|
|
71
|
-
| "item-selected"
|
|
72
|
-
| "section-navigation-change"
|
|
73
|
-
| "content-loaded"
|
|
74
|
-
| "item-player-error"
|
|
75
|
-
| "item-complete-changed"
|
|
76
|
-
| "section-loading-complete"
|
|
77
|
-
| "section-items-complete-changed"
|
|
78
|
-
| "section-error";
|
|
79
|
-
type EventLevel = "item" | "section";
|
|
80
|
-
type EventLimitOverrides = Partial<Record<EventLevel, number>>;
|
|
81
|
-
|
|
82
|
-
type EventRecord = {
|
|
83
|
-
id: number;
|
|
84
|
-
type: EventType;
|
|
85
|
-
timestamp: number;
|
|
86
|
-
targetTag: string | null;
|
|
87
|
-
itemId: string | null;
|
|
88
|
-
canonicalItemId: string | null;
|
|
89
|
-
intent: string | null;
|
|
90
|
-
duplicateCount: number;
|
|
91
|
-
payload: unknown;
|
|
92
|
-
fingerprint: string;
|
|
93
|
-
semanticFingerprint: string;
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
const dispatch = createEventDispatcher<{ close: undefined }>();
|
|
97
|
-
|
|
98
|
-
let {
|
|
99
|
-
maxEvents = 200,
|
|
100
|
-
maxEventsByLevel = {},
|
|
101
|
-
toolkitCoordinator = null,
|
|
102
|
-
sectionId = "",
|
|
103
|
-
attemptId = undefined,
|
|
104
|
-
persistenceScope = "",
|
|
105
|
-
persistencePanelId = "controller-events",
|
|
106
|
-
}: {
|
|
107
|
-
maxEvents?: number;
|
|
108
|
-
maxEventsByLevel?: EventLimitOverrides;
|
|
109
|
-
toolkitCoordinator?: ToolkitCoordinatorLike | null;
|
|
110
|
-
sectionId?: string;
|
|
111
|
-
attemptId?: string;
|
|
112
|
-
persistenceScope?: string;
|
|
113
|
-
persistencePanelId?: string;
|
|
114
|
-
} = $props();
|
|
115
|
-
let isPaused = $state(false);
|
|
116
|
-
let selectedLevel = $state<EventLevel>("item");
|
|
117
|
-
let selectedRecordId = $state<number | null>(null);
|
|
118
|
-
let records = $state<EventRecord[]>([]);
|
|
119
|
-
let controllerAvailable = $state(false);
|
|
120
|
-
|
|
121
|
-
let nextRecordId = 1;
|
|
122
|
-
let resubscribeQueued = false;
|
|
123
|
-
const subscriptions: {
|
|
124
|
-
controller: (() => void) | null;
|
|
125
|
-
lifecycle: (() => void) | null;
|
|
126
|
-
activeSectionId: string;
|
|
127
|
-
activeAttemptId?: string;
|
|
128
|
-
} = {
|
|
129
|
-
controller: null,
|
|
130
|
-
lifecycle: null,
|
|
131
|
-
activeSectionId: "",
|
|
132
|
-
activeAttemptId: undefined,
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
function safeClone<T>(value: T): T {
|
|
136
|
-
try {
|
|
137
|
-
return structuredClone(value);
|
|
138
|
-
} catch {
|
|
139
|
-
try {
|
|
140
|
-
return JSON.parse(JSON.stringify(value)) as T;
|
|
141
|
-
} catch {
|
|
142
|
-
return value;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function createFingerprint(type: EventType, payload: unknown): string {
|
|
148
|
-
let payloadString = "";
|
|
149
|
-
try {
|
|
150
|
-
payloadString = JSON.stringify(payload);
|
|
151
|
-
} catch {
|
|
152
|
-
payloadString = String(payload);
|
|
153
|
-
}
|
|
154
|
-
return `${type}:${payloadString}`;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function createSemanticFingerprint(type: EventType, payload: unknown): string {
|
|
158
|
-
const semantic =
|
|
159
|
-
payload && typeof payload === "object"
|
|
160
|
-
? { ...(payload as Record<string, unknown>) }
|
|
161
|
-
: payload;
|
|
162
|
-
if (semantic && typeof semantic === "object") {
|
|
163
|
-
delete (semantic as Record<string, unknown>).timestamp;
|
|
164
|
-
delete (semantic as Record<string, unknown>).sourceRuntimeId;
|
|
165
|
-
}
|
|
166
|
-
let payloadString = "";
|
|
167
|
-
try {
|
|
168
|
-
payloadString = JSON.stringify(semantic);
|
|
169
|
-
} catch {
|
|
170
|
-
payloadString = String(semantic);
|
|
171
|
-
}
|
|
172
|
-
return `${type}:${payloadString}`;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function normalizeEventType(input: unknown): EventType | null {
|
|
176
|
-
const value = String(input || "");
|
|
177
|
-
if (
|
|
178
|
-
value === "item-session-data-changed" ||
|
|
179
|
-
value === "item-session-meta-changed" ||
|
|
180
|
-
value === "item-selected" ||
|
|
181
|
-
value === "section-navigation-change" ||
|
|
182
|
-
value === "content-loaded" ||
|
|
183
|
-
value === "item-player-error" ||
|
|
184
|
-
value === "item-complete-changed" ||
|
|
185
|
-
value === "section-loading-complete" ||
|
|
186
|
-
value === "section-items-complete-changed" ||
|
|
187
|
-
value === "section-error"
|
|
188
|
-
) {
|
|
189
|
-
return value;
|
|
190
|
-
}
|
|
191
|
-
return null;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function getEventLevel(type: EventType): EventLevel {
|
|
195
|
-
if (
|
|
196
|
-
type === "section-navigation-change" ||
|
|
197
|
-
type === "section-loading-complete" ||
|
|
198
|
-
type === "section-items-complete-changed" ||
|
|
199
|
-
type === "section-error"
|
|
200
|
-
) {
|
|
201
|
-
return "section";
|
|
202
|
-
}
|
|
203
|
-
return "item";
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
function getValueAsString(value: unknown): string | null {
|
|
207
|
-
return typeof value === "string" && value.trim() ? value : null;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function normalizeRecord(detail: ControllerEvent, type: EventType): EventRecord {
|
|
211
|
-
const payload = safeClone((detail || {}) as unknown);
|
|
212
|
-
const fingerprint = createFingerprint(type, payload);
|
|
213
|
-
const semanticFingerprint = createSemanticFingerprint(type, payload);
|
|
214
|
-
return {
|
|
215
|
-
id: nextRecordId++,
|
|
216
|
-
type,
|
|
217
|
-
timestamp: typeof detail.timestamp === "number" ? detail.timestamp : Date.now(),
|
|
218
|
-
targetTag: "section-controller",
|
|
219
|
-
itemId: getValueAsString(detail?.itemId),
|
|
220
|
-
canonicalItemId: getValueAsString(detail?.canonicalItemId),
|
|
221
|
-
intent: getValueAsString(detail?.intent),
|
|
222
|
-
duplicateCount: 1,
|
|
223
|
-
payload,
|
|
224
|
-
fingerprint,
|
|
225
|
-
semanticFingerprint,
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function pushRecord(detail: ControllerEvent) {
|
|
230
|
-
if (isPaused) return;
|
|
231
|
-
const type = normalizeEventType(detail?.type);
|
|
232
|
-
if (!type) return;
|
|
233
|
-
const next = normalizeRecord(detail, type);
|
|
234
|
-
const latest = records[0];
|
|
235
|
-
if (latest && latest.fingerprint === next.fingerprint) {
|
|
236
|
-
records = [
|
|
237
|
-
{
|
|
238
|
-
...latest,
|
|
239
|
-
timestamp: next.timestamp,
|
|
240
|
-
duplicateCount: latest.duplicateCount + 1,
|
|
241
|
-
},
|
|
242
|
-
...records.slice(1),
|
|
243
|
-
];
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
records = pruneAndSortRecords([next, ...records]);
|
|
247
|
-
if (selectedRecordId == null) {
|
|
248
|
-
selectedRecordId = next.id;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
function resolveCap(rawCap: unknown, fallback: number): number {
|
|
253
|
-
const parsed = Number(rawCap);
|
|
254
|
-
if (!Number.isFinite(parsed)) return Math.max(10, Math.min(2000, fallback));
|
|
255
|
-
return Math.max(10, Math.min(2000, parsed));
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
function getCapForLevel(level: EventLevel): number {
|
|
259
|
-
const globalCap = resolveCap(maxEvents || 200, 200);
|
|
260
|
-
const override = maxEventsByLevel?.[level];
|
|
261
|
-
return resolveCap(override, globalCap);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
function pruneAndSortRecords(nextRecords: EventRecord[]): EventRecord[] {
|
|
265
|
-
const sorted = [...nextRecords].sort((left, right) => {
|
|
266
|
-
if (left.timestamp === right.timestamp) {
|
|
267
|
-
return right.id - left.id;
|
|
268
|
-
}
|
|
269
|
-
return right.timestamp - left.timestamp;
|
|
270
|
-
});
|
|
271
|
-
const nextByLevel: Record<EventLevel, number> = { item: 0, section: 0 };
|
|
272
|
-
const pruned: EventRecord[] = [];
|
|
273
|
-
for (const record of sorted) {
|
|
274
|
-
const level = getEventLevel(record.type);
|
|
275
|
-
const levelCap = getCapForLevel(level);
|
|
276
|
-
if (nextByLevel[level] >= levelCap) continue;
|
|
277
|
-
pruned.push(record);
|
|
278
|
-
nextByLevel[level] += 1;
|
|
279
|
-
}
|
|
280
|
-
return pruned;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function reconcileRecordsWithLimits(): void {
|
|
284
|
-
const nextRecords = pruneAndSortRecords(records);
|
|
285
|
-
if (nextRecords.length !== records.length) {
|
|
286
|
-
records = nextRecords;
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
for (let index = 0; index < nextRecords.length; index += 1) {
|
|
290
|
-
if (nextRecords[index]?.id !== records[index]?.id) {
|
|
291
|
-
records = nextRecords;
|
|
292
|
-
return;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
function handleControllerEvent(event: ControllerEvent): void {
|
|
298
|
-
pushRecord(event || {});
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
function handleItemControllerEvent(event: ControllerEvent): void {
|
|
302
|
-
handleControllerEvent(event);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
function handleSectionControllerEvent(event: ControllerEvent): void {
|
|
306
|
-
handleControllerEvent(event);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
function getController(): any | null {
|
|
310
|
-
return getSectionControllerFromCoordinator(
|
|
311
|
-
toolkitCoordinator,
|
|
312
|
-
sectionId,
|
|
313
|
-
attemptId,
|
|
314
|
-
);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
function seedFromRuntimeState(controller: {
|
|
318
|
-
getRuntimeState?: () => ControllerRuntimeState;
|
|
319
|
-
}): void {
|
|
320
|
-
const runtimeState = controller?.getRuntimeState?.();
|
|
321
|
-
if (!runtimeState || typeof runtimeState !== "object") return;
|
|
322
|
-
const totalItems =
|
|
323
|
-
typeof runtimeState.totalItems === "number" ? runtimeState.totalItems : 0;
|
|
324
|
-
const now = Date.now();
|
|
325
|
-
pushRecord({
|
|
326
|
-
type: "section-items-complete-changed",
|
|
327
|
-
complete: runtimeState.itemsComplete === true,
|
|
328
|
-
completedCount:
|
|
329
|
-
typeof runtimeState.completedCount === "number"
|
|
330
|
-
? runtimeState.completedCount
|
|
331
|
-
: 0,
|
|
332
|
-
totalItems,
|
|
333
|
-
timestamp: now,
|
|
334
|
-
});
|
|
335
|
-
if (runtimeState.loadingComplete === true) {
|
|
336
|
-
pushRecord({
|
|
337
|
-
type: "section-loading-complete",
|
|
338
|
-
totalRegistered:
|
|
339
|
-
typeof runtimeState.totalRegistered === "number"
|
|
340
|
-
? runtimeState.totalRegistered
|
|
341
|
-
: 0,
|
|
342
|
-
totalLoaded:
|
|
343
|
-
typeof runtimeState.totalLoaded === "number"
|
|
344
|
-
? runtimeState.totalLoaded
|
|
345
|
-
: 0,
|
|
346
|
-
timestamp: now,
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
function detachControllerSubscription() {
|
|
352
|
-
subscriptions.controller?.();
|
|
353
|
-
subscriptions.controller = null;
|
|
354
|
-
subscriptions.activeSectionId = "";
|
|
355
|
-
subscriptions.activeAttemptId = undefined;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
function detachLifecycleSubscription() {
|
|
359
|
-
subscriptions.lifecycle?.();
|
|
360
|
-
subscriptions.lifecycle = null;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
function ensureControllerSubscription() {
|
|
364
|
-
const controller = getController();
|
|
365
|
-
controllerAvailable = Boolean(controller);
|
|
366
|
-
if (!controller) {
|
|
367
|
-
detachControllerSubscription();
|
|
368
|
-
return;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
const nextAttemptId = attemptId || undefined;
|
|
372
|
-
const isSameTarget =
|
|
373
|
-
subscriptions.activeSectionId === sectionId &&
|
|
374
|
-
subscriptions.activeAttemptId === nextAttemptId;
|
|
375
|
-
if (isSameTarget && subscriptions.controller) {
|
|
376
|
-
return;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
detachControllerSubscription();
|
|
380
|
-
const unsubscribeItem =
|
|
381
|
-
toolkitCoordinator?.subscribeItemEvents?.({
|
|
382
|
-
listener: handleItemControllerEvent,
|
|
383
|
-
}) || null;
|
|
384
|
-
const unsubscribeSection =
|
|
385
|
-
toolkitCoordinator?.subscribeSectionLifecycleEvents?.({
|
|
386
|
-
listener: handleSectionControllerEvent,
|
|
387
|
-
}) || null;
|
|
388
|
-
subscriptions.controller = () => {
|
|
389
|
-
unsubscribeItem?.();
|
|
390
|
-
unsubscribeSection?.();
|
|
391
|
-
};
|
|
392
|
-
subscriptions.activeSectionId = sectionId;
|
|
393
|
-
subscriptions.activeAttemptId = nextAttemptId;
|
|
394
|
-
seedFromRuntimeState(controller);
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
function queueEnsureControllerSubscription(): void {
|
|
398
|
-
if (resubscribeQueued) return;
|
|
399
|
-
resubscribeQueued = true;
|
|
400
|
-
queueMicrotask(() => {
|
|
401
|
-
resubscribeQueued = false;
|
|
402
|
-
ensureControllerSubscription();
|
|
403
|
-
});
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
function clearRecords() {
|
|
407
|
-
records = [];
|
|
408
|
-
selectedRecordId = null;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
function formatTimestamp(timestamp: number): string {
|
|
412
|
-
return new Date(timestamp).toLocaleTimeString();
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
const visibleRecords = $derived.by(() =>
|
|
416
|
-
records.filter(
|
|
417
|
-
(record) => getEventLevel(record.type) === selectedLevel,
|
|
418
|
-
),
|
|
419
|
-
);
|
|
420
|
-
const semanticCounts = $derived.by(() => {
|
|
421
|
-
const counts = new Map<string, number>();
|
|
422
|
-
for (const record of visibleRecords) {
|
|
423
|
-
counts.set(
|
|
424
|
-
record.semanticFingerprint,
|
|
425
|
-
(counts.get(record.semanticFingerprint) || 0) + record.duplicateCount,
|
|
426
|
-
);
|
|
427
|
-
}
|
|
428
|
-
return counts;
|
|
429
|
-
});
|
|
430
|
-
const selectedRecord = $derived.by(
|
|
431
|
-
() => visibleRecords.find((record) => record.id === selectedRecordId) || visibleRecords[0] || null,
|
|
432
|
-
);
|
|
433
|
-
|
|
434
|
-
$effect(() => {
|
|
435
|
-
void toolkitCoordinator;
|
|
436
|
-
void sectionId;
|
|
437
|
-
void attemptId;
|
|
438
|
-
untrack(() => {
|
|
439
|
-
ensureControllerSubscription();
|
|
440
|
-
detachLifecycleSubscription();
|
|
441
|
-
subscriptions.lifecycle = toolkitCoordinator?.onSectionControllerLifecycle?.(
|
|
442
|
-
(event: {
|
|
443
|
-
type?: "ready" | "disposed";
|
|
444
|
-
key?: { sectionId?: string; attemptId?: string };
|
|
445
|
-
}) => {
|
|
446
|
-
if (
|
|
447
|
-
!isMatchingSectionControllerLifecycleEvent(event, sectionId, attemptId)
|
|
448
|
-
)
|
|
449
|
-
return;
|
|
450
|
-
if (event?.type === "disposed") {
|
|
451
|
-
detachControllerSubscription();
|
|
452
|
-
queueEnsureControllerSubscription();
|
|
453
|
-
return;
|
|
454
|
-
}
|
|
455
|
-
const nextAttemptId = attemptId || undefined;
|
|
456
|
-
if (
|
|
457
|
-
subscriptions.controller &&
|
|
458
|
-
subscriptions.activeSectionId === sectionId &&
|
|
459
|
-
subscriptions.activeAttemptId === nextAttemptId
|
|
460
|
-
) {
|
|
461
|
-
return;
|
|
462
|
-
}
|
|
463
|
-
queueEnsureControllerSubscription();
|
|
464
|
-
},
|
|
465
|
-
) || null;
|
|
466
|
-
});
|
|
467
|
-
return () => {
|
|
468
|
-
detachControllerSubscription();
|
|
469
|
-
detachLifecycleSubscription();
|
|
470
|
-
};
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
$effect(() => {
|
|
474
|
-
void maxEvents;
|
|
475
|
-
void maxEventsByLevel;
|
|
476
|
-
reconcileRecordsWithLimits();
|
|
477
|
-
});
|
|
478
|
-
|
|
479
|
-
onDestroy(() => {
|
|
480
|
-
detachControllerSubscription();
|
|
481
|
-
detachLifecycleSubscription();
|
|
482
|
-
});
|
|
483
|
-
</script>
|
|
484
|
-
|
|
485
|
-
<SharedFloatingPanel
|
|
486
|
-
title="Controller Events"
|
|
487
|
-
ariaLabel="Drag event debugger panel"
|
|
488
|
-
minWidth={360}
|
|
489
|
-
minHeight={280}
|
|
490
|
-
{persistenceScope}
|
|
491
|
-
{persistencePanelId}
|
|
492
|
-
initialSizing={{
|
|
493
|
-
widthRatio: 0.34,
|
|
494
|
-
heightRatio: 0.74,
|
|
495
|
-
minWidth: 380,
|
|
496
|
-
maxWidth: 720,
|
|
497
|
-
minHeight: 360,
|
|
498
|
-
maxHeight: 860,
|
|
499
|
-
alignX: "right",
|
|
500
|
-
alignY: "center",
|
|
501
|
-
paddingX: 16,
|
|
502
|
-
paddingY: 16,
|
|
503
|
-
}}
|
|
504
|
-
className="pie-section-player-tools-event-debugger"
|
|
505
|
-
bodyClass="pie-section-player-tools-event-debugger__content-shell"
|
|
506
|
-
onClose={() => dispatch("close")}
|
|
507
|
-
>
|
|
508
|
-
<svelte:fragment slot="icon">
|
|
509
|
-
<svg
|
|
510
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
511
|
-
class="pie-section-player-tools-event-debugger__icon-sm"
|
|
512
|
-
fill="none"
|
|
513
|
-
viewBox="0 0 24 24"
|
|
514
|
-
stroke="currentColor"
|
|
515
|
-
>
|
|
516
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 10h8M8 14h5m-7 7h12a2 2 0 002-2V5a2 2 0 00-2-2H6a2 2 0 00-2 2v14a2 2 0 002 2z" />
|
|
517
|
-
</svg>
|
|
518
|
-
</svelte:fragment>
|
|
519
|
-
|
|
520
|
-
<div class="pie-section-player-tools-event-debugger__toolbar">
|
|
521
|
-
<div
|
|
522
|
-
class="pie-section-player-tools-event-debugger__toggle-group"
|
|
523
|
-
role="group"
|
|
524
|
-
aria-label="Event level filter"
|
|
525
|
-
>
|
|
526
|
-
<button
|
|
527
|
-
class="pie-section-player-tools-event-debugger__toggle-button"
|
|
528
|
-
class:pie-section-player-tools-event-debugger__toggle-button--active={selectedLevel ===
|
|
529
|
-
"item"}
|
|
530
|
-
onclick={() => (selectedLevel = "item")}
|
|
531
|
-
aria-pressed={selectedLevel === "item"}
|
|
532
|
-
>
|
|
533
|
-
item
|
|
534
|
-
</button>
|
|
535
|
-
<button
|
|
536
|
-
class="pie-section-player-tools-event-debugger__toggle-button"
|
|
537
|
-
class:pie-section-player-tools-event-debugger__toggle-button--active={selectedLevel ===
|
|
538
|
-
"section"}
|
|
539
|
-
onclick={() => (selectedLevel = "section")}
|
|
540
|
-
aria-pressed={selectedLevel === "section"}
|
|
541
|
-
>
|
|
542
|
-
section
|
|
543
|
-
</button>
|
|
544
|
-
</div>
|
|
545
|
-
<button class="pie-section-player-tools-event-debugger__button" onclick={() => (isPaused = !isPaused)}>
|
|
546
|
-
{isPaused ? "resume" : "pause"}
|
|
547
|
-
</button>
|
|
548
|
-
<button class="pie-section-player-tools-event-debugger__button" onclick={clearRecords}>
|
|
549
|
-
clear
|
|
550
|
-
</button>
|
|
551
|
-
{#if !controllerAvailable}
|
|
552
|
-
<span class="pie-section-player-tools-event-debugger__status">
|
|
553
|
-
controller unavailable
|
|
554
|
-
</span>
|
|
555
|
-
{/if}
|
|
556
|
-
</div>
|
|
557
|
-
|
|
558
|
-
<div class="pie-section-player-tools-event-debugger__grid">
|
|
559
|
-
<div class="pie-section-player-tools-event-debugger__list">
|
|
560
|
-
{#if visibleRecords.length === 0}
|
|
561
|
-
<div class="pie-section-player-tools-event-debugger__empty">
|
|
562
|
-
No matching events yet. Interact with an item to capture controller events.
|
|
563
|
-
</div>
|
|
564
|
-
{:else}
|
|
565
|
-
{#each visibleRecords as record (record.id)}
|
|
566
|
-
<button
|
|
567
|
-
class="pie-section-player-tools-event-debugger__row"
|
|
568
|
-
class:pie-section-player-tools-event-debugger__row--active={selectedRecord?.id ===
|
|
569
|
-
record.id}
|
|
570
|
-
onclick={() => (selectedRecordId = record.id)}
|
|
571
|
-
>
|
|
572
|
-
<div class="pie-section-player-tools-event-debugger__row-top">
|
|
573
|
-
<span class="pie-section-player-tools-event-debugger__event-type">{record.type}</span>
|
|
574
|
-
<span class="pie-section-player-tools-event-debugger__event-time">
|
|
575
|
-
{formatTimestamp(record.timestamp)}
|
|
576
|
-
</span>
|
|
577
|
-
</div>
|
|
578
|
-
<div class="pie-section-player-tools-event-debugger__row-meta">
|
|
579
|
-
{#if record.itemId}
|
|
580
|
-
<span>item: {record.itemId}</span>
|
|
581
|
-
{/if}
|
|
582
|
-
{#if record.intent}
|
|
583
|
-
<span>intent: {record.intent}</span>
|
|
584
|
-
{/if}
|
|
585
|
-
{#if (semanticCounts.get(record.semanticFingerprint) || 0) > record.duplicateCount}
|
|
586
|
-
<span>
|
|
587
|
-
semantic repeats: {semanticCounts.get(record.semanticFingerprint)}
|
|
588
|
-
</span>
|
|
589
|
-
{/if}
|
|
590
|
-
{#if record.duplicateCount > 1}
|
|
591
|
-
<span>dupes: {record.duplicateCount}</span>
|
|
592
|
-
{/if}
|
|
593
|
-
</div>
|
|
594
|
-
</button>
|
|
595
|
-
{/each}
|
|
596
|
-
{/if}
|
|
597
|
-
</div>
|
|
598
|
-
<div
|
|
599
|
-
class="pie-section-player-tools-event-debugger__detail"
|
|
600
|
-
role="textbox"
|
|
601
|
-
aria-readonly="true"
|
|
602
|
-
tabindex="0"
|
|
603
|
-
aria-label="Controller event details"
|
|
604
|
-
>
|
|
605
|
-
{#if selectedRecord}
|
|
606
|
-
<div class="pie-section-player-tools-event-debugger__detail-meta">
|
|
607
|
-
<div><strong>Type:</strong> {selectedRecord.type}</div>
|
|
608
|
-
<div><strong>Target:</strong> {selectedRecord.targetTag || "unknown"}</div>
|
|
609
|
-
<div><strong>Item:</strong> {selectedRecord.itemId || "n/a"}</div>
|
|
610
|
-
<div><strong>Canonical:</strong> {selectedRecord.canonicalItemId || "n/a"}</div>
|
|
611
|
-
<div><strong>Intent:</strong> {selectedRecord.intent || "n/a"}</div>
|
|
612
|
-
<div><strong>Duplicates:</strong> {selectedRecord.duplicateCount}</div>
|
|
613
|
-
<div>
|
|
614
|
-
<strong>Semantic Repeats:</strong>
|
|
615
|
-
{semanticCounts.get(selectedRecord.semanticFingerprint) || selectedRecord.duplicateCount}
|
|
616
|
-
</div>
|
|
617
|
-
</div>
|
|
618
|
-
<pre class="pie-section-player-tools-event-debugger__pre">{JSON.stringify(
|
|
619
|
-
selectedRecord.payload,
|
|
620
|
-
null,
|
|
621
|
-
2,
|
|
622
|
-
)}</pre>
|
|
623
|
-
{:else}
|
|
624
|
-
<div class="pie-section-player-tools-event-debugger__empty">
|
|
625
|
-
Select an event to inspect payload details.
|
|
626
|
-
</div>
|
|
627
|
-
{/if}
|
|
628
|
-
</div>
|
|
629
|
-
</div>
|
|
630
|
-
</SharedFloatingPanel>
|
|
631
|
-
|
|
632
|
-
<style>
|
|
633
|
-
.pie-section-player-tools-event-debugger__icon-sm {
|
|
634
|
-
width: 1rem;
|
|
635
|
-
height: 1rem;
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
.pie-section-player-tools-event-debugger__toolbar {
|
|
639
|
-
display: flex;
|
|
640
|
-
align-items: center;
|
|
641
|
-
gap: 8px;
|
|
642
|
-
padding: 10px 12px;
|
|
643
|
-
border-bottom: 1px solid var(--color-base-300, #d1d5db);
|
|
644
|
-
flex-wrap: wrap;
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
.pie-section-player-tools-event-debugger__button {
|
|
648
|
-
border: 1px solid var(--color-base-300, #d1d5db);
|
|
649
|
-
background: var(--color-base-100, #fff);
|
|
650
|
-
color: inherit;
|
|
651
|
-
border-radius: 6px;
|
|
652
|
-
font-size: 0.78rem;
|
|
653
|
-
padding: 6px 8px;
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
.pie-section-player-tools-event-debugger__toggle-group {
|
|
657
|
-
display: inline-flex;
|
|
658
|
-
border: 1px solid var(--color-base-300, #d1d5db);
|
|
659
|
-
border-radius: 6px;
|
|
660
|
-
overflow: hidden;
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
.pie-section-player-tools-event-debugger__toggle-button {
|
|
664
|
-
border: none;
|
|
665
|
-
background: var(--color-base-100, #fff);
|
|
666
|
-
color: inherit;
|
|
667
|
-
font-size: 0.78rem;
|
|
668
|
-
padding: 6px 10px;
|
|
669
|
-
cursor: pointer;
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
.pie-section-player-tools-event-debugger__toggle-button + .pie-section-player-tools-event-debugger__toggle-button {
|
|
673
|
-
border-left: 1px solid var(--color-base-300, #d1d5db);
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
.pie-section-player-tools-event-debugger__toggle-button--active {
|
|
677
|
-
background: color-mix(in srgb, var(--color-primary, #2563eb) 18%, transparent);
|
|
678
|
-
font-weight: 600;
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
.pie-section-player-tools-event-debugger__status {
|
|
682
|
-
font-size: 0.72rem;
|
|
683
|
-
opacity: 0.75;
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
.pie-section-player-tools-event-debugger__grid {
|
|
687
|
-
display: grid;
|
|
688
|
-
grid-template-columns: minmax(180px, 1fr) minmax(260px, 1.3fr);
|
|
689
|
-
flex: 1;
|
|
690
|
-
min-height: 0;
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
.pie-section-player-tools-event-debugger__list {
|
|
694
|
-
border-right: 1px solid var(--color-base-300, #d1d5db);
|
|
695
|
-
overflow: auto;
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
.pie-section-player-tools-event-debugger__detail {
|
|
699
|
-
overflow: auto;
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
.pie-section-player-tools-event-debugger__row {
|
|
703
|
-
display: block;
|
|
704
|
-
width: 100%;
|
|
705
|
-
border: 0;
|
|
706
|
-
text-align: left;
|
|
707
|
-
background: transparent;
|
|
708
|
-
padding: 8px 10px;
|
|
709
|
-
border-bottom: 1px solid var(--color-base-300, #e5e7eb);
|
|
710
|
-
cursor: pointer;
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
.pie-section-player-tools-event-debugger__row--active {
|
|
714
|
-
background: color-mix(in srgb, var(--color-primary, #2563eb) 14%, transparent);
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
.pie-section-player-tools-event-debugger__row-top {
|
|
718
|
-
display: flex;
|
|
719
|
-
justify-content: space-between;
|
|
720
|
-
gap: 8px;
|
|
721
|
-
font-size: 0.74rem;
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
.pie-section-player-tools-event-debugger__event-type {
|
|
725
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
726
|
-
font-weight: 600;
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
.pie-section-player-tools-event-debugger__event-time {
|
|
730
|
-
opacity: 0.75;
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
.pie-section-player-tools-event-debugger__row-meta {
|
|
734
|
-
margin-top: 4px;
|
|
735
|
-
display: flex;
|
|
736
|
-
flex-wrap: wrap;
|
|
737
|
-
gap: 8px;
|
|
738
|
-
font-size: 0.7rem;
|
|
739
|
-
opacity: 0.88;
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
.pie-section-player-tools-event-debugger__detail-meta {
|
|
743
|
-
display: grid;
|
|
744
|
-
gap: 3px;
|
|
745
|
-
padding: 10px 12px;
|
|
746
|
-
font-size: 0.78rem;
|
|
747
|
-
border-bottom: 1px solid var(--color-base-300, #d1d5db);
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
.pie-section-player-tools-event-debugger__pre {
|
|
751
|
-
margin: 0;
|
|
752
|
-
padding: 12px;
|
|
753
|
-
font-size: 0.74rem;
|
|
754
|
-
line-height: 1.35;
|
|
755
|
-
white-space: pre-wrap;
|
|
756
|
-
word-break: break-word;
|
|
757
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
.pie-section-player-tools-event-debugger__empty {
|
|
761
|
-
padding: 12px;
|
|
762
|
-
font-size: 0.8rem;
|
|
763
|
-
opacity: 0.8;
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
</style>
|