@@ -18,10 +18,12 @@ import { message } from '@/StaticAnt';
18
18
import AnnotationContext from '../../annotation.context' ;
19
19
20
20
interface AnnotationRightCornerProps {
21
- isLastSample : boolean ;
22
- isFirstSample : boolean ;
23
21
// 用于标注预览
24
22
noSave ?: boolean ;
23
+
24
+ fetchNext ?: ( ) => void ;
25
+
26
+ totalSize : number ;
25
27
}
26
28
27
29
export const SAMPLE_CHANGED = 'sampleChanged' ;
@@ -56,7 +58,7 @@ export interface AnnotationLoaderData {
56
58
samples : SampleListResponse ;
57
59
}
58
60
59
- const AnnotationRightCorner = ( { isLastSample , isFirstSample , noSave } : AnnotationRightCornerProps ) => {
61
+ const AnnotationRightCorner = ( { noSave , fetchNext , totalSize } : AnnotationRightCornerProps ) => {
60
62
const isFetching = useIsFetching ( ) ;
61
63
const isMutating = useIsMutating ( ) ;
62
64
const isGlobalLoading = isFetching > 0 || isMutating > 0 ;
@@ -67,10 +69,20 @@ const AnnotationRightCorner = ({ isLastSample, isFirstSample, noSave }: Annotati
67
69
const sampleId = routeParams . sampleId ;
68
70
const { samples, setSamples, task } = useContext ( AnnotationContext ) ;
69
71
const sampleIndex = _ . findIndex ( samples , ( sample : SampleResponse ) => sample . id === + sampleId ! ) ;
72
+ const isLastSample = _ . findIndex ( samples , { id : + sampleId ! } ) === samples . length - 1 ;
73
+ const isFirstSample = _ . findIndex ( samples , { id : + sampleId ! } ) === 0 ;
70
74
const currentSample = samples [ sampleIndex ] ;
71
75
const isSampleSkipped = currentSample ?. state === SampleState . SKIPPED ;
72
76
const [ searchParams ] = useSearchParams ( ) ;
73
77
78
+ // 第一次进入就是40的倍数时,获取下一页数据
79
+ useEffect ( ( ) => {
80
+ if ( isLastSample && samples . length < totalSize ) {
81
+ // TODO: fetchNext 调用两次
82
+ fetchNext ?.( ) ;
83
+ }
84
+ } , [ fetchNext , isLastSample , samples . length , totalSize ] ) ;
85
+
74
86
const navigateWithSearch = useCallback (
75
87
( to : string ) => {
76
88
const searchStr = searchParams . toString ( ) ;
@@ -84,78 +96,6 @@ const AnnotationRightCorner = ({ isLastSample, isFirstSample, noSave }: Annotati
84
96
[ navigate , searchParams ] ,
85
97
) ;
86
98
87
- const handleCancelSkipSample = async ( ) => {
88
- if ( noSave ) {
89
- return ;
90
- }
91
-
92
- await updateSampleState (
93
- {
94
- task_id : + taskId ! ,
95
- sample_id : + sampleId ! ,
96
- } ,
97
- {
98
- ...currentSample ,
99
- state : SampleState . NEW ,
100
- } ,
101
- ) ;
102
-
103
- setSamples (
104
- samples . map ( ( sample : SampleResponse ) =>
105
- sample . id === + sampleId ! ? { ...sample , state : SampleState . NEW } : sample ,
106
- ) ,
107
- ) ;
108
- } ;
109
-
110
- const handleSkipSample = async ( ) => {
111
- if ( noSave ) {
112
- return ;
113
- }
114
-
115
- await updateSampleState (
116
- {
117
- task_id : + taskId ! ,
118
- sample_id : + sampleId ! ,
119
- } ,
120
- {
121
- ...currentSample ,
122
- state : SampleState . SKIPPED ,
123
- } ,
124
- ) ;
125
-
126
- setSamples (
127
- samples . map ( ( sample : SampleResponse ) =>
128
- sample . id === + sampleId ! ? { ...sample , state : SampleState . SKIPPED } : sample ,
129
- ) ,
130
- ) ;
131
- // 切换到下一个文件
132
- if ( ! isLastSample ) {
133
- navigateWithSearch ( `/tasks/${ taskId } /samples/${ _ . get ( samples , `[${ sampleIndex + 1 } ].id` ) } ` ) ;
134
- } else {
135
- navigateWithSearch ( `/tasks/${ taskId } /samples/finished` ) ;
136
- }
137
- } ;
138
-
139
- useHotkeys (
140
- 'ctrl+space, meta+space' ,
141
- ( ) => {
142
- if ( noSave ) {
143
- return ;
144
- }
145
-
146
- if ( currentSample . state === SampleState . SKIPPED ) {
147
- handleCancelSkipSample ( ) ;
148
- } else {
149
- handleSkipSample ( ) ;
150
- }
151
- } ,
152
- {
153
- keyup : true ,
154
- keydown : false ,
155
- } ,
156
- [ handleSkipSample , handleCancelSkipSample , currentSample ] ,
157
- ) ;
158
-
159
99
const saveCurrentSample = useCallback ( async ( ) => {
160
100
if ( currentSample ?. state === SampleState . SKIPPED || noSave || ! task ?. media_type ) {
161
101
return ;
@@ -301,7 +241,66 @@ const AnnotationRightCorner = ({ isLastSample, isFirstSample, noSave }: Annotati
301
241
setTimeout ( revalidator . revalidate ) ;
302
242
} , [ saveCurrentSample , navigateWithSearch , taskId , revalidator . revalidate ] ) ;
303
243
244
+ const handleCancelSkipSample = async ( ) => {
245
+ if ( noSave ) {
246
+ return ;
247
+ }
248
+
249
+ await updateSampleState (
250
+ {
251
+ task_id : + taskId ! ,
252
+ sample_id : + sampleId ! ,
253
+ } ,
254
+ {
255
+ ...currentSample ,
256
+ state : SampleState . NEW ,
257
+ } ,
258
+ ) ;
259
+
260
+ setSamples (
261
+ samples . map ( ( sample : SampleResponse ) =>
262
+ sample . id === + sampleId ! ? { ...sample , state : SampleState . NEW } : sample ,
263
+ ) ,
264
+ ) ;
265
+ } ;
266
+
267
+ const handleSkipSample = async ( ) => {
268
+ if ( noSave ) {
269
+ return ;
270
+ }
271
+
272
+ await updateSampleState (
273
+ {
274
+ task_id : + taskId ! ,
275
+ sample_id : + sampleId ! ,
276
+ } ,
277
+ {
278
+ ...currentSample ,
279
+ state : SampleState . SKIPPED ,
280
+ } ,
281
+ ) ;
282
+
283
+ setSamples (
284
+ samples . map ( ( sample : SampleResponse ) =>
285
+ sample . id === + sampleId ! ? { ...sample , state : SampleState . SKIPPED } : sample ,
286
+ ) ,
287
+ ) ;
288
+
289
+ await saveCurrentSample ( ) ;
290
+ // 切换到下一个文件
291
+ if ( ! isLastSample ) {
292
+ navigateWithSearch ( `/tasks/${ taskId } /samples/${ _ . get ( samples , `[${ sampleIndex + 1 } ].id` ) } ` ) ;
293
+ } else {
294
+ navigateWithSearch ( `/tasks/${ taskId } /samples/finished` ) ;
295
+ }
296
+ } ;
297
+
304
298
const handleNextSample = useCallback ( ( ) => {
299
+ // 到达分页边界,触发加载下一页
300
+ if ( sampleIndex === samples . length - 2 && samples . length < totalSize ) {
301
+ fetchNext ?.( ) ;
302
+ }
303
+
305
304
if ( noSave ) {
306
305
navigateWithSearch ( `/tasks/${ taskId } /samples/${ _ . get ( samples , `[${ sampleIndex + 1 } ].id` ) } ` ) ;
307
306
@@ -315,7 +314,18 @@ const AnnotationRightCorner = ({ isLastSample, isFirstSample, noSave }: Annotati
315
314
navigateWithSearch ( `/tasks/${ taskId } /samples/${ _ . get ( samples , `[${ sampleIndex + 1 } ].id` ) } ` ) ;
316
315
} ) ;
317
316
}
318
- } , [ noSave , isLastSample , navigateWithSearch , taskId , samples , sampleIndex , handleComplete , saveCurrentSample ] ) ;
317
+ } , [
318
+ sampleIndex ,
319
+ samples ,
320
+ totalSize ,
321
+ noSave ,
322
+ isLastSample ,
323
+ fetchNext ,
324
+ navigateWithSearch ,
325
+ taskId ,
326
+ handleComplete ,
327
+ saveCurrentSample ,
328
+ ] ) ;
319
329
320
330
const handlePrevSample = useCallback ( async ( ) => {
321
331
if ( sampleIndex === 0 ) {
@@ -344,6 +354,26 @@ const AnnotationRightCorner = ({ isLastSample, isFirstSample, noSave }: Annotati
344
354
500 ,
345
355
) ;
346
356
357
+ useHotkeys (
358
+ 'ctrl+space, meta+space' ,
359
+ ( ) => {
360
+ if ( noSave ) {
361
+ return ;
362
+ }
363
+
364
+ if ( currentSample . state === SampleState . SKIPPED ) {
365
+ handleCancelSkipSample ( ) ;
366
+ } else {
367
+ handleSkipSample ( ) ;
368
+ }
369
+ } ,
370
+ {
371
+ keyup : true ,
372
+ keydown : false ,
373
+ } ,
374
+ [ handleSkipSample , handleCancelSkipSample , currentSample ] ,
375
+ ) ;
376
+
347
377
useEffect ( ( ) => {
348
378
document . addEventListener ( 'keydown' , onKeyDown ) ;
349
379
0 commit comments