feat(ui): add section 'Shared with me'

This commit is contained in:
Edouard Vanbelle
2026-05-22 10:15:25 +02:00
parent 143b13d7bc
commit 60d53ea779
26 changed files with 816 additions and 56 deletions
+24 -7
View File
@@ -42,7 +42,17 @@ async function getFolder(id) {
}
/**
* rebuild breadcrumb from selected folder (iterate up to root)
* Rebuild breadcrumb from selected folder (iterate up to root).
*
* Stops traversal gracefully when a parent folder is not accessible
* (e.g. the user entered via a "Shared with me" grant whose parent
* folder they have no permission on). In that case the partial
* breadcrumb built so far is kept — the deepest reachable ancestor
* acts as the visual root, matching how Google Drive / Dropbox handle
* shared subtrees.
*
* An error on the *target folder itself* (first iteration) is still
* treated as a real error and redirects to the home folder.
*/
async function rebuildBreadCrumb() {
/**
@@ -80,12 +90,19 @@ async function rebuildBreadCrumb() {
// iterate to parent folder
id = folderInfo.parent_id;
} catch (_e) {
console.log(`Error loading information from folder ${app.currentPath}, falling back to ${app.userHomeFolderId}`);
// fallback of root
uiNotifications.show('error: folder not found or permission denied', 'the given folder is not available or you do not have sufficient rights');
app.breadcrumbPath = [];
id = app.userHomeFolderId;
if (id) app.currentPath = id;
if (currentFolderInfo === null) {
// Failed on the target folder itself — real error, fall back to home.
console.warn(`Cannot access target folder ${app.currentPath}, falling back to home`);
uiNotifications.show('error: folder not found or permission denied', 'the given folder is not available or you do not have sufficient rights');
app.breadcrumbPath = [];
id = app.userHomeFolderId;
if (id) app.currentPath = id;
} else {
// Failed on a parent — hit the permission boundary of a shared subtree.
// Stop traversal; the partial breadcrumb is the best we can show.
console.log(`Stopped breadcrumb traversal at permission boundary (parent of ${currentFolderInfo.id} is not accessible)`);
break;
}
}
}
+21 -1
View File
@@ -16,10 +16,12 @@ import { multiSelect } from '../features/files/multiSelect.js';
import { favorites } from '../features/library/favorites.js';
import { recent } from '../features/library/recent.js';
import { fileSharing } from '../features/sharing/fileSharing.js';
import { grants } from '../model/grants.js';
import { sharedView } from '../views/shared/sharedView.js';
import { checkAuthentication } from './authSession.js';
import { loadFiles } from './filesView.js';
import {
activateFilesUI,
SECTIONS_MAPPER,
switchToFavoritesSection,
switchToFilesSection,
@@ -27,6 +29,7 @@ import {
switchToPhotosSection,
switchToRecentFilesSection,
switchToSharedSection,
switchToSharedWithMeSection,
switchToTrashSection
} from './navigation.js';
import { performSearch } from './searchView.js';
@@ -140,12 +143,16 @@ const ACTIONS_BAR_TEMPLATES = {
</div>
${_multiSelectButons}
${_toggleButtons}
`,
sharedwithme: `
<div class="action-buttons" id="default-buttons"></div>
${_toggleButtons}
`
};
/**
*
* @param {'files' | 'trash' | 'favorites' | 'recent' | 'hidden'} mode
* @param {'files' | 'trash' | 'favorites' | 'recent' | 'sharedwithme' | 'hidden'} mode
* @param {boolean} [force=false]
* @returns
*/
@@ -398,6 +405,9 @@ function initApp() {
app.viewFile = hashContext.file;
}
// get grants (xxx: async methods)
await grants.fetchIncomingGrants();
await grants.fetchOutgoingGrants();
loadFiles();
}
});
@@ -652,6 +662,10 @@ function setupEventListeners() {
switchToSharedSection();
break;
case 'nav.sharedwithme':
switchToSharedWithMeSection();
break;
case 'nav.favorites':
// Switch to favorites view
switchToFavoritesSection();
@@ -723,6 +737,12 @@ function setupEventListeners() {
* @param {string} name
*/
export function selectFolder(id, name) {
// When entering from a non-files section (e.g. "Shared with me"),
// activate the Files UI (nav active state, breadcrumb, action bar,
// container) without resetting the current path.
if (app.currentSection !== 'files') {
activateFilesUI();
}
app.breadcrumbPath.push({ id, name });
app.currentPath = id;
ui.updateBreadcrumb();
+48
View File
@@ -10,6 +10,7 @@ import { musicView } from '../features/library/music.js';
import { photosView } from '../features/library/photos.js';
import { recent } from '../features/library/recent.js';
import { sharedView } from '../views/shared/sharedView.js';
import { sharedWithMeView } from '../views/sharedWithMe/sharedWithMeView.js';
import { loadFiles } from './filesView.js';
import { setActionsBarMode } from './main.js';
import { app, appElements } from './state.js';
@@ -122,6 +123,7 @@ function getSectionFromNavItem(navItem) {
export const SECTIONS_MAPPER = {
files: switchToFilesSection,
shared: switchToSharedSection,
sharedwithme: switchToSharedWithMeSection,
recent: switchToRecentFilesSection,
favorites: switchToFavoritesSection,
trash: switchToTrashSection,
@@ -157,6 +159,11 @@ function setCurrentSection(section) {
sharedView.hide();
}
// Hide "Load more" button when leaving the sharedwithme section
if (section !== 'sharedwithme' && sharedWithMeView) {
sharedWithMeView.hide();
}
// Hide photosView when switching to any other section
if (section !== 'photos' && photosView) {
photosView.hide();
@@ -194,6 +201,26 @@ function switchToSharedSection() {
if (multiSelect) multiSelect.clear();
}
function switchToSharedWithMeSection() {
if (!setCurrentSection('sharedwithme')) return;
// Hide breadcrumb (only shown in Files view)
const breadcrumb = document.querySelector('.breadcrumb');
breadcrumb?.classList.add('hidden');
// Show actions-bar with view toggle (no upload / new-folder in this view)
setActionsBarMode('sharedwithme');
// Show the standard files container and respect grid/list preference
toggleFileContainer(true);
syncViewContainers();
if (multiSelect) multiSelect.clear();
// Load and render items into the files container
sharedWithMeView.init();
}
function switchToFilesSection() {
if (!setCurrentSection('files')) return;
@@ -368,13 +395,34 @@ function switchToMusicSection() {
if (multiSelect) multiSelect.clear();
}
/**
* Activate the Files section UI (nav state, breadcrumb, actions bar,
* files container, grid/list sync) WITHOUT resetting `app.currentPath`
* or `app.breadcrumbPath`.
*
* Used by `selectFolder` when the user clicks a folder from a
* non-files section (e.g. "Shared with me") so the Files view is
* fully set up before the folder content loads.
*/
function activateFilesUI() {
setCurrentSection('files');
setActionsBarMode('files', true);
const breadcrumb = document.querySelector('.breadcrumb');
breadcrumb?.classList.remove('hidden');
toggleFileContainer(true);
syncViewContainers();
if (multiSelect) multiSelect.clear();
}
export {
activateFilesUI,
switchToFavoritesSection,
switchToFilesSection,
switchToMusicSection,
switchToPhotosSection,
switchToRecentFilesSection,
switchToSharedSection,
switchToSharedWithMeSection,
switchToTrashSection,
syncViewContainers
};
+15 -9
View File
@@ -17,10 +17,10 @@ import { favorites } from '../features/library/favorites.js';
import { recent } from '../features/library/recent.js';
import { fileSharing } from '../features/sharing/fileSharing.js';
import { thumbnail } from '../features/thumbnail.js';
import { sharedView } from '../views/shared/sharedView.js';
import { grants } from '../model/grants.js';
import { loadFiles } from './filesView.js';
import { updateHistory } from './main.js';
import { switchToFilesSection, syncViewContainers } from './navigation.js';
import { activateFilesUI, switchToFilesSection, syncViewContainers } from './navigation.js';
import { app } from './state.js';
import { uiFileTypes } from './uiFileTypes.js';
import { uiNotifications } from './uiNotifications.js';
@@ -54,7 +54,7 @@ const ui = {
<i class="fas fa-star"></i> <span data-i18n="actions.favorite">Add to favorites</span>
</div>
<div class="context-menu-item" id="share-folder-option">
<i class="fas fa-share-alt"></i> <span data-i18n="actions.share">Share</span>
<i class="fas fa-oxiexport"></i> <span data-i18n="actions.share">Share</span>
</div>
<div class="context-menu-separator"></div>
<div class="context-menu-item" id="rename-folder-option">
@@ -98,7 +98,7 @@ const ui = {
<i class="fas fa-star"></i> <span data-i18n="actions.favorite">Add to favorites</span>
</div>
<div class="context-menu-item" id="share-file-option">
<i class="fas fa-share-alt"></i> <span data-i18n="actions.share">Share</span>
<i class="fas fa-oxiexport"></i> <span data-i18n="actions.share">Share</span>
</div>
<div class="context-menu-item hidden" id="add-to-playlist-option">
<i class="fas fa-compact-disc"></i> <span data-i18n="music.add_to_playlist">Add to Playlist</span>
@@ -154,7 +154,7 @@ const ui = {
shareDialog.innerHTML = `
<div class="share-dialog-content">
<div class="share-dialog-header">
<i class="fas fa-share-alt dialog-header-icon"></i>
<i class="fas fa-oxiexport dialog-header-icon"></i>
<span data-i18n="dialogs.share_file">Share file</span>
</div>
<div class="shared-item-info">
@@ -937,6 +937,11 @@ const ui = {
loadFiles();
return;
}
if (app.currentSection === 'sharedwithme') {
// Activate Files UI (nav, breadcrumb, actions bar) without
// resetting the path — the shared folder becomes the entry point.
activateFilesUI();
}
app.breadcrumbPath.push({ id: folderId, name: folderName });
app.currentPath = folderId;
this.updateBreadcrumb();
@@ -1334,7 +1339,7 @@ const ui = {
if (folder.path) el.dataset.path = folder.path;
const isFav = favorites?.isFavorite(folder.id, 'folder');
const isShared = sharedView.isShared(folder.id, 'folder');
const isShared = grants.getOutgoingGrantsFor('folder', folder.id).length > 0; //sharedView.isShared(folder.id, 'folder');
const formattedDate = formatDateTime(folder.modified_at);
el.innerHTML = `
@@ -1345,7 +1350,7 @@ const ui = {
</div>
<span>${escapeHtml(folder.name)}</span>
<div class="file-badge file-badge-favorite ${isFav ? '' : 'hidden'}"><i class="fas fa-star favorite-star-inline"></i></div>
<div class="file-badge file-badge-shared ${isShared ? '' : 'hidden'}"><i class="fas fa-share-alt"></i></div>
<div class="file-badge file-badge-shared ${isShared ? '' : 'hidden'}"><i class="fas fa-oxiexport"></i></div>
</div>
<div class="type-cell">${i18n.t('files.file_types.folder')}</div>
<div class="size-cell">--</div>
@@ -1377,7 +1382,8 @@ const ui = {
const fileSize = file.size_formatted || formatFileSize(file.size);
const formattedDate = formatDateTime(file.modified_at);
const isFav = favorites?.isFavorite(file.id, 'file');
const isShared = sharedView.isShared(file.id, 'file');
const isShared = grants.getOutgoingGrantsFor('file', file.id).length > 0;
//const isShared = sharedView.isShared(file.id, 'file');
const canThumbnail = thumbnail.canHandle(file);
const el = document.createElement('div');
@@ -1398,7 +1404,7 @@ const ui = {
</div>
<span>${escapeHtml(file.name)}</span>
<div class="file-badge file-badge-favorite ${isFav ? '' : 'hidden'}"><i class="fas fa-star favorite-star-inline"></i></div>
<div class="file-badge file-badge-shared ${isShared ? '' : 'hidden'}"><i class="fas fa-share-alt"></i></div>
<div class="file-badge file-badge-shared ${isShared ? '' : 'hidden'}"><i class="fas fa-oxiexport"></i></div>
</div>
<div class="type-cell">${typeLabel}</div>
<div class="size-cell">${fileSize}</div>