@adventurelabs/scout-core 1.4.31 → 1.4.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -20,6 +20,7 @@ export declare const useInfiniteEventsByHerd: (herdId: number, options: UseInfin
|
|
|
20
20
|
export declare const useInfiniteEventsByDevice: (deviceId: number, options: UseInfiniteScrollOptions) => InfiniteScrollData<IEventAndTagsPrettyLocation>;
|
|
21
21
|
export declare const useInfiniteArtifactsByHerd: (herdId: number, options: UseInfiniteScrollOptions) => InfiniteScrollData<IArtifactWithMediaUrl>;
|
|
22
22
|
export declare const useInfiniteArtifactsByDevice: (deviceId: number, options: UseInfiniteScrollOptions) => InfiniteScrollData<IArtifactWithMediaUrl>;
|
|
23
|
+
/** useInfiniteFeedByHerd: logic matches useInfiniteFeedByHerdDummy verbatim; only the fetch is via API (RTK Query) instead of supabase.rpc. */
|
|
23
24
|
export declare const useInfiniteFeedByHerd: (herdId: number, options: UseInfiniteScrollOptions) => InfiniteScrollData<IFeedItem>;
|
|
24
25
|
export declare const useInfiniteFeedByDevice: (deviceId: number, options: UseInfiniteScrollOptions) => InfiniteScrollData<IFeedItem>;
|
|
25
26
|
export declare const useIntersectionObserver: (callback: () => void, options?: IntersectionObserverInit) => import("react").Dispatch<import("react").SetStateAction<Element | null>>;
|
|
@@ -408,96 +408,143 @@ export const useInfiniteArtifactsByDevice = (deviceId, options) => {
|
|
|
408
408
|
};
|
|
409
409
|
};
|
|
410
410
|
const feedCursorEq = (a, b) => {
|
|
411
|
-
if (a ===
|
|
411
|
+
if (a === b)
|
|
412
412
|
return true;
|
|
413
|
-
if (
|
|
413
|
+
if (a == null || b == null)
|
|
414
414
|
return false;
|
|
415
|
-
return a.timestamp === b.timestamp &&
|
|
415
|
+
return (a.timestamp === b.timestamp &&
|
|
416
|
+
a.id === b.id &&
|
|
417
|
+
a.feed_type === b.feed_type);
|
|
416
418
|
};
|
|
419
|
+
/** useInfiniteFeedByHerd: logic matches useInfiniteFeedByHerdDummy verbatim; only the fetch is via API (RTK Query) instead of supabase.rpc. */
|
|
417
420
|
export const useInfiniteFeedByHerd = (herdId, options) => {
|
|
421
|
+
const limit = options.limit ?? 20;
|
|
422
|
+
const enabled = !!(options.enabled && herdId);
|
|
418
423
|
const [pages, setPages] = useState([]);
|
|
419
424
|
const [currentCursor, setCurrentCursor] = useState(null);
|
|
420
|
-
|
|
421
|
-
const [lastResult, setLastResult] = useState(null);
|
|
425
|
+
const [currentResult, setCurrentResult] = useState(null);
|
|
422
426
|
const prevHerdIdRef = useRef(undefined);
|
|
423
427
|
const lastAddedCursorRef = useRef(undefined);
|
|
424
428
|
const pagesLengthRef = useRef(0);
|
|
429
|
+
/** When true, pass null to the query so we don't request (newHerdId, oldCursor) before state commits. */
|
|
430
|
+
const forceNullCursorRef = useRef(false);
|
|
431
|
+
const cursorForQuery = forceNullCursorRef.current ? null : currentCursor;
|
|
425
432
|
const currentQuery = useGetFeedInfiniteByHerdQuery({
|
|
426
433
|
herdId,
|
|
427
|
-
limit
|
|
428
|
-
cursor:
|
|
434
|
+
limit,
|
|
435
|
+
cursor: cursorForQuery,
|
|
429
436
|
supabase: options.supabase,
|
|
430
|
-
}, { skip: !
|
|
437
|
+
}, { skip: !enabled });
|
|
438
|
+
const isLoading = currentQuery.isLoading;
|
|
431
439
|
useEffect(() => {
|
|
432
440
|
pagesLengthRef.current = pages.length;
|
|
433
441
|
}, [pages.length]);
|
|
434
|
-
//
|
|
442
|
+
// Reset when herd changes (match dummy: prev !== herdId && enabled && herdId)
|
|
435
443
|
useEffect(() => {
|
|
436
444
|
if (prevHerdIdRef.current !== undefined &&
|
|
437
|
-
prevHerdIdRef.current !== herdId
|
|
445
|
+
prevHerdIdRef.current !== herdId &&
|
|
446
|
+
enabled &&
|
|
447
|
+
herdId) {
|
|
448
|
+
forceNullCursorRef.current = true;
|
|
438
449
|
setPages([]);
|
|
439
450
|
setCurrentCursor(null);
|
|
451
|
+
setCurrentResult(null);
|
|
440
452
|
lastAddedCursorRef.current = undefined;
|
|
441
|
-
setLastResult(null);
|
|
442
453
|
}
|
|
443
454
|
prevHerdIdRef.current = herdId;
|
|
444
|
-
}, [herdId]);
|
|
445
|
-
// When
|
|
455
|
+
}, [herdId, enabled]);
|
|
456
|
+
// When cursor changes, clear ref so we merge the new response
|
|
446
457
|
useEffect(() => {
|
|
447
458
|
lastAddedCursorRef.current = undefined;
|
|
448
459
|
}, [currentCursor]);
|
|
460
|
+
// Merge when we have data (mirror dummy's .then() logic; fetch is done by RTK Query)
|
|
449
461
|
useEffect(() => {
|
|
450
462
|
if (!currentQuery.data || currentQuery.isLoading)
|
|
451
463
|
return;
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
feedCursorEq(lastAddedCursorRef.current ?? null, currentCursor))
|
|
455
|
-
return;
|
|
456
|
-
const items = Array.isArray(currentQuery.data?.items)
|
|
464
|
+
const cursor = cursorForQuery;
|
|
465
|
+
const items = Array.isArray(currentQuery.data.items)
|
|
457
466
|
? currentQuery.data.items
|
|
458
467
|
: [];
|
|
459
|
-
const
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
468
|
+
const nextCursor = currentQuery.data.nextCursor ?? null;
|
|
469
|
+
// Derive hasMore from items we received (like dummy), so we don't get stuck when API
|
|
470
|
+
// returns hasMore: false e.g. due to RPC/PostgREST returning fewer rows than limit.
|
|
471
|
+
const hasMore = (items.length >= limit || (items.length > 0 && items.length < limit)) &&
|
|
472
|
+
nextCursor != null;
|
|
473
|
+
// After herd switch we force null cursor; once we've merged that first page, allow normal cursor again
|
|
474
|
+
if (cursor === null) {
|
|
475
|
+
forceNullCursorRef.current = false;
|
|
476
|
+
}
|
|
477
|
+
// Only update currentResult for successful response (match dummy)
|
|
478
|
+
if (items.length === 0 && cursor === null) {
|
|
479
|
+
// Leave currentResult unchanged on spurious empty first page
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
setCurrentResult({
|
|
483
|
+
hasMore,
|
|
484
|
+
nextCursor,
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
if (items.length === 0)
|
|
488
|
+
return;
|
|
489
|
+
// Skip merge exactly like dummy: full page already added for this cursor and we have pages
|
|
490
|
+
if (items.length >= limit &&
|
|
491
|
+
feedCursorEq(lastAddedCursorRef.current ?? null, cursor) &&
|
|
492
|
+
pagesLengthRef.current > 0) {
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
465
495
|
setPages((prev) => {
|
|
466
|
-
const existingPage = prev.find((p) => feedCursorEq(p.cursor,
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
];
|
|
496
|
+
const existingPage = prev.find((p) => feedCursorEq(p.cursor, cursor));
|
|
497
|
+
const next = !existingPage
|
|
498
|
+
? [...prev, { cursor, data: items }]
|
|
499
|
+
: items.length > existingPage.data.length
|
|
500
|
+
? prev.map((p) => feedCursorEq(p.cursor, cursor) ? { cursor, data: items } : p)
|
|
501
|
+
: prev;
|
|
502
|
+
if (!existingPage && items.length >= limit) {
|
|
503
|
+
lastAddedCursorRef.current = cursor;
|
|
475
504
|
}
|
|
476
|
-
|
|
505
|
+
if (existingPage &&
|
|
506
|
+
items.length > existingPage.data.length &&
|
|
507
|
+
items.length >= limit) {
|
|
508
|
+
lastAddedCursorRef.current = cursor;
|
|
509
|
+
}
|
|
510
|
+
return next;
|
|
477
511
|
});
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
512
|
+
}, [
|
|
513
|
+
currentQuery.data,
|
|
514
|
+
currentQuery.isLoading,
|
|
515
|
+
cursorForQuery,
|
|
516
|
+
pages.length,
|
|
517
|
+
limit,
|
|
518
|
+
]);
|
|
484
519
|
const loadMore = useCallback(() => {
|
|
485
|
-
if (
|
|
486
|
-
|
|
487
|
-
!
|
|
488
|
-
setCurrentCursor(
|
|
520
|
+
if (currentResult?.hasMore &&
|
|
521
|
+
currentResult.nextCursor != null &&
|
|
522
|
+
!isLoading) {
|
|
523
|
+
setCurrentCursor(currentResult.nextCursor);
|
|
489
524
|
}
|
|
490
|
-
}, [
|
|
525
|
+
}, [currentResult, isLoading]);
|
|
491
526
|
const refetch = useCallback(() => {
|
|
527
|
+
forceNullCursorRef.current = true;
|
|
492
528
|
setPages([]);
|
|
493
529
|
setCurrentCursor(null);
|
|
530
|
+
setCurrentResult(null);
|
|
494
531
|
lastAddedCursorRef.current = undefined;
|
|
495
|
-
setLastResult(null);
|
|
496
532
|
currentQuery.refetch();
|
|
497
533
|
}, [currentQuery]);
|
|
498
534
|
const allItems = useMemo(() => {
|
|
535
|
+
const sorted = [...pages].sort((a, b) => {
|
|
536
|
+
if (feedCursorEq(a.cursor, b.cursor))
|
|
537
|
+
return 0;
|
|
538
|
+
if (a.cursor === null)
|
|
539
|
+
return -1;
|
|
540
|
+
if (b.cursor === null)
|
|
541
|
+
return 1;
|
|
542
|
+
const ta = a.cursor.timestamp ?? '';
|
|
543
|
+
const tb = b.cursor.timestamp ?? '';
|
|
544
|
+
return tb.localeCompare(ta);
|
|
545
|
+
});
|
|
499
546
|
const seen = new Set();
|
|
500
|
-
return
|
|
547
|
+
return sorted.flatMap((p) => p.data).filter((item) => {
|
|
501
548
|
const key = `${item.sort_ts ?? ''}_${item.sort_id ?? ''}_${item.feed_type ?? ''}`;
|
|
502
549
|
if (seen.has(key))
|
|
503
550
|
return false;
|
|
@@ -507,10 +554,10 @@ export const useInfiniteFeedByHerd = (herdId, options) => {
|
|
|
507
554
|
}, [pages]);
|
|
508
555
|
return {
|
|
509
556
|
items: allItems,
|
|
510
|
-
isLoading:
|
|
511
|
-
isLoadingMore:
|
|
512
|
-
hasMore:
|
|
513
|
-
(currentCursor !== null && pages.length > 0)
|
|
557
|
+
isLoading: isLoading && pages.length === 0,
|
|
558
|
+
isLoadingMore: isLoading && pages.length > 0,
|
|
559
|
+
hasMore: currentResult?.hasMore ??
|
|
560
|
+
(currentCursor !== null && pages.length > 0) ??
|
|
514
561
|
false,
|
|
515
562
|
loadMore,
|
|
516
563
|
refetch,
|
|
@@ -524,10 +571,12 @@ export const useInfiniteFeedByDevice = (deviceId, options) => {
|
|
|
524
571
|
const prevDeviceIdRef = useRef(undefined);
|
|
525
572
|
const lastAddedCursorRef = useRef(undefined);
|
|
526
573
|
const pagesLengthRef = useRef(0);
|
|
574
|
+
const forceNullCursorRef = useRef(false);
|
|
575
|
+
const cursorForQuery = forceNullCursorRef.current ? null : currentCursor;
|
|
527
576
|
const currentQuery = useGetFeedInfiniteByDeviceQuery({
|
|
528
577
|
deviceId,
|
|
529
578
|
limit: options.limit || 20,
|
|
530
|
-
cursor:
|
|
579
|
+
cursor: cursorForQuery,
|
|
531
580
|
supabase: options.supabase,
|
|
532
581
|
}, { skip: !options.enabled || !deviceId });
|
|
533
582
|
useEffect(() => {
|
|
@@ -537,6 +586,7 @@ export const useInfiniteFeedByDevice = (deviceId, options) => {
|
|
|
537
586
|
useEffect(() => {
|
|
538
587
|
if (prevDeviceIdRef.current !== undefined &&
|
|
539
588
|
prevDeviceIdRef.current !== deviceId) {
|
|
589
|
+
forceNullCursorRef.current = true;
|
|
540
590
|
setPages([]);
|
|
541
591
|
setCurrentCursor(null);
|
|
542
592
|
lastAddedCursorRef.current = undefined;
|
|
@@ -551,35 +601,39 @@ export const useInfiniteFeedByDevice = (deviceId, options) => {
|
|
|
551
601
|
useEffect(() => {
|
|
552
602
|
if (!currentQuery.data || currentQuery.isLoading)
|
|
553
603
|
return;
|
|
604
|
+
const cursor = cursorForQuery;
|
|
605
|
+
if (cursor === null) {
|
|
606
|
+
forceNullCursorRef.current = false;
|
|
607
|
+
}
|
|
554
608
|
if (pagesLengthRef.current > 0 &&
|
|
555
|
-
feedCursorEq(lastAddedCursorRef.current ?? null,
|
|
609
|
+
feedCursorEq(lastAddedCursorRef.current ?? null, cursor))
|
|
556
610
|
return;
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
611
|
+
const items = Array.isArray(currentQuery.data?.items)
|
|
612
|
+
? currentQuery.data.items
|
|
613
|
+
: [];
|
|
614
|
+
const limitForPage = options.limit || 20;
|
|
615
|
+
const nextCursor = currentQuery.data?.nextCursor ?? null;
|
|
616
|
+
const hasMore = (items.length >= limitForPage ||
|
|
617
|
+
(items.length > 0 && items.length < limitForPage)) &&
|
|
618
|
+
nextCursor != null;
|
|
619
|
+
setLastResult({ hasMore, nextCursor });
|
|
562
620
|
setPages((prev) => {
|
|
563
|
-
const existingPage = prev.find((p) => feedCursorEq(p.cursor,
|
|
621
|
+
const existingPage = prev.find((p) => feedCursorEq(p.cursor, cursor));
|
|
564
622
|
if (!existingPage) {
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
: [];
|
|
568
|
-
const limit = options.limit || 20;
|
|
569
|
-
if (items.length >= limit) {
|
|
570
|
-
lastAddedCursorRef.current = currentCursor;
|
|
623
|
+
if (items.length >= limitForPage) {
|
|
624
|
+
lastAddedCursorRef.current = cursor;
|
|
571
625
|
}
|
|
572
626
|
return [
|
|
573
627
|
...prev,
|
|
574
|
-
{ cursor
|
|
628
|
+
{ cursor, data: items },
|
|
575
629
|
];
|
|
576
630
|
}
|
|
577
631
|
return prev;
|
|
578
632
|
});
|
|
579
|
-
if (
|
|
580
|
-
setCurrentCursor(
|
|
633
|
+
if (hasMore && nextCursor != null) {
|
|
634
|
+
setCurrentCursor(nextCursor);
|
|
581
635
|
}
|
|
582
|
-
}, [currentQuery.data, currentQuery.isLoading,
|
|
636
|
+
}, [currentQuery.data, currentQuery.isLoading, cursorForQuery, pages.length, options.limit]);
|
|
583
637
|
const loadMore = useCallback(() => {
|
|
584
638
|
if (lastResult?.hasMore &&
|
|
585
639
|
lastResult.nextCursor != null &&
|
|
@@ -588,6 +642,7 @@ export const useInfiniteFeedByDevice = (deviceId, options) => {
|
|
|
588
642
|
}
|
|
589
643
|
}, [lastResult, currentQuery.isLoading]);
|
|
590
644
|
const refetch = useCallback(() => {
|
|
645
|
+
forceNullCursorRef.current = true;
|
|
591
646
|
setPages([]);
|
|
592
647
|
setCurrentCursor(null);
|
|
593
648
|
lastAddedCursorRef.current = undefined;
|