···55import { Header } from "~/components/Header";
66import Login from "~/components/Login";
77import {
88+ aturiListServiceAtom,
89 constellationURLAtom,
1010+ defaultaturilistservice,
911 defaultconstellationURL,
1012 defaulthue,
1113 defaultImgCDN,
···5153 title={"Slingshot"}
5254 description={"Customize the Slingshot instance to be used by Red Dwarf"}
5355 init={defaultslingshotURL}
5656+ />
5757+ <TextInputSetting
5858+ atom={aturiListServiceAtom}
5959+ title={"AtUriListService"}
6060+ description={"Customize the AtUriListService instance to be used by Red Dwarf"}
6161+ init={defaultaturilistservice}
5462 />
5563 <TextInputSetting
5664 atom={imgCDNAtom}
···565565 });
566566}
567567568568+export const ATURI_PAGE_LIMIT = 100;
569569+570570+export interface AturiDirectoryAturisItem {
571571+ uri: string;
572572+ cid: string;
573573+ rkey: string;
574574+}
575575+576576+export type AturiDirectoryAturis = AturiDirectoryAturisItem[];
577577+578578+export function constructAturiListQuery(aturilistservice: string, did: string, collection: string, reverse?: boolean) {
579579+ return queryOptions({
580580+ // A unique key for this query, including all parameters that affect the data.
581581+ queryKey: ["aturiList", did, collection, { reverse }],
582582+583583+ // The function that fetches the data.
584584+ queryFn: async ({ pageParam }: QueryFunctionContext) => {
585585+ const cursor = pageParam as string | undefined;
586586+587587+ // Use URLSearchParams for safe and clean URL construction.
588588+ const params = new URLSearchParams({
589589+ did,
590590+ collection,
591591+ });
592592+593593+ if (cursor) {
594594+ params.set("cursor", cursor);
595595+ }
596596+597597+ // Add the reverse parameter if it's true
598598+ if (reverse) {
599599+ params.set("reverse", "true");
600600+ }
601601+602602+ const url = `https://${aturilistservice}/aturis?${params.toString()}`;
603603+604604+ const res = await fetch(url);
605605+ if (!res.ok) {
606606+ // You can add more specific error handling here
607607+ throw new Error(`Failed to fetch AT-URI list for ${did}`);
608608+ }
609609+610610+ return res.json() as Promise<AturiDirectoryAturis>;
611611+ },
612612+ });
613613+}
614614+615615+export function useInfiniteQueryAturiList({aturilistservice, did, collection, reverse}:{aturilistservice: string, did: string | undefined, collection: string | undefined, reverse?: boolean}) {
616616+ // We only enable the query if both `did` and `collection` are provided.
617617+ const isEnabled = !!did && !!collection;
618618+619619+ const { queryKey, queryFn } = constructAturiListQuery(aturilistservice, did!, collection!, reverse);
620620+621621+ return useInfiniteQuery({
622622+ queryKey,
623623+ queryFn,
624624+ initialPageParam: undefined as never, // ???? what is this shit
625625+626626+ // @ts-expect-error i wouldve used as null | undefined, anyways
627627+ getNextPageParam: (lastPage: AturiDirectoryAturis) => {
628628+ // If the last page returned no records, we're at the end.
629629+ if (!lastPage || lastPage.length === 0) {
630630+ return undefined;
631631+ }
632632+633633+ // If the number of records is less than our page limit, it must be the last page.
634634+ if (lastPage.length < ATURI_PAGE_LIMIT) {
635635+ return undefined;
636636+ }
637637+638638+ // The cursor for the next page is the `rkey` of the last item we received.
639639+ const lastItem = lastPage[lastPage.length - 1];
640640+ return lastItem.rkey;
641641+ },
642642+643643+ enabled: isEnabled,
644644+ });
645645+}
646646+647647+568648type FeedSkeletonPage = ATPAPI.AppBskyFeedGetFeedSkeleton.OutputSchema;
569649570650export function constructInfiniteFeedSkeletonQuery(options: {