1
1
import { useStore } from '@nanostores/react' ;
2
2
import { useAppSelector , useAppStore } from 'app/store/storeHooks' ;
3
3
import { getOutputImageName } from 'features/controlLayers/components/SimpleSession/shared' ;
4
+ import { loadImage } from 'features/controlLayers/konva/util' ;
4
5
import { selectStagingAreaAutoSwitch } from 'features/controlLayers/store/canvasSettingsSlice' ;
5
6
import {
6
7
buildSelectCanvasQueueItems ,
@@ -99,7 +100,6 @@ type CanvasSessionContextValue = {
99
100
selectPrev : ( ) => void ;
100
101
selectFirst : ( ) => void ;
101
102
selectLast : ( ) => void ;
102
- onImageLoad : ( itemId : number ) => void ;
103
103
discard : ( itemId : number ) => void ;
104
104
discardAll : ( ) => void ;
105
105
} ;
@@ -127,23 +127,12 @@ export const CanvasSessionContextProvider = memo(({ children }: PropsWithChildre
127
127
*/
128
128
const $lastCompletedItemId = useState ( ( ) => atom < number | null > ( null ) ) [ 0 ] ;
129
129
130
- /**
131
- * Track the last started item. Used to implement autoswitch.
132
- */
133
- const $lastStartedItemId = useState ( ( ) => atom < number | null > ( null ) ) [ 0 ] ;
134
-
135
130
/**
136
131
* Manually-synced atom containing queue items for the current session. This is populated from the RTK Query cache
137
132
* and kept in sync with it via a redux subscription.
138
133
*/
139
134
const $items = useState ( ( ) => atom < S [ 'SessionQueueItem' ] [ ] > ( [ ] ) ) [ 0 ] ;
140
135
141
- /**
142
- * An internal flag used to work around race conditions with auto-switch switching to queue items before their
143
- * output images have fully loaded.
144
- */
145
- const $lastLoadedItemId = useState ( ( ) => atom < number | null > ( null ) ) [ 0 ] ;
146
-
147
136
/**
148
137
* An ephemeral store of progress events and images for all items in the current session.
149
138
*/
@@ -282,30 +271,6 @@ export const CanvasSessionContextProvider = memo(({ children }: PropsWithChildre
282
271
$selectedItemId . set ( last . item_id ) ;
283
272
} , [ $items , $selectedItemId ] ) ;
284
273
285
- const onImageLoad = useCallback (
286
- ( itemId : number ) => {
287
- const progressData = $progressData . get ( ) ;
288
- const current = progressData [ itemId ] ;
289
- if ( current ) {
290
- const next = { ...current , imageLoaded : true } ;
291
- $progressData . setKey ( itemId , next ) ;
292
- } else {
293
- $progressData . setKey ( itemId , {
294
- ...getInitialProgressData ( itemId ) ,
295
- imageLoaded : true ,
296
- } ) ;
297
- }
298
- if (
299
- $lastCompletedItemId . get ( ) === itemId &&
300
- selectStagingAreaAutoSwitch ( store . getState ( ) ) === 'switch_on_finish'
301
- ) {
302
- $selectedItemId . set ( itemId ) ;
303
- $lastCompletedItemId . set ( null ) ;
304
- }
305
- } ,
306
- [ $lastCompletedItemId , $progressData , $selectedItemId , store ]
307
- ) ;
308
-
309
274
// Set up socket listeners
310
275
useEffect ( ( ) => {
311
276
if ( ! socket ) {
@@ -324,10 +289,19 @@ export const CanvasSessionContextProvider = memo(({ children }: PropsWithChildre
324
289
return ;
325
290
}
326
291
if ( data . status === 'completed' ) {
292
+ /**
293
+ * There is an unpleasant bit of indirection here. When an item is completed, and auto-switch is set to
294
+ * switch_on_finish, we want to load the image and switch to it. In this socket handler, we don't have
295
+ * access to the full queue item, which we need to get the output image and load it. We get the full
296
+ * queue items as part of the list query, so it's rather inefficient to fetch it again here.
297
+ *
298
+ * To reduce the number of extra network requests, we instead store this item as the last completed item.
299
+ * Then in the progress data sync effect, we process the queue item load its image.
300
+ */
327
301
$lastCompletedItemId . set ( data . item_id ) ;
328
302
}
329
- if ( data . status === 'in_progress' ) {
330
- $lastStartedItemId . set ( data . item_id ) ;
303
+ if ( data . status === 'in_progress' && selectStagingAreaAutoSwitch ( store . getState ( ) ) === 'switch_on_start' ) {
304
+ $selectedItemId . set ( data . item_id ) ;
331
305
}
332
306
} ;
333
307
@@ -338,7 +312,7 @@ export const CanvasSessionContextProvider = memo(({ children }: PropsWithChildre
338
312
socket . off ( 'invocation_progress' , onProgress ) ;
339
313
socket . off ( 'queue_item_status_changed' , onQueueItemStatusChanged ) ;
340
314
} ;
341
- } , [ $lastCompletedItemId , $lastStartedItemId , $ progressData, $selectedItemId , sessionId , socket ] ) ;
315
+ } , [ $progressData , $selectedItemId , sessionId , socket , $lastCompletedItemId , store ] ) ;
342
316
343
317
// Set up state subscriptions and effects
344
318
useEffect ( ( ) => {
@@ -357,41 +331,32 @@ export const CanvasSessionContextProvider = memo(({ children }: PropsWithChildre
357
331
} ) ;
358
332
359
333
// Handle cases that could result in a nonexistent queue item being selected.
360
- const unsubEnsureSelectedItemIdExists = effect (
361
- [ $items , $selectedItemId , $lastStartedItemId ] ,
362
- ( items , selectedItemId , lastStartedItemId ) => {
363
- if ( items . length === 0 ) {
364
- // If there are no items, cannot have a selected item.
365
- $selectedItemId . set ( null ) ;
366
- } else if ( selectedItemId === null && items . length > 0 ) {
367
- // If there is no selected item but there are items, select the first one.
368
- $selectedItemId . set ( items [ 0 ] ?. item_id ?? null ) ;
369
- return ;
370
- } else if (
371
- selectStagingAreaAutoSwitch ( store . getState ( ) ) === 'switch_on_start' &&
372
- items . findIndex ( ( { item_id } ) => item_id === lastStartedItemId ) !== - 1
373
- ) {
374
- $selectedItemId . set ( lastStartedItemId ) ;
375
- $lastStartedItemId . set ( null ) ;
376
- } else if ( selectedItemId !== null && items . findIndex ( ( { item_id } ) => item_id === selectedItemId ) === - 1 ) {
377
- // If an item is selected and it is not in the list of items, un-set it. This effect will run again and we'll
378
- // the above case, selecting the first item if there are any.
379
- let prevIndex = _prevItems . findIndex ( ( { item_id } ) => item_id === selectedItemId ) ;
380
- if ( prevIndex >= items . length ) {
381
- prevIndex = items . length - 1 ;
382
- }
383
- const nextItem = items [ prevIndex ] ;
384
- $selectedItemId . set ( nextItem ?. item_id ?? null ) ;
334
+ const unsubEnsureSelectedItemIdExists = effect ( [ $items , $selectedItemId ] , ( items , selectedItemId ) => {
335
+ if ( items . length === 0 ) {
336
+ // If there are no items, cannot have a selected item.
337
+ $selectedItemId . set ( null ) ;
338
+ } else if ( selectedItemId === null && items . length > 0 ) {
339
+ // If there is no selected item but there are items, select the first one.
340
+ $selectedItemId . set ( items [ 0 ] ?. item_id ?? null ) ;
341
+ return ;
342
+ } else if ( selectedItemId !== null && items . findIndex ( ( { item_id } ) => item_id === selectedItemId ) === - 1 ) {
343
+ // If an item is selected and it is not in the list of items, un-set it. This effect will run again and we'll
344
+ // the above case, selecting the first item if there are any.
345
+ let prevIndex = _prevItems . findIndex ( ( { item_id } ) => item_id === selectedItemId ) ;
346
+ if ( prevIndex >= items . length ) {
347
+ prevIndex = items . length - 1 ;
385
348
}
349
+ const nextItem = items [ prevIndex ] ;
350
+ $selectedItemId . set ( nextItem ?. item_id ?? null ) ;
351
+ }
386
352
387
- if ( items !== _prevItems ) {
388
- _prevItems = items ;
389
- }
353
+ if ( items !== _prevItems ) {
354
+ _prevItems = items ;
390
355
}
391
- ) ;
356
+ } ) ;
392
357
393
- // Clean up the progress data when a queue item is discarded.
394
- const unsubCleanUpProgressData = $items . subscribe ( async ( items ) => {
358
+ // Sync progress data - remove canceled/failed items, update progress data with new images, and load images
359
+ const unsubSyncProgressData = $items . subscribe ( async ( items ) => {
395
360
const progressData = $progressData . get ( ) ;
396
361
397
362
const toDelete : number [ ] = [ ] ;
@@ -418,36 +383,34 @@ export const CanvasSessionContextProvider = memo(({ children }: PropsWithChildre
418
383
for ( const item of items ) {
419
384
const datum = progressData [ item . item_id ] ;
420
385
421
- if ( datum ) {
422
- if ( datum . imageDTO ) {
423
- continue ;
424
- }
425
- const outputImageName = getOutputImageName ( item ) ;
426
- if ( ! outputImageName ) {
427
- continue ;
428
- }
429
- const imageDTO = await getImageDTOSafe ( outputImageName ) ;
430
- if ( ! imageDTO ) {
431
- continue ;
432
- }
433
- toUpdate . push ( {
434
- ...datum ,
435
- imageDTO,
436
- } ) ;
437
- } else {
438
- const outputImageName = getOutputImageName ( item ) ;
439
- if ( ! outputImageName ) {
440
- continue ;
441
- }
442
- const imageDTO = await getImageDTOSafe ( outputImageName ) ;
443
- if ( ! imageDTO ) {
444
- continue ;
445
- }
446
- toUpdate . push ( {
447
- ...getInitialProgressData ( item . item_id ) ,
448
- imageDTO,
386
+ if ( datum ?. imageDTO ) {
387
+ continue ;
388
+ }
389
+ const outputImageName = getOutputImageName ( item ) ;
390
+ if ( ! outputImageName ) {
391
+ continue ;
392
+ }
393
+ const imageDTO = await getImageDTOSafe ( outputImageName ) ;
394
+ if ( ! imageDTO ) {
395
+ continue ;
396
+ }
397
+
398
+ // This is the load logic mentioned in the comment in the QueueItemStatusChangedEvent handler above.
399
+ if (
400
+ $lastCompletedItemId . get ( ) === item . item_id &&
401
+ selectStagingAreaAutoSwitch ( store . getState ( ) ) === 'switch_on_finish'
402
+ ) {
403
+ loadImage ( imageDTO . image_url , true ) . then ( ( ) => {
404
+ $selectedItemId . set ( item . item_id ) ;
405
+ $lastCompletedItemId . set ( null ) ;
449
406
} ) ;
450
407
}
408
+
409
+ toUpdate . push ( {
410
+ ...getInitialProgressData ( item . item_id ) ,
411
+ ...datum ,
412
+ imageDTO,
413
+ } ) ;
451
414
}
452
415
453
416
for ( const itemId of toDelete ) {
@@ -459,24 +422,6 @@ export const CanvasSessionContextProvider = memo(({ children }: PropsWithChildre
459
422
}
460
423
} ) ;
461
424
462
- // We only want to auto-switch to completed queue items once their images have fully loaded to prevent flashes
463
- // of fallback content and/or progress images. The only surefire way to determine when images have fully loaded
464
- // is via the image elements' `onLoad` callback. Images set `$lastLoadedItemId` to their queue item ID in their
465
- // `onLoad` handler, and we listen for that here. If auto-switch is enabled, we then switch the to the item.
466
- //
467
- // TODO: This isn't perfect... we set $lastLoadedItemId in the mini preview component, but the full view
468
- // component still needs to retrieve the image from the browser cache... can result in a flash of the progress
469
- // image...
470
- const unsubHandleAutoSwitch = $lastLoadedItemId . listen ( ( lastLoadedItemId ) => {
471
- if ( lastLoadedItemId === null ) {
472
- return ;
473
- }
474
- if ( selectStagingAreaAutoSwitch ( store . getState ( ) ) === 'switch_on_finish' ) {
475
- $selectedItemId . set ( lastLoadedItemId ) ;
476
- }
477
- $lastLoadedItemId . set ( null ) ;
478
- } ) ;
479
-
480
425
// Create an RTK Query subscription. Without this, the query cache selector will never return anything bc RTK
481
426
// doesn't know we care about it.
482
427
const { unsubscribe : unsubQueueItemsQuery } = store . dispatch (
@@ -485,25 +430,15 @@ export const CanvasSessionContextProvider = memo(({ children }: PropsWithChildre
485
430
486
431
// Clean up all subscriptions and top-level (i.e. non-computed/derived state)
487
432
return ( ) => {
488
- unsubHandleAutoSwitch ( ) ;
489
433
unsubQueueItemsQuery ( ) ;
490
434
unsubReduxSyncToItemsAtom ( ) ;
491
435
unsubEnsureSelectedItemIdExists ( ) ;
492
- unsubCleanUpProgressData ( ) ;
436
+ unsubSyncProgressData ( ) ;
493
437
$items . set ( [ ] ) ;
494
438
$progressData . set ( { } ) ;
495
439
$selectedItemId . set ( null ) ;
496
440
} ;
497
- } , [
498
- $items ,
499
- $lastLoadedItemId ,
500
- $lastStartedItemId ,
501
- $progressData ,
502
- $selectedItemId ,
503
- selectQueueItems ,
504
- sessionId ,
505
- store ,
506
- ] ) ;
441
+ } , [ $items , $progressData , $selectedItemId , selectQueueItems , sessionId , store , $lastCompletedItemId ] ) ;
507
442
508
443
const value = useMemo < CanvasSessionContextValue > (
509
444
( ) => ( {
@@ -520,7 +455,6 @@ export const CanvasSessionContextProvider = memo(({ children }: PropsWithChildre
520
455
selectPrev,
521
456
selectFirst,
522
457
selectLast,
523
- onImageLoad,
524
458
discard,
525
459
discardAll,
526
460
} ) ,
@@ -538,7 +472,6 @@ export const CanvasSessionContextProvider = memo(({ children }: PropsWithChildre
538
472
selectPrev ,
539
473
selectFirst ,
540
474
selectLast ,
541
- onImageLoad ,
542
475
discard ,
543
476
discardAll ,
544
477
]
0 commit comments