nuxt-devtools-observatory 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +209 -0
- package/client/dist/assets/index-C76d764s.js +17 -0
- package/client/dist/assets/index-yIuOV1_N.css +1 -0
- package/client/dist/index.html +47 -0
- package/client/index.html +46 -0
- package/client/src/App.vue +114 -0
- package/client/src/main.ts +5 -0
- package/client/src/stores/observatory.ts +65 -0
- package/client/src/style.css +261 -0
- package/client/src/views/ComposableTracker.vue +347 -0
- package/client/src/views/FetchDashboard.vue +492 -0
- package/client/src/views/ProvideInjectGraph.vue +481 -0
- package/client/src/views/RenderHeatmap.vue +492 -0
- package/client/src/views/TransitionTimeline.vue +527 -0
- package/client/tsconfig.json +16 -0
- package/client/vite.config.ts +12 -0
- package/dist/module.d.mts +38 -0
- package/dist/module.json +12 -0
- package/dist/module.mjs +562 -0
- package/dist/runtime/composables/composable-registry.d.ts +40 -0
- package/dist/runtime/composables/composable-registry.js +135 -0
- package/dist/runtime/composables/fetch-registry.d.ts +63 -0
- package/dist/runtime/composables/fetch-registry.js +83 -0
- package/dist/runtime/composables/provide-inject-registry.d.ts +57 -0
- package/dist/runtime/composables/provide-inject-registry.js +96 -0
- package/dist/runtime/composables/render-registry.d.ts +36 -0
- package/dist/runtime/composables/render-registry.js +85 -0
- package/dist/runtime/composables/transition-registry.d.ts +21 -0
- package/dist/runtime/composables/transition-registry.js +125 -0
- package/dist/runtime/plugin.d.ts +2 -0
- package/dist/runtime/plugin.js +66 -0
- package/dist/types.d.mts +3 -0
- package/package.json +89 -0
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, computed } from 'vue'
|
|
3
|
+
import { useObservatoryData, type TransitionEntry } from '../stores/observatory'
|
|
4
|
+
|
|
5
|
+
const { transitions: entries, connected } = useObservatoryData()
|
|
6
|
+
|
|
7
|
+
type FilterMode = 'all' | 'cancelled' | 'active' | 'completed'
|
|
8
|
+
const filter = ref<FilterMode>('all')
|
|
9
|
+
const search = ref('')
|
|
10
|
+
const selected = ref<TransitionEntry | null>(null)
|
|
11
|
+
|
|
12
|
+
const filtered = computed(() => {
|
|
13
|
+
let list = entries.value
|
|
14
|
+
|
|
15
|
+
if (search.value) {
|
|
16
|
+
const q = search.value.toLowerCase()
|
|
17
|
+
list = list.filter((e) => e.transitionName.toLowerCase().includes(q) || e.parentComponent.toLowerCase().includes(q))
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (filter.value === 'cancelled') {
|
|
21
|
+
list = list.filter((e) => e.cancelled || e.phase === 'interrupted')
|
|
22
|
+
} else if (filter.value === 'active') {
|
|
23
|
+
list = list.filter((e) => e.phase === 'entering' || e.phase === 'leaving')
|
|
24
|
+
} else if (filter.value === 'completed') {
|
|
25
|
+
list = list.filter((e) => e.phase === 'entered' || e.phase === 'left')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return list
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const stats = computed(() => ({
|
|
32
|
+
total: entries.value.length,
|
|
33
|
+
active: entries.value.filter((e) => e.phase === 'entering' || e.phase === 'leaving').length,
|
|
34
|
+
cancelled: entries.value.filter((e) => e.cancelled || e.phase === 'interrupted').length,
|
|
35
|
+
avgMs: (() => {
|
|
36
|
+
const completed = entries.value.filter((e) => e.durationMs !== undefined)
|
|
37
|
+
|
|
38
|
+
if (!completed.length) {
|
|
39
|
+
return 0
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return Math.round(completed.reduce((s, e) => s + (e.durationMs ?? 0), 0) / completed.length)
|
|
43
|
+
})(),
|
|
44
|
+
}))
|
|
45
|
+
|
|
46
|
+
// Timeline bar geometry — relative to the earliest startTime
|
|
47
|
+
const timelineGeometry = computed(() => {
|
|
48
|
+
const all = filtered.value
|
|
49
|
+
|
|
50
|
+
if (!all.length) {
|
|
51
|
+
return []
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const minT = Math.min(...all.map((e) => e.startTime))
|
|
55
|
+
const maxT = Math.max(...all.map((e) => e.endTime ?? e.startTime + 400))
|
|
56
|
+
const span = Math.max(maxT - minT, 1)
|
|
57
|
+
|
|
58
|
+
return all.map((e) => ({
|
|
59
|
+
left: ((e.startTime - minT) / span) * 100,
|
|
60
|
+
width: (((e.endTime ?? e.startTime + 80) - e.startTime) / span) * 100,
|
|
61
|
+
}))
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
function phaseColor(phase: TransitionEntry['phase'], direction: TransitionEntry['direction']): string {
|
|
65
|
+
if (phase === 'entering' || phase === 'leaving') {
|
|
66
|
+
return '#7f77dd'
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (phase === 'entered') {
|
|
70
|
+
return '#1d9e75'
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (phase === 'left') {
|
|
74
|
+
return '#378add'
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (phase === 'enter-cancelled' || phase === 'leave-cancelled') {
|
|
78
|
+
return '#e24b4a'
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (phase === 'interrupted') {
|
|
82
|
+
return '#e09a3a'
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return '#888'
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function phaseBadgeClass(phase: TransitionEntry['phase']): string {
|
|
89
|
+
if (phase === 'entering' || phase === 'leaving') {
|
|
90
|
+
return 'badge-purple'
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (phase === 'entered' || phase === 'left') {
|
|
94
|
+
return 'badge-ok'
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (phase.includes('cancelled')) {
|
|
98
|
+
return 'badge-err'
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (phase === 'interrupted') {
|
|
102
|
+
return 'badge-warn'
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return 'badge-gray'
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function directionLabel(e: TransitionEntry): string {
|
|
109
|
+
if (e.appear) {
|
|
110
|
+
return '✦ appear'
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return e.direction === 'enter' ? '→ enter' : '← leave'
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function directionColor(e: TransitionEntry): string {
|
|
117
|
+
if (e.appear) {
|
|
118
|
+
return 'var(--amber)'
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return e.direction === 'enter' ? 'var(--teal)' : 'var(--blue)'
|
|
122
|
+
}
|
|
123
|
+
</script>
|
|
124
|
+
|
|
125
|
+
<template>
|
|
126
|
+
<div class="timeline-root">
|
|
127
|
+
<!-- Stats bar -->
|
|
128
|
+
<div class="stats-row">
|
|
129
|
+
<div class="stat-card">
|
|
130
|
+
<div class="stat-val">{{ stats.total }}</div>
|
|
131
|
+
<div class="stat-label">total</div>
|
|
132
|
+
</div>
|
|
133
|
+
<div class="stat-card">
|
|
134
|
+
<div class="stat-val" style="color: var(--purple)">{{ stats.active }}</div>
|
|
135
|
+
<div class="stat-label">active</div>
|
|
136
|
+
</div>
|
|
137
|
+
<div class="stat-card">
|
|
138
|
+
<div class="stat-val" style="color: var(--red)">{{ stats.cancelled }}</div>
|
|
139
|
+
<div class="stat-label">cancelled</div>
|
|
140
|
+
</div>
|
|
141
|
+
<div class="stat-card">
|
|
142
|
+
<div class="stat-val">
|
|
143
|
+
{{ stats.avgMs }}
|
|
144
|
+
<span class="stat-unit">ms</span>
|
|
145
|
+
</div>
|
|
146
|
+
<div class="stat-label">avg duration</div>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
<!-- Toolbar -->
|
|
151
|
+
<div class="toolbar">
|
|
152
|
+
<input v-model="search" type="search" placeholder="filter by name or component…" class="search-input" />
|
|
153
|
+
<div class="filter-group">
|
|
154
|
+
<button :class="{ active: filter === 'all' }" @click="filter = 'all'">All</button>
|
|
155
|
+
<button :class="{ active: filter === 'active' }" @click="filter = 'active'">Active</button>
|
|
156
|
+
<button :class="{ active: filter === 'completed' }" @click="filter = 'completed'">Completed</button>
|
|
157
|
+
<button :class="{ active: filter === 'cancelled', 'danger-active': filter === 'cancelled' }" @click="filter = 'cancelled'">
|
|
158
|
+
Cancelled
|
|
159
|
+
</button>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
<!-- Main content -->
|
|
164
|
+
<div class="content-area">
|
|
165
|
+
<!-- Timeline table -->
|
|
166
|
+
<div class="table-pane" :class="{ 'has-panel': selected }">
|
|
167
|
+
<table class="data-table">
|
|
168
|
+
<thead>
|
|
169
|
+
<tr>
|
|
170
|
+
<th style="width: 110px">NAME</th>
|
|
171
|
+
<th style="width: 80px">DIR</th>
|
|
172
|
+
<th style="width: 90px">PHASE</th>
|
|
173
|
+
<th style="width: 70px">DURATION</th>
|
|
174
|
+
<th>COMPONENT</th>
|
|
175
|
+
<th>TIMELINE</th>
|
|
176
|
+
</tr>
|
|
177
|
+
</thead>
|
|
178
|
+
<tbody>
|
|
179
|
+
<tr
|
|
180
|
+
v-for="(entry, i) in filtered"
|
|
181
|
+
:key="entry.id"
|
|
182
|
+
:class="{ selected: selected?.id === entry.id }"
|
|
183
|
+
@click="selected = selected?.id === entry.id ? null : entry"
|
|
184
|
+
>
|
|
185
|
+
<td>
|
|
186
|
+
<span class="mono" style="font-size: 11px; font-weight: 500">{{ entry.transitionName }}</span>
|
|
187
|
+
</td>
|
|
188
|
+
<td>
|
|
189
|
+
<span class="mono" style="font-size: 11px" :style="{ color: directionColor(entry) }">
|
|
190
|
+
{{ directionLabel(entry) }}
|
|
191
|
+
</span>
|
|
192
|
+
</td>
|
|
193
|
+
<td>
|
|
194
|
+
<span class="badge" :class="phaseBadgeClass(entry.phase)">{{ entry.phase }}</span>
|
|
195
|
+
</td>
|
|
196
|
+
<td class="mono" style="font-size: 11px; color: var(--text2)">
|
|
197
|
+
{{ entry.durationMs !== undefined ? entry.durationMs + 'ms' : '—' }}
|
|
198
|
+
</td>
|
|
199
|
+
<td class="muted" style="font-size: 11px">{{ entry.parentComponent }}</td>
|
|
200
|
+
<td class="bar-cell">
|
|
201
|
+
<div class="bar-track">
|
|
202
|
+
<div
|
|
203
|
+
class="bar-fill"
|
|
204
|
+
:style="{
|
|
205
|
+
left: timelineGeometry[i]?.left + '%',
|
|
206
|
+
width: Math.max(timelineGeometry[i]?.width ?? 1, 1) + '%',
|
|
207
|
+
background: phaseColor(entry.phase, entry.direction),
|
|
208
|
+
opacity: entry.phase === 'entering' || entry.phase === 'leaving' ? '0.55' : '1',
|
|
209
|
+
}"
|
|
210
|
+
/>
|
|
211
|
+
</div>
|
|
212
|
+
</td>
|
|
213
|
+
</tr>
|
|
214
|
+
|
|
215
|
+
<tr v-if="!filtered.length">
|
|
216
|
+
<td colspan="6" style="text-align: center; color: var(--text3); padding: 24px">
|
|
217
|
+
{{
|
|
218
|
+
connected
|
|
219
|
+
? 'No transitions recorded yet — trigger one on the page.'
|
|
220
|
+
: 'Waiting for connection to the Nuxt app…'
|
|
221
|
+
}}
|
|
222
|
+
</td>
|
|
223
|
+
</tr>
|
|
224
|
+
</tbody>
|
|
225
|
+
</table>
|
|
226
|
+
</div>
|
|
227
|
+
|
|
228
|
+
<!-- Detail panel -->
|
|
229
|
+
<transition name="panel-slide">
|
|
230
|
+
<aside v-if="selected" class="detail-panel">
|
|
231
|
+
<div class="panel-header">
|
|
232
|
+
<span class="panel-title">{{ selected.transitionName }}</span>
|
|
233
|
+
<button class="close-btn" @click="selected = null">✕</button>
|
|
234
|
+
</div>
|
|
235
|
+
|
|
236
|
+
<div class="panel-section">
|
|
237
|
+
<div class="panel-row">
|
|
238
|
+
<span class="panel-key">Direction</span>
|
|
239
|
+
<span class="panel-val" :style="{ color: directionColor(selected) }">{{ directionLabel(selected) }}</span>
|
|
240
|
+
</div>
|
|
241
|
+
<div class="panel-row">
|
|
242
|
+
<span class="panel-key">Phase</span>
|
|
243
|
+
<span class="badge" :class="phaseBadgeClass(selected.phase)">{{ selected.phase }}</span>
|
|
244
|
+
</div>
|
|
245
|
+
<div class="panel-row">
|
|
246
|
+
<span class="panel-key">Component</span>
|
|
247
|
+
<span class="panel-val mono">{{ selected.parentComponent }}</span>
|
|
248
|
+
</div>
|
|
249
|
+
<div v-if="selected.mode" class="panel-row">
|
|
250
|
+
<span class="panel-key">Mode</span>
|
|
251
|
+
<span class="panel-val mono">{{ selected.mode }}</span>
|
|
252
|
+
</div>
|
|
253
|
+
</div>
|
|
254
|
+
|
|
255
|
+
<div class="panel-section">
|
|
256
|
+
<div class="panel-section-title">Timing</div>
|
|
257
|
+
<div class="panel-row">
|
|
258
|
+
<span class="panel-key">Start</span>
|
|
259
|
+
<span class="panel-val mono">{{ selected.startTime.toFixed(2) }}ms</span>
|
|
260
|
+
</div>
|
|
261
|
+
<div class="panel-row">
|
|
262
|
+
<span class="panel-key">End</span>
|
|
263
|
+
<span class="panel-val mono">
|
|
264
|
+
{{ selected.endTime !== undefined ? selected.endTime.toFixed(2) + 'ms' : '—' }}
|
|
265
|
+
</span>
|
|
266
|
+
</div>
|
|
267
|
+
<div class="panel-row">
|
|
268
|
+
<span class="panel-key">Duration</span>
|
|
269
|
+
<span class="panel-val mono" style="font-weight: 500">
|
|
270
|
+
{{ selected.durationMs !== undefined ? selected.durationMs + 'ms' : selected.phase === 'interrupted' ? 'interrupted' : 'in progress' }}
|
|
271
|
+
</span>
|
|
272
|
+
</div>
|
|
273
|
+
</div>
|
|
274
|
+
|
|
275
|
+
<div class="panel-section">
|
|
276
|
+
<div class="panel-section-title">Flags</div>
|
|
277
|
+
<div class="panel-row">
|
|
278
|
+
<span class="panel-key">Appear</span>
|
|
279
|
+
<span class="panel-val" :style="{ color: selected.appear ? 'var(--amber)' : 'var(--text3)' }">
|
|
280
|
+
{{ selected.appear ? 'yes' : 'no' }}
|
|
281
|
+
</span>
|
|
282
|
+
</div>
|
|
283
|
+
<div class="panel-row">
|
|
284
|
+
<span class="panel-key">Cancelled</span>
|
|
285
|
+
<span class="panel-val" :style="{ color: selected.cancelled ? 'var(--red)' : 'var(--text3)' }">
|
|
286
|
+
{{ selected.cancelled ? 'yes' : 'no' }}
|
|
287
|
+
</span>
|
|
288
|
+
</div>
|
|
289
|
+
</div>
|
|
290
|
+
|
|
291
|
+
<div v-if="selected.cancelled" class="cancel-notice">
|
|
292
|
+
This transition was cancelled mid-flight. The element may be stuck in a partial animation state if the interruption
|
|
293
|
+
was not handled with
|
|
294
|
+
<code>onEnterCancelled</code>
|
|
295
|
+
/
|
|
296
|
+
<code>onLeaveCancelled</code>
|
|
297
|
+
.
|
|
298
|
+
</div>
|
|
299
|
+
|
|
300
|
+
<div v-if="selected.phase === 'entering' || selected.phase === 'leaving'" class="active-notice">
|
|
301
|
+
Transition is currently in progress. If it stays in this state longer than expected, the
|
|
302
|
+
<code>done()</code>
|
|
303
|
+
callback may not be getting called (JS-mode transition stall).
|
|
304
|
+
</div>
|
|
305
|
+
</aside>
|
|
306
|
+
</transition>
|
|
307
|
+
</div>
|
|
308
|
+
</div>
|
|
309
|
+
</template>
|
|
310
|
+
|
|
311
|
+
<style scoped>
|
|
312
|
+
.timeline-root {
|
|
313
|
+
display: flex;
|
|
314
|
+
flex-direction: column;
|
|
315
|
+
height: 100%;
|
|
316
|
+
overflow: hidden;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/* ── Stats ───────────────────────────────────────────────────────────────── */
|
|
320
|
+
.stats-row {
|
|
321
|
+
display: flex;
|
|
322
|
+
gap: 10px;
|
|
323
|
+
padding: 12px 14px 0;
|
|
324
|
+
flex-shrink: 0;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.stat-card {
|
|
328
|
+
background: var(--bg2);
|
|
329
|
+
border: 0.5px solid var(--border);
|
|
330
|
+
border-radius: var(--radius);
|
|
331
|
+
padding: 8px 14px;
|
|
332
|
+
min-width: 72px;
|
|
333
|
+
text-align: center;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.stat-val {
|
|
337
|
+
font-size: 20px;
|
|
338
|
+
font-weight: 600;
|
|
339
|
+
font-family: var(--mono);
|
|
340
|
+
line-height: 1.1;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.stat-unit {
|
|
344
|
+
font-size: 12px;
|
|
345
|
+
opacity: 0.6;
|
|
346
|
+
margin-left: 1px;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.stat-label {
|
|
350
|
+
font-size: 10px;
|
|
351
|
+
color: var(--text3);
|
|
352
|
+
margin-top: 2px;
|
|
353
|
+
text-transform: uppercase;
|
|
354
|
+
letter-spacing: 0.4px;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/* ── Toolbar ─────────────────────────────────────────────────────────────── */
|
|
358
|
+
.toolbar {
|
|
359
|
+
display: flex;
|
|
360
|
+
align-items: center;
|
|
361
|
+
gap: 8px;
|
|
362
|
+
padding: 10px 14px;
|
|
363
|
+
flex-shrink: 0;
|
|
364
|
+
border-bottom: 0.5px solid var(--border);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
.search-input {
|
|
368
|
+
flex: 1;
|
|
369
|
+
max-width: 260px;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
.filter-group {
|
|
373
|
+
display: flex;
|
|
374
|
+
gap: 4px;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/* ── Content ─────────────────────────────────────────────────────────────── */
|
|
378
|
+
.content-area {
|
|
379
|
+
display: flex;
|
|
380
|
+
flex: 1;
|
|
381
|
+
overflow: hidden;
|
|
382
|
+
min-height: 0;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.table-pane {
|
|
386
|
+
flex: 1;
|
|
387
|
+
overflow: hidden auto;
|
|
388
|
+
min-width: 0;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/* ── Timeline bar ────────────────────────────────────────────────────────── */
|
|
392
|
+
.bar-cell {
|
|
393
|
+
width: 200px;
|
|
394
|
+
padding: 4px 8px;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.bar-track {
|
|
398
|
+
position: relative;
|
|
399
|
+
height: 8px;
|
|
400
|
+
background: var(--bg2);
|
|
401
|
+
border-radius: 4px;
|
|
402
|
+
overflow: hidden;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.bar-fill {
|
|
406
|
+
position: absolute;
|
|
407
|
+
top: 0;
|
|
408
|
+
height: 100%;
|
|
409
|
+
min-width: 3px;
|
|
410
|
+
border-radius: 4px;
|
|
411
|
+
transition: width 0.15s;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/* ── Detail panel ────────────────────────────────────────────────────────── */
|
|
415
|
+
.detail-panel {
|
|
416
|
+
width: 260px;
|
|
417
|
+
flex-shrink: 0;
|
|
418
|
+
border-left: 0.5px solid var(--border);
|
|
419
|
+
overflow-y: auto;
|
|
420
|
+
background: var(--bg3);
|
|
421
|
+
padding: 0 0 16px;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
.panel-header {
|
|
425
|
+
display: flex;
|
|
426
|
+
align-items: center;
|
|
427
|
+
justify-content: space-between;
|
|
428
|
+
padding: 10px 14px 8px;
|
|
429
|
+
border-bottom: 0.5px solid var(--border);
|
|
430
|
+
position: sticky;
|
|
431
|
+
top: 0;
|
|
432
|
+
background: var(--bg3);
|
|
433
|
+
z-index: 1;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
.panel-title {
|
|
437
|
+
font-family: var(--mono);
|
|
438
|
+
font-size: 13px;
|
|
439
|
+
font-weight: 500;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
.close-btn {
|
|
443
|
+
border: none;
|
|
444
|
+
background: transparent;
|
|
445
|
+
color: var(--text3);
|
|
446
|
+
font-size: 11px;
|
|
447
|
+
padding: 2px 6px;
|
|
448
|
+
cursor: pointer;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
.panel-section {
|
|
452
|
+
padding: 10px 14px 6px;
|
|
453
|
+
border-bottom: 0.5px solid var(--border);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
.panel-section-title {
|
|
457
|
+
font-size: 10px;
|
|
458
|
+
font-weight: 500;
|
|
459
|
+
color: var(--text3);
|
|
460
|
+
text-transform: uppercase;
|
|
461
|
+
letter-spacing: 0.4px;
|
|
462
|
+
margin-bottom: 8px;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
.panel-row {
|
|
466
|
+
display: flex;
|
|
467
|
+
justify-content: space-between;
|
|
468
|
+
align-items: center;
|
|
469
|
+
gap: 8px;
|
|
470
|
+
padding: 3px 0;
|
|
471
|
+
font-size: 12px;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
.panel-key {
|
|
475
|
+
color: var(--text3);
|
|
476
|
+
flex-shrink: 0;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
.panel-val {
|
|
480
|
+
color: var(--text);
|
|
481
|
+
text-align: right;
|
|
482
|
+
word-break: break-all;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
.cancel-notice,
|
|
486
|
+
.active-notice {
|
|
487
|
+
margin: 10px 14px 0;
|
|
488
|
+
font-size: 11px;
|
|
489
|
+
line-height: 1.6;
|
|
490
|
+
padding: 8px 10px;
|
|
491
|
+
border-radius: var(--radius);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
.cancel-notice {
|
|
495
|
+
background: rgb(226 75 74 / 10%);
|
|
496
|
+
color: var(--red);
|
|
497
|
+
border: 0.5px solid rgb(226 75 74 / 30%);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
.active-notice {
|
|
501
|
+
background: rgb(127 119 221 / 10%);
|
|
502
|
+
color: var(--purple);
|
|
503
|
+
border: 0.5px solid rgb(127 119 221 / 30%);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
code {
|
|
507
|
+
font-family: var(--mono);
|
|
508
|
+
font-size: 10px;
|
|
509
|
+
background: rgb(0 0 0 / 15%);
|
|
510
|
+
padding: 1px 4px;
|
|
511
|
+
border-radius: 3px;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/* Panel slide transition */
|
|
515
|
+
.panel-slide-enter-active,
|
|
516
|
+
.panel-slide-leave-active {
|
|
517
|
+
transition:
|
|
518
|
+
transform 0.18s ease,
|
|
519
|
+
opacity 0.18s ease;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
.panel-slide-enter-from,
|
|
523
|
+
.panel-slide-leave-to {
|
|
524
|
+
transform: translateX(12px);
|
|
525
|
+
opacity: 0;
|
|
526
|
+
}
|
|
527
|
+
</style>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"jsx": "preserve",
|
|
8
|
+
"lib": ["ESNext", "DOM"],
|
|
9
|
+
"baseUrl": ".",
|
|
10
|
+
"forceConsistentCasingInFileNames": true,
|
|
11
|
+
"paths": {
|
|
12
|
+
"*": ["../node_modules/*"]
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"include": ["**/*.ts", "**/*.vue"]
|
|
16
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import * as nuxt_schema from 'nuxt/schema';
|
|
2
|
+
|
|
3
|
+
interface ModuleOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Enable the useFetch / useAsyncData dashboard tab
|
|
6
|
+
* @default true
|
|
7
|
+
*/
|
|
8
|
+
fetchDashboard?: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Enable the provide/inject graph tab
|
|
11
|
+
* @default true
|
|
12
|
+
*/
|
|
13
|
+
provideInjectGraph?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Enable the composable tracker tab
|
|
16
|
+
* @default true
|
|
17
|
+
*/
|
|
18
|
+
composableTracker?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Enable the render heatmap tab
|
|
21
|
+
* @default true
|
|
22
|
+
*/
|
|
23
|
+
renderHeatmap?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Enable the transition tracker tab
|
|
26
|
+
* @default true
|
|
27
|
+
*/
|
|
28
|
+
transitionTracker?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Minimum render count / ms threshold to highlight in the heatmap
|
|
31
|
+
* @default 5
|
|
32
|
+
*/
|
|
33
|
+
heatmapThreshold?: number;
|
|
34
|
+
}
|
|
35
|
+
declare const _default: nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
|
|
36
|
+
|
|
37
|
+
export { _default as default };
|
|
38
|
+
export type { ModuleOptions };
|