qdadm 0.30.0 → 0.32.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/package.json +2 -1
- package/src/components/forms/FormPage.vue +1 -1
- package/src/components/layout/AppLayout.vue +13 -1
- package/src/components/layout/Zone.vue +40 -23
- package/src/composables/index.js +1 -0
- package/src/composables/useAuth.js +44 -4
- package/src/composables/useCurrentEntity.js +44 -0
- package/src/composables/useFormPageBuilder.js +3 -3
- package/src/composables/useNavContext.js +24 -8
- package/src/debug/AuthCollector.js +340 -0
- package/src/debug/Collector.js +235 -0
- package/src/debug/DebugBridge.js +163 -0
- package/src/debug/DebugModule.js +215 -0
- package/src/debug/EntitiesCollector.js +403 -0
- package/src/debug/ErrorCollector.js +66 -0
- package/src/debug/LocalStorageAdapter.js +150 -0
- package/src/debug/SignalCollector.js +87 -0
- package/src/debug/ToastCollector.js +82 -0
- package/src/debug/ZonesCollector.js +300 -0
- package/src/debug/components/DebugBar.vue +1232 -0
- package/src/debug/components/ObjectTree.vue +194 -0
- package/src/debug/components/index.js +8 -0
- package/src/debug/components/panels/AuthPanel.vue +174 -0
- package/src/debug/components/panels/EntitiesPanel.vue +712 -0
- package/src/debug/components/panels/EntriesPanel.vue +188 -0
- package/src/debug/components/panels/ToastsPanel.vue +112 -0
- package/src/debug/components/panels/ZonesPanel.vue +232 -0
- package/src/debug/components/panels/index.js +8 -0
- package/src/debug/index.js +31 -0
- package/src/entity/EntityManager.js +142 -20
- package/src/entity/auth/CompositeAuthAdapter.js +212 -0
- package/src/entity/auth/factory.js +207 -0
- package/src/entity/auth/factory.test.js +257 -0
- package/src/entity/auth/index.js +14 -0
- package/src/entity/storage/MockApiStorage.js +51 -2
- package/src/entity/storage/index.js +9 -2
- package/src/index.js +7 -0
- package/src/kernel/Kernel.js +468 -48
- package/src/kernel/KernelContext.js +385 -0
- package/src/kernel/Module.js +111 -0
- package/src/kernel/ModuleLoader.js +573 -0
- package/src/kernel/SignalBus.js +2 -7
- package/src/kernel/index.js +14 -0
- package/src/toast/ToastBridgeModule.js +70 -0
- package/src/toast/ToastListener.vue +47 -0
- package/src/toast/index.js +15 -0
- package/src/toast/useSignalToast.js +113 -0
|
@@ -0,0 +1,1232 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
/**
|
|
3
|
+
* DebugBar - Floating debug toolbar for qdadm
|
|
4
|
+
*
|
|
5
|
+
* Display modes:
|
|
6
|
+
* - bottom: wide horizontal layout (entries side by side)
|
|
7
|
+
* - right: vertical sidebar (entries stacked)
|
|
8
|
+
* - window: floating draggable/resizable window
|
|
9
|
+
* - fullscreen: covers entire viewport
|
|
10
|
+
* - minimized: small corner button
|
|
11
|
+
*/
|
|
12
|
+
import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
|
|
13
|
+
import Badge from 'primevue/badge'
|
|
14
|
+
import Button from 'primevue/button'
|
|
15
|
+
import { ZonesPanel, AuthPanel, EntitiesPanel, ToastsPanel, EntriesPanel } from './panels'
|
|
16
|
+
|
|
17
|
+
const props = defineProps({
|
|
18
|
+
bridge: { type: Object, required: true },
|
|
19
|
+
zIndex: { type: Number, default: 9999 }
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
// Persistence
|
|
23
|
+
const STORAGE_KEY = 'qdadm-debug-bar'
|
|
24
|
+
|
|
25
|
+
const defaultWindowBounds = { x: 100, y: 100, width: 500, height: 400 }
|
|
26
|
+
const defaultPanelSizes = { bottomHeight: 180, rightWidth: 420 }
|
|
27
|
+
|
|
28
|
+
function loadState() {
|
|
29
|
+
try {
|
|
30
|
+
const saved = localStorage.getItem(STORAGE_KEY)
|
|
31
|
+
if (saved) {
|
|
32
|
+
const state = JSON.parse(saved)
|
|
33
|
+
// Validate windowBounds (min 280x200)
|
|
34
|
+
if (state.windowBounds) {
|
|
35
|
+
const b = state.windowBounds
|
|
36
|
+
if (typeof b.x !== 'number' || typeof b.y !== 'number' ||
|
|
37
|
+
typeof b.width !== 'number' || typeof b.height !== 'number' ||
|
|
38
|
+
b.width < 280 || b.height < 200) {
|
|
39
|
+
state.windowBounds = { ...defaultWindowBounds }
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Validate panelSizes
|
|
43
|
+
if (state.panelSizes) {
|
|
44
|
+
const p = state.panelSizes
|
|
45
|
+
if (typeof p.bottomHeight !== 'number' || p.bottomHeight < 100) {
|
|
46
|
+
p.bottomHeight = defaultPanelSizes.bottomHeight
|
|
47
|
+
}
|
|
48
|
+
if (typeof p.rightWidth !== 'number' || p.rightWidth < 200) {
|
|
49
|
+
p.rightWidth = defaultPanelSizes.rightWidth
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
state.panelSizes = { ...defaultPanelSizes }
|
|
53
|
+
}
|
|
54
|
+
return state
|
|
55
|
+
}
|
|
56
|
+
} catch (e) { /* ignore */ }
|
|
57
|
+
return { minimized: true, expanded: false, mode: 'bottom', fullscreen: false, windowBounds: { ...defaultWindowBounds }, panelSizes: { ...defaultPanelSizes } }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function saveState() {
|
|
61
|
+
try {
|
|
62
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify({
|
|
63
|
+
minimized: minimized.value,
|
|
64
|
+
expanded: expanded.value,
|
|
65
|
+
mode: displayMode.value,
|
|
66
|
+
fullscreen: fullscreen.value,
|
|
67
|
+
windowBounds: windowBounds.value,
|
|
68
|
+
panelSizes: panelSizes.value,
|
|
69
|
+
countAllBadges: countAllBadges.value
|
|
70
|
+
}))
|
|
71
|
+
} catch (e) { /* ignore */ }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const savedState = loadState()
|
|
75
|
+
|
|
76
|
+
// State
|
|
77
|
+
const minimized = ref(savedState.minimized)
|
|
78
|
+
const expanded = ref(savedState.expanded)
|
|
79
|
+
const displayMode = ref(savedState.mode) // 'bottom', 'right', or 'window'
|
|
80
|
+
const fullscreen = ref(savedState.fullscreen || false)
|
|
81
|
+
const activeCollector = ref(0)
|
|
82
|
+
const countAllBadges = ref(savedState.countAllBadges ?? false) // false = show unseen only
|
|
83
|
+
|
|
84
|
+
// Reactive tick from bridge - collectors notify when they have new data
|
|
85
|
+
const bridgeTick = computed(() => props.bridge?.tick?.value ?? 0)
|
|
86
|
+
|
|
87
|
+
// Window mode state
|
|
88
|
+
const windowBounds = ref(savedState.windowBounds || defaultWindowBounds)
|
|
89
|
+
const isDragging = ref(false)
|
|
90
|
+
const isResizing = ref(false)
|
|
91
|
+
const dragOffset = ref({ x: 0, y: 0 })
|
|
92
|
+
const resizeStart = ref({ x: 0, y: 0, width: 0, height: 0 })
|
|
93
|
+
|
|
94
|
+
// Docked panel sizes (resizable)
|
|
95
|
+
const panelSizes = ref(savedState.panelSizes || defaultPanelSizes)
|
|
96
|
+
const isPanelResizing = ref(false)
|
|
97
|
+
const panelResizeStart = ref({ y: 0, x: 0, height: 0, width: 0 })
|
|
98
|
+
|
|
99
|
+
// Responsive tabs state (applies to bottom and window modes with horizontal header)
|
|
100
|
+
const headerRef = ref(null)
|
|
101
|
+
const headerWidth = ref(1000)
|
|
102
|
+
const isHorizontalHeader = computed(() => (displayMode.value === 'bottom' || displayMode.value === 'window' || fullscreen.value) && displayMode.value !== 'right')
|
|
103
|
+
const tabsCompact = computed(() => isHorizontalHeader.value && headerWidth.value < 600)
|
|
104
|
+
const tabsDropdown = computed(() => isHorizontalHeader.value && headerWidth.value < 400)
|
|
105
|
+
const showTabsDropdown = ref(false)
|
|
106
|
+
|
|
107
|
+
// Close dropdown when exiting dropdown mode
|
|
108
|
+
watch(tabsDropdown, (isDropdown) => {
|
|
109
|
+
if (!isDropdown) showTabsDropdown.value = false
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
let resizeObserver = null
|
|
113
|
+
onMounted(() => {
|
|
114
|
+
resizeObserver = new ResizeObserver((entries) => {
|
|
115
|
+
for (const entry of entries) {
|
|
116
|
+
headerWidth.value = entry.contentRect.width
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
// Close dropdown on outside click
|
|
120
|
+
document.addEventListener('click', onDocumentClick)
|
|
121
|
+
})
|
|
122
|
+
onUnmounted(() => {
|
|
123
|
+
resizeObserver?.disconnect()
|
|
124
|
+
document.removeEventListener('click', onDocumentClick)
|
|
125
|
+
})
|
|
126
|
+
watch(headerRef, (el) => {
|
|
127
|
+
resizeObserver?.disconnect()
|
|
128
|
+
if (el) resizeObserver?.observe(el)
|
|
129
|
+
}, { immediate: true })
|
|
130
|
+
|
|
131
|
+
function onDocumentClick(e) {
|
|
132
|
+
if (showTabsDropdown.value && !e.target.closest('.debug-tabs-dropdown')) {
|
|
133
|
+
showTabsDropdown.value = false
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Computed
|
|
138
|
+
const isEnabled = computed(() => props.bridge?.enabled?.value ?? false)
|
|
139
|
+
|
|
140
|
+
const collectors = computed(() => {
|
|
141
|
+
bridgeTick.value // Reactive dependency on bridge tick
|
|
142
|
+
const result = []
|
|
143
|
+
if (props.bridge?.collectors) {
|
|
144
|
+
for (const [name, collector] of props.bridge.collectors) {
|
|
145
|
+
result.push({
|
|
146
|
+
name,
|
|
147
|
+
collector,
|
|
148
|
+
badge: collector.getBadge(countAllBadges.value),
|
|
149
|
+
totalBadge: collector.getTotalCount?.() ?? collector.getBadge(true),
|
|
150
|
+
entries: collector.getEntries(),
|
|
151
|
+
records: collector.records, // Does this collector record events?
|
|
152
|
+
enabled: collector.enabled // Per-collector enabled state
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return result
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
const currentCollector = computed(() => collectors.value[activeCollector.value])
|
|
160
|
+
const totalBadge = computed(() => {
|
|
161
|
+
bridgeTick.value // Reactive dependency
|
|
162
|
+
return props.bridge?.getTotalBadge?.(countAllBadges.value) ?? 0
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
// Separate badges for minimized state (always show unseen)
|
|
166
|
+
const errorBadge = computed(() => {
|
|
167
|
+
bridgeTick.value // Reactive dependency
|
|
168
|
+
const c = collectors.value.find(c => c.name === 'ErrorCollector' || c.name === 'errors')
|
|
169
|
+
return c?.badge || 0
|
|
170
|
+
})
|
|
171
|
+
const signalBadge = computed(() => {
|
|
172
|
+
bridgeTick.value // Reactive dependency
|
|
173
|
+
const c = collectors.value.find(c => c.name === 'SignalCollector' || c.name === 'signals')
|
|
174
|
+
return c?.badge || 0
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
// Mark previous collector as seen when leaving tab
|
|
178
|
+
watch(activeCollector, (newIdx, oldIdx) => {
|
|
179
|
+
if (oldIdx !== undefined && collectors.value[oldIdx]?.collector?.markAsSeen) {
|
|
180
|
+
collectors.value[oldIdx].collector.markAsSeen()
|
|
181
|
+
props.bridge?.notify()
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
// Mark current collector as seen when collapsing panel
|
|
186
|
+
watch(expanded, (isExpanded, wasExpanded) => {
|
|
187
|
+
if (wasExpanded && !isExpanded) {
|
|
188
|
+
const idx = activeCollector.value
|
|
189
|
+
if (collectors.value[idx]?.collector?.markAsSeen) {
|
|
190
|
+
collectors.value[idx].collector.markAsSeen()
|
|
191
|
+
props.bridge?.notify()
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
function toggleCountMode() {
|
|
197
|
+
countAllBadges.value = !countAllBadges.value
|
|
198
|
+
saveState()
|
|
199
|
+
props.bridge?.notify()
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Actions
|
|
203
|
+
function toggle() {
|
|
204
|
+
expanded.value = !expanded.value
|
|
205
|
+
saveState()
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function expand() {
|
|
209
|
+
minimized.value = false
|
|
210
|
+
displayMode.value = 'right'
|
|
211
|
+
expanded.value = true
|
|
212
|
+
saveState()
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function minimize() {
|
|
216
|
+
minimized.value = true
|
|
217
|
+
// Keep expanded state - will be restored when un-minimizing
|
|
218
|
+
fullscreen.value = false
|
|
219
|
+
saveState()
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function toggleMode() {
|
|
223
|
+
// Toggle between bottom and right (not window)
|
|
224
|
+
if (displayMode.value === 'window') {
|
|
225
|
+
displayMode.value = 'bottom'
|
|
226
|
+
} else {
|
|
227
|
+
displayMode.value = displayMode.value === 'bottom' ? 'right' : 'bottom'
|
|
228
|
+
}
|
|
229
|
+
saveState()
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function enterWindowMode() {
|
|
233
|
+
displayMode.value = 'window'
|
|
234
|
+
expanded.value = true
|
|
235
|
+
// Ensure window is visible within viewport with min size
|
|
236
|
+
const vw = window.innerWidth
|
|
237
|
+
const vh = window.innerHeight
|
|
238
|
+
const bounds = windowBounds.value
|
|
239
|
+
const width = Math.max(280, Math.min(bounds.width, vw - 40))
|
|
240
|
+
const height = Math.max(200, Math.min(bounds.height, vh - 40))
|
|
241
|
+
windowBounds.value = {
|
|
242
|
+
x: Math.max(20, Math.min(vw - width - 20, bounds.x)),
|
|
243
|
+
y: Math.max(20, Math.min(vh - height - 20, bounds.y)),
|
|
244
|
+
width,
|
|
245
|
+
height
|
|
246
|
+
}
|
|
247
|
+
saveState()
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function exitWindowMode() {
|
|
251
|
+
displayMode.value = 'bottom'
|
|
252
|
+
saveState()
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function getModeIcon() {
|
|
256
|
+
if (displayMode.value === 'window') return 'pi pi-window-maximize'
|
|
257
|
+
return null // Using custom SVG icons instead
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function getModeTitle() {
|
|
261
|
+
if (displayMode.value === 'window') return 'Docked mode'
|
|
262
|
+
return displayMode.value === 'bottom' ? 'Dock to right side' : 'Dock to bottom'
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Window drag handlers
|
|
266
|
+
function startDrag(e) {
|
|
267
|
+
if (displayMode.value !== 'window') return
|
|
268
|
+
isDragging.value = true
|
|
269
|
+
dragOffset.value = {
|
|
270
|
+
x: e.clientX - windowBounds.value.x,
|
|
271
|
+
y: e.clientY - windowBounds.value.y
|
|
272
|
+
}
|
|
273
|
+
document.addEventListener('mousemove', onDrag)
|
|
274
|
+
document.addEventListener('mouseup', stopDrag)
|
|
275
|
+
e.preventDefault()
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function onDrag(e) {
|
|
279
|
+
if (!isDragging.value) return
|
|
280
|
+
windowBounds.value = {
|
|
281
|
+
...windowBounds.value,
|
|
282
|
+
x: Math.max(0, Math.min(window.innerWidth - 100, e.clientX - dragOffset.value.x)),
|
|
283
|
+
y: Math.max(0, Math.min(window.innerHeight - 50, e.clientY - dragOffset.value.y))
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function stopDrag() {
|
|
288
|
+
if (isDragging.value) {
|
|
289
|
+
isDragging.value = false
|
|
290
|
+
document.removeEventListener('mousemove', onDrag)
|
|
291
|
+
document.removeEventListener('mouseup', stopDrag)
|
|
292
|
+
saveState()
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Window resize handlers
|
|
297
|
+
function startResize(e) {
|
|
298
|
+
if (displayMode.value !== 'window') return
|
|
299
|
+
isResizing.value = true
|
|
300
|
+
resizeStart.value = {
|
|
301
|
+
x: e.clientX,
|
|
302
|
+
y: e.clientY,
|
|
303
|
+
width: windowBounds.value.width,
|
|
304
|
+
height: windowBounds.value.height
|
|
305
|
+
}
|
|
306
|
+
document.addEventListener('mousemove', onResize)
|
|
307
|
+
document.addEventListener('mouseup', stopResize)
|
|
308
|
+
e.preventDefault()
|
|
309
|
+
e.stopPropagation()
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const MIN_WINDOW_WIDTH = 280
|
|
313
|
+
const MIN_WINDOW_HEIGHT = 200
|
|
314
|
+
|
|
315
|
+
function onResize(e) {
|
|
316
|
+
if (!isResizing.value) return
|
|
317
|
+
const deltaX = e.clientX - resizeStart.value.x
|
|
318
|
+
const deltaY = e.clientY - resizeStart.value.y
|
|
319
|
+
windowBounds.value = {
|
|
320
|
+
...windowBounds.value,
|
|
321
|
+
width: Math.max(MIN_WINDOW_WIDTH, resizeStart.value.width + deltaX),
|
|
322
|
+
height: Math.max(MIN_WINDOW_HEIGHT, resizeStart.value.height + deltaY)
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function stopResize() {
|
|
327
|
+
if (isResizing.value) {
|
|
328
|
+
isResizing.value = false
|
|
329
|
+
document.removeEventListener('mousemove', onResize)
|
|
330
|
+
document.removeEventListener('mouseup', stopResize)
|
|
331
|
+
saveState()
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Docked panel resize handlers
|
|
336
|
+
const MIN_PANEL_HEIGHT = 100
|
|
337
|
+
const MAX_PANEL_HEIGHT = 600
|
|
338
|
+
const MIN_PANEL_WIDTH = 200
|
|
339
|
+
const MAX_PANEL_WIDTH = 800
|
|
340
|
+
|
|
341
|
+
function startPanelResize(e, direction) {
|
|
342
|
+
if (displayMode.value === 'window' || fullscreen.value) return
|
|
343
|
+
isPanelResizing.value = true
|
|
344
|
+
panelResizeStart.value = {
|
|
345
|
+
y: e.clientY,
|
|
346
|
+
x: e.clientX,
|
|
347
|
+
height: panelSizes.value.bottomHeight,
|
|
348
|
+
width: panelSizes.value.rightWidth
|
|
349
|
+
}
|
|
350
|
+
const handler = direction === 'vertical' ? onPanelResizeVertical : onPanelResizeHorizontal
|
|
351
|
+
const stop = () => stopPanelResize(handler, stop)
|
|
352
|
+
document.addEventListener('mousemove', handler)
|
|
353
|
+
document.addEventListener('mouseup', stop)
|
|
354
|
+
e.preventDefault()
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function onPanelResizeVertical(e) {
|
|
358
|
+
if (!isPanelResizing.value) return
|
|
359
|
+
const deltaY = panelResizeStart.value.y - e.clientY
|
|
360
|
+
panelSizes.value = {
|
|
361
|
+
...panelSizes.value,
|
|
362
|
+
bottomHeight: Math.max(MIN_PANEL_HEIGHT, Math.min(MAX_PANEL_HEIGHT, panelResizeStart.value.height + deltaY))
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function onPanelResizeHorizontal(e) {
|
|
367
|
+
if (!isPanelResizing.value) return
|
|
368
|
+
const deltaX = panelResizeStart.value.x - e.clientX
|
|
369
|
+
panelSizes.value = {
|
|
370
|
+
...panelSizes.value,
|
|
371
|
+
rightWidth: Math.max(MIN_PANEL_WIDTH, Math.min(MAX_PANEL_WIDTH, panelResizeStart.value.width + deltaX))
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function stopPanelResize(moveHandler, upHandler) {
|
|
376
|
+
if (isPanelResizing.value) {
|
|
377
|
+
isPanelResizing.value = false
|
|
378
|
+
document.removeEventListener('mousemove', moveHandler)
|
|
379
|
+
document.removeEventListener('mouseup', upHandler)
|
|
380
|
+
saveState()
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// State before entering fullscreen (to restore on exit)
|
|
385
|
+
let preFullscreenExpanded = false
|
|
386
|
+
|
|
387
|
+
function toggleFullscreen() {
|
|
388
|
+
if (!fullscreen.value) {
|
|
389
|
+
// Entering fullscreen - save current state
|
|
390
|
+
preFullscreenExpanded = expanded.value
|
|
391
|
+
fullscreen.value = true
|
|
392
|
+
expanded.value = true
|
|
393
|
+
} else {
|
|
394
|
+
// Exiting fullscreen - restore previous state
|
|
395
|
+
fullscreen.value = false
|
|
396
|
+
expanded.value = preFullscreenExpanded
|
|
397
|
+
}
|
|
398
|
+
saveState()
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function toggleEnabled() {
|
|
402
|
+
props.bridge?.toggle()
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function clearAll() {
|
|
406
|
+
props.bridge?.clearAll()
|
|
407
|
+
props.bridge?.notify()
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function clearCollector(collector) {
|
|
411
|
+
collector.clear()
|
|
412
|
+
props.bridge?.notify()
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function toggleCollector(collector) {
|
|
416
|
+
collector.toggle()
|
|
417
|
+
props.bridge?.notify()
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function notifyBridge() {
|
|
421
|
+
props.bridge?.notify()
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function getCollectorIcon(name) {
|
|
425
|
+
const icons = {
|
|
426
|
+
errors: 'pi-exclamation-triangle',
|
|
427
|
+
signals: 'pi-bolt',
|
|
428
|
+
toasts: 'pi-bell',
|
|
429
|
+
zones: 'pi-th-large',
|
|
430
|
+
auth: 'pi-user',
|
|
431
|
+
entities: 'pi-database',
|
|
432
|
+
ErrorCollector: 'pi-exclamation-triangle',
|
|
433
|
+
SignalCollector: 'pi-bolt',
|
|
434
|
+
ToastCollector: 'pi-bell',
|
|
435
|
+
ZonesCollector: 'pi-th-large',
|
|
436
|
+
AuthCollector: 'pi-user',
|
|
437
|
+
EntitiesCollector: 'pi-database'
|
|
438
|
+
}
|
|
439
|
+
return icons[name] || 'pi-database'
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function getCollectorLabel(name) {
|
|
443
|
+
const labels = {
|
|
444
|
+
errors: 'Errors',
|
|
445
|
+
signals: 'Signals',
|
|
446
|
+
toasts: 'Toasts',
|
|
447
|
+
zones: 'Zones',
|
|
448
|
+
auth: 'Auth',
|
|
449
|
+
entities: 'Entities',
|
|
450
|
+
ErrorCollector: 'Errors',
|
|
451
|
+
SignalCollector: 'Signals',
|
|
452
|
+
ToastCollector: 'Toasts',
|
|
453
|
+
ZonesCollector: 'Zones',
|
|
454
|
+
AuthCollector: 'Auth',
|
|
455
|
+
EntitiesCollector: 'Entities'
|
|
456
|
+
}
|
|
457
|
+
return labels[name] || name
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function getCollectorColor(name) {
|
|
461
|
+
const colors = {
|
|
462
|
+
errors: '#ef4444',
|
|
463
|
+
toasts: '#f59e0b',
|
|
464
|
+
signals: '#8b5cf6',
|
|
465
|
+
zones: '#06b6d4',
|
|
466
|
+
auth: '#10b981',
|
|
467
|
+
entities: '#3b82f6'
|
|
468
|
+
}
|
|
469
|
+
return colors[name] || '#6b7280'
|
|
470
|
+
}
|
|
471
|
+
</script>
|
|
472
|
+
|
|
473
|
+
<template>
|
|
474
|
+
<Teleport to="body">
|
|
475
|
+
<!-- Minimized: corner button with separate badges -->
|
|
476
|
+
<div v-if="minimized" class="debug-minimized" :style="{ zIndex }" @click="expand">
|
|
477
|
+
<svg viewBox="0 0 100 100" width="28" height="28">
|
|
478
|
+
<polygon points="50,5 93,27.5 93,72.5 50,95 7,72.5 7,27.5" fill="#1E3A8A"/>
|
|
479
|
+
<text x="48" y="50" text-anchor="middle" dominant-baseline="central" font-family="system-ui" font-size="58" font-weight="800" letter-spacing="-4">
|
|
480
|
+
<tspan fill="#60A5FA">Q</tspan><tspan fill="#93C5FD">D</tspan>
|
|
481
|
+
</text>
|
|
482
|
+
</svg>
|
|
483
|
+
<div v-if="errorBadge > 0 || signalBadge > 0" class="debug-badges">
|
|
484
|
+
<span v-if="errorBadge > 0" class="debug-badge debug-badge-error" title="Errors">
|
|
485
|
+
<i class="pi pi-exclamation-triangle" />{{ errorBadge }}
|
|
486
|
+
</span>
|
|
487
|
+
<span v-if="signalBadge > 0" class="debug-badge debug-badge-signal" title="Signals">
|
|
488
|
+
<i class="pi pi-bolt" />{{ signalBadge }}
|
|
489
|
+
</span>
|
|
490
|
+
</div>
|
|
491
|
+
</div>
|
|
492
|
+
|
|
493
|
+
<!-- Full panel -->
|
|
494
|
+
<div v-else class="debug-panel" :class="[
|
|
495
|
+
fullscreen ? 'debug-fullscreen' : `debug-${displayMode}`,
|
|
496
|
+
{ 'debug-expanded': expanded }
|
|
497
|
+
]" :style="displayMode === 'window' && !fullscreen ? {
|
|
498
|
+
zIndex,
|
|
499
|
+
left: windowBounds.x + 'px',
|
|
500
|
+
top: windowBounds.y + 'px',
|
|
501
|
+
width: windowBounds.width + 'px',
|
|
502
|
+
height: windowBounds.height + 'px'
|
|
503
|
+
} : displayMode === 'bottom' && expanded && !fullscreen ? {
|
|
504
|
+
zIndex,
|
|
505
|
+
height: panelSizes.bottomHeight + 'px'
|
|
506
|
+
} : displayMode === 'right' && expanded && !fullscreen ? {
|
|
507
|
+
zIndex,
|
|
508
|
+
width: panelSizes.rightWidth + 'px'
|
|
509
|
+
} : { zIndex }">
|
|
510
|
+
<!-- Header -->
|
|
511
|
+
<div ref="headerRef" class="debug-header" @mousedown="displayMode === 'window' && !fullscreen ? startDrag($event) : null" :class="{ 'debug-header-draggable': displayMode === 'window' && !fullscreen }">
|
|
512
|
+
<div class="debug-title" :class="{ 'debug-title-draggable': displayMode === 'window' && !fullscreen }">
|
|
513
|
+
<svg viewBox="0 0 100 100" width="18" height="18">
|
|
514
|
+
<polygon points="50,5 93,27.5 93,72.5 50,95 7,72.5 7,27.5" fill="#1E3A8A"/>
|
|
515
|
+
<text x="48" y="50" text-anchor="middle" dominant-baseline="central" font-family="system-ui" font-size="58" font-weight="800" letter-spacing="-4">
|
|
516
|
+
<tspan fill="#60A5FA">Q</tspan><tspan fill="#93C5FD">D</tspan>
|
|
517
|
+
</text>
|
|
518
|
+
</svg>
|
|
519
|
+
<span>Debug</span>
|
|
520
|
+
<span v-if="errorBadge > 0" class="debug-header-badge debug-header-badge-error" title="Errors">
|
|
521
|
+
<i class="pi pi-exclamation-triangle" />{{ errorBadge }}
|
|
522
|
+
</span>
|
|
523
|
+
<span v-if="signalBadge > 0" class="debug-header-badge debug-header-badge-signal" title="Signals">
|
|
524
|
+
<i class="pi pi-bolt" />{{ signalBadge }}
|
|
525
|
+
</span>
|
|
526
|
+
</div>
|
|
527
|
+
|
|
528
|
+
<!-- Collector tabs - dropdown mode when very narrow -->
|
|
529
|
+
<div v-if="expanded && tabsDropdown" class="debug-tabs-dropdown">
|
|
530
|
+
<button class="debug-dropdown-trigger" @click="showTabsDropdown = !showTabsDropdown">
|
|
531
|
+
<i :class="['pi', getCollectorIcon(currentCollector?.name)]" :style="{ color: getCollectorColor(currentCollector?.name) }" />
|
|
532
|
+
<span>{{ getCollectorLabel(currentCollector?.name) }}</span>
|
|
533
|
+
<Badge v-if="currentCollector?.badge > 0" :value="currentCollector.badge" severity="secondary" />
|
|
534
|
+
<i class="pi pi-chevron-down" />
|
|
535
|
+
</button>
|
|
536
|
+
<div v-if="showTabsDropdown" class="debug-dropdown-menu" @click="showTabsDropdown = false">
|
|
537
|
+
<button
|
|
538
|
+
v-for="(c, idx) in collectors"
|
|
539
|
+
:key="c.name"
|
|
540
|
+
class="debug-dropdown-item"
|
|
541
|
+
:class="{ 'debug-dropdown-item-active': activeCollector === idx }"
|
|
542
|
+
@click="activeCollector = idx"
|
|
543
|
+
>
|
|
544
|
+
<i :class="['pi', getCollectorIcon(c.name)]" :style="{ color: getCollectorColor(c.name) }" />
|
|
545
|
+
<span>{{ getCollectorLabel(c.name) }}</span>
|
|
546
|
+
<Badge v-if="c.badge > 0" :value="c.badge" severity="secondary" />
|
|
547
|
+
</button>
|
|
548
|
+
</div>
|
|
549
|
+
</div>
|
|
550
|
+
|
|
551
|
+
<!-- Collector tabs - normal/compact mode -->
|
|
552
|
+
<div v-else-if="expanded" class="debug-tabs" :class="{ 'debug-tabs-compact': tabsCompact }">
|
|
553
|
+
<button
|
|
554
|
+
v-for="(c, idx) in collectors"
|
|
555
|
+
:key="c.name"
|
|
556
|
+
class="debug-tab"
|
|
557
|
+
:class="{ 'debug-tab-active': activeCollector === idx }"
|
|
558
|
+
:title="getCollectorLabel(c.name)"
|
|
559
|
+
@click="activeCollector = idx"
|
|
560
|
+
>
|
|
561
|
+
<i :class="['pi', getCollectorIcon(c.name)]" :style="{ color: getCollectorColor(c.name) }" />
|
|
562
|
+
<span v-if="!tabsCompact" class="debug-tab-label">{{ getCollectorLabel(c.name) }}</span>
|
|
563
|
+
<Badge v-if="c.badge > 0" :value="c.badge" severity="secondary" />
|
|
564
|
+
<!-- Per-collector play/pause for recording collectors (bottom mode only, not compact) -->
|
|
565
|
+
<button
|
|
566
|
+
v-if="c.records && displayMode === 'bottom' && !tabsCompact"
|
|
567
|
+
class="debug-tab-toggle"
|
|
568
|
+
:class="{ 'debug-tab-toggle-paused': !c.enabled }"
|
|
569
|
+
:title="c.enabled ? 'Pause recording' : 'Resume recording'"
|
|
570
|
+
@click.stop="toggleCollector(c.collector)"
|
|
571
|
+
>
|
|
572
|
+
<i :class="['pi', c.enabled ? 'pi-pause' : 'pi-play']" />
|
|
573
|
+
</button>
|
|
574
|
+
<!-- Per-collector clear button for recording collectors (bottom mode only, not compact) -->
|
|
575
|
+
<button
|
|
576
|
+
v-if="c.records && c.badge > 0 && displayMode === 'bottom' && !tabsCompact"
|
|
577
|
+
class="debug-tab-clear"
|
|
578
|
+
title="Clear entries"
|
|
579
|
+
@click.stop="clearCollector(c.collector)"
|
|
580
|
+
>
|
|
581
|
+
<i class="pi pi-trash" />
|
|
582
|
+
</button>
|
|
583
|
+
</button>
|
|
584
|
+
</div>
|
|
585
|
+
|
|
586
|
+
<div class="debug-actions">
|
|
587
|
+
<Button :icon="isEnabled ? 'pi pi-pause' : 'pi pi-play'" :severity="isEnabled ? 'success' : 'secondary'" size="small" text rounded :title="isEnabled ? 'Pause' : 'Resume'" @click="toggleEnabled" />
|
|
588
|
+
<Button icon="pi pi-trash" severity="secondary" size="small" text rounded title="Clear all" @click="clearAll" />
|
|
589
|
+
<!-- Badge count mode toggle -->
|
|
590
|
+
<Button :icon="countAllBadges ? 'pi pi-hashtag' : 'pi pi-eye'" :severity="countAllBadges ? 'info' : 'secondary'" size="small" text rounded :title="countAllBadges ? 'Showing all (click for unseen only)' : 'Showing unseen (click for all)'" @click="toggleCountMode" />
|
|
591
|
+
<!-- Mode toggle (bottom/right) - not shown in window/fullscreen mode -->
|
|
592
|
+
<button
|
|
593
|
+
v-if="displayMode !== 'window' && !fullscreen"
|
|
594
|
+
class="debug-mode-btn"
|
|
595
|
+
:title="getModeTitle()"
|
|
596
|
+
@click="toggleMode"
|
|
597
|
+
>{{ displayMode === 'bottom' ? '|' : '―' }}</button>
|
|
598
|
+
<!-- Enter window mode button - not in fullscreen -->
|
|
599
|
+
<Button v-if="displayMode !== 'window' && !fullscreen" icon="pi pi-external-link" severity="secondary" size="small" text rounded title="Detach window" @click="enterWindowMode" />
|
|
600
|
+
<!-- Exit window mode button (dock back) -->
|
|
601
|
+
<Button v-if="displayMode === 'window'" icon="pi pi-link" severity="secondary" size="small" text rounded title="Dock panel" @click="exitWindowMode" />
|
|
602
|
+
<!-- Fullscreen (not in window mode) -->
|
|
603
|
+
<Button v-if="displayMode !== 'window'" :icon="fullscreen ? 'pi pi-window-minimize' : 'pi pi-window-maximize'" severity="secondary" size="small" text rounded :title="fullscreen ? 'Exit fullscreen' : 'Fullscreen'" @click="toggleFullscreen" />
|
|
604
|
+
<!-- Expand/collapse (not in window/fullscreen mode) -->
|
|
605
|
+
<Button v-if="displayMode !== 'window' && !fullscreen" :icon="displayMode === 'right' ? (expanded ? 'pi pi-chevron-right' : 'pi pi-chevron-left') : (expanded ? 'pi pi-chevron-down' : 'pi pi-chevron-up')" severity="secondary" size="small" text rounded @click="toggle" />
|
|
606
|
+
<!-- Minimize - not in fullscreen -->
|
|
607
|
+
<Button v-if="!fullscreen" icon="pi pi-arrow-down-right" severity="secondary" size="small" text rounded title="Minimize" @click="minimize" />
|
|
608
|
+
</div>
|
|
609
|
+
</div>
|
|
610
|
+
|
|
611
|
+
<!-- Resize handle for window mode (not in fullscreen) -->
|
|
612
|
+
<div v-if="displayMode === 'window' && !fullscreen" class="debug-resize-handle" @mousedown="startResize" />
|
|
613
|
+
|
|
614
|
+
<!-- Resize handle for bottom mode (drag to resize height) -->
|
|
615
|
+
<div
|
|
616
|
+
v-if="displayMode === 'bottom' && expanded && !fullscreen"
|
|
617
|
+
class="debug-panel-resize debug-panel-resize-top"
|
|
618
|
+
@mousedown="startPanelResize($event, 'vertical')"
|
|
619
|
+
/>
|
|
620
|
+
|
|
621
|
+
<!-- Resize handle for right mode (drag to resize width) -->
|
|
622
|
+
<div
|
|
623
|
+
v-if="displayMode === 'right' && expanded && !fullscreen"
|
|
624
|
+
class="debug-panel-resize debug-panel-resize-left"
|
|
625
|
+
@mousedown="startPanelResize($event, 'horizontal')"
|
|
626
|
+
/>
|
|
627
|
+
|
|
628
|
+
<!-- Content -->
|
|
629
|
+
<div v-if="expanded && currentCollector" class="debug-content">
|
|
630
|
+
<!-- Content header with controls (right/window/fullscreen mode, for recording collectors) -->
|
|
631
|
+
<div v-if="(displayMode === 'right' || displayMode === 'window' || fullscreen) && currentCollector.records" class="debug-content-header">
|
|
632
|
+
<span class="debug-content-title">{{ getCollectorLabel(currentCollector.name) }}</span>
|
|
633
|
+
<div class="debug-content-controls">
|
|
634
|
+
<button
|
|
635
|
+
class="debug-tab-toggle"
|
|
636
|
+
:class="{ 'debug-tab-toggle-paused': !currentCollector.enabled }"
|
|
637
|
+
:title="currentCollector.enabled ? 'Pause recording' : 'Resume recording'"
|
|
638
|
+
@click="toggleCollector(currentCollector.collector)"
|
|
639
|
+
>
|
|
640
|
+
<i :class="['pi', currentCollector.enabled ? 'pi-pause' : 'pi-play']" />
|
|
641
|
+
</button>
|
|
642
|
+
<button
|
|
643
|
+
v-if="currentCollector.badge > 0"
|
|
644
|
+
class="debug-tab-clear"
|
|
645
|
+
title="Clear entries"
|
|
646
|
+
@click="clearCollector(currentCollector.collector)"
|
|
647
|
+
>
|
|
648
|
+
<i class="pi pi-trash" />
|
|
649
|
+
</button>
|
|
650
|
+
</div>
|
|
651
|
+
</div>
|
|
652
|
+
|
|
653
|
+
<!-- Zones collector - always render for toolbar access -->
|
|
654
|
+
<ZonesPanel
|
|
655
|
+
v-if="currentCollector.name === 'zones' || currentCollector.name === 'ZonesCollector'"
|
|
656
|
+
:collector="currentCollector.collector"
|
|
657
|
+
:entries="currentCollector.entries"
|
|
658
|
+
@update="notifyBridge"
|
|
659
|
+
/>
|
|
660
|
+
|
|
661
|
+
<div v-else-if="currentCollector.entries.length === 0" class="debug-empty">
|
|
662
|
+
<i class="pi pi-inbox" />
|
|
663
|
+
<span>No entries</span>
|
|
664
|
+
</div>
|
|
665
|
+
|
|
666
|
+
<!-- Auth collector -->
|
|
667
|
+
<AuthPanel
|
|
668
|
+
v-else-if="currentCollector.name === 'auth' || currentCollector.name === 'AuthCollector'"
|
|
669
|
+
:collector="currentCollector.collector"
|
|
670
|
+
:entries="currentCollector.entries"
|
|
671
|
+
/>
|
|
672
|
+
|
|
673
|
+
<!-- Entities collector -->
|
|
674
|
+
<EntitiesPanel
|
|
675
|
+
v-else-if="currentCollector.name === 'entities' || currentCollector.name === 'EntitiesCollector'"
|
|
676
|
+
:collector="currentCollector.collector"
|
|
677
|
+
:entries="currentCollector.entries"
|
|
678
|
+
@update="notifyBridge"
|
|
679
|
+
/>
|
|
680
|
+
|
|
681
|
+
<!-- Toasts collector (vertical modes) -->
|
|
682
|
+
<ToastsPanel
|
|
683
|
+
v-else-if="(currentCollector.name === 'toasts' || currentCollector.name === 'ToastCollector') && (displayMode !== 'bottom' || fullscreen)"
|
|
684
|
+
:entries="currentCollector.entries"
|
|
685
|
+
/>
|
|
686
|
+
|
|
687
|
+
<!-- Default entries - horizontal (bottom mode) -->
|
|
688
|
+
<EntriesPanel
|
|
689
|
+
v-else-if="displayMode === 'bottom' && !fullscreen"
|
|
690
|
+
:entries="currentCollector.entries"
|
|
691
|
+
mode="horizontal"
|
|
692
|
+
/>
|
|
693
|
+
|
|
694
|
+
<!-- Default entries - vertical (right/fullscreen mode) -->
|
|
695
|
+
<EntriesPanel
|
|
696
|
+
v-else
|
|
697
|
+
:entries="currentCollector.entries"
|
|
698
|
+
mode="vertical"
|
|
699
|
+
/>
|
|
700
|
+
</div>
|
|
701
|
+
</div>
|
|
702
|
+
</Teleport>
|
|
703
|
+
</template>
|
|
704
|
+
|
|
705
|
+
<style scoped>
|
|
706
|
+
/* Minimized button */
|
|
707
|
+
.debug-minimized {
|
|
708
|
+
position: fixed;
|
|
709
|
+
bottom: 1rem;
|
|
710
|
+
right: 1rem;
|
|
711
|
+
width: 44px;
|
|
712
|
+
height: 44px;
|
|
713
|
+
border-radius: 8px;
|
|
714
|
+
background: #18181b;
|
|
715
|
+
border: 1px solid #3f3f46;
|
|
716
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.4);
|
|
717
|
+
display: flex;
|
|
718
|
+
align-items: center;
|
|
719
|
+
justify-content: center;
|
|
720
|
+
cursor: pointer;
|
|
721
|
+
transition: transform 0.15s;
|
|
722
|
+
}
|
|
723
|
+
.debug-minimized:hover {
|
|
724
|
+
transform: scale(1.08);
|
|
725
|
+
border-color: #60a5fa;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/* Separate badges */
|
|
729
|
+
.debug-badge {
|
|
730
|
+
position: absolute;
|
|
731
|
+
min-width: 16px;
|
|
732
|
+
height: 16px;
|
|
733
|
+
padding: 0 4px;
|
|
734
|
+
border-radius: 8px;
|
|
735
|
+
font-size: 10px;
|
|
736
|
+
font-weight: 600;
|
|
737
|
+
display: flex;
|
|
738
|
+
align-items: center;
|
|
739
|
+
justify-content: center;
|
|
740
|
+
gap: 2px;
|
|
741
|
+
}
|
|
742
|
+
.debug-badge .pi {
|
|
743
|
+
font-size: 8px;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
/* Header inline badges */
|
|
747
|
+
.debug-header-badge {
|
|
748
|
+
display: inline-flex;
|
|
749
|
+
align-items: center;
|
|
750
|
+
gap: 3px;
|
|
751
|
+
padding: 2px 6px;
|
|
752
|
+
border-radius: 10px;
|
|
753
|
+
font-size: 11px;
|
|
754
|
+
font-weight: 600;
|
|
755
|
+
}
|
|
756
|
+
.debug-header-badge .pi {
|
|
757
|
+
font-size: 10px;
|
|
758
|
+
}
|
|
759
|
+
.debug-header-badge-error {
|
|
760
|
+
background: #ef4444;
|
|
761
|
+
color: white;
|
|
762
|
+
}
|
|
763
|
+
.debug-header-badge-signal {
|
|
764
|
+
background: #8b5cf6;
|
|
765
|
+
color: white;
|
|
766
|
+
}
|
|
767
|
+
.debug-badge-error {
|
|
768
|
+
top: -4px;
|
|
769
|
+
right: -4px;
|
|
770
|
+
background: #ef4444;
|
|
771
|
+
color: white;
|
|
772
|
+
}
|
|
773
|
+
.debug-badge-signal {
|
|
774
|
+
bottom: -4px;
|
|
775
|
+
right: -4px;
|
|
776
|
+
background: #8b5cf6;
|
|
777
|
+
color: white;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/* Panel base */
|
|
781
|
+
.debug-panel {
|
|
782
|
+
position: fixed;
|
|
783
|
+
background: #18181b;
|
|
784
|
+
border: 1px solid #3f3f46;
|
|
785
|
+
font-family: system-ui, sans-serif;
|
|
786
|
+
font-size: 13px;
|
|
787
|
+
color: #f4f4f5;
|
|
788
|
+
display: flex;
|
|
789
|
+
flex-direction: column;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/* Bottom mode */
|
|
793
|
+
.debug-bottom {
|
|
794
|
+
bottom: 0; left: 0; right: 0;
|
|
795
|
+
border-top: 1px solid #3f3f46;
|
|
796
|
+
border-left: none; border-right: none; border-bottom: none;
|
|
797
|
+
}
|
|
798
|
+
.debug-bottom.debug-expanded {
|
|
799
|
+
min-height: 100px;
|
|
800
|
+
max-height: 600px;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/* Right mode - vertical header on left, horizontal slide */
|
|
804
|
+
.debug-right {
|
|
805
|
+
top: 0; right: 0; bottom: 0;
|
|
806
|
+
flex-direction: row;
|
|
807
|
+
border-left: 1px solid #3f3f46;
|
|
808
|
+
border-top: none; border-right: none; border-bottom: none;
|
|
809
|
+
transition: transform 0.2s ease-out;
|
|
810
|
+
}
|
|
811
|
+
.debug-right:not(.debug-expanded) {
|
|
812
|
+
transform: translateX(calc(100% - 44px));
|
|
813
|
+
}
|
|
814
|
+
.debug-right.debug-expanded {
|
|
815
|
+
min-width: 200px;
|
|
816
|
+
max-width: 800px;
|
|
817
|
+
transform: translateX(0);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
/* Window mode - floating draggable/resizable */
|
|
821
|
+
.debug-window {
|
|
822
|
+
/* Window appearance */
|
|
823
|
+
position: fixed;
|
|
824
|
+
border-radius: 8px;
|
|
825
|
+
box-shadow: 0 8px 32px rgba(0,0,0,0.5);
|
|
826
|
+
border: 1px solid #3f3f46;
|
|
827
|
+
min-width: 280px;
|
|
828
|
+
min-height: 200px;
|
|
829
|
+
}
|
|
830
|
+
.debug-window .debug-header {
|
|
831
|
+
cursor: move;
|
|
832
|
+
user-select: none;
|
|
833
|
+
flex-wrap: nowrap;
|
|
834
|
+
}
|
|
835
|
+
.debug-window .debug-tabs {
|
|
836
|
+
flex: 1;
|
|
837
|
+
min-width: 0;
|
|
838
|
+
overflow: hidden;
|
|
839
|
+
}
|
|
840
|
+
.debug-window .debug-actions {
|
|
841
|
+
flex-shrink: 0;
|
|
842
|
+
}
|
|
843
|
+
.debug-window .debug-content {
|
|
844
|
+
flex: 1;
|
|
845
|
+
min-height: 0;
|
|
846
|
+
overflow: auto;
|
|
847
|
+
}
|
|
848
|
+
.debug-resize-handle {
|
|
849
|
+
position: absolute;
|
|
850
|
+
bottom: 0;
|
|
851
|
+
right: 0;
|
|
852
|
+
width: 16px;
|
|
853
|
+
height: 16px;
|
|
854
|
+
cursor: nwse-resize;
|
|
855
|
+
background: linear-gradient(135deg, transparent 50%, #3f3f46 50%);
|
|
856
|
+
border-bottom-right-radius: 8px;
|
|
857
|
+
z-index: 10;
|
|
858
|
+
}
|
|
859
|
+
.debug-resize-handle:hover {
|
|
860
|
+
background: linear-gradient(135deg, transparent 50%, #60a5fa 50%);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/* Panel resize handles for docked modes */
|
|
864
|
+
.debug-panel-resize {
|
|
865
|
+
position: absolute;
|
|
866
|
+
z-index: 10;
|
|
867
|
+
background: transparent;
|
|
868
|
+
}
|
|
869
|
+
.debug-panel-resize-top {
|
|
870
|
+
top: 0;
|
|
871
|
+
left: 0;
|
|
872
|
+
right: 0;
|
|
873
|
+
height: 4px;
|
|
874
|
+
cursor: ns-resize;
|
|
875
|
+
}
|
|
876
|
+
.debug-panel-resize-top:hover {
|
|
877
|
+
background: linear-gradient(180deg, #60a5fa 0%, transparent 100%);
|
|
878
|
+
}
|
|
879
|
+
.debug-panel-resize-left {
|
|
880
|
+
top: 0;
|
|
881
|
+
left: 0;
|
|
882
|
+
bottom: 0;
|
|
883
|
+
width: 4px;
|
|
884
|
+
cursor: ew-resize;
|
|
885
|
+
}
|
|
886
|
+
.debug-panel-resize-left:hover {
|
|
887
|
+
background: linear-gradient(90deg, #60a5fa 0%, transparent 100%);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
.debug-right .debug-header {
|
|
891
|
+
flex-direction: column;
|
|
892
|
+
width: 44px;
|
|
893
|
+
padding: 8px 6px;
|
|
894
|
+
border-bottom: none;
|
|
895
|
+
border-right: 1px solid #3f3f46;
|
|
896
|
+
align-items: center;
|
|
897
|
+
gap: 4px;
|
|
898
|
+
}
|
|
899
|
+
.debug-right .debug-title {
|
|
900
|
+
flex-direction: column;
|
|
901
|
+
gap: 4px;
|
|
902
|
+
}
|
|
903
|
+
.debug-right .debug-title span:not(.debug-header-badge) {
|
|
904
|
+
display: none;
|
|
905
|
+
}
|
|
906
|
+
.debug-right .debug-tabs {
|
|
907
|
+
flex-direction: column;
|
|
908
|
+
margin-left: 0;
|
|
909
|
+
margin-top: 8px;
|
|
910
|
+
gap: 2px;
|
|
911
|
+
}
|
|
912
|
+
.debug-right .debug-tab {
|
|
913
|
+
width: 32px;
|
|
914
|
+
height: 32px;
|
|
915
|
+
padding: 0;
|
|
916
|
+
justify-content: center;
|
|
917
|
+
position: relative;
|
|
918
|
+
}
|
|
919
|
+
.debug-right .debug-tab-label {
|
|
920
|
+
display: none;
|
|
921
|
+
}
|
|
922
|
+
.debug-right .debug-tab .p-badge {
|
|
923
|
+
position: absolute;
|
|
924
|
+
top: -2px;
|
|
925
|
+
right: -2px;
|
|
926
|
+
min-width: 14px;
|
|
927
|
+
height: 14px;
|
|
928
|
+
font-size: 9px;
|
|
929
|
+
padding: 0 3px;
|
|
930
|
+
}
|
|
931
|
+
/* Content header for right mode */
|
|
932
|
+
.debug-content-header {
|
|
933
|
+
display: flex;
|
|
934
|
+
align-items: center;
|
|
935
|
+
justify-content: space-between;
|
|
936
|
+
padding: 6px 12px;
|
|
937
|
+
background: #27272a;
|
|
938
|
+
border-bottom: 1px solid #3f3f46;
|
|
939
|
+
flex-shrink: 0;
|
|
940
|
+
}
|
|
941
|
+
.debug-content-title {
|
|
942
|
+
font-weight: 600;
|
|
943
|
+
font-size: 12px;
|
|
944
|
+
color: #a1a1aa;
|
|
945
|
+
}
|
|
946
|
+
.debug-content-controls {
|
|
947
|
+
display: flex;
|
|
948
|
+
gap: 4px;
|
|
949
|
+
}
|
|
950
|
+
.debug-content-controls .debug-tab-toggle,
|
|
951
|
+
.debug-content-controls .debug-tab-clear {
|
|
952
|
+
margin-left: 0;
|
|
953
|
+
width: 22px;
|
|
954
|
+
height: 22px;
|
|
955
|
+
}
|
|
956
|
+
.debug-right .debug-actions {
|
|
957
|
+
flex-direction: column;
|
|
958
|
+
margin-left: 0;
|
|
959
|
+
margin-top: auto;
|
|
960
|
+
gap: 2px;
|
|
961
|
+
}
|
|
962
|
+
.debug-right .debug-actions .p-button {
|
|
963
|
+
width: 28px;
|
|
964
|
+
height: 28px;
|
|
965
|
+
}
|
|
966
|
+
.debug-right .debug-content {
|
|
967
|
+
flex: 1;
|
|
968
|
+
min-width: 0;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
/* Fullscreen - standalone mode like window but full viewport */
|
|
972
|
+
.debug-fullscreen {
|
|
973
|
+
position: fixed;
|
|
974
|
+
top: 0;
|
|
975
|
+
left: 0;
|
|
976
|
+
right: 0;
|
|
977
|
+
bottom: 0;
|
|
978
|
+
width: auto;
|
|
979
|
+
height: auto;
|
|
980
|
+
border: none;
|
|
981
|
+
border-radius: 0;
|
|
982
|
+
flex-direction: column;
|
|
983
|
+
}
|
|
984
|
+
.debug-fullscreen .debug-header {
|
|
985
|
+
flex-direction: row;
|
|
986
|
+
width: auto;
|
|
987
|
+
height: auto;
|
|
988
|
+
padding: 8px 16px;
|
|
989
|
+
border-bottom: 1px solid #3f3f46;
|
|
990
|
+
border-right: none;
|
|
991
|
+
}
|
|
992
|
+
.debug-fullscreen .debug-title {
|
|
993
|
+
flex-direction: row;
|
|
994
|
+
}
|
|
995
|
+
.debug-fullscreen .debug-title span:not(.debug-header-badge) {
|
|
996
|
+
display: inline;
|
|
997
|
+
}
|
|
998
|
+
.debug-fullscreen .debug-tabs {
|
|
999
|
+
flex-direction: row;
|
|
1000
|
+
margin-left: 12px;
|
|
1001
|
+
margin-top: 0;
|
|
1002
|
+
}
|
|
1003
|
+
.debug-fullscreen .debug-tab {
|
|
1004
|
+
width: auto;
|
|
1005
|
+
height: auto;
|
|
1006
|
+
padding: 4px 10px;
|
|
1007
|
+
}
|
|
1008
|
+
.debug-fullscreen .debug-tab-label {
|
|
1009
|
+
display: inline;
|
|
1010
|
+
}
|
|
1011
|
+
.debug-fullscreen .debug-tab .p-badge {
|
|
1012
|
+
position: static;
|
|
1013
|
+
}
|
|
1014
|
+
.debug-fullscreen .debug-actions {
|
|
1015
|
+
flex-direction: row;
|
|
1016
|
+
margin-left: auto;
|
|
1017
|
+
margin-top: 0;
|
|
1018
|
+
}
|
|
1019
|
+
.debug-fullscreen .debug-actions .p-button {
|
|
1020
|
+
width: auto;
|
|
1021
|
+
height: auto;
|
|
1022
|
+
}
|
|
1023
|
+
.debug-fullscreen .debug-content {
|
|
1024
|
+
flex: 1;
|
|
1025
|
+
min-height: 0;
|
|
1026
|
+
overflow: auto;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
/* Header */
|
|
1030
|
+
.debug-header {
|
|
1031
|
+
display: flex;
|
|
1032
|
+
align-items: center;
|
|
1033
|
+
gap: 8px;
|
|
1034
|
+
padding: 6px 12px;
|
|
1035
|
+
background: #27272a;
|
|
1036
|
+
border-bottom: 1px solid #3f3f46;
|
|
1037
|
+
flex-shrink: 0;
|
|
1038
|
+
}
|
|
1039
|
+
.debug-title {
|
|
1040
|
+
display: flex;
|
|
1041
|
+
align-items: center;
|
|
1042
|
+
gap: 6px;
|
|
1043
|
+
font-weight: 600;
|
|
1044
|
+
}
|
|
1045
|
+
.debug-title-draggable {
|
|
1046
|
+
cursor: grab;
|
|
1047
|
+
}
|
|
1048
|
+
.debug-title-draggable:active {
|
|
1049
|
+
cursor: grabbing;
|
|
1050
|
+
}
|
|
1051
|
+
.debug-tabs {
|
|
1052
|
+
display: flex;
|
|
1053
|
+
gap: 2px;
|
|
1054
|
+
margin-left: 12px;
|
|
1055
|
+
}
|
|
1056
|
+
.debug-tabs-compact {
|
|
1057
|
+
gap: 1px;
|
|
1058
|
+
}
|
|
1059
|
+
.debug-tabs-compact .debug-tab {
|
|
1060
|
+
padding: 4px 6px;
|
|
1061
|
+
}
|
|
1062
|
+
.debug-tab {
|
|
1063
|
+
display: flex;
|
|
1064
|
+
align-items: center;
|
|
1065
|
+
gap: 4px;
|
|
1066
|
+
padding: 4px 10px;
|
|
1067
|
+
background: transparent;
|
|
1068
|
+
border: none;
|
|
1069
|
+
border-radius: 4px;
|
|
1070
|
+
color: #a1a1aa;
|
|
1071
|
+
cursor: pointer;
|
|
1072
|
+
font-size: 12px;
|
|
1073
|
+
}
|
|
1074
|
+
.debug-tab:hover {
|
|
1075
|
+
background: #3f3f46;
|
|
1076
|
+
color: #f4f4f5;
|
|
1077
|
+
}
|
|
1078
|
+
.debug-tab-active {
|
|
1079
|
+
background: #3f3f46;
|
|
1080
|
+
color: #a78bfa;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
/* Tabs dropdown mode */
|
|
1084
|
+
.debug-tabs-dropdown {
|
|
1085
|
+
position: relative;
|
|
1086
|
+
margin-left: 12px;
|
|
1087
|
+
overflow: visible;
|
|
1088
|
+
}
|
|
1089
|
+
.debug-dropdown-trigger {
|
|
1090
|
+
display: flex;
|
|
1091
|
+
align-items: center;
|
|
1092
|
+
gap: 6px;
|
|
1093
|
+
padding: 4px 10px;
|
|
1094
|
+
background: #3f3f46;
|
|
1095
|
+
border: none;
|
|
1096
|
+
border-radius: 4px;
|
|
1097
|
+
color: #f4f4f5;
|
|
1098
|
+
cursor: pointer;
|
|
1099
|
+
font-size: 12px;
|
|
1100
|
+
}
|
|
1101
|
+
.debug-dropdown-trigger:hover {
|
|
1102
|
+
background: #52525b;
|
|
1103
|
+
}
|
|
1104
|
+
.debug-dropdown-trigger .pi-chevron-down {
|
|
1105
|
+
font-size: 10px;
|
|
1106
|
+
color: #71717a;
|
|
1107
|
+
margin-left: 4px;
|
|
1108
|
+
}
|
|
1109
|
+
.debug-dropdown-menu {
|
|
1110
|
+
position: absolute;
|
|
1111
|
+
top: 100%;
|
|
1112
|
+
left: 0;
|
|
1113
|
+
margin-top: 4px;
|
|
1114
|
+
background: #27272a;
|
|
1115
|
+
border: 1px solid #3f3f46;
|
|
1116
|
+
border-radius: 6px;
|
|
1117
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.4);
|
|
1118
|
+
min-width: 160px;
|
|
1119
|
+
z-index: 10001;
|
|
1120
|
+
padding: 4px;
|
|
1121
|
+
}
|
|
1122
|
+
.debug-dropdown-item {
|
|
1123
|
+
display: flex;
|
|
1124
|
+
align-items: center;
|
|
1125
|
+
gap: 8px;
|
|
1126
|
+
width: 100%;
|
|
1127
|
+
padding: 6px 10px;
|
|
1128
|
+
background: transparent;
|
|
1129
|
+
border: none;
|
|
1130
|
+
border-radius: 4px;
|
|
1131
|
+
color: #a1a1aa;
|
|
1132
|
+
cursor: pointer;
|
|
1133
|
+
font-size: 12px;
|
|
1134
|
+
text-align: left;
|
|
1135
|
+
}
|
|
1136
|
+
.debug-dropdown-item:hover {
|
|
1137
|
+
background: #3f3f46;
|
|
1138
|
+
color: #f4f4f5;
|
|
1139
|
+
}
|
|
1140
|
+
.debug-dropdown-item-active {
|
|
1141
|
+
background: #3f3f46;
|
|
1142
|
+
color: #a78bfa;
|
|
1143
|
+
}
|
|
1144
|
+
.debug-actions {
|
|
1145
|
+
display: flex;
|
|
1146
|
+
gap: 2px;
|
|
1147
|
+
margin-left: auto;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
/* Content */
|
|
1151
|
+
.debug-content {
|
|
1152
|
+
flex: 1;
|
|
1153
|
+
overflow: auto;
|
|
1154
|
+
background: #18181b;
|
|
1155
|
+
}
|
|
1156
|
+
.debug-empty {
|
|
1157
|
+
display: flex;
|
|
1158
|
+
flex-direction: column;
|
|
1159
|
+
align-items: center;
|
|
1160
|
+
justify-content: center;
|
|
1161
|
+
height: 100%;
|
|
1162
|
+
color: #71717a;
|
|
1163
|
+
gap: 8px;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
/* Per-collector toggle button */
|
|
1167
|
+
.debug-tab-toggle {
|
|
1168
|
+
display: flex;
|
|
1169
|
+
align-items: center;
|
|
1170
|
+
justify-content: center;
|
|
1171
|
+
width: 18px;
|
|
1172
|
+
height: 18px;
|
|
1173
|
+
margin-left: 4px;
|
|
1174
|
+
padding: 0;
|
|
1175
|
+
background: transparent;
|
|
1176
|
+
border: none;
|
|
1177
|
+
border-radius: 3px;
|
|
1178
|
+
color: #71717a;
|
|
1179
|
+
cursor: pointer;
|
|
1180
|
+
font-size: 9px;
|
|
1181
|
+
}
|
|
1182
|
+
.debug-tab-toggle:hover {
|
|
1183
|
+
background: #52525b;
|
|
1184
|
+
color: #f4f4f5;
|
|
1185
|
+
}
|
|
1186
|
+
.debug-tab-toggle-paused {
|
|
1187
|
+
color: #f59e0b;
|
|
1188
|
+
}
|
|
1189
|
+
.debug-tab-clear {
|
|
1190
|
+
display: flex;
|
|
1191
|
+
align-items: center;
|
|
1192
|
+
justify-content: center;
|
|
1193
|
+
width: 18px;
|
|
1194
|
+
height: 18px;
|
|
1195
|
+
margin-left: 2px;
|
|
1196
|
+
padding: 0;
|
|
1197
|
+
background: transparent;
|
|
1198
|
+
border: none;
|
|
1199
|
+
border-radius: 3px;
|
|
1200
|
+
color: #71717a;
|
|
1201
|
+
cursor: pointer;
|
|
1202
|
+
font-size: 9px;
|
|
1203
|
+
}
|
|
1204
|
+
.debug-tab-clear:hover {
|
|
1205
|
+
background: #ef4444;
|
|
1206
|
+
color: white;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
/* Custom mode toggle button */
|
|
1210
|
+
.debug-mode-btn {
|
|
1211
|
+
display: flex;
|
|
1212
|
+
align-items: center;
|
|
1213
|
+
justify-content: center;
|
|
1214
|
+
width: 2rem;
|
|
1215
|
+
height: 2rem;
|
|
1216
|
+
padding: 0;
|
|
1217
|
+
background: transparent;
|
|
1218
|
+
border: none;
|
|
1219
|
+
border-radius: 50%;
|
|
1220
|
+
color: #a1a1aa;
|
|
1221
|
+
cursor: pointer;
|
|
1222
|
+
font-size: 18px;
|
|
1223
|
+
font-weight: bold;
|
|
1224
|
+
font-family: monospace;
|
|
1225
|
+
line-height: 1;
|
|
1226
|
+
transition: background-color 0.15s, color 0.15s;
|
|
1227
|
+
}
|
|
1228
|
+
.debug-mode-btn:hover {
|
|
1229
|
+
background: rgba(255, 255, 255, 0.1);
|
|
1230
|
+
color: #f4f4f5;
|
|
1231
|
+
}
|
|
1232
|
+
</style>
|