diff --git a/static/css/components/filesView.css b/static/css/components/filesView.css index 523dc6b8..5ac0c7ee 100644 --- a/static/css/components/filesView.css +++ b/static/css/components/filesView.css @@ -61,6 +61,10 @@ background-color: var(--color-warning-ring); } +.file-badge-shared { + color: var(--color-badge-blue-text); +} + /* ------------------File list View --------------------- */ .list-header { @@ -95,7 +99,7 @@ } .files-list-view { - --files-list-columns: 36px minmax(200px, 2fr) 100px 110px 130px 64px; + --files-list-columns: 36px minmax(200px, 2fr) 100px 110px 130px 72px; display: flex; flex-direction: column; width: 100%; @@ -174,7 +178,8 @@ text-align: right; } -.files-list-view .file-item .action-cell button { +.files-list-view .file-item .action-cell button, +.files-list-view .file-item .action-cell div { display: inline; width: 28px; height: 28px; @@ -327,6 +332,25 @@ border: 2px dashed var(--color-warning-border); } +.files-grid-view .file-item .file-badge-shared { + position: absolute; + top: 38px; + right: 38px; + width: 30px; + height: 30px; + border-radius: 8px; + border: none; + background: transparent; + display: flex; + align-items: center; + justify-content: center; + + z-index: 12; + font-size: 15px; + padding: 0; + line-height: 1; +} + /* Favorite star (mirrors file-actions button pattern) */ .files-grid-view .file-item button.favorite-star { position: absolute; diff --git a/static/index.html b/static/index.html index dc5264b6..d0000a75 100644 --- a/static/index.html +++ b/static/index.html @@ -70,7 +70,7 @@
${i18n ? i18n.t('files.file_types.folder') : 'Folder'}
--
${formattedDate}
- - + +
`; @@ -1247,6 +1250,7 @@ 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 el = document.createElement('div'); el.className = 'file-item'; @@ -1265,6 +1269,8 @@ const ui = { ${escapeHtml(file.name)} ${isFav ? '' : ''} + ${isShared ? '
' : ''} +
${typeLabel}
${fileSize}
diff --git a/static/js/views/shared/sharedView.js b/static/js/views/shared/sharedView.js index 55ab6911..3b2d9c52 100644 --- a/static/js/views/shared/sharedView.js +++ b/static/js/views/shared/sharedView.js @@ -10,9 +10,17 @@ import { formatDateShort } from '../../core/formatters.js'; import { i18n } from '../../core/i18n.js'; import { fileSharing } from '../../features/sharing/fileSharing.js'; +const TTL = 5 * 60 * 1000; // 5 min + const sharedView = { // State items: [], + + _expires: 0, + + /** @type {Map} key = "file:" | "folder:" */ + _knownItemsId: new Map(), + filteredItems: [], currentItem: null, @@ -23,15 +31,15 @@ const sharedView = { return h; }, - init() { + async init() { console.log('Initializing shared view component (API-backed)'); - this.loadItems(); + await this.loadItems(); }, show() { this.displayUI(); this.attachEventListeners(); - this.loadItems().then(() => this.filterAndSortItems()); + this.filterAndSortItems(); const c = document.getElementById('shared-container'); if (c) c.classList.remove('hidden'); }, @@ -41,8 +49,27 @@ const sharedView = { if (c) c.classList.add('hidden'); }, - // Load shared items from backend API - async loadItems() { + /** + * tells if item_id is shared + * + * @param {string} id the item_id + * @param {string} type folder|file + * @returns {boolean} true if this item is shared + */ + isShared(id, type) { + return this._knownItemsId.has(`${type}:${id}`); + }, + + // Load shared items from backend API, + // TODO cache entries to minimize calls + /** + * load shared items + * + * @param {boolean} force ignore cache + */ + async loadItems(force) { + if (this._expires > Date.now() && !force) return; + try { const res = await fetch('/api/shares?page=1&per_page=1000', { headers: this._headers() @@ -53,11 +80,16 @@ const sharedView = { } else { this.items = []; } + this.filteredItems = { ...this.items }; + this._knownItemsId.clear(); + this.items.forEach((item) => { + this._knownItemsId.set(`${item.item_type}:${item.item_id}`, true); + }); + this._expires = Date.now() + TTL; } catch (err) { console.error('Error loading shared items:', err); this.items = []; } - this.filteredItems = [...this.items]; }, // Create and display the shared view UI @@ -559,7 +591,7 @@ const sharedView = { } this.closeShareDialog(); - await this.loadItems(); + await this.loadItems(true); this.filterAndSortItems(); }, @@ -580,7 +612,7 @@ const sharedView = { } this.closeShareDialog(); - await this.loadItems(); + await this.loadItems(true); this.filterAndSortItems(); }, diff --git a/static/sw.js b/static/sw.js index d7053aab..bad30e9b 100644 --- a/static/sw.js +++ b/static/sw.js @@ -1,5 +1,6 @@ // OxiCloud Service Worker -const CACHE_NAME = 'oxicloud-cache-v17'; +// FIXME: generate cache name according build ? +const CACHE_NAME = 'oxicloud-cache-v19'; // Only cache static assets — NOT HTML files. // HTML files are served network-first so browsers always get the latest