From 1310d16597699980c57949e60e43bcedb9dab04a Mon Sep 17 00:00:00 2001 From: silentrald Date: Wed, 2 Oct 2024 21:27:30 +0800 Subject: [PATCH 01/10] [feat-216] support for sorting maps/songs --- assets/jsons/translations/de.json | 9 +++ assets/jsons/translations/en.json | 9 +++ assets/jsons/translations/es.json | 9 +++ assets/jsons/translations/fr.json | 9 +++ assets/jsons/translations/ja.json | 9 +++ assets/jsons/translations/ru.json | 9 +++ assets/jsons/translations/zh-tw.json | 9 +++ assets/jsons/translations/zh.json | 9 +++ .../maps-playlists-panel.component.tsx | 53 ++++++++++++++- .../maps/local-maps-list-panel.component.tsx | 17 ++++- src/renderer/services/maps/sorter.service.ts | 65 +++++++++++++++++++ src/shared/models/generics.type.ts | 3 + src/shared/models/maps/beat-saver.model.ts | 7 ++ 13 files changed, 212 insertions(+), 5 deletions(-) create mode 100644 src/renderer/services/maps/sorter.service.ts create mode 100644 src/shared/models/generics.type.ts diff --git a/assets/jsons/translations/de.json b/assets/jsons/translations/de.json index 5f662c4e..203fc4b7 100644 --- a/assets/jsons/translations/de.json +++ b/assets/jsons/translations/de.json @@ -104,6 +104,15 @@ "subtext": "Ziehe deine \".bplist\" oder \".json\" Dateien hierhin, um sie zu importieren" } } + }, + "sort": { + "name": "Name", + "song-author": "Song Author", + "map-author": "Map Author", + "bpm": "BPM", + "duration": "Duration", + "likes": "Likes", + "date-uploaded": "Date Uploaded" } }, "mods": { diff --git a/assets/jsons/translations/en.json b/assets/jsons/translations/en.json index 9deaba07..f332853c 100644 --- a/assets/jsons/translations/en.json +++ b/assets/jsons/translations/en.json @@ -104,6 +104,15 @@ "subtext": "Drop your \".bplist\" or \".json\" files here to import them" } } + }, + "sort": { + "name": "Name", + "song-author": "Song Author", + "map-author": "Map Author", + "bpm": "BPM", + "duration": "Duration", + "likes": "Likes", + "date-uploaded": "Date Uploaded" } }, "mods": { diff --git a/assets/jsons/translations/es.json b/assets/jsons/translations/es.json index 528e4939..9c801685 100644 --- a/assets/jsons/translations/es.json +++ b/assets/jsons/translations/es.json @@ -104,6 +104,15 @@ "subtext": "Suelta tus archivos \".bplist\" o \".json\" aquí para importarlos" } } + }, + "sort": { + "name": "Name", + "song-author": "Song Author", + "map-author": "Map Author", + "bpm": "BPM", + "duration": "Duration", + "likes": "Likes", + "date-uploaded": "Date Uploaded" } }, "mods": { diff --git a/assets/jsons/translations/fr.json b/assets/jsons/translations/fr.json index e0e3c769..44f3c3eb 100644 --- a/assets/jsons/translations/fr.json +++ b/assets/jsons/translations/fr.json @@ -104,6 +104,15 @@ "subtext": "Déposez vos fichiers \".bplist\" ou \".json\" ici pour les importer" } } + }, + "sort": { + "name": "Name", + "song-author": "Song Author", + "map-author": "Map Author", + "bpm": "BPM", + "duration": "Duration", + "likes": "Likes", + "date-uploaded": "Date Uploaded" } }, "mods": { diff --git a/assets/jsons/translations/ja.json b/assets/jsons/translations/ja.json index 61ffe3a7..29f2a3f7 100644 --- a/assets/jsons/translations/ja.json +++ b/assets/jsons/translations/ja.json @@ -104,6 +104,15 @@ "subtext": "\".bplist\" または \".json\" ファイルをここにドロップしてインポート" } } + }, + "sort": { + "name": "Name", + "song-author": "Song Author", + "map-author": "Map Author", + "bpm": "BPM", + "duration": "Duration", + "likes": "Likes", + "date-uploaded": "Date Uploaded" } }, "mods": { diff --git a/assets/jsons/translations/ru.json b/assets/jsons/translations/ru.json index 651e11a9..019e8e97 100644 --- a/assets/jsons/translations/ru.json +++ b/assets/jsons/translations/ru.json @@ -104,6 +104,15 @@ "subtext": "Перетащите сюда файлы \".bplist\" или \".json\" для их импорта" } } + }, + "sort": { + "name": "Name", + "song-author": "Song Author", + "map-author": "Map Author", + "bpm": "BPM", + "duration": "Duration", + "likes": "Likes", + "date-uploaded": "Date Uploaded" } }, "mods": { diff --git a/assets/jsons/translations/zh-tw.json b/assets/jsons/translations/zh-tw.json index 668517a5..08867a1e 100644 --- a/assets/jsons/translations/zh-tw.json +++ b/assets/jsons/translations/zh-tw.json @@ -104,6 +104,15 @@ "subtext": "將您的 \".bplist\" 或 \".json\" 檔案拖曳到這裡進行匯入" } } + }, + "sort": { + "name": "Name", + "song-author": "Song Author", + "map-author": "Map Author", + "bpm": "BPM", + "duration": "Duration", + "likes": "Likes", + "date-uploaded": "Date Uploaded" } }, "mods": { diff --git a/assets/jsons/translations/zh.json b/assets/jsons/translations/zh.json index ace849b7..f7f01108 100644 --- a/assets/jsons/translations/zh.json +++ b/assets/jsons/translations/zh.json @@ -104,6 +104,15 @@ "subtext": "将您的 \".bplist\" 或 \".json\" 文件拖放到这里进行导入" } } + }, + "sort": { + "name": "Name", + "song-author": "Song Author", + "map-author": "Map Author", + "bpm": "BPM", + "duration": "Duration", + "likes": "Likes", + "date-uploaded": "Date Uploaded" } }, "mods": { diff --git a/src/renderer/components/maps-playlists-panel/maps-playlists-panel.component.tsx b/src/renderer/components/maps-playlists-panel/maps-playlists-panel.component.tsx index 31308119..26c9764b 100644 --- a/src/renderer/components/maps-playlists-panel/maps-playlists-panel.component.tsx +++ b/src/renderer/components/maps-playlists-panel/maps-playlists-panel.component.tsx @@ -3,7 +3,7 @@ import { BSVersion } from "shared/bs-version.interface"; import { LocalMapsListPanel, LocalMapsListPanelRef } from "./maps/local-maps-list-panel.component"; import { BsmDropdownButton, DropDownItem } from "../shared/bsm-dropdown-button.component"; import { FilterPanel } from "./maps/filter-panel.component"; -import { MapFilter } from "shared/models/maps/beat-saver.model"; +import { MapFilter, MapSort } from "shared/models/maps/beat-saver.model"; import { MapsManagerService } from "renderer/services/maps-manager.service"; import { MapsDownloaderService } from "renderer/services/maps-downloader.service"; import { useTranslation } from "renderer/hooks/use-translation.hook"; @@ -24,6 +24,9 @@ import { LocalPlaylistFilter, LocalPlaylistFilterPanel } from "./playlists/local import { noop } from "shared/helpers/function.helpers"; import { Dropzone } from "../shared/dropzone.component"; import { logRenderError } from "renderer"; +import { BsmSelect, BsmSelectOption } from "../shared/bsm-select.component"; +import { MapsSorterService } from "renderer/services/maps/sorter.service"; +import { BsmButton } from "../shared/bsm-button.component"; type Props = { readonly version?: BSVersion; @@ -41,6 +44,7 @@ export function MapsPlaylistsPanel({ version, isActive }: Props) { const mapsManager = useService(MapsManagerService); const mapsDownloader = useService(MapsDownloaderService); + const mapsSorter = useService(MapsSorterService); const playlistsManager = useService(PlaylistsManagerService); const playlistsDownloader = useService(PlaylistDownloaderService); @@ -64,6 +68,10 @@ export function MapsPlaylistsPanel({ version, isActive }: Props) { const playlistsRef = useRef(); const [mapFilter, setMapFilter] = useState({}); + const [mapSort, setMapSort] = useState({ + compare: mapsSorter.getDefaultComparator(), + ascending: true, + }); const [playlistFilter, setPlaylistFilter] = useState({}); const [search, setSearch] = useState(""); @@ -135,6 +143,20 @@ export function MapsPlaylistsPanel({ version, isActive }: Props) { ]; })(); + const sortOptions: BsmSelectOption[] = useConstant(() => { + return mapsSorter.getComparatorKeys().map(key => ({ + text: `pages.version-viewer.maps.sort.${key}`, + value: key, + })); + }); + + const handleSortChange = (key: string) => { + setMapSort({ + compare: mapsSorter.getComparator(key), + ascending: mapSort.ascending, + }); + } + const dropDownItems = ((): DropDownItem[] => { if (tabIndex === 0) { return [ @@ -188,6 +210,24 @@ export function MapsPlaylistsPanel({ version, isActive }: Props) { ) )} + + setMapSort({ + compare: mapSort.compare, + ascending: !mapSort.ascending, + })} + /> - + void; } -export const LocalMapsListPanel = forwardRef(({ version, className, filter, search, linkedState, isActive }, forwardRef) => { +export const LocalMapsListPanel = forwardRef(({ + version, + className, + filter, + search, + linkedState, + isActive, + sort, +}, forwardRef) => { const mapsManager = useService(MapsManagerService); const mapsDownloader = useService(MapsDownloaderService); @@ -204,7 +213,9 @@ export const LocalMapsListPanel = forwardRef(({ ve }, [version]); const preppedMaps: RenderableMap[] = (() => { - return renderableMaps?.filter(renderableMap => isLocalMapFitMapFilter({ map: renderableMap.map, filter, search })) ?? []; + return (renderableMaps?.filter(renderableMap => isLocalMapFitMapFilter({ map: renderableMap.map, filter, search })) ?? []) + // NOTE: might be good to cache sorted maps, to reduce loading + .sort((map1, map2) => sort.compare(map1.map, map2.map) * (sort.ascending === false ? -1 : 1)); })(); if (!maps) { diff --git a/src/renderer/services/maps/sorter.service.ts b/src/renderer/services/maps/sorter.service.ts new file mode 100644 index 00000000..14330b40 --- /dev/null +++ b/src/renderer/services/maps/sorter.service.ts @@ -0,0 +1,65 @@ +import { Comparator } from "shared/models/generics.type"; +import { BsmLocalMap } from "shared/models/maps/bsm-local-map.interface"; + + +export class MapsSorterService { + private static instance: MapsSorterService; + + public static getInstance() { + if (!MapsSorterService.instance) { + MapsSorterService.instance = new MapsSorterService(); + } + return MapsSorterService.instance; + } + + private readonly comparators: { + [key: string]: Comparator; + } = { + name: (map1, map2) => + map1.rawInfo._songName.localeCompare(map2.rawInfo._songName), + "song-author": (map1, map2) => + map1.rawInfo._songAuthorName.localeCompare(map2.rawInfo._songAuthorName), + "map-author": (map1, map2) => + map1.rawInfo._levelAuthorName.localeCompare(map2.rawInfo._levelAuthorName), + bpm: (map1, map2) => + map1.rawInfo._beatsPerMinute - map2.rawInfo._beatsPerMinute, + duration: (map1, map2) => { + if (!map1.songDetails) { + return map2.songDetails ? -1 : 0; + } + + return !map2.songDetails ? -1 + : map1.songDetails.duration - map2.songDetails.duration; + }, + likes: (map1, map2) => { + if (!map1.songDetails) { + return map2.songDetails ? -1 : 0; + } + + return !map2.songDetails ? -1 + : map1.songDetails.upVotes - map2.songDetails.upVotes; + }, + "date-uploaded": (map1, map2) => { + if (!map1.songDetails) { + return map2.songDetails ? -1 : 0; + } + + return !map2.songDetails ? -1 + : map1.songDetails.uploadedAt - map2.songDetails.uploadedAt; + } + }; + + public getComparatorKeys(): string[] { + return Object.keys(this.comparators); + } + + public getDefaultComparator(): Comparator { + return this.comparators.name; + } + + public getComparator(key: string): Comparator { + const comparator = this.comparators[key]; + return comparator || this.comparators.name; + } + +} diff --git a/src/shared/models/generics.type.ts b/src/shared/models/generics.type.ts new file mode 100644 index 00000000..33ad62f4 --- /dev/null +++ b/src/shared/models/generics.type.ts @@ -0,0 +1,3 @@ + +export type Comparator = (object1: T, object2: T) => number; + diff --git a/src/shared/models/maps/beat-saver.model.ts b/src/shared/models/maps/beat-saver.model.ts index dc0ed9e7..d08bc525 100644 --- a/src/shared/models/maps/beat-saver.model.ts +++ b/src/shared/models/maps/beat-saver.model.ts @@ -1,5 +1,7 @@ import { ObjectValues } from "shared/helpers/type.helpers"; import { SongDetailDiffCharactertistic, SongDiffName } from "./song-details-cache/song-details-cache.model"; +import { BsmLocalMap } from "./bsm-local-map.interface"; +import { Comparator } from "../generics.type"; export interface BsvMapDetail { automapper: boolean; @@ -250,6 +252,11 @@ export interface MapFilter { excludedTags?: Set; } +export interface MapSort { + compare: Comparator; + ascending: boolean; +} + export interface SearchResponse { docs: BsvMapDetail[]; redirect: string; From 61b728ed5524d209432547ee63052f692030f18a Mon Sep 17 00:00:00 2001 From: silentrald Date: Wed, 2 Oct 2024 21:42:59 +0800 Subject: [PATCH 02/10] [feat-216] added translations for maps/songs sorting --- assets/jsons/translations/de.json | 8 ++++---- assets/jsons/translations/es.json | 12 ++++++------ assets/jsons/translations/fr.json | 12 ++++++------ assets/jsons/translations/ja.json | 12 ++++++------ assets/jsons/translations/ru.json | 12 ++++++------ assets/jsons/translations/zh-tw.json | 12 ++++++------ assets/jsons/translations/zh.json | 12 ++++++------ 7 files changed, 40 insertions(+), 40 deletions(-) diff --git a/assets/jsons/translations/de.json b/assets/jsons/translations/de.json index 203fc4b7..a31eeca8 100644 --- a/assets/jsons/translations/de.json +++ b/assets/jsons/translations/de.json @@ -107,12 +107,12 @@ }, "sort": { "name": "Name", - "song-author": "Song Author", - "map-author": "Map Author", + "song-author": "Liedautor", + "map-author": "Kartenautor", "bpm": "BPM", - "duration": "Duration", + "duration": "Dauer", "likes": "Likes", - "date-uploaded": "Date Uploaded" + "date-uploaded": "Hochladedatum" } }, "mods": { diff --git a/assets/jsons/translations/es.json b/assets/jsons/translations/es.json index 9c801685..3cb06f96 100644 --- a/assets/jsons/translations/es.json +++ b/assets/jsons/translations/es.json @@ -106,13 +106,13 @@ } }, "sort": { - "name": "Name", - "song-author": "Song Author", - "map-author": "Map Author", + "name": "Nombre", + "song-author": "Autor de la canción", + "map-author": "Autor del mapa", "bpm": "BPM", - "duration": "Duration", - "likes": "Likes", - "date-uploaded": "Date Uploaded" + "duration": "Duración", + "likes": "Me gusta", + "date-uploaded": "Fecha de subida" } }, "mods": { diff --git a/assets/jsons/translations/fr.json b/assets/jsons/translations/fr.json index 44f3c3eb..8624f473 100644 --- a/assets/jsons/translations/fr.json +++ b/assets/jsons/translations/fr.json @@ -106,13 +106,13 @@ } }, "sort": { - "name": "Name", - "song-author": "Song Author", - "map-author": "Map Author", + "name": "Nom", + "song-author": "Auteur de la chanson", + "map-author": "Auteur de la carte", "bpm": "BPM", - "duration": "Duration", - "likes": "Likes", - "date-uploaded": "Date Uploaded" + "duration": "Durée", + "likes": "J'aime", + "date-uploaded": "Date de téléchargement" } }, "mods": { diff --git a/assets/jsons/translations/ja.json b/assets/jsons/translations/ja.json index 29f2a3f7..213615ec 100644 --- a/assets/jsons/translations/ja.json +++ b/assets/jsons/translations/ja.json @@ -106,13 +106,13 @@ } }, "sort": { - "name": "Name", - "song-author": "Song Author", - "map-author": "Map Author", + "name": "名前", + "song-author": "曲の作者", + "map-author": "地図の作者", "bpm": "BPM", - "duration": "Duration", - "likes": "Likes", - "date-uploaded": "Date Uploaded" + "duration": "持続時間", + "likes": "いいね", + "date-uploaded": "アップロード日" } }, "mods": { diff --git a/assets/jsons/translations/ru.json b/assets/jsons/translations/ru.json index 019e8e97..8c576cc9 100644 --- a/assets/jsons/translations/ru.json +++ b/assets/jsons/translations/ru.json @@ -106,13 +106,13 @@ } }, "sort": { - "name": "Name", - "song-author": "Song Author", - "map-author": "Map Author", + "name": "Имя", + "song-author": "Автор песни", + "map-author": "Автор карты", "bpm": "BPM", - "duration": "Duration", - "likes": "Likes", - "date-uploaded": "Date Uploaded" + "duration": "Длительность", + "likes": "Лайки", + "date-uploaded": "Дата загрузки" } }, "mods": { diff --git a/assets/jsons/translations/zh-tw.json b/assets/jsons/translations/zh-tw.json index 08867a1e..055926e5 100644 --- a/assets/jsons/translations/zh-tw.json +++ b/assets/jsons/translations/zh-tw.json @@ -106,13 +106,13 @@ } }, "sort": { - "name": "Name", - "song-author": "Song Author", - "map-author": "Map Author", + "name": "名字", + "song-author": "歌曲作者", + "map-author": "地圖作者", "bpm": "BPM", - "duration": "Duration", - "likes": "Likes", - "date-uploaded": "Date Uploaded" + "duration": "持續時間", + "likes": "喜歡", + "date-uploaded": "上傳日期" } }, "mods": { diff --git a/assets/jsons/translations/zh.json b/assets/jsons/translations/zh.json index f7f01108..9b39cd49 100644 --- a/assets/jsons/translations/zh.json +++ b/assets/jsons/translations/zh.json @@ -106,13 +106,13 @@ } }, "sort": { - "name": "Name", - "song-author": "Song Author", - "map-author": "Map Author", + "name": "名字", + "song-author": "歌曲作者", + "map-author": "地图作者", "bpm": "BPM", - "duration": "Duration", - "likes": "Likes", - "date-uploaded": "Date Uploaded" + "duration": "持续时间", + "likes": "喜欢", + "date-uploaded": "上传日期" } }, "mods": { From b4874eced47ceb49fb7210548f24f2cb86dd57ba Mon Sep 17 00:00:00 2001 From: silentrald Date: Mon, 7 Oct 2024 22:51:12 +0800 Subject: [PATCH 03/10] [feat-216] support for sorting playlists --- assets/jsons/translations/de.json | 10 ++ assets/jsons/translations/en.json | 10 ++ assets/jsons/translations/es.json | 10 ++ assets/jsons/translations/fr.json | 10 ++ assets/jsons/translations/ja.json | 10 ++ assets/jsons/translations/ru.json | 10 ++ assets/jsons/translations/zh-tw.json | 10 ++ assets/jsons/translations/zh.json | 10 ++ .../maps-playlists-panel.component.tsx | 134 ++++++++++++++---- .../local-playlist-filter-panel.component.tsx | 7 + .../local-playlists-list-panel.component.tsx | 43 ++++-- src/renderer/services/maps/sorter.service.ts | 14 +- .../services/playlists/sorter.service.ts | 74 ++++++++++ 13 files changed, 312 insertions(+), 40 deletions(-) create mode 100644 src/renderer/services/playlists/sorter.service.ts diff --git a/assets/jsons/translations/de.json b/assets/jsons/translations/de.json index a31eeca8..c4b3f3c6 100644 --- a/assets/jsons/translations/de.json +++ b/assets/jsons/translations/de.json @@ -115,6 +115,16 @@ "date-uploaded": "Hochladedatum" } }, + "playlists": { + "sort": { + "title": "Title", + "author": "Author", + "number-of-maps": "Number of Maps", + "duration": "Duration", + "min-notes-per-second": "Min Notes per Second", + "max-notes-per-second": "Max Notes per Second" + } + }, "mods": { "loading-mods": "Lade Mods...", "no-internet": "Kein Internet", diff --git a/assets/jsons/translations/en.json b/assets/jsons/translations/en.json index f332853c..8652ce7f 100644 --- a/assets/jsons/translations/en.json +++ b/assets/jsons/translations/en.json @@ -115,6 +115,16 @@ "date-uploaded": "Date Uploaded" } }, + "playlists": { + "sort": { + "title": "Title", + "author": "Author", + "number-of-maps": "Number of Maps", + "duration": "Duration", + "min-notes-per-second": "Min Notes per Second", + "max-notes-per-second": "Max Notes per Second" + } + }, "mods": { "loading-mods": "Loading mods...", "no-internet": "No internet", diff --git a/assets/jsons/translations/es.json b/assets/jsons/translations/es.json index 3cb06f96..dcc44d63 100644 --- a/assets/jsons/translations/es.json +++ b/assets/jsons/translations/es.json @@ -115,6 +115,16 @@ "date-uploaded": "Fecha de subida" } }, + "playlists": { + "sort": { + "title": "Title", + "author": "Author", + "number-of-maps": "Number of Maps", + "duration": "Duration", + "min-notes-per-second": "Min Notes per Second", + "max-notes-per-second": "Max Notes per Second" + } + }, "mods": { "loading-mods": "Cargando mods...", "no-internet": "Sin internet", diff --git a/assets/jsons/translations/fr.json b/assets/jsons/translations/fr.json index 8624f473..6bfdaaf5 100644 --- a/assets/jsons/translations/fr.json +++ b/assets/jsons/translations/fr.json @@ -115,6 +115,16 @@ "date-uploaded": "Date de téléchargement" } }, + "playlists": { + "sort": { + "title": "Title", + "author": "Author", + "number-of-maps": "Number of Maps", + "duration": "Duration", + "min-notes-per-second": "Min Notes per Second", + "max-notes-per-second": "Max Notes per Second" + } + }, "mods": { "loading-mods": "Chargement des mods...", "no-internet": "Pas d'accès Internet", diff --git a/assets/jsons/translations/ja.json b/assets/jsons/translations/ja.json index 213615ec..dcbd5e75 100644 --- a/assets/jsons/translations/ja.json +++ b/assets/jsons/translations/ja.json @@ -115,6 +115,16 @@ "date-uploaded": "アップロード日" } }, + "playlists": { + "sort": { + "title": "Title", + "author": "Author", + "number-of-maps": "Number of Maps", + "duration": "Duration", + "min-notes-per-second": "Min Notes per Second", + "max-notes-per-second": "Max Notes per Second" + } + }, "mods": { "loading-mods": "Modを読み込み中...", "no-internet": "インターネット接続がありません!", diff --git a/assets/jsons/translations/ru.json b/assets/jsons/translations/ru.json index 8c576cc9..7603de71 100644 --- a/assets/jsons/translations/ru.json +++ b/assets/jsons/translations/ru.json @@ -115,6 +115,16 @@ "date-uploaded": "Дата загрузки" } }, + "playlists": { + "sort": { + "title": "Title", + "author": "Author", + "number-of-maps": "Number of Maps", + "duration": "Duration", + "min-notes-per-second": "Min Notes per Second", + "max-notes-per-second": "Max Notes per Second" + } + }, "mods": { "loading-mods": "Загрузка модов...", "no-internet": "Нет интернета", diff --git a/assets/jsons/translations/zh-tw.json b/assets/jsons/translations/zh-tw.json index 055926e5..83dbebc6 100644 --- a/assets/jsons/translations/zh-tw.json +++ b/assets/jsons/translations/zh-tw.json @@ -115,6 +115,16 @@ "date-uploaded": "上傳日期" } }, + "playlists": { + "sort": { + "title": "Title", + "author": "Author", + "number-of-maps": "Number of Maps", + "duration": "Duration", + "min-notes-per-second": "Min Notes per Second", + "max-notes-per-second": "Max Notes per Second" + } + }, "mods": { "loading-mods": "Mod 載入中...", "no-internet": "無網路", diff --git a/assets/jsons/translations/zh.json b/assets/jsons/translations/zh.json index 9b39cd49..5331c77f 100644 --- a/assets/jsons/translations/zh.json +++ b/assets/jsons/translations/zh.json @@ -115,6 +115,16 @@ "date-uploaded": "上传日期" } }, + "playlists": { + "sort": { + "title": "Title", + "author": "Author", + "number-of-maps": "Number of Maps", + "duration": "Duration", + "min-notes-per-second": "Min Notes per Second", + "max-notes-per-second": "Max Notes per Second" + } + }, "mods": { "loading-mods": "Mod 加载中...", "no-internet": "无网络", diff --git a/src/renderer/components/maps-playlists-panel/maps-playlists-panel.component.tsx b/src/renderer/components/maps-playlists-panel/maps-playlists-panel.component.tsx index 26c9764b..b947889f 100644 --- a/src/renderer/components/maps-playlists-panel/maps-playlists-panel.component.tsx +++ b/src/renderer/components/maps-playlists-panel/maps-playlists-panel.component.tsx @@ -20,13 +20,14 @@ import { BsmLocalMap } from "shared/models/maps/bsm-local-map.interface"; import { useConstant } from "renderer/hooks/use-constant.hook"; import { LocalBPListsDetails } from "shared/models/playlists/local-playlist.models"; import { PlaylistDownloaderService } from "renderer/services/playlist-downloader.service"; -import { LocalPlaylistFilter, LocalPlaylistFilterPanel } from "./playlists/local-playlist-filter-panel.component"; +import { LocalPlaylistFilter, LocalPlaylistFilterPanel, LocalPlaylistSort } from "./playlists/local-playlist-filter-panel.component"; import { noop } from "shared/helpers/function.helpers"; import { Dropzone } from "../shared/dropzone.component"; import { logRenderError } from "renderer"; import { BsmSelect, BsmSelectOption } from "../shared/bsm-select.component"; import { MapsSorterService } from "renderer/services/maps/sorter.service"; import { BsmButton } from "../shared/bsm-button.component"; +import { PlaylistsSorterService } from "renderer/services/playlists/sorter.service"; type Props = { readonly version?: BSVersion; @@ -42,11 +43,15 @@ export const InstalledMapsContext = createContext<{ export function MapsPlaylistsPanel({ version, isActive }: Props) { + const MAP_TAB = 0; + const PLAYLIST_TAB = 1; + const mapsManager = useService(MapsManagerService); const mapsDownloader = useService(MapsDownloaderService); const mapsSorter = useService(MapsSorterService); const playlistsManager = useService(PlaylistsManagerService); const playlistsDownloader = useService(PlaylistDownloaderService); + const playlistsSorter = useService(PlaylistsSorterService); const t = useTranslation(); const [tabIndex, setTabIndex] = useState(0); @@ -73,6 +78,18 @@ export function MapsPlaylistsPanel({ version, isActive }: Props) { ascending: true, }); const [playlistFilter, setPlaylistFilter] = useState({}); + const [playlistSort, setPlaylistSort] = useState({ + compare: playlistsSorter.getDefaultComparator(), + ascending: true, + }); + const [selectedSort, setSelectedSort] = useState("name"); + + const [sortOptions, setSortOptions] = useState[]>( + () => mapsSorter.getComparatorKeys().map(key => ({ + text: `pages.version-viewer.maps.sort.${key}`, + value: key, + })) + ); const [search, setSearch] = useState(""); const mapsLinkedState = useObservable(() => { @@ -108,6 +125,39 @@ export function MapsPlaylistsPanel({ version, isActive }: Props) { return mapsManager.unlinkVersion(version); }; + const handleTabChange = (index: number) => { + setTabIndex(index); + + switch (index) { + case MAP_TAB: + setSortOptions(() => mapsSorter.getComparatorKeys().map(key => ({ + text: `pages.version-viewer.maps.sort.${key}`, + value: key, + }))); + setSelectedSort(mapsSorter.getDefaultComparatorKey()); + setMapSort({ + compare: mapsSorter.getDefaultComparator(), + ascending: playlistSort.ascending, + }); + break; + + case PLAYLIST_TAB: + setSortOptions(() => playlistsSorter.getComparatorKeys().map(key => ({ + text: `pages.version-viewer.playlists.sort.${key}`, + value: key, + }))); + setSelectedSort(playlistsSorter.getDefaultComparatorKey()); + setPlaylistSort({ + compare: playlistsSorter.getDefaultComparator(), + ascending: mapSort.ascending, + }); + break; + + default: + break; + } + }; + const handlePlaylistLinkClick = () => { if(playlistLinkedState === FolderLinkState.Pending || playlistLinkedState === FolderLinkState.Processing){ return Promise.resolve(false); } @@ -143,22 +193,50 @@ export function MapsPlaylistsPanel({ version, isActive }: Props) { ]; })(); - const sortOptions: BsmSelectOption[] = useConstant(() => { - return mapsSorter.getComparatorKeys().map(key => ({ - text: `pages.version-viewer.maps.sort.${key}`, - value: key, - })); - }); - const handleSortChange = (key: string) => { - setMapSort({ - compare: mapsSorter.getComparator(key), - ascending: mapSort.ascending, - }); + switch (tabIndex) { + case MAP_TAB: + setMapSort({ + compare: mapsSorter.getComparator(key), + ascending: mapSort.ascending, + }); + break; + + case PLAYLIST_TAB: + setPlaylistSort({ + compare: playlistsSorter.getComparator(key), + ascending: playlistSort.ascending, + }); + break; + + default: + break; + } + } + + const handleAscendingClick = () => { + switch (tabIndex) { + case MAP_TAB: + setMapSort({ + compare: mapSort.compare, + ascending: !mapSort.ascending, + }); + break; + + case PLAYLIST_TAB: + setPlaylistSort({ + compare: playlistSort.compare, + ascending: !playlistSort.ascending, + }); + break; + + default: + break; + } } const dropDownItems = ((): DropDownItem[] => { - if (tabIndex === 0) { + if (tabIndex === MAP_TAB) { return [ { icon: "export", text: "pages.version-viewer.maps.search-bar.dropdown.export-maps", onClick: () => mapsRef.current.exportMaps?.() }, { icon: "trash", text: "pages.version-viewer.maps.search-bar.dropdown.delete-maps", onClick: () => mapsRef.current.deleteMaps?.() }, @@ -195,7 +273,7 @@ export function MapsPlaylistsPanel({ version, isActive }: Props) { setSearch(() => e.target.value)} tabIndex={-1} @@ -213,31 +291,30 @@ export function MapsPlaylistsPanel({ version, isActive }: Props) { setMapSort({ - compare: mapSort.compare, - ascending: !mapSort.ascending, - })} + onClick={handleAscendingClick} /> setTabIndex(index)} + onTabChange={handleTabChange} tabs={[ { text: "misc.maps", icon: MapIcon, - onClick: () => setTabIndex(0), + onClick: () => handleTabChange(MAP_TAB), linkProps: version ? { state: mapsLinkedState, onClick: handleMapsLinkClick, @@ -246,7 +323,7 @@ export function MapsPlaylistsPanel({ version, isActive }: Props) { { text: "misc.playlists", icon: PlaylistIcon, - onClick: () => setTabIndex(1), + onClick: () => handleTabChange(PLAYLIST_TAB), linkProps: version ? { state: playlistLinkedState, onClick: handlePlaylistLinkClick, @@ -292,7 +369,16 @@ export function MapsPlaylistsPanel({ version, isActive }: Props) { properties: ["openFile", "multiSelections"], }}} > - + diff --git a/src/renderer/components/maps-playlists-panel/playlists/local-playlist-filter-panel.component.tsx b/src/renderer/components/maps-playlists-panel/playlists/local-playlist-filter-panel.component.tsx index 7571c991..630b6e38 100644 --- a/src/renderer/components/maps-playlists-panel/playlists/local-playlist-filter-panel.component.tsx +++ b/src/renderer/components/maps-playlists-panel/playlists/local-playlist-filter-panel.component.tsx @@ -6,6 +6,8 @@ import { useTranslation } from "renderer/hooks/use-translation.hook"; import { hourToS, sToMs } from "shared/helpers/time.helpers"; import { useOnUpdate } from "renderer/hooks/use-on-update.hook"; import formatDuration from "format-duration"; +import { LocalBPListsDetails } from "shared/models/playlists/local-playlist.models"; +import { Comparator } from "shared/models/generics.type"; type Props = { className?: string; @@ -120,3 +122,8 @@ export type LocalPlaylistFilter = Partial<{ minDuration: number; maxDuration: number; }> + +export interface LocalPlaylistSort { + compare: Comparator; + ascending: boolean; +} diff --git a/src/renderer/components/maps-playlists-panel/playlists/local-playlists-list-panel.component.tsx b/src/renderer/components/maps-playlists-panel/playlists/local-playlists-list-panel.component.tsx index 094adee9..96138052 100644 --- a/src/renderer/components/maps-playlists-panel/playlists/local-playlists-list-panel.component.tsx +++ b/src/renderer/components/maps-playlists-panel/playlists/local-playlists-list-panel.component.tsx @@ -24,7 +24,7 @@ import { DeletePlaylistModal } from "renderer/components/modal/modal-types/playl import { OsDiagnosticService } from "renderer/services/os-diagnostic.service"; import { PlaylistItemComponentPropsMapper } from "shared/mappers/playlist/playlist-item-component-props.mapper"; import { VirtualScroll } from "renderer/components/shared/virtual-scroll/virtual-scroll.component"; -import { LocalPlaylistFilter } from "./local-playlist-filter-panel.component"; +import { LocalPlaylistFilter, LocalPlaylistSort } from "./local-playlist-filter-panel.component"; import { BsmImage } from "renderer/components/shared/bsm-image.component"; import { BsmButton } from "renderer/components/shared/bsm-button.component"; import BeatConflict from "../../../../../assets/images/apngs/beat-conflict.png"; @@ -48,6 +48,7 @@ type Props = { search?: string; linkedState?: FolderLinkState; isActive?: boolean; + sort: LocalPlaylistSort; }; export type LocalPlaylistsListRef = { @@ -57,7 +58,15 @@ export type LocalPlaylistsListRef = { exportPlaylists: () => Promise; } -export const LocalPlaylistsListPanel = forwardRef(({ version, className, filter: playlistFiler, search, isActive, linkedState }, forwardedRef) => { +export const LocalPlaylistsListPanel = forwardRef(({ + version, + className, + filter: playlistFilter, + search, + isActive, + linkedState, + sort: playlistSort, +}, forwardedRef) => { const t = useTranslation(); @@ -397,27 +406,31 @@ export const LocalPlaylistsListPanel = forwardRef( if(!p.playlistTitle?.toLowerCase().includes(search.toLowerCase())){ return false; } if(!p.playlistAuthor?.toLowerCase().includes(search.toLowerCase())){ return false; } - if(typeof p.nbMaps === "number" && (typeof playlistFiler?.minNbMaps === "number" || typeof playlistFiler?.maxNbMaps === "number")){ - if(playlistFiler?.minNbMaps && p.nbMaps < playlistFiler.minNbMaps){ return false; } - if(playlistFiler?.maxNbMaps && p.nbMaps > playlistFiler.maxNbMaps){ return false; } + if(typeof p.nbMaps === "number" && (typeof playlistFilter?.minNbMaps === "number" || typeof playlistFilter?.maxNbMaps === "number")){ + if(playlistFilter?.minNbMaps && p.nbMaps < playlistFilter.minNbMaps){ return false; } + if(playlistFilter?.maxNbMaps && p.nbMaps > playlistFilter.maxNbMaps){ return false; } } - if(typeof p.nbMappers === "number" && (typeof playlistFiler?.minNbMappers === "number" || typeof playlistFiler?.maxNbMappers === "number")){ - if(playlistFiler?.minNbMappers && p.nbMappers < playlistFiler.minNbMappers){ return false; } - if(playlistFiler?.maxNbMappers && p.nbMappers > playlistFiler.maxNbMappers){ return false; } + if(typeof p.nbMappers === "number" && (typeof playlistFilter?.minNbMappers === "number" || typeof playlistFilter?.maxNbMappers === "number")){ + if(playlistFilter?.minNbMappers && p.nbMappers < playlistFilter.minNbMappers){ return false; } + if(playlistFilter?.maxNbMappers && p.nbMappers > playlistFilter.maxNbMappers){ return false; } } - if(typeof p.duration === "number" && (typeof playlistFiler?.minDuration === "number" || typeof playlistFiler?.maxDuration === "number")){ - if(playlistFiler?.minDuration && p.duration < playlistFiler.minDuration){ return false; } - if(playlistFiler?.maxDuration && p.duration > playlistFiler.maxDuration){ return false; } + if(typeof p.duration === "number" && (typeof playlistFilter?.minDuration === "number" || typeof playlistFilter?.maxDuration === "number")){ + if(playlistFilter?.minDuration && p.duration < playlistFilter.minDuration){ return false; } + if(playlistFilter?.maxDuration && p.duration > playlistFilter.maxDuration){ return false; } } - if(typeof p.minNps === "number" && typeof playlistFiler.minNps === "number" && p.minNps < playlistFiler.minNps){ return false; } - if(typeof p.maxNps === "number" && typeof playlistFiler.maxNps === "number" && p.maxNps > playlistFiler.maxNps){ return false; } + if(typeof p.minNps === "number" && typeof playlistFilter.minNps === "number" && p.minNps < playlistFilter.minNps){ return false; } + if(typeof p.maxNps === "number" && typeof playlistFilter.maxNps === "number" && p.maxNps > playlistFilter.maxNps){ return false; } return true; - }); - }, [playlists, search, playlistFiler]); + }) + .sort((playlist1, playlist2) => + playlistSort.compare(playlist1, playlist2) + * (playlistSort.ascending ? 1 : -1) + ); + }, [playlists, search, playlistFilter, playlistSort]); return (
diff --git a/src/renderer/services/maps/sorter.service.ts b/src/renderer/services/maps/sorter.service.ts index 14330b40..bcf759c4 100644 --- a/src/renderer/services/maps/sorter.service.ts +++ b/src/renderer/services/maps/sorter.service.ts @@ -49,17 +49,29 @@ export class MapsSorterService { } }; + private addTiebreak(comparator: Comparator): Comparator { + return (map1, map2) => + comparator(map1, map2) + || this.getDefaultComparator()(map1, map2); + } + public getComparatorKeys(): string[] { return Object.keys(this.comparators); } + public getDefaultComparatorKey(): string { + return "name"; + } + public getDefaultComparator(): Comparator { return this.comparators.name; } public getComparator(key: string): Comparator { const comparator = this.comparators[key]; - return comparator || this.comparators.name; + return comparator + ? this.addTiebreak(comparator) + : this.getDefaultComparator(); } } diff --git a/src/renderer/services/playlists/sorter.service.ts b/src/renderer/services/playlists/sorter.service.ts new file mode 100644 index 00000000..a194be6b --- /dev/null +++ b/src/renderer/services/playlists/sorter.service.ts @@ -0,0 +1,74 @@ +import { Comparator } from "shared/models/generics.type"; +import { LocalBPListsDetails } from "shared/models/playlists/local-playlist.models"; + +export class PlaylistsSorterService { + private static instance: PlaylistsSorterService; + + public static getInstance() { + if (!PlaylistsSorterService.instance) { + PlaylistsSorterService.instance = new PlaylistsSorterService(); + } + return PlaylistsSorterService.instance; + } + + private readonly comparators: { + [key: string]: Comparator; + } = { + title: (playlist1, playlist2) => + playlist1.playlistTitle.localeCompare(playlist2.playlistTitle), + author: (playlist1, playlist2) => + playlist1.playlistAuthor.localeCompare(playlist2.playlistAuthor), + "number-of-maps": (playlist1, playlist2) => + playlist1.nbMaps - playlist2.nbMaps, + duration: (playlist1, playlist2) => { + if (!playlist1.duration) { + return playlist2.duration ? -1 : 0; + } + + return !playlist2.duration ? -1 + : playlist1.duration - playlist2.duration; + }, + "min-notes-per-second": (playlist1, playlist2) => { + if (!playlist1.minNps) { + return playlist2.minNps ? -1 : 0; + } + + return !playlist2.minNps ? -1 + : playlist1.minNps - playlist2.minNps; + }, + "max-notes-per-second": (playlist1, playlist2) => { + if (!playlist1.maxNps) { + return playlist2.maxNps ? -1 : 0; + } + + return !playlist2.maxNps ? -1 + : playlist1.maxNps - playlist2.maxNps; + }, + }; + + private addTiebreak(comparator: Comparator): Comparator { + return (playlist1, playlist2) => + comparator(playlist1, playlist2) + || this.getDefaultComparator()(playlist1, playlist2); + } + + public getComparatorKeys(): string[] { + return Object.keys(this.comparators); + } + + public getDefaultComparatorKey(): string { + return "title"; + } + + public getDefaultComparator(): Comparator { + return this.comparators.title; + } + + public getComparator(key: string): Comparator { + const comparator = this.comparators[key]; + return comparator + ? this.addTiebreak(comparator) + : this.getDefaultComparator(); + } + +} From 22af4610ebd71abae500dc4972c198a989c2fa89 Mon Sep 17 00:00:00 2001 From: silentrald Date: Mon, 7 Oct 2024 23:01:59 +0800 Subject: [PATCH 04/10] [feat-216] added translations for playlists sorting --- assets/jsons/translations/de.json | 12 ++++++------ assets/jsons/translations/es.json | 12 ++++++------ assets/jsons/translations/fr.json | 12 ++++++------ assets/jsons/translations/ja.json | 12 ++++++------ assets/jsons/translations/ru.json | 12 ++++++------ assets/jsons/translations/zh-tw.json | 12 ++++++------ assets/jsons/translations/zh.json | 12 ++++++------ 7 files changed, 42 insertions(+), 42 deletions(-) diff --git a/assets/jsons/translations/de.json b/assets/jsons/translations/de.json index c4b3f3c6..3ba20a8d 100644 --- a/assets/jsons/translations/de.json +++ b/assets/jsons/translations/de.json @@ -117,12 +117,12 @@ }, "playlists": { "sort": { - "title": "Title", - "author": "Author", - "number-of-maps": "Number of Maps", - "duration": "Duration", - "min-notes-per-second": "Min Notes per Second", - "max-notes-per-second": "Max Notes per Second" + "title": "Titel", + "author": "Autor", + "number-of-maps": "Anzahl der Karten", + "duration": "Dauer", + "min-notes-per-second": "Minimale Noten pro Sekunde", + "max-notes-per-second": "Maximale Noten pro Sekunde" } }, "mods": { diff --git a/assets/jsons/translations/es.json b/assets/jsons/translations/es.json index dcc44d63..00d45fd3 100644 --- a/assets/jsons/translations/es.json +++ b/assets/jsons/translations/es.json @@ -117,12 +117,12 @@ }, "playlists": { "sort": { - "title": "Title", - "author": "Author", - "number-of-maps": "Number of Maps", - "duration": "Duration", - "min-notes-per-second": "Min Notes per Second", - "max-notes-per-second": "Max Notes per Second" + "title": "Título", + "author": "Autor", + "number-of-maps": "Número de mapas", + "duration": "Duración", + "min-notes-per-second": "Mínimo de notas por segundo", + "max-notes-per-second": "Máximo de notas por segundo" } }, "mods": { diff --git a/assets/jsons/translations/fr.json b/assets/jsons/translations/fr.json index 6bfdaaf5..7d1f7d66 100644 --- a/assets/jsons/translations/fr.json +++ b/assets/jsons/translations/fr.json @@ -117,12 +117,12 @@ }, "playlists": { "sort": { - "title": "Title", - "author": "Author", - "number-of-maps": "Number of Maps", - "duration": "Duration", - "min-notes-per-second": "Min Notes per Second", - "max-notes-per-second": "Max Notes per Second" + "title": "Titre", + "author": "Auteur", + "number-of-maps": "Nombre de cartes", + "duration": "Durée", + "min-notes-per-second": "Minimum de notes par seconde", + "max-notes-per-second": "Maximum de notes par seconde" } }, "mods": { diff --git a/assets/jsons/translations/ja.json b/assets/jsons/translations/ja.json index dcbd5e75..8cc2ecb1 100644 --- a/assets/jsons/translations/ja.json +++ b/assets/jsons/translations/ja.json @@ -117,12 +117,12 @@ }, "playlists": { "sort": { - "title": "Title", - "author": "Author", - "number-of-maps": "Number of Maps", - "duration": "Duration", - "min-notes-per-second": "Min Notes per Second", - "max-notes-per-second": "Max Notes per Second" + "title": "タイトル", + "author": "著者", + "number-of-maps": "地図の数", + "duration": "持続時間", + "min-notes-per-second": "秒あたりの最小ノート数", + "max-notes-per-second": "秒あたりの最大ノート数" } }, "mods": { diff --git a/assets/jsons/translations/ru.json b/assets/jsons/translations/ru.json index 7603de71..83bd88e0 100644 --- a/assets/jsons/translations/ru.json +++ b/assets/jsons/translations/ru.json @@ -117,12 +117,12 @@ }, "playlists": { "sort": { - "title": "Title", - "author": "Author", - "number-of-maps": "Number of Maps", - "duration": "Duration", - "min-notes-per-second": "Min Notes per Second", - "max-notes-per-second": "Max Notes per Second" + "title": "Заголовок", + "author": "Автор", + "number-of-maps": "Количество карт", + "duration": "Длительность", + "min-notes-per-second": "Минимальное количество нот в секунду", + "max-notes-per-second": "Максимальное количество нот в секунду" } }, "mods": { diff --git a/assets/jsons/translations/zh-tw.json b/assets/jsons/translations/zh-tw.json index 83dbebc6..f24b87f6 100644 --- a/assets/jsons/translations/zh-tw.json +++ b/assets/jsons/translations/zh-tw.json @@ -117,12 +117,12 @@ }, "playlists": { "sort": { - "title": "Title", - "author": "Author", - "number-of-maps": "Number of Maps", - "duration": "Duration", - "min-notes-per-second": "Min Notes per Second", - "max-notes-per-second": "Max Notes per Second" + "title": "標題", + "author": "作者", + "number-of-maps": "地圖數量", + "duration": "持續時間", + "min-notes-per-second": "每秒最少音符數", + "max-notes-per-second": "每秒最多音符數" } }, "mods": { diff --git a/assets/jsons/translations/zh.json b/assets/jsons/translations/zh.json index 5331c77f..4744fc6c 100644 --- a/assets/jsons/translations/zh.json +++ b/assets/jsons/translations/zh.json @@ -117,12 +117,12 @@ }, "playlists": { "sort": { - "title": "Title", - "author": "Author", - "number-of-maps": "Number of Maps", - "duration": "Duration", - "min-notes-per-second": "Min Notes per Second", - "max-notes-per-second": "Max Notes per Second" + "title": "标题", + "author": "作者", + "number-of-maps": "地图数量", + "duration": "持续时间", + "min-notes-per-second": "每秒最少音符数", + "max-notes-per-second": "每秒最多音符数" } }, "mods": { From 6d1418dd7cc70ba6216f6bf8a7b0c62bee71fe0f Mon Sep 17 00:00:00 2001 From: silentrald Date: Wed, 16 Oct 2024 09:03:44 +0800 Subject: [PATCH 05/10] [feat-216] aligned with change with v4 schema maps * linted/auto-formatted sorter.service.ts files * changed generics.type to comparator.type --- .../local-playlist-filter-panel.component.tsx | 2 +- src/renderer/services/maps/sorter.service.ts | 45 ++++++++----------- .../services/playlists/sorter.service.ts | 35 +++++---------- src/shared/models/comparator.type.ts | 8 ++++ src/shared/models/generics.type.ts | 3 -- src/shared/models/maps/beat-saver.model.ts | 2 +- 6 files changed, 40 insertions(+), 55 deletions(-) create mode 100644 src/shared/models/comparator.type.ts delete mode 100644 src/shared/models/generics.type.ts diff --git a/src/renderer/components/maps-playlists-panel/playlists/local-playlist-filter-panel.component.tsx b/src/renderer/components/maps-playlists-panel/playlists/local-playlist-filter-panel.component.tsx index 630b6e38..355cb452 100644 --- a/src/renderer/components/maps-playlists-panel/playlists/local-playlist-filter-panel.component.tsx +++ b/src/renderer/components/maps-playlists-panel/playlists/local-playlist-filter-panel.component.tsx @@ -7,7 +7,7 @@ import { hourToS, sToMs } from "shared/helpers/time.helpers"; import { useOnUpdate } from "renderer/hooks/use-on-update.hook"; import formatDuration from "format-duration"; import { LocalBPListsDetails } from "shared/models/playlists/local-playlist.models"; -import { Comparator } from "shared/models/generics.type"; +import { Comparator } from "shared/models/comparator.type"; type Props = { className?: string; diff --git a/src/renderer/services/maps/sorter.service.ts b/src/renderer/services/maps/sorter.service.ts index bcf759c4..a73529b3 100644 --- a/src/renderer/services/maps/sorter.service.ts +++ b/src/renderer/services/maps/sorter.service.ts @@ -1,7 +1,6 @@ -import { Comparator } from "shared/models/generics.type"; +import { Comparator, Comparison } from "shared/models/comparator.type"; import { BsmLocalMap } from "shared/models/maps/bsm-local-map.interface"; - export class MapsSorterService { private static instance: MapsSorterService; @@ -15,44 +14,39 @@ export class MapsSorterService { private readonly comparators: { [key: string]: Comparator; } = { - name: (map1, map2) => - map1.rawInfo._songName.localeCompare(map2.rawInfo._songName), - "song-author": (map1, map2) => - map1.rawInfo._songAuthorName.localeCompare(map2.rawInfo._songAuthorName), - "map-author": (map1, map2) => - map1.rawInfo._levelAuthorName.localeCompare(map2.rawInfo._levelAuthorName), - bpm: (map1, map2) => - map1.rawInfo._beatsPerMinute - map2.rawInfo._beatsPerMinute, + name: (map1, map2) => map1.mapInfo.songName.localeCompare(map2.mapInfo.songName), + "song-author": (map1, map2) => map1.mapInfo.songAuthorName.localeCompare(map2.mapInfo.songAuthorName), + "map-author": (map1, map2) => { + // Compare the number of mappers, else compare the first mapper + const difference = map1.mapInfo.levelMappers.length - map2.mapInfo.levelMappers.length; + return difference || map1.mapInfo.levelMappers[0].localeCompare(map2.mapInfo.levelMappers[0]); + }, + bpm: (map1, map2) => map1.mapInfo.beatsPerMinute - map2.mapInfo.beatsPerMinute, duration: (map1, map2) => { if (!map1.songDetails) { - return map2.songDetails ? -1 : 0; + return map2.songDetails ? Comparison.LESSER : Comparison.EQUAL; } - return !map2.songDetails ? -1 - : map1.songDetails.duration - map2.songDetails.duration; + return !map2.songDetails ? Comparison.GREATER : map1.songDetails.duration - map2.songDetails.duration; }, likes: (map1, map2) => { if (!map1.songDetails) { - return map2.songDetails ? -1 : 0; + return map2.songDetails ? Comparison.LESSER : Comparison.EQUAL; } - return !map2.songDetails ? -1 - : map1.songDetails.upVotes - map2.songDetails.upVotes; + return !map2.songDetails ? Comparison.GREATER : map1.songDetails.upVotes - map2.songDetails.upVotes; }, "date-uploaded": (map1, map2) => { if (!map1.songDetails) { - return map2.songDetails ? -1 : 0; + return map2.songDetails ? Comparison.LESSER : Comparison.EQUAL; } - return !map2.songDetails ? -1 - : map1.songDetails.uploadedAt - map2.songDetails.uploadedAt; - } + return !map2.songDetails ? Comparison.GREATER : map1.songDetails.uploadedAt - map2.songDetails.uploadedAt; + }, }; private addTiebreak(comparator: Comparator): Comparator { - return (map1, map2) => - comparator(map1, map2) - || this.getDefaultComparator()(map1, map2); + return (map1, map2) => comparator(map1, map2) || this.getDefaultComparator()(map1, map2); } public getComparatorKeys(): string[] { @@ -69,9 +63,6 @@ export class MapsSorterService { public getComparator(key: string): Comparator { const comparator = this.comparators[key]; - return comparator - ? this.addTiebreak(comparator) - : this.getDefaultComparator(); + return comparator ? this.addTiebreak(comparator) : this.getDefaultComparator(); } - } diff --git a/src/renderer/services/playlists/sorter.service.ts b/src/renderer/services/playlists/sorter.service.ts index a194be6b..f073ecaa 100644 --- a/src/renderer/services/playlists/sorter.service.ts +++ b/src/renderer/services/playlists/sorter.service.ts @@ -1,4 +1,4 @@ -import { Comparator } from "shared/models/generics.type"; +import { Comparator, Comparison } from "shared/models/comparator.type"; import { LocalBPListsDetails } from "shared/models/playlists/local-playlist.models"; export class PlaylistsSorterService { @@ -14,42 +14,34 @@ export class PlaylistsSorterService { private readonly comparators: { [key: string]: Comparator; } = { - title: (playlist1, playlist2) => - playlist1.playlistTitle.localeCompare(playlist2.playlistTitle), - author: (playlist1, playlist2) => - playlist1.playlistAuthor.localeCompare(playlist2.playlistAuthor), - "number-of-maps": (playlist1, playlist2) => - playlist1.nbMaps - playlist2.nbMaps, + title: (playlist1, playlist2) => playlist1.playlistTitle.localeCompare(playlist2.playlistTitle), + author: (playlist1, playlist2) => playlist1.playlistAuthor.localeCompare(playlist2.playlistAuthor), + "number-of-maps": (playlist1, playlist2) => playlist1.nbMaps - playlist2.nbMaps, duration: (playlist1, playlist2) => { if (!playlist1.duration) { - return playlist2.duration ? -1 : 0; + return playlist2.duration ? Comparison.LESSER : Comparison.EQUAL; } - return !playlist2.duration ? -1 - : playlist1.duration - playlist2.duration; + return !playlist2.duration ? Comparison.GREATER : playlist1.duration - playlist2.duration; }, "min-notes-per-second": (playlist1, playlist2) => { if (!playlist1.minNps) { - return playlist2.minNps ? -1 : 0; + return playlist2.minNps ? Comparison.LESSER : Comparison.EQUAL; } - return !playlist2.minNps ? -1 - : playlist1.minNps - playlist2.minNps; + return !playlist2.minNps ? Comparison.GREATER : playlist1.minNps - playlist2.minNps; }, "max-notes-per-second": (playlist1, playlist2) => { if (!playlist1.maxNps) { - return playlist2.maxNps ? -1 : 0; + return playlist2.maxNps ? Comparison.LESSER : Comparison.EQUAL; } - return !playlist2.maxNps ? -1 - : playlist1.maxNps - playlist2.maxNps; + return !playlist2.maxNps ? Comparison.GREATER : playlist1.maxNps - playlist2.maxNps; }, }; private addTiebreak(comparator: Comparator): Comparator { - return (playlist1, playlist2) => - comparator(playlist1, playlist2) - || this.getDefaultComparator()(playlist1, playlist2); + return (playlist1, playlist2) => comparator(playlist1, playlist2) || this.getDefaultComparator()(playlist1, playlist2); } public getComparatorKeys(): string[] { @@ -66,9 +58,6 @@ export class PlaylistsSorterService { public getComparator(key: string): Comparator { const comparator = this.comparators[key]; - return comparator - ? this.addTiebreak(comparator) - : this.getDefaultComparator(); + return comparator ? this.addTiebreak(comparator) : this.getDefaultComparator(); } - } diff --git a/src/shared/models/comparator.type.ts b/src/shared/models/comparator.type.ts new file mode 100644 index 00000000..c6c69dfc --- /dev/null +++ b/src/shared/models/comparator.type.ts @@ -0,0 +1,8 @@ + +export enum Comparison { + EQUAL = 0, + GREATER = 1, + LESSER = -1, +}; + +export type Comparator = (object1: T, object2: T) => number; diff --git a/src/shared/models/generics.type.ts b/src/shared/models/generics.type.ts deleted file mode 100644 index 33ad62f4..00000000 --- a/src/shared/models/generics.type.ts +++ /dev/null @@ -1,3 +0,0 @@ - -export type Comparator = (object1: T, object2: T) => number; - diff --git a/src/shared/models/maps/beat-saver.model.ts b/src/shared/models/maps/beat-saver.model.ts index d08bc525..2a2205d2 100644 --- a/src/shared/models/maps/beat-saver.model.ts +++ b/src/shared/models/maps/beat-saver.model.ts @@ -1,7 +1,7 @@ import { ObjectValues } from "shared/helpers/type.helpers"; import { SongDetailDiffCharactertistic, SongDiffName } from "./song-details-cache/song-details-cache.model"; import { BsmLocalMap } from "./bsm-local-map.interface"; -import { Comparator } from "../generics.type"; +import { Comparator } from "../comparator.type"; export interface BsvMapDetail { automapper: boolean; From e3d27e4b4fef075d4bb4296c44dfe1becfb23e10 Mon Sep 17 00:00:00 2001 From: silentrald Date: Mon, 6 Jan 2025 19:25:51 +0800 Subject: [PATCH 06/10] [feat-216] applied ui for filter/sorting map/playlist * also applied change to model filter ui --- .../maps-playlists-panel.component.tsx | 48 ++++++++++--------- .../models-panel.component.tsx | 4 +- .../components/svgs/bsm-icon.component.tsx | 7 ++- .../icons/arrow-upward-icon.component.tsx | 15 ++++++ 4 files changed, 49 insertions(+), 25 deletions(-) create mode 100644 src/renderer/components/svgs/icons/arrow-upward-icon.component.tsx diff --git a/src/renderer/components/maps-playlists-panel/maps-playlists-panel.component.tsx b/src/renderer/components/maps-playlists-panel/maps-playlists-panel.component.tsx index b947889f..54e3fe46 100644 --- a/src/renderer/components/maps-playlists-panel/maps-playlists-panel.component.tsx +++ b/src/renderer/components/maps-playlists-panel/maps-playlists-panel.component.tsx @@ -253,7 +253,7 @@ export function MapsPlaylistsPanel({ version, isActive }: Props) { return ( <> -