This component works "output-console.component.html (1-89)


@if (dataEntries() && dataEntries().length > 0) {
  <div class="d-flex justify-content-between mb-2">
    <button
      (click)="showRawData.set(!showRawData())"
      mat-flat-button
      color="primary"
    >
      @if (showRawData()) {
        Show Beautified Data
      } @else {
        Show Raw Data
      }
    </button>

    <button (click)="copyToClipboard()" mat-flat-button color="primary">
      Copy to Clipboard
    </button>
  </div>

  <div class="border bg-body-secondary console">
    <samp>
      @switch (detectedLanguage()) {
        @case ("json") {
          <div class="d-flex flex-column gap-4">
            @for (data of parsedJson(); track data) {
              @if (showRawData()) {
                <pre><code>{{ data }}</code></pre>
              } @else {
                <ngx-json-viewer class="bg-white" [json]="data" />
              }
            }
          </div>
        }
        @case ("xml") {
          @for (data of dataEntries(); track data) {
            @if (showRawData()) {
              <pre><code>{{ data }}</code></pre>
            } @else {
              <pre class="xml-pre">
                <code [highlight]="data | prettyXml" language="xml"></code>
              </pre>
            }
          }
        }
        @case ("csv") {
          @if (showRawData()) {
            <pre><code>{{ dataEntries().join('\n') }}</code></pre>
          } @else {
            <table class="table table-striped">
              <thead>
                <tr>
                  @for (header of getCsvHeaders(parsedCsv()[0]); track header) {
                    <th>
                      {{ header }}
                    </th>
                  }
                </tr>
              </thead>
              <tbody>
                @for (row of parsedCsv(); track row) {
                  <tr>
                    @for (
                      header of getCsvHeaders(parsedCsv()[0]);
                      track header
                    ) {
                      <td>
                        @if (isObject(row[header])) {
                          <div [innerHTML]="getNestedTable(row[header])"></div>
                        } @else {
                          {{ row[header] }}
                        }
                      </td>
                    }
                  </tr>
                }
              </tbody>
            </table>
          }
        }
        @default {
          @for (data of dataEntries(); track data) {
            <pre><code>{{ data }}</code></pre>
          }
        }
      }
    </samp>
  </div>
}


output-console.component.ts (1-166)


import { CommonModule } from '@angular/common';
import {
  ChangeDetectionStrategy,
  Component,
  computed,
  input,
  signal,
} from '@angular/core';
import { Highlight } from 'ngx-highlightjs';
import { HighlightLineNumbers } from 'ngx-highlightjs/line-numbers';
import { PrettyJsonPipe } from '../shared/pipes/pretty-json.pipe';
import { PrettyXmlPipe } from '../shared/pipes/pretty-xml.pipe';
import { NgxJsonViewerModule } from 'ngx-json-viewer';
import Papa from 'papaparse';
import { MatButtonModule } from '@angular/material/button';

@Component({
  selector: 'app-output-console',
  standalone: true,
  imports: [
    CommonModule,
    Highlight,
    PrettyJsonPipe,
    PrettyXmlPipe,
    NgxJsonViewerModule,
    MatButtonModule,
    HighlightLineNumbers,
  ],
  templateUrl: './output-console.component.html',
  styleUrls: ['./output-console.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class OutputConsoleComponent {
  public consoleData = input.required<string[]>();

  public showRawData = signal<boolean>(false);

  public dataEntries = computed(() => {
    let entries: string[] = [];
    for (let dataString of this.consoleData()) {
      entries.push(
        ...dataString
          .split('\n')
          .map((line) => line.trim())
          .filter((line) => line.length > 0),
      );
    }
    return entries;
  });

  public detectedLanguage = computed(() => {
    return this.detectLanguage(this.dataEntries()[0]);
  });

  public parsedJson = computed(() => {
    if (this.detectedLanguage() !== 'json') {
      return [];
    }

    return this.dataEntries().map((dataString) => {
      try {
        return JSON.parse(dataString);
      } catch (e) {
        console.error('JSON Parsing Error:', e);
        return null;
      }
    });
  });

  public parsedCsv = computed(() => {
    if (this.detectedLanguage() !== 'csv') {
      return [];
    }

    const processedDataEntries = this.dataEntries().map((line) => {
      const index = line.indexOf(',');
      if (index >= 0) {
        const id = line.substring(0, index);
        const data = line.substring(index + 1);

        const quotedData = '"' + data.replace(/"/g, '""') + '"';
        return `${id},${quotedData}`;
      } else {
        return line;
      }
    });

    const allData = processedDataEntries.join('\n');

    const result = Papa.parse<{ [key: string]: any }>(allData, {
      header: true,
      skipEmptyLines: true,
    });

    return result.data.map((row) => {
      Object.keys(row).forEach((key) => {
        const value = row[key];
        if (typeof value === 'string' && value.trim().startsWith('{')) {
          try {
            row[key] = JSON.parse(value);
          } catch (e) {
            console.warn(`Could not parse JSON in CSV field ${key}:`, e);
          }
        }
      });
      return row;
    });
  });

  public isObject(value: any): boolean {
    return value && typeof value === 'object' && !Array.isArray(value);
  }

  public getCsvHeaders(row: any): string[] {
    return row ? Object.keys(row) : [];
  }

  public detectLanguage(data: string): 'json' | 'xml' | 'csv' | 'plaintext' {
    data = data.trim();
    try {
      JSON.parse(data);
      return 'json';
    } catch {
      // Not JSON
    }

    if (data.startsWith('<')) {
      return 'xml';
    }

    if (data.includes(',') || data.includes('\n')) {
      return 'csv';
    }

    return 'plaintext';
  }

  public getNestedTable(obj: any): string {
    let table = '<table class="table table-bordered nested-table">';
    table += '<thead><tr>';

    for (const key of Object.keys(obj)) {
      table += `<th>${key}</th>`;
    }

    table += '</tr></thead><tbody><tr>';

    for (const key of Object.keys(obj)) {
      const value = obj[key];
      if (this.isObject(value)) {
        table += `<td>${this.getNestedTable(value)}</td>`;
      } else {
        table += `<td>${value}</td>`;
      }
    }

    table += '</tr></tbody></table>';

    return table;
  }

  public copyToClipboard(): void {
    throw new Error('Method not implemented.');
  }
}


" but can you add better types where it's possible? For example parsedJson it's "(property) OutputConsoleComponent.parsedJson: Signal<any[]>" and i don't like any, and parsedCsv it's "(property) OutputConsoleComponent.parsedCsv: Signal<{
    [key: string]: any;
}[]>".