website / dashboardlogic.js
theguywhosucks's picture
Upload 17 files
8f199d9 verified
import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client@1.9.0/dist/index.min.js";
// Read/write tokens from sessionStorage
function getReadToken() {
return sessionStorage.getItem('hf_read_token') || '';
}
function getWriteToken() {
return sessionStorage.getItem('hf_write_token') || '';
}
// Helper to add tokens to every API call
function withTokens(obj = {}) {
return {
...obj,
read_token: getReadToken(),
write_token: getWriteToken(),
};
}
// Helper to call secure server-side proxy
async function callSecureApi(fn, args) {
const resp = await fetch('/api/proxy', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ fn, args })
});
if (!resp.ok) throw new Error('Proxy error: ' + resp.status);
return await resp.json();
}
let client;
let currentUser = null;
let currentPassword = null;
let files = [];
let selected = new Set();
let currentView = 'grid';
let previewFile = null;
let isLoadingFiles = false;
const iconMap = {
'pdf': 'πŸ“„', 'doc': 'πŸ“„', 'docx': 'πŸ“„',
'png': 'πŸ–ΌοΈ', 'jpg': 'πŸ–ΌοΈ', 'jpeg': 'πŸ–ΌοΈ', 'gif': 'πŸ–ΌοΈ',
'mp3': '🎡', 'wav': '🎡', 'flac': '🎡',
'mp4': 'πŸ“Ή', 'avi': 'πŸ“Ή', 'mov': 'πŸ“Ή',
'zip': 'πŸ—œοΈ', 'rar': 'πŸ—œοΈ', '7z': 'πŸ—œοΈ',
'txt': 'πŸ“', 'md': 'πŸ“',
'py': '🐍', 'js': 'πŸ“œ', 'html': '🌐', 'css': '🌐',
'psd': '🎨', 'ai': '🎨',
'sql': 'πŸ—ƒοΈ', 'xlsx': 'πŸ“Š', 'csv': 'πŸ“Š',
'pptx': 'πŸ“‘', 'ppt': 'πŸ“‘',
};
// Initialize dashboard on page load
async function initDashboard() {
console.log('=== Dashboard Init Started ===');
currentUser = sessionStorage.getItem('pockit_user');
currentPassword = sessionStorage.getItem('pockit_pass');
if (!currentUser || !currentPassword) {
console.warn('No session found, redirecting to login');
window.location.href = 'index.html';
return;
}
try {
// Update account info first
const initials = currentUser.substring(0, 2).toUpperCase();
document.querySelector('.avatar').textContent = initials;
document.querySelector('.account-name').textContent = currentUser;
// Load data using proxy
await loadUserData();
await loadFiles();
console.log('βœ“ Dashboard loaded');
} catch (err) {
console.error('Init error:', err);
showToast('Connection failed');
setTimeout(() => window.location.href = 'index.html', 2000);
}
}
// Load user info and update cards
async function loadUserData() {
try {
console.log('Loading user data...');
const result = await callSecureApi("/search_user", withTokens({ user_id: currentUser }));
const [log, isDev, isPro, isTester, capacityUsed, usedTotal] = result.data;
// Determine role
let role = 'free';
if (isDev) role = 'dev';
else if (isPro) role = 'pro';
else if (isTester) role = 'tester';
const tag = document.getElementById('role-tag');
tag.className = 'role-tag ' + role;
tag.textContent = role.charAt(0).toUpperCase() + role.slice(1);
document.getElementById('tier-val').textContent = tag.textContent;
// Parse capacity
const capacityPercent = Math.round(capacityUsed);
let usedDisplay = usedTotal || '0 / 5 GB';
let usedParts = usedDisplay.split('/');
let usedAmount = usedParts[0].trim();
let maxAmount = usedParts[1] ? usedParts[1].trim() : '5 GB';
// Update storage displays
document.querySelector('.card-bar-fill').style.width = capacityPercent + '%';
document.querySelector('.stor-label span:last-child').textContent = usedDisplay;
document.querySelector('.sbar-fill').style.width = capacityPercent + '%';
const storageCard = document.querySelectorAll('.card')[0];
storageCard.querySelector('.card-val').innerHTML = usedAmount + ' <span style="font-size:.38em;font-weight:400;color:rgba(0,0,0,.38)">(' + capacityPercent + '%)</span>';
storageCard.querySelector('.card-sub').textContent = usedDisplay + ' used';
document.querySelectorAll('.card')[3].querySelector('.card-sub').textContent = maxAmount + ' max capacity';
console.log('βœ“ User data loaded');
} catch (err) {
console.error('User data error:', err);
}
}
// Load files from API
async function loadFiles() {
// Prevent concurrent loads
if (isLoadingFiles) return;
isLoadingFiles = true;
try {
console.log('Loading files...');
loading(true);
const result = await callSecureApi("/get_files_secure", withTokens({
user_id: currentUser,
password: currentPassword,
}));
// API returns [dropdown, status]
const dropdownData = result.data[0];
console.log('Raw dropdownData:', dropdownData);
let fileNames = [];
if (dropdownData && typeof dropdownData === 'object' && dropdownData.choices) {
// Flatten all sub-arrays in choices
fileNames = dropdownData.choices.flat ? dropdownData.choices.flat() : [].concat(...dropdownData.choices);
} else if (Array.isArray(dropdownData)) {
fileNames = dropdownData;
} else if (typeof dropdownData === 'string') {
fileNames = [dropdownData];
}
// Convert all to string, trim, and filter empty
fileNames = fileNames.map(f => String(f).trim()).filter(Boolean);
console.log('Extracted fileNames from dropdown:', fileNames);
// Deduplicate file names (case-insensitive, trim)
const seen = new Set();
const deduped = [];
for (const name of fileNames) {
const key = name.toLowerCase();
if (!key || seen.has(key)) continue;
seen.add(key);
deduped.push(name);
}
files = deduped.map((name, idx) => {
const ext = name.includes('.') ? name.split('.').pop().toLowerCase() : '';
return {
id: idx,
name: name,
icon: iconMap[ext] || 'πŸ“',
size: 'β€”',
date: 'Recently',
type: ext ? ext.toUpperCase() : '',
preview: null // for preview content
};
});
console.log('Final files array before render:', files);
selected.clear();
updateStatsCards();
render(files);
loading(false);
console.log('βœ“ Files loaded and rendered');
preloadTextPreviews();
} catch (err) {
console.error('Files load error:', err);
loading(false);
files = [];
selected.clear();
render([]);
showToast('Failed to load files');
} finally {
isLoadingFiles = false;
}
}
// Preload short text previews for text files so grid view
// can show the first few words instead of just an icon.
async function preloadTextPreviews() {
// Only attempt after files are loaded and user is known
if (!currentUser || !currentPassword || !Array.isArray(files)) return;
for (const file of files) {
// Simple heuristic: only fetch preview for obvious text formats
const name = (file && file.name) ? String(file.name).toLowerCase() : '';
if (!name.endsWith('.txt') && !name.endsWith('.md')) continue;
try {
const result = await callSecureApi("/get_preview_text_action", withTokens({
user_id: currentUser,
password: currentPassword,
filename: file.name,
}));
const text = Array.isArray(result.data) ? result.data[0] : result.data;
if (typeof text === 'string' && text.trim()) {
file.preview = text;
}
} catch (err) {
console.error('Text preview preload error for', file.name, err);
}
}
render(files);
}
// Update stats cards
function updateStatsCards() {
const cards = document.querySelectorAll('.card');
if (cards.length < 3) return;
// Files count
cards[1].querySelector('.card-val').textContent = files.length;
// File types
const fileTypes = new Set(files.map(f => f.type));
cards[1].querySelector('.card-sub').textContent = `Across ${fileTypes.size} file type${fileTypes.size !== 1 ? 's' : ''}`;
// Upload card
cards[2].querySelector('.card-val').textContent = files.length > 0 ? '1' : '0';
cards[2].querySelector('.card-sub').textContent = files.length > 0 ? 'Last upload: Recently' : 'No uploads yet';
}
// Render grid and table
function render(list) {
console.log('Rendering', list.length, 'files:', list.map(f => f.name));
const grid = document.getElementById('file-grid');
const tbody = document.getElementById('file-tbody');
if (!grid) {
console.error('file-grid missing');
return;
}
if (!tbody) {
console.error('file-tbody missing');
return;
}
// Clear everything first
grid.innerHTML = '';
tbody.innerHTML = '';
// Only render if there are files
if (!list || list.length === 0) {
console.log('No files to render');
return;
}
// Grid HTML
const gridHTML = list.map((f, i) => {
let iconOrPreview = f.icon;
if (f.preview && typeof f.preview === 'string' && f.preview.trim()) {
const words = f.preview.trim().split(/\s+/).slice(0, 5).join(' ');
const snippet = words + (f.preview.trim().length > words.length ? '...' : '');
iconOrPreview = escapeHtml(snippet);
}
return `
<div class="file-card ${selected.has(i) ? 'selected' : ''}" onclick="toggleSelect(${i})" ondblclick="openPreview(${i})">
<span class="fc-check"><i class="fa-solid fa-check"></i></span>
<span class="fc-icon">${iconOrPreview}</span>
<span class="fc-name">${f.name}</span>
<span class="fc-meta">${f.size}</span>
</div>
`;
}).join('');
grid.innerHTML = gridHTML;
console.log('Grid rendered with', list.length, 'items');
// Table HTML
const tableHTML = list.map((f, i) => `
<tr onclick="openPreview(${i})">
<td class="td-name"><span>${f.icon}</span> ${f.name}</td>
<td class="td-muted">${f.size}</td>
<td class="td-muted">${f.date}</td>
<td class="td-muted">${f.type}</td>
<td class="td-actions">
<button class="tbl-btn" onclick="event.stopPropagation();openPreview(${i})"><i class="fa-solid fa-eye"></i></button>
<button class="tbl-btn" onclick="event.stopPropagation();downloadFile(${i})"><i class="fa-solid fa-download"></i></button>
<button class="tbl-btn del" onclick="event.stopPropagation();deleteFile(${i})"><i class="fa-solid fa-trash"></i></button>
</td>
</tr>
`).join('');
tbody.innerHTML = tableHTML;
console.log('Table rendered with', list.length, 'items');
}
// Select/deselect file
function toggleSelect(id) {
if (id < 0 || id >= files.length) return;
selected.has(id) ? selected.delete(id) : selected.add(id);
render(currentView === 'grid' ? files : files);
}
function clearSelection() {
selected.clear();
render(files);
}
function handleSelectAll(checked) {
if (checked) {
files.forEach((_, i) => selected.add(i));
} else {
selected.clear();
}
render(files);
}
// Open file preview
function openPreview(id) {
if (id < 0 || id >= files.length) return;
previewFile = files[id];
document.getElementById('preview-thumb').textContent = previewFile.icon;
document.getElementById('preview-name').textContent = previewFile.name;
document.getElementById('preview-name').style.color = '#1a1a2e';
document.getElementById('pv-size').textContent = previewFile.size;
document.getElementById('pv-date').textContent = previewFile.date;
document.getElementById('pv-type').textContent = previewFile.type;
// Load preview content from API
loadPreviewContent(previewFile);
selected.clear();
selected.add(id);
render(files);
}
// Load preview content for a file using /handle_preview_secure and /get_preview_text_action
async function loadPreviewContent(file) {
if (!file || !file.name) return;
const previewContainer = document.getElementById('preview-content');
if (!previewContainer) return;
previewContainer.innerHTML = '<span style="color:#aaa">Loading preview...</span>';
try {
// Try /handle_preview_secure first
const result = await callSecureApi("/handle_preview_secure", withTokens({
user_id: currentUser,
password: currentPassword,
filename: file.name,
}));
// result.data: [file, localPath, html, image, code]
let previewHtml = '';
if (result.data[3] && typeof result.data[3] === 'string' && result.data[3].startsWith('data:image')) {
// Image preview
previewHtml = `<img src="${result.data[3]}" alt="Preview" style="max-width:100%;max-height:200px;">`;
} else if (result.data[2] && typeof result.data[2] === 'string' && result.data[2].length > 0) {
// HTML preview
previewHtml = `<div class="pv-html">${result.data[2]}</div>`;
} else if (result.data[4] && typeof result.data[4] === 'string' && result.data[4].length > 0) {
// Code preview
previewHtml = `<pre class="pv-code">${escapeHtml(result.data[4])}</pre>`;
} else {
// Try /get_preview_text_action for text preview
const textResult = await callSecureApi("/get_preview_text_action", withTokens({
user_id: currentUser,
password: currentPassword,
filename: file.name,
}));
if (textResult.data && typeof textResult.data[0] === 'string' && textResult.data[0].length > 0) {
previewHtml = `<pre class="pv-text">${escapeHtml(textResult.data[0])}</pre>`;
} else {
previewHtml = '<span style="color:#aaa">No preview available.</span>';
}
}
previewContainer.innerHTML = previewHtml;
} catch (err) {
console.error('Preview error:', err);
previewContainer.innerHTML = '<span style="color:#f55">Failed to load preview.</span>';
}
}
// Helper to escape HTML for code/text preview
function escapeHtml(str) {
return String(str)
.replace(/&/g, '&amp;')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, '&#39;');
}
// Delete single file
async function deleteFile(id) {
if (id < 0 || id >= files.length) return;
const file = files[id];
try {
loading(true);
const result = await callSecureApi("/delete_file_secure", withTokens({
user_id: currentUser,
password: currentPassword,
filename: file.name,
}));
console.log('Deleted:', file.name);
// Update storage
const [status, capacityUsed, usedTotal] = result.data;
const percent = Math.round(capacityUsed);
document.querySelector('.card-bar-fill').style.width = percent + '%';
document.querySelector('.sbar-fill').style.width = percent + '%';
document.querySelector('.stor-label span:last-child').textContent = usedTotal;
// Remove from array
files.splice(id, 1);
selected.delete(id);
updateStatsCards();
render(files);
loading(false);
showToast(file.name + ' deleted');
} catch (err) {
console.error('Delete error:', err);
loading(false);
showToast('Delete failed');
}
}
// Delete selected files
async function deleteSelected() {
if (selected.size === 0) {
showToast('No files selected');
return;
}
const ids = Array.from(selected).sort((a, b) => b - a);
for (const id of ids) {
await deleteFile(id);
}
}
// Delete preview file
async function deletePreviewFile() {
if (!previewFile) return;
const idx = files.indexOf(previewFile);
if (idx >= 0) await deleteFile(idx);
previewFile = null;
document.getElementById('preview-name').textContent = 'Select a file';
document.getElementById('preview-name').style.color = 'rgba(0,0,0,.3)';
}
// Download file
async function downloadFile(id) {
if (id < 0 || id >= files.length) return;
const file = files[id];
try {
loading(true);
const resp = await fetch('/api/download-link', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
user_id: currentUser,
password: currentPassword,
filename: file.name,
}),
});
if (!resp.ok) throw new Error('Download-link HTTP ' + resp.status);
const payload = await resp.json();
const fileTarget = Array.isArray(payload.data) ? payload.data[0] : payload.data;
if (typeof fileTarget === 'string' && fileTarget) {
const a = document.createElement('a');
a.href = '/api/download?file=' + encodeURIComponent(fileTarget);
a.download = file.name;
document.body.appendChild(a);
a.click();
setTimeout(() => {
document.body.removeChild(a);
}, 100);
showToast('Download started: ' + file.name);
} else {
showToast('Download link error');
}
loading(false);
} catch (err) {
console.error('Download error:', err);
loading(false);
showToast('Download failed');
}
}
async function downloadPreviewFile() {
if (!previewFile) return;
const idx = files.indexOf(previewFile);
if (idx >= 0) {
await downloadFile(idx);
}
}
// Upload file (single file) via server-side endpoint so private HF token stays on the server
async function handleUpload(input) {
if (!input.files || !input.files[0]) return;
const file = input.files[0];
try {
loading(true);
const formData = new FormData();
formData.append('file', file);
formData.append('user_id', currentUser);
formData.append('password', currentPassword);
formData.append('custom_name', file.name);
const resp = await fetch('/api/upload', {
method: 'POST',
body: formData,
});
if (!resp.ok) throw new Error('Upload HTTP ' + resp.status);
const result = await resp.json();
console.log('Uploaded:', file.name);
// Update storage
const [status, capacityUsed, usedTotal] = result.data;
const percent = Math.round(capacityUsed);
document.querySelector('.card-bar-fill').style.width = percent + '%';
document.querySelector('.sbar-fill').style.width = percent + '%';
document.querySelector('.stor-label span:last-child').textContent = usedTotal;
// Reload files
await loadFiles();
showToast(file.name + ' uploaded');
input.value = '';
} catch (err) {
console.error('Upload error:', err);
showToast('Upload failed');
} finally {
loading(false);
}
}
function handleDrop(e) {
e.preventDefault();
document.getElementById('drop-zone').classList.remove('drag');
if (e.dataTransfer.files) {
handleUpload({ files: e.dataTransfer.files });
}
}
// Filter files by search
function filterFiles(q) {
const query = q.toLowerCase();
const filtered = files.filter(f => f.name.toLowerCase().includes(query));
render(filtered);
}
// Toggle view
function setView(v) {
currentView = v;
document.getElementById('file-grid').style.display = v === 'grid' ? 'grid' : 'none';
document.getElementById('file-table-wrap').style.display = v === 'list' ? 'block' : 'none';
document.getElementById('vt-grid').classList.toggle('active', v === 'grid');
document.getElementById('vt-list').classList.toggle('active', v === 'list');
}
// Navigation sections
function setNav(el, section) {
document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
el.classList.add('active');
if (section === 'recent') {
document.getElementById('page-title').textContent = 'Recent Files';
render(files.slice(0, 5));
} else {
document.getElementById('page-title').textContent = 'My Files';
render(files);
}
}
// Change password
async function changePassword() {
const p1 = document.getElementById('new-pw').value;
const p2 = document.getElementById('conf-pw').value;
if (!p1 || p1 !== p2) {
showToast('Passwords do not match');
return;
}
try {
loading(true);
const result = await callSecureApi("/update_password", withTokens({
user_id: currentUser,
new_password: p1,
}));
if (result.data[0].toLowerCase().includes('success') || result.data[0].toLowerCase().includes('updated')) {
currentPassword = p1;
sessionStorage.setItem('pockit_pass', p1);
closeModal('pw-modal');
showToast('Password updated');
document.getElementById('new-pw').value = '';
document.getElementById('conf-pw').value = '';
} else {
showToast('Password update failed');
}
loading(false);
} catch (err) {
console.error('Password error:', err);
loading(false);
showToast('Password update failed');
}
}
// Logout
function logoutUser() {
sessionStorage.clear();
showToast('Logged out');
setTimeout(() => window.location.href = 'index.html', 1500);
}
// Modal helpers
function openModal(id) {
document.getElementById(id).classList.add('open');
}
function closeModal(id) {
document.getElementById(id).classList.remove('open');
}
// UI helpers
function loading(on) {
document.getElementById('loader').classList.toggle('on', on);
}
let toastTimer;
function showToast(msg) {
const toast = document.getElementById('toast');
document.getElementById('toast-msg').textContent = msg;
toast.classList.add('show');
clearTimeout(toastTimer);
toastTimer = setTimeout(() => toast.classList.remove('show'), 3000);
}
// Initialize on page load
document.addEventListener('DOMContentLoaded', initDashboard);
// Expose to global scope
window.initDashboard = initDashboard;
window.loadUserData = loadUserData;
window.loadFiles = loadFiles;
window.updateStatsCards = updateStatsCards;
window.render = render;
window.toggleSelect = toggleSelect;
window.clearSelection = clearSelection;
window.handleSelectAll = handleSelectAll;
window.openPreview = openPreview;
window.loadPreviewContent = loadPreviewContent;
window.deleteFile = deleteFile;
window.deleteSelected = deleteSelected;
window.deletePreviewFile = deletePreviewFile;
window.downloadFile = downloadFile;
window.downloadPreviewFile = downloadPreviewFile;
window.handleUpload = handleUpload;
window.handleDrop = handleDrop;
window.filterFiles = filterFiles;
window.setView = setView;
window.setNav = setNav;
window.changePassword = changePassword;
window.logoutUser = logoutUser;
window.openModal = openModal;
window.closeModal = closeModal;
window.loading = loading;
window.showToast = showToast;