mirror of
https://github.com/versia-pub/frontend.git
synced 2025-12-06 08:28:20 +01:00
114 lines
3.2 KiB
TypeScript
114 lines
3.2 KiB
TypeScript
import type { Client, Output } from "@versia/client";
|
|
import type { Notification, Status } from "@versia/client/types";
|
|
import { useIntervalFn } from "@vueuse/core";
|
|
|
|
export interface TimelineOptions<T> {
|
|
fetchFunction: (client: Client, options: object) => Promise<Output<T[]>>;
|
|
updateInterval?: number;
|
|
limit?: number;
|
|
}
|
|
|
|
export function useTimeline<T extends Status | Notification>(
|
|
client: Client,
|
|
options: TimelineOptions<T>,
|
|
) {
|
|
const items = ref<T[]>([]) as Ref<T[]>;
|
|
const isLoading = ref(false);
|
|
const hasReachedEnd = ref(false);
|
|
const error = ref<Error | null>(null);
|
|
|
|
const nextMaxId = ref<string | undefined>(undefined);
|
|
const prevMinId = ref<string | undefined>(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,
|
|
};
|
|
}
|