Skip to content

Commit 5252475

Browse files
committed
feat: user filter
1 parent 54d81e8 commit 5252475

File tree

6 files changed

+173
-42
lines changed

6 files changed

+173
-42
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
"linkify-react": "^3.0.4",
4545
"linkifyjs": "^3.0.5",
4646
"lodash-es": "^4.17.21",
47-
"maa-copilot-client": "https://github.com/MaaAssistantArknights/maa-copilot-client-ts.git#0.1.0-SNAPSHOT.822.787cfb1",
47+
"maa-copilot-client": "https://github.com/MaaAssistantArknights/maa-copilot-client-ts.git#0.1.0-SNAPSHOT.824.f8ad839",
4848
"normalize.css": "^8.0.1",
4949
"prettier": "^3.2.5",
5050
"react": "^18.0.0",

src/apis/user.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import useSWR from 'swr'
22

3-
import { NotFoundError } from '../utils/error'
43
import { UserApi } from '../utils/maa-copilot-client'
54

65
export function useUserInfo({
@@ -20,13 +19,27 @@ export function useUserInfo({
2019
userId,
2120
})
2221

23-
// FIXME: 严谨一点!!!
24-
if (res.data.userName === '未知用户:(') {
25-
throw new NotFoundError()
26-
}
27-
2822
return res.data
2923
},
3024
{ suspense },
3125
)
3226
}
27+
28+
export function useUserSearch({ keyword }: { keyword?: string }) {
29+
return useSWR(
30+
keyword ? ['userSearch', keyword] : null,
31+
async ([, keyword]) => {
32+
const res = await new UserApi({
33+
sendToken: 'never',
34+
requireData: true,
35+
}).searchUsers({
36+
userName: keyword,
37+
})
38+
return res.data
39+
},
40+
{
41+
revalidateOnFocus: false,
42+
keepPreviousData: true,
43+
},
44+
)
45+
}

src/components/Operations.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
import { UseOperationsParams } from 'apis/operation'
1010
import { useAtom } from 'jotai'
1111
import { debounce } from 'lodash-es'
12+
import { MaaUserInfo } from 'maa-copilot-client'
1213
import { ComponentType, useMemo, useState } from 'react'
1314

1415
import { CardTitle } from 'components/CardTitle'
@@ -20,6 +21,7 @@ import { authAtom } from '../store/auth'
2021
import { LevelSelect } from './LevelSelect'
2122
import { OperatorFilter } from './OperatorFilter'
2223
import { withSuspensable } from './Suspensable'
24+
import { UserFilter } from './UserFilter'
2325

2426
export const Operations: ComponentType = withSuspensable(() => {
2527
const [queryParams, setQueryParams] = useState<
@@ -33,6 +35,7 @@ export const Operations: ComponentType = withSuspensable(() => {
3335
[],
3436
)
3537

38+
const [selectedUser, setSelectedUser] = useState<MaaUserInfo>()
3639
const [selectedOperators, setSelectedOperators] = useState<string[]>([])
3740

3841
const [authState] = useAtom(authAtom)
@@ -133,6 +136,17 @@ export const Operations: ComponentType = withSuspensable(() => {
133136
operators={selectedOperators}
134137
onChange={setSelectedOperators}
135138
/>
139+
<UserFilter
140+
className="mt-2"
141+
user={selectedUser}
142+
onChange={(user) => {
143+
setSelectedUser(user)
144+
setQueryParams((old) => ({
145+
...old,
146+
uploaderId: user?.id,
147+
}))
148+
}}
149+
/>
136150
</div>
137151
</div>
138152
<div className="flex flex-col">

src/components/Suggest.tsx

Lines changed: 53 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,29 @@
11
import { Suggest2, Suggest2Props } from '@blueprintjs/select'
22

3-
import { noop } from 'lodash-es'
3+
import { debounce, noop } from 'lodash-es'
44
import { useEffect, useMemo, useRef, useState } from 'react'
55
import { ControllerFieldState } from 'react-hook-form'
66

77
import { FieldResetButton } from './FieldResetButton'
88

99
interface SuggestProps<T> extends Suggest2Props<T> {
10-
debounce?: number // defaults to 100(ms), set to 0 to disable
10+
query?: string // controlled query, optional
11+
debounce?: number // defaults to 100(ms)
1112
updateQueryOnSelect?: boolean
1213
fieldState?: ControllerFieldState
14+
onDebouncedQueryChange?: (query: string) => void
1315
onReset?: () => void
1416
}
1517

1618
export const Suggest = <T,>({
17-
debounce = 100,
19+
debounce: debounceTime = 100,
1820
updateQueryOnSelect,
1921
fieldState,
22+
query: propQuery,
23+
onQueryChange,
24+
onDebouncedQueryChange,
2025
onReset,
2126

22-
items,
2327
itemListPredicate,
2428
selectedItem,
2529
inputValueRenderer,
@@ -33,45 +37,60 @@ export const Suggest = <T,>({
3337
ref.current['selectText'] = noop
3438
}
3539

36-
const [query, setQuery] = useState('')
40+
const onQueryChangeRef = useRef(onQueryChange)
41+
onQueryChangeRef.current = onQueryChange
42+
const onDebouncedQueryChangeRef = useRef(onDebouncedQueryChange)
43+
onDebouncedQueryChangeRef.current = onDebouncedQueryChange
44+
45+
const [internalQuery, setInternalQuery] = useState('')
3746
const [debouncedQuery, setDebouncedQuery] = useState('')
47+
const query = propQuery ?? internalQuery
48+
const updateQuery = useMemo(() => {
49+
// note: debouncing is required to fix https://github.com/MaaAssistantArknights/maa-copilot-frontend/issues/72
50+
const debouncedUpdateQuery = debounce((query: string) => {
51+
setDebouncedQuery(query)
52+
onDebouncedQueryChangeRef.current?.(query)
53+
}, debounceTime)
3854

39-
// the debounce fixes https://github.com/MaaAssistantArknights/maa-copilot-frontend/issues/72
40-
useEffect(() => {
41-
if (debounce) {
42-
const timer = setTimeout(() => setDebouncedQuery(query), debounce)
43-
return () => clearTimeout(timer)
55+
const updateQuery = (query: string, immediately: boolean) => {
56+
setInternalQuery(query)
57+
onQueryChangeRef.current?.(query)
58+
debouncedUpdateQuery(query)
59+
if (immediately) {
60+
debouncedUpdateQuery.flush()
61+
}
4462
}
45-
setDebouncedQuery(query)
46-
return undefined
47-
}, [query, debounce])
63+
updateQuery.cancel = debouncedUpdateQuery.cancel
64+
return updateQuery
65+
}, [debounceTime])
4866

49-
const filteredItems = useMemo(
50-
() => itemListPredicate?.(debouncedQuery, items) || items,
51-
[itemListPredicate, debouncedQuery, items],
52-
)
67+
// 取消等待中的调用
68+
useEffect(() => () => updateQuery.cancel(), [updateQuery])
5369

5470
useEffect(() => {
55-
if (!fieldState?.isTouched) {
56-
setQuery('')
57-
setDebouncedQuery('')
71+
if (fieldState && !fieldState.isTouched) {
72+
updateQuery('', true)
5873
}
59-
}, [fieldState?.isTouched])
74+
}, [fieldState, updateQuery])
6075

6176
useEffect(() => {
6277
if (updateQueryOnSelect && selectedItem) {
63-
setQuery(inputValueRenderer(selectedItem))
78+
updateQuery(inputValueRenderer(selectedItem), true)
6479
}
65-
}, [updateQueryOnSelect, selectedItem, inputValueRenderer])
80+
}, [updateQueryOnSelect, selectedItem, inputValueRenderer, updateQuery])
6681

6782
return (
6883
<Suggest2<T>
6984
ref={ref}
70-
items={filteredItems}
7185
query={query}
72-
onQueryChange={setQuery}
86+
onQueryChange={(query) => updateQuery(query, false)}
7387
selectedItem={selectedItem}
7488
inputValueRenderer={inputValueRenderer}
89+
itemListPredicate={
90+
itemListPredicate
91+
? (query, items) => itemListPredicate(debouncedQuery, items)
92+
: undefined
93+
}
7594
inputProps={{
7695
onKeyDown: (event) => {
7796
// prevent form submission
@@ -82,15 +101,17 @@ export const Suggest = <T,>({
82101
rightElement: (
83102
<FieldResetButton
84103
disabled={
85-
fieldState
86-
? !fieldState.isDirty
87-
: onReset
88-
? !(query || selectedItem !== null)
89-
: true
104+
!(
105+
// enabled =
106+
(fieldState
107+
? fieldState.isDirty
108+
: onReset
109+
? query || selectedItem !== null
110+
: false)
111+
)
90112
}
91113
onReset={() => {
92-
setQuery('')
93-
setDebouncedQuery('')
114+
updateQuery('', true)
94115
onReset?.()
95116
}}
96117
/>

src/components/UserFilter.tsx

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { MenuItem, Spinner } from '@blueprintjs/core'
2+
3+
import clsx from 'clsx'
4+
import { MaaUserInfo } from 'maa-copilot-client'
5+
import { FC, useState } from 'react'
6+
7+
import { useUserSearch } from '../apis/user'
8+
import { formatError } from '../utils/error'
9+
import { Suggest } from './Suggest'
10+
11+
interface UserFilterProps {
12+
className?: string
13+
user?: MaaUserInfo
14+
onChange: (user: MaaUserInfo | undefined) => void
15+
}
16+
17+
export const UserFilter: FC<UserFilterProps> = ({
18+
className,
19+
user,
20+
onChange,
21+
}) => {
22+
const [keyword, setKeyword] = useState('')
23+
const [debouncedKeyword, setDebouncedKeyword] = useState('')
24+
const {
25+
data: users = [],
26+
error,
27+
isLoading,
28+
isValidating,
29+
} = useUserSearch({
30+
// 如果已经选中了用户,就不再搜索
31+
keyword: debouncedKeyword === user?.userName ? undefined : debouncedKeyword,
32+
})
33+
34+
return (
35+
<Suggest<MaaUserInfo>
36+
debounce={500}
37+
items={users}
38+
itemListPredicate={() => (error ? [] : users)} // 有 error 时用 noResults 显示错误信息
39+
query={keyword}
40+
onQueryChange={setKeyword}
41+
onDebouncedQueryChange={setDebouncedKeyword}
42+
onReset={() => onChange(undefined)}
43+
className={clsx(className, user && '[&_input:not(:focus)]:italic')}
44+
itemRenderer={(item, { handleClick, handleFocus, modifiers }) => (
45+
<MenuItem
46+
key={item.id}
47+
text={item.userName}
48+
onClick={handleClick}
49+
onFocus={handleFocus}
50+
selected={modifiers.active}
51+
disabled={modifiers.disabled}
52+
/>
53+
)}
54+
selectedItem={user ?? null}
55+
onItemSelect={(user) => onChange(user)}
56+
inputValueRenderer={(item) => item.userName}
57+
noResults={
58+
<MenuItem
59+
disabled
60+
text={
61+
isLoading
62+
? '正在搜索...'
63+
: error
64+
? '搜索失败:' + formatError(error)
65+
: keyword && debouncedKeyword
66+
? '查无此人 (゚Д゚≡゚д゚)!?'
67+
: '输入用户名以搜索'
68+
}
69+
/>
70+
}
71+
inputProps={{
72+
placeholder: '上传者',
73+
leftIcon: isValidating ? (
74+
<Spinner className="bp4-icon" size={16} />
75+
) : (
76+
'person'
77+
),
78+
large: true,
79+
size: 64,
80+
}}
81+
/>
82+
)
83+
}

yarn.lock

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3642,9 +3642,9 @@ lru-cache@^6.0.0:
36423642
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.0.tgz#0bd445ca57363465900f4d1f9bd8db343a4d95c3"
36433643
integrity sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==
36443644

3645-
"maa-copilot-client@https://github.com/MaaAssistantArknights/maa-copilot-client-ts.git#0.1.0-SNAPSHOT.822.787cfb1":
3646-
version "0.1.0-SNAPSHOT.822.787cfb1"
3647-
resolved "https://github.com/MaaAssistantArknights/maa-copilot-client-ts.git#4660878e9f25a4ec9a356dfca6762c3ef5797a55"
3645+
"maa-copilot-client@https://github.com/MaaAssistantArknights/maa-copilot-client-ts.git#0.1.0-SNAPSHOT.824.f8ad839":
3646+
version "0.1.0-SNAPSHOT.824.f8ad839"
3647+
resolved "https://github.com/MaaAssistantArknights/maa-copilot-client-ts.git#02c72f9a306c097e3307b8120a35b8e72aff167e"
36483648

36493649
make-dir@^2.1.0:
36503650
version "2.1.0"

0 commit comments

Comments
 (0)