Files
Oxicloud/static/index.html
T
Dionisio 1672889044 feat(#93): notification bell with upload progress
Replace the floating upload toast with a notification bell in the top bar
(between language selector and user avatar). All upload progress, completion,
and quota errors now flow through the bell dropdown panel.

- Add notification bell button with animated badge counter
- Dropdown panel shows per-file upload progress bars and overall batch progress
- Bell rings on new notifications when panel is closed
- Upload success/error states with color-coded icons
- Quota exceeded errors shown as notification items
- Clear all button to dismiss notifications
- Panel auto-opens when upload starts
- Full dark mode support
- Mutual exclusion with user menu (opening one closes the other)
- i18n keys for en/es (notifications.title, notifications.empty)
- SW cache bump to v10

Files:
- static/js/notifications.js (new module)
- static/index.html: bell markup + remove old toast
- static/css/style.css: bell + panel styles + dark mode
- static/js/fileOperations.js: redirect upload progress to notification bell
- static/js/app.js: close bell when user menu opens
- static/locales/{en,es}.json: i18n keys
- static/sw.js: cache v10 + notifications.js asset
2026-02-14 10:46:23 +01:00

320 lines
16 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title data-i18n="app.title">OxiCloud</title>
<!-- Apply saved theme immediately to prevent flash of light mode -->
<script>if(localStorage.getItem('oxicloud_theme')==='dark')document.documentElement.setAttribute('data-theme','dark');</script>
<!-- Styles -->
<link rel="stylesheet" href="/css/style.css">
<link rel="stylesheet" href="/css/inlineViewer.css">
<link rel="stylesheet" href="/css/fileViewer.css">
<link rel="stylesheet" href="/css/favorites.css">
<link rel="stylesheet" href="/css/recent.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin>
<!-- Scripts -->
<script src="/js/i18n.js"></script>
<script src="/js/languageSelector.js"></script>
<script src="/js/notifications.js"></script>
<script src="/js/modal.js"></script>
<script src="/js/ui.js"></script>
<script src="/js/contextMenus.js"></script>
<script src="/js/fileOperations.js"></script>
<script src="/js/multiSelect.js"></script>
<script src="/js/search.js"></script>
<script src="/js/favorites.js"></script>
<script src="/js/recent.js"></script>
<script src="/js/fileRenderer.js"></script>
<script src="/js/fileSharing.js"></script>
<script src="/js/components/sharedView.js"></script>
<!-- Viewers are loaded last to ensure document.body is available -->
<script defer src="/js/fileViewer.js"></script>
<script defer src="/js/inlineViewer.js"></script>
<script src="/js/app.js"></script>
<!-- Service Worker Registration -->
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then(reg => console.log('Service Worker registered successfully'))
.catch(err => console.log('Service Worker registration failed:', err));
});
}
</script>
</head>
<body>
<!-- Sidebar -->
<div class="sidebar">
<a href="/" class="logo-container" style="text-decoration:none;color:inherit;">
<div class="logo">
<svg viewBox="0 0 500 500">
<path d="M345 310c32 0 58-26 58-58s-26-58-58-58c-6.2 0-12 0.9-17.5 2.7C318 166 289 143 255 143c-34.3 0-63.1 22.6-73 53.7C176.9 195.7 171 195 165 195c-32 0-58 26-58 58s26 58 58 58h180z" fill="#fff"/>
</svg>
</div>
<div class="app-name">OxiCloud</div>
</a>
<div class="nav-menu">
<div class="nav-item active">
<i class="fas fa-folder"></i>
<span data-i18n="nav.files">Files</span>
</div>
<div class="nav-item" id="nav-shared">
<i class="fas fa-share-alt"></i>
<span data-i18n="nav.shared">Shared</span>
</div>
<div class="nav-item">
<i class="fas fa-clock"></i>
<span data-i18n="nav.recent">Recent</span>
</div>
<div class="nav-item">
<i class="fas fa-star"></i>
<span data-i18n="nav.favorites">Favorites</span>
</div>
<div class="nav-item">
<i class="fas fa-trash"></i>
<span data-i18n="nav.trash">Trash</span>
</div>
</div>
<div class="storage-container">
<div class="storage-title"><i class="fas fa-database"></i> <span data-i18n="storage.title">Storage</span></div>
<div class="storage-bar">
<div class="storage-fill"></div>
</div>
<div class="storage-info" data-i18n="storage.calculating">Calculating...</div>
</div>
</div>
<!-- Main Content -->
<div class="main-content">
<!-- Top Bar -->
<div class="top-bar">
<div class="search-container">
<i class="fas fa-search search-icon"></i>
<input type="text" id="search-input" data-i18n-placeholder="actions.search" placeholder="Search files, folders...">
<button id="search-button" class="search-button" data-i18n-title="actions.search_btn" title="Search">
<i class="fas fa-search"></i>
</button>
</div>
<div class="user-controls">
<div id="language-selector"></div>
<!-- Notification Bell -->
<div class="notif-wrapper" id="notif-wrapper">
<button class="notif-bell-btn" id="notif-bell-btn" title="Notifications">
<i class="fas fa-bell"></i>
<span class="notif-badge" id="notif-badge" style="display:none">0</span>
</button>
<div class="notif-panel" id="notif-panel">
<div class="notif-panel-header">
<span class="notif-panel-title" data-i18n="notifications.title">Notifications</span>
<button class="notif-clear-btn" id="notif-clear-btn" title="Clear all">
<i class="fas fa-check-double"></i>
</button>
</div>
<div class="notif-panel-body" id="notif-panel-body">
<div class="notif-empty" id="notif-empty">
<i class="fas fa-bell-slash"></i>
<span data-i18n="notifications.empty">No notifications</span>
</div>
</div>
</div>
</div>
<div class="user-menu-wrapper" id="user-menu-wrapper">
<button class="user-avatar-btn" id="user-avatar-btn">
<div class="user-avatar" id="user-avatar">AD</div>
</button>
<div class="user-menu" id="user-menu">
<div class="user-menu-header">
<div class="user-menu-avatar" id="user-menu-avatar">AD</div>
<div class="user-menu-info">
<div class="user-menu-name" id="user-menu-name">User</div>
<div class="user-menu-email" id="user-menu-email">user@oxicloud.app</div>
</div>
</div>
<div class="user-menu-role-badge" id="user-menu-role-badge" style="display:none">
<span class="role-badge role-badge-admin"><i class="fas fa-shield-alt"></i> Admin</span>
</div>
<div class="user-menu-storage">
<div class="user-menu-storage-label">
<i class="fas fa-database"></i>
<span data-i18n="storage.title">Storage</span>
</div>
<div class="user-menu-storage-bar">
<div class="user-menu-storage-fill" id="user-menu-storage-fill"></div>
</div>
<div class="user-menu-storage-text" id="user-menu-storage-text">0% used</div>
</div>
<div class="user-menu-divider"></div>
<button class="user-menu-item user-menu-admin" id="user-menu-admin" style="display:none">
<i class="fas fa-cogs"></i>
<span data-i18n="user_menu.admin_panel">Admin panel</span>
</button>
<button class="user-menu-item" id="user-menu-profile">
<i class="fas fa-user-circle"></i>
<span data-i18n="user_menu.profile">My profile</span>
</button>
<div class="user-menu-divider" id="user-menu-admin-divider" style="display:none"></div>
<button class="user-menu-item" id="user-menu-theme">
<i class="fas fa-moon"></i>
<span data-i18n="user_menu.appearance">Appearance</span>
<div class="theme-toggle-pill" id="theme-toggle-pill">
<div class="theme-toggle-knob"></div>
</div>
</button>
<button class="user-menu-item" id="user-menu-about">
<i class="fas fa-info-circle"></i>
<span data-i18n="user_menu.about">About OxiCloud</span>
</button>
<div class="user-menu-divider"></div>
<button class="user-menu-item user-menu-logout" id="user-menu-logout">
<i class="fas fa-sign-out-alt"></i>
<span data-i18n="actions.logout">Log out</span>
</button>
</div>
</div>
</div>
</div>
<div class="content-area">
<h1 class="page-title" data-i18n="nav.files">Files</h1>
<div class="actions-bar">
<div class="action-buttons">
<div class="upload-dropdown" id="upload-dropdown">
<button class="btn btn-primary" id="upload-btn">
<i class="fas fa-cloud-upload-alt"></i>
<span data-i18n="actions.upload">Upload</span>
<i class="fas fa-caret-down" style="margin-left: 4px; font-size: 12px;"></i>
</button>
<div class="upload-dropdown-menu" id="upload-dropdown-menu">
<button class="upload-dropdown-item" id="upload-files-btn">
<i class="fas fa-file"></i>
<span data-i18n="actions.upload_files">Upload files</span>
</button>
<button class="upload-dropdown-item" id="upload-folder-btn">
<i class="fas fa-folder-open"></i>
<span data-i18n="actions.upload_folder">Upload folder</span>
</button>
</div>
</div>
<button class="btn btn-secondary" id="new-folder-btn">
<i class="fas fa-folder-plus"></i>
<span data-i18n="actions.new_folder">New folder</span>
</button>
</div>
<div class="view-toggle">
<button class="toggle-btn active" id="grid-view-btn" title="Grid view">
<i class="fas fa-th"></i>
</button>
<button class="toggle-btn" id="list-view-btn" title="List view">
<i class="fas fa-list"></i>
</button>
</div>
</div>
<div class="dropzone" id="dropzone">
<i class="fas fa-cloud-upload-alt" style="font-size: 32px; margin-bottom: 10px;"></i>
<p data-i18n="dropzone.drag_files">Drag files here or click to select</p>
<input type="file" id="file-input" style="display: none;" multiple>
<input type="file" id="folder-input" style="display: none;" webkitdirectory directory multiple>
<div class="upload-progress">
<div class="progress-bar">
<div class="progress-fill"></div>
</div>
</div>
</div>
<div class="breadcrumb">
<span class="breadcrumb-item" data-i18n="breadcrumb.home">Home</span>
</div>
<!-- Files Container -->
<div class="files-container">
<!-- Grid View -->
<div class="files-grid" id="files-grid">
<!-- Files will be populated here -->
</div>
<!-- List View (hidden by default) -->
<div class="files-list-view" id="files-list-view" style="display: none;">
<div class="list-header">
<div class="list-header-checkbox"><input type="checkbox" id="select-all-checkbox" title="Select all"></div>
<div data-i18n="files.name">Name</div>
<div data-i18n="files.type">Type</div>
<div data-i18n="files.size">Size</div>
<div data-i18n="files.modified">Modified</div>
</div>
<!-- Files will be populated here in list view -->
</div>
</div>
</div>
</div>
<!-- Input Modal (for New Folder, Rename, etc.) -->
<div id="input-modal" class="modal-overlay">
<div class="modal-container">
<div class="modal-header">
<div class="modal-icon">
<i id="modal-icon" class="fas fa-folder-plus"></i>
</div>
<h3 id="modal-title" data-i18n="dialogs.new_folder_title">New folder</h3>
<button class="modal-close-btn" id="modal-close-btn">
<i class="fas fa-times"></i>
</button>
</div>
<div class="modal-body">
<label id="modal-label" for="modal-input" data-i18n="dialogs.folder_name">Folder name</label>
<input type="text" id="modal-input" class="modal-input" placeholder="" autocomplete="off">
</div>
<div class="modal-footer">
<button class="btn btn-secondary" id="modal-cancel-btn" data-i18n="actions.cancel">Cancel</button>
<button class="btn btn-primary" id="modal-confirm-btn" data-i18n="actions.confirm">Create</button>
</div>
</div>
</div>
<!-- About Modal -->
<div class="about-modal-overlay" id="about-modal-overlay">
<div class="about-modal">
<div class="about-modal-logo">
<i class="fas fa-cloud"></i>
</div>
<h2>OxiCloud</h2>
<div class="about-version" id="about-version">v...</div>
<div class="about-description" data-i18n="user_menu.about_description">
Cloud storage platform built with Rust &amp; Clean Architecture. Fast, secure, and private.
</div>
<div class="about-tech">
<span class="about-tech-badge">Rust</span>
<span class="about-tech-badge">Axum</span>
<span class="about-tech-badge">PostgreSQL</span>
<span class="about-tech-badge">Clean Architecture</span>
</div>
<div class="about-links">
<a href="https://github.com" class="about-link" target="_blank" rel="noopener">
<i class="fab fa-github"></i> GitHub
</a>
<a href="#" class="about-link" id="about-license-link">
<i class="fas fa-file-alt"></i> MIT License
</a>
</div>
<button class="about-close-btn" id="about-close-btn" data-i18n="actions.close">Close</button>
</div>
</div>
<!-- Upload Progress Toast (hidden – driven by notification bell) -->
<div class="upload-toast" id="upload-toast" style="display:none"></div>
</body>
</html>