window.App = window.App || {}; App.favoritesView = App.favoritesView || {}; // Browsing favorites as a full grid, the same way the channel listing is // browsed: same cards, same virtualized masonry, same infinite scroll, same // reels mode. The only difference is where the pages come from -- localStorage // instead of the server -- so App.videos.loadVideos routes here while this view // is active and every page hands its slice to App.videos.renderVideos. (function() { const state = App.state; // A page of a local list can be bigger than a page from the server: there's // no request behind it, only the cost of building cards, which the // virtualizer already keeps to what's on screen. const PAGE_SIZE = 24; const view = { active: false, queue: [], // the sorted favorites still to be handed to the grid offset: 0 }; // A favorite as the grid expects a video: `id` has to be unique per card // (the virtualizer and renderedVideoIds key on it), and an imported // favorite has no server id -- its key, which is the URL, stands in. const toVideo = function(entry) { return Object.assign({}, entry, { id: entry.key, tags: [] }); }; App.favoritesView.isActive = function() { return view.active; }; App.favoritesView.loadNext = function() { if (!view.active) return false; const slice = view.queue.slice(view.offset, view.offset + PAGE_SIZE); view.offset += slice.length; state.hasNextPage = view.offset < view.queue.length; if (!slice.length) { App.videos.updateLoadMoreState(); return false; } App.videos.renderVideos({ items: slice.map(toVideo) }); App.videos.updateLoadMoreState(); return true; }; // Starts (or restarts, after a sort change) the favorites grid. App.favoritesView.open = function(options) { const sort = (options && options.sort) || App.favorites.getSort(); const favorites = App.favorites.sorted(sort); if (!favorites.length) { App.ui.showError('No favorites yet. Tap the heart on a video to save one.'); return false; } App.videos.resetGrid(); view.active = true; view.queue = favorites; view.offset = 0; state.hasNextPage = true; document.body.classList.add('favorites-view-open'); // Re-render the bar so it re-decides whether to be mounted: hidden in // settings or not, its header has to be on screen now, since that's // where the way back out lives (the palette can open this view too). App.favorites.renderBar(); App.favoritesView.syncControls(); App.favoritesView.loadNext(); window.scrollTo({ top: 0, behavior: 'auto' }); return true; }; App.favoritesView.close = function(options) { if (!view.active) return; view.active = false; view.queue = []; view.offset = 0; document.body.classList.remove('favorites-view-open'); // Back to whatever the settings say, and with its cards built again. App.favorites.renderBar(); App.favoritesView.syncControls(); // Back to the channel listing, unless the caller is about to load // something itself (a search, a channel switch). if (!(options && options.silent)) App.videos.resetAndReload(); }; App.favoritesView.toggle = function() { if (view.active) App.favoritesView.close(); else App.favoritesView.open(); }; // Re-pages the grid under a new order, and re-renders the bar so both show // favorites the same way round. App.favoritesView.applySort = function(sort) { App.favorites.setSort(sort); App.favorites.renderBar(); if (view.active) App.favoritesView.open({ sort }); }; App.favoritesView.syncControls = function() { const button = document.getElementById('favorites-browse-btn'); if (button) { button.textContent = view.active ? 'Back to videos' : 'Browse all'; button.setAttribute('aria-pressed', view.active ? 'true' : 'false'); } const select = document.getElementById('favorites-sort'); if (select && select.value !== App.favorites.getSort()) { select.value = App.favorites.getSort(); } }; })();