📔 JsDoc for index.ts
This commit is contained in:
@@ -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">
|
||||||
|
|||||||
@@ -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']>;
|
||||||
|
|||||||
@@ -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,
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
Reference in New Issue
Block a user