Temporary Advertisements:
Ad
Ad
Ad
JSON Easy Viewer
by darkhorse10123 - Sunday May 24, 2026 at 12:39 PM
#1
Hi guy , I would like to share simple but also useful tool that can give end user easy way to view any json file like if you have mnay logs in that format and you want to read and search around it


here the code just save this as html format and then choose your csv or json file to read and view :


<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta name="description" content="Professional JSON Viewer - View, search, and analyze JSON data with ease">
  <title>JSON Viewer Pro</title>
  <style>
    /* CSS Variables for theming */
    :root {
      --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
      --font-mono: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
      --radius: 8px;
      --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
      --shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
      --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
      --transition: all 0.2s ease;
    }

    /* Dark theme (default) */
    [data-theme="dark"] {
      --bg-primary: #0d1117;
      --bg-secondary: #161b22;
      --bg-tertiary: #21262d;
      --bg-hover: #1f242c;
      --border-primary: #30363d;
      --border-secondary: #21262d;
      --text-primary: #e6edf3;
      --text-secondary: #7d8590;
      --text-muted: #5f6b7a;
      --accent-primary: #2f81f7;
      --accent-secondary: #388bfd;
      --accent-hover: #58a6ff;
      --success: #3fb950;
      --warning: #d29922;
      --error: #f85149;
    }

    /* Light theme */
    [data-theme="light"] {
      --bg-primary: #ffffff;
      --bg-secondary: #f6f8fa;
      --bg-tertiary: #eaeef2;
      --bg-hover: #f3f4f6;
      --border-primary: #d0d7de;
      --border-secondary: #eaeef2;
      --text-primary: #1f2328;
      --text-secondary: #57606a;
      --text-muted: #6e7781;
      --accent-primary: #0969da;
      --accent-secondary: #1a7f37;
      --accent-hover: #218bff;
      --success: #1a7f37;
      --warning: #9a6700;
      --error: #cf222e;
    }

    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    body {
      font-family: var(--font-sans);
      background: var(--bg-primary);
      color: var(--text-primary);
      line-height: 1.6;
      min-height: 100vh;
      display: flex;
      flex-direction: column;
    }

    /* Header */
    .header {
      background: var(--bg-secondary);
      border-bottom: 1px solid var(--border-primary);
      padding: 1rem 1.5rem;
      display: flex;
      align-items: center;
      justify-content: space-between;
      position: sticky;
      top: 0;
      z-index: 100;
    }

    .header-left {
      display: flex;
      align-items: center;
      gap: 0.75rem;
    }

    .logo {
      font-size: 1.5rem;
    }

    .brand {
      font-size: 1.25rem;
      font-weight: 600;
      color: var(--text-primary);
    }

    .brand span {
      color: var(--accent-primary);
    }

    .header-right {
      display: flex;
      align-items: center;
      gap: 0.75rem;
    }

    /* Theme toggle */
    .theme-toggle {
      background: var(--bg-tertiary);
      border: 1px solid var(--border-primary);
      border-radius: var(--radius);
      padding: 0.5rem;
      cursor: pointer;
      transition: var(--transition);
      display: flex;
      align-items: center;
      justify-content: center;
      width: 36px;
      height: 36px;
    }

    .theme-toggle:hover {
      background: var(--bg-hover);
      border-color: var(--accent-primary);
    }

    .theme-toggle svg {
      width: 18px;
      height: 18px;
      fill: var(--text-secondary);
    }

    /* Main container */
    .container {
      flex: 1;
      padding: 1.5rem;
      max-width: 1600px;
      margin: 0 auto;
      width: 100%;
      display: flex;
      flex-direction: column;
      gap: 1rem;
    }

    /* Cards */
    .card {
      background: var(--bg-secondary);
      border: 1px solid var(--border-primary);
      border-radius: var(--radius);
      padding: 1.25rem;
    }

    .card-header {
      display: flex;
      align-items: center;
      justify-content: space-between;
      margin-bottom: 1rem;
      flex-wrap: wrap;
      gap: 0.5rem;
    }

    .card-title {
      font-size: 0.875rem;
      font-weight: 600;
      color: var(--text-secondary);
      text-transform: uppercase;
      letter-spacing: 0.025em;
    }

    /* Form elements */
    .form-row {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      gap: 0.75rem;
    }

    .form-group {
      display: flex;
      flex-direction: column;
      gap: 0.25rem;
    }

    .form-label {
      font-size: 0.75rem;
      font-weight: 500;
      color: var(--text-secondary);
      text-transform: uppercase;
    }

    input[type="text"],
    input[type="file"],
    select {
      background: var(--bg-primary);
      border: 1px solid var(--border-primary);
      border-radius: var(--radius);
      color: var(--text-primary);
      padding: 0.5rem 0.75rem;
      font-size: 0.875rem;
      transition: var(--transition);
      min-width: 200px;
    }

    input[type="file"] {
      padding: 0.375rem;
      min-width: 280px;
    }

    input[type="text"]:focus,
    select:focus {
      outline: none;
      border-color: var(--accent-primary);
      box-shadow: 0 0 0 3px rgba(47, 129, 247, 0.15);
    }

    input[type="text"]::placeholder {
      color: var(--text-muted);
    }

    /* Buttons */
    .btn {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      gap: 0.5rem;
      padding: 0.5rem 1rem;
      font-size: 0.875rem;
      font-weight: 500;
      border-radius: var(--radius);
      border: 1px solid transparent;
      cursor: pointer;
      transition: var(--transition);
      white-space: nowrap;
    }

    .btn:disabled {
      opacity: 0.6;
      cursor: not-allowed;
    }

    .btn-primary {
      background: var(--accent-primary);
      color: #fff;
      border-color: var(--accent-primary);
    }

    .btn-primary:hover:not(:disabled) {
      background: var(--accent-hover);
      border-color: var(--accent-hover);
    }

    .btn-secondary {
      background: var(--bg-tertiary);
      color: var(--text-primary);
      border-color: var(--border-primary);
    }

    .btn-secondary:hover:not(:disabled) {
      background: var(--bg-hover);
      border-color: var(--border-primary);
    }

    .btn-danger {
      background: var(--error);
      color: #fff;
      border-color: var(--error);
    }

    .btn-sm {
      padding: 0.375rem 0.75rem;
      font-size: 0.75rem;
    }

    /* Status badge */
    .status {
      display: inline-flex;
      align-items: center;
      gap: 0.375rem;
      padding: 0.25rem 0.75rem;
      border-radius: 9999px;
      font-size: 0.75rem;
      font-weight: 500;
    }

    .status-success {
      background: rgba(63, 185, 80, 0.15);
      color: var(--success);
    }

    .status-error {
      background: rgba(248, 81, 73, 0.15);
      color: var(--error);
    }

    .status-warning {
      background: rgba(210, 153, 34, 0.15);
      color: var(--warning);
    }

    .status-muted {
      background: var(--bg-tertiary);
      color: var(--text-muted);
    }

    /* Stats */
    .stats-grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
      gap: 1rem;
    }

    .stat {
      text-align: center;
    }

    .stat-value {
      font-size: 1.5rem;
      font-weight: 700;
      color: var(--text-primary);
    }

    .stat-label {
      font-size: 0.75rem;
      color: var(--text-muted);
      text-transform: uppercase;
      letter-spacing: 0.05em;
    }

    /* Table */
    .table-container {
      overflow-x: auto;
      border: 1px solid var(--border-primary);
      border-radius: var(--radius);
      max-height: 60vh;
    }

    table {
      width: 100%;
      border-collapse: collapse;
      font-size: 0.875rem;
    }

    th, td {
      padding: 0.75rem;
      text-align: left;
      border-bottom: 1px solid var(--border-primary);
      vertical-align: top;
    }

    th {
      background: var(--bg-tertiary);
      font-weight: 600;
      color: var(--text-secondary);
      position: sticky;
      top: 0;
      z-index: 10;
      text-transform: uppercase;
      font-size: 0.75rem;
      letter-spacing: 0.05em;
    }

    tbody tr {
      transition: var(--transition);
    }

    tbody tr:hover {
      background: var(--bg-hover);
      cursor: pointer;
    }

    tbody tr.selected {
      background: rgba(47, 129, 247, 0.1);
    }

    /* Code/JSON output */
    .code-block {
      background: var(--bg-primary);
      border: 1px solid var(--border-primary);
      border-radius: var(--radius);
      padding: 1rem;
      overflow: auto;
      max-height: 60vh;
      font-family: var(--font-mono);
      font-size: 0.8125rem;
      line-height: 1.6;
    }

    .json-key { color: #7ee787; }
    .json-string { color: #a5d6ff; }
    .json-number { color: #79c0ff; }
    .json-boolean { color: #ff7b72; }
    .json-null { color: #ff7b72; }

    /* Pagination */
    .pagination {
      display: flex;
      align-items: center;
      gap: 0.5rem;
      margin-top: 1rem;
    }

    .pagination-info {
      font-size: 0.875rem;
      color: var(--text-secondary);
      margin-left: auto;
    }

    /* Drop zone */
    .drop-zone {
      border: 2px dashed var(--border-primary);
      border-radius: var(--radius);
      padding: 2rem;
      text-align: center;
      transition: var(--transition);
      cursor: pointer;
    }

    .drop-zone:hover {
      border-color: var(--accent-primary);
      background: var(--bg-hover);
    }

    .drop-zone.drag-over {
      border-color: var(--accent-primary);
      background: rgba(47, 129, 247, 0.1);
    }

    .drop-zone-icon {
      font-size: 2.5rem;
      margin-bottom: 0.75rem;
    }

    .drop-zone-text {
      font-size: 0.9375rem;
      color: var(--text-secondary);
      margin-bottom: 0.5rem;
    }

    .drop-zone-hint {
      font-size: 0.75rem;
      color: var(--text-muted);
    }

    /* Empty state */
    .empty-state {
      text-align: center;
      padding: 3rem 2rem;
      color: var(--text-muted);
    }

    .empty-state-icon {
      font-size: 3rem;
      margin-bottom: 1rem;
      opacity: 0.5;
    }

    .empty-state-title {
      font-size: 1.125rem;
      font-weight: 600;
      color: var(--text-secondary);
      margin-bottom: 0.5rem;
    }

    /* Section */
    .section {
      display: none;
    }

    .section.active {
      display: block;
    }

    /* Mode indicator */
    .mode-badge {
      display: inline-flex;
      align-items: center;
      gap: 0.375rem;
      padding: 0.25rem 0.625rem;
      background: var(--bg-tertiary);
      border: 1px solid var(--border-primary);
      border-radius: var(--radius);
      font-size: 0.75rem;
      font-weight: 500;
      color: var(--text-secondary);
      text-transform: uppercase;
    }

    /* Loading spinner */
    .spinner {
      width: 20px;
      height: 20px;
      border: 2px solid var(--border-primary);
      border-top-color: var(--accent-primary);
      border-radius: 50%;
      animation: spin 0.8s linear infinite;
    }

    @keyframes spin {
      to { transform: rotate(360deg); }
    }

    /* Tooltip */
    .tooltip {
      position: relative;
    }

    .tooltip:hover::after {
      content: attr(data-tooltip);
      position: absolute;
      bottom: 100%;
      left: 50%;
      transform: translateX(-50%);
      padding: 0.375rem 0.625rem;
      background: var(--bg-tertiary);
      border: 1px solid var(--border-primary);
      border-radius: var(--radius);
      font-size: 0.75rem;
      color: var(--text-secondary);
      white-space: nowrap;
      z-index: 1000;
      margin-bottom: 0.5rem;
    }

    /* Responsive */
    @media (max-width: 768px) {
      .container {
        padding: 1rem;
      }

      .header {
        padding: 0.75rem 1rem;
        flex-wrap: wrap;
        gap: 0.5rem;
      }

      .brand {
        font-size: 1.1rem;
      }

      .form-row {
        flex-direction: column;
        align-items: stretch;
      }

      input[type="text"],
      input[type="file"],
      select {
        min-width: unset;
        width: 100%;
      }

      .stats-grid {
        grid-template-columns: repeat(2, 1fr);
      }

      .pagination {
        flex-wrap: wrap;
      }
    }

    /* Print styles */
    @media print {
      .header,
      .drop-zone,
      .btn,
      .pagination {
        display: none !important;
      }

      body {
        background: white;
        color: black;
      }

      .code-block {
        border: 1px solid #ccc;
        background: white;
      }
    }
  </style>
</head>
<body>
  <header class="header">
    <div class="header-left">
      <span class="logo">📊</span>
      <h1 class="brand">JSON <span>Viewer Pro</span></h1>
    </div>
    <div class="header-right">
      <span id="mode-badge" class="mode-badge" style="display: none;">No File</span>
      <button id="theme-toggle" class="theme-toggle tooltip" data-tooltip="Toggle theme" aria-label="Toggle theme">
        <svg id="theme-icon-dark" viewBox="0 0 24 24" style="display: none;"><path d="M12 3a9 9 0 1 0 9 9c0-.46-.04-.92-.1-1.36a5.389 5.389 0 0 1-4.4 2.26 5.403 5.403 0 0 1-3.14-9.8c-.44-.06-.9-.1-1.36-.1z"/></svg>
        <svg id="theme-icon-light" viewBox="0 0 24 24"><path d="M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zM2 13h2c.55 0 1-.45 1-1s-.45-1-1-1H2c-.55 0-1 .45-1 1s.45 1 1 1zm18 0h2c.55 0 1-.45 1-1s-.45-1-1-1h-2c-.55 0-1 .45-1 1s.45 1 1 1zM11 2v2c0 .55.45 1 1 1s1-.45 1-1V2c0-.55-.45-1-1-1s-1 .45-1 1zm0 18v2c0 .55.45 1 1 1s1-.45 1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1zM5.99 4.58a.996.996 0 0 0-1.41 0 .996.996 0 0 0 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0s.39-1.03 0-1.41L5.99 4.58zm12.37 12.37a.996.996 0 0 0-1.41 0 .996.996 0 0 0 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0 .39-.39.39-1.03 0-1.41l-1.06-1.06zm1.06-10.96a.996.996 0 0 0 0-1.41.996.996 0 0 0-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06zM7.05 18.36a.996.996 0 0 0 0-1.41.996.996 0 0 0-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06z"/></svg>
      </button>
    </div>
  </header>

  <main class="container">
    <!-- Upload Section -->
    <section class="card">
      <div class="card-header">
        <span class="card-title">📁 Load Data</span>
        <span id="upload-status" class="status status-muted">Ready</span>
      </div>
      <div id="drop-zone" class="drop-zone">
        <div class="drop-zone-icon">📂</div>
        <div class="drop-zone-text">Drag & drop your JSON or CSV file here</div>
        <div class="drop-zone-hint">or click to browse • Supports .json, .csv</div>
        <input type="file" id="file-input" accept=".json,.csv,application/json,text/csv" style="display: none;">
      </div>
    </section>

    <!-- Stats Section -->
    <section id="stats-section" class="card" style="display: none;">
      <div class="card-header">
        <span class="card-title">📊 Statistics</span>
      </div>
      <div class="stats-grid">
        <div class="stat">
          <div id="stat-total" class="stat-value">-</div>
          <div class="stat-label">Total</div>
        </div>
        <div class="stat">
          <div id="stat-records" class="stat-value">-</div>
          <div class="stat-label">Records</div>
        </div>
        <div class="stat">
          <div id="stat-errors" class="stat-value">-</div>
          <div class="stat-label">Errors</div>
        </div>
        <div class="stat">
          <div id="stat-matching" class="stat-value">-</div>
          <div class="stat-label">Matching</div>
        </div>
      </div>
    </section>

    <!-- Tools Section -->
    <section id="tools-section" class="card" style="display: none;">
      <div class="card-header">
        <span class="card-title">🔍 Tools</span>
      </div>
      <div class="form-row">
        <div class="form-group">
          <label class="form-label">Search</label>
          <input type="text" id="search-input" placeholder="Search in data...">
        </div>
        <div class="form-group">
          <label class="form-label">Field</label>
          <select id="field-select">
            <option value="any">Any field</option>
          </select>
        </div>
        <div class="form-group">
          <label class="form-label">Page Size</label>
          <input type="text" id="page-size" value="50" style="width: 80px;">
        </div>
        <button id="search-btn" class="btn btn-primary">Search</button>
        <button id="clear-btn" class="btn btn-secondary">Clear</button>
        <button id="random-btn" class="btn btn-secondary">Random</button>
      </div>
    </section>

    <!-- Results Section -->
    <section id="results-section" class="card" style="display: none; flex: 1; display: flex; flex-direction: column;">
      <div class="card-header">
        <span class="card-title">📋 Results</span>
        <div class="pagination">
          <button id="prev-btn" class="btn btn-sm btn-secondary" disabled>← Prev</button>
          <span id="page-info" class="pagination-info">Page 1 of 1</span>
          <button id="next-btn" class="btn btn-sm btn-secondary" disabled>Next →</button>
          <button id="export-btn" class="btn btn-sm btn-secondary">Export</button>
          <button id="view-json-btn" class="btn btn-sm btn-secondary">View JSON</button>
        </div>
      </div>
      <div id="table-container" class="table-container" style="flex: 1;">
        <table id="results-table">
          <thead>
            <tr id="table-header">
              <th>Select a file to view data</th>
            </tr>
          </thead>
          <tbody id="table-body">
          </tbody>
        </table>
      </div>
    </section>

    <!-- JSON View Modal -->
    <section id="json-modal" class="card" style="display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 1000; max-width: 90vw; max-height: 90vh; width: 800px; box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);">
      <div class="card-header">
        <span class="card-title">📄 JSON Preview</span>
        <button id="close-modal" class="btn btn-sm btn-secondary">✕ Close</button>
      </div>
      <div class="code-block" id="json-preview" style="max-height: 70vh;"></div>
    </section>

    <!-- Modal overlay -->
    <div id="modal-overlay" style="display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); z-index: 999;"></div>
  </main>

  <script>
    // Application State
    const AppState = {
      currentFile: null,
      data: null,
      dataType: null, // 'json' or 'csv'
      keys: [],
      fields: [],
      filteredKeys: [],
      currentPage: 0,
      pageSize: 50,
      theme: localStorage.getItem('theme') || 'dark',
      selectedRow: null
    };

    // DOM Elements
    const Elements = {
      themeToggle: document.getElementById('theme-toggle'),
      themeIconDark: document.getElementById('theme-icon-dark'),
      themeIconLight: document.getElementById('theme-icon-light'),
      dropZone: document.getElementById('drop-zone'),
      fileInput: document.getElementById('file-input'),
      uploadStatus: document.getElementById('upload-status'),
      modeBadge: document.getElementById('mode-badge'),
      statsSection: document.getElementById('stats-section'),
      toolsSection: document.getElementById('tools-section'),
      resultsSection: document.getElementById('results-section'),
      statTotal: document.getElementById('stat-total'),
      statRecords: document.getElementById('stat-records'),
      statErrors: document.getElementById('stat-errors'),
      statMatching: document.getElementById('stat-matching'),
      searchInput: document.getElementById('search-input'),
      fieldSelect: document.getElementById('field-select'),
      pageSize: document.getElementById('page-size'),
      searchBtn: document.getElementById('search-btn'),
      clearBtn: document.getElementById('clear-btn'),
      randomBtn: document.getElementById('random-btn'),
      prevBtn: document.getElementById('prev-btn'),
      nextBtn: document.getElementById('next-btn'),
      pageInfo: document.getElementById('page-info'),
      exportBtn: document.getElementById('export-btn'),
      viewJsonBtn: document.getElementById('view-json-btn'),
      tableHeader: document.getElementById('table-header'),
      tableBody: document.getElementById('table-body'),
      jsonModal: document.getElementById('json-modal'),
      modalOverlay: document.getElementById('modal-overlay'),
      closeModal: document.getElementById('close-modal'),
      jsonPreview: document.getElementById('json-preview')
    };

    // Utility Functions
    const Utils = {
      escapeHtml(str) {
        const div = document.createElement('div');
        div.textContent = String(str ?? '');
        return div.innerHTML;
      },

      formatNumber(num) {
        return new Intl.NumberFormat().format(num);
      },

      debounce(func, wait) {
        let timeout;
        return function executedFunction(...args) {
          const later = () => {
            clearTimeout(timeout);
            func(...args);
          };
          clearTimeout(timeout);
          timeout = setTimeout(later, wait);
        };
      },

      downloadFile(content, filename, type) {
        const blob = new Blob([content], { type });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = filename;
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        URL.revokeObjectURL(url);
      },

      parseCSVLine(line) {
        const result = [];
        let current = '';
        let inQuotes = false;

        for (let i = 0; i < line.length; i++) {
          const char = line[i];
          const nextChar = line[i + 1];

          if (char === '"') {
            if (inQuotes && nextChar === '"') {
              current += '"';
              i++;
            } else {
              inQuotes = !inQuotes;
            }
          } else if (char === ',' && !inQuotes) {
            result.push(current.trim());
            current = '';
          } else {
            current += char;
          }
        }

        result.push(current.trim());
        return result;
      }
    };

    // JSON Syntax Highlighter
    const SyntaxHighlighter = {
      highlight(json) {
        if (!json) return '';
        const str = typeof json === 'string' ? json : JSON.stringify(json, null, 2);

        return str
          .replace(/&/g, '&')
          .replace(/</g, '<')
          .replace(/>/g, '>')
          .replace(/(".*?")/g, '<span class="json-string">$1</span>')
          .replace(/\b(true|false)\b/g, '<span class="json-boolean">$1</span>')
          .replace(/\b(null)\b/g, '<span class="json-null">$1</span>')
          .replace(/\b(\d+\.?\d*)\b/g, '<span class="json-number">$1</span>')
          .replace(/([{}[\]])/g, '<span style="color: #ff7b72;">$1</span>')
          .replace(/(".*?")\s*:/g, '<span class="json-key">$1</span>:');
      }
    };

    // Theme Manager
    const ThemeManager = {
      init() {
        document.documentElement.setAttribute('data-theme', AppState.theme);
        this.updateIcon();

        Elements.themeToggle.addEventListener('click', () => this.toggle());
      },

      toggle() {
        AppState.theme = AppState.theme === 'dark' ? 'light' : 'dark';
        document.documentElement.setAttribute('data-theme', AppState.theme);
        localStorage.setItem('theme', AppState.theme);
        this.updateIcon();
      },

      updateIcon() {
        if (AppState.theme === 'dark') {
          Elements.themeIconDark.style.display = 'block';
          Elements.themeIconLight.style.display = 'none';
        } else {
          Elements.themeIconDark.style.display = 'none';
          Elements.themeIconLight.style.display = 'block';
        }
      }
    };

    // File Manager
    const FileManager = {
      init() {
        // Drag and drop
        Elements.dropZone.addEventListener('click', () => Elements.fileInput.click());
        Elements.dropZone.addEventListener('dragover', (e) => {
          e.preventDefault();
          Elements.dropZone.classList.add('drag-over');
        });
        Elements.dropZone.addEventListener('dragleave', () => {
          Elements.dropZone.classList.remove('drag-over');
        });
        Elements.dropZone.addEventListener('drop', (e) => {
          e.preventDefault();
          Elements.dropZone.classList.remove('drag-over');
          const files = e.dataTransfer.files;
          if (files.length > 0) this.handleFile(files[0]);
        });

        // File input
        Elements.fileInput.addEventListener('change', (e) => {
          if (e.target.files.length > 0) this.handleFile(e.target.files[0]);
        });
      },

      async handleFile(file) {
        AppState.currentFile = file;
        Elements.uploadStatus.textContent = 'Loading...';
        Elements.uploadStatus.className = 'status status-warning';

        const fileName = file.name.toLowerCase();
        const isJson = fileName.endsWith('.json') || file.type === 'application/json';
        const isCsv = fileName.endsWith('.csv') || file.type === 'text/csv';

        try {
          if (isJson) {
            await this.loadJSON(file);
          } else if (isCsv) {
            await this.loadCSV(file);
          } else {
            throw new Error('Unsupported file type. Please upload a JSON or CSV file.');
          }

          Elements.uploadStatus.textContent = 'Loaded';
          Elements.uploadStatus.className = 'status status-success';
        } catch (error) {
          Elements.uploadStatus.textContent = 'Error';
          Elements.uploadStatus.className = 'status status-error';
          alert(`Error loading file: ${error.message}`);
          console.error(error);
        }
      },

      async loadJSON(file) {
        const text = await file.text();
        AppState.data = JSON.parse(text);
        AppState.dataType = 'json';

        // Extract keys for object data
        if (AppState.data && typeof AppState.data === 'object') {
          AppState.keys = Object.keys(AppState.data);

          // Extract fields from first few records
          const sampleKeys = AppState.keys.slice(0, 100);
          const fieldSet = new Set();
          sampleKeys.forEach(key => {
            const item = AppState.data[key];
            if (item && typeof item === 'object') {
              Object.keys(item).forEach(f => fieldSet.add(f));
            }
          });
          AppState.fields = Array.from(fieldSet).sort();
        }

        AppState.filteredKeys = [...AppState.keys];
        AppState.currentPage = 0;

        this.updateUI('json');
      },

      async loadCSV(file) {
        const text = await file.text();
        const lines = text.split(/\r?\n/).filter(l => l.trim());

        if (lines.length < 2) {
          throw new Error('CSV file appears to be empty or invalid');
        }

        const headers = Utils.parseCSVLine(lines[0]);
        const data = [];
        let errors = 0;

        for (let i = 1; i < lines.length; i++) {
          try {
            const values = Utils.parseCSVLine(lines[i]);
            const row = {};
            headers.forEach((h, idx) => {
              row[h] = values[idx] || '';
            });
            data.push(row);
          } catch (e) {
            errors++;
          }
        }

        AppState.data = data;
        AppState.dataType = 'csv';
        AppState.keys = data.map((_, i) => String(i));
        AppState.fields = headers;
        AppState.filteredKeys = [...AppState.keys];
        AppState.currentPage = 0;

        this.updateUI('csv', { errors });
      },

      updateUI(type, options = {}) {
        // Update mode badge
        Elements.modeBadge.textContent = type.toUpperCase();
        Elements.modeBadge.style.display = 'flex';

        // Show sections
        Elements.statsSection.style.display = 'block';
        Elements.toolsSection.style.display = 'block';
        Elements.resultsSection.style.display = 'flex';

        // Update stats
        Elements.statTotal.textContent = Utils.formatNumber(AppState.keys.length);
        Elements.statRecords.textContent = Utils.formatNumber(AppState.filteredKeys.length);
        Elements.statErrors.textContent = Utils.formatNumber(options.errors || 0);
        Elements.statMatching.textContent = Utils.formatNumber(AppState.filteredKeys.length);

        // Update field select
        Elements.fieldSelect.innerHTML = '<option value="any">Any field</option>';
        AppState.fields.forEach(field => {
          const option = document.createElement('option');
          option.value = field;
          option.textContent = field;
          Elements.fieldSelect.appendChild(option);
        });

        // Render table
        DataViewer.renderTable();
      }
    };

    // Data Viewer
    const DataViewer = {
      init() {
        // Search
        Elements.searchBtn.addEventListener('click', () => this.search());
        Elements.searchInput.addEventListener('keydown', (e) => {
          if (e.key === 'Enter') this.search();
        });
        Elements.searchInput.addEventListener('input', Utils.debounce(() => this.search(), 300));

        // Clear
        Elements.clearBtn.addEventListener('click', () => this.clearSearch());

        // Random
        Elements.randomBtn.addEventListener('click', () => this.showRandom());

        // Pagination
        Elements.prevBtn.addEventListener('click', () => this.prevPage());
        Elements.nextBtn.addEventListener('click', () => this.nextPage());
        Elements.pageSize.addEventListener('change', () => {
          AppState.pageSize = parseInt(Elements.pageSize.value) || 50;
          AppState.currentPage = 0;
          this.renderTable();
        });

        // Export
        Elements.exportBtn.addEventListener('click', () => this.exportData());

        // JSON View
        Elements.viewJsonBtn.addEventListener('click', () => this.showJSONModal());
        Elements.closeModal.addEventListener('click', () => this.hideJSONModal());
        Elements.modalOverlay.addEventListener('click', () => this.hideJSONModal());

        // Keyboard shortcuts
        document.addEventListener('keydown', (e) => {
          if (e.key === 'Escape') this.hideJSONModal();
          if (e.ctrlKey || e.metaKey) {
            if (e.key === 'f') {
              e.preventDefault();
              Elements.searchInput.focus();
            }
            if (e.key === 'e') {
              e.preventDefault();
              this.exportData();
            }
          }
        });
      },

      search() {
        const query = Elements.searchInput.value.trim().toLowerCase();
        const field = Elements.fieldSelect.value;

        if (!query) {
          AppState.filteredKeys = [...AppState.keys];
        } else {
          AppState.filteredKeys = AppState.keys.filter(key => {
            let data;
            if (AppState.dataType === 'json') {
              data = AppState.data[key];
            } else {
              data = AppState.data[parseInt(key)];
            }

            if (!data) return false;

            if (field === 'any') {
              const values = Object.values(data).map(v => String(v).toLowerCase());
              return values.some(v => v.includes(query));
            } else {
              const value = String(data[field] ?? '').toLowerCase();
              return value.includes(query);
            }
          });
        }

        AppState.currentPage = 0;
        Elements.statMatching.textContent = Utils.formatNumber(AppState.filteredKeys.length);
        this.renderTable();
      },

      clearSearch() {
        Elements.searchInput.value = '';
        Elements.fieldSelect.value = 'any';
        this.search();
      },

      showRandom() {
        if (AppState.filteredKeys.length === 0) return;
        const randomKey = AppState.filteredKeys[Math.floor(Math.random() * AppState.filteredKeys.length)];
        let data;
        if (AppState.dataType === 'json') {
          data = AppState.data[randomKey];
        } else {
          data = AppState.data[parseInt(randomKey)];
        }

        Elements.jsonPreview.innerHTML = SyntaxHighlighter.highlight(data);
        this.showJSONModal();
      },

      prevPage() {
        if (AppState.currentPage > 0) {
          AppState.currentPage--;
          this.renderTable();
        }
      },

      nextPage() {
        const maxPage = Math.ceil(AppState.filteredKeys.length / AppState.pageSize) - 1;
        if (AppState.currentPage < maxPage) {
          AppState.currentPage++;
          this.renderTable();
        }
      },

      renderTable() {
        const start = AppState.currentPage * AppState.pageSize;
        const end = start + AppState.pageSize;
        const pageKeys = AppState.filteredKeys.slice(start, end);

        // Update pagination
        const totalPages = Math.ceil(AppState.filteredKeys.length / AppState.pageSize);
        Elements.pageInfo.textContent = `Page ${AppState.currentPage + 1} of ${totalPages} (${Utils.formatNumber(AppState.filteredKeys.length)} items)`;
        Elements.prevBtn.disabled = AppState.currentPage === 0;
        Elements.nextBtn.disabled = AppState.currentPage >= totalPages - 1;

        // Determine columns
        let columns = [];
        if (AppState.fields.length > 0) {
          columns = AppState.fields.slice(0, 8);
        } else if (pageKeys.length > 0) {
          const sample = AppState.dataType === 'json' ? AppState.data[pageKeys[0]] : AppState.data[parseInt(pageKeys[0])];
          if (sample && typeof sample === 'object') {
            columns = Object.keys(sample).slice(0, 8);
          }
        }

        // Render header
        Elements.tableHeader.innerHTML = columns.map(col => `<th>${Utils.escapeHtml(col)}</th>`).join('');

        // Render body
        Elements.tableBody.innerHTML = pageKeys.map(key => {
          let rowData;
          if (AppState.dataType === 'json') {
            rowData = AppState.data[key];
          } else {
            rowData = AppState.data[parseInt(key)];
          }

          return `<tr data-key="${Utils.escapeHtml(key)}" class="${key === AppState.selectedRow ? 'selected' : ''}">
            ${columns.map(col => `<td>${Utils.escapeHtml(rowData?.[col] ?? '')}</td>`).join('')}
          </tr>`;
        }).join('');

        // Add click handlers
        Elements.tableBody.querySelectorAll('tr').forEach(row => {
          row.addEventListener('click', () => {
            const key = row.getAttribute('data-key');
            AppState.selectedRow = key;
            this.showRowDetails(key);

            // Update selection visual
            Elements.tableBody.querySelectorAll('tr').forEach(r => r.classList.remove('selected'));
            row.classList.add('selected');
          });
        });
      },

      showRowDetails(key) {
        let data;
        if (AppState.dataType === 'json') {
          data = AppState.data[key];
        } else {
          data = AppState.data[parseInt(key)];
        }

        Elements.jsonPreview.innerHTML = SyntaxHighlighter.highlight(data);
        this.showJSONModal();
      },

      showJSONModal() {
        Elements.jsonModal.style.display = 'block';
        Elements.modalOverlay.style.display = 'block';
      },

      hideJSONModal() {
        Elements.jsonModal.style.display = 'none';
        Elements.modalOverlay.style.display = 'none';
      },

      exportData() {
        if (!AppState.data) return;

        const exportData = AppState.filteredKeys.map(key => {
          if (AppState.dataType === 'json') {
            return { id: key, ...AppState.data[key] };
          } else {
            return AppState.data[parseInt(key)];
          }
        });

        const json = JSON.stringify(exportData, null, 2);
        const filename = `export_${new Date().toISOString().slice(0, 10)}.json`;
        Utils.downloadFile(json, filename, 'application/json');
      }
    };

    // Initialize
    document.addEventListener('DOMContentLoaded', () => {
      ThemeManager.init();
      FileManager.init();
      DataViewer.init();

      // Load sample data on first visit hint
      console.log('JSON Viewer Pro loaded. Drag a file or use the sample-data.json file to test.');
    });
  </script>
</body>
</html>
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Flash Usdt Crypto Trc20 Erc20 Bep20 BTC ETH Sender App Software Tradeable And Demo daivesake 0 14 17 minutes ago
Last Post: daivesake
  Make money and spread malware with cheats. SPARK 487 29,480 4 hours ago
Last Post: Jrx
  Forums and Telegrams channels Databases leak sites j4c1nt0 3,372 256,518 5 hours ago
Last Post: northek9f2w
  Discord Remote Access Trojan (RAT) Piplup 178 15,125 7 hours ago
Last Post: shouijin
  INSTAGRAM BAN METHOD (REAL) (NO PAYMENT/ TOOL ETC) corplexor 0 104 8 hours ago
Last Post: corplexor

Forum Jump:


 Users browsing this forum: