/* ============================================================================= * SGLV PTW · Tab: 全部游戏列表 (All Games) * ----------------------------------------------------------------------------- * 迁移自主脚本 mountPlaytimeAllList(v2.20.0 :15850-16058,209 行) * 迁移纪律:纯搬运不改逻辑。唯一改动 = 依赖改为经 ctx.host 注入。 * * 依赖注入对照表: * state.ownedGames → ctx.host.getOwnedGames() * isGameOwnedByMe(g) → ctx.host.isGameOwnedByMe(g) * ptGetAcquiredMap() → ctx.host.getAcquiredMap() * buildListToolbar / hbarRow / emptyHtml / paginate / renderPagination * → ctx.host.* * escHtml / T / isZh → ctx.host.* * getGameIconUrl → ctx.host.getGameIconUrl * applyGameAliases → ctx.host.applyGameAliases * sglvToast → ctx.host.showToast * * 视图态:原实现用闭包变量(page/query/sortKey/sortAsc/hideUnplayed/hideShared), * 此处改为读写 ctx.view —— 换取跨 Tab 联动 + 刷新不丢。 * ========================================================================== */ (function (root) { 'use strict'; const PT_ALL_PAGE_SIZE = 50; function mount(container, ctx) { const H = ctx.host; const T = H.T; const isZh = H.isZh; const escHtml = H.escHtml; const sortedGames = ctx.getGames(); const view = ctx.view; const nameCmp = (a, b) => String(a.name || '').localeCompare( String(b.name || ''), isZh ? 'zh-Hans-CN' : 'en', { numeric: true, sensitivity: 'base' }); let acquiredMap = view.acquiredMap || null; function mountList(container2, data) { let sortKey = view.sortKey || 'playtime'; let sortAsc = !!view.sortAsc; let hideUnplayed = !!view.hideUnplayed; let hideShared = !!view.hideShared; let page = view.page || 1; let query = view.query || ''; const hasSharedGames = data.some(g => !H.isGameOwnedByMe(g)); function sortBy(key) { const m = acquiredMap || {}; const hasAcq = Object.keys(m).length > 0; const acqDesc = (a, b) => (hasAcq ? ((m[b.appid] || 0) - (m[a.appid] || 0)) : 0); let cmp; if (key === 'recent') { cmp = (a, b) => (b.lastPlayed || 0) - (a.lastPlayed || 0) || (b.playtime || 0) - (a.playtime || 0) || acqDesc(a, b) || nameCmp(a, b); } else if (key === 'name') { cmp = (a, b) => nameCmp(a, b) || (b.playtime || 0) - (a.playtime || 0); } else if (key === 'acquired') { cmp = (a, b) => acqDesc(a, b) || (b.playtime || 0) - (a.playtime || 0) || nameCmp(a, b); } else { cmp = (a, b) => (b.playtime || 0) - (a.playtime || 0) || (b.lastPlayed || 0) - (a.lastPlayed || 0) || acqDesc(a, b) || nameCmp(a, b); } const mul = sortAsc ? -1 : 1; data.sort((a, b) => mul * cmp(a, b)); } const filterChipsHtml = ` ${hasSharedGames ? `` : ''} `; const toolbar = H.buildListToolbar({ cls: 'sglv-pt-all-toolbar', search: { id: 'sglv-pt-search', placeholder: T.ptSearchPlaceholder, style: 'flex:1;min-width:140px' }, sort: { id: 'sglv-pt-sort', value: sortKey, options: [ { value: 'playtime', label: T.ptSortPlaytime }, { value: 'recent', label: T.ptSortRecent }, { value: 'acquired', label: T.ptSortAcquired }, { value: 'name', label: T.ptSortName }, ], }, extraHtml: `${filterChipsHtml} `, onChange: ({ type, value }) => { if (type === 'input') { query = value; ctx.setView({ query }); renderList(1); } else if (type === 'sort') { applySort(value).then(() => renderList(1)); } }, }); container2.appendChild(toolbar); const hintEl = document.createElement('div'); hintEl.className = 'sglv-pt-all-hint'; hintEl.style.display = 'none'; container2.appendChild(hintEl); const listEl = document.createElement('div'); listEl.className = 'sglv-pt-all-list'; listEl.id = 'sglv-pt-all-list'; const pagEl = document.createElement('div'); pagEl.className = 'sglv-pagination'; pagEl.id = 'sglv-pt-pagination'; container2.appendChild(listEl); container2.appendChild(pagEl); // 事件委托:整页 HTML 一次性渲染后仅保留 1 个 click 监听 listEl.addEventListener('click', e => { const row = e.target.closest('.sglv-pt-all-item'); if (row && row.dataset.appid) { root.open(`https://store.steampowered.com/app/${row.dataset.appid}`, '_blank'); } }); const countEl = toolbar.querySelector('#sglv-pt-count'); const sortSel = toolbar.querySelector('#sglv-pt-sort'); const hideUnplayedBtn = toolbar.querySelector('#sglv-pt-hide-unplayed'); if (hideUnplayedBtn) { if (hideUnplayed) hideUnplayedBtn.classList.add('active'); hideUnplayedBtn.addEventListener('click', () => { hideUnplayed = !hideUnplayed; hideUnplayedBtn.classList.toggle('active', hideUnplayed); ctx.setView({ hideUnplayed }); renderList(1); }); } const hideSharedBtn = toolbar.querySelector('#sglv-pt-hide-shared'); if (hideSharedBtn) { if (hideShared) hideSharedBtn.classList.add('active'); hideSharedBtn.addEventListener('click', () => { hideShared = !hideShared; hideSharedBtn.classList.toggle('active', hideShared); ctx.setView({ hideShared }); renderList(1); }); } // v2.20.2 (T3.6): 导出当前视图态(全量筛选结果,非当前页)→ CSV const exportBtn = toolbar.querySelector('#sglv-pt-export'); if (exportBtn) { exportBtn.addEventListener('click', () => { const rows = currentViewGames().map(g => [ g.appid, g.name || '', Math.round((g.playtime || 0) / 60 * 10) / 10, g.lastPlayed ? new Date(g.lastPlayed * 1000).toISOString().slice(0, 10) : '', H.isGameOwnedByMe(g) ? 1 : 0, ]); const headers = ['appid', 'name', 'playtime_hours', 'last_played', 'owned_by_me']; if (typeof ctx.exportCSV === 'function') { ctx.exportCSV(headers, rows, 'sglv-games'); } else if (typeof H.showToast === 'function') { H.showToast(isZh ? '导出功能不可用' : 'Export unavailable', 'warn'); } }); } const sortDirBtn = toolbar.querySelector('#sglv-pt-sort-dir'); if (sortDirBtn) { if (sortAsc) { sortDirBtn.textContent = T.ptSortAsc; sortDirBtn.title = T.ptSortAsc; sortDirBtn.classList.add('active'); } sortDirBtn.addEventListener('click', () => { sortAsc = !sortAsc; sortDirBtn.textContent = sortAsc ? T.ptSortAsc : T.ptSortDesc; sortDirBtn.title = sortAsc ? T.ptSortAsc : T.ptSortDesc; sortDirBtn.classList.toggle('active', sortAsc); sortBy(sortKey); ctx.setView({ sortAsc }); renderList(1); }); } const resetToPlaytime = () => { sortKey = 'playtime'; if (sortSel) sortSel.value = 'playtime'; sortBy('playtime'); ctx.setView({ sortKey }); }; // 入库时间排序依赖家庭组数据(异步);无数据 / 失败回退时长排序 function applySort(key) { if (key === 'acquired' && !acquiredMap) { listEl.innerHTML = `