@adventurelabs/scout-core 1.4.17 → 1.4.19
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.
- package/dist/helpers/artifacts.d.ts +11 -2
- package/dist/helpers/artifacts.js +33 -6
- package/dist/helpers/bounding_boxes.d.ts +2 -2
- package/dist/helpers/bounding_boxes.js +3 -5
- package/dist/helpers/events.d.ts +2 -1
- package/dist/helpers/events.js +24 -0
- package/dist/helpers/tags.js +35 -16
- package/dist/hooks/useInfiniteQuery.d.ts +3 -1
- package/dist/hooks/useInfiniteQuery.js +122 -1
- package/dist/providers/ScoutRefreshProvider.d.ts +326 -52
- package/dist/store/api.d.ts +387 -1
- package/dist/store/api.js +168 -1
- package/dist/store/configureStore.d.ts +48 -0
- package/dist/types/db.d.ts +14 -0
- package/dist/types/supabase.d.ts +337 -52
- package/package.json +1 -1
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import { IArtifact, IArtifactWithMediaUrl, ArtifactInsert, ArtifactUpdate } from "../types/db";
|
|
2
2
|
import { IWebResponseCompatible } from "../types/requests";
|
|
3
3
|
import { SupabaseClient } from "@supabase/supabase-js";
|
|
4
|
-
export declare function
|
|
4
|
+
export declare function server_get_artifact_by_id(id: number, client?: SupabaseClient): Promise<IWebResponseCompatible<IArtifactWithMediaUrl | null>>;
|
|
5
|
+
export declare function server_get_artifacts_by_herd(herd_id: number, limit?: number, offset?: number, options?: {
|
|
6
|
+
start_timestamp?: string;
|
|
7
|
+
end_timestamp?: string;
|
|
8
|
+
client?: SupabaseClient;
|
|
9
|
+
}): Promise<IWebResponseCompatible<IArtifactWithMediaUrl[]>>;
|
|
5
10
|
export declare function server_get_artifacts_by_device_id(device_id: number, limit?: number, offset?: number, client?: SupabaseClient): Promise<IWebResponseCompatible<IArtifactWithMediaUrl[]>>;
|
|
6
11
|
export declare function server_get_total_artifacts_by_herd(herd_id: number): Promise<IWebResponseCompatible<number>>;
|
|
7
|
-
export declare function server_get_artifacts_by_device_ids_batch(device_ids: number[], limit_per_device?: number,
|
|
12
|
+
export declare function server_get_artifacts_by_device_ids_batch(device_ids: number[], limit_per_device?: number, options?: {
|
|
13
|
+
start_timestamp?: string;
|
|
14
|
+
end_timestamp?: string;
|
|
15
|
+
client?: SupabaseClient;
|
|
16
|
+
}): Promise<{
|
|
8
17
|
[device_id: number]: IArtifactWithMediaUrl[];
|
|
9
18
|
}>;
|
|
10
19
|
export declare function server_insert_artifact(artifact: ArtifactInsert | ArtifactInsert[], client?: SupabaseClient): Promise<IWebResponseCompatible<IArtifact[]>>;
|
|
@@ -1,13 +1,38 @@
|
|
|
1
1
|
"use server";
|
|
2
2
|
import { newServerClient } from "../supabase/server";
|
|
3
3
|
import { IWebResponse } from "../types/requests";
|
|
4
|
-
import { generateSignedUrlsBatch } from "./storage";
|
|
5
|
-
export async function
|
|
4
|
+
import { generateSignedUrlsBatch, generateSignedUrl } from "./storage";
|
|
5
|
+
export async function server_get_artifact_by_id(id, client) {
|
|
6
6
|
const supabase = client || (await newServerClient());
|
|
7
|
+
const { data, error } = await supabase
|
|
8
|
+
.from("artifacts")
|
|
9
|
+
.select("*")
|
|
10
|
+
.eq("id", id)
|
|
11
|
+
.single();
|
|
12
|
+
if (error) {
|
|
13
|
+
return IWebResponse.error(error.message).to_compatible();
|
|
14
|
+
}
|
|
15
|
+
if (!data) {
|
|
16
|
+
return IWebResponse.success(null).to_compatible();
|
|
17
|
+
}
|
|
18
|
+
const artifact = data;
|
|
19
|
+
let media_url = null;
|
|
20
|
+
if (artifact.file_path) {
|
|
21
|
+
media_url = await generateSignedUrl(artifact.file_path, undefined, client);
|
|
22
|
+
}
|
|
23
|
+
return IWebResponse.success({
|
|
24
|
+
...artifact,
|
|
25
|
+
media_url,
|
|
26
|
+
}).to_compatible();
|
|
27
|
+
}
|
|
28
|
+
export async function server_get_artifacts_by_herd(herd_id, limit = 50, offset = 0, options) {
|
|
29
|
+
const supabase = options?.client || (await newServerClient());
|
|
7
30
|
const { data, error } = await supabase.rpc("get_artifacts_for_herd", {
|
|
8
31
|
herd_id_caller: herd_id,
|
|
9
32
|
limit_caller: limit,
|
|
10
33
|
offset_caller: offset,
|
|
34
|
+
start_timestamp: options?.start_timestamp,
|
|
35
|
+
end_timestamp: options?.end_timestamp,
|
|
11
36
|
});
|
|
12
37
|
if (error) {
|
|
13
38
|
return IWebResponse.error(error.message).to_compatible();
|
|
@@ -22,7 +47,7 @@ export async function server_get_artifacts_by_herd(herd_id, limit = 50, offset =
|
|
|
22
47
|
if (uniqueFilePaths.length === 0) {
|
|
23
48
|
return IWebResponse.success(data).to_compatible();
|
|
24
49
|
}
|
|
25
|
-
const signedUrls = await generateSignedUrlsBatch(uniqueFilePaths, undefined, client);
|
|
50
|
+
const signedUrls = await generateSignedUrlsBatch(uniqueFilePaths, undefined, options?.client);
|
|
26
51
|
const urlMap = new Map();
|
|
27
52
|
uniqueFilePaths.forEach((path, index) => {
|
|
28
53
|
urlMap.set(path, signedUrls[index]);
|
|
@@ -78,14 +103,16 @@ export async function server_get_total_artifacts_by_herd(herd_id) {
|
|
|
78
103
|
}
|
|
79
104
|
return IWebResponse.success(data || 0).to_compatible();
|
|
80
105
|
}
|
|
81
|
-
export async function server_get_artifacts_by_device_ids_batch(device_ids, limit_per_device = 10,
|
|
82
|
-
const supabase = client || (await newServerClient());
|
|
106
|
+
export async function server_get_artifacts_by_device_ids_batch(device_ids, limit_per_device = 10, options) {
|
|
107
|
+
const supabase = options?.client || (await newServerClient());
|
|
83
108
|
if (device_ids.length === 0) {
|
|
84
109
|
return {};
|
|
85
110
|
}
|
|
86
111
|
const { data, error } = await supabase.rpc("get_artifacts_for_devices_batch", {
|
|
87
112
|
device_ids: device_ids,
|
|
88
113
|
limit_per_device: limit_per_device,
|
|
114
|
+
start_timestamp: options?.start_timestamp,
|
|
115
|
+
end_timestamp: options?.end_timestamp,
|
|
89
116
|
});
|
|
90
117
|
if (error || !data) {
|
|
91
118
|
console.warn("Error fetching artifacts batch:", error?.message);
|
|
@@ -97,7 +124,7 @@ export async function server_get_artifacts_by_device_ids_batch(device_ids, limit
|
|
|
97
124
|
.filter((path) => !!path)));
|
|
98
125
|
let artifactsWithUrls = data;
|
|
99
126
|
if (uniqueFilePaths.length > 0) {
|
|
100
|
-
const signedUrls = await generateSignedUrlsBatch(uniqueFilePaths, undefined, client);
|
|
127
|
+
const signedUrls = await generateSignedUrlsBatch(uniqueFilePaths, undefined, options?.client);
|
|
101
128
|
const urlMap = new Map();
|
|
102
129
|
uniqueFilePaths.forEach((path, index) => {
|
|
103
130
|
urlMap.set(path, signedUrls[index]);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BoundingBox, EnumSourceBoundingBox } from "../types/bounding_boxes";
|
|
2
|
-
import { Tag } from "../types/db";
|
|
3
|
-
export declare function convertManualBoundingBoxToTag(boundingBox: BoundingBox, event_id: number):
|
|
2
|
+
import { Tag, TagInsert } from "../types/db";
|
|
3
|
+
export declare function convertManualBoundingBoxToTag(boundingBox: BoundingBox, event_id: number): TagInsert;
|
|
4
4
|
export declare function convertTagToBoundingBox(tag: Tag, source: EnumSourceBoundingBox): BoundingBox;
|
|
5
5
|
export declare function formatBoundingBoxLabel(label: string): string;
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { SelectionStatus, } from "../types/bounding_boxes";
|
|
2
2
|
export function convertManualBoundingBoxToTag(boundingBox, event_id) {
|
|
3
3
|
const newClassName = boundingBox.label;
|
|
4
|
-
// try to convert if nan, make it 0
|
|
5
4
|
let newId = Number(boundingBox.id);
|
|
6
5
|
if (isNaN(newId)) {
|
|
7
6
|
newId = 0;
|
|
8
7
|
}
|
|
9
|
-
|
|
8
|
+
return {
|
|
10
9
|
id: newId,
|
|
11
10
|
x: boundingBox.xCenterPercentage,
|
|
12
11
|
y: boundingBox.yCenterPercentage,
|
|
@@ -15,11 +14,10 @@ export function convertManualBoundingBoxToTag(boundingBox, event_id) {
|
|
|
15
14
|
inserted_at: new Date().toISOString(),
|
|
16
15
|
conf: 1,
|
|
17
16
|
observation_type: "manual",
|
|
18
|
-
|
|
17
|
+
class: newClassName,
|
|
19
18
|
event_id: event_id,
|
|
20
19
|
location: null,
|
|
21
20
|
};
|
|
22
|
-
return newTag;
|
|
23
21
|
}
|
|
24
22
|
export function convertTagToBoundingBox(tag, source) {
|
|
25
23
|
const newBoundingBox = {
|
|
@@ -27,7 +25,7 @@ export function convertTagToBoundingBox(tag, source) {
|
|
|
27
25
|
yCenterPercentage: tag.y,
|
|
28
26
|
widthPercentage: tag.width,
|
|
29
27
|
heightPercentage: tag.height,
|
|
30
|
-
label: tag.
|
|
28
|
+
label: tag.class,
|
|
31
29
|
id: tag.id ? tag.id.toString() : "0",
|
|
32
30
|
left: 0,
|
|
33
31
|
top: 0,
|
package/dist/helpers/events.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { IWebResponseCompatible } from "../types/requests";
|
|
2
2
|
import { EnumSessionsVisibility } from "../types/events";
|
|
3
|
-
import { IEvent, EventInsert, EventUpdate } from "../types/db";
|
|
3
|
+
import { IEvent, IEventWithMediaUrl, EventInsert, EventUpdate } from "../types/db";
|
|
4
4
|
import { SupabaseClient } from "@supabase/supabase-js";
|
|
5
|
+
export declare function server_get_event_by_id(id: number, client?: SupabaseClient): Promise<IWebResponseCompatible<IEventWithMediaUrl | null>>;
|
|
5
6
|
export declare function server_get_total_events_by_herd(herd_id: number, sessions_visibility: EnumSessionsVisibility): Promise<IWebResponseCompatible<number>>;
|
|
6
7
|
export declare function server_insert_event(events: EventInsert | EventInsert[], client?: SupabaseClient): Promise<IWebResponseCompatible<IEvent[]>>;
|
|
7
8
|
export declare function server_update_event(events: (EventUpdate & {
|
package/dist/helpers/events.js
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
import { newServerClient } from "../supabase/server";
|
|
3
3
|
import { EnumWebResponse, IWebResponse, } from "../types/requests";
|
|
4
4
|
import { EnumSessionsVisibility } from "../types/events";
|
|
5
|
+
import { generateSignedUrl } from "./storage";
|
|
6
|
+
export async function server_get_event_by_id(id, client) {
|
|
7
|
+
const supabase = client || (await newServerClient());
|
|
8
|
+
const { data, error } = await supabase
|
|
9
|
+
.from("events")
|
|
10
|
+
.select("*")
|
|
11
|
+
.eq("id", id)
|
|
12
|
+
.single();
|
|
13
|
+
if (error) {
|
|
14
|
+
return IWebResponse.error(error.message).to_compatible();
|
|
15
|
+
}
|
|
16
|
+
if (!data) {
|
|
17
|
+
return IWebResponse.success(null).to_compatible();
|
|
18
|
+
}
|
|
19
|
+
const event = data;
|
|
20
|
+
let media_url = null;
|
|
21
|
+
if (event.file_path) {
|
|
22
|
+
media_url = await generateSignedUrl(event.file_path, undefined, client);
|
|
23
|
+
}
|
|
24
|
+
return IWebResponse.success({
|
|
25
|
+
...event,
|
|
26
|
+
media_url,
|
|
27
|
+
}).to_compatible();
|
|
28
|
+
}
|
|
5
29
|
// function to get total number of events for a herd
|
|
6
30
|
export async function server_get_total_events_by_herd(herd_id, sessions_visibility) {
|
|
7
31
|
const supabase = await newServerClient();
|
package/dist/helpers/tags.js
CHANGED
|
@@ -125,7 +125,7 @@ export async function server_update_tags(tags) {
|
|
|
125
125
|
y: updateData.y,
|
|
126
126
|
width: updateData.width,
|
|
127
127
|
height: updateData.height,
|
|
128
|
-
|
|
128
|
+
class: updateData.class,
|
|
129
129
|
conf: updateData.conf,
|
|
130
130
|
observation_type: updateData.observation_type,
|
|
131
131
|
})
|
|
@@ -402,21 +402,40 @@ export async function get_event_and_tags_by_event_id(event_id) {
|
|
|
402
402
|
timestamp_observation: data[0].timestamp_observation,
|
|
403
403
|
is_public: data[0].is_public,
|
|
404
404
|
herd_id: deviceData.herd_id,
|
|
405
|
-
tags: (data[0].tags || []).map((tag) =>
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
405
|
+
tags: (data[0].tags || []).map((tag) => {
|
|
406
|
+
const loc = tag.location;
|
|
407
|
+
const lat = loc ? extractLatitude(loc) : null;
|
|
408
|
+
const lon = loc ? extractLongitude(loc) : null;
|
|
409
|
+
return {
|
|
410
|
+
id: tag.id,
|
|
411
|
+
inserted_at: tag.inserted_at,
|
|
412
|
+
artifact_id: tag.artifact_id,
|
|
413
|
+
event_id: tag.event_id,
|
|
414
|
+
detector: tag.detector,
|
|
415
|
+
width: tag.width,
|
|
416
|
+
height: tag.height,
|
|
417
|
+
conf: tag.conf,
|
|
418
|
+
x: tag.x,
|
|
419
|
+
y: tag.y,
|
|
420
|
+
class: tag.class,
|
|
421
|
+
timestamp_observation: tag.timestamp_observation,
|
|
422
|
+
frame_index: tag.frame_index ?? null,
|
|
423
|
+
origin_location: tag.origin_location ?? null,
|
|
424
|
+
origin_pitch: tag.origin_pitch,
|
|
425
|
+
origin_heading: tag.origin_heading,
|
|
426
|
+
origin_roll: tag.origin_roll,
|
|
427
|
+
sensor_pitch: tag.sensor_pitch,
|
|
428
|
+
sensor_yaw: tag.sensor_yaw,
|
|
429
|
+
sensor_roll: tag.sensor_roll,
|
|
430
|
+
origin_height: tag.origin_height,
|
|
431
|
+
subject_location: tag.subject_location ?? null,
|
|
432
|
+
subject_height: tag.subject_height,
|
|
433
|
+
origin_latitude: lat,
|
|
434
|
+
origin_longitude: lon,
|
|
435
|
+
subject_latitude: null,
|
|
436
|
+
subject_longitude: null,
|
|
437
|
+
};
|
|
438
|
+
}),
|
|
420
439
|
earthranger_url: data[0].earthranger_url,
|
|
421
440
|
file_path: data[0].file_path,
|
|
422
441
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SupabaseClient } from "@supabase/supabase-js";
|
|
2
|
-
import { IArtifactWithMediaUrl, ISessionWithCoordinates, IEventAndTagsPrettyLocation } from "../types/db";
|
|
2
|
+
import { IArtifactWithMediaUrl, ISessionWithCoordinates, IEventAndTagsPrettyLocation, IFeedItem } from "../types/db";
|
|
3
3
|
interface UseInfiniteScrollOptions {
|
|
4
4
|
limit?: number;
|
|
5
5
|
enabled?: boolean;
|
|
@@ -20,5 +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
|
+
export declare const useInfiniteFeedByHerd: (herdId: number, options: UseInfiniteScrollOptions) => InfiniteScrollData<IFeedItem>;
|
|
24
|
+
export declare const useInfiniteFeedByDevice: (deviceId: number, options: UseInfiniteScrollOptions) => InfiniteScrollData<IFeedItem>;
|
|
23
25
|
export declare const useIntersectionObserver: (callback: () => void, options?: IntersectionObserverInit) => import("react").Dispatch<import("react").SetStateAction<Element | null>>;
|
|
24
26
|
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState, useCallback, useMemo, useEffect, useRef } from "react";
|
|
2
|
-
import { useGetSessionsInfiniteByHerdQuery, useGetSessionsInfiniteByDeviceQuery, useGetEventsInfiniteByHerdQuery, useGetEventsInfiniteByDeviceQuery, useGetArtifactsInfiniteByHerdQuery, useGetArtifactsInfiniteByDeviceQuery, } from "../store/api";
|
|
2
|
+
import { useGetSessionsInfiniteByHerdQuery, useGetSessionsInfiniteByDeviceQuery, useGetEventsInfiniteByHerdQuery, useGetEventsInfiniteByDeviceQuery, useGetArtifactsInfiniteByHerdQuery, useGetArtifactsInfiniteByDeviceQuery, useGetFeedInfiniteByHerdQuery, useGetFeedInfiniteByDeviceQuery, } from "../store/api";
|
|
3
3
|
// =====================================================
|
|
4
4
|
// SESSIONS INFINITE SCROLL HOOKS
|
|
5
5
|
// =====================================================
|
|
@@ -407,6 +407,127 @@ export const useInfiniteArtifactsByDevice = (deviceId, options) => {
|
|
|
407
407
|
error: currentQuery.error,
|
|
408
408
|
};
|
|
409
409
|
};
|
|
410
|
+
const feedCursorEq = (a, b) => {
|
|
411
|
+
if (a === null && b === null)
|
|
412
|
+
return true;
|
|
413
|
+
if (!a || !b)
|
|
414
|
+
return false;
|
|
415
|
+
return a.timestamp === b.timestamp && a.id === b.id && a.feed_type === b.feed_type;
|
|
416
|
+
};
|
|
417
|
+
export const useInfiniteFeedByHerd = (herdId, options) => {
|
|
418
|
+
const [pages, setPages] = useState([]);
|
|
419
|
+
const [currentCursor, setCurrentCursor] = useState(null);
|
|
420
|
+
const prevHerdIdRef = useRef();
|
|
421
|
+
const currentQuery = useGetFeedInfiniteByHerdQuery({
|
|
422
|
+
herdId,
|
|
423
|
+
limit: options.limit || 20,
|
|
424
|
+
cursor: currentCursor,
|
|
425
|
+
supabase: options.supabase,
|
|
426
|
+
}, { skip: !options.enabled || !herdId });
|
|
427
|
+
useEffect(() => {
|
|
428
|
+
if (prevHerdIdRef.current !== undefined &&
|
|
429
|
+
prevHerdIdRef.current !== herdId &&
|
|
430
|
+
options.enabled &&
|
|
431
|
+
herdId) {
|
|
432
|
+
setPages([]);
|
|
433
|
+
setCurrentCursor(null);
|
|
434
|
+
}
|
|
435
|
+
prevHerdIdRef.current = herdId;
|
|
436
|
+
}, [herdId, options.enabled]);
|
|
437
|
+
useEffect(() => {
|
|
438
|
+
if (currentQuery.data && !currentQuery.isLoading) {
|
|
439
|
+
setPages((prev) => {
|
|
440
|
+
const existingPage = prev.find((p) => feedCursorEq(p.cursor, currentCursor));
|
|
441
|
+
if (!existingPage) {
|
|
442
|
+
return [
|
|
443
|
+
...prev,
|
|
444
|
+
{ cursor: currentCursor, data: currentQuery.data.items },
|
|
445
|
+
];
|
|
446
|
+
}
|
|
447
|
+
return prev;
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
}, [currentQuery.data, currentQuery.isLoading, currentCursor]);
|
|
451
|
+
const loadMore = useCallback(() => {
|
|
452
|
+
if (currentQuery.data?.hasMore &&
|
|
453
|
+
currentQuery.data.nextCursor &&
|
|
454
|
+
!currentQuery.isLoading) {
|
|
455
|
+
setCurrentCursor(currentQuery.data.nextCursor);
|
|
456
|
+
}
|
|
457
|
+
}, [currentQuery.data, currentQuery.isLoading]);
|
|
458
|
+
const refetch = useCallback(() => {
|
|
459
|
+
setPages([]);
|
|
460
|
+
setCurrentCursor(null);
|
|
461
|
+
currentQuery.refetch();
|
|
462
|
+
}, [currentQuery]);
|
|
463
|
+
const allItems = useMemo(() => pages.flatMap((p) => p.data), [pages]);
|
|
464
|
+
return {
|
|
465
|
+
items: allItems,
|
|
466
|
+
isLoading: currentQuery.isLoading && pages.length === 0,
|
|
467
|
+
isLoadingMore: currentQuery.isLoading && pages.length > 0,
|
|
468
|
+
hasMore: currentQuery.data?.hasMore ?? false,
|
|
469
|
+
loadMore,
|
|
470
|
+
refetch,
|
|
471
|
+
error: currentQuery.error,
|
|
472
|
+
};
|
|
473
|
+
};
|
|
474
|
+
export const useInfiniteFeedByDevice = (deviceId, options) => {
|
|
475
|
+
const [pages, setPages] = useState([]);
|
|
476
|
+
const [currentCursor, setCurrentCursor] = useState(null);
|
|
477
|
+
const prevDeviceIdRef = useRef();
|
|
478
|
+
const currentQuery = useGetFeedInfiniteByDeviceQuery({
|
|
479
|
+
deviceId,
|
|
480
|
+
limit: options.limit || 20,
|
|
481
|
+
cursor: currentCursor,
|
|
482
|
+
supabase: options.supabase,
|
|
483
|
+
}, { skip: !options.enabled || !deviceId });
|
|
484
|
+
useEffect(() => {
|
|
485
|
+
if (prevDeviceIdRef.current !== undefined &&
|
|
486
|
+
prevDeviceIdRef.current !== deviceId &&
|
|
487
|
+
options.enabled &&
|
|
488
|
+
deviceId) {
|
|
489
|
+
setPages([]);
|
|
490
|
+
setCurrentCursor(null);
|
|
491
|
+
}
|
|
492
|
+
prevDeviceIdRef.current = deviceId;
|
|
493
|
+
}, [deviceId, options.enabled]);
|
|
494
|
+
useEffect(() => {
|
|
495
|
+
if (currentQuery.data && !currentQuery.isLoading) {
|
|
496
|
+
setPages((prev) => {
|
|
497
|
+
const existingPage = prev.find((p) => feedCursorEq(p.cursor, currentCursor));
|
|
498
|
+
if (!existingPage) {
|
|
499
|
+
return [
|
|
500
|
+
...prev,
|
|
501
|
+
{ cursor: currentCursor, data: currentQuery.data.items },
|
|
502
|
+
];
|
|
503
|
+
}
|
|
504
|
+
return prev;
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
}, [currentQuery.data, currentQuery.isLoading, currentCursor]);
|
|
508
|
+
const loadMore = useCallback(() => {
|
|
509
|
+
if (currentQuery.data?.hasMore &&
|
|
510
|
+
currentQuery.data.nextCursor &&
|
|
511
|
+
!currentQuery.isLoading) {
|
|
512
|
+
setCurrentCursor(currentQuery.data.nextCursor);
|
|
513
|
+
}
|
|
514
|
+
}, [currentQuery.data, currentQuery.isLoading]);
|
|
515
|
+
const refetch = useCallback(() => {
|
|
516
|
+
setPages([]);
|
|
517
|
+
setCurrentCursor(null);
|
|
518
|
+
currentQuery.refetch();
|
|
519
|
+
}, [currentQuery]);
|
|
520
|
+
const allItems = useMemo(() => pages.flatMap((p) => p.data), [pages]);
|
|
521
|
+
return {
|
|
522
|
+
items: allItems,
|
|
523
|
+
isLoading: currentQuery.isLoading && pages.length === 0,
|
|
524
|
+
isLoadingMore: currentQuery.isLoading && pages.length > 0,
|
|
525
|
+
hasMore: currentQuery.data?.hasMore ?? false,
|
|
526
|
+
loadMore,
|
|
527
|
+
refetch,
|
|
528
|
+
error: currentQuery.error,
|
|
529
|
+
};
|
|
530
|
+
};
|
|
410
531
|
// =====================================================
|
|
411
532
|
// INTERSECTION OBSERVER HOOK FOR AUTO-LOADING
|
|
412
533
|
// =====================================================
|