import app from '../app.js'; import './login-view.js'; import './file-list.js'; import './file-upload-view.js'; import './share-modal.js'; import './photo-gallery.js'; import './file-preview.js'; import './deleted-files.js'; import './admin-dashboard.js'; import './toast-notification.js'; import './starred-items.js'; import './recent-files.js'; import './shared-items.js'; import './billing-dashboard.js'; import './admin-billing.js'; import './code-editor-view.js'; import { shortcuts } from '../shortcuts.js'; const api = app.getAPI(); const logger = app.getLogger(); const appState = app.getState(); export class RBoxApp extends HTMLElement { constructor() { super(); this.currentView = 'files'; this.user = null; this.navigationStack = []; this.boundHandlePopState = this.handlePopState.bind(this); this.popstateAttached = false; this.currentSearchId = 0; } async connectedCallback() { try { await this.init(); this.addEventListener('show-toast', this.handleShowToast); if (!this.popstateAttached) { window.addEventListener('popstate', this.boundHandlePopState); this.popstateAttached = true; logger.debug('Popstate listener attached'); } } catch (error) { logger.error('Failed to initialize RBoxApp', error); this.innerHTML = `

Failed to Load Application

${error.message}

`; } } disconnectedCallback() { this.removeEventListener('show-toast', this.handleShowToast); if (this.popstateAttached) { window.removeEventListener('popstate', this.boundHandlePopState); this.popstateAttached = false; logger.debug('Popstate listener removed'); } } handleShowToast = (event) => { const { message, type, duration } = event.detail; this.showToast(message, type, duration); } showToast(message, type = 'info', duration = 3000) { const toast = document.createElement('toast-notification'); document.body.appendChild(toast); toast.show(message, type, duration); } async init() { try { if (!api.getToken()) { logger.info('No token found, showing login'); this.showLogin(); } else { logger.info('Initializing application with stored token'); this.user = await api.getCurrentUser(); appState.setState({ user: this.user }); logger.info('User loaded successfully', { username: this.user.username }); this.render(); } } catch (error) { logger.error('Failed to initialize application', error); api.setToken(null); this.showLogin(); } } showLogin() { this.innerHTML = ''; const loginView = this.querySelector('login-view'); loginView.addEventListener('auth-success', () => this.init()); } render() { this.innerHTML = `

RBox

`; this.initializeNavigation(); this.attachListeners(); this.registerShortcuts(); } initializeNavigation() { if (!window.history.state) { const hash = window.location.hash.slice(1); if (hash && hash !== '') { const view = hash.split('/')[0]; const validViews = ['files', 'photos', 'shared', 'deleted', 'starred', 'recent', 'admin', 'billing', 'admin-billing']; if (validViews.includes(view)) { window.history.replaceState({ view: view }, '', `#${hash}`); this.currentView = view; } else { window.history.replaceState({ view: 'files' }, '', '#files'); } } else { window.history.replaceState({ view: 'files' }, '', '#files'); } } } registerShortcuts() { shortcuts.register('ctrl+u', () => { const fileList = this.querySelector('file-list'); const folderId = fileList ? fileList.currentFolderId : null; this.showUpload(folderId); }); shortcuts.register('ctrl+f', () => { const searchInput = this.querySelector('#search-input'); if (searchInput) { searchInput.focus(); } }); shortcuts.register('ctrl+/', () => { this.showShortcutsHelp(); }); shortcuts.register('ctrl+shift+n', () => { if (this.currentView === 'files') { const fileList = this.querySelector('file-list'); if (fileList) { fileList.triggerCreateFolder(); } } }); shortcuts.register('1', () => { this.switchView('files'); }); shortcuts.register('2', () => { this.switchView('photos'); }); shortcuts.register('3', () => { this.switchView('shared'); }); shortcuts.register('4', () => { this.switchView('deleted'); }); shortcuts.register('5', () => { if (this.user && this.user.is_superuser) { this.switchView('admin'); } }); shortcuts.register('f2', () => { const fileListComponent = document.querySelector('file-list'); if (fileListComponent && fileListComponent.selectedFiles && fileListComponent.selectedFiles.size === 1) { const fileId = Array.from(fileListComponent.selectedFiles)[0]; const file = fileListComponent.files.find(f => f.id === fileId); if (file) { const newName = prompt('Enter new name:', file.name); if (newName && newName !== file.name) { api.renameFile(fileId, newName).then(() => { fileListComponent.loadContents(fileListComponent.currentFolderId); document.dispatchEvent(new CustomEvent('show-toast', { detail: { message: 'File renamed successfully', type: 'success' } })); }).catch(error => { document.dispatchEvent(new CustomEvent('show-toast', { detail: { message: 'Failed to rename file', type: 'error' } })); }); } } } else if (fileListComponent && fileListComponent.selectedFolders && fileListComponent.selectedFolders.size === 1) { const folderId = Array.from(fileListComponent.selectedFolders)[0]; const folder = fileListComponent.folders.find(f => f.id === folderId); if (folder) { const newName = prompt('Enter new name:', folder.name); if (newName && newName !== folder.name) { api.updateFolder(folderId, { name: newName }).then(() => { fileListComponent.loadContents(fileListComponent.currentFolderId); document.dispatchEvent(new CustomEvent('show-toast', { detail: { message: 'Folder renamed successfully', type: 'success' } })); }).catch(error => { document.dispatchEvent(new CustomEvent('show-toast', { detail: { message: 'Failed to rename folder', type: 'error' } })); }); } } } }); } showShortcutsHelp() { const helpContent = `

Keyboard Shortcuts

File Operations

Ctrl + U Upload files
Ctrl + Shift + N Create new folder
Ctrl + F Focus search

Navigation

1 My Files
2 Photo Gallery
3 Shared Items
4 Deleted Files
${this.user && this.user.is_superuser ? `
5 Admin Dashboard
` : ''}

General

ESC Close modals
Ctrl + / Show this help
`; const helpDiv = document.createElement('div'); helpDiv.innerHTML = helpContent; helpDiv.querySelector('.shortcuts-help-modal').style.cssText = ` position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 10000; `; document.body.appendChild(helpDiv); const closeHelp = () => { document.body.removeChild(helpDiv); document.removeEventListener('keydown', handleEscape); }; const handleEscape = (e) => { if (e.key === 'Escape') { closeHelp(); } }; const closeBtn = helpDiv.querySelector('#close-shortcuts-help'); closeBtn.addEventListener('click', closeHelp); helpDiv.querySelector('.shortcuts-help-modal').addEventListener('click', (e) => { if (e.target.classList.contains('shortcuts-help-modal')) closeHelp(); }); document.addEventListener('keydown', handleEscape); } attachListeners() { this.querySelector('#logout-btn')?.addEventListener('click', () => { api.logout(); }); this.querySelectorAll('.nav-link').forEach(link => { link.addEventListener('click', (e) => { e.preventDefault(); const view = link.dataset.view; this.switchView(view); }); }); const fileList = this.querySelector('file-list'); if (fileList) { fileList.addEventListener('upload-request', (e) => { this.showUpload(e.detail.folderId); }); fileList.addEventListener('folder-open', (e) => { fileList.loadContents(e.detail.folderId); }); fileList.addEventListener('share-request', (e) => { const modal = this.querySelector('share-modal'); modal.show(e.detail.fileId); }); } this.addEventListener('upload-complete', () => { const fileList = this.querySelector('file-list'); if (fileList) { fileList.loadContents(fileList.currentFolderId); } }); const searchInput = this.querySelector('#search-input'); if (searchInput) { let searchTimeout; searchInput.addEventListener('input', (e) => { clearTimeout(searchTimeout); const query = e.target.value.trim(); if (query.length > 0) { searchTimeout = setTimeout(() => this.performSearch(query), 300); } }); } this.addEventListener('photo-click', (e) => { this.showFilePreview(e.detail.photo); }); this.addEventListener('share-file', (e) => { const modal = this.querySelector('share-modal'); modal.show(e.detail.file.id); }); this.addEventListener('edit-file', (e) => { this.showCodeEditor(e.detail.file); }); } handlePopState(e) { logger.debug('Popstate event', { state: e.state, url: window.location.href }); this.closeAllOverlays(); if (e.state && e.state.view) { const view = e.state.view; if (view === 'code-editor' && e.state.file) { logger.debug('Restoring code editor view'); this.showCodeEditor(e.state.file, false); } else if (view === 'file-preview' && e.state.file) { logger.debug('Restoring file preview view'); this.showFilePreview(e.state.file, false); } else if (view === 'upload') { logger.debug('Restoring upload view'); const folderId = e.state.folderId !== undefined ? e.state.folderId : null; this.showUpload(folderId, false); } else { logger.debug('Switching to view', { view }); this.switchView(view, false); } } else { logger.debug('No state, defaulting to files view'); this.switchView('files', false); } } closeAllOverlays() { logger.debug('Closing all overlays'); const existingEditor = this.querySelector('code-editor-view'); if (existingEditor) { logger.debug('Hiding code editor'); existingEditor.hide(); } const existingPreview = this.querySelector('file-preview'); if (existingPreview) { logger.debug('Hiding file preview'); existingPreview.hide(); } const existingUpload = this.querySelector('file-upload-view'); if (existingUpload) { logger.debug('Hiding file upload'); existingUpload.hide(); } const shareModal = this.querySelector('share-modal'); if (shareModal && shareModal.style.display !== 'none') { logger.debug('Hiding share modal'); shareModal.style.display = 'none'; } } showCodeEditor(file, pushState = true) { logger.debug('Showing code editor', { file: file.name, pushState }); this.closeAllOverlays(); const mainElement = this.querySelector('.app-main'); const editorView = document.createElement('code-editor-view'); mainElement.appendChild(editorView); editorView.setFile(file, this.currentView); if (pushState) { const currentState = window.history.state || {}; const currentView = currentState.view || this.currentView; if (currentView !== 'code-editor') { window.history.pushState( { view: 'code-editor', file: file, previousView: currentView }, '', `#editor/${file.id}` ); logger.debug('Pushed code editor state', { previousView: currentView }); } else { logger.debug('Already in code editor view, replacing state'); window.history.replaceState( { view: 'code-editor', file: file, previousView: currentView }, '', `#editor/${file.id}` ); } } } showFilePreview(file, pushState = true) { logger.debug('Showing file preview', { file: file.name, pushState }); this.closeAllOverlays(); const mainElement = this.querySelector('.app-main'); const preview = document.createElement('file-preview'); mainElement.appendChild(preview); preview.show(file, false); if (pushState) { const currentState = window.history.state || {}; const currentView = currentState.view || this.currentView; if (currentView !== 'file-preview') { window.history.pushState( { view: 'file-preview', file: file, previousView: currentView }, '', `#preview/${file.id}` ); logger.debug('Pushed file preview state', { previousView: currentView }); } else { logger.debug('Already in file preview view, replacing state'); window.history.replaceState( { view: 'file-preview', file: file, previousView: currentView }, '', `#preview/${file.id}` ); } } } showUpload(folderId = null, pushState = true) { logger.debug('Showing upload view', { folderId, pushState }); this.closeAllOverlays(); const mainElement = this.querySelector('.app-main'); const uploadView = document.createElement('file-upload-view'); mainElement.appendChild(uploadView); uploadView.setFolder(folderId); if (pushState) { const currentState = window.history.state || {}; const currentView = currentState.view || this.currentView; if (currentView !== 'upload') { window.history.pushState( { view: 'upload', folderId: folderId, previousView: currentView }, '', '#upload' ); logger.debug('Pushed upload state', { previousView: currentView }); } else { logger.debug('Already in upload view, replacing state'); window.history.replaceState( { view: 'upload', folderId: folderId, previousView: currentView }, '', '#upload' ); } } } async performSearch(query) { const searchId = ++this.currentSearchId; try { const files = await api.searchFiles(query); if (searchId !== this.currentSearchId) return; const mainContent = this.querySelector('#main-content'); mainContent.innerHTML = `

Search Results for "${query}"

`; const fileList = mainContent.querySelector('file-list'); fileList.setFiles(files); this.attachListeners(); } catch (error) { if (searchId === this.currentSearchId) { console.error('Search failed:', error); } } } switchView(view, pushState = true) { this.closeAllOverlays(); if(this.currentView === view) return; this.currentView = view; this.querySelectorAll('.nav-link').forEach(link => { link.classList.remove('active'); }); this.querySelector(`[data-view="${view}"]`)?.classList.add('active'); const mainContent = this.querySelector('#main-content'); if (pushState) { window.history.pushState({ view: view }, '', `#${view}`); } switch (view) { case 'files': mainContent.innerHTML = ''; this.attachListeners(); break; case 'photos': mainContent.innerHTML = ''; this.attachListeners(); break; case 'shared': mainContent.innerHTML = ''; this.attachListeners(); break; case 'deleted': mainContent.innerHTML = ''; this.attachListeners(); break; case 'starred': mainContent.innerHTML = ''; this.attachListeners(); break; case 'recent': mainContent.innerHTML = ''; this.attachListeners(); break; case 'admin': mainContent.innerHTML = ''; this.attachListeners(); break; case 'billing': mainContent.innerHTML = ''; this.attachListeners(); break; case 'admin-billing': mainContent.innerHTML = ''; this.attachListeners(); break; } } }