some more features and fixes (title and show info)
This commit is contained in:
@@ -274,6 +274,61 @@ App.favorites = App.favorites || {};
|
||||
const BAR_PAGE_AHEAD_PX = 800;
|
||||
const barPage = { items: [], rendered: 0 };
|
||||
|
||||
// Bar titles are one line that scrolls when it doesn't fit, the same as the
|
||||
// grid card's. Which one scrolls follows the grid too: with a real pointer
|
||||
// it's the card under it, on touch the card nearest the middle of the strip.
|
||||
// Animating every overflowing title at once turns the bar into a wall of
|
||||
// moving text.
|
||||
const barTitleEnv = {
|
||||
useHoverFocus: window.matchMedia('(hover: hover) and (pointer: fine)').matches
|
||||
};
|
||||
|
||||
const setBarTitleActive = function(card, active) {
|
||||
const title = card && card.querySelector('.favorite-title');
|
||||
if (!title) return;
|
||||
card.classList.toggle('is-title-active', !!active && title.classList.contains('has-marquee'));
|
||||
};
|
||||
|
||||
// Touch: the card nearest the centre of the visible strip is the one being
|
||||
// read, so it is the one whose title scrolls.
|
||||
const syncBarTitleActive = function(list) {
|
||||
if (!list) return;
|
||||
const listRect = list.getBoundingClientRect();
|
||||
const centre = listRect.left + listRect.width / 2;
|
||||
let best = null;
|
||||
let bestDistance = Infinity;
|
||||
const cards = list.querySelectorAll('.favorite-card');
|
||||
cards.forEach((card) => {
|
||||
const rect = card.getBoundingClientRect();
|
||||
if (rect.right <= listRect.left || rect.left >= listRect.right) return;
|
||||
const distance = Math.abs((rect.left + rect.width / 2) - centre);
|
||||
if (distance < bestDistance) {
|
||||
bestDistance = distance;
|
||||
best = card;
|
||||
}
|
||||
});
|
||||
cards.forEach((card) => setBarTitleActive(card, card === best));
|
||||
};
|
||||
|
||||
// Measures every card in the strip. Cheap enough to redo wholesale: a card's
|
||||
// width is fixed, so this only really runs when cards are added.
|
||||
const measureBarTitles = function(list) {
|
||||
const target = list || document.getElementById('favorites-list');
|
||||
if (!target) return;
|
||||
target.querySelectorAll('.favorite-card').forEach((card) => {
|
||||
App.marquee.measure(card.querySelector('.favorite-title'),
|
||||
card.querySelector('.favorite-title-text'));
|
||||
});
|
||||
if (!barTitleEnv.useHoverFocus) syncBarTitleActive(target);
|
||||
};
|
||||
|
||||
// The display font arrives after the first render, and it changes how wide
|
||||
// every title is -- so whatever was measured against the fallback font has
|
||||
// to be measured again once the real one is in.
|
||||
if (document.fonts && document.fonts.ready) {
|
||||
document.fonts.ready.then(() => measureBarTitles()).catch(() => {});
|
||||
}
|
||||
|
||||
App.favorites.renderBar = function() {
|
||||
const bar = document.getElementById('favorites-bar');
|
||||
const list = document.getElementById('favorites-list');
|
||||
@@ -296,7 +351,16 @@ App.favorites = App.favorites || {};
|
||||
|
||||
// Assignment rather than addEventListener: renderBar runs on every
|
||||
// favorite change, and this must not stack up handlers.
|
||||
let scrollRaf = null;
|
||||
list.onscroll = () => {
|
||||
// Which card is centred changes as the strip moves, but only once
|
||||
// per frame is worth measuring.
|
||||
if (!barTitleEnv.useHoverFocus && !scrollRaf) {
|
||||
scrollRaf = requestAnimationFrame(() => {
|
||||
scrollRaf = null;
|
||||
syncBarTitleActive(list);
|
||||
});
|
||||
}
|
||||
if (barPage.rendered >= barPage.items.length) return;
|
||||
const remaining = list.scrollWidth - (list.scrollLeft + list.clientWidth);
|
||||
if (remaining <= BAR_PAGE_AHEAD_PX) appendBarPage(list);
|
||||
@@ -336,7 +400,7 @@ App.favorites = App.favorites || {};
|
||||
${durationText ? `<span class="video-duration">${durationText}</span>` : ''}
|
||||
</div>
|
||||
<div class="favorite-info">
|
||||
<h4>${item.title}</h4>
|
||||
<h4 class="favorite-title"><span class="favorite-title-text">${item.title}</span></h4>
|
||||
</div>
|
||||
`;
|
||||
const thumb = card.querySelector('img');
|
||||
@@ -372,9 +436,10 @@ App.favorites = App.favorites || {};
|
||||
showInfoBtn.onclick = (event) => {
|
||||
event.stopPropagation();
|
||||
App.videos.closeAllMenus();
|
||||
// Favorites deliberately store no resolved metadata, so pull
|
||||
// it fresh before showing the full info dump.
|
||||
App.videos.ensureFormats(item).then(() => App.ui.showInfo(item));
|
||||
// Favorites deliberately store no resolved metadata; the
|
||||
// panel opens on what the entry holds and resolves the rest
|
||||
// itself.
|
||||
App.ui.openInfo(item);
|
||||
};
|
||||
}
|
||||
if (downloadBtn) {
|
||||
@@ -394,7 +459,17 @@ App.favorites = App.favorites || {};
|
||||
App.videos.handleSearch(uploader);
|
||||
};
|
||||
}
|
||||
if (barTitleEnv.useHoverFocus) {
|
||||
card.addEventListener('pointerenter', () => setBarTitleActive(card, true));
|
||||
card.addEventListener('pointerleave', () => setBarTitleActive(card, false));
|
||||
}
|
||||
// Keyboard: tabbing to a card's heart should reveal its whole title
|
||||
// too, on touch devices as much as on desktop.
|
||||
card.addEventListener('focusin', () => setBarTitleActive(card, true));
|
||||
card.addEventListener('focusout', () => setBarTitleActive(card, false));
|
||||
list.appendChild(card);
|
||||
});
|
||||
// Widths only exist once the cards are laid out.
|
||||
requestAnimationFrame(() => measureBarTitles(list));
|
||||
}
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user