feat(ui): use infinite scrolling on all sections (no need anymore to to click on "load more")
This commit is contained in:
@@ -24,6 +24,7 @@ import { inlineViewer } from '../features/files/inlineViewer.js';
|
|||||||
import { favorites } from '../features/library/favorites.js';
|
import { favorites } from '../features/library/favorites.js';
|
||||||
import { fetchResourcesPage, rebuildBreadCrumb } from '../model/filesModel.js';
|
import { fetchResourcesPage, rebuildBreadCrumb } from '../model/filesModel.js';
|
||||||
import { grants } from '../model/grants.js';
|
import { grants } from '../model/grants.js';
|
||||||
|
import { attachInfiniteScroll } from '../utils/infiniteScroll.js';
|
||||||
import { resolveHomeFolder } from './authSession.js';
|
import { resolveHomeFolder } from './authSession.js';
|
||||||
import { updateHistory } from './main.js';
|
import { updateHistory } from './main.js';
|
||||||
import { app } from './state.js';
|
import { app } from './state.js';
|
||||||
@@ -289,6 +290,8 @@ function _ensureLoadMoreButton() {
|
|||||||
|
|
||||||
wrapper.appendChild(btn);
|
wrapper.appendChild(btn);
|
||||||
filesContainer.after(wrapper);
|
filesContainer.after(wrapper);
|
||||||
|
|
||||||
|
attachInfiniteScroll(wrapper, () => _loadPage({ isFirstPage: false }));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
// @ts-check
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Infinite-scroll for cursor-paginated views.
|
||||||
|
*
|
||||||
|
* Each section that paginates (Files, Favorites, Recent, Trash,
|
||||||
|
* SharedWithMe, MyShares) injects a "Load more" button wrapped in a
|
||||||
|
* `<div>` below `.files-container`. The wrapper toggles `.hidden` to
|
||||||
|
* reflect whether a next page exists.
|
||||||
|
*
|
||||||
|
* This utility wires an [`IntersectionObserver`] onto the same wrapper
|
||||||
|
* so it auto-fires the load-more action when the user scrolls near it.
|
||||||
|
* The visible button stays as a fallback (accessibility, browsers
|
||||||
|
* without IntersectionObserver support, the user's reflex from
|
||||||
|
* before infinite-scroll landed).
|
||||||
|
*
|
||||||
|
* Wire it once per wrapper at creation time. The observer is idempotent
|
||||||
|
* across re-renders because the wrapper itself is re-used; appended
|
||||||
|
* items push it down the document and trigger fresh intersection
|
||||||
|
* events when the user scrolls again.
|
||||||
|
*
|
||||||
|
* Re-entrancy: every existing `_loadPage` implementation has its own
|
||||||
|
* in-flight guard (a `_loading` flag returning early on re-entry), so
|
||||||
|
* this utility doesn't add another. If a future view doesn't, add a
|
||||||
|
* `_loading` field on the view object before adopting infinite-scroll.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-fire `onLoadMore()` when `wrapper` scrolls within
|
||||||
|
* `rootMargin` of the viewport AND is visible (the `.hidden` class is
|
||||||
|
* the cursor-exhausted signal — see `_setLoadMoreVisible` in each view).
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} wrapper The `.swm-load-more-wrapper` element.
|
||||||
|
* @param {() => void} onLoadMore Called on each intersection-enter.
|
||||||
|
* @param {{rootMargin?: string}} [opts]
|
||||||
|
* @returns {() => void} Teardown — disconnects the observer.
|
||||||
|
*/
|
||||||
|
export function attachInfiniteScroll(wrapper, onLoadMore, opts = {}) {
|
||||||
|
if (!('IntersectionObserver' in window)) {
|
||||||
|
// Older browser — degrade to the button-only experience.
|
||||||
|
return () => {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (!entry.isIntersecting) continue;
|
||||||
|
// No next page → wrapper hidden → skip.
|
||||||
|
if (wrapper.classList.contains('hidden')) continue;
|
||||||
|
onLoadMore();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Trigger a bit before the wrapper actually enters the viewport
|
||||||
|
// so the next page is on its way by the time the user reaches
|
||||||
|
// the bottom — smoother than a perceptible "click… wait…" beat.
|
||||||
|
{ rootMargin: opts.rootMargin ?? '200px' }
|
||||||
|
);
|
||||||
|
observer.observe(wrapper);
|
||||||
|
return () => observer.disconnect();
|
||||||
|
}
|
||||||
@@ -27,6 +27,7 @@ import * as itemTooltip from '../../features/itemTooltip.js';
|
|||||||
import { favorites } from '../../features/library/favorites.js';
|
import { favorites } from '../../features/library/favorites.js';
|
||||||
import { fetchFavoritesPage } from '../../model/favoritesModel.js';
|
import { fetchFavoritesPage } from '../../model/favoritesModel.js';
|
||||||
import { systemUsers } from '../../model/systemUsers.js';
|
import { systemUsers } from '../../model/systemUsers.js';
|
||||||
|
import { attachInfiniteScroll } from '../../utils/infiniteScroll.js';
|
||||||
|
|
||||||
/** @import {FavoritesResourceItem, FileItem, FolderItem, ResourceTypeEnum} from '../../core/types.js' */
|
/** @import {FavoritesResourceItem, FileItem, FolderItem, ResourceTypeEnum} from '../../core/types.js' */
|
||||||
|
|
||||||
@@ -429,6 +430,8 @@ const favoritesView = {
|
|||||||
|
|
||||||
wrapper.appendChild(btn);
|
wrapper.appendChild(btn);
|
||||||
filesContainer.after(wrapper);
|
filesContainer.after(wrapper);
|
||||||
|
|
||||||
|
attachInfiniteScroll(wrapper, () => this._loadPage());
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import * as viewPrefs from '../../core/viewPrefs.js';
|
|||||||
import * as itemTooltip from '../../features/itemTooltip.js';
|
import * as itemTooltip from '../../features/itemTooltip.js';
|
||||||
import { grants } from '../../model/grants.js';
|
import { grants } from '../../model/grants.js';
|
||||||
import { groups } from '../../model/groups.js';
|
import { groups } from '../../model/groups.js';
|
||||||
|
import { attachInfiniteScroll } from '../../utils/infiniteScroll.js';
|
||||||
|
|
||||||
/** @import {FileItem, FolderItem} from '../../core/types.js' */
|
/** @import {FileItem, FolderItem} from '../../core/types.js' */
|
||||||
|
|
||||||
@@ -256,6 +257,8 @@ const mySharesView = {
|
|||||||
|
|
||||||
wrapper.appendChild(btn);
|
wrapper.appendChild(btn);
|
||||||
filesContainer.after(wrapper);
|
filesContainer.after(wrapper);
|
||||||
|
|
||||||
|
attachInfiniteScroll(wrapper, () => this._loadPage());
|
||||||
},
|
},
|
||||||
|
|
||||||
/** @param {boolean} visible */
|
/** @param {boolean} visible */
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import * as itemTooltip from '../../features/itemTooltip.js';
|
|||||||
import { favorites } from '../../features/library/favorites.js';
|
import { favorites } from '../../features/library/favorites.js';
|
||||||
import { fetchRecentPage } from '../../model/recentModel.js';
|
import { fetchRecentPage } from '../../model/recentModel.js';
|
||||||
import { systemUsers } from '../../model/systemUsers.js';
|
import { systemUsers } from '../../model/systemUsers.js';
|
||||||
|
import { attachInfiniteScroll } from '../../utils/infiniteScroll.js';
|
||||||
|
|
||||||
/** @import {FileItem, FolderItem, ResourceTypeEnum} from '../../core/types.js' */
|
/** @import {FileItem, FolderItem, ResourceTypeEnum} from '../../core/types.js' */
|
||||||
|
|
||||||
@@ -436,6 +437,8 @@ const recentView = {
|
|||||||
|
|
||||||
wrapper.appendChild(btn);
|
wrapper.appendChild(btn);
|
||||||
filesContainer.after(wrapper);
|
filesContainer.after(wrapper);
|
||||||
|
|
||||||
|
attachInfiniteScroll(wrapper, () => this._loadPage());
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import * as itemTooltip from '../../features/itemTooltip.js';
|
|||||||
import { favorites } from '../../features/library/favorites.js';
|
import { favorites } from '../../features/library/favorites.js';
|
||||||
import { grants } from '../../model/grants.js';
|
import { grants } from '../../model/grants.js';
|
||||||
import { systemUsers } from '../../model/systemUsers.js';
|
import { systemUsers } from '../../model/systemUsers.js';
|
||||||
|
import { attachInfiniteScroll } from '../../utils/infiniteScroll.js';
|
||||||
|
|
||||||
/** @import {SharedWithMeItem, FileItem, FolderItem, ResourceTypeEnum} from '../../core/types.js' */
|
/** @import {SharedWithMeItem, FileItem, FolderItem, ResourceTypeEnum} from '../../core/types.js' */
|
||||||
|
|
||||||
@@ -438,6 +439,8 @@ const sharedWithMeView = {
|
|||||||
|
|
||||||
wrapper.appendChild(btn);
|
wrapper.appendChild(btn);
|
||||||
filesContainer.after(wrapper);
|
filesContainer.after(wrapper);
|
||||||
|
|
||||||
|
attachInfiniteScroll(wrapper, () => this._loadPage());
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import * as viewPrefs from '../../core/viewPrefs.js';
|
|||||||
import { fileOps } from '../../features/files/fileOperations.js';
|
import { fileOps } from '../../features/files/fileOperations.js';
|
||||||
import * as itemTooltip from '../../features/itemTooltip.js';
|
import * as itemTooltip from '../../features/itemTooltip.js';
|
||||||
import { fetchTrashPage } from '../../model/trashModel.js';
|
import { fetchTrashPage } from '../../model/trashModel.js';
|
||||||
|
import { attachInfiniteScroll } from '../../utils/infiniteScroll.js';
|
||||||
|
|
||||||
/** @import {FileItem, FolderItem, ResourceTypeEnum, TrashResourceItem} from '../../core/types.js' */
|
/** @import {FileItem, FolderItem, ResourceTypeEnum, TrashResourceItem} from '../../core/types.js' */
|
||||||
|
|
||||||
@@ -449,6 +450,8 @@ const trashView = {
|
|||||||
|
|
||||||
wrapper.appendChild(btn);
|
wrapper.appendChild(btn);
|
||||||
filesContainer.after(wrapper);
|
filesContainer.after(wrapper);
|
||||||
|
|
||||||
|
attachInfiniteScroll(wrapper, () => this._loadPage());
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user