@pie-players/pie-section-player-tools-event-debugger 0.3.3 → 0.3.5

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/EventPanel.svelte CHANGED
@@ -1,7 +1,7 @@
1
1
  <svelte:options
2
2
  customElement={{
3
3
  tag: "pie-section-player-tools-event-debugger",
4
- shadow: "none",
4
+ shadow: "open",
5
5
  props: {
6
6
  maxEvents: { type: "Number", attribute: "max-events" },
7
7
  toolkitCoordinator: { type: "Object", attribute: "toolkit-coordinator" },
@@ -13,7 +13,15 @@
13
13
 
14
14
  <script lang="ts">
15
15
  import "@pie-players/pie-theme/components.css";
16
- import { createEventDispatcher, onDestroy, onMount } from "svelte";
16
+ import PanelResizeHandle from "@pie-players/pie-section-player-tools-shared/PanelResizeHandle.svelte";
17
+ import PanelWindowControls from "@pie-players/pie-section-player-tools-shared/PanelWindowControls.svelte";
18
+ import {
19
+ computePanelSizeFromViewport,
20
+ createFloatingPanelPointerController,
21
+ getSectionControllerFromCoordinator,
22
+ isMatchingSectionControllerLifecycleEvent,
23
+ } from "@pie-players/pie-section-player-tools-shared";
24
+ import { createEventDispatcher, onDestroy, onMount, untrack } from "svelte";
17
25
 
18
26
  type ControllerEvent = {
19
27
  type?: string;
@@ -21,14 +29,38 @@
21
29
  itemId?: string;
22
30
  canonicalItemId?: string;
23
31
  intent?: string;
24
- replayed?: boolean;
25
32
  [key: string]: unknown;
26
33
  };
34
+ type ControllerRuntimeState = {
35
+ loadingComplete?: boolean;
36
+ totalRegistered?: number;
37
+ totalLoaded?: number;
38
+ itemsComplete?: boolean;
39
+ completedCount?: number;
40
+ totalItems?: number;
41
+ } | null;
42
+ type ToolkitCoordinatorLike = {
43
+ subscribeSectionEvents: (args: {
44
+ sectionId: string;
45
+ attemptId?: string;
46
+ listener: (event: ControllerEvent) => void;
47
+ }) => () => void;
48
+ getSectionController?: (args: {
49
+ sectionId: string;
50
+ attemptId?: string;
51
+ }) => unknown;
52
+ onSectionControllerLifecycle?: (
53
+ listener: (event: {
54
+ key?: { sectionId?: string; attemptId?: string };
55
+ }) => void,
56
+ ) => () => void;
57
+ };
27
58
 
28
59
  type EventType =
29
60
  | "item-session-data-changed"
30
61
  | "item-session-meta-changed"
31
62
  | "item-selected"
63
+ | "section-navigation-change"
32
64
  | "content-loaded"
33
65
  | "item-player-error"
34
66
  | "item-complete-changed"
@@ -45,7 +77,6 @@
45
77
  itemId: string | null;
46
78
  canonicalItemId: string | null;
47
79
  intent: string | null;
48
- replayed: boolean;
49
80
  duplicateCount: number;
50
81
  payload: unknown;
51
82
  fingerprint: string;
@@ -61,7 +92,7 @@
61
92
  attemptId = undefined,
62
93
  }: {
63
94
  maxEvents?: number;
64
- toolkitCoordinator?: any;
95
+ toolkitCoordinator?: ToolkitCoordinatorLike | null;
65
96
  sectionId?: string;
66
97
  attemptId?: string;
67
98
  } = $props();
@@ -76,22 +107,19 @@
76
107
  let records = $state<EventRecord[]>([]);
77
108
  let controllerAvailable = $state(false);
78
109
 
79
- let isDragging = false;
80
- let isResizing = false;
81
- let dragStartX = 0;
82
- let dragStartY = 0;
83
- let dragStartPanelX = 0;
84
- let dragStartPanelY = 0;
85
- let resizeStartX = 0;
86
- let resizeStartY = 0;
87
- let resizeStartWidth = 0;
88
- let resizeStartHeight = 0;
89
110
  let nextRecordId = 1;
90
- let activeController: {
91
- subscribe?: (listener: (event: ControllerEvent) => void) => () => void;
92
- } | null = null;
93
- let unsubscribeController: (() => void) | null = null;
94
- let unsubscribeLifecycle: (() => void) | null = null;
111
+ let resubscribeQueued = false;
112
+ const subscriptions: {
113
+ controller: (() => void) | null;
114
+ lifecycle: (() => void) | null;
115
+ activeSectionId: string;
116
+ activeAttemptId?: string;
117
+ } = {
118
+ controller: null,
119
+ lifecycle: null,
120
+ activeSectionId: "",
121
+ activeAttemptId: undefined,
122
+ };
95
123
 
96
124
  function safeClone<T>(value: T): T {
97
125
  try {
@@ -139,6 +167,7 @@
139
167
  value === "item-session-data-changed" ||
140
168
  value === "item-session-meta-changed" ||
141
169
  value === "item-selected" ||
170
+ value === "section-navigation-change" ||
142
171
  value === "content-loaded" ||
143
172
  value === "item-player-error" ||
144
173
  value === "item-complete-changed" ||
@@ -153,6 +182,7 @@
153
182
 
154
183
  function getEventLevel(type: EventType): EventLevel {
155
184
  if (
185
+ type === "section-navigation-change" ||
156
186
  type === "section-loading-complete" ||
157
187
  type === "section-items-complete-changed" ||
158
188
  type === "section-error"
@@ -178,7 +208,6 @@
178
208
  itemId: getValueAsString(detail?.itemId),
179
209
  canonicalItemId: getValueAsString(detail?.canonicalItemId),
180
210
  intent: getValueAsString(detail?.intent),
181
- replayed: detail?.replayed === true,
182
211
  duplicateCount: 1,
183
212
  payload,
184
213
  fingerprint,
@@ -210,25 +239,62 @@
210
239
  }
211
240
  }
212
241
 
242
+ function handleControllerEvent(event: ControllerEvent): void {
243
+ pushRecord(event || {});
244
+ }
245
+
213
246
  function getController(): any | null {
214
- if (!toolkitCoordinator || !sectionId) return null;
215
- return (
216
- toolkitCoordinator.getSectionController?.({
217
- sectionId,
218
- attemptId,
219
- }) || null
247
+ return getSectionControllerFromCoordinator(
248
+ toolkitCoordinator,
249
+ sectionId,
250
+ attemptId,
220
251
  );
221
252
  }
222
253
 
254
+ function seedFromRuntimeState(controller: {
255
+ getRuntimeState?: () => ControllerRuntimeState;
256
+ }): void {
257
+ const runtimeState = controller?.getRuntimeState?.();
258
+ if (!runtimeState || typeof runtimeState !== "object") return;
259
+ const totalItems =
260
+ typeof runtimeState.totalItems === "number" ? runtimeState.totalItems : 0;
261
+ const now = Date.now();
262
+ pushRecord({
263
+ type: "section-items-complete-changed",
264
+ complete: runtimeState.itemsComplete === true,
265
+ completedCount:
266
+ typeof runtimeState.completedCount === "number"
267
+ ? runtimeState.completedCount
268
+ : 0,
269
+ totalItems,
270
+ timestamp: now,
271
+ });
272
+ if (runtimeState.loadingComplete === true) {
273
+ pushRecord({
274
+ type: "section-loading-complete",
275
+ totalRegistered:
276
+ typeof runtimeState.totalRegistered === "number"
277
+ ? runtimeState.totalRegistered
278
+ : 0,
279
+ totalLoaded:
280
+ typeof runtimeState.totalLoaded === "number"
281
+ ? runtimeState.totalLoaded
282
+ : 0,
283
+ timestamp: now,
284
+ });
285
+ }
286
+ }
287
+
223
288
  function detachControllerSubscription() {
224
- unsubscribeController?.();
225
- unsubscribeController = null;
226
- activeController = null;
289
+ subscriptions.controller?.();
290
+ subscriptions.controller = null;
291
+ subscriptions.activeSectionId = "";
292
+ subscriptions.activeAttemptId = undefined;
227
293
  }
228
294
 
229
295
  function detachLifecycleSubscription() {
230
- unsubscribeLifecycle?.();
231
- unsubscribeLifecycle = null;
296
+ subscriptions.lifecycle?.();
297
+ subscriptions.lifecycle = null;
232
298
  }
233
299
 
234
300
  function ensureControllerSubscription() {
@@ -238,64 +304,57 @@
238
304
  detachControllerSubscription();
239
305
  return;
240
306
  }
241
- if (controller === activeController) return;
242
- detachControllerSubscription();
243
- activeController = controller;
244
- unsubscribeController =
245
- controller.subscribe?.((event: ControllerEvent) => {
246
- pushRecord(event || {});
247
- }) || null;
248
- }
249
-
250
- function startDrag(event: MouseEvent) {
251
- isDragging = true;
252
- dragStartX = event.clientX;
253
- dragStartY = event.clientY;
254
- dragStartPanelX = panelX;
255
- dragStartPanelY = panelY;
256
- document.addEventListener("mousemove", onDrag);
257
- document.addEventListener("mouseup", stopDrag);
258
- }
259
-
260
- function onDrag(event: MouseEvent) {
261
- if (!isDragging) return;
262
- const deltaX = event.clientX - dragStartX;
263
- const deltaY = event.clientY - dragStartY;
264
- panelX = Math.max(0, Math.min(dragStartPanelX + deltaX, window.innerWidth - panelWidth));
265
- panelY = Math.max(0, Math.min(dragStartPanelY + deltaY, window.innerHeight - 100));
266
- }
267
307
 
268
- function stopDrag() {
269
- isDragging = false;
270
- document.removeEventListener("mousemove", onDrag);
271
- document.removeEventListener("mouseup", stopDrag);
272
- }
273
-
274
- function startResize(event: MouseEvent) {
275
- isResizing = true;
276
- resizeStartX = event.clientX;
277
- resizeStartY = event.clientY;
278
- resizeStartWidth = panelWidth;
279
- resizeStartHeight = panelHeight;
280
- document.addEventListener("mousemove", onResize);
281
- document.addEventListener("mouseup", stopResize);
282
- event.preventDefault();
283
- event.stopPropagation();
284
- }
285
-
286
- function onResize(event: MouseEvent) {
287
- if (!isResizing) return;
288
- const deltaX = event.clientX - resizeStartX;
289
- const deltaY = event.clientY - resizeStartY;
290
- panelWidth = Math.max(340, Math.min(resizeStartWidth + deltaX, window.innerWidth - panelX));
291
- panelHeight = Math.max(260, Math.min(resizeStartHeight + deltaY, window.innerHeight - panelY));
292
- }
308
+ const nextAttemptId = attemptId || undefined;
309
+ const isSameTarget =
310
+ subscriptions.activeSectionId === sectionId &&
311
+ subscriptions.activeAttemptId === nextAttemptId;
312
+ if (isSameTarget && subscriptions.controller) {
313
+ return;
314
+ }
293
315
 
294
- function stopResize() {
295
- isResizing = false;
296
- document.removeEventListener("mousemove", onResize);
297
- document.removeEventListener("mouseup", stopResize);
298
- }
316
+ detachControllerSubscription();
317
+ subscriptions.controller =
318
+ toolkitCoordinator?.subscribeSectionEvents({
319
+ sectionId,
320
+ attemptId,
321
+ listener: handleControllerEvent,
322
+ }) || null;
323
+ subscriptions.activeSectionId = sectionId;
324
+ subscriptions.activeAttemptId = nextAttemptId;
325
+ seedFromRuntimeState(controller);
326
+ }
327
+
328
+ function queueEnsureControllerSubscription(): void {
329
+ if (resubscribeQueued) return;
330
+ resubscribeQueued = true;
331
+ queueMicrotask(() => {
332
+ resubscribeQueued = false;
333
+ ensureControllerSubscription();
334
+ });
335
+ }
336
+
337
+ const pointerController = createFloatingPanelPointerController({
338
+ getState: () => ({
339
+ x: panelX,
340
+ y: panelY,
341
+ width: panelWidth,
342
+ height: panelHeight,
343
+ }),
344
+ setState: (next: {
345
+ x: number;
346
+ y: number;
347
+ width: number;
348
+ height: number;
349
+ }) => {
350
+ panelX = next.x;
351
+ panelY = next.y;
352
+ panelWidth = next.width;
353
+ panelHeight = next.height;
354
+ },
355
+ minWidth: 340,
356
+ minHeight: 260,
357
+ });
299
358
 
300
359
  function clearRecords() {
301
360
  records = [];
@@ -326,53 +385,68 @@
326
385
  );
327
386
 
328
387
  onMount(() => {
329
- const clamp = (value: number, min: number, max: number) => Math.max(min, Math.min(value, max));
330
- const viewportWidth = window.innerWidth;
331
- const viewportHeight = window.innerHeight;
332
- panelWidth = clamp(Math.round(viewportWidth * 0.34), 380, 720);
333
- panelHeight = clamp(Math.round(viewportHeight * 0.74), 360, 860);
334
- panelX = Math.max(16, Math.round(viewportWidth * 0.58));
335
- panelY = Math.max(16, Math.round((viewportHeight - panelHeight) / 2));
336
- ensureControllerSubscription();
337
- unsubscribeLifecycle = toolkitCoordinator?.onSectionControllerLifecycle?.(
338
- (event: { key?: { sectionId?: string; attemptId?: string } }) => {
339
- const eventSectionId = event?.key?.sectionId || "";
340
- const eventAttemptId = event?.key?.attemptId || undefined;
341
- if (eventSectionId !== sectionId) return;
342
- if ((eventAttemptId || undefined) !== (attemptId || undefined)) return;
343
- ensureControllerSubscription();
388
+ const initial = computePanelSizeFromViewport(
389
+ { width: window.innerWidth, height: window.innerHeight },
390
+ {
391
+ widthRatio: 0.34,
392
+ heightRatio: 0.74,
393
+ minWidth: 380,
394
+ maxWidth: 720,
395
+ minHeight: 360,
396
+ maxHeight: 860,
397
+ alignX: "right",
398
+ alignY: "center",
399
+ paddingX: 16,
400
+ paddingY: 16,
344
401
  },
345
402
  );
346
-
347
- return () => {
348
- detachControllerSubscription();
349
- detachLifecycleSubscription();
350
- };
403
+ panelX = initial.x;
404
+ panelY = initial.y;
405
+ panelWidth = initial.width;
406
+ panelHeight = initial.height;
351
407
  });
352
408
 
353
409
  $effect(() => {
354
410
  void toolkitCoordinator;
355
411
  void sectionId;
356
412
  void attemptId;
357
- ensureControllerSubscription();
358
- detachLifecycleSubscription();
359
- unsubscribeLifecycle = toolkitCoordinator?.onSectionControllerLifecycle?.(
360
- (event: { key?: { sectionId?: string; attemptId?: string } }) => {
361
- const eventSectionId = event?.key?.sectionId || "";
362
- const eventAttemptId = event?.key?.attemptId || undefined;
363
- if (eventSectionId !== sectionId) return;
364
- if ((eventAttemptId || undefined) !== (attemptId || undefined)) return;
365
- ensureControllerSubscription();
366
- },
367
- );
413
+ untrack(() => {
414
+ ensureControllerSubscription();
415
+ detachLifecycleSubscription();
416
+ subscriptions.lifecycle = toolkitCoordinator?.onSectionControllerLifecycle?.(
417
+ (event: {
418
+ type?: "ready" | "disposed";
419
+ key?: { sectionId?: string; attemptId?: string };
420
+ }) => {
421
+ if (
422
+ !isMatchingSectionControllerLifecycleEvent(event, sectionId, attemptId)
423
+ )
424
+ return;
425
+ if (event?.type === "disposed") {
426
+ detachControllerSubscription();
427
+ queueEnsureControllerSubscription();
428
+ return;
429
+ }
430
+ const nextAttemptId = attemptId || undefined;
431
+ if (
432
+ subscriptions.controller &&
433
+ subscriptions.activeSectionId === sectionId &&
434
+ subscriptions.activeAttemptId === nextAttemptId
435
+ ) {
436
+ return;
437
+ }
438
+ queueEnsureControllerSubscription();
439
+ },
440
+ ) || null;
441
+ });
368
442
  return () => {
443
+ detachControllerSubscription();
369
444
  detachLifecycleSubscription();
370
445
  };
371
446
  });
372
447
 
373
448
  onDestroy(() => {
374
- stopDrag();
375
- stopResize();
449
+ pointerController.stop();
376
450
  detachControllerSubscription();
377
451
  detachLifecycleSubscription();
378
452
  });
@@ -384,35 +458,34 @@
384
458
  >
385
459
  <div
386
460
  class="pie-section-player-tools-event-debugger__header"
387
- onmousedown={startDrag}
461
+ onmousedown={(event: MouseEvent) => pointerController.startDrag(event)}
388
462
  role="button"
389
463
  tabindex="0"
390
464
  aria-label="Drag event debugger panel"
391
465
  >
392
466
  <div class="pie-section-player-tools-event-debugger__header-title">
393
- <h3 class="pie-section-player-tools-event-debugger__title">Session Broadcasts</h3>
394
- <span class="pie-section-player-tools-event-debugger__counter">{records.length}</span>
467
+ <svg
468
+ xmlns="http://www.w3.org/2000/svg"
469
+ class="pie-section-player-tools-event-debugger__icon-sm"
470
+ fill="none"
471
+ viewBox="0 0 24 24"
472
+ stroke="currentColor"
473
+ >
474
+ <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" />
475
+ </svg>
476
+ <h3 class="pie-section-player-tools-event-debugger__title">Controller Events</h3>
395
477
  </div>
396
478
  <div class="pie-section-player-tools-event-debugger__header-actions">
397
- <button
398
- class="pie-section-player-tools-event-debugger__icon-button"
399
- onclick={() => (isMinimized = !isMinimized)}
400
- title={isMinimized ? "Maximize" : "Minimize"}
401
- >
402
- {isMinimized ? "▴" : "▾"}
403
- </button>
404
- <button
405
- class="pie-section-player-tools-event-debugger__icon-button"
406
- onclick={() => dispatch("close")}
407
- title="Close"
408
- >
409
-
410
- </button>
479
+ <PanelWindowControls
480
+ minimized={isMinimized}
481
+ onToggle={() => (isMinimized = !isMinimized)}
482
+ onClose={() => dispatch("close")}
483
+ />
411
484
  </div>
412
485
  </div>
413
486
 
414
487
  {#if !isMinimized}
415
- <div class="pie-section-player-tools-event-debugger__content-shell" style="height: {panelHeight - 56}px;">
488
+ <div class="pie-section-player-tools-event-debugger__content-shell" style="height: {panelHeight - 50}px;">
416
489
  <div class="pie-section-player-tools-event-debugger__toolbar">
417
490
  <div
418
491
  class="pie-section-player-tools-event-debugger__toggle-group"
@@ -455,7 +528,7 @@
455
528
  <div class="pie-section-player-tools-event-debugger__list">
456
529
  {#if visibleRecords.length === 0}
457
530
  <div class="pie-section-player-tools-event-debugger__empty">
458
- No matching events yet. Interact with an item to capture broadcasts.
531
+ No matching events yet. Interact with an item to capture controller events.
459
532
  </div>
460
533
  {:else}
461
534
  {#each visibleRecords as record (record.id)}
@@ -475,9 +548,6 @@
475
548
  {#if record.itemId}
476
549
  <span>item: {record.itemId}</span>
477
550
  {/if}
478
- {#if record.replayed}
479
- <span>replayed</span>
480
- {/if}
481
551
  {#if record.intent}
482
552
  <span>intent: {record.intent}</span>
483
553
  {/if}
@@ -501,7 +571,6 @@
501
571
  <div><strong>Target:</strong> {selectedRecord.targetTag || "unknown"}</div>
502
572
  <div><strong>Item:</strong> {selectedRecord.itemId || "n/a"}</div>
503
573
  <div><strong>Canonical:</strong> {selectedRecord.canonicalItemId || "n/a"}</div>
504
- <div><strong>Replayed:</strong> {selectedRecord.replayed ? "yes" : "no"}</div>
505
574
  <div><strong>Intent:</strong> {selectedRecord.intent || "n/a"}</div>
506
575
  <div><strong>Duplicates:</strong> {selectedRecord.duplicateCount}</div>
507
576
  <div>
@@ -525,15 +594,7 @@
525
594
  {/if}
526
595
 
527
596
  {#if !isMinimized}
528
- <div
529
- class="pie-section-player-tools-event-debugger__resize-handle"
530
- onmousedown={startResize}
531
- role="button"
532
- tabindex="0"
533
- title="Resize window"
534
- >
535
-
536
- </div>
597
+ <PanelResizeHandle onPointerDown={(event: MouseEvent) => pointerController.startResize(event)} />
537
598
  {/if}
538
599
  </div>
539
600
 
@@ -543,22 +604,22 @@
543
604
  z-index: 9999;
544
605
  background: var(--color-base-100, #fff);
545
606
  color: var(--color-base-content, #1f2937);
546
- border: 1px solid var(--color-base-300, #d1d5db);
607
+ border: 2px solid var(--color-base-300, #d1d5db);
547
608
  border-radius: 8px;
548
- box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
609
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
549
610
  overflow: hidden;
550
611
  font-family: var(--pie-font-family, Inter, system-ui, sans-serif);
551
612
  }
552
613
 
553
614
  .pie-section-player-tools-event-debugger__header {
554
- height: 56px;
555
- padding: 0 12px;
615
+ padding: 8px 16px;
556
616
  display: flex;
557
617
  align-items: center;
558
618
  justify-content: space-between;
559
619
  background: var(--color-base-200, #f3f4f6);
560
620
  cursor: move;
561
621
  user-select: none;
622
+ border-bottom: 1px solid var(--color-base-300, #d1d5db);
562
623
  }
563
624
 
564
625
  .pie-section-player-tools-event-debugger__header-title {
@@ -567,32 +628,20 @@
567
628
  gap: 8px;
568
629
  }
569
630
 
631
+ .pie-section-player-tools-event-debugger__icon-sm {
632
+ width: 1rem;
633
+ height: 1rem;
634
+ }
635
+
570
636
  .pie-section-player-tools-event-debugger__title {
571
637
  margin: 0;
572
638
  font-size: 0.95rem;
573
639
  font-weight: 700;
574
640
  }
575
641
 
576
- .pie-section-player-tools-event-debugger__counter {
577
- font-size: 0.75rem;
578
- padding: 2px 7px;
579
- border-radius: 999px;
580
- background: var(--color-primary, #2563eb);
581
- color: white;
582
- }
583
-
584
642
  .pie-section-player-tools-event-debugger__header-actions {
585
643
  display: flex;
586
- gap: 6px;
587
- }
588
-
589
- .pie-section-player-tools-event-debugger__icon-button {
590
- border: 1px solid var(--color-base-300, #d1d5db);
591
- background: var(--color-base-100, #fff);
592
- border-radius: 6px;
593
- font-size: 0.8rem;
594
- padding: 4px 7px;
595
- cursor: pointer;
644
+ gap: 4px;
596
645
  }
597
646
 
598
647
  .pie-section-player-tools-event-debugger__content-shell {
@@ -729,13 +778,4 @@
729
778
  opacity: 0.8;
730
779
  }
731
780
 
732
- .pie-section-player-tools-event-debugger__resize-handle {
733
- position: absolute;
734
- right: 6px;
735
- bottom: 4px;
736
- cursor: nwse-resize;
737
- user-select: none;
738
- font-size: 0.9rem;
739
- opacity: 0.7;
740
- }
741
781
  </style>