import type { LysandClient, Output } from "@lysand-org/client"; import type { Notification, Status } from "@lysand-org/client/types"; import { useIntervalFn } from "@vueuse/core"; export interface TimelineOptions { fetchFunction: ( client: LysandClient, options: object, ) => Promise>; updateInterval?: number; limit?: number; } export function useTimeline( client: LysandClient, options: TimelineOptions, ) { const items = ref([]) as Ref; const isLoading = ref(false); const hasReachedEnd = ref(false); const error = ref(null); const nextMaxId = ref(undefined); const prevMinId = ref(undefined); const fetchItems = async (direction: "next" | "prev") => { if (isLoading.value || (direction === "next" && hasReachedEnd.value)) { return; } isLoading.value = true; error.value = null; try { const response = await options.fetchFunction(client, { limit: options.limit || 20, max_id: direction === "next" ? nextMaxId.value : undefined, min_id: direction === "prev" ? prevMinId.value : undefined, }); const newItems = response.data.filter( (item: T) => !items.value.some((existing) => existing.id === item.id), ); if (direction === "next") { items.value.push(...newItems); if (newItems.length < (options.limit || 20)) { hasReachedEnd.value = true; } if (newItems.length > 0) { nextMaxId.value = newItems[newItems.length - 1]?.id; } } else { items.value.unshift(...newItems); if (newItems.length > 0) { prevMinId.value = newItems[0]?.id; } } } catch (e) { error.value = e instanceof Error ? e : new Error("An error occurred"); } finally { isLoading.value = false; } }; const loadNext = () => fetchItems("next"); const loadPrev = () => fetchItems("prev"); const addItem = (newItem: T) => { items.value.unshift(newItem); }; const removeItem = (id: string) => { const index = items.value.findIndex((item) => item.id === id); if (index !== -1) { items.value.splice(index, 1); } }; const updateItem = (updatedItem: T) => { const index = items.value.findIndex( (item) => item.id === updatedItem.id, ); if (index !== -1) { items.value[index] = updatedItem; } }; // Set up periodic updates const { pause, resume } = useIntervalFn(() => { loadPrev(); }, options.updateInterval || 30000); onMounted(() => { loadNext(); resume(); }); onUnmounted(() => { pause(); }); return { items, isLoading, hasReachedEnd, error, loadNext, loadPrev, addItem, removeItem, updateItem, }; }