80 lines
2.2 KiB
TypeScript
80 lines
2.2 KiB
TypeScript
import { normalizeSearch, searchItems } from "../search";
|
|
|
|
export type TeamSearchTarget =
|
|
| "all"
|
|
| "team"
|
|
| "character"
|
|
| "weapon"
|
|
| "artifact";
|
|
|
|
export interface TeamSearchValues {
|
|
team: string[];
|
|
character: string[];
|
|
weapon: string[];
|
|
artifact: string[];
|
|
}
|
|
|
|
export interface DpsTeam {
|
|
rotationSeconds: string | number;
|
|
members: Array<{ damage: string | number }>;
|
|
}
|
|
|
|
export type DpsOrder = "best" | "worst" | null;
|
|
|
|
export function teamDps(team: DpsTeam): number {
|
|
const seconds = Number(team.rotationSeconds);
|
|
if (!(seconds > 0)) return 0;
|
|
return team.members.reduce((sum, member) => sum + Number(member.damage), 0) / seconds;
|
|
}
|
|
|
|
export function sortTeamsByDps<Team extends DpsTeam>(
|
|
teams: readonly Team[],
|
|
order: DpsOrder,
|
|
): Team[] {
|
|
if (!order) return [...teams];
|
|
|
|
return teams
|
|
.map((team, index) => ({ team, index }))
|
|
.sort((left, right) => {
|
|
const difference = order === "best"
|
|
? teamDps(right.team) - teamDps(left.team)
|
|
: teamDps(left.team) - teamDps(right.team);
|
|
return difference || left.index - right.index;
|
|
})
|
|
.map(({ team }) => team);
|
|
}
|
|
|
|
export function matchesTeamSearch(
|
|
values: TeamSearchValues,
|
|
query: string,
|
|
target: TeamSearchTarget,
|
|
): boolean {
|
|
const normalizedQuery = normalizeSearch(query);
|
|
if (!normalizedQuery) return true;
|
|
|
|
const candidates = target === "all"
|
|
? Object.values(values).flat()
|
|
: values[target];
|
|
|
|
return candidates.some((value) =>
|
|
normalizeSearch(value).includes(normalizedQuery),
|
|
);
|
|
}
|
|
|
|
export function searchTeams<T>(
|
|
teams: readonly T[],
|
|
values: (team: T) => TeamSearchValues,
|
|
query: string,
|
|
target: TeamSearchTarget,
|
|
) {
|
|
const candidates = (team: T) => {
|
|
const fields = values(team);
|
|
return target === "all" ? Object.values(fields).flat() : fields[target];
|
|
};
|
|
// Separate candidates retain the existing rule that a direct match must
|
|
// occur within one name, rather than spanning two members or categories.
|
|
const direct = teams.filter((team) => matchesTeamSearch(values(team), query, target));
|
|
if (direct.length || !query.trim()) return { items: direct, fuzzy: false };
|
|
return searchItems(teams, query, () => "", candidates);
|
|
}
|