Public Access
feat(stats): add stats page and pile trend
Show the stat groups on /stats, the pile trend on the dashboard card, one switch per group in the settings, and the shared groups on the public profile.
This commit is contained in:
1 parent
83bbc2730d
commit
9d1c7bb40d
31 files changed
+978
-11
No files matched your search
@@ -0,0 +1,52 @@
|
||||
import {apiFetch} from './api.js';
|
||||
|
||||
// The groups a user can show on the public profile; the overview numbers are always there.
|
||||
export const PROFILE_STAT_GROUPS = ['PILE', 'TREND', 'TOP_GAMES', 'TASTE', 'HABITS', 'FINISHED'];
|
||||
|
||||
/**
|
||||
* PUT /api/users/settings/profile-stats on Auth. The caller handles the response status.
|
||||
*/
|
||||
export const saveProfileStats = (groups) =>
|
||||
apiFetch(`${import.meta.env.VITE_API_URL}/api/users/settings/profile-stats`, {
|
||||
method: 'PUT',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({groups}),
|
||||
});
|
||||
|
||||
/**
|
||||
* @param groups comma separated stat groups of Core (StatGroup); empty for all
|
||||
*/
|
||||
export const statsUrl = (groups) => {
|
||||
const query = groups ? `?groups=${groups}` : '';
|
||||
return `${import.meta.env.VITE_CORE_API_URL}/api/library/stats${query}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* The i18n key of an IGDB genre name, e.g. "Role-playing (RPG)" -> "ROLE_PLAYING_RPG". Callers fall back to the
|
||||
* name, so a genre IGDB adds later still shows.
|
||||
*/
|
||||
export const genreKey = (name) => name.toUpperCase().replace(/[^A-Z0-9]+/g, '_').replace(/^_|_$/g, '');
|
||||
|
||||
export const formatDecimal = (value, locale, digits = 1) =>
|
||||
value.toLocaleString(locale, {minimumFractionDigits: 0, maximumFractionDigits: digits});
|
||||
|
||||
export const formatPercent = (share, locale) =>
|
||||
share.toLocaleString(locale, {style: 'percent', maximumFractionDigits: 0});
|
||||
|
||||
const DAY_MS = 24 * 60 * 60 * 1000;
|
||||
const dayNumber = (day) => Math.round(Date.parse(`${day}T00:00:00Z`) / DAY_MS);
|
||||
|
||||
/**
|
||||
* Points of an SVG polyline for the pile per day, oldest left. x follows the date, so days without a snapshot
|
||||
* keep their width. The y axis starts at 0, so a small change looks small. One day gives a flat line over the
|
||||
* whole width.
|
||||
*/
|
||||
export const trendPoints = (days, width, height) => {
|
||||
if (days.length === 0) return '';
|
||||
const max = Math.max(1, ...days.map((d) => d.backlogGames));
|
||||
const y = (d) => height - (d.backlogGames / max) * height;
|
||||
if (days.length === 1) return `0,${y(days[0])} ${width},${y(days[0])}`;
|
||||
const first = dayNumber(days[0].day);
|
||||
const span = dayNumber(days[days.length - 1].day) - first;
|
||||
return days.map((d) => `${((dayNumber(d.day) - first) / span) * width},${y(d)}`).join(' ');
|
||||
};
|
||||
Reference in new issue
Block a user