import { CommonModule } from '@angular/common';
import { AfterViewInit, Component, ElementRef, Input, OnChanges, OnDestroy, ViewChild } from '@angular/core';
import type { ChatEntry } from './models';
type JsonViewTree = object;
type JsonViewApi = {
renderJSON(value: unknown, container: HTMLElement): JsonViewTree;
expand?(tree: JsonViewTree): void;
destroy?(tree: JsonViewTree): void;
};
declare global {
interface Window {
jsonview?: JsonViewApi;
}
}
@Component({
selector: 'app-json-file-viewer',
imports: [CommonModule],
template: `
@if (errorMessage) {
{{ errorMessage }}
}
`,
styleUrl: './json-file-viewer.component.scss',
})
export class JsonFileViewerComponent implements AfterViewInit, OnChanges, OnDestroy {
@Input({ required: true }) entry!: ChatEntry;
@ViewChild('host') private readonly host?: ElementRef;
errorMessage: string | null = null;
private renderedTree: JsonViewTree | null = null;
private renderVersion = 0;
ngAfterViewInit(): void {
void this.renderJsonTree();
}
ngOnChanges(): void {
void this.renderJsonTree();
}
ngOnDestroy(): void {
this.destroyRenderedTree();
}
private async renderJsonTree(): Promise {
const host = this.host?.nativeElement;
if (!host || !this.entry.downloadUrl) {
return;
}
const renderVersion = ++this.renderVersion;
const jsonview = window.jsonview;
this.destroyRenderedTree();
host.replaceChildren();
this.errorMessage = null;
if (!jsonview) {
this.errorMessage = 'JSON viewer unavailable.';
return;
}
try {
const response = await fetch(this.entry.downloadUrl);
const content = await response.text();
const parsed = JSON.parse(content);
if (renderVersion !== this.renderVersion) {
return;
}
const tree = jsonview.renderJSON(parsed, host);
jsonview.expand?.(tree);
this.renderedTree = tree;
} catch {
if (renderVersion !== this.renderVersion) {
return;
}
this.errorMessage = 'Could not render JSON preview.';
}
}
private destroyRenderedTree(): void {
if (!this.renderedTree) {
return;
}
window.jsonview?.destroy?.(this.renderedTree);
this.renderedTree = null;
}
}