346 lines
12 KiB
JavaScript
346 lines
12 KiB
JavaScript
|
|
import { api } from '../api.js';
|
||
|
|
import './login-view.js';
|
||
|
|
import './file-list.js';
|
||
|
|
import './file-upload.js';
|
||
|
|
import './share-modal.js';
|
||
|
|
import './photo-gallery.js';
|
||
|
|
import './file-preview.js';
|
||
|
|
import { shortcuts } from '../shortcuts.js';
|
||
|
|
|
||
|
|
export class RBoxApp extends HTMLElement {
|
||
|
|
constructor() {
|
||
|
|
super();
|
||
|
|
this.currentView = 'files';
|
||
|
|
this.currentFolderId = null;
|
||
|
|
this.user = null;
|
||
|
|
}
|
||
|
|
|
||
|
|
async connectedCallback() {
|
||
|
|
await this.init();
|
||
|
|
}
|
||
|
|
|
||
|
|
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>
|
||
|
|
</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>
|
||
|
|
|
||
|
|
<file-upload></file-upload>
|
||
|
|
<share-modal></share-modal>
|
||
|
|
<file-preview></file-preview>
|
||
|
|
</div>
|
||
|
|
`;
|
||
|
|
|
||
|
|
this.attachListeners();
|
||
|
|
this.registerShortcuts();
|
||
|
|
}
|
||
|
|
|
||
|
|
registerShortcuts() {
|
||
|
|
shortcuts.register('ctrl+u', () => {
|
||
|
|
const upload = this.querySelector('file-upload');
|
||
|
|
if (upload) {
|
||
|
|
upload.setFolder(this.currentFolderId);
|
||
|
|
upload.show();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
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('f2', () => {
|
||
|
|
console.log('Rename shortcut - to be implemented');
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
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>
|
||
|
|
|
||
|
|
<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', () => {
|
||
|
|
const upload = this.querySelector('file-upload');
|
||
|
|
upload.setFolder(this.currentFolderId);
|
||
|
|
upload.show();
|
||
|
|
});
|
||
|
|
|
||
|
|
fileList.addEventListener('folder-open', (e) => {
|
||
|
|
this.currentFolderId = e.detail.folderId;
|
||
|
|
fileList.loadContents(this.currentFolderId);
|
||
|
|
});
|
||
|
|
|
||
|
|
fileList.addEventListener('share-request', (e) => {
|
||
|
|
const modal = this.querySelector('share-modal');
|
||
|
|
modal.show(e.detail.fileId);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
const upload = this.querySelector('file-upload');
|
||
|
|
if (upload) {
|
||
|
|
upload.addEventListener('upload-complete', () => {
|
||
|
|
const fileList = this.querySelector('file-list');
|
||
|
|
fileList.loadContents(this.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) => {
|
||
|
|
const preview = this.querySelector('file-preview');
|
||
|
|
preview.show(e.detail.photo);
|
||
|
|
});
|
||
|
|
|
||
|
|
this.addEventListener('share-file', (e) => {
|
||
|
|
const modal = this.querySelector('share-modal');
|
||
|
|
modal.show(e.detail.file.id);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
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) {
|
||
|
|
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');
|
||
|
|
|
||
|
|
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 = '<div class="placeholder">Shared Items - Coming Soon</div>';
|
||
|
|
break;
|
||
|
|
case 'deleted':
|
||
|
|
mainContent.innerHTML = '<div class="placeholder">Deleted Files - Coming Soon</div>';
|
||
|
|
break;
|
||
|
|
case 'starred':
|
||
|
|
mainContent.innerHTML = '<div class="placeholder">Starred Items - Coming Soon</div>';
|
||
|
|
break;
|
||
|
|
case 'recent':
|
||
|
|
mainContent.innerHTML = '<div class="placeholder">Recent Files - Coming Soon</div>';
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|