|
import { api } from '../api.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';
|
|
|
|
export class RBoxApp extends HTMLElement {
|
|
constructor() {
|
|
super();
|
|
this.currentView = 'files';
|
|
this.user = null;
|
|
this.navigationStack = [];
|
|
}
|
|
|
|
async connectedCallback() {
|
|
await this.init();
|
|
this.addEventListener('show-toast', this.handleShowToast);
|
|
window.addEventListener('popstate', this.handlePopState.bind(this));
|
|
}
|
|
|
|
disconnectedCallback() {
|
|
this.removeEventListener('show-toast', this.handleShowToast);
|
|
}
|
|
|
|
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() {
|
|
if (!api.getToken()) {
|
|
this.showLogin();
|
|
} else {
|
|
try {
|
|
this.user = await api.getCurrentUser();
|
|
this.render();
|
|
} catch (error) {
|
|
this.showLogin();
|
|
}
|
|
}
|
|
}
|
|
|
|
showLogin() {
|
|
this.innerHTML = '<login-view></login-view>';
|
|
const loginView = this.querySelector('login-view');
|
|
loginView.addEventListener('auth-success', () => this.init());
|
|
}
|
|
|
|
render() {
|
|
this.innerHTML = `
|
|
<div class="app-container">
|
|
<header class="app-header">
|
|
<div class="header-left">
|
|
<h1 class="app-title">RBox</h1>
|
|
</div>
|
|
<div class="header-center">
|
|
<input type="search" placeholder="Search..." class="search-input" id="search-input">
|
|
</div>
|
|
<div class="header-right">
|
|
<span class="user-info">${this.user.username}</span>
|
|
<button class="button" id="logout-btn">Logout</button>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="app-body">
|
|
<aside class="app-sidebar">
|
|
<nav class="sidebar-nav">
|
|
<h3 class="nav-title">Navigation</h3>
|
|
<ul class="nav-list">
|
|
<li><a href="#" class="nav-link active" data-view="files">My Files</a></li>
|
|
<li><a href="#" class="nav-link" data-view="photos">Photo Gallery</a></li>
|
|
<li><a href="#" class="nav-link" data-view="shared">Shared Items</a></li>
|
|
<li><a href="#" class="nav-link" data-view="deleted">Deleted Files</a></li>
|
|
<li><a href="#" class="nav-link" data-view="billing">Billing</a></li>
|
|
${this.user && this.user.is_superuser ? `<li><a href="#" class="nav-link" data-view="admin">Admin Dashboard</a></li>` : ''}
|
|
${this.user && this.user.is_superuser ? `<li><a href="#" class="nav-link" data-view="admin-billing">Admin Billing</a></li>` : ''}
|
|
</ul>
|
|
<h3 class="nav-title">Quick Access</h3>
|
|
<ul class="nav-list">
|
|
<li><a href="#" class="nav-link" data-view="starred">Starred</a></li>
|
|
<li><a href="#" class="nav-link" data-view="recent">Recent</a></li>
|
|
</ul>
|
|
</nav>
|
|
</aside>
|
|
|
|
<main class="app-main">
|
|
<div id="main-content">
|
|
<file-list></file-list>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
|
|
<share-modal></share-modal>
|
|
</div>
|
|
`;
|
|
|
|
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 = `
|
|
<div class="shortcuts-help-modal">
|
|
<div class="shortcuts-help-content">
|
|
<h2>Keyboard Shortcuts</h2>
|
|
<div class="shortcuts-list">
|
|
<h3>File Operations</h3>
|
|
<div class="shortcut-item">
|
|
<kbd>Ctrl + U</kbd>
|
|
<span>Upload files</span>
|
|
</div>
|
|
<div class="shortcut-item">
|
|
<kbd>Ctrl + Shift + N</kbd>
|
|
<span>Create new folder</span>
|
|
</div>
|
|
<div class="shortcut-item">
|
|
<kbd>Ctrl + F</kbd>
|
|
<span>Focus search</span>
|
|
</div>
|
|
|
|
<h3>Navigation</h3>
|
|
<div class="shortcut-item">
|
|
<kbd>1</kbd>
|
|
<span>My Files</span>
|
|
</div>
|
|
<div class="shortcut-item">
|
|
<kbd>2</kbd>
|
|
<span>Photo Gallery</span>
|
|
</div>
|
|
<div class="shortcut-item">
|
|
<kbd>3</kbd>
|
|
<span>Shared Items</span>
|
|
</div>
|
|
<div class="shortcut-item">
|
|
<kbd>4</kbd>
|
|
<span>Deleted Files</span>
|
|
</div>
|
|
${this.user && this.user.is_superuser ? `
|
|
<div class="shortcut-item">
|
|
<kbd>5</kbd>
|
|
<span>Admin Dashboard</span>
|
|
</div>` : ''}
|
|
|
|
<h3>General</h3>
|
|
<div class="shortcut-item">
|
|
<kbd>ESC</kbd>
|
|
<span>Close modals</span>
|
|
</div>
|
|
<div class="shortcut-item">
|
|
<kbd>Ctrl + /</kbd>
|
|
<span>Show this help</span>
|
|
</div>
|
|
</div>
|
|
<button class="button" id="close-shortcuts-help">Close</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
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) {
|
|
this.closeAllOverlays();
|
|
|
|
if (e.state && e.state.view) {
|
|
if (e.state.view === 'code-editor' && e.state.file) {
|
|
this.showCodeEditor(e.state.file, false);
|
|
} else if (e.state.view === 'file-preview' && e.state.file) {
|
|
this.showFilePreview(e.state.file, false);
|
|
} else if (e.state.view === 'upload') {
|
|
const folderId = e.state.folderId !== undefined ? e.state.folderId : null;
|
|
this.showUpload(folderId, false);
|
|
} else {
|
|
this.switchView(e.state.view, false);
|
|
}
|
|
} else {
|
|
this.switchView('files', false);
|
|
}
|
|
}
|
|
|
|
closeAllOverlays() {
|
|
const existingEditor = this.querySelector('code-editor-view');
|
|
if (existingEditor) {
|
|
existingEditor.hide();
|
|
}
|
|
|
|
const existingPreview = this.querySelector('file-preview');
|
|
if (existingPreview) {
|
|
existingPreview.hide();
|
|
}
|
|
|
|
const existingUpload = this.querySelector('file-upload-view');
|
|
if (existingUpload) {
|
|
existingUpload.hide();
|
|
}
|
|
|
|
const shareModal = this.querySelector('share-modal');
|
|
if (shareModal && shareModal.style.display !== 'none') {
|
|
shareModal.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
showCodeEditor(file, pushState = true) {
|
|
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) {
|
|
window.history.pushState(
|
|
{ view: 'code-editor', file: file },
|
|
'',
|
|
`#editor/${file.id}`
|
|
);
|
|
}
|
|
}
|
|
|
|
showFilePreview(file, pushState = true) {
|
|
this.closeAllOverlays();
|
|
|
|
const mainElement = this.querySelector('.app-main');
|
|
const preview = document.createElement('file-preview');
|
|
mainElement.appendChild(preview);
|
|
preview.show(file, false);
|
|
|
|
if (pushState) {
|
|
window.history.pushState(
|
|
{ view: 'file-preview', file: file },
|
|
'',
|
|
`#preview/${file.id}`
|
|
);
|
|
}
|
|
}
|
|
|
|
showUpload(folderId = null, pushState = true) {
|
|
this.closeAllOverlays();
|
|
|
|
const mainElement = this.querySelector('.app-main');
|
|
const uploadView = document.createElement('file-upload-view');
|
|
mainElement.appendChild(uploadView);
|
|
uploadView.setFolder(folderId);
|
|
|
|
if (pushState) {
|
|
window.history.pushState(
|
|
{ view: 'upload', folderId: folderId },
|
|
'',
|
|
'#upload'
|
|
);
|
|
}
|
|
}
|
|
|
|
async performSearch(query) {
|
|
try {
|
|
const files = await api.searchFiles(query);
|
|
const mainContent = this.querySelector('#main-content');
|
|
mainContent.innerHTML = `
|
|
<div class="search-results">
|
|
<h2>Search Results for "${query}"</h2>
|
|
<file-list data-search-mode="true"></file-list>
|
|
</div>
|
|
`;
|
|
const fileList = mainContent.querySelector('file-list');
|
|
fileList.setFiles(files);
|
|
this.attachListeners();
|
|
} catch (error) {
|
|
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 = '<file-list></file-list>';
|
|
this.attachListeners();
|
|
break;
|
|
case 'photos':
|
|
mainContent.innerHTML = '<photo-gallery></photo-gallery>';
|
|
this.attachListeners();
|
|
break;
|
|
case 'shared':
|
|
mainContent.innerHTML = '<shared-items></shared-items>';
|
|
this.attachListeners();
|
|
break;
|
|
case 'deleted':
|
|
mainContent.innerHTML = '<deleted-files></deleted-files>';
|
|
this.attachListeners();
|
|
break;
|
|
case 'starred':
|
|
mainContent.innerHTML = '<starred-items></starred-items>';
|
|
this.attachListeners();
|
|
break;
|
|
case 'recent':
|
|
mainContent.innerHTML = '<recent-files></recent-files>';
|
|
this.attachListeners();
|
|
break;
|
|
case 'admin':
|
|
mainContent.innerHTML = '<admin-dashboard></admin-dashboard>';
|
|
this.attachListeners();
|
|
break;
|
|
case 'billing':
|
|
mainContent.innerHTML = '<billing-dashboard></billing-dashboard>';
|
|
this.attachListeners();
|
|
break;
|
|
case 'admin-billing':
|
|
mainContent.innerHTML = '<admin-billing></admin-billing>';
|
|
this.attachListeners();
|
|
break;
|
|
}
|
|
}
|
|
}
|