From 744d2c88fb3586304a3fc026e3a1b97486d7e794 Mon Sep 17 00:00:00 2001 From: Edouard Vanbelle Date: Tue, 31 Mar 2026 18:35:34 +0200 Subject: [PATCH] refactor: simplify multiSelect module (remove duplicates, etc) - remove duplicate code - use one uniq selection bar (for both list & grid view) --- static/css/components/multiSelect.css | 6 +- static/index.html | 3 + static/js/app/main.js | 2 +- static/js/features/files/multiSelect.js | 171 +++++++----------------- 4 files changed, 57 insertions(+), 125 deletions(-) diff --git a/static/css/components/multiSelect.css b/static/css/components/multiSelect.css index 1b39ce1c..0416753c 100644 --- a/static/css/components/multiSelect.css +++ b/static/css/components/multiSelect.css @@ -34,7 +34,7 @@ min-width: 0; } -.batch-action-bar { +.batch-selection-bar { display: flex; align-items: center; justify-content: space-between; @@ -51,7 +51,7 @@ pointer-events: none; } -.batch-action-bar.visible { +.batch-selection-bar.visible { opacity: 1; max-height: 60px; transform: translateY(0); @@ -140,7 +140,7 @@ accent-color: #ff5e3a; } -[data-theme="dark"] .batch-action-bar { +[data-theme="dark"] .batch-selection-bar { background-color: #1e293b; border-color: #334155; color: #e2e8f0; diff --git a/static/index.html b/static/index.html index a9fdb626..a7e957c4 100644 --- a/static/index.html +++ b/static/index.html @@ -255,6 +255,8 @@ Home +
+
@@ -332,5 +334,6 @@ + diff --git a/static/js/app/main.js b/static/js/app/main.js index 55cef862..196575c9 100644 --- a/static/js/app/main.js +++ b/static/js/app/main.js @@ -653,7 +653,7 @@ function setupEventListeners() { // selection state; this handler only covers the legacy CSS class removal. // Skip if a rubber-band selection just finished — the click is a side-effect. if (window.__rubberBandJustFinished) return; - if (!e.target.closest('.file-card') && !e.target.closest('.file-item') && !e.target.closest('.context-menu') && !e.target.closest('.about-modal') && !e.target.closest('.batch-action-bar') && !e.target.closest('.list-header.selection-mode')) { + if (!e.target.closest('.file-card') && !e.target.closest('.file-item') && !e.target.closest('.context-menu') && !e.target.closest('.about-modal') && !e.target.closest('.batch-selection-bar') && !e.target.closest('.list-header.selection-mode')) { document.querySelectorAll('.file-card.selected').forEach(c => c.classList.remove('selected')); document.querySelectorAll('.file-item.selected').forEach(c => c.classList.remove('selected')); } diff --git a/static/js/features/files/multiSelect.js b/static/js/features/files/multiSelect.js index c2c14c80..cca75273 100644 --- a/static/js/features/files/multiSelect.js +++ b/static/js/features/files/multiSelect.js @@ -6,6 +6,9 @@ * provides batch delete / move / download / favorites operations. */ +// TODO: rename into selection-bar ? +// TODO: merge with photo part + const multiSelect = { /** Currently selected items: Map */ _selected: new Map(), @@ -16,9 +19,6 @@ const multiSelect = { /** Whether the selection bar is currently visible */ _barVisible: false, - /** Saved original list-header HTML so we can restore it */ - _savedHeaderHTML: '', - // ── Public API ────────────────────────────────────────── get count() { return this._selected.size; }, @@ -93,11 +93,14 @@ const multiSelect = { }, _getAllVisibleItems() { + return [...document.querySelectorAll('.file-item, .file-card')]; + /* const grid = document.getElementById('files-grid'); if (grid && grid.style.display !== 'none') { return [...grid.querySelectorAll('.file-card')]; } return [...document.querySelectorAll('#files-list-view .file-item')]; + */ }, _extractInfo(el) { @@ -151,146 +154,61 @@ const multiSelect = { * Build the inner HTML for the selection bar that replaces the * normal list-header columns (Name / Type / Size / Modified). */ - _buildSelectionBarHTML(n) { - const countText = n === 1 - ? (this._t('batch.one_selected') || '1 item selected') - : (this._t('batch.n_selected', { count: n }) || `${n} items selected`); - - const favLabel = this._t('batch.add_favorites') || 'Add to favorites'; - const moveLabel = this._t('batch.move_copy') || 'Move or copy'; - const dlLabel = this._t('actions.download') || 'Download'; - const delLabel = this._t('actions.delete') || 'Delete'; + _buildSelectionBarHTML() { + //FIXME: should support i18n lang change return ` -
- +
+ +
- ${countText}
- - - -
`; }, - /** Ensure the grid-view batch bar exists (shown only when grid is visible) */ - _ensureGridBar() { - if (document.getElementById('batch-grid-bar')) return; - const bar = document.createElement('div'); - bar.id = 'batch-grid-bar'; - bar.className = 'batch-action-bar'; // reuse same styles - const container = document.querySelector('.files-container'); - if (container) { - container.insertBefore(bar, container.firstChild); - } - }, - /** Main UI sync — called after every selection change */ _syncUI() { - const listHeader = document.querySelector('.list-header'); const n = this._selected.size; - // ── Save original header HTML on first use ── - if (listHeader && !this._savedHeaderHTML) { - this._savedHeaderHTML = listHeader.innerHTML; - } + const batchSelectionBar = document.getElementById('batch-selection-bar'); if (n > 0) { this._barVisible = true; - // ── List view: replace header with selection bar ── - if (listHeader) { - listHeader.classList.add('selection-mode'); - listHeader.innerHTML = this._buildSelectionBarHTML(n); + const countText = n === 1 + ? (this._t('batch.one_selected') || '1 item selected') + : (this._t('batch.n_selected', { count: n }) || `${n} items selected`); + document.getElementById("batch-bar-count").innerText = countText; - // Wire checkbox - const cb = document.getElementById('select-all-checkbox'); - if (cb) cb.addEventListener('change', () => this.toggleAll()); - - // Wire action buttons - this._wireBarButtons(); - } - - // ── Grid view: show floating bar ── - this._ensureGridBar(); - const gridBar = document.getElementById('batch-grid-bar'); - if (gridBar) { - const grid = document.getElementById('files-grid'); - const gridVisible = grid && grid.style.display !== 'none'; - if (gridVisible) { - gridBar.classList.add('visible'); - gridBar.innerHTML = ` -
- - ${ - n === 1 - ? (this._t('batch.one_selected') || '1 item selected') - : (this._t('batch.n_selected', { count: n }) || `${n} items selected`) - } -
-
- - - - -
- `; - const closeBtn = document.getElementById('batch-grid-close'); - if (closeBtn) closeBtn.addEventListener('click', () => this.clear()); - this._wireBarButtons(); - } else { - gridBar.classList.remove('visible'); - } - } + batchSelectionBar.classList.add('visible'); + + } else { this._barVisible = false; - // Restore original list header - if (listHeader) { - listHeader.classList.remove('selection-mode'); - if (this._savedHeaderHTML) { - listHeader.innerHTML = this._savedHeaderHTML; - } - // Re-wire the select-all checkbox - const cb = document.getElementById('select-all-checkbox'); - if (cb) cb.addEventListener('change', () => this.toggleAll()); - // Translate restored header (scoped to list header) - if (window.i18n && window.i18n.translateElement) window.i18n.translateElement(listHeader); - } - // Hide grid bar - const gridBar = document.getElementById('batch-grid-bar'); - if (gridBar) gridBar.classList.remove('visible'); + batchSelectionBar.classList.remove('visible'); } // Sync individual item checkboxes @@ -301,14 +219,16 @@ const multiSelect = { /** Wire click handlers on batch action buttons (idempotent per render) */ _wireBarButtons() { - const del = document.getElementById('batch-delete'); - const move = document.getElementById('batch-move'); - const dl = document.getElementById('batch-download'); - const fav = document.getElementById('batch-fav'); - if (del) del.onclick = () => this.batchDelete(); - if (move) move.onclick = () => this.batchMove(); - if (dl) dl.onclick = () => this.batchDownload(); - if (fav) fav.onclick = () => this.batchFavorites(); + const del = document.getElementById('batch-delete'); + const move = document.getElementById('batch-move'); + const dl = document.getElementById('batch-download'); + const fav = document.getElementById('batch-fav'); + const closeBtn = document.getElementById('batch-grid-close'); + if (del) del.onclick = () => this.batchDelete(); + if (move) move.onclick = () => this.batchMove(); + if (dl) dl.onclick = () => this.batchDownload(); + if (fav) fav.onclick = () => this.batchFavorites(); + if (closeBtn) closeBtn.onclick = () => this.clear(); }, _syncItemCheckboxes() { @@ -522,6 +442,15 @@ const multiSelect = { if (e.key === 'Escape' && this.hasSelection) this.clear(); if (e.key === 'Delete' && this.hasSelection) this.batchDelete(); }); + + const batchSelectionBar = document.getElementById('batch-selection-bar'); + batchSelectionBar.innerHTML = this._buildSelectionBarHTML(); + + if (window.i18n && window.i18n.translateElement) { + window.i18n.translateElement(batchSelectionBar); + } + this._wireBarButtons(); + }, _injectListHeaderCheckbox() { @@ -533,7 +462,7 @@ const multiSelect = { _hookGlobalDeselect() { document.addEventListener('click', (e) => { if (window.__rubberBandJustFinished) return; - if (e.target.closest('.file-card, .file-item, .context-menu, .batch-action-bar, .list-header.selection-mode, .about-modal, .rename-dialog, .share-dialog, .confirm-dialog, .modal-overlay, input, button')) return; + if (e.target.closest('.file-card, .file-item, .context-menu, .batch-selection-bar, .list-header.selection-mode, .about-modal, .rename-dialog, .share-dialog, .confirm-dialog, .modal-overlay, input, button')) return; if (this.hasSelection) this.clear(); }); }