nuxt-devtools-observatory 0.1.26 → 0.1.30

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.
@@ -1,6 +1,8 @@
1
1
  <script setup lang="ts">
2
2
  import { ref, computed, watch } from 'vue'
3
- import { useObservatoryData, getObservatoryOrigin, type InjectEntry, type ProvideEntry } from '../stores/observatory'
3
+ import { useResizablePane } from '@observatory-client/composables/useResizablePane'
4
+ import { useObservatoryData, openInEditor as openInEditorFromStore } from '@observatory-client/stores/observatory'
5
+ import type { InjectEntry, ProvideEntry } from '@observatory/types/snapshot'
4
6
 
5
7
  interface TreeNodeData {
6
8
  id: string
@@ -45,6 +47,7 @@ const V_GAP = 72
45
47
  const H_GAP = 18
46
48
 
47
49
  const { provideInject, connected } = useObservatoryData()
50
+ const { paneWidth: detailWidth, onHandleMouseDown } = useResizablePane(280, 'observatory:provide:detailWidth')
48
51
 
49
52
  function nodeColor(node: TreeNodeData): string {
50
53
  if (node.injects.some((entry) => !entry.ok)) {
@@ -97,7 +100,6 @@ function matchesSearch(node: TreeNodeData, query: string): boolean {
97
100
  * Count leaf nodes in a subtree iteratively to avoid stack overflow on
98
101
  * pathologically deep provide/inject trees (e.g. every component re-providing
99
102
  * the same key creates a chain as long as the component tree itself).
100
- *
101
103
  * @param {TreeNodeData} root - The root node of the subtree to count leaves for.
102
104
  * @returns {number} The number of leaf nodes in the subtree.
103
105
  */
@@ -173,14 +175,7 @@ function basename(file: string) {
173
175
  }
174
176
 
175
177
  function openInEditor(file: string) {
176
- if (!file || file === 'unknown') {
177
- return
178
- }
179
- const origin = getObservatoryOrigin()
180
- if (!origin) {
181
- return
182
- }
183
- window.top?.postMessage({ type: 'observatory:open-in-editor', file }, origin)
178
+ openInEditorFromStore(file)
184
179
  }
185
180
 
186
181
  function componentId(entry: ProvideEntry | InjectEntry) {
@@ -457,20 +452,20 @@ const edges = computed<Edge[]>(() => {
457
452
  </script>
458
453
 
459
454
  <template>
460
- <div class="view">
461
- <div class="toolbar">
455
+ <div class="provide-graph tracker-view">
456
+ <div class="provide-graph__toolbar tracker-toolbar">
462
457
  <button :class="{ active: activeFilter === 'all' }" @click="activeFilter = 'all'">all keys</button>
463
458
  <button
464
459
  v-for="key in allKeys"
465
460
  :key="key"
466
- style="font-family: var(--mono)"
461
+ class="provide-graph__key-filter mono"
467
462
  :class="{ active: activeFilter === key }"
468
463
  @click="activeFilter = key"
469
464
  >
470
465
  {{ key }}
471
466
  </button>
472
467
  <button
473
- style="margin-left: auto"
468
+ class="provide-graph__toolbar-spacer"
474
469
  :class="{ 'danger-active': activeFilter === 'shadow' }"
475
470
  @click="activeFilter = activeFilter === 'shadow' ? 'all' : 'shadow'"
476
471
  >
@@ -479,37 +474,37 @@ const edges = computed<Edge[]>(() => {
479
474
  <button :class="{ 'danger-active': activeFilter === 'warn' }" @click="activeFilter = activeFilter === 'warn' ? 'all' : 'warn'">
480
475
  warnings
481
476
  </button>
482
- <input v-model="searchQuery" type="search" placeholder="search component or key…" style="max-width: 200px" />
477
+ <input v-model="searchQuery" type="search" class="provide-graph__search" placeholder="search component or key…" />
483
478
  </div>
484
479
 
485
- <div class="split">
486
- <div class="graph-area">
487
- <div class="legend">
488
- <span class="dot" style="background: var(--teal)"></span>
480
+ <div class="provide-graph__split tracker-split">
481
+ <div class="provide-graph__graph-area">
482
+ <div class="provide-graph__legend">
483
+ <span class="provide-graph__legend-dot provide-graph__legend-dot--provides"></span>
489
484
  <span>provides</span>
490
- <span class="dot" style="background: var(--blue)"></span>
485
+ <span class="provide-graph__legend-dot provide-graph__legend-dot--both"></span>
491
486
  <span>both</span>
492
- <span class="dot" style="background: var(--text3)"></span>
487
+ <span class="provide-graph__legend-dot provide-graph__legend-dot--injects"></span>
493
488
  <span>injects</span>
494
- <span class="dot" style="background: var(--red)"></span>
489
+ <span class="provide-graph__legend-dot provide-graph__legend-dot--missing"></span>
495
490
  <span>missing provider</span>
496
491
  </div>
497
- <div v-if="layout.length" class="canvas-stage">
498
- <div class="canvas-wrap" :style="{ width: `${canvasW}px`, height: `${canvasH}px` }">
499
- <svg class="edges-svg" :width="canvasW" :height="canvasH" :viewBox="`0 0 ${canvasW} ${canvasH}`">
492
+ <div v-if="layout.length" class="provide-graph__canvas-stage">
493
+ <div class="provide-graph__canvas-wrap" :style="{ width: `${canvasW}px`, height: `${canvasH}px` }">
494
+ <svg class="provide-graph__edges-svg" :width="canvasW" :height="canvasH" :viewBox="`0 0 ${canvasW} ${canvasH}`">
500
495
  <path
501
496
  v-for="edge in edges"
502
497
  :key="edge.id"
503
498
  :d="`M ${edge.x1},${edge.y1} C ${edge.x1},${(edge.y1 + edge.y2) / 2} ${edge.x2},${(edge.y1 + edge.y2) / 2} ${edge.x2},${edge.y2}`"
504
- class="edge"
499
+ class="provide-graph__edge"
505
500
  fill="none"
506
501
  />
507
502
  </svg>
508
503
  <div
509
504
  v-for="layoutNode in layout"
510
505
  :key="layoutNode.data.id"
511
- class="graph-node"
512
- :class="{ 'is-selected': selectedNode?.id === layoutNode.data.id }"
506
+ class="provide-graph__node"
507
+ :class="{ 'provide-graph__node--selected': selectedNode?.id === layoutNode.data.id }"
513
508
  :style="{
514
509
  left: `${layoutNode.x - NODE_W / 2}px`,
515
510
  top: `${layoutNode.y - NODE_H / 2}px`,
@@ -518,8 +513,8 @@ const edges = computed<Edge[]>(() => {
518
513
  }"
519
514
  @click="selectedNode = layoutNode.data"
520
515
  >
521
- <span class="node-dot" :style="{ background: nodeColor(layoutNode.data) }"></span>
522
- <span class="mono node-label">{{ layoutNode.data.label }}</span>
516
+ <span class="provide-graph__node-dot" :style="{ background: nodeColor(layoutNode.data) }"></span>
517
+ <span class="mono provide-graph__node-label">{{ layoutNode.data.label }}</span>
523
518
  <span v-if="layoutNode.data.provides.length" class="badge badge-ok badge-xs">
524
519
  +{{ layoutNode.data.provides.length }}
525
520
  </span>
@@ -527,14 +522,16 @@ const edges = computed<Edge[]>(() => {
527
522
  </div>
528
523
  </div>
529
524
  </div>
530
- <div v-else class="graph-empty">
525
+ <div v-else class="provide-graph__graph-empty">
531
526
  {{ connected ? 'No components match the current provide/inject filter.' : 'Waiting for connection to the Nuxt app…' }}
532
527
  </div>
533
528
  </div>
534
529
 
535
- <div v-if="selectedNode" class="detail-panel">
536
- <div class="detail-header">
537
- <span class="mono bold" style="font-size: 12px">{{ selectedNode.label }}</span>
530
+ <div v-if="selectedNode" class="tracker-resize-handle" @mousedown="onHandleMouseDown" />
531
+
532
+ <div v-if="selectedNode" class="provide-graph__detail tracker-detail-panel" :style="{ width: detailWidth + 'px' }">
533
+ <div class="provide-graph__detail-header">
534
+ <span class="provide-graph__detail-title mono bold">{{ selectedNode.label }}</span>
538
535
  <button
539
536
  v-if="selectedNode.componentFile && selectedNode.componentFile !== 'unknown'"
540
537
  class="jump-btn"
@@ -547,7 +544,7 @@ const edges = computed<Edge[]>(() => {
547
544
  </div>
548
545
 
549
546
  <div v-if="selectedNode.provides.length" class="detail-section">
550
- <div class="section-label">provides ({{ selectedNode.provides.length }})</div>
547
+ <div class="tracker-section-label provide-graph__section-label">provides ({{ selectedNode.provides.length }})</div>
551
548
  <div class="detail-list">
552
549
  <div
553
550
  v-for="(entry, index) in selectedNode.provides"
@@ -577,7 +574,7 @@ const edges = computed<Edge[]>(() => {
577
574
  <span v-for="name in entry.consumerNames" :key="name" class="consumer-chip mono">{{ name }}</span>
578
575
  <span v-if="!entry.consumerNames.length" class="muted text-sm">no consumers</span>
579
576
  </div>
580
- <div v-else class="muted text-sm" style="padding: 2px 0; font-size: 11px">no consumers detected</div>
577
+ <div v-else class="provide-graph__compact-muted muted text-sm">no consumers detected</div>
581
578
  <pre
582
579
  v-if="entry.complex && expandedProvideValues.has(provideValueId(selectedNode.id, entry.key, index))"
583
580
  class="value-box"
@@ -592,7 +589,7 @@ const edges = computed<Edge[]>(() => {
592
589
  class="detail-section"
593
590
  :style="{ marginTop: selectedNode.provides.length ? '10px' : '0' }"
594
591
  >
595
- <div class="section-label">injects ({{ selectedNode.injects.length }})</div>
592
+ <div class="tracker-section-label provide-graph__section-label">injects ({{ selectedNode.injects.length }})</div>
596
593
  <div class="detail-list">
597
594
  <div
598
595
  v-for="entry in selectedNode.injects"
@@ -610,11 +607,11 @@ const edges = computed<Edge[]>(() => {
610
607
  </div>
611
608
  </div>
612
609
 
613
- <div v-if="!selectedNode.provides.length && !selectedNode.injects.length" class="muted text-sm" style="margin-top: 8px">
610
+ <div v-if="!selectedNode.provides.length && !selectedNode.injects.length" class="provide-graph__empty-detail muted text-sm">
614
611
  no provide/inject in this component
615
612
  </div>
616
613
  </div>
617
- <div v-else class="detail-empty">
614
+ <div v-else class="tracker-detail-empty">
618
615
  {{ connected ? 'Click a node to inspect.' : 'Waiting for connection to the Nuxt app…' }}
619
616
  </div>
620
617
  </div>
@@ -622,57 +619,40 @@ const edges = computed<Edge[]>(() => {
622
619
  </template>
623
620
 
624
621
  <style scoped>
625
- .view {
626
- display: flex;
627
- flex-direction: column;
628
- height: 100%;
629
- overflow: hidden;
630
- padding: 12px;
631
- gap: 10px;
632
- }
633
-
634
- .toolbar {
635
- display: flex;
636
- align-items: center;
637
- gap: 6px;
638
- flex-shrink: 0;
639
- flex-wrap: wrap;
622
+ .provide-graph__toolbar-spacer {
623
+ margin-left: auto;
640
624
  }
641
625
 
642
- .split {
643
- display: flex;
644
- gap: 12px;
645
- flex: 1;
646
- overflow: hidden;
647
- min-height: 0;
626
+ .provide-graph__search {
627
+ max-width: 200px;
648
628
  }
649
629
 
650
- .graph-area {
630
+ .provide-graph__graph-area {
651
631
  flex: 1;
652
632
  overflow: auto;
653
- border: 0.5px solid var(--border);
633
+ border: var(--tracker-border-width) solid var(--border);
654
634
  border-radius: var(--radius-lg);
655
- padding: 12px;
635
+ padding: var(--tracker-space-3);
656
636
  background: var(--bg3);
657
637
  }
658
638
 
659
- .legend {
639
+ .provide-graph__legend {
660
640
  display: flex;
661
641
  align-items: center;
662
- gap: 12px;
663
- font-size: 11px;
642
+ gap: var(--tracker-space-3);
643
+ font-size: var(--tracker-font-size-sm);
664
644
  color: var(--text2);
665
- margin-bottom: 12px;
645
+ margin-bottom: var(--tracker-space-3);
666
646
  }
667
647
 
668
- .canvas-stage {
648
+ .provide-graph__canvas-stage {
669
649
  display: flex;
670
650
  justify-content: center;
671
651
  align-items: flex-start;
672
652
  min-width: 100%;
673
653
  }
674
654
 
675
- .dot {
655
+ .provide-graph__legend-dot {
676
656
  width: 8px;
677
657
  height: 8px;
678
658
  border-radius: 50%;
@@ -680,23 +660,39 @@ const edges = computed<Edge[]>(() => {
680
660
  margin-right: 2px;
681
661
  }
682
662
 
683
- .canvas-wrap {
663
+ .provide-graph__legend-dot--provides {
664
+ background: var(--teal);
665
+ }
666
+
667
+ .provide-graph__legend-dot--both {
668
+ background: var(--blue);
669
+ }
670
+
671
+ .provide-graph__legend-dot--injects {
672
+ background: var(--text3);
673
+ }
674
+
675
+ .provide-graph__legend-dot--missing {
676
+ background: var(--red);
677
+ }
678
+
679
+ .provide-graph__canvas-wrap {
684
680
  position: relative;
685
681
  }
686
682
 
687
- .edges-svg {
683
+ .provide-graph__edges-svg {
688
684
  position: absolute;
689
685
  top: 0;
690
686
  left: 0;
691
687
  pointer-events: none;
692
688
  }
693
689
 
694
- .edge {
690
+ .provide-graph__edge {
695
691
  stroke: var(--border);
696
692
  stroke-width: 1.5;
697
693
  }
698
694
 
699
- .graph-node {
695
+ .provide-graph__node {
700
696
  position: absolute;
701
697
  display: flex;
702
698
  align-items: center;
@@ -704,35 +700,35 @@ const edges = computed<Edge[]>(() => {
704
700
  padding: 0 10px;
705
701
  height: 32px;
706
702
  border-radius: var(--radius);
707
- border: 0.5px solid var(--border);
703
+ border: var(--tracker-border-width) solid var(--border);
708
704
  background: var(--bg3);
709
705
  cursor: pointer;
710
706
  transition:
711
- border-color 0.12s,
712
- background 0.12s;
707
+ border-color var(--tracker-transition-fast),
708
+ background var(--tracker-transition-fast);
713
709
  overflow: hidden;
714
710
  box-sizing: border-box;
715
711
  white-space: nowrap;
716
712
  }
717
713
 
718
- .graph-node:hover {
714
+ .provide-graph__node:hover {
719
715
  border-color: var(--text3);
720
716
  }
721
717
 
722
- .graph-node.is-selected {
718
+ .provide-graph__node--selected {
723
719
  border-color: var(--node-color);
724
720
  background: color-mix(in srgb, var(--node-color) 8%, transparent);
725
721
  }
726
722
 
727
- .node-dot {
723
+ .provide-graph__node-dot {
728
724
  width: 7px;
729
725
  height: 7px;
730
726
  border-radius: 50%;
731
727
  flex-shrink: 0;
732
728
  }
733
729
 
734
- .node-label {
735
- font-size: 11px;
730
+ .provide-graph__node-label {
731
+ font-size: var(--tracker-font-size-sm);
736
732
  flex: 1;
737
733
  overflow: hidden;
738
734
  text-overflow: ellipsis;
@@ -743,54 +739,32 @@ const edges = computed<Edge[]>(() => {
743
739
  padding: 1px 4px;
744
740
  }
745
741
 
746
- .detail-panel {
747
- width: 280px;
748
- flex-shrink: 0;
749
- overflow: auto;
750
- border: 0.5px solid var(--border);
751
- border-radius: var(--radius-lg);
752
- padding: 12px;
753
- background: var(--bg3);
754
- display: flex;
755
- flex-direction: column;
756
- gap: 4px;
742
+ .provide-graph__detail {
757
743
  min-height: 0;
744
+ gap: var(--tracker-space-1);
758
745
  }
759
746
 
760
- .detail-empty {
761
- width: 280px;
762
- display: flex;
763
- align-items: center;
764
- justify-content: center;
765
- color: var(--text3);
766
- font-size: 12px;
767
- border: 0.5px dashed var(--border);
768
- border-radius: var(--radius-lg);
769
- flex-shrink: 0;
770
- }
771
-
772
- .graph-empty {
747
+ .provide-graph__graph-empty {
773
748
  display: flex;
774
749
  align-items: center;
775
750
  justify-content: center;
776
751
  min-height: 180px;
777
752
  color: var(--text3);
778
- font-size: 12px;
753
+ font-size: var(--tracker-font-size-md);
779
754
  }
780
755
 
781
- .detail-header {
756
+ .provide-graph__detail-header {
782
757
  display: flex;
783
758
  align-items: center;
784
759
  justify-content: space-between;
785
- margin-bottom: 6px;
760
+ margin-bottom: var(--tracker-gap-toolbar);
786
761
  }
787
762
 
788
- .section-label {
789
- font-size: 10px;
790
- font-weight: 500;
791
- text-transform: uppercase;
792
- letter-spacing: 0.4px;
793
- color: var(--text3);
763
+ .provide-graph__detail-title {
764
+ font-size: var(--tracker-font-size-md);
765
+ }
766
+
767
+ .provide-graph__section-label {
794
768
  margin: 8px 0 5px;
795
769
  }
796
770
 
@@ -805,7 +779,6 @@ const edges = computed<Edge[]>(() => {
805
779
  flex-direction: column;
806
780
  gap: 3px;
807
781
  overflow: auto;
808
- max-height: 220px;
809
782
  padding-right: 2px;
810
783
  }
811
784
 
@@ -820,11 +793,20 @@ const edges = computed<Edge[]>(() => {
820
793
  }
821
794
 
822
795
  .row-warning {
823
- font-size: 11px;
796
+ font-size: var(--tracker-font-size-sm);
824
797
  color: var(--amber);
825
798
  padding: 2px 0;
826
799
  }
827
800
 
801
+ .provide-graph__compact-muted {
802
+ padding: 2px 0;
803
+ font-size: var(--tracker-font-size-sm);
804
+ }
805
+
806
+ .provide-graph__empty-detail {
807
+ margin-top: var(--tracker-space-2);
808
+ }
809
+
828
810
  .row-consumers {
829
811
  display: flex;
830
812
  flex-wrap: wrap;
@@ -869,7 +851,8 @@ const edges = computed<Edge[]>(() => {
869
851
  .row-main {
870
852
  display: flex;
871
853
  align-items: center;
872
- gap: 8px;
854
+ flex-wrap: wrap;
855
+ gap: 4px 8px;
873
856
  min-width: 0;
874
857
  }
875
858
 
@@ -900,7 +883,7 @@ const edges = computed<Edge[]>(() => {
900
883
  border-radius: var(--radius);
901
884
  padding: 8px 10px;
902
885
  white-space: pre-wrap;
903
- word-break: break-word;
886
+ overflow-wrap: break-word;
904
887
  overflow: auto;
905
888
  max-height: 180px;
906
889
  }