📔 JsDoc for index.ts

This commit is contained in:
2026-03-05 11:00:56 -05:00
parent 74c0d6513c
commit 5d18bd1e43
3 changed files with 53 additions and 123 deletions

View File

@@ -307,7 +307,7 @@ export const Import = (props: IProps): ReactElement => {
<td className="whitespace-nowrap px-2 py-2 text-gray-700 dark:text-slate-300"> <td className="whitespace-nowrap px-2 py-2 text-gray-700 dark:text-slate-300">
{format( {format(
new Date(jobResult.earliestTimestamp), new Date(jobResult.earliestTimestamp),
"EEEE, hh:mma, do LLLL Y", "EEEE, hh:mma, do LLLL y",
)} )}
</td> </td>
<td className="whitespace-nowrap px-2 py-2 text-gray-700 dark:text-slate-300"> <td className="whitespace-nowrap px-2 py-2 text-gray-700 dark:text-slate-300">

View File

@@ -278,23 +278,9 @@ export type InferredMetadataInput = {
export type Issue = { export type Issue = {
__typename?: 'Issue'; __typename?: 'Issue';
api_detail_url?: Maybe<Scalars['String']['output']>;
character_credits?: Maybe<Array<CharacterCredit>>;
cover_date?: Maybe<Scalars['String']['output']>;
description?: Maybe<Scalars['String']['output']>;
id: Scalars['Int']['output'];
image?: Maybe<ImageUrls>;
issue_number?: Maybe<Scalars['String']['output']>;
location_credits?: Maybe<Array<LocationCredit>>;
name?: Maybe<Scalars['String']['output']>; name?: Maybe<Scalars['String']['output']>;
number?: Maybe<Scalars['Int']['output']>; number?: Maybe<Scalars['Int']['output']>;
person_credits?: Maybe<Array<PersonCredit>>;
site_detail_url?: Maybe<Scalars['String']['output']>;
store_date?: Maybe<Scalars['String']['output']>;
story_arc_credits?: Maybe<Array<StoryArcCredit>>;
subtitle?: Maybe<Scalars['String']['output']>; subtitle?: Maybe<Scalars['String']['output']>;
team_credits?: Maybe<Array<TeamCredit>>;
volume?: Maybe<Volume>;
year?: Maybe<Scalars['String']['output']>; year?: Maybe<Scalars['String']['output']>;
}; };
@@ -534,9 +520,10 @@ export type PullListItem = {
__typename?: 'PullListItem'; __typename?: 'PullListItem';
cover?: Maybe<Scalars['String']['output']>; cover?: Maybe<Scalars['String']['output']>;
description?: Maybe<Scalars['String']['output']>; description?: Maybe<Scalars['String']['output']>;
name: Scalars['String']['output']; name?: Maybe<Scalars['String']['output']>;
potw?: Maybe<Scalars['Int']['output']>; potw?: Maybe<Scalars['Int']['output']>;
price?: Maybe<Scalars['String']['output']>; price?: Maybe<Scalars['String']['output']>;
publicationDate?: Maybe<Scalars['String']['output']>;
publisher?: Maybe<Scalars['String']['output']>; publisher?: Maybe<Scalars['String']['output']>;
pulls?: Maybe<Scalars['Int']['output']>; pulls?: Maybe<Scalars['Int']['output']>;
rating?: Maybe<Scalars['Float']['output']>; rating?: Maybe<Scalars['Float']['output']>;
@@ -1009,7 +996,7 @@ export type GetWeeklyPullListQueryVariables = Exact<{
}>; }>;
export type GetWeeklyPullListQuery = { __typename?: 'Query', getWeeklyPullList: { __typename?: 'PullListResponse', result: Array<{ __typename?: 'PullListItem', name: string, publisher?: string | null, cover?: string | null }> } }; export type GetWeeklyPullListQuery = { __typename?: 'Query', getWeeklyPullList: { __typename?: 'PullListResponse', result: Array<{ __typename?: 'PullListItem', name?: string | null, publisher?: string | null, cover?: string | null }> } };
export type GetLibraryComicsQueryVariables = Exact<{ export type GetLibraryComicsQueryVariables = Exact<{
page?: InputMaybe<Scalars['Int']['input']>; page?: InputMaybe<Scalars['Int']['input']>;

View File

@@ -1,177 +1,120 @@
import { create } from "zustand"; import { create } from "zustand";
import io, { Socket } from "socket.io-client"; import io, { Socket } from "socket.io-client";
import { SOCKET_BASE_URI } from "../constants/endpoints"; import { SOCKET_BASE_URI } from "../constants/endpoints";
import { isNil } from "lodash";
import { QueryClient } from "@tanstack/react-query"; import { QueryClient } from "@tanstack/react-query";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css"; import "react-toastify/dist/ReactToastify.css";
const queryClient = new QueryClient(); const queryClient = new QueryClient();
// Type for global state /**
* Global application state interface
*/
interface StoreState { interface StoreState {
/** Active socket.io connections by namespace */
socketInstances: Record<string, Socket>; socketInstances: Record<string, Socket>;
/**
* Get or create socket connection for namespace
* @param namespace - Socket namespace (default: "/")
* @returns Socket instance
*/
getSocket: (namespace?: string) => Socket; getSocket: (namespace?: string) => Socket;
/**
* Disconnect and remove socket instance
* @param namespace - Socket namespace to disconnect
*/
disconnectSocket: (namespace: string) => void; disconnectSocket: (namespace: string) => void;
/** ComicVine scraping state */
comicvine: { comicvine: {
scrapingStatus: string; scrapingStatus: string;
}; };
/** Import job queue state and actions */
importJobQueue: { importJobQueue: {
successfulJobCount: number; successfulJobCount: number;
failedJobCount: number; failedJobCount: number;
status: string | undefined; status: string | undefined;
mostRecentImport: string | null; mostRecentImport: string | null;
setStatus: (status: string) => void; setStatus: (status: string) => void;
setJobCount: (jobType: string, count: number) => void; setJobCount: (jobType: string, count: number) => void;
setMostRecentImport: (fileName: string) => void; setMostRecentImport: (fileName: string) => void;
}; };
} }
/**
* Zustand store for global app state and socket management
*/
export const useStore = create<StoreState>((set, get) => ({ export const useStore = create<StoreState>((set, get) => ({
socketInstances: {}, socketInstances: {},
getSocket: (namespace = "/") => { getSocket: (namespace = "/") => {
const fullNamespace = namespace === "/" ? "" : namespace; const ns = namespace === "/" ? "" : namespace;
const existing = get().socketInstances[namespace]; const existing = get().socketInstances[namespace];
if (existing?.connected) return existing;
if (existing && existing.connected) return existing;
const sessionId = localStorage.getItem("sessionId"); const sessionId = localStorage.getItem("sessionId");
const socket = io(`${SOCKET_BASE_URI}${fullNamespace}`, { const socket = io(`${SOCKET_BASE_URI}${ns}`, {
transports: ["websocket"], transports: ["websocket"],
withCredentials: true, withCredentials: true,
query: { sessionId }, query: { sessionId },
}); });
socket.on("connect", () => { socket.on("sessionInitialized", (id) => localStorage.setItem("sessionId", id));
// Socket connected successfully if (sessionId) socket.emit("call", "socket.resumeSession", { sessionId, namespace });
});
// Always listen for sessionInitialized in case backend creates a new session socket.on("RESTORE_JOB_COUNTS_AFTER_SESSION_RESTORATION", ({ completedJobCount, failedJobCount, queueStatus }) =>
socket.on("sessionInitialized", (id) => { set((s) => ({ importJobQueue: { ...s.importJobQueue, successfulJobCount: completedJobCount, failedJobCount, status: queueStatus } }))
localStorage.setItem("sessionId", id); );
});
if (sessionId) { socket.on("LS_COVER_EXTRACTED", ({ completedJobCount, importResult }) =>
socket.emit("call", "socket.resumeSession", { sessionId, namespace }); set((s) => ({ importJobQueue: { ...s.importJobQueue, successfulJobCount: completedJobCount, mostRecentImport: importResult.data.rawFileDetails.name } }))
} );
socket.on("RESTORE_JOB_COUNTS_AFTER_SESSION_RESTORATION", (data) => { socket.on("LS_COVER_EXTRACTION_FAILED", ({ failedJobCount }) =>
const { completedJobCount, failedJobCount, queueStatus } = data; set((s) => ({ importJobQueue: { ...s.importJobQueue, failedJobCount } }))
set((state) => ({ );
importJobQueue: {
...state.importJobQueue,
successfulJobCount: completedJobCount,
failedJobCount,
status: queueStatus,
},
}));
});
socket.on("LS_COVER_EXTRACTED", ({ completedJobCount, importResult }) => {
set((state) => ({
importJobQueue: {
...state.importJobQueue,
successfulJobCount: completedJobCount,
mostRecentImport: importResult.data.rawFileDetails.name,
},
}));
});
socket.on("LS_COVER_EXTRACTION_FAILED", ({ failedJobCount }) => {
set((state) => ({
importJobQueue: {
...state.importJobQueue,
failedJobCount,
},
}));
});
socket.on("LS_IMPORT_QUEUE_DRAINED", () => { socket.on("LS_IMPORT_QUEUE_DRAINED", () => {
set((state) => ({ set((s) => ({ importJobQueue: { ...s.importJobQueue, status: "drained" } }));
importJobQueue: {
...state.importJobQueue,
status: "drained",
},
}));
// Delay query invalidation and sessionId removal to ensure backend has persisted data
setTimeout(() => { setTimeout(() => {
queryClient.invalidateQueries({ queryKey: ["allImportJobResults"] }); queryClient.invalidateQueries({ queryKey: ["allImportJobResults"] });
localStorage.removeItem("sessionId"); localStorage.removeItem("sessionId");
}, 500); }, 500);
}); });
socket.on("CV_SCRAPING_STATUS", (data) => { socket.on("CV_SCRAPING_STATUS", ({ message }) =>
set((state) => ({ set((s) => ({ comicvine: { ...s.comicvine, scrapingStatus: message } }))
comicvine: { );
...state.comicvine,
scrapingStatus: data.message,
},
}));
});
socket.on("searchResultsAvailable", (data) => { socket.on("searchResultsAvailable", ({ query }) =>
toast(`Results found for query: ${JSON.stringify(data.query, null, 2)}`); toast(`Results found for query: ${JSON.stringify(query, null, 2)}`)
}); );
set((state) => ({
socketInstances: {
...state.socketInstances,
[namespace]: socket,
},
}));
set((s) => ({ socketInstances: { ...s.socketInstances, [namespace]: socket } }));
return socket; return socket;
}, },
disconnectSocket: (namespace: string) => { disconnectSocket: (namespace) => {
const socket = get().socketInstances[namespace]; const socket = get().socketInstances[namespace];
if (socket) { if (socket) {
socket.disconnect(); socket.disconnect();
set((state) => { set((s) => {
const { [namespace]: _, ...rest } = state.socketInstances; const { [namespace]: _, ...rest } = s.socketInstances;
return { socketInstances: rest }; return { socketInstances: rest };
}); });
} }
}, },
comicvine: { comicvine: { scrapingStatus: "" },
scrapingStatus: "",
},
importJobQueue: { importJobQueue: {
successfulJobCount: 0, successfulJobCount: 0,
failedJobCount: 0, failedJobCount: 0,
status: undefined, status: undefined,
mostRecentImport: null, mostRecentImport: null,
setStatus: (status) => set((s) => ({ importJobQueue: { ...s.importJobQueue, status } })),
setStatus: (status: string) => setJobCount: (jobType, count) => set((s) => ({
set((state) => ({ importJobQueue: { ...s.importJobQueue, [jobType === "successful" ? "successfulJobCount" : "failedJobCount"]: count }
importJobQueue: { })),
...state.importJobQueue, setMostRecentImport: (fileName) => set((s) => ({ importJobQueue: { ...s.importJobQueue, mostRecentImport: fileName } })),
status,
},
})),
setJobCount: (jobType: string, count: number) =>
set((state) => ({
importJobQueue: {
...state.importJobQueue,
...(jobType === "successful"
? { successfulJobCount: count }
: { failedJobCount: count }),
},
})),
setMostRecentImport: (fileName: string) =>
set((state) => ({
importJobQueue: {
...state.importJobQueue,
mostRecentImport: fileName,
},
})),
}, },
})); }));