mirror of
https://github.com/gabehf/Koito.git
synced 2026-03-07 13:38:15 -08:00
fix: use sql rank (#148)
This commit is contained in:
parent
aa7fddd518
commit
d2d6924e05
20 changed files with 386 additions and 270 deletions
|
|
@ -48,32 +48,32 @@ async function getLastListens(
|
|||
|
||||
async function getTopTracks(
|
||||
args: getItemsArgs
|
||||
): Promise<PaginatedResponse<Track>> {
|
||||
): Promise<PaginatedResponse<Ranked<Track>>> {
|
||||
let url = `/apis/web/v1/top-tracks?period=${args.period}&limit=${args.limit}&page=${args.page}`;
|
||||
|
||||
if (args.artist_id) url += `&artist_id=${args.artist_id}`;
|
||||
else if (args.album_id) url += `&album_id=${args.album_id}`;
|
||||
|
||||
const r = await fetch(url);
|
||||
return handleJson<PaginatedResponse<Track>>(r);
|
||||
return handleJson<PaginatedResponse<Ranked<Track>>>(r);
|
||||
}
|
||||
|
||||
async function getTopAlbums(
|
||||
args: getItemsArgs
|
||||
): Promise<PaginatedResponse<Album>> {
|
||||
): Promise<PaginatedResponse<Ranked<Album>>> {
|
||||
let url = `/apis/web/v1/top-albums?period=${args.period}&limit=${args.limit}&page=${args.page}`;
|
||||
if (args.artist_id) url += `&artist_id=${args.artist_id}`;
|
||||
|
||||
const r = await fetch(url);
|
||||
return handleJson<PaginatedResponse<Album>>(r);
|
||||
return handleJson<PaginatedResponse<Ranked<Album>>>(r);
|
||||
}
|
||||
|
||||
async function getTopArtists(
|
||||
args: getItemsArgs
|
||||
): Promise<PaginatedResponse<Artist>> {
|
||||
): Promise<PaginatedResponse<Ranked<Artist>>> {
|
||||
const url = `/apis/web/v1/top-artists?period=${args.period}&limit=${args.limit}&page=${args.page}`;
|
||||
const r = await fetch(url);
|
||||
return handleJson<PaginatedResponse<Artist>>(r);
|
||||
return handleJson<PaginatedResponse<Ranked<Artist>>>(r);
|
||||
}
|
||||
|
||||
async function getActivity(
|
||||
|
|
@ -407,6 +407,10 @@ type PaginatedResponse<T> = {
|
|||
current_page: number;
|
||||
items_per_page: number;
|
||||
};
|
||||
type Ranked<T> = {
|
||||
item: T;
|
||||
rank: number;
|
||||
};
|
||||
type ListenActivityItem = {
|
||||
start_time: Date;
|
||||
listens: number;
|
||||
|
|
@ -480,6 +484,7 @@ export type {
|
|||
Listen,
|
||||
SearchResponse,
|
||||
PaginatedResponse,
|
||||
Ranked,
|
||||
ListenActivityItem,
|
||||
InterestBucket,
|
||||
User,
|
||||
|
|
|
|||
|
|
@ -6,11 +6,12 @@ import {
|
|||
type Artist,
|
||||
type Track,
|
||||
type PaginatedResponse,
|
||||
type Ranked,
|
||||
} from "api/api";
|
||||
|
||||
type Item = Album | Track | Artist;
|
||||
|
||||
interface Props<T extends Item> {
|
||||
interface Props<T extends Ranked<Item>> {
|
||||
data: PaginatedResponse<T>;
|
||||
separators?: ConstrainBoolean;
|
||||
ranked?: boolean;
|
||||
|
|
@ -18,33 +19,17 @@ interface Props<T extends Item> {
|
|||
className?: string;
|
||||
}
|
||||
|
||||
export default function TopItemList<T extends Item>({
|
||||
export default function TopItemList<T extends Ranked<Item>>({
|
||||
data,
|
||||
separators,
|
||||
type,
|
||||
className,
|
||||
ranked,
|
||||
}: Props<T>) {
|
||||
const currentParams = new URLSearchParams(location.search);
|
||||
const page = Math.max(parseInt(currentParams.get("page") || "1"), 1);
|
||||
|
||||
let lastRank = 0;
|
||||
|
||||
const calculateRank = (data: Item[], page: number, index: number): number => {
|
||||
if (
|
||||
index === 0 ||
|
||||
data[index] == undefined ||
|
||||
!(data[index].listen_count === data[index - 1].listen_count)
|
||||
) {
|
||||
lastRank = index + 1 + (page - 1) * 100;
|
||||
}
|
||||
return lastRank;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`flex flex-col gap-1 ${className} min-w-[200px]`}>
|
||||
{data.items.map((item, index) => {
|
||||
const key = `${type}-${item.id}`;
|
||||
const key = `${type}-${item.item.id}`;
|
||||
return (
|
||||
<div
|
||||
key={key}
|
||||
|
|
@ -57,10 +42,10 @@ export default function TopItemList<T extends Item>({
|
|||
>
|
||||
<ItemCard
|
||||
ranked={ranked}
|
||||
rank={calculateRank(data.items, page, index)}
|
||||
item={item}
|
||||
rank={item.rank}
|
||||
item={item.item}
|
||||
type={type}
|
||||
key={type + item.id}
|
||||
key={type + item.item.id}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import TopItemList from "~/components/TopItemList";
|
||||
import ChartLayout from "./ChartLayout";
|
||||
import { useLoaderData, type LoaderFunctionArgs } from "react-router";
|
||||
import { type Album, type PaginatedResponse } from "api/api";
|
||||
import { type Album, type PaginatedResponse, type Ranked } from "api/api";
|
||||
|
||||
export async function clientLoader({ request }: LoaderFunctionArgs) {
|
||||
const url = new URL(request.url);
|
||||
|
|
@ -21,7 +21,7 @@ export async function clientLoader({ request }: LoaderFunctionArgs) {
|
|||
|
||||
export default function AlbumChart() {
|
||||
const { top_albums: initialData } = useLoaderData<{
|
||||
top_albums: PaginatedResponse<Album>;
|
||||
top_albums: PaginatedResponse<Ranked<Album>>;
|
||||
}>();
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import TopItemList from "~/components/TopItemList";
|
||||
import ChartLayout from "./ChartLayout";
|
||||
import { useLoaderData, type LoaderFunctionArgs } from "react-router";
|
||||
import { type Album, type PaginatedResponse } from "api/api";
|
||||
import { type Album, type PaginatedResponse, type Ranked } from "api/api";
|
||||
|
||||
export async function clientLoader({ request }: LoaderFunctionArgs) {
|
||||
const url = new URL(request.url);
|
||||
|
|
@ -21,7 +21,7 @@ export async function clientLoader({ request }: LoaderFunctionArgs) {
|
|||
|
||||
export default function Artist() {
|
||||
const { top_artists: initialData } = useLoaderData<{
|
||||
top_artists: PaginatedResponse<Album>;
|
||||
top_artists: PaginatedResponse<Ranked<Album>>;
|
||||
}>();
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import TopItemList from "~/components/TopItemList";
|
||||
import ChartLayout from "./ChartLayout";
|
||||
import { useLoaderData, type LoaderFunctionArgs } from "react-router";
|
||||
import { type Album, type PaginatedResponse } from "api/api";
|
||||
import { type Track, type PaginatedResponse, type Ranked } from "api/api";
|
||||
|
||||
export async function clientLoader({ request }: LoaderFunctionArgs) {
|
||||
const url = new URL(request.url);
|
||||
|
|
@ -15,13 +15,13 @@ export async function clientLoader({ request }: LoaderFunctionArgs) {
|
|||
throw new Response("Failed to load top tracks", { status: 500 });
|
||||
}
|
||||
|
||||
const top_tracks: PaginatedResponse<Album> = await res.json();
|
||||
const top_tracks: PaginatedResponse<Track> = await res.json();
|
||||
return { top_tracks };
|
||||
}
|
||||
|
||||
export default function TrackChart() {
|
||||
const { top_tracks: initialData } = useLoaderData<{
|
||||
top_tracks: PaginatedResponse<Album>;
|
||||
top_tracks: PaginatedResponse<Ranked<Track>>;
|
||||
}>();
|
||||
|
||||
return (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue