285 lines
7.8 KiB
TypeScript
285 lines
7.8 KiB
TypeScript
import {
|
|
Component,
|
|
HostListener,
|
|
computed,
|
|
effect,
|
|
inject,
|
|
input,
|
|
signal
|
|
} from '@angular/core';
|
|
import { CommonModule } from '@angular/common';
|
|
import { NgIcon, provideIcons } from '@ng-icons/core';
|
|
import { lucideBug } from '@ng-icons/lucide';
|
|
|
|
import { DebuggingService, type DebugLogLevel } from '../../../core/services/debugging.service';
|
|
import { DebugConsoleEntryListComponent } from './debug-console-entry-list/debug-console-entry-list.component';
|
|
import { DebugConsoleNetworkMapComponent } from './debug-console-network-map/debug-console-network-map.component';
|
|
import { DebugConsoleToolbarComponent } from './debug-console-toolbar/debug-console-toolbar.component';
|
|
import { DebugConsoleResizeService } from './services/debug-console-resize.service';
|
|
import { DebugConsoleExportService, type DebugExportFormat } from './services/debug-console-export.service';
|
|
import { DebugConsoleEnvironmentService } from './services/debug-console-environment.service';
|
|
|
|
type DebugLevelState = Record<DebugLogLevel, boolean>;
|
|
|
|
type DebugConsoleTab = 'logs' | 'network';
|
|
type DebugConsoleLauncherVariant = 'floating' | 'inline' | 'compact';
|
|
|
|
@Component({
|
|
selector: 'app-debug-console',
|
|
standalone: true,
|
|
imports: [
|
|
CommonModule,
|
|
NgIcon,
|
|
DebugConsoleEntryListComponent,
|
|
DebugConsoleNetworkMapComponent,
|
|
DebugConsoleToolbarComponent
|
|
],
|
|
viewProviders: [
|
|
provideIcons({
|
|
lucideBug
|
|
})
|
|
],
|
|
templateUrl: './debug-console.component.html',
|
|
host: {
|
|
style: 'display: contents;',
|
|
'data-debug-console-root': 'true'
|
|
}
|
|
})
|
|
export class DebugConsoleComponent {
|
|
readonly debugging = inject(DebuggingService);
|
|
readonly resizeService = inject(DebugConsoleResizeService);
|
|
readonly exportService = inject(DebugConsoleExportService);
|
|
readonly envService = inject(DebugConsoleEnvironmentService);
|
|
readonly entries = this.debugging.entries;
|
|
readonly isOpen = this.debugging.isConsoleOpen;
|
|
readonly networkSnapshot = this.debugging.networkSnapshot;
|
|
readonly launcherVariant = input<DebugConsoleLauncherVariant>('floating');
|
|
readonly showLauncher = input(true);
|
|
readonly showPanel = input(true);
|
|
|
|
readonly activeTab = signal<DebugConsoleTab>('logs');
|
|
readonly detached = signal(false);
|
|
readonly searchTerm = signal('');
|
|
readonly selectedSource = signal('all');
|
|
readonly autoScroll = signal(true);
|
|
readonly showAllEntries = signal(false);
|
|
readonly panelHeight = this.resizeService.panelHeight;
|
|
readonly panelWidth = this.resizeService.panelWidth;
|
|
readonly panelLeft = this.resizeService.panelLeft;
|
|
readonly panelTop = this.resizeService.panelTop;
|
|
readonly levelState = signal<DebugLevelState>({
|
|
event: true,
|
|
info: true,
|
|
warn: true,
|
|
error: true,
|
|
debug: true
|
|
});
|
|
|
|
readonly sourceOptions = computed(() => {
|
|
return Array.from(new Set(this.entries().map((entry) => entry.source))).sort();
|
|
});
|
|
private static readonly VISIBLE_ENTRY_LIMIT = 500;
|
|
|
|
readonly filteredEntries = computed(() => {
|
|
const searchTerm = this.searchTerm().trim()
|
|
.toLowerCase();
|
|
const selectedSource = this.selectedSource();
|
|
const levelState = this.levelState();
|
|
|
|
return this.entries().filter((entry) => {
|
|
if (!levelState[entry.level])
|
|
return false;
|
|
|
|
if (selectedSource !== 'all' && entry.source !== selectedSource)
|
|
return false;
|
|
|
|
if (!searchTerm)
|
|
return true;
|
|
|
|
return [
|
|
entry.message,
|
|
entry.source,
|
|
entry.level,
|
|
entry.timeLabel,
|
|
entry.dateTimeLabel,
|
|
entry.payloadText || ''
|
|
].some((value) => value.toLowerCase().includes(searchTerm));
|
|
});
|
|
});
|
|
readonly visibleEntries = computed(() => {
|
|
const all = this.filteredEntries();
|
|
|
|
if (this.showAllEntries() || all.length <= DebugConsoleComponent.VISIBLE_ENTRY_LIMIT)
|
|
return all;
|
|
|
|
return all.slice(-DebugConsoleComponent.VISIBLE_ENTRY_LIMIT);
|
|
});
|
|
readonly isTruncated = computed(() => {
|
|
return !this.showAllEntries() && this.filteredEntries().length > DebugConsoleComponent.VISIBLE_ENTRY_LIMIT;
|
|
});
|
|
readonly levelCounts = computed<Record<DebugLogLevel, number>>(() => {
|
|
const counts: Record<DebugLogLevel, number> = {
|
|
event: 0,
|
|
info: 0,
|
|
warn: 0,
|
|
error: 0,
|
|
debug: 0
|
|
};
|
|
|
|
for (const entry of this.entries()) {
|
|
counts[entry.level] += entry.count;
|
|
}
|
|
|
|
return counts;
|
|
});
|
|
readonly visibleCount = computed(() => {
|
|
return this.visibleEntries().reduce((sum, entry) => sum + entry.count, 0);
|
|
});
|
|
readonly badgeCount = computed(() => {
|
|
const counts = this.levelCounts();
|
|
|
|
return counts.error > 0 ? counts.error : this.entries().reduce((sum, entry) => sum + entry.count, 0);
|
|
});
|
|
readonly hasErrors = computed(() => this.levelCounts().error > 0);
|
|
readonly networkSummary = computed(() => this.networkSnapshot().summary);
|
|
|
|
constructor() {
|
|
this.resizeService.syncBounds(this.detached());
|
|
|
|
effect(() => {
|
|
const selectedSource = this.selectedSource();
|
|
const sourceOptions = this.sourceOptions();
|
|
|
|
if (selectedSource !== 'all' && !sourceOptions.includes(selectedSource))
|
|
this.selectedSource.set('all');
|
|
});
|
|
}
|
|
|
|
@HostListener('window:mousemove', ['$event'])
|
|
onResizeMove(event: MouseEvent): void {
|
|
this.resizeService.onMouseMove(event, this.detached());
|
|
}
|
|
|
|
@HostListener('window:mouseup')
|
|
onResizeEnd(): void {
|
|
this.resizeService.onMouseUp();
|
|
}
|
|
|
|
@HostListener('window:resize')
|
|
onWindowResize(): void {
|
|
this.resizeService.syncBounds(this.detached());
|
|
}
|
|
|
|
toggleConsole(): void {
|
|
this.debugging.toggleConsole();
|
|
}
|
|
|
|
closeConsole(): void {
|
|
this.debugging.closeConsole();
|
|
}
|
|
|
|
updateSearchTerm(value: string): void {
|
|
this.searchTerm.set(value);
|
|
}
|
|
|
|
updateSelectedSource(source: string): void {
|
|
this.selectedSource.set(source);
|
|
}
|
|
|
|
setActiveTab(tab: DebugConsoleTab): void {
|
|
this.activeTab.set(tab);
|
|
}
|
|
|
|
exportLogs(format: DebugExportFormat): void {
|
|
const env = this.envService.getEnvironment();
|
|
const name = this.envService.getFilenameSafeDisplayName();
|
|
|
|
this.exportService.exportLogs(
|
|
this.filteredEntries(),
|
|
format,
|
|
env,
|
|
name
|
|
);
|
|
}
|
|
|
|
exportNetwork(format: DebugExportFormat): void {
|
|
const env = this.envService.getEnvironment();
|
|
const name = this.envService.getFilenameSafeDisplayName();
|
|
|
|
this.exportService.exportNetwork(
|
|
this.networkSnapshot(),
|
|
format,
|
|
env,
|
|
name
|
|
);
|
|
}
|
|
|
|
toggleDetached(): void {
|
|
const nextDetached = !this.detached();
|
|
|
|
this.detached.set(nextDetached);
|
|
this.resizeService.syncBounds(nextDetached);
|
|
|
|
if (nextDetached)
|
|
this.resizeService.initializeDetachedPosition();
|
|
}
|
|
|
|
toggleLevel(level: DebugLogLevel): void {
|
|
this.levelState.update((current) => ({
|
|
...current,
|
|
[level]: !current[level]
|
|
}));
|
|
}
|
|
|
|
toggleAutoScroll(): void {
|
|
this.autoScroll.update((enabled) => !enabled);
|
|
}
|
|
|
|
disableAutoScroll(): void {
|
|
this.autoScroll.set(false);
|
|
}
|
|
|
|
clearLogs(): void {
|
|
this.debugging.clear();
|
|
this.showAllEntries.set(false);
|
|
}
|
|
|
|
toggleShowAllEntries(): void {
|
|
this.showAllEntries.update((v) => !v);
|
|
}
|
|
|
|
startTopResize(event: MouseEvent): void {
|
|
this.resizeService.startTopResize(event);
|
|
}
|
|
|
|
startBottomResize(event: MouseEvent): void {
|
|
this.resizeService.startBottomResize(event);
|
|
}
|
|
|
|
startLeftResize(event: MouseEvent): void {
|
|
this.resizeService.startLeftResize(event);
|
|
}
|
|
|
|
startRightResize(event: MouseEvent): void {
|
|
this.resizeService.startRightResize(event);
|
|
}
|
|
|
|
startCornerResize(event: MouseEvent): void {
|
|
this.resizeService.startCornerResize(event);
|
|
}
|
|
|
|
startDrag(event: MouseEvent): void {
|
|
if (!this.detached())
|
|
return;
|
|
|
|
this.resizeService.startDrag(event);
|
|
}
|
|
|
|
formatBadgeCount(count: number): string {
|
|
if (count > 99)
|
|
return '99+';
|
|
|
|
return count.toString();
|
|
}
|
|
}
|