11import React from "react" ;
22import { 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" ;
66import { Redirect } from "react-router-dom" ;
77import { generatePlayerPathById } from "./routeUtils" ;
88import { useTranslation } from "react-i18next" ;
@@ -11,12 +11,16 @@ import { networkError } from "../../utils/notify";
1111import Conf , { CONFIGURATIONS } from "../../utils/conf" ;
1212import 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 [ ] > > ( ) ;
1519const NUM_FETCH = 20 ;
1620
1721const 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}
5357export 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 }
0 commit comments