Skip to content

Commit d182b7b

Browse files
committed
Indicate deleted account in search result
1 parent fc22d93 commit d182b7b

File tree

2 files changed

+33
-11
lines changed

2 files changed

+33
-11
lines changed

src/components/gameRecords/playerSearch.tsx

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import React from "react";
22
import { useEffect, useState, useMemo } from "react";
33

4-
import { PlayerMetadataLite, LevelWithDelta, Level } from "../../data/types";
5-
import { searchPlayer } from "../../data/source/misc";
4+
import { LevelWithDelta, Level } from "../../data/types";
5+
import { searchPlayer, PlayerSearchResult } from "../../data/source/misc";
66
import { Redirect } from "react-router-dom";
77
import { generatePlayerPathById } from "./routeUtils";
88
import { useTranslation } from "react-i18next";
@@ -11,12 +11,16 @@ import { networkError } from "../../utils/notify";
1111
import Conf, { CONFIGURATIONS } from "../../utils/conf";
1212
import Loading from "../misc/loading";
1313

14-
const playerSearchCache = new Map<string, PlayerMetadataLite[] | Promise<PlayerMetadataLite[]>>();
14+
type PlayerSearchResultExt = PlayerSearchResult & {
15+
isDeleted?: boolean;
16+
};
17+
18+
const playerSearchCache = new Map<string, PlayerSearchResultExt[] | Promise<PlayerSearchResultExt[]>>();
1519
const NUM_FETCH = 20;
1620

1721
const normalizeName = (s: string) => s.toLowerCase().trim();
1822

19-
function findRawResultFromCache(prefix: string): { result: PlayerMetadataLite[]; isExactMatch: boolean } | null {
23+
function findRawResultFromCache(prefix: string): { result: PlayerSearchResultExt[]; isExactMatch: boolean } | null {
2024
const normalizedPrefix = normalizeName(prefix);
2125
prefix = normalizedPrefix;
2226
while (prefix) {
@@ -33,7 +37,7 @@ function findRawResultFromCache(prefix: string): { result: PlayerMetadataLite[];
3337
return null;
3438
}
3539

36-
function getCrossSiteConf(x: PlayerMetadataLite) {
40+
function getCrossSiteConf(x: PlayerSearchResultExt) {
3741
if (Conf.availableModes.length > 1) {
3842
const level = new Level(x.level.id);
3943
if (!Conf.availableModes.some((mode) => level.isAllowedMode(mode))) {
@@ -42,7 +46,7 @@ function getCrossSiteConf(x: PlayerMetadataLite) {
4246
}
4347
return null;
4448
}
45-
function getOptionLabel(x: PlayerMetadataLite, t: (x: string) => string): string {
49+
function getOptionLabel(x: PlayerSearchResultExt, t: (x: string) => string): string {
4650
let ret = `[${LevelWithDelta.getTag(x.level)}] ${x.nickname}`;
4751
const conf = getCrossSiteConf(x);
4852
if (conf) {
@@ -52,7 +56,7 @@ function getOptionLabel(x: PlayerMetadataLite, t: (x: string) => string): string
5256
}
5357
export function PlayerSearch() {
5458
const { t } = useTranslation("form");
55-
const [selectedItem, setSelectedItem] = useState(null as PlayerMetadataLite | null);
59+
const [selectedItem, setSelectedItem] = useState(null as PlayerSearchResultExt | null);
5660
const [version, setVersion] = useState(0);
5761
const [searchText, setSearchText] = useState("");
5862
const [open, setOpen] = React.useState(false);
@@ -69,7 +73,7 @@ export function PlayerSearch() {
6973
}
7074
const normalizedPrefix = normalizeName(searchText);
7175
let mayHaveMore = cachedResult.result.length >= NUM_FETCH;
72-
const filteredPlayers = [] as PlayerMetadataLite[];
76+
const filteredPlayers = [] as PlayerSearchResultExt[];
7377
cachedResult.result.forEach((player) => {
7478
if (normalizeName(player.nickname).startsWith(normalizedPrefix)) {
7579
filteredPlayers.push(player);
@@ -100,7 +104,10 @@ export function PlayerSearch() {
100104
if (playerSearchCache.has(prefix)) {
101105
return;
102106
}
103-
const promise = searchPlayer(prefix, NUM_FETCH).then(function (players) {
107+
const promise = searchPlayer(prefix, NUM_FETCH).then(function (players: PlayerSearchResultExt[]) {
108+
players.forEach((x) => {
109+
x.isDeleted = players.some((y) => x.nickname === y.nickname && x.latest_timestamp < y.latest_timestamp);
110+
});
104111
playerSearchCache.set(prefix, players);
105112
if (!cancelled) {
106113
setVersion(new Date().getTime());
@@ -145,6 +152,17 @@ export function PlayerSearch() {
145152
onChange={(_, value, reason) => reason === "selectOption" && setSelectedItem(value)}
146153
options={players}
147154
getOptionLabel={(x) => getOptionLabel(x, t)}
155+
renderOption={(props, option) => {
156+
const { key, ...otherProps } = props as typeof props & { key: string };
157+
return (
158+
<li key={key} {...otherProps}>
159+
<span style={option.isDeleted ? { textDecoration: "line-through", color: "#888" } : {}}>
160+
{" "}
161+
{getOptionLabel(option, t)}
162+
</span>
163+
</li>
164+
);
165+
}}
148166
isOptionEqualToValue={(option, value) => option.id === value.id}
149167
loading={isLoading}
150168
filterOptions={(x) => x}

src/data/source/misc.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,16 @@ import { RankRateBySeat } from "../types";
99
import { CareerRankingItem, CareerRankingType } from "../types/ranking";
1010
import { GlobalStatistics, FanStats, GlobalHistogram, LevelStatistics } from "../types/statistics";
1111

12-
export async function searchPlayer(prefix: string, limit = 20): Promise<PlayerMetadataLite[]> {
12+
13+
export type PlayerSearchResult = Pick<PlayerMetadataLite, "id" | "nickname" | "level"> & {
14+
latest_timestamp: number;
15+
};
16+
export async function searchPlayer(prefix: string, limit = 20): Promise<PlayerSearchResult[]> {
1317
prefix = prefix.trim();
1418
if (!prefix) {
1519
return [];
1620
}
17-
const result = await apiGet<PlayerMetadataLite[]>(
21+
const result = await apiGet<PlayerSearchResult[]>(
1822
`search_player/${encodeURIComponent(prefix)}?limit=${limit}&tag=all`
1923
);
2024
return result || [];

0 commit comments

Comments
 (0)