Public Access
feat(play-next): add series and studio rows
This commit is contained in:
1 parent
df3d6d2537
commit
e041c71cf8
5 files changed
+41
-8
No files matched your search
@@ -28,8 +28,9 @@ To Be Played is split into three repositories:
|
|||||||
- **Coming games**: a game that is not released yet (and nobody played) shows a "Coming <date>" badge in the library, on
|
- **Coming games**: a game that is not released yet (and nobody played) shows a "Coming <date>" badge in the library, on
|
||||||
the details page, in the stat game lists and on the public profile. The pile counts only the games Core sends in
|
the details page, in the stat game lists and on the public profile. The pile counts only the games Core sends in
|
||||||
`pileGames`, so coming games are not on it; Core never picks them.
|
`pileGames`, so coming games are not on it; Core never picks them.
|
||||||
- **What to play next** (`/play-next`): four rows of up to 3 backlog games each: up next (your own backlog order),
|
- **What to play next** (`/play-next`): six rows of up to 3 backlog games each: up next (your own backlog order),
|
||||||
because you played (a similar game for each of your 3 most played), longest on your list, and mood picks. A time
|
next in series (the next part of a series you played), because you played (a similar game for each of your 3 most
|
||||||
|
played), same studio (a developer you played), longest on your list, and mood picks. A time
|
||||||
budget (up to 5 / 15 / 40 h or any) filters all rows; a mood (story, co-op, chill, action, scary from IGDB; speedrun
|
budget (up to 5 / 15 / 40 h or any) filters all rows; a mood (story, co-op, chill, action, scary from IGDB; speedrun
|
||||||
from speedrun.com) only the mood row. Each row has its own reroll without repeats, and "Start playing" in one click.
|
from speedrun.com) only the mood row. Each row has its own reroll without repeats, and "Start playing" in one click.
|
||||||
On a phone each row scrolls sideways.
|
On a phone each row scrolls sideways.
|
||||||
|
|||||||
+5
-1
@@ -431,14 +431,18 @@
|
|||||||
},
|
},
|
||||||
"PLAY_NEXT": {
|
"PLAY_NEXT": {
|
||||||
"TITLE": "Was spiele ich als Nächstes?",
|
"TITLE": "Was spiele ich als Nächstes?",
|
||||||
"SUBTITLE": "Spiele aus deinem Backlog in vier Reihen: als Nächstes in deiner Reihenfolge, wie Spiele, die du viel gespielt hast, am längsten wartend und nach Stimmung. Wähle eine Dauer für alle Reihen und eine Stimmung für die letzte.",
|
"SUBTITLE": "Spiele aus deinem Backlog in sechs Reihen: als Nächstes in deiner Reihenfolge, der nächste Teil einer Reihe, die du gespielt hast, wie Spiele, die du viel gespielt hast, von einem Studio, das du gespielt hast, am längsten wartend und nach Stimmung. Wähle eine Dauer für alle Reihen und eine Stimmung für die letzte.",
|
||||||
"REASON": {
|
"REASON": {
|
||||||
"SIMILAR": "Weil du {{name}} gespielt hast ({{playtime}})",
|
"SIMILAR": "Weil du {{name}} gespielt hast ({{playtime}})",
|
||||||
|
"SERIES": "Reihe: {{name}}",
|
||||||
|
"STUDIO": "Von {{name}}",
|
||||||
"LONGEST": "Auf deiner Liste seit {{date}}"
|
"LONGEST": "Auf deiner Liste seit {{date}}"
|
||||||
},
|
},
|
||||||
"CATEGORY": {
|
"CATEGORY": {
|
||||||
"NEXT_UP": "Als Nächstes",
|
"NEXT_UP": "Als Nächstes",
|
||||||
|
"SERIES": "Nächster Teil der Reihe",
|
||||||
"SIMILAR": "Weil du gespielt hast",
|
"SIMILAR": "Weil du gespielt hast",
|
||||||
|
"STUDIO": "Vom selben Studio",
|
||||||
"LONGEST": "Am längsten auf deiner Liste",
|
"LONGEST": "Am längsten auf deiner Liste",
|
||||||
"RANDOM": "Zufallstreffer",
|
"RANDOM": "Zufallstreffer",
|
||||||
"MOOD": "Stimmung: {{mood}}"
|
"MOOD": "Stimmung: {{mood}}"
|
||||||
|
|||||||
+5
-1
@@ -431,14 +431,18 @@
|
|||||||
},
|
},
|
||||||
"PLAY_NEXT": {
|
"PLAY_NEXT": {
|
||||||
"TITLE": "What to play next",
|
"TITLE": "What to play next",
|
||||||
"SUBTITLE": "Games from your backlog in four rows: next in your order, like games you played a lot, waiting longest, and by mood. Pick a length for all rows and a mood for the last one.",
|
"SUBTITLE": "Games from your backlog in six rows: next in your order, the next part of a series you played, like games you played a lot, from a studio you played, waiting longest, and by mood. Pick a length for all rows and a mood for the last one.",
|
||||||
"REASON": {
|
"REASON": {
|
||||||
"SIMILAR": "Because you played {{name}} ({{playtime}})",
|
"SIMILAR": "Because you played {{name}} ({{playtime}})",
|
||||||
|
"SERIES": "Series: {{name}}",
|
||||||
|
"STUDIO": "From {{name}}",
|
||||||
"LONGEST": "On your list since {{date}}"
|
"LONGEST": "On your list since {{date}}"
|
||||||
},
|
},
|
||||||
"CATEGORY": {
|
"CATEGORY": {
|
||||||
"NEXT_UP": "Up next",
|
"NEXT_UP": "Up next",
|
||||||
|
"SERIES": "Next in series",
|
||||||
"SIMILAR": "Because you played",
|
"SIMILAR": "Because you played",
|
||||||
|
"STUDIO": "Same studio",
|
||||||
"LONGEST": "Longest on your list",
|
"LONGEST": "Longest on your list",
|
||||||
"RANDOM": "Random picks",
|
"RANDOM": "Random picks",
|
||||||
"MOOD": "Mood: {{mood}}"
|
"MOOD": "Mood: {{mood}}"
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const BUDGETS = [
|
|||||||
// games without that data.
|
// games without that data.
|
||||||
const MOODS = ['ANY', 'STORY', 'COOP', 'CHILL', 'ACTION', 'SCARY', 'SPEEDRUN'];
|
const MOODS = ['ANY', 'STORY', 'COOP', 'CHILL', 'ACTION', 'SCARY', 'SPEEDRUN'];
|
||||||
// The rows in the order Core sends them; RANDOM is the mood row.
|
// The rows in the order Core sends them; RANDOM is the mood row.
|
||||||
const ROW_ORDER = ['NEXT_UP', 'SIMILAR', 'LONGEST', 'RANDOM'];
|
const ROW_ORDER = ['NEXT_UP', 'SERIES', 'SIMILAR', 'STUDIO', 'LONGEST', 'RANDOM'];
|
||||||
// The backend reads only this many excludes. With a bigger backlog the oldest seen games can come back.
|
// The backend reads only this many excludes. With a bigger backlog the oldest seen games can come back.
|
||||||
const MAX_EXCLUDES = 100;
|
const MAX_EXCLUDES = 100;
|
||||||
const ROW_CLASS = 'flex gap-4 overflow-x-auto snap-x snap-mandatory pb-2 sm:grid sm:grid-cols-3 sm:overflow-visible';
|
const ROW_CLASS = 'flex gap-4 overflow-x-auto snap-x snap-mandatory pb-2 sm:grid sm:grid-cols-3 sm:overflow-visible';
|
||||||
@@ -253,6 +253,12 @@ const reasonText = (reason, t, language) => {
|
|||||||
playtime: formatPlaytime(reason.anchorPlaytimeMinutes),
|
playtime: formatPlaytime(reason.anchorPlaytimeMinutes),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (reason?.source === 'SERIES') {
|
||||||
|
return t('PLAY_NEXT.REASON.SERIES', {name: reason.seriesName});
|
||||||
|
}
|
||||||
|
if (reason?.source === 'STUDIO') {
|
||||||
|
return t('PLAY_NEXT.REASON.STUDIO', {name: reason.studioName});
|
||||||
|
}
|
||||||
if (reason?.source === 'LONGEST') {
|
if (reason?.source === 'LONGEST') {
|
||||||
return t('PLAY_NEXT.REASON.LONGEST', {date: formatDate(reason.firstSeenAt, language, {dateStyle: 'medium'})});
|
return t('PLAY_NEXT.REASON.LONGEST', {date: formatDate(reason.firstSeenAt, language, {dateStyle: 'medium'})});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ const game = (appid, extra = {}) => ({
|
|||||||
const row = (source, ...games) => ({source, games});
|
const row = (source, ...games) => ({source, games});
|
||||||
const ALL = () => [
|
const ALL = () => [
|
||||||
row('NEXT_UP', game(1), game(2)),
|
row('NEXT_UP', game(1), game(2)),
|
||||||
|
row('SERIES', game(6, {reason: {source: 'SERIES', seriesName: 'Mass Effect'}})),
|
||||||
row('SIMILAR', game(3, {reason: {source: 'SIMILAR', anchorAppid: 9, anchorName: 'Celeste', anchorPlaytimeMinutes: 600}})),
|
row('SIMILAR', game(3, {reason: {source: 'SIMILAR', anchorAppid: 9, anchorName: 'Celeste', anchorPlaytimeMinutes: 600}})),
|
||||||
|
row('STUDIO', game(8, {reason: {source: 'STUDIO', studioName: 'BioWare'}})),
|
||||||
row('LONGEST', game(4, {reason: {source: 'LONGEST', firstSeenAt: '2024-03-12T10:00:00Z'}})),
|
row('LONGEST', game(4, {reason: {source: 'LONGEST', firstSeenAt: '2024-03-12T10:00:00Z'}})),
|
||||||
row('RANDOM', game(5, {reason: {source: 'RANDOM'}})),
|
row('RANDOM', game(5, {reason: {source: 'RANDOM'}})),
|
||||||
];
|
];
|
||||||
@@ -75,8 +77,8 @@ describe('PlayNextPage', () => {
|
|||||||
|
|
||||||
await screen.findByText('Game 1');
|
await screen.findByText('Game 1');
|
||||||
expect(screen.getAllByRole('heading', {level: 2}).map((h) => h.textContent)).toEqual([
|
expect(screen.getAllByRole('heading', {level: 2}).map((h) => h.textContent)).toEqual([
|
||||||
'PLAY_NEXT.CATEGORY.NEXT_UP', 'PLAY_NEXT.CATEGORY.SIMILAR', 'PLAY_NEXT.CATEGORY.LONGEST',
|
'PLAY_NEXT.CATEGORY.NEXT_UP', 'PLAY_NEXT.CATEGORY.SERIES', 'PLAY_NEXT.CATEGORY.SIMILAR',
|
||||||
'PLAY_NEXT.CATEGORY.RANDOM']);
|
'PLAY_NEXT.CATEGORY.STUDIO', 'PLAY_NEXT.CATEGORY.LONGEST', 'PLAY_NEXT.CATEGORY.RANDOM']);
|
||||||
const params = pickerUrls()[0];
|
const params = pickerUrls()[0];
|
||||||
expect(params.has('count')).toBe(false);
|
expect(params.has('count')).toBe(false);
|
||||||
expect(params.has('category')).toBe(false);
|
expect(params.has('category')).toBe(false);
|
||||||
@@ -175,6 +177,20 @@ describe('PlayNextPage', () => {
|
|||||||
expect(pickerUrls()[2].has('mood')).toBe(false);
|
expect(pickerUrls()[2].has('mood')).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('rerolls the series row with category=SERIES and without a mood', async () => {
|
||||||
|
mockRows(ALL(), {SERIES: [row('SERIES', game(10))]});
|
||||||
|
renderPage();
|
||||||
|
await screen.findByText('Game 1');
|
||||||
|
fireEvent.click(screen.getByRole('button', {name: 'PLAY_NEXT.MOOD.STORY'}));
|
||||||
|
await screen.findByText('Game 5');
|
||||||
|
|
||||||
|
fireEvent.click(rerollOf('PLAY_NEXT.CATEGORY.SERIES'));
|
||||||
|
|
||||||
|
await screen.findByText('Game 10');
|
||||||
|
expect(pickerUrls()[2].get('category')).toBe('SERIES');
|
||||||
|
expect(pickerUrls()[2].has('mood')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
it('starts a row over when it has no unseen game left', async () => {
|
it('starts a row over when it has no unseen game left', async () => {
|
||||||
mockBackend((params) => {
|
mockBackend((params) => {
|
||||||
if (!params.get('category')) return ALL();
|
if (!params.get('category')) return ALL();
|
||||||
@@ -228,12 +244,14 @@ describe('PlayNextPage', () => {
|
|||||||
expect(await screen.findByText('PLAY_NEXT.EMPTY_MOOD')).toBeTruthy();
|
expect(await screen.findByText('PLAY_NEXT.EMPTY_MOOD')).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows a reason line for the similar and longest rows only', async () => {
|
it('shows a reason line for the series, similar, studio and longest rows only', async () => {
|
||||||
mockRows();
|
mockRows();
|
||||||
|
|
||||||
renderPage();
|
renderPage();
|
||||||
|
|
||||||
expect(await screen.findByText('PLAY_NEXT.REASON.SIMILAR')).toBeTruthy();
|
expect(await screen.findByText('PLAY_NEXT.REASON.SIMILAR')).toBeTruthy();
|
||||||
|
expect(screen.getByText('PLAY_NEXT.REASON.SERIES')).toBeTruthy();
|
||||||
|
expect(screen.getByText('PLAY_NEXT.REASON.STUDIO')).toBeTruthy();
|
||||||
expect(screen.getByText('PLAY_NEXT.REASON.LONGEST')).toBeTruthy();
|
expect(screen.getByText('PLAY_NEXT.REASON.LONGEST')).toBeTruthy();
|
||||||
expect(screen.queryByText(/PLAY_NEXT\.REASON\.(RANDOM|NEXT_UP)/)).toBeNull();
|
expect(screen.queryByText(/PLAY_NEXT\.REASON\.(RANDOM|NEXT_UP)/)).toBeNull();
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in new issue
Block a user