Public Access
feat(library): show set total playtime on dashboard cards
Most played and currently playing cards show the total playtime of the game set. Use the own minutes when the API sends no total.
This commit is contained in:
1 parent
f1a6bc0443
commit
9adc606c57
5 files changed
+44
-6
No files matched your search
@@ -68,7 +68,7 @@ To Be Played is split into three repositories:
|
|||||||
- **Detail per platform**: the details page of a game with several platforms (or a PlayStation title on a Steam game)
|
- **Detail per platform**: the details page of a game with several platforms (or a PlayStation title on a Steam game)
|
||||||
shows `PlatformsCard` above the editions: one row per platform with playtime, last played and the achievement or
|
shows `PlatformsCard` above the editions: one row per platform with playtime, last played and the achievement or
|
||||||
trophy progress of that platform, a total row, and a link to the edition whose page lists the achievements. The hero
|
trophy progress of that platform, a total row, and a link to the edition whose page lists the achievements. The hero
|
||||||
playtime and the library card show the set total (`totalPlaytimeMinutes`); sorting and the stats stay per edition.
|
playtime and the library card show the set total (`totalPlaytimeMinutes`); sorting, stats, dashboard cards and the public profile use the set total (Core V45); per row stay the playtime history, rewards and the played / never started counts.
|
||||||
- **Add a game by name** for games outside Steam (PlayStation, Switch, Xbox, physical): search IGDB, pick a hit,
|
- **Add a game by name** for games outside Steam (PlayStation, Switch, Xbox, physical): search IGDB, pick a hit,
|
||||||
a platform and optional hours. Works without a Steam ID. Games already in ToBePlayed come first. Check several
|
a platform and optional hours. Works without a Steam ID. Games already in ToBePlayed come first. Check several
|
||||||
hits to add them at once through the import review.
|
hits to add them at once through the import review.
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export default function CurrentlyPlayingCard({games, syncing, wide}) {
|
|||||||
<FitText text={g.name ?? `App ${g.appid}`}/>
|
<FitText text={g.name ?? `App ${g.appid}`}/>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-muted-foreground tabular-nums">
|
<div className="text-xs text-muted-foreground tabular-nums">
|
||||||
{formatPlaytime(g.playtimeMinutes)}
|
{formatPlaytime(g.totalPlaytimeMinutes ?? g.playtimeMinutes)}
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
// @vitest-environment jsdom
|
||||||
|
import {afterEach, describe, expect, it, vi} from 'vitest';
|
||||||
|
import {cleanup, render, screen} from '@testing-library/react';
|
||||||
|
import {MemoryRouter} from 'react-router-dom';
|
||||||
|
import CurrentlyPlayingCard from './CurrentlyPlayingCard.jsx';
|
||||||
|
|
||||||
|
vi.mock('react-i18next', () => import('../test/i18n-mock.js'));
|
||||||
|
|
||||||
|
describe('CurrentlyPlayingCard', () => {
|
||||||
|
afterEach(cleanup);
|
||||||
|
|
||||||
|
it('shows the set total of a game, not the minutes of its own row', () => {
|
||||||
|
render(<MemoryRouter><CurrentlyPlayingCard games={[{appid: 620, name: 'Portal 2', playtimeMinutes: 100,
|
||||||
|
totalPlaytimeMinutes: 600, status: 'PLAYING'}]}/></MemoryRouter>);
|
||||||
|
|
||||||
|
expect(screen.getByText('10h')).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -12,15 +12,17 @@ import {formatPlaytime} from '../utils/games.js';
|
|||||||
import {coverEffect} from '../utils/rewards.js';
|
import {coverEffect} from '../utils/rewards.js';
|
||||||
import {playChart} from '../utils/statCharts.js';
|
import {playChart} from '../utils/statCharts.js';
|
||||||
|
|
||||||
|
const total = (g) => g.totalPlaytimeMinutes ?? g.playtimeMinutes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param chart LIST, BARS, PIE or DONUT
|
* @param chart LIST, BARS, PIE or DONUT
|
||||||
* @param onChart `(form)`: shows the picker
|
* @param onChart `(form)`: shows the picker
|
||||||
*/
|
*/
|
||||||
export default function MostPlayedCard({games, syncing, equipped, chart = 'LIST', onChart}) {
|
export default function MostPlayedCard({games, syncing, equipped, chart = 'LIST', onChart}) {
|
||||||
const {t} = useTranslation();
|
const {t} = useTranslation();
|
||||||
const mapped = games.map((g) => ({appid: g.appid, name: g.name, minutes: g.playtimeMinutes}));
|
const mapped = games.map((g) => ({appid: g.appid, name: g.name, minutes: total(g)}));
|
||||||
const form = playChart(chart, mapped);
|
const form = playChart(chart, mapped);
|
||||||
const maxMinutes = games[0]?.playtimeMinutes || 1;
|
const maxMinutes = mapped[0]?.minutes || 1;
|
||||||
return (
|
return (
|
||||||
<Card className="gap-0 py-0">
|
<Card className="gap-0 py-0">
|
||||||
<div className="flex flex-wrap items-center justify-between gap-x-3 px-5 py-4 border-b">
|
<div className="flex flex-wrap items-center justify-between gap-x-3 px-5 py-4 border-b">
|
||||||
@@ -62,12 +64,12 @@ export default function MostPlayedCard({games, syncing, equipped, chart = 'LIST'
|
|||||||
<div className="mt-2 h-1.5 rounded-full bg-muted">
|
<div className="mt-2 h-1.5 rounded-full bg-muted">
|
||||||
<div
|
<div
|
||||||
className="h-full rounded-full bg-primary"
|
className="h-full rounded-full bg-primary"
|
||||||
style={{width: `${Math.max(2, (g.playtimeMinutes / maxMinutes) * 100)}%`}}
|
style={{width: `${Math.max(2, (total(g) / maxMinutes) * 100)}%`}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span className="w-12 sm:w-14 text-right text-sm font-medium text-muted-foreground tabular-nums">
|
<span className="w-12 sm:w-14 text-right text-sm font-medium text-muted-foreground tabular-nums">
|
||||||
{formatPlaytime(g.playtimeMinutes)}
|
{formatPlaytime(total(g))}
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
// @vitest-environment jsdom
|
||||||
|
import {afterEach, describe, expect, it, vi} from 'vitest';
|
||||||
|
import {cleanup, render, screen} from '@testing-library/react';
|
||||||
|
import {MemoryRouter} from 'react-router-dom';
|
||||||
|
import MostPlayedCard from './MostPlayedCard.jsx';
|
||||||
|
|
||||||
|
vi.mock('react-i18next', () => import('../test/i18n-mock.js'));
|
||||||
|
|
||||||
|
describe('MostPlayedCard', () => {
|
||||||
|
afterEach(cleanup);
|
||||||
|
|
||||||
|
it('shows the set total of a game, not the minutes of its own row', () => {
|
||||||
|
render(<MemoryRouter><MostPlayedCard games={[{appid: 620, name: 'Portal 2', playtimeMinutes: 100,
|
||||||
|
totalPlaytimeMinutes: 600, status: 'PLAYING'}]}/></MemoryRouter>);
|
||||||
|
|
||||||
|
expect(screen.getByText('10h')).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in new issue
Block a user