feat: add breadcrumb navigation and refactor SPA view management
- Add breadcrumb navigation with folder hierarchy display - Create setCurrentSection() helper to centralize view state management - Derive nav item section dynamically from DOM data-i18n attribute - Remove remnant /shared page and consolidate to SPA sharedView - Add search input to sharedView for client-side filtering - Fix 'Go to Files' button to use switchToFilesView()
This commit is contained in:
+2
-35
@@ -467,41 +467,8 @@ function setupEventListeners() {
|
||||
// Load trash items
|
||||
window.loadTrashItems();
|
||||
} else {
|
||||
// Check if we need to reset shared view
|
||||
if (app.isSharedView) {
|
||||
// Hide shared view
|
||||
if (window.sharedView) {
|
||||
window.sharedView.hide();
|
||||
}
|
||||
|
||||
// Reset shared view flag
|
||||
app.isSharedView = false;
|
||||
|
||||
// Clean up shared containers if they exist
|
||||
const sharedContainer = document.getElementById('shared-container');
|
||||
if (sharedContainer) {
|
||||
sharedContainer.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Show regular files view
|
||||
app.isTrashView = false;
|
||||
app.currentSection = 'files';
|
||||
|
||||
// Reset UI
|
||||
elements.pageTitle.textContent = window.i18n ? window.i18n.t('nav.files') : 'Files';
|
||||
setActionsBarMode('files');
|
||||
|
||||
// Show files containers
|
||||
const filesGrid = document.getElementById('files-grid');
|
||||
const filesListView = document.getElementById('files-list-view');
|
||||
if (filesGrid) filesGrid.style.display = app.currentView === 'grid' ? 'grid' : 'none';
|
||||
if (filesListView) filesListView.style.display = app.currentView === 'list' ? 'block' : 'none';
|
||||
|
||||
// Load regular files
|
||||
app.currentPath = '';
|
||||
ui.updateBreadcrumb('');
|
||||
window.loadFiles();
|
||||
// Use the proper switchToFilesView function which handles all UI restoration
|
||||
window.switchToFilesView();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
+78
-110
@@ -3,35 +3,71 @@
|
||||
* Extracted from main.js to keep navigation concerns isolated.
|
||||
*/
|
||||
|
||||
function switchToSharedView() {
|
||||
window.app.isTrashView = false;
|
||||
window.app.isSharedView = true;
|
||||
window.app.currentSection = 'shared';
|
||||
// Mapping of section names to their corresponding view flags
|
||||
const VIEW_FLAGS = {
|
||||
'files': 'isFilesView',
|
||||
'shared': 'isSharedView',
|
||||
'recent': 'isRecentView',
|
||||
'favorites': 'isFavoritesView',
|
||||
'trash': 'isTrashView'
|
||||
};
|
||||
|
||||
window.appElements.navItems.forEach(navItem => navItem.classList.remove('active'));
|
||||
/**
|
||||
* Derive section name from nav item's data-i18n attribute.
|
||||
* @param {HTMLElement} navItem - The nav item element
|
||||
* @returns {string|null} - Section name or null if not found
|
||||
*/
|
||||
function getSectionFromNavItem(navItem) {
|
||||
const i18nKey = navItem.querySelector('span[data-i18n]')?.getAttribute('data-i18n');
|
||||
return i18nKey ? i18nKey.replace('nav.', '') : null;
|
||||
}
|
||||
|
||||
const sharedNavItem = document.querySelector('.nav-item:nth-child(2)');
|
||||
if (sharedNavItem) {
|
||||
sharedNavItem.classList.add('active');
|
||||
/**
|
||||
* Set the current active section, updating all view flags and nav UI.
|
||||
* @param {string} section - The section to activate ('files', 'shared', 'recent', 'favorites', 'trash')
|
||||
*/
|
||||
function setCurrentSection(section) {
|
||||
// Set all view flags - true for active section, false for others
|
||||
Object.entries(VIEW_FLAGS).forEach(([key, flag]) => {
|
||||
window.app[flag] = (key === section);
|
||||
});
|
||||
|
||||
window.app.currentSection = section;
|
||||
|
||||
// Update nav item active classes by finding matching item from DOM
|
||||
window.appElements.navItems.forEach(item => {
|
||||
const itemSection = getSectionFromNavItem(item);
|
||||
item.classList.toggle('active', itemSection === section);
|
||||
});
|
||||
|
||||
// Update page title
|
||||
const titleKey = `nav.${section}`;
|
||||
const defaultTitle = section.charAt(0).toUpperCase() + section.slice(1);
|
||||
window.appElements.pageTitle.textContent = window.i18n ? window.i18n.t(titleKey) : defaultTitle;
|
||||
window.appElements.pageTitle.setAttribute('data-i18n', titleKey);
|
||||
|
||||
// Hide sharedView when switching to any other section
|
||||
if (section !== 'shared' && window.sharedView) {
|
||||
window.sharedView.hide();
|
||||
}
|
||||
}
|
||||
|
||||
window.appElements.pageTitle.textContent = window.i18n ? window.i18n.t('nav.shared') : 'Shared';
|
||||
window.appElements.pageTitle.setAttribute('data-i18n', 'nav.shared');
|
||||
|
||||
window.ui.updateBreadcrumb('');
|
||||
function switchToSharedView() {
|
||||
setCurrentSection('shared');
|
||||
|
||||
// Hide breadcrumb (only shown in Files view)
|
||||
const breadcrumb = document.querySelector('.breadcrumb');
|
||||
if (breadcrumb) breadcrumb.style.display = 'none';
|
||||
|
||||
if (window.appElements.actionsBar) {
|
||||
window.appElements.actionsBar.style.display = 'none';
|
||||
}
|
||||
// Hide actions-bar for shared view
|
||||
window.setActionsBarMode('hidden');
|
||||
|
||||
const filesGrid = document.getElementById('files-grid');
|
||||
const filesListView = document.getElementById('files-list-view');
|
||||
if (filesGrid) filesGrid.style.display = 'none';
|
||||
if (filesListView) filesListView.style.display = 'none';
|
||||
|
||||
// Show shared view
|
||||
if (window.sharedView) {
|
||||
window.sharedView.init();
|
||||
window.sharedView.show();
|
||||
@@ -39,92 +75,47 @@ function switchToSharedView() {
|
||||
}
|
||||
|
||||
function switchToFilesView() {
|
||||
window.app.isTrashView = false;
|
||||
window.app.isSharedView = false;
|
||||
window.app.isFavoritesView = false;
|
||||
window.app.isRecentView = false;
|
||||
window.app.currentSection = 'files';
|
||||
setCurrentSection('files');
|
||||
|
||||
window.appElements.pageTitle.textContent = window.i18n ? window.i18n.t('nav.files') : 'Files';
|
||||
window.appElements.pageTitle.setAttribute('data-i18n', 'nav.files');
|
||||
// Set actions bar mode
|
||||
window.setActionsBarMode('files', true);
|
||||
|
||||
// Show breadcrumb (only in Files view)
|
||||
const breadcrumb = document.querySelector('.breadcrumb');
|
||||
if (breadcrumb) breadcrumb.style.display = '';
|
||||
|
||||
window.appElements.navItems.forEach(navItem => navItem.classList.remove('active'));
|
||||
|
||||
const filesNavItem = document.querySelector('.nav-item:first-child');
|
||||
if (filesNavItem) {
|
||||
filesNavItem.classList.add('active');
|
||||
}
|
||||
|
||||
window.setActionsBarMode('files');
|
||||
|
||||
if (window.sharedView) {
|
||||
window.sharedView.hide();
|
||||
}
|
||||
|
||||
const filesGrid = document.getElementById('files-grid');
|
||||
if (filesGrid) {
|
||||
filesGrid.style.display = window.app.currentView === 'grid' ? 'grid' : 'none';
|
||||
}
|
||||
|
||||
const filesListView = document.getElementById('files-list-view');
|
||||
if (filesListView) {
|
||||
filesListView.style.display = window.app.currentView === 'list' ? 'block' : 'none';
|
||||
}
|
||||
if (filesGrid) filesGrid.style.display = window.app.currentView === 'grid' ? 'grid' : 'none';
|
||||
if (filesListView) filesListView.style.display = window.app.currentView === 'list' ? 'block' : 'none';
|
||||
|
||||
// Reset to home folder and update breadcrumb
|
||||
window.app.currentPath = window.app.userHomeFolderId || '';
|
||||
window.app.breadcrumbPath = [];
|
||||
window.ui.updateBreadcrumb();
|
||||
|
||||
if (window.app.userHomeFolderId) {
|
||||
window.app.currentPath = window.app.userHomeFolderId;
|
||||
window.app.breadcrumbPath = [];
|
||||
window.ui.updateBreadcrumb();
|
||||
} else {
|
||||
window.app.currentPath = '';
|
||||
}
|
||||
window.loadFiles();
|
||||
}
|
||||
|
||||
function switchToFavoritesView() {
|
||||
window.app.isTrashView = false;
|
||||
window.app.isSharedView = false;
|
||||
|
||||
window.app.isFavoritesView = true;
|
||||
window.app.currentSection = 'favorites';
|
||||
|
||||
window.appElements.navItems.forEach(navItem => navItem.classList.remove('active'));
|
||||
|
||||
const favoritesNavItem = document.querySelector('.nav-item:nth-child(4)');
|
||||
if (favoritesNavItem) {
|
||||
favoritesNavItem.classList.add('active');
|
||||
}
|
||||
|
||||
window.appElements.pageTitle.textContent = window.i18n ? window.i18n.t('nav.favorites') : 'Favorites';
|
||||
window.appElements.pageTitle.setAttribute('data-i18n', 'nav.favorites');
|
||||
|
||||
window.ui.updateBreadcrumb('');
|
||||
|
||||
if (window.sharedView) {
|
||||
window.sharedView.hide();
|
||||
}
|
||||
setCurrentSection('favorites');
|
||||
|
||||
// Set actions bar mode
|
||||
window.setActionsBarMode('favorites');
|
||||
|
||||
// Hide breadcrumb (only shown in Files view)
|
||||
const breadcrumb = document.querySelector('.breadcrumb');
|
||||
if (breadcrumb) breadcrumb.style.display = 'none';
|
||||
|
||||
const filesGrid = document.getElementById('files-grid');
|
||||
const filesListView = document.getElementById('files-list-view');
|
||||
|
||||
if (filesGrid) {
|
||||
filesGrid.style.display = window.app.currentView === 'grid' ? 'grid' : 'none';
|
||||
}
|
||||
|
||||
if (filesListView) {
|
||||
filesListView.style.display = window.app.currentView === 'list' ? 'block' : 'none';
|
||||
}
|
||||
if (filesGrid) filesGrid.style.display = window.app.currentView === 'grid' ? 'grid' : 'none';
|
||||
if (filesListView) filesListView.style.display = window.app.currentView === 'list' ? 'block' : 'none';
|
||||
|
||||
if (window.favorites) {
|
||||
window.favorites.displayFavorites();
|
||||
} else {
|
||||
console.error('Favorites module not loaded or initialized');
|
||||
|
||||
const filesGridError = document.getElementById('files-grid');
|
||||
if (filesGridError) {
|
||||
filesGridError.innerHTML = `
|
||||
@@ -138,47 +129,24 @@ function switchToFavoritesView() {
|
||||
}
|
||||
|
||||
function switchToRecentFilesView() {
|
||||
window.app.isTrashView = false;
|
||||
window.app.isSharedView = false;
|
||||
window.app.isFavoritesView = false;
|
||||
|
||||
window.app.isRecentView = true;
|
||||
window.app.currentSection = 'recent';
|
||||
|
||||
window.appElements.navItems.forEach(navItem => navItem.classList.remove('active'));
|
||||
|
||||
const recentNavItem = document.querySelector('.nav-item:nth-child(3)');
|
||||
if (recentNavItem) {
|
||||
recentNavItem.classList.add('active');
|
||||
}
|
||||
|
||||
window.appElements.pageTitle.textContent = window.i18n ? window.i18n.t('nav.recent') : 'Recent';
|
||||
window.appElements.pageTitle.setAttribute('data-i18n', 'nav.recent');
|
||||
|
||||
window.ui.updateBreadcrumb('');
|
||||
|
||||
if (window.sharedView) {
|
||||
window.sharedView.hide();
|
||||
}
|
||||
setCurrentSection('recent');
|
||||
|
||||
// Set actions bar mode
|
||||
window.setActionsBarMode('recent');
|
||||
|
||||
// Hide breadcrumb (only shown in Files view)
|
||||
const breadcrumb = document.querySelector('.breadcrumb');
|
||||
if (breadcrumb) breadcrumb.style.display = 'none';
|
||||
|
||||
const filesGrid = document.getElementById('files-grid');
|
||||
const filesListView = document.getElementById('files-list-view');
|
||||
|
||||
if (filesGrid) {
|
||||
filesGrid.style.display = window.app.currentView === 'grid' ? 'grid' : 'none';
|
||||
}
|
||||
|
||||
if (filesListView) {
|
||||
filesListView.style.display = window.app.currentView === 'list' ? 'block' : 'none';
|
||||
}
|
||||
if (filesGrid) filesGrid.style.display = window.app.currentView === 'grid' ? 'grid' : 'none';
|
||||
if (filesListView) filesListView.style.display = window.app.currentView === 'list' ? 'block' : 'none';
|
||||
|
||||
if (window.recent) {
|
||||
window.recent.displayRecentFiles();
|
||||
} else {
|
||||
console.error('Recent files module not loaded or initialized');
|
||||
|
||||
const filesGridError = document.getElementById('files-grid');
|
||||
if (filesGridError) {
|
||||
filesGridError.innerHTML = `
|
||||
|
||||
@@ -11,6 +11,7 @@ window.app = {
|
||||
contextMenuTargetFile: null,
|
||||
selectedTargetFolderId: '',
|
||||
moveDialogMode: 'file',
|
||||
isFilesView: true,
|
||||
isTrashView: false,
|
||||
isSharedView: false,
|
||||
isFavoritesView: false,
|
||||
@@ -20,6 +21,8 @@ window.app = {
|
||||
shareDialogItem: null,
|
||||
shareDialogItemType: null,
|
||||
notificationShareUrl: null,
|
||||
userHomeFolderId: null,
|
||||
userHomeFolderName: null,
|
||||
breadcrumbPath: [] // Array of {id, name} tracking folder navigation hierarchy
|
||||
};
|
||||
|
||||
|
||||
+56
-30
@@ -129,7 +129,7 @@ const ui = {
|
||||
`;
|
||||
document.body.appendChild(moveDialog);
|
||||
}
|
||||
|
||||
|
||||
// Share dialog
|
||||
if (!document.getElementById('share-dialog')) {
|
||||
const shareDialog = document.createElement('div');
|
||||
@@ -144,25 +144,25 @@ const ui = {
|
||||
<div class="shared-item-info">
|
||||
<strong>Item:</strong> <span id="shared-item-name"></span>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="existing-shares-section" style="display:none; margin: 15px 0;">
|
||||
<h3 data-i18n="dialogs.existing_shares">Existing shared links</h3>
|
||||
<div id="existing-shares-container"></div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="share-options">
|
||||
<h3 data-i18n="dialogs.share_options">Share options</h3>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="share-password" data-i18n="dialogs.password">Password (optional):</label>
|
||||
<input type="password" id="share-password" placeholder="Protect with password">
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="share-expiration" data-i18n="dialogs.expiration">Expiration date (optional):</label>
|
||||
<input type="date" id="share-expiration">
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label data-i18n="dialogs.permissions">Permissions:</label>
|
||||
<div class="permission-options">
|
||||
@@ -181,7 +181,7 @@ const ui = {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="new-share-section" style="display:none; margin: 15px 0;">
|
||||
<h3 data-i18n="dialogs.generated_link">Generated link</h3>
|
||||
<div class="form-group">
|
||||
@@ -196,7 +196,7 @@ const ui = {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="share-dialog-buttons">
|
||||
<button class="btn btn-secondary" id="share-cancel-btn" data-i18n="actions.cancel">Cancel</button>
|
||||
<button class="btn btn-primary" id="share-confirm-btn" data-i18n="actions.share">Share</button>
|
||||
@@ -204,27 +204,27 @@ const ui = {
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(shareDialog);
|
||||
|
||||
|
||||
// Add event listeners for share dialog
|
||||
document.getElementById('share-cancel-btn').addEventListener('click', () => {
|
||||
contextMenus.closeShareDialog();
|
||||
});
|
||||
|
||||
|
||||
document.getElementById('share-confirm-btn').addEventListener('click', async () => {
|
||||
await contextMenus.createSharedLink();
|
||||
});
|
||||
|
||||
|
||||
document.getElementById('copy-share-btn').addEventListener('click', async () => {
|
||||
const shareUrl = document.getElementById('generated-share-url').value;
|
||||
await fileSharing.copyLinkToClipboard(shareUrl);
|
||||
});
|
||||
|
||||
|
||||
document.getElementById('notify-share-btn').addEventListener('click', () => {
|
||||
const shareUrl = document.getElementById('generated-share-url').value;
|
||||
contextMenus.showEmailNotificationDialog(shareUrl);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Notification dialog
|
||||
if (!document.getElementById('notification-dialog')) {
|
||||
const notificationDialog = document.createElement('div');
|
||||
@@ -236,19 +236,19 @@ const ui = {
|
||||
<i class="fas fa-envelope" style="color:#ff5e3a"></i>
|
||||
<span data-i18n="dialogs.notify">Notify shared link</span>
|
||||
</div>
|
||||
|
||||
|
||||
<p><strong>URL:</strong> <span id="notification-share-url"></span></p>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="notification-email" data-i18n="dialogs.recipient">Recipient:</label>
|
||||
<input type="email" id="notification-email" placeholder="Email address">
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="notification-message" data-i18n="dialogs.message">Message (optional):</label>
|
||||
<textarea id="notification-message" rows="3"></textarea>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="share-dialog-buttons">
|
||||
<button class="btn btn-secondary" id="notification-cancel-btn" data-i18n="actions.cancel">Cancel</button>
|
||||
<button class="btn btn-primary" id="notification-send-btn" data-i18n="actions.send">Send</button>
|
||||
@@ -256,12 +256,12 @@ const ui = {
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(notificationDialog);
|
||||
|
||||
|
||||
// Add event listeners for notification dialog
|
||||
document.getElementById('notification-cancel-btn').addEventListener('click', () => {
|
||||
contextMenus.closeNotificationDialog();
|
||||
});
|
||||
|
||||
|
||||
document.getElementById('notification-send-btn').addEventListener('click', () => {
|
||||
contextMenus.sendShareNotification();
|
||||
});
|
||||
@@ -495,24 +495,50 @@ const ui = {
|
||||
return window.i18n.t(key);
|
||||
};
|
||||
|
||||
// -- Home item (always present) --
|
||||
const homeItem = document.createElement('span');
|
||||
homeItem.className = 'breadcrumb-item';
|
||||
homeItem.textContent = getTranslatedText('breadcrumb.home', 'Home');
|
||||
// -- Home icon (always present, clickable to go to root) --
|
||||
const homeIcon = document.createElement('span');
|
||||
homeIcon.className = 'breadcrumb-item breadcrumb-home';
|
||||
homeIcon.innerHTML = '<i class="fas fa-home"></i>';
|
||||
homeIcon.title = getTranslatedText('breadcrumb.home', 'Home');
|
||||
|
||||
// If we have deeper segments, Home is clickable
|
||||
if (path.length > 0 && window.app.userHomeFolderId) {
|
||||
homeItem.classList.add('breadcrumb-link');
|
||||
homeItem.addEventListener('click', () => {
|
||||
// Home is always clickable if we have a home folder
|
||||
if (window.app.userHomeFolderId) {
|
||||
homeIcon.classList.add('breadcrumb-link');
|
||||
homeIcon.addEventListener('click', () => {
|
||||
window.app.breadcrumbPath = [];
|
||||
window.app.currentPath = window.app.userHomeFolderId;
|
||||
self.updateBreadcrumb();
|
||||
window.loadFiles();
|
||||
});
|
||||
} else {
|
||||
homeItem.classList.add('breadcrumb-current');
|
||||
}
|
||||
breadcrumb.appendChild(homeItem);
|
||||
breadcrumb.appendChild(homeIcon);
|
||||
|
||||
// -- Root/Home folder name (if available) --
|
||||
if (window.app.userHomeFolderName) {
|
||||
const separator1 = document.createElement('span');
|
||||
separator1.className = 'breadcrumb-separator';
|
||||
separator1.textContent = '>';
|
||||
breadcrumb.appendChild(separator1);
|
||||
|
||||
const rootFolderItem = document.createElement('span');
|
||||
rootFolderItem.className = 'breadcrumb-item';
|
||||
rootFolderItem.textContent = window.app.userHomeFolderName;
|
||||
|
||||
// If we're at the home folder level (no deeper navigation), show as current
|
||||
if (path.length === 0) {
|
||||
rootFolderItem.classList.add('breadcrumb-current');
|
||||
} else {
|
||||
// Otherwise clickable to go back to home
|
||||
rootFolderItem.classList.add('breadcrumb-link');
|
||||
rootFolderItem.addEventListener('click', () => {
|
||||
window.app.breadcrumbPath = [];
|
||||
window.app.currentPath = window.app.userHomeFolderId;
|
||||
self.updateBreadcrumb();
|
||||
window.loadFiles();
|
||||
});
|
||||
}
|
||||
breadcrumb.appendChild(rootFolderItem);
|
||||
}
|
||||
|
||||
// -- Intermediate + current segments --
|
||||
path.forEach((segment, index) => {
|
||||
|
||||
Reference in New Issue
Block a user