anycoder-6ed4b0e2 / index.html
Stable-X's picture
🎨 Redesign from AnyCoder (#1)
c2b49d2 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Anycoder - Dataset Explorer</title>
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<!-- Phosphor Icons -->
<script src="https://unpkg.com/@phosphor-icons/web"></script>
<style>
/* --- CSS Variables & Reset --- */
:root {
/* Modern Palette */
--bg-body: #0f1115;
--bg-sidebar: #161b22;
--bg-card: #1c2128;
--bg-hover: #2a303c;
--text-main: #f0f6fc;
--text-muted: #8b949e;
--accent-primary: #58a6ff; /* GitHub Blue */
--accent-secondary: #3fb950; /* Success Green */
--accent-warn: #d29922;
--accent-danger: #f85149;
--border-subtle: #30363d;
--border-focus: #58a6ff;
--radius-md: 8px;
--radius-lg: 12px;
--shadow-card: 0 4px 12px rgba(0, 0, 0, 0.4);
--header-height: 64px;
--sidebar-width: 280px;
}
* { box-sizing: border-box; margin: 0; padding: 0; outline: none; }
body {
font-family: 'Inter', sans-serif;
background-color: var(--bg-body);
color: var(--text-main);
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
-webkit-font-smoothing: antialiased;
}
/* --- Scrollbar --- */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: var(--bg-body); }
::-webkit-scrollbar-thumb { background: var(--border-subtle); border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
/* --- Header --- */
.header {
height: var(--header-height);
background: var(--bg-sidebar);
border-bottom: 1px solid var(--border-subtle);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
flex-shrink: 0;
z-index: 50;
}
.header-left { display: flex; align-items: center; gap: 16px; }
.btn-icon {
background: transparent;
border: none;
color: var(--text-muted);
font-size: 1.25rem;
cursor: pointer;
padding: 6px;
border-radius: var(--radius-md);
transition: all 0.2s;
}
.btn-icon:hover { background: var(--bg-hover); color: var(--text-main); }
.logo {
display: flex;
align-items: center;
gap: 10px;
font-weight: 700;
font-size: 1.1rem;
color: var(--text-main);
}
.logo i { color: var(--accent-primary); font-size: 1.5rem; }
.header-actions { display: flex; align-items: center; gap: 12px; }
.btn {
padding: 8px 16px;
border-radius: var(--radius-md);
font-size: 0.85rem;
font-weight: 500;
cursor: pointer;
border: 1px solid var(--border-subtle);
background: var(--bg-card);
color: var(--text-main);
display: inline-flex;
align-items: center;
gap: 8px;
transition: all 0.2s ease;
}
.btn:hover { background: var(--bg-hover); border-color: var(--text-muted); }
.btn-primary { background: var(--text-main); color: var(--bg-body); border-color: var(--text-main); }
.btn-primary:hover { opacity: 0.9; }
/* --- Layout --- */
.app-container {
display: flex;
flex: 1;
overflow: hidden;
position: relative;
}
/* --- Sidebar --- */
.sidebar {
width: var(--sidebar-width);
background: var(--bg-sidebar);
border-right: 1px solid var(--border-subtle);
display: flex;
flex-direction: column;
padding: 20px;
overflow-y: auto;
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
z-index: 40;
}
.sidebar-section { margin-bottom: 24px; }
.sidebar-title {
font-size: 0.7rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-muted);
margin-bottom: 12px;
font-weight: 600;
}
.file-list { display: flex; flex-direction: column; gap: 4px; }
.file-item {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 12px;
border-radius: var(--radius-md);
color: var(--text-muted);
font-size: 0.9rem;
cursor: pointer;
transition: all 0.2s;
}
.file-item:hover { background: var(--bg-hover); color: var(--text-main); }
.file-item.active { background: rgba(88, 166, 255, 0.1); color: var(--accent-primary); font-weight: 500; }
.file-item i { font-size: 1.1rem; }
.info-card {
background: var(--bg-card);
padding: 16px;
border-radius: var(--radius-md);
border: 1px solid var(--border-subtle);
}
.info-row { display: flex; justify-content: space-between; margin-bottom: 8px; font-size: 0.85rem; }
.info-row:last-child { margin-bottom: 0; }
.info-label { color: var(--text-muted); }
.info-val { font-weight: 500; color: var(--text-main); }
/* --- Main Content --- */
.main-content {
flex: 1;
display: flex;
flex-direction: column;
background: var(--bg-body);
overflow: hidden;
position: relative;
}
/* Toolbar */
.toolbar {
height: 60px;
border-bottom: 1px solid var(--border-subtle);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
background: var(--bg-body);
}
.toolbar-group { display: flex; align-items: center; gap: 16px; }
.select-control {
background: var(--bg-card);
border: 1px solid var(--border-subtle);
color: var(--text-main);
padding: 6px 12px;
border-radius: var(--radius-md);
font-size: 0.9rem;
cursor: pointer;
}
.search-wrapper {
position: relative;
width: 300px;
}
.search-wrapper i {
position: absolute;
left: 12px;
top: 50%;
transform: translateY(-50%);
color: var(--text-muted);
}
.search-input {
width: 100%;
background: var(--bg-card);
border: 1px solid var(--border-subtle);
color: var(--text-main);
padding: 8px 12px 8px 36px;
border-radius: var(--radius-md);
font-size: 0.9rem;
transition: border-color 0.2s;
}
.search-input:focus { border-color: var(--accent-primary); }
/* Table Area */
.table-wrapper {
flex: 1;
overflow: auto;
position: relative;
}
table {
width: 100%;
border-collapse: collapse;
font-size: 0.85rem;
min-width: 800px;
}
th {
background: var(--bg-sidebar);
color: var(--text-muted);
font-weight: 600;
text-align: left;
padding: 12px 20px;
position: sticky;
top: 0;
z-index: 10;
border-bottom: 1px solid var(--border-subtle);
cursor: pointer;
user-select: none;
}
th:hover { color: var(--text-main); background: #1a1e23; }
th i { margin-left: 6px; font-size: 0.8rem; opacity: 0.5; }
td {
padding: 12px 20px;
border-bottom: 1px solid var(--border-subtle);
color: var(--text-main);
vertical-align: middle;
max-width: 300px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
tr:hover td { background: rgba(255, 255, 255, 0.02); }
/* Cell Types */
.cell-img {
width: 40px;
height: 40px;
border-radius: 6px;
object-fit: cover;
border: 1px solid var(--border-subtle);
background: #000;
}
.cell-link {
color: var(--accent-primary);
text-decoration: none;
cursor: pointer;
}
.cell-link:hover { text-decoration: underline; }
/* Pagination */
.pagination {
height: 50px;
border-top: 1px solid var(--border-subtle);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
background: var(--bg-sidebar);
font-size: 0.85rem;
color: var(--text-muted);
}
/* Empty State */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: var(--text-muted);
text-align: center;
}
.empty-state i { font-size: 3rem; margin-bottom: 16px; color: var(--border-subtle); opacity: 0.5; }
/* Modal */
.modal-overlay {
position: fixed;
top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.7);
backdrop-filter: blur(4px);
z-index: 100;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s;
}
.modal-overlay.open { opacity: 1; pointer-events: auto; }
.modal {
background: var(--bg-card);
width: 90%;
max-width: 700px;
max-height: 85vh;
border-radius: var(--radius-lg);
border: 1px solid var(--border-subtle);
box-shadow: var(--shadow-card);
display: flex;
flex-direction: column;
transform: scale(0.95);
transition: transform 0.2s;
}
.modal-overlay.open .modal { transform: scale(1); }
.modal-header {
padding: 16px 24px;
border-bottom: 1px solid var(--border-subtle);
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 600;
color: var(--text-main);
}
.modal-body {
padding: 24px;
overflow: auto;
font-family: 'Menlo', 'Monaco', monospace;
font-size: 0.9rem;
color: var(--text-main);
background: #0d1117;
white-space: pre-wrap;
}
.close-modal {
background: none; border: none; color: var(--text-muted);
font-size: 1.5rem; cursor: pointer;
}
.close-modal:hover { color: var(--text-main); }
/* Toast */
.toast {
position: fixed;
bottom: 24px;
right: 24px;
background: var(--bg-sidebar);
color: var(--text-main);
padding: 12px 20px;
border-radius: var(--radius-md);
border: 1px solid var(--border-subtle);
box-shadow: var(--shadow-card);
transform: translateY(100px);
opacity: 0;
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
z-index: 200;
display: flex;
align-items: center;
gap: 10px;
}
.toast.show { transform: translateY(0); opacity: 1; }
.toast-success { border-left: 3px solid var(--accent-secondary); }
.toast-error { border-left: 3px solid var(--accent-danger); }
/* Responsive */
@media (max-width: 768px) {
.sidebar {
position: absolute;
height: 100%;
transform: translateX(-100%);
box-shadow: 10px 0 20px rgba(0,0,0,0.5);
}
.sidebar.open { transform: translateX(0); }
.toolbar { padding: 0 12px; }
.search-wrapper { width: 140px; }
.header-actions span { display: none; } /* Hide text in header */
.logo span { display: none; }
.logo i { display: block; }
}
</style>
</head>
<body>
<!-- Header -->
<header class="header">
<div class="header-left">
<button class="btn-icon" id="menu-toggle" aria-label="Toggle Menu">
<i class="ph ph-list"></i>
</button>
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="logo">
<i class="ph-fill ph-cube-transparent"></i>
<span>Anycoder</span>
</a>
</div>
<div class="header-actions">
<button class="btn" id="upload-btn">
<i class="ph ph-upload-simple"></i>
<span>Load Data</span>
</button>
<input type="file" id="file-input" hidden accept=".json,.csv,.parquet">
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="btn btn-primary" style="padding: 8px 14px;">
<i class="ph-fill ph-code"></i>
<span>Spaces</span>
</a>
</div>
</header>
<div class="app-container">
<!-- Sidebar -->
<aside class="sidebar" id="sidebar">
<div class="sidebar-section">
<div class="sidebar-title">Dataset Overview</div>
<div class="info-card">
<div class="info-row">
<span class="info-label">Name</span>
<span class="info-val" id="dataset-name">Sample Dataset</span>
</div>
<div class="info-row">
<span class="info-label">Size</span>
<span class="info-val" id="dataset-size">--</span>
</div>
<div class="info-row">
<span class="info-label">Columns</span>
<span class="info-val" id="dataset-cols">--</span>
</div>
</div>
</div>
<div class="sidebar-section">
<div class="sidebar-title">Files</div>
<ul class="file-list">
<li class="file-item active">
<i class="ph ph-file-json"></i>
<span>train_data.json</span>
</li>
<li class="file-item">
<i class="ph ph-file-code"></i>
<span>config.json</span>
</li>
<li class="file-item">
<i class="ph ph-database"></i>
<span>metadata.parquet</span>
</li>
</ul>
</div>
</aside>
<!-- Main Content -->
<main class="main-content">
<!-- Toolbar -->
<div class="toolbar">
<div class="toolbar-group">
<select class="select-control" id="split-select">
<option value="train">Train Split</option>
<option value="test">Test Split</option>
</select>
<div class="search-wrapper">
<i class="ph ph-magnifying-glass"></i>
<input type="text" class="search-input" placeholder="Search..." id="search-input">
</div>
</div>
<div class="toolbar-group">
<button class="btn" id="columns-btn">
<i class="ph ph-table"></i> <span>Columns</span>
</button>
</div>
</div>
<!-- Table -->
<div class="table-wrapper" id="table-container">
<table id="data-table">
<thead>
<tr id="table-header">
<!-- Headers injected via JS -->
</tr>
</thead>
<tbody id="table-body">
<!-- Rows injected via JS -->
</tbody>
</table>
</div>
<!-- Empty State -->
<div class="empty-state" id="empty-state" style="display: none;">
<i class="ph ph-files"></i>
<h3>No Data Loaded</h3>
<p>Click "Load Data" to upload a JSON or CSV file.</p>
</div>
<!-- Pagination -->
<div class="pagination">
<span id="page-info">Showing 0 - 0 of 0</span>
<div style="display: flex; gap: 8px;">
<button class="btn" id="prev-btn" style="padding: 4px 12px; font-size: 0.8rem;"><i class="ph ph-caret-left"></i> Prev</button>
<button class="btn" id="next-btn" style="padding: 4px 12px; font-size: 0.8rem;">Next <i class="ph ph-caret-right"></i></button>
</div>
</div>
</main>
</div>
<!-- Modal -->
<div class="modal-overlay" id="modal-overlay">
<div class="modal">
<div class="modal-header">
<span id="modal-title">Cell Content</span>
<button class="close-modal" id="close-modal">&times;</button>
</div>
<div class="modal-body" id="modal-content"></div>
</div>
</div>
<!-- Toast -->
<div class="toast" id="toast">
<i class="ph-fill ph-check-circle" id="toast-icon"></i>
<span id="toast-msg">Operation successful</span>
</div>
<script>
// --- State Management ---
const state = {
data: [],
filteredData: [],
columns: [],
currentPage: 1,
rowsPerPage: 50,
sortCol: null,
sortAsc: true,
totalRows: 0
};
// --- Mock Data Generator ---
function generateMockData(count = 500) {
const data = [];
const items = ['Laptop', 'Coffee', 'Phone', 'Keyboard', 'Mouse'];
for (let i = 0; i < count; i++) {
data.push({
id: i,
item: items[Math.floor(Math.random() * items.length)],
price: (Math.random() * 1000).toFixed(2),
in_stock: Math.random() > 0.2,
rating: (Math.random() * 5).toFixed(1),
category: ['Electronics', 'Office', 'Home'][Math.floor(Math.random() * 3)],
image_url: `https://picsum.photos/seed/${i}/100/100`,
description: `High-quality ${items[Math.floor(Math.random() * items.length)]} for professional use.`
});
}
return data;
}
// --- Core Application Logic ---
const App = {
init() {
this.cacheDOM();
this.bindEvents();
this.loadMockData();
},
cacheDOM() {
this.dom = {
tableHeader: document.getElementById('table-header'),
tableBody: document.getElementById('table-body'),
pageInfo: document.getElementById('page-info'),
prevBtn: document.getElementById('prev-btn'),
nextBtn: document.getElementById('next-btn'),
searchInput: document.getElementById('search-input'),
fileInput: document.getElementById('file-input'),
uploadBtn: document.getElementById('upload-btn'),
modalOverlay: document.getElementById('modal-overlay'),
modalContent: document.getElementById('modal-content'),
closeModal: document.getElementById('close-modal'),
menuToggle: document.getElementById('menu-toggle'),
sidebar: document.getElementById('sidebar'),
toast: document.getElementById('toast'),
toastMsg: document.getElementById('toast-msg'),
datasetName: document.getElementById('dataset-name'),
datasetSize: document.getElementById('dataset-size'),
datasetCols: document.getElementById('dataset-cols'),
emptyState: document.getElementById('empty-state'),
tableContainer: document.getElementById('table-container')
};
},
bindEvents() {
// Pagination
this.dom.prevBtn.addEventListener('click', () => this.changePage(-1));
this.dom.nextBtn.addEventListener('click', () => this.changePage(1));
// Search
this.dom.searchInput.addEventListener('input', (e) => this.handleSearch(e.target.value));
// File Upload
this.dom.uploadBtn.addEventListener('click', () => this.dom.fileInput.click());
this.dom.fileInput.addEventListener('change', (e) => this.handleFileUpload(e));
// Modal
this.dom.closeModal.addEventListener('click', () => this.closeModal());
this.dom.modalOverlay.addEventListener('click', (e) => {
if (e.target === this.dom.modalOverlay) this.closeModal();
});
// Sidebar Toggle
this.dom.menuToggle.addEventListener('click', () => {
this.dom.sidebar.classList.toggle('open');
});
},
loadMockData() {
const mockData = generateMockData(2000);
this.processData(mockData);
this.showToast("Sample dataset loaded", "success");
},
processData(rawData) {
if (!rawData || rawData.length === 0) {
this.dom.tableContainer.style.display = 'none';
document.querySelector('.pagination').style.display = 'none';
this.dom.emptyState.style.display = 'flex';
return;
}
this.dom.tableContainer.style.display = 'block';
document.querySelector('.pagination').style.display = 'flex';
this.dom.emptyState.style.display = 'none';
state.data = rawData;
state.filteredData = rawData;
state.columns = Object.keys(rawData[0]);
state.totalRows = rawData.length;
state.currentPage = 1;
this.renderHeaders();
this.renderTable();
this.updatePagination();
this.updateSidebarInfo();
},
renderHeaders() {
this.dom.tableHeader.innerHTML = '';
state.columns.forEach(col => {
const th = document.createElement('th');
th.innerHTML = `${col} <i class="ph ph-caret-up-down"></i>`;
th.addEventListener('click', () => this.handleSort(col));
this.dom.tableHeader.appendChild(th);
});
},
renderTable() {
this.dom.tableBody.innerHTML = '';
const start = (state.currentPage - 1) * state.rowsPerPage;
const end = start + state.rowsPerPage;
const pageData = state.filteredData.slice(start, end);
pageData.forEach(row => {
const tr = document.createElement('tr');
state.columns.forEach(col => {
const td = document.createElement('td');
const val = row[col];
if (val === null || val === undefined) {
td.textContent = 'null';
td.style.color = 'var(--text-muted)';
} else if (typeof val === 'object') {
// Image check
if (col === 'image_url' || col === 'image') {
td.innerHTML = `<img src="${val}" class="cell-img" loading="lazy" alt="preview">`;
} else {
td.innerHTML = `<span class="cell-link" onclick="App.openModal(${JSON.stringify(val)})">View JSON</span>`;
}
} else if (Array.isArray(val)) {
td.innerHTML = `<span class="cell-link" onclick="App.openModal(${JSON.stringify(val)})">Array[${val.length}]</span>`;
} else {
td.textContent = val;
}
tr.appendChild(td);
});
this.dom.tableBody.appendChild(tr);
});
},
updatePagination() {
const total = state.filteredData.length;
const start = total === 0 ? 0 : (state.currentPage - 1) * state.rowsPerPage + 1;
const end = Math.min(start + state.rowsPerPage - 1, total);
this.dom.pageInfo.textContent = `Showing ${start}-${end} of ${total.toLocaleString()}`;
this.dom.prevBtn.disabled = state.currentPage === 1;
this.dom.nextBtn.disabled = end >= total;
},
changePage(delta) {
const totalPages = Math.ceil(state.filteredData.length / state.rowsPerPage);
const newPage = state.currentPage + delta;
if (newPage >= 1 && newPage <= totalPages) {
state.currentPage = newPage;
this.renderTable();
this.updatePagination();
this.dom.tableContainer.scrollTop = 0;
}
},
handleSearch(query) {
const lowerQ = query.toLowerCase();
state.filteredData = state.data.filter(row => {
return state.columns.some(col => {
const val = row[col];
if (val == null) return false;
return String(val).toLowerCase().includes(lowerQ);
});
});
state.currentPage = 1;
this.renderTable();
this.updatePagination();
},
handleSort(col) {
if (state.sortCol === col) {
state.sortAsc = !state.sortAsc;
} else {
state.sortCol = col;
state.sortAsc = true;
}
state.filteredData.sort((a, b) => {
let valA = a[col];
let valB = b[col];
if (typeof valA === 'string') valA = valA.toLowerCase();
if (typeof valB === 'string') valB = valB.toLowerCase();
if (valA < valB) return state.sortAsc ? -1 : 1;
if (valA > valB) return state.sortAsc ? 1 : -1;
return 0;
});
this.renderTable();
},
handleFileUpload(e) {
const file = e.target.files[0];
if (!file) return;
this.dom.datasetName.textContent = file.name;
this.dom.datasetSize.textContent = (file.size / 1024).toFixed(1) + ' KB';
this.showToast(`Loading ${file.name}...`);
const reader = new FileReader();
reader.onload = (event) => {
try {
if (file.name.endsWith('.json')) {
const json = JSON.parse(event.target.result);
const data = Array.isArray(json) ? json : (json.data || json);
this.processData(data);
this.showToast("JSON loaded successfully", "success");
} else if (file.name.endsWith('.csv')) {
const csv = this.parseCSV(event.target.result);
this.processData(csv);
this.showToast("CSV loaded successfully", "success");
} else {
this.showToast("Unsupported file type", "error");
}
} catch (err) {
console.error(err);
this.showToast("Error parsing file", "error");
}
};
reader.readAsText(file);
},
parseCSV(text) {
const lines = text.trim().split('\n');
const headers = lines[0].split(',').map(h => h.trim().replace(/"/g, ''));
const data = [];
for (let i = 1; i < lines.length; i++) {
const row = lines[i].split(',');
const obj = {};
headers.forEach((h, index) => {
let val = row[index] ? row[index].trim().replace(/"/g, '') : null;
// Simple type conversion
if (!isNaN(val) && val !== '') val = Number(val);
obj[h] = val;
});
data.push(obj);
}
return data;
},
updateSidebarInfo() {
this.dom.datasetCols.textContent = state.columns.length;
},
openModal(content) {
// Handle circular JSON or non-string objects safely
let formatted;
try {
formatted = JSON.stringify(content, null, 2);
} catch (e) {
formatted = content.toString();
}
this.dom.modalContent.textContent = formatted;
this.dom.modalOverlay.classList.add('open');
},
closeModal() {
this.dom.modalOverlay.classList.remove('open');
},
showToast(msg, type = 'success') {
this.dom.toastMsg.textContent = msg;
this.dom.toast.className = `toast toast-${type} show`;
const icon = document.getElementById('toast-icon');
icon.className = type === 'success' ? 'ph-fill ph-check-circle' : 'ph-fill ph-warning-circle';
setTimeout(() => {
this.dom.toast.classList.remove('show');
}, 3000);
}
};
// Start App
document.addEventListener('DOMContentLoaded', () => {
App.init();
});
</script>
</body>
</html>