You are offline. Changes will sync when connection resumes.
FCOS FCOS Sprint
My Files
Starred
Recent

Select a project

Choose a project from the sidebar to view and manage your files.

No starred files

Star your important files for quick access.

No recent files

Your recently uploaded files will appear here.

File Preview

Open
Rename
Move to folder
Star
Download
Copy path
Delete
Rename
Delete

Drop files to upload

Files will be added to the current folder
0 selected
`; const win = window.open('', '_blank'); win.document.write(html); win.document.close(); setTimeout(() => { win.print(); }, 300); } // ── Polling ── function startPolling() { if (state.polling) clearInterval(state.polling); state.polling = setInterval(async () => { if (state.currentProject) { await loadFiles(); await loadFolders(); } }, 5000); } // ── Agent (opens custom GPT -- no API cost) ── const AGENT_URL = 'https://chatgpt.com/g/g-69af0fb012108191a5078db17bb26419-fcos-master-agent-builder'; function openAgent() { window.open(AGENT_URL, 'fcos-agent', 'width=480,height=720,left=' + (screen.width - 520) + ',top=100'); if (state.chatOpen) toggleAgentPanel(); } function toggleAgentPanel() { state.chatOpen = !state.chatOpen; document.getElementById('chatPanel').classList.toggle('show', state.chatOpen); } // ── Settings ── function openSettings() { const provider = localStorage.getItem('fcos_ai_provider') || 'chatgpt'; const key = localStorage.getItem('fcos_api_key') || ''; const quota = localStorage.getItem('fcos_storage_quota') || '5'; document.getElementById('settingsProvider').value = provider; document.getElementById('settingsApiKey').value = key; document.getElementById('settingsQuota').value = quota; document.getElementById('settingsModal').classList.add('show'); } function closeSettings() { document.getElementById('settingsModal').classList.remove('show'); } function saveSetting(key, value) { localStorage.setItem(key, value); toast('Setting saved', 'success'); } // ── Command Palette (fcos-app.js) ── function registerCommands() { if (typeof fcosApp === 'undefined' || !fcosApp.commands) return; fcosApp.commands.register('Upload File', 'ctrl+u', function() { triggerUpload(); }, 'Files'); fcosApp.commands.register('New Folder', 'ctrl+shift+n', function() { openNewFolderModal(); }, 'Files'); fcosApp.commands.register('Search Files', 'ctrl+f', function() { document.getElementById('searchInput').focus(); }, 'Navigation'); fcosApp.commands.register('Switch to Grid View', '', function() { setView('grid'); }, 'View'); fcosApp.commands.register('Switch to List View', '', function() { setView('list'); }, 'View'); fcosApp.commands.register('Open Settings', '', function() { openSettings(); }, 'App'); fcosApp.commands.register('Go to Synapse', '', function() { window.location.href = '/products/synapse/'; }, 'Navigation'); fcosApp.commands.register('Toggle Theme', '', function() { toggleTheme(); }, 'App'); fcosApp.commands.register('Export File List', 'ctrl+p', function() { printFileList(); }, 'Files'); fcosApp.commands.register('Select All Files', 'ctrl+a', function() { toggleSelectAll(true); }, 'Files'); fcosApp.commands.register('Open Agent', '', function() { openAgent(); }, 'App'); fcosApp.commands.register('My Files', '', function() { activateTab('files'); }, 'Tabs'); fcosApp.commands.register('Starred Files', '', function() { activateTab('starred'); }, 'Tabs'); fcosApp.commands.register('Recent Files', '', function() { activateTab('recent'); }, 'Tabs'); } // ── Offline Indicator / IndexedDB Cache (fcos-app.js) ── function initOfflineDetection() { function updateOnlineStatus() { const banner = document.getElementById('offlineBanner'); if (!navigator.onLine) { state.isOffline = true; banner.classList.add('show'); toast('You are offline. Cached data will be used.', 'warning'); // Try to load cached files loadCachedFiles(); } else { if (state.isOffline) { toast('Back online. Syncing...', 'success'); // Reload fresh data if (state.currentProject) { loadFiles(); loadFolders(); } } state.isOffline = false; banner.classList.remove('show'); } } window.addEventListener('online', updateOnlineStatus); window.addEventListener('offline', updateOnlineStatus); // Initial check if (!navigator.onLine) { updateOnlineStatus(); } } // ── IndexedDB for offline cache (via fcosApp.store if available) ── function cacheFilesOffline() { if (typeof fcosApp !== 'undefined' && fcosApp.store && fcosApp.store.set) { const key = 'sprint_files_' + (state.currentProject ? state.currentProject.id : 'none'); fcosApp.store.set(key, JSON.stringify({ files: state.files, folders: state.folders, timestamp: Date.now() })); } else { // Fallback: use localStorage try { const key = 'fcos_cache_' + (state.currentProject ? state.currentProject.id : 'none'); localStorage.setItem(key, JSON.stringify({ files: state.files, folders: state.folders, timestamp: Date.now() })); } catch (e) { // localStorage full, silently fail } } } async function loadCachedFiles() { if (!state.currentProject) return; const key = 'sprint_files_' + state.currentProject.id; let cached = null; if (typeof fcosApp !== 'undefined' && fcosApp.store && fcosApp.store.get) { try { const raw = await fcosApp.store.get(key); if (raw) cached = JSON.parse(raw); } catch (e) {} } if (!cached) { try { const lsKey = 'fcos_cache_' + state.currentProject.id; const raw = localStorage.getItem(lsKey); if (raw) cached = JSON.parse(raw); } catch (e) {} } if (cached && cached.files) { state.files = cached.files; state.folders = cached.folders || []; updateStats(); updateQuota(); renderFavorites(); renderFolderTree(); renderContent(); renderStarredTab(); renderRecentTab(); toast('Loaded cached files (offline)', 'info'); } } // ── Keyboard shortcuts ── document.addEventListener('keydown', (e) => { // Command palette: Ctrl+K if ((e.ctrlKey || e.metaKey) && e.key === 'k') { e.preventDefault(); if (typeof fcosApp !== 'undefined' && fcosApp.commands) { fcosApp.commands.open(); } return; } if (e.key === 'Escape') { if (document.getElementById('moveModal').classList.contains('show')) { closeMoveModal(); } else if (document.getElementById('settingsModal').classList.contains('show')) { closeSettings(); } else if (document.getElementById('folderModal').classList.contains('show')) { closeFolderModal(); } else if (document.getElementById('renameModal').classList.contains('show')) { closeRenameModal(); } else if (state.chatOpen) { toggleAgentPanel(); } else if (document.getElementById('previewPanel').classList.contains('open')) { closePreview(); renderContent(); } else if (state.selectedFiles.length > 0) { clearSelection(); } closeContextMenus(); return; } // Delete: delete selected file(s) (only when not typing) if (e.key === 'Delete' && !isTyping(e)) { if (state.selectedFiles.length > 0) { batchDelete(); } else if (state.selectedFile) { confirmDelete(state.selectedFile); } } // Ctrl+A: select all (when not typing) if ((e.ctrlKey || e.metaKey) && e.key === 'a' && !isTyping(e)) { e.preventDefault(); toggleSelectAll(true); } // Ctrl+P: print file list if ((e.ctrlKey || e.metaKey) && e.key === 'p' && !isTyping(e)) { e.preventDefault(); printFileList(); } // G then F: grid view, G then L: list view (when not typing) if (e.key === 'g' && !isTyping(e)) { // Wait for next key const handler = (e2) => { document.removeEventListener('keydown', handler); if (e2.key === 'f') setView('grid'); else if (e2.key === 'l') setView('list'); }; document.addEventListener('keydown', handler); setTimeout(() => document.removeEventListener('keydown', handler), 500); } }); function isTyping(e) { const tag = e.target.tagName; return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || e.target.isContentEditable; } // ── Utility ── function escHtml(str) { if (!str) return ''; const div = document.createElement('div'); div.textContent = str; return div.innerHTML; } // ── Init ── async function init() { initTheme(); // Register commands for command palette (fcos-app.js) registerCommands(); // Init offline detection initOfflineDetection(); // Load projects await loadProjects(); // Initialize fcos-widgets tooltips for static elements requestAnimationFrame(() => { initTooltipsOnContent(); }); // Initialize tabs (fcos-widgets.js) const tabContainer = document.querySelector('#fileTabs'); if (tabContainer) { // Manually ensure first tab is active activateTab('files'); } } init();