diff --git a/assets/jsons/translations/de.json b/assets/jsons/translations/de.json index 295bd888..a4eb81c7 100644 --- a/assets/jsons/translations/de.json +++ b/assets/jsons/translations/de.json @@ -915,7 +915,8 @@ "tags": "Stichwörter", "specificities": "Allgemein", "requirements": "Anforderungen", - "exclude": "Ausschließen" + "exclude": "Ausschließen", + "leaderboard": "Rangliste" }, "map-types": { "accuracy": "Präzision", @@ -966,11 +967,16 @@ }, "map-specificities": { "automapper": "KI", - "ranked": "Ranked", "curated": "Curated", "verified": "Verifiziert", "fullSpread": "Full Spread" }, + "map-leaderboard": { + "All": "Alle", + "Ranked": "Ranked", + "BeatLeader": "BeatLeader", + "ScoreSaber": "ScoreSaber" + }, "map-excludes": { "installed": "Installiert" }, diff --git a/assets/jsons/translations/en.json b/assets/jsons/translations/en.json index faeaab7c..7919ac1c 100644 --- a/assets/jsons/translations/en.json +++ b/assets/jsons/translations/en.json @@ -910,7 +910,8 @@ "tags": "tags", "specificities": "general", "requirements": "requirements", - "exclude": "exclude" + "exclude": "exclude", + "leaderboard": "leaderboard" }, "map-types": { "accuracy": "accuracy", @@ -961,11 +962,16 @@ }, "map-specificities": { "automapper": "AI", - "ranked": "ranked", "curated": "curated", "verified": "verified", "fullSpread": "full spread" }, + "map-leaderboard": { + "All": "All", + "Ranked": "Ranked", + "BeatLeader": "BeatLeader", + "ScoreSaber": "ScoreSaber" + }, "map-excludes": { "installed": "installed" }, diff --git a/assets/jsons/translations/es.json b/assets/jsons/translations/es.json index 68713736..04eb4532 100644 --- a/assets/jsons/translations/es.json +++ b/assets/jsons/translations/es.json @@ -915,7 +915,8 @@ "tags": "tags", "specificities": "general", "requirements": "requisitos", - "exclude": "excluir" + "exclude": "excluir", + "leaderboard": "tabla de clasificación" }, "map-types": { "accuracy": "precisión", @@ -966,11 +967,16 @@ }, "map-specificities": { "automapper": "IA", - "ranked": "rankado", "curated": "recomendado", "verified": "verificado", "fullSpread": "panel completo" }, + "map-leaderboard": { + "All": "Todos", + "Ranked": "Rankado", + "BeatLeader": "BeatLeader", + "ScoreSaber": "ScoreSaber" + }, "map-excludes": { "installed": "instalado" }, diff --git a/assets/jsons/translations/fr.json b/assets/jsons/translations/fr.json index cab4b104..38d7f6dc 100644 --- a/assets/jsons/translations/fr.json +++ b/assets/jsons/translations/fr.json @@ -916,7 +916,8 @@ "tags": "tags", "specificities": "général", "requirements": "requis", - "exclude": "exclure" + "exclude": "exclure", + "leaderboard": "tableau des leaders" }, "map-types": { "accuracy": "précision", @@ -967,11 +968,16 @@ }, "map-specificities": { "automapper": "IA", - "ranked": "classée", "curated": "recommandée", "verified": "vérifiée", "fullSpread": "panel complet" }, + "map-leaderboard": { + "All": "Tout", + "Ranked": "Classée", + "BeatLeader": "BeatLeader", + "ScoreSaber": "ScoreSaber" + }, "map-excludes": { "installed": "installé" }, diff --git a/assets/jsons/translations/ja.json b/assets/jsons/translations/ja.json index c6855519..dbe7e9c6 100644 --- a/assets/jsons/translations/ja.json +++ b/assets/jsons/translations/ja.json @@ -915,7 +915,8 @@ "tags": "タグ", "specificities": "一般", "requirements": "要Mod", - "exclude": "除外する" + "exclude": "除外する", + "leaderboard": "ランキング" }, "map-types": { "accuracy": "正確", @@ -966,11 +967,16 @@ }, "map-specificities": { "automapper": "AI", - "ranked": "ランク入賞", "curated": "厳選済み", "verified": "認証済み", "fullSpread": "フルスプレッド" }, + "map-leaderboard": { + "All": "すべて", + "Ranked": "ランク入賞", + "BeatLeader": "BeatLeader", + "ScoreSaber": "ScoreSaber" + }, "map-excludes": { "installed": "インストール済み" }, diff --git a/assets/jsons/translations/ru.json b/assets/jsons/translations/ru.json index 8dca4978..7378fe8c 100644 --- a/assets/jsons/translations/ru.json +++ b/assets/jsons/translations/ru.json @@ -914,7 +914,8 @@ "tags": "тэги", "specificities": "основное", "requirements": "требуемые моды", - "exclude": "исключить" + "exclude": "исключить", + "leaderboard": "Таблица лидеров" }, "map-types": { "accuracy": "точность", @@ -965,11 +966,16 @@ }, "map-specificities": { "automapper": "ИИ", - "ranked": "с рейтингом", "curated": "оценённые куратором", "verified": "проверенные авторы", "fullSpread": "полный набор" }, + "map-leaderboard": { + "All": "все", + "Ranked": "с рейтингом", + "BeatLeader": "BeatLeader", + "ScoreSaber": "ScoreSaber" + }, "map-excludes": { "installed": "установленный" }, diff --git a/assets/jsons/translations/zh-tw.json b/assets/jsons/translations/zh-tw.json index 474b674f..ecab4dd4 100644 --- a/assets/jsons/translations/zh-tw.json +++ b/assets/jsons/translations/zh-tw.json @@ -915,7 +915,8 @@ "tags": "標籤", "specificities": "general", "requirements": "要求", - "exclude": "排除" + "exclude": "排除", + "leaderboard": "排行榜" }, "map-types": { "accuracy": "精確度", @@ -966,11 +967,16 @@ }, "map-specificities": { "automapper": "AI", - "ranked": "Ranked", "curated": "Curated", "verified": "Verified", "fullSpread": "Full spread" }, + "map-leaderboard": { + "All": "所有", + "Ranked": "排名的", + "BeatLeader": "BeatLeader", + "ScoreSaber": "ScoreSaber" + }, "map-excludes": { "installed": "已安裝" }, diff --git a/assets/jsons/translations/zh.json b/assets/jsons/translations/zh.json index 4b8ef7bd..7394b8db 100644 --- a/assets/jsons/translations/zh.json +++ b/assets/jsons/translations/zh.json @@ -915,7 +915,8 @@ "tags": "标签", "specificities": "general", "requirements": "要求", - "exclude": "排除" + "exclude": "排除", + "leaderboard": "排行榜" }, "map-types": { "accuracy": "精确度", @@ -966,11 +967,16 @@ }, "map-specificities": { "automapper": "AI", - "ranked": "ranked", "curated": "curated", "verified": "verified", "fullSpread": "full spread" }, + "map-leaderboard": { + "All": "所有", + "Ranked": "排名的", + "BeatLeader": "BeatLeader", + "ScoreSaber": "ScoreSaber" + }, "map-excludes": { "installed": "已安装" }, diff --git a/src/main/services/thrid-party/beat-saver/beat-saver-api.service.ts b/src/main/services/thrid-party/beat-saver/beat-saver-api.service.ts index 5659ccea..5747311e 100644 --- a/src/main/services/thrid-party/beat-saver/beat-saver-api.service.ts +++ b/src/main/services/thrid-party/beat-saver/beat-saver-api.service.ts @@ -1,5 +1,5 @@ import { BsvMapDetail } from "shared/models/maps"; -import { BsvPlaylistPage, MapFilter, PlaylistSearchParams, PlaylistSearchResponse, SearchParams, SearchResponse } from "shared/models/maps/beat-saver.model"; +import { BsvPlaylistPage, MapFilter, MapLeaderboard, PlaylistSearchParams, PlaylistSearchResponse, SearchParams, SearchResponse } from "shared/models/maps/beat-saver.model"; import { RequestService } from "../../request.service"; import { CustomError } from "shared/models/exceptions/custom-error.class"; @@ -32,6 +32,10 @@ export class BeatSaverApiService { return new URLSearchParams(); } + if (!filter.leaderboard) { + filter.leaderboard = MapLeaderboard.All; + } + const enbledTagsString = filter.enabledTags ? Array.from(filter.enabledTags) : null; const excludedTagsString = filter.excludedTags ? Array.from(filter.excludedTags).map(tag => `!${tag}`) : null; diff --git a/src/renderer/components/maps-playlists-panel/maps/filter-panel.component.tsx b/src/renderer/components/maps-playlists-panel/maps/filter-panel.component.tsx index 07a4fc3e..a86f1098 100644 --- a/src/renderer/components/maps-playlists-panel/maps/filter-panel.component.tsx +++ b/src/renderer/components/maps-playlists-panel/maps/filter-panel.component.tsx @@ -1,4 +1,4 @@ -import { BsvMapDetail, MapFilter, MapRequirement, MapSpecificity, MapStyle, MapTag, MapType } from "shared/models/maps/beat-saver.model"; +import { BsvMapDetail, MapFilter, MapLeaderboard, MapRequirement, MapSpecificity, MapStyle, MapTag, MapType } from "shared/models/maps/beat-saver.model"; import { motion } from "framer-motion"; import { MutableRefObject, useEffect, useRef, useState } from "react"; import { BsmCheckbox } from "../../shared/bsm-checkbox.component"; @@ -14,6 +14,8 @@ import { BsmLocalMap } from "shared/models/maps/bsm-local-map.interface"; import { SongDetails } from "shared/models/maps"; import formatDuration from "format-duration"; import { MapInfo } from "shared/models/maps/info/map-info.model"; +import { BsmSelect, BsmSelectOption } from "renderer/components/shared/bsm-select.component"; +import { useConstant } from "renderer/hooks/use-constant.hook"; export type Props = { className?: string; @@ -45,6 +47,13 @@ export function FilterPanel({ className, ref, playlist = false, filter, localDat const isTagActivated = (tag: MapTag): boolean => filter?.enabledTags?.has(tag) || filter?.excludedTags?.has(tag); const isTagExcluded = (tag: MapTag): boolean => filter?.excludedTags?.has(tag); + const leaderboardOptions: BsmSelectOption[] = useConstant( + () => Object.values(MapLeaderboard).map(key => ({ + text: `maps.map-leaderboard.${key}`, + value: key, + })) + ); + useEffect(() => { if (firstRun.current) { firstRun.current = false; @@ -137,10 +146,6 @@ export function FilterPanel({ className, ref, playlist = false, filter, localDat return t(`maps.map-styles.${style}`); }; - const translateMapSpecificity = (specificity: MapSpecificity): string => { - return t(`maps.map-specificities.${specificity}`); - }; - type BooleanKeys = { [k in keyof T]: T[k] extends boolean ? k : never }[keyof T]; const handleCheckbox = (key: BooleanKeys) => { @@ -153,6 +158,12 @@ export function FilterPanel({ className, ref, playlist = false, filter, localDat onChange(newFilter); }; + const handleLeaderboardChange = (value: MapLeaderboard) => { + const newFilter = { ...(filter ?? {}) }; + newFilter.leaderboard = value; + onChange(newFilter); + } + const handleApply = () => { onApply(filter); setHaveChanged(() => false); @@ -173,9 +184,18 @@ export function FilterPanel({ className, ref, playlist = false, filter, localDat {Object.values(MapSpecificity).map(specificity => (
handleCheckbox(specificity)}> handleCheckbox(specificity)} /> - {translateMapSpecificity(specificity)} + {t(`maps.map-specificities.${specificity}`)}
))} + +

{t("maps.map-filter-panel.leaderboard")}

+ +

{t("maps.map-filter-panel.requirements")}

{Object.values(MapRequirement).map(requirement => (
handleCheckbox(requirement)}> @@ -287,10 +307,23 @@ function isFitAutomapper(filter: MapFilter, automapper: boolean): boolean { return automapper; } +function isFitLeaderboard(filter: MapFilter, ranked: boolean, blRanked: boolean): boolean { + switch (filter?.leaderboard) { + case MapLeaderboard.All: + return true; -function isFitRanked(filter: MapFilter, ranked: boolean): boolean { - if (!filter?.ranked) { return true; } - return ranked; + case MapLeaderboard.Ranked: + return ranked || blRanked; + + case MapLeaderboard.ScoreSaber: + return ranked; + + case MapLeaderboard.BeatLeader: + return blRanked; + + default: // filter is undefined + return true; + } } function isFitCurated(filter: MapFilter, curated: boolean): boolean { @@ -322,7 +355,7 @@ export const isLocalMapFitMapFilter = ({filter, map, search}: { filter: MapFilte if (!isFitChroma(filter, map.songDetails?.difficulties.some(diff => !!diff.chroma))) { return false; } if (!isFitFullSpread(filter, map.songDetails?.difficulties.length)) { return false; } if (!isFitAutomapper(filter, map.songDetails?.automapper)){ return false; } - if (!isFitRanked(filter, map.songDetails?.ranked || map.songDetails?.blRanked)) { return false; } + if (!isFitLeaderboard(filter, map.songDetails?.ranked, map.songDetails?.blRanked)) { return false; } if (!isFitCurated(filter, map.songDetails?.curated)) { return false; } if (!isFitVerified(filter, map.songDetails?.uploader.verified)) { return false; } if (!isFitSearch(search, {songName: map.mapInfo?.songName, songAuthorName: map.mapInfo?.songAuthorName, levelMappers: map.mapInfo?.levelMappers})) { return false; } @@ -343,7 +376,7 @@ export const isBsvMapFitMapFilter = ({filter, map, search}: { filter: MapFilter, if (!isFitChroma(filter, map.versions?.at(0)?.diffs.some(diff => !!diff.chroma))) { return false; } if (!isFitFullSpread(filter, map.versions?.at(0)?.diffs.length)) { return false; } if (!isFitAutomapper(filter, map.automapper)){ return false; } - if (!isFitRanked(filter, map.ranked || map.blRanked)) { return false; } + if (!isFitLeaderboard(filter, map.ranked, map.blRanked)) { return false; } if (!isFitCurated(filter, !!map.curator)) { return false; } if (!isFitVerified(filter, !!map.curatedAt)) { return false; } if (!isFitSearch(search, {songName: map.name, songAuthorName: map.metadata.songAuthorName, levelMappers: [map.metadata.levelAuthorName]})) { return false; } @@ -363,7 +396,7 @@ export const isSongDetailsFitMapFilter = ({filter, map, search}: { filter: MapFi if (!isFitChroma(filter, map.difficulties.some(diff => !!diff.chroma))) { return false; } if (!isFitFullSpread(filter, map.difficulties.length)) { return false; } if (!isFitAutomapper(filter, map.automapper)){ return false; } - if (!isFitRanked(filter, map.ranked || map.blRanked)) { return false; } + if (!isFitLeaderboard(filter, map.ranked, map.blRanked)) { return false; } if (!isFitCurated(filter, map.curated)) { return false; } if (!isFitVerified(filter, map.uploader.verified)) { return false; } if (!isFitSearch(search, {songName: map.name, songAuthorName: map.uploader.name, levelMappers: [map.uploader.name]})) { return false; } diff --git a/src/renderer/components/maps-playlists-panel/maps/map-item.component.tsx b/src/renderer/components/maps-playlists-panel/maps/map-item.component.tsx index d5cecbf8..6f343b8b 100644 --- a/src/renderer/components/maps-playlists-panel/maps/map-item.component.tsx +++ b/src/renderer/components/maps-playlists-panel/maps/map-item.component.tsx @@ -324,12 +324,12 @@ export function MapItemComponent ({ hash, title, autor, songAutor, {ranked && (
- {t("maps.map-specificities.ranked")} + {t("maps.map-leaderboard.Ranked")}
)} {blRanked && (
- {t("maps.map-specificities.ranked")} + {t("maps.map-leaderboard.Ranked")}
)}
{renderDiffPreview()}
diff --git a/src/shared/models/maps/beat-saver.model.ts b/src/shared/models/maps/beat-saver.model.ts index d8d2c1d2..dc0ed9e7 100644 --- a/src/shared/models/maps/beat-saver.model.ts +++ b/src/shared/models/maps/beat-saver.model.ts @@ -209,11 +209,16 @@ export enum MapRequirement { export enum MapSpecificity { Automapper = "automapper", - Ranked = "ranked", Curated = "curated", Verified = "verified", FullSpread = "fullSpread" +} +export enum MapLeaderboard { + All = "All", + Ranked = "Ranked", + BeatLeader = "BeatLeader", + ScoreSaber = "ScoreSaber", } // [ Admin, Uploader, SageScore, None ] @@ -235,7 +240,7 @@ export interface MapFilter { from?: number; to?: number; fullSpread?: boolean; - ranked?: boolean; + leaderboard?: MapLeaderboard; installed?: boolean; minDuration?: number; maxDuration?: number;