102 lines
4.6 KiB
JavaScript
102 lines
4.6 KiB
JavaScript
import { $, api } from './ui-core.js';
|
|
|
|
export function createRecommendations({merge, changed, switched, allowed = () => true}) {
|
|
const feeds = new Map();
|
|
let enabled = false, genre = 'short_play', controller, sequence = 0, busy = false;
|
|
function current() {
|
|
if (!feeds.has(genre)) feeds.set(genre, {items: [], offset: 0, sessionId: '', hasMore: true, loaded: false, error: '', saved: true});
|
|
return feeds.get(genre);
|
|
}
|
|
function cancel() {
|
|
sequence++;
|
|
controller?.abort();
|
|
controller = null;
|
|
busy = false;
|
|
}
|
|
function render() {
|
|
const feed = current();
|
|
$('recommendFooter').hidden = !enabled;
|
|
$('recommendMoreBtn').hidden = !feed.hasMore && !feed.error;
|
|
$('recommendMoreBtn').disabled = busy;
|
|
$('recommendMoreBtn').textContent = busy ? '读取中…' : feed.error ? '重试' : feed.loaded ? '继续推荐' : '获取推荐';
|
|
$('recommendRefreshBtn').disabled = busy;
|
|
$('recommendRefreshBtn').setAttribute('aria-busy', String(busy));
|
|
$('recommendStatus').textContent = busy ? '正在读取红果分类推荐…' : feed.error || (feed.loaded ? '已显示 ' + feed.items.length + ' 部' + (feed.hasMore ? ',按红果默认推荐顺序' : ',本轮已读完') + (feed.saved ? '' : ';缓存暂未保存') : '选择分类获取推荐');
|
|
$('recommendStatus').classList.toggle('error', Boolean(feed.error));
|
|
}
|
|
async function load(reset = Boolean(current().retryReset)) {
|
|
if (!allowed() || !enabled || busy) return;
|
|
const feed = current();
|
|
if (!reset && !feed.hasMore && !feed.error) return;
|
|
cancel();
|
|
const token = sequence, requestGenre = genre;
|
|
controller = new AbortController();
|
|
const signal = controller.signal;
|
|
busy = true;
|
|
feed.error = '';
|
|
render();
|
|
try {
|
|
const result = await api('/api/ui/recommendations', {
|
|
method: 'POST', signal,
|
|
body: JSON.stringify({genre, offset: reset ? 0 : feed.offset, sessionId: reset ? '' : feed.sessionId, seen: reset ? [] : feed.items.slice(-540).map(item => item.id)})
|
|
});
|
|
if (signal.aborted || token !== sequence || genre !== requestGenre || !enabled) return;
|
|
if (!Array.isArray(result.data) || typeof result.hasMore !== 'boolean' || !Number.isInteger(result.nextOffset) || typeof result.sessionId !== 'string') throw new Error('推荐数据格式无效');
|
|
const known = new Set(reset ? [] : feed.items.map(item => item.id));
|
|
const rows = result.data.filter(item => /^hongguo:[0-9]{1,32}$/.test(item.id) && item.source === 'hongguo').filter(item => {
|
|
if (known.has(item.id)) return false;
|
|
known.add(item.id);
|
|
return true;
|
|
});
|
|
if (result.hasMore && (result.nextOffset <= (reset ? 0 : feed.offset) || !rows.length)) throw new Error('推荐分页未前进,请重新获取');
|
|
feed.items = reset ? rows : feed.items.concat(rows);
|
|
feed.offset = result.nextOffset;
|
|
feed.sessionId = result.sessionId;
|
|
feed.hasMore = result.hasMore;
|
|
feed.loaded = true;
|
|
feed.retryReset = false;
|
|
feed.saved = result.saved !== false;
|
|
merge(rows);
|
|
changed();
|
|
} catch (error) {
|
|
if (token === sequence && !signal.aborted) {feed.error = error.message; feed.retryReset = reset;}
|
|
} finally {
|
|
if (token === sequence) {
|
|
busy = false;
|
|
controller = null;
|
|
render();
|
|
}
|
|
}
|
|
}
|
|
function setEnabled(value) {
|
|
if (value && !allowed()) return;
|
|
cancel();
|
|
enabled = value;
|
|
$('librarySearch').hidden = enabled;
|
|
$('libraryFilters').hidden = enabled;
|
|
$('recommendToolbar').hidden = !enabled;
|
|
$('recentSearches').hidden = true;
|
|
$('libraryModeBtn').setAttribute('aria-pressed', String(!enabled));
|
|
$('recommendModeBtn').setAttribute('aria-pressed', String(enabled));
|
|
document.body.classList.toggle('show-recommendations', enabled);
|
|
switched();
|
|
render();
|
|
if (enabled && !current().loaded) load();
|
|
}
|
|
function init() {
|
|
$('recommendModeBtn').hidden = !allowed();
|
|
$('libraryModeBtn').addEventListener('click', () => setEnabled(false));
|
|
$('recommendModeBtn').addEventListener('click', () => setEnabled(true));
|
|
$('recommendGenre').addEventListener('change', () => {
|
|
cancel();
|
|
genre = $('recommendGenre').value;
|
|
changed();
|
|
render();
|
|
if (!current().loaded) load();
|
|
});
|
|
$('recommendMoreBtn').addEventListener('click', () => load());
|
|
$('recommendRefreshBtn').addEventListener('click', () => load(true));
|
|
}
|
|
return {init, get enabled() {return enabled;}, items: () => current().items, all: () => Array.from(feeds.values()).flatMap(feed => feed.items)};
|
|
}
|