58 lines
2.0 KiB
TypeScript
58 lines
2.0 KiB
TypeScript
export type SongSort = 'title' | 'difficulty-asc' | 'difficulty-desc';
|
|
|
|
export type SongChartSummary = {
|
|
songId: string;
|
|
title: string;
|
|
difficulty: string;
|
|
difficultyLevel?: number | null;
|
|
scoreCalibrationSource?: 'measured' | 'local-estimated-v1' | string;
|
|
releaseTime?: number | null;
|
|
};
|
|
|
|
export type SongOption = {
|
|
id: string;
|
|
title: string;
|
|
difficultyLevel: number | null;
|
|
};
|
|
|
|
const englishTitleCollator = new Intl.Collator('en', {
|
|
sensitivity: 'base',
|
|
numeric: true
|
|
});
|
|
|
|
function normalizedSearch(value: string): string {
|
|
return value.normalize('NFKD').toLocaleLowerCase('en').replace(/[^a-z0-9]+/g, ' ').trim();
|
|
}
|
|
|
|
export function songOptions(
|
|
charts: readonly SongChartSummary[],
|
|
difficulty: string,
|
|
query = '',
|
|
sort: SongSort = 'title'
|
|
): SongOption[] {
|
|
const levelBySongId = new Map(charts
|
|
.filter((chart) => chart.difficulty === difficulty)
|
|
.map((chart) => [chart.songId, chart.difficultyLevel ?? null]));
|
|
const options = Array.from(new Map(charts.map((chart) => [chart.songId, {
|
|
id: chart.songId,
|
|
title: chart.title,
|
|
difficultyLevel: levelBySongId.get(chart.songId) ?? null
|
|
}])).values());
|
|
const search = normalizedSearch(query);
|
|
const filtered = search
|
|
? options.filter((song) => normalizedSearch(`${song.title} ${song.id}`).includes(search))
|
|
: options;
|
|
const titleOrder = (left: SongOption, right: SongOption) =>
|
|
englishTitleCollator.compare(left.title, right.title) || left.id.localeCompare(right.id);
|
|
|
|
return filtered.sort((left, right) => {
|
|
if (sort === 'title') return titleOrder(left, right);
|
|
if (left.difficultyLevel === null) return right.difficultyLevel === null ? titleOrder(left, right) : 1;
|
|
if (right.difficultyLevel === null) return -1;
|
|
const leftLevel = left.difficultyLevel;
|
|
const rightLevel = right.difficultyLevel;
|
|
const levelOrder = sort === 'difficulty-desc' ? rightLevel - leftLevel : leftLevel - rightLevel;
|
|
return levelOrder || titleOrder(left, right);
|
|
});
|
|
}
|