diff --git a/static/css/components/multiSelect.css b/static/css/components/batchToolbar.css
similarity index 100%
rename from static/css/components/multiSelect.css
rename to static/css/components/batchToolbar.css
diff --git a/static/css/main.css b/static/css/main.css
index 380b2f4b..83aa67a1 100644
--- a/static/css/main.css
+++ b/static/css/main.css
@@ -25,7 +25,7 @@
@import url("./components/notifications.css");
@import url("./components/userMenu.css");
@import url("./components/languageSelector.css");
-@import url("./components/multiSelect.css");
+@import url("./components/batchToolbar.css");
@import url("./components/spinner.css");
@import url("./components/search.css");
@import url("./components/icons.css");
diff --git a/static/js/app/filesView.js b/static/js/app/filesView.js
index ddff0609..12be1db0 100644
--- a/static/js/app/filesView.js
+++ b/static/js/app/filesView.js
@@ -2,7 +2,7 @@
import { i18n } from '../core/i18n.js';
import { inlineViewer } from '../features/files/inlineViewer.js';
-import { multiSelect } from '../features/files/multiSelect.js';
+import { batchToolbar } from '../features/files/batchToolbar.js';
import { resolveHomeFolder } from './authSession.js';
import { updateHistory } from './main.js';
import { app } from './state.js';
@@ -217,9 +217,9 @@ async function loadFiles(options = { insertHistory: true }) {
ui._items.clear();
ui.resetFilesList();
- if (multiSelect) {
- multiSelect.clear();
- multiSelect.init(); // this will wire buttons & select-all-checkbox
+ if (batchToolbar) {
+ batchToolbar.clear();
+ batchToolbar.init(); // this will wire buttons & select-all-checkbox
}
/** @type {FolderItem[]} */
diff --git a/static/js/app/main.js b/static/js/app/main.js
index 037712ac..e6c8768d 100644
--- a/static/js/app/main.js
+++ b/static/js/app/main.js
@@ -12,7 +12,7 @@ import { formatFileSize, formatQuotaSize } from '../core/formatters.js';
import { i18n } from '../core/i18n.js';
import { oxiIconsInit } from '../core/icons.js';
import { fileOps } from '../features/files/fileOperations.js';
-import { multiSelect } from '../features/files/multiSelect.js';
+import { batchToolbar } from '../features/files/batchToolbar.js';
import { favorites } from '../features/library/favorites.js';
import { recent } from '../features/library/recent.js';
import { fileSharing } from '../features/sharing/fileSharing.js';
@@ -50,7 +50,7 @@ let uploadDropdownDocumentClickHandler = null;
let uploadDropdownBindingsController = null;
let actionsBarDelegationBound = false;
-const _multiSelectButons = `
+const _batchToolbarButons = `
- ${_multiSelectButons}
+ ${_batchToolbarButons}
${_toggleButtons}
`,
sharedwithme: `
@@ -386,9 +386,9 @@ function initApp() {
}
// Initialize multi-select / batch actions
- if (multiSelect?.init) {
+ if (batchToolbar?.init) {
console.log('Initializing multi-select module');
- multiSelect.init();
+ batchToolbar.init();
}
window.addEventListener('authenticationDone', async () => {
diff --git a/static/js/app/navigation.js b/static/js/app/navigation.js
index 08ac0f33..3be34396 100644
--- a/static/js/app/navigation.js
+++ b/static/js/app/navigation.js
@@ -4,7 +4,7 @@
*/
import { i18n } from '../core/i18n.js';
-import { multiSelect } from '../features/files/multiSelect.js';
+import { batchToolbar } from '../features/files/batchToolbar.js';
import { favorites } from '../features/library/favorites.js';
import { musicView } from '../features/library/music.js';
import { photosView } from '../features/library/photos.js';
@@ -201,7 +201,7 @@ function switchToSharedSection() {
sharedView.show();
});
- if (multiSelect) multiSelect.clear();
+ if (batchToolbar) batchToolbar.clear();
}
function switchToSharedWithMeSection() {
@@ -221,7 +221,7 @@ function switchToSharedWithMeSection() {
toggleFileContainer(true);
syncViewContainers();
- if (multiSelect) multiSelect.clear();
+ if (batchToolbar) batchToolbar.clear();
// Load and render items into the files container
sharedWithMeView.init();
@@ -253,7 +253,7 @@ function switchToFilesSection() {
app.currentPath = app.userHomeFolderId || '';
app.breadcrumbPath = [];
ui.updateBreadcrumb();
- if (multiSelect) multiSelect.clear();
+ if (batchToolbar) batchToolbar.clear();
// temp solution
sharedView.loadItems().then(() => {
@@ -296,7 +296,7 @@ function switchToFavoritesSection() {
`);
}
- if (multiSelect) multiSelect.clear();
+ if (batchToolbar) batchToolbar.clear();
}
function switchToRecentFilesSection() {
@@ -329,7 +329,7 @@ function switchToRecentFilesSection() {
Error loading the recent module
`);
}
- if (multiSelect) multiSelect.clear();
+ if (batchToolbar) batchToolbar.clear();
}
function switchToPhotosSection() {
@@ -352,7 +352,7 @@ function switchToPhotosSection() {
if (photosView) {
photosView.show();
}
- if (multiSelect) multiSelect.clear();
+ if (batchToolbar) batchToolbar.clear();
}
function switchToTrashSection() {
@@ -377,7 +377,7 @@ function switchToTrashSection() {
// Load trash items
loadTrashItems();
- if (multiSelect) multiSelect.clear();
+ if (batchToolbar) batchToolbar.clear();
}
function switchToMusicSection() {
@@ -404,7 +404,7 @@ function switchToMusicSection() {
if (musicView) {
musicView.show();
}
- if (multiSelect) multiSelect.clear();
+ if (batchToolbar) batchToolbar.clear();
}
/**
@@ -423,7 +423,7 @@ function activateFilesUI() {
breadcrumb?.classList.remove('hidden');
toggleFileContainer(true);
syncViewContainers();
- if (multiSelect) multiSelect.clear();
+ if (batchToolbar) batchToolbar.clear();
}
export {
diff --git a/static/js/app/trashView.js b/static/js/app/trashView.js
index b7066906..c9cb2d33 100644
--- a/static/js/app/trashView.js
+++ b/static/js/app/trashView.js
@@ -5,7 +5,7 @@
import { escapeHtml, formatDateTime } from '../core/formatters.js';
import { i18n } from '../core/i18n.js';
import { fileOps } from '../features/files/fileOperations.js';
-import { multiSelect } from '../features/files/multiSelect.js';
+import { batchToolbar } from '../features/files/batchToolbar.js';
import * as pathTooltip from '../features/pathTooltip.js';
import { appElements } from './state.js';
import { ui } from './ui.js';
@@ -22,7 +22,7 @@ async function loadTrashItems() {
const elements = appElements;
try {
- if (multiSelect) multiSelect.clear();
+ if (batchToolbar) batchToolbar.clear();
pathTooltip.destroy(elements.filesList);
ui.resetFilesList(); // ensure also list visible & error hidden
elements.filesList.innerHTML = `
diff --git a/static/js/app/ui.js b/static/js/app/ui.js
index 7862e0dd..9daa7624 100644
--- a/static/js/app/ui.js
+++ b/static/js/app/ui.js
@@ -13,7 +13,7 @@ import { OxiIcons } from '../core/icons.js';
import { contextMenus } from '../features/files/contextMenus.js';
import { fileOps } from '../features/files/fileOperations.js';
import { inlineViewer } from '../features/files/inlineViewer.js';
-import { multiSelect } from '../features/files/multiSelect.js';
+import { batchToolbar } from '../features/files/batchToolbar.js';
import { wopiEditor } from '../features/files/wopiEditor.js';
import { favorites } from '../features/library/favorites.js';
import { recent } from '../features/library/recent.js';
@@ -727,9 +727,9 @@ const ui = {
* @param {any} dataTransfer fallback if nothing is selected
*/
async _dropToFolder(action, targetFolderId, dataTransfer) {
- const selection = multiSelect.getSelection(targetFolderId);
+ const selection = batchToolbar.getSelection(targetFolderId);
- multiSelect.clear();
+ batchToolbar.clear();
if (selection.fileIds.length === 0 && selection.folderIds.length === 0) {
// try to use dataTransfer (direct move without selection)
@@ -774,7 +774,7 @@ const ui = {
console.error(`drag and drop: action ${action} unknown`);
return;
}
- multiSelect.showBatchResult(action, result);
+ batchToolbar.showBatchResult(action, result);
console.log(result);
},
@@ -941,8 +941,8 @@ const ui = {
}
// shiftkey is used to complete selection
- if (e.shiftKey && multiSelect) {
- multiSelect.handleToggleItem(card, e);
+ if (e.shiftKey && batchToolbar) {
+ batchToolbar.handleToggleItem(card, e);
return;
}
@@ -1512,13 +1512,13 @@ const ui = {
/**
* Toggle selection state of a file/folder card.
- * Routes through the multiSelect module so batch actions know about selected items.
+ * Routes through the batchToolbar module so batch actions know about selected items.
* @param {HTMLDivElement} card
* @param {MouseEvent} event
*/
function toggleCardSelection(card, event) {
- if (multiSelect) {
- multiSelect.handleToggleItem(card, event);
+ if (batchToolbar) {
+ batchToolbar.handleToggleItem(card, event);
} else {
card.classList.toggle('selected');
}
@@ -1641,17 +1641,17 @@ function initRubberBandSelection() {
if (intersects) {
card.classList.add('selected');
- // Sync with multiSelect module
- if (multiSelect) {
- const info = multiSelect._extractInfo(/** @type {HTMLDivElement} */ (card));
- if (info) multiSelect.select(info.id, info.name, info.type, info.parentId);
+ // Sync with batchToolbar module
+ if (batchToolbar) {
+ const info = batchToolbar._extractInfo(/** @type {HTMLDivElement} */ (card));
+ if (info) batchToolbar.select(info.id, info.name, info.type, info.parentId);
}
} else {
card.classList.remove('selected');
- // Deselect from multiSelect module
- if (multiSelect) {
- const info = multiSelect._extractInfo(/** @type {HTMLDivElement} */ (card));
- if (info) multiSelect.deselect(info.id);
+ // Deselect from batchToolbar module
+ if (batchToolbar) {
+ const info = batchToolbar._extractInfo(/** @type {HTMLDivElement} */ (card));
+ if (info) batchToolbar.deselect(info.id);
}
}
});
@@ -1663,7 +1663,7 @@ function initRubberBandSelection() {
const hadSelection = selRect.style.display === 'block';
selRect.style.display = 'none';
// Update the batch bar after rubber band selection completes
- if (multiSelect) multiSelect._syncUI();
+ if (batchToolbar) batchToolbar._syncUI();
// Suppress the click event that follows mouseup so the global
// deselect handler doesn't immediately clear the selection.
if (hadSelection) {
diff --git a/static/js/components/resourceList.js b/static/js/components/resourceList.js
index 7d4af5b0..f2ddb286 100644
--- a/static/js/components/resourceList.js
+++ b/static/js/components/resourceList.js
@@ -86,6 +86,9 @@ export class ResourceListComponent {
/** @type {Set} */
this._selected = new Set();
+ /** Index of the last clicked item — used for shift-click range selection. */
+ this._lastClickedIndex = -1;
+
this._ownerVisible = this._cfg.showOwner;
this._initDelegation();
@@ -110,8 +113,10 @@ export class ResourceListComponent {
this._selected.clear();
this._items.clear();
+ this._lastClickedIndex = -1;
this._appendItems(folders, files, groupFn);
+ this._wireSelectAll();
}
/**
@@ -132,6 +137,40 @@ export class ResourceListComponent {
if (header) this._container.appendChild(header);
this._selected.clear();
this._items.clear();
+ this._lastClickedIndex = -1;
+ }
+
+ /**
+ * Deselect all items without removing them from the DOM.
+ * Used by the batch toolbar after an operation completes.
+ */
+ clearSelection() {
+ this._selected.clear();
+ this._lastClickedIndex = -1;
+ this._container.querySelectorAll('.file-item.selected').forEach((card) => {
+ card.classList.remove('selected');
+ const cb = /** @type {HTMLInputElement | null} */ (card.querySelector('.item-checkbox'));
+ if (cb) cb.checked = false;
+ });
+ this._syncSelectAllCheckbox();
+ this._cfg.onSelectionChange?.([]);
+ }
+
+ /**
+ * Select all visible items in the container.
+ */
+ selectAll() {
+ this._container.querySelectorAll('.file-item').forEach((card) => {
+ const el = /** @type {HTMLElement} */ (card);
+ const id = el.dataset.fileId || el.dataset.folderId || '';
+ if (!id) return;
+ el.classList.add('selected');
+ const cb = /** @type {HTMLInputElement | null} */ (el.querySelector('.item-checkbox'));
+ if (cb) cb.checked = true;
+ this._selected.add(id);
+ });
+ this._syncSelectAllCheckbox();
+ this._notifySelectionChange();
}
/**
@@ -406,9 +445,13 @@ export class ResourceListComponent {
return;
}
- // Checkbox cell → selection
+ // Checkbox cell → selection (shift extends range)
if (cfg.selectable && target.closest('.checkbox-cell')) {
- this._toggleSelection(card, /** @type {MouseEvent} */ (e));
+ if (e.shiftKey) {
+ this._handleShiftSelect(card);
+ } else {
+ this._toggleSelection(card);
+ }
return;
}
@@ -417,7 +460,13 @@ export class ResourceListComponent {
// Modifier-key click → selection toggle
if (e.metaKey || e.altKey || e.ctrlKey) {
- if (cfg.selectable) this._toggleSelection(card, /** @type {MouseEvent} */ (e));
+ if (cfg.selectable) this._toggleSelection(card);
+ return;
+ }
+
+ // Shift-click anywhere on the card → extend selection range
+ if (e.shiftKey && cfg.selectable) {
+ this._handleShiftSelect(card);
return;
}
@@ -454,11 +503,11 @@ export class ResourceListComponent {
}
/**
- * Toggle selection state on a card and notify via `onSelectionChange`.
+ * Toggle selection on a single card and notify.
+ * Tracks `_lastClickedIndex` for subsequent shift-clicks.
* @param {HTMLElement} card
- * @param {MouseEvent} _e - Reserved for future shift-click range selection.
*/
- _toggleSelection(card, _e) {
+ _toggleSelection(card) {
const id = card.dataset.fileId || card.dataset.folderId || '';
if (!id) return;
@@ -474,13 +523,96 @@ export class ResourceListComponent {
this._selected.delete(id);
}
- if (this._cfg.onSelectionChange) {
- /** @type {Array} */
- const selectedItems = [...this._selected].flatMap((sid) => {
- const item = this._items.get(sid);
- return item ? [item] : [];
- });
- this._cfg.onSelectionChange(selectedItems);
+ // Record position for the next shift-click
+ const items = [...this._container.querySelectorAll('.file-item')];
+ this._lastClickedIndex = items.indexOf(card);
+
+ this._syncSelectAllCheckbox();
+ this._notifySelectionChange();
+ }
+
+ /**
+ * Extend the selection from `_lastClickedIndex` to `card` (inclusive).
+ * If no previous click exists, falls back to a plain toggle.
+ * @param {HTMLElement} card
+ */
+ _handleShiftSelect(card) {
+ const items = /** @type {HTMLElement[]} */ ([...this._container.querySelectorAll('.file-item')]);
+ const index = items.indexOf(card);
+
+ if (this._lastClickedIndex >= 0 && index >= 0) {
+ const start = Math.min(this._lastClickedIndex, index);
+ const end = Math.max(this._lastClickedIndex, index);
+ for (let i = start; i <= end; i++) {
+ const el = items[i];
+ const id = el.dataset.fileId || el.dataset.folderId || '';
+ if (!id) continue;
+ el.classList.add('selected');
+ const cb = /** @type {HTMLInputElement | null} */ (el.querySelector('.item-checkbox'));
+ if (cb) cb.checked = true;
+ this._selected.add(id);
+ }
+ } else {
+ this._toggleSelection(card);
+ return;
+ }
+
+ this._lastClickedIndex = index;
+ this._syncSelectAllCheckbox();
+ this._notifySelectionChange();
+ }
+
+ /**
+ * Find the select-all checkbox in the container header and wire its
+ * `change` event. Called after every `render()`.
+ */
+ _wireSelectAll() {
+ if (!this._cfg.selectable) return;
+ const cb = /** @type {HTMLInputElement | null} */ (this._container.querySelector('#select-all-checkbox'));
+ if (!cb) return;
+ // Replace with a fresh listener to avoid duplicates across re-renders
+ const fresh = /** @type {HTMLInputElement} */ (cb.cloneNode(true));
+ cb.parentNode?.replaceChild(fresh, cb);
+ fresh.addEventListener('change', () => {
+ if (fresh.checked) {
+ this.selectAll();
+ } else {
+ this.clearSelection();
+ }
+ });
+ }
+
+ /**
+ * Sync the three-state select-all checkbox in the list header.
+ * Checked = all selected, indeterminate = some selected, unchecked = none.
+ */
+ _syncSelectAllCheckbox() {
+ const cb = /** @type {HTMLInputElement | null} */ (this._container.querySelector('#select-all-checkbox'));
+ if (!cb) return;
+ const total = this._container.querySelectorAll('.file-item').length;
+ if (total === 0) {
+ cb.checked = false;
+ cb.indeterminate = false;
+ } else if (this._selected.size >= total) {
+ cb.checked = true;
+ cb.indeterminate = false;
+ } else if (this._selected.size > 0) {
+ cb.checked = false;
+ cb.indeterminate = true;
+ } else {
+ cb.checked = false;
+ cb.indeterminate = false;
}
}
+
+ /** Build the selected-items array and fire `onSelectionChange`. */
+ _notifySelectionChange() {
+ if (!this._cfg.onSelectionChange) return;
+ /** @type {Array} */
+ const selectedItems = [...this._selected].flatMap((id) => {
+ const item = this._items.get(id);
+ return item ? [item] : [];
+ });
+ this._cfg.onSelectionChange(selectedItems);
+ }
}
diff --git a/static/js/features/files/multiSelect.js b/static/js/features/files/batchToolbar.js
similarity index 97%
rename from static/js/features/files/multiSelect.js
rename to static/js/features/files/batchToolbar.js
index de9c9525..ceaebecc 100644
--- a/static/js/features/files/multiSelect.js
+++ b/static/js/features/files/batchToolbar.js
@@ -1,14 +1,16 @@
/**
- * OxiCloud - Multi-Select & Batch Actions Module
+ * OxiCloud — Batch Toolbar Module
*
- * Adds checkboxes to grid and list views, replaces the list-view header
- * with a NextCloud-style selection bar when items are selected, and
- * provides batch delete / move / download / favorites operations.
+ * Manages the floating selection bar that appears when items are selected,
+ * and executes batch operations (delete, move, download, favorites).
+ *
+ * Selection state (_selected, handleToggleItem, selectAll, …) is kept here
+ * while the main file manager still uses its own delegation (ui.js).
+ * Once ui.js is migrated to ResourceListComponent (plan step B5), all
+ * selection mechanics will live in the component and this module will
+ * shrink to only the toolbar UI and batch-operation API calls.
*/
-// TODO: rename into selection-bar ?
-// TODO: merge with photo part
-
import { loadFiles } from '../../app/filesView.js';
import { app } from '../../app/state.js';
import { showConfirmDialog, ui } from '../../app/ui.js';
@@ -22,7 +24,7 @@ import { getAuthHeaders } from './fileOperations.js';
* @import {BatchResult} from './fileOperations.js'
*/
-const multiSelect = {
+const batchToolbar = {
/** @type {Map} items: Map */
_selected: new Map(),
@@ -536,4 +538,4 @@ const multiSelect = {
}
};
-export { multiSelect };
+export { batchToolbar };
diff --git a/static/js/features/files/contextMenus.js b/static/js/features/files/contextMenus.js
index e4395fb1..de6c493d 100644
--- a/static/js/features/files/contextMenus.js
+++ b/static/js/features/files/contextMenus.js
@@ -18,7 +18,7 @@ import { musicView } from '../library/music.js';
import { fileSharing } from '../sharing/fileSharing.js';
import { fileOps } from './fileOperations.js';
import { inlineViewer } from './inlineViewer.js';
-import { multiSelect } from './multiSelect.js';
+import { batchToolbar } from './batchToolbar.js';
import { wopiEditor } from './wopiEditor.js';
/**
@@ -330,8 +330,8 @@ const contextMenus = {
// Copy button handler
copyConfirmBtn.addEventListener('click', async () => {
- // Batch copy mode (from multiSelect)
- if (app.moveDialogMode === 'batch' && multiSelect) {
+ // Batch copy mode (from batchToolbar)
+ if (app.moveDialogMode === 'batch' && batchToolbar) {
const targetId = app.selectedTargetFolderId;
const items = app.batchMoveItems || [];
@@ -341,10 +341,10 @@ const contextMenus = {
const result = await fileOps.batchCopy(fileIds, folderIds, targetId);
this.closeMoveDialog();
- multiSelect.clear();
+ batchToolbar.clear();
loadFiles();
- multiSelect.showBatchResult('copy', result);
+ batchToolbar.showBatchResult('copy', result);
return;
}
@@ -363,8 +363,8 @@ const contextMenus = {
});
moveConfirmBtn.addEventListener('click', async () => {
- // Batch move mode (from multiSelect)
- if (app.moveDialogMode === 'batch' && multiSelect) {
+ // Batch move mode (from batchToolbar)
+ if (app.moveDialogMode === 'batch' && batchToolbar) {
const targetId = app.selectedTargetFolderId;
const items = app.batchMoveItems || [];
@@ -374,9 +374,9 @@ const contextMenus = {
const result = await fileOps.batchMove(fileIds, folderIds, targetId);
this.closeMoveDialog();
- multiSelect.clear();
+ batchToolbar.clear();
loadFiles();
- multiSelect.showBatchResult('move', result);
+ batchToolbar.showBatchResult('move', result);
return;
}
diff --git a/static/js/features/library/favorites.js b/static/js/features/library/favorites.js
index 86939cc7..28fabf64 100644
--- a/static/js/features/library/favorites.js
+++ b/static/js/features/library/favorites.js
@@ -9,7 +9,7 @@
import { ui } from '../../app/ui.js';
import { getCsrfHeaders } from '../../core/csrf.js';
import { i18n } from '../../core/i18n.js';
-import { multiSelect } from '../files/multiSelect.js';
+import { batchToolbar } from '../files/batchToolbar.js';
import * as pathTooltip from '../pathTooltip.js';
/** @import {FavoriteItem, FileItem, FolderItem} from '../../core/types.js' */
@@ -181,7 +181,7 @@ const favorites = {
ui.resetFilesList(); // ensure also list visible & error hidden
// wire buttons & select-all-checkbox as list header has changed in ui.resetFilesList()
// FIXME: this case is not easy to understand, should apply better implementation
- multiSelect.init();
+ batchToolbar.init();
ui.updateBreadcrumb();
diff --git a/static/js/features/library/recent.js b/static/js/features/library/recent.js
index f6e21494..08cec86f 100644
--- a/static/js/features/library/recent.js
+++ b/static/js/features/library/recent.js
@@ -9,7 +9,7 @@
import { ui } from '../../app/ui.js';
import { getCsrfHeaders } from '../../core/csrf.js';
import { i18n } from '../../core/i18n.js';
-import { multiSelect } from '../files/multiSelect.js';
+import { batchToolbar } from '../files/batchToolbar.js';
import * as pathTooltip from '../pathTooltip.js';
/** @import {FileItem, FolderItem, ItemTypeEnum, RecentItem} from '../../core/types.js' */
@@ -111,9 +111,9 @@ const recent = {
`;
- if (multiSelect) {
- multiSelect.clear();
- multiSelect.init(); // this will wire buttons & select-all-checkbox
+ if (batchToolbar) {
+ batchToolbar.clear();
+ batchToolbar.init(); // this will wire buttons & select-all-checkbox
}
ui.updateBreadcrumb();
diff --git a/static/js/views/sharedWithMe/sharedWithMeView.js b/static/js/views/sharedWithMe/sharedWithMeView.js
index 4e3f8f9e..2b5dc371 100644
--- a/static/js/views/sharedWithMe/sharedWithMeView.js
+++ b/static/js/views/sharedWithMe/sharedWithMeView.js
@@ -16,7 +16,7 @@
import { ui } from '../../app/ui.js';
import { i18n } from '../../core/i18n.js';
-import { multiSelect } from '../../features/files/multiSelect.js';
+import { batchToolbar } from '../../features/files/batchToolbar.js';
import { ownerTooltip } from '../../features/ownerTooltip.js';
import { grants } from '../../model/grants.js';
import { systemUsers } from '../../model/systemUsers.js';
@@ -52,7 +52,7 @@ const sharedWithMeView = {
// Standard files-view setup: clear list, show container, init multiselect
ui.resetFilesList();
- multiSelect.init();
+ batchToolbar.init();
ui.updateBreadcrumb();
await this._loadPage();