🧸 Zustand and Tanstack Query (#96)
* ↪️ Removed node-sass, added sass * 🏗️ Refactoring Navbar to read from zustand store * ⬆️ Bumped deps * 🏗️ Refactored AirDC++ session status indicator * 🏗️ Refactored Import page to read from global state * 🏗 Wired up the event emit correctly * 🏗️ Added import queue related state * 🏗 Implemented setQueueAction * 🏗️ Wired up job queue control methods * 🏗️ Added null check and removed useless deps * 🏗️ Refactored the Import page * ↪️ Added cache invalidation to job statistics query * 🏗️ Refactoring the Library page * 🏗️ Fixed pagination and disabled states * ✏️ Changed page to offset To better reflect what we are doing with the pagination controls * 🏗️ Refactoring ComicDetail page and its children * 🏗️ Refactored ComicDetailContainer with useQuery * 🔧 Fixed the error check on Library page * 🏗️ Refactoring AcquisitionPanel * 🏗️ Refactoring the AirDC++ Forms * 🦃 Thanksgiving Day bug fixes * ⬆️ Bumped up Vite to 5.0 * 🔧 Refactoring AcquisitionPanel * 🏗️ Wiring up the DC++ search method * 🏗️ Refactoring AirDC++ search method * 🔎 Added some validation to ADC++ Hubs settings form * 🏗️ Fixed the ADC++ search results * 🏗️ Cleanup of the search results pane
This commit was merged in pull request #96.
This commit is contained in:
@@ -1,90 +1,198 @@
|
||||
import { create } from "zustand";
|
||||
import { isEmpty } from "lodash";
|
||||
import { isEmpty, isNil, isUndefined } from "lodash";
|
||||
import io from "socket.io-client";
|
||||
import { SOCKET_BASE_URI } from "../constants/endpoints";
|
||||
import { produce } from "immer";
|
||||
import AirDCPPSocket from "../services/DcppSearchService";
|
||||
import axios from "axios";
|
||||
|
||||
/* Broadly, this file sets up:
|
||||
* 1. The zustand-based global client state
|
||||
* 2. socket.io client
|
||||
* 3. AirDC++ websocket connection
|
||||
*/
|
||||
export const useStore = create((set, get) => ({
|
||||
// AirDC++ state
|
||||
airDCPPSocketInstance: {},
|
||||
airDCPPSocketConnected: false,
|
||||
airDCPPDisconnectionInfo: {},
|
||||
airDCPPClientConfiguration: {},
|
||||
airDCPPSocketConnectionInformation: {},
|
||||
airDCPPSessionInformation: {},
|
||||
setAirDCPPSocketConnectionStatus: () =>
|
||||
set((value) => ({
|
||||
airDCPPSocketConnected: value,
|
||||
})),
|
||||
getAirDCPPConnectionStatus: () => {
|
||||
const airDCPPSocketConnectionStatus = get().airDCPPSocketConnected;
|
||||
},
|
||||
airDCPPDownloadTick: {},
|
||||
// Socket.io state
|
||||
socketIOInstance: {},
|
||||
|
||||
// Import job queue and associated statuses
|
||||
importJobQueue: {
|
||||
successfulJobCount: 0,
|
||||
failedJobCount: 0,
|
||||
status: undefined,
|
||||
setStatus: (status: string) =>
|
||||
set(
|
||||
produce((draftState) => {
|
||||
draftState.importJobQueue.status = status;
|
||||
}),
|
||||
),
|
||||
setJobCount: (jobType: string, count: Number) => {
|
||||
switch (jobType) {
|
||||
case "successful":
|
||||
set(
|
||||
produce((draftState) => {
|
||||
draftState.importJobQueue.successfulJobCount = count;
|
||||
}),
|
||||
);
|
||||
break;
|
||||
|
||||
case "failed":
|
||||
set(
|
||||
produce((draftState) => {
|
||||
draftState.importJobQueue.failedJobCount = count;
|
||||
}),
|
||||
);
|
||||
break;
|
||||
}
|
||||
},
|
||||
mostRecentImport: null,
|
||||
setMostRecentImport: (fileName: string) => {
|
||||
set(
|
||||
produce((state) => {
|
||||
state.importJobQueue.mostRecentImport = fileName;
|
||||
}),
|
||||
);
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const { getState, setState } = useStore;
|
||||
|
||||
// Method to init AirDC++ Socket with supplied settings
|
||||
const initializeAirDCPPSocket = async (configuration) => {
|
||||
console.log("[AirDCPP]: Initializing socket...");
|
||||
/** Socket.IO initialization **/
|
||||
// 1. Fetch sessionId from localStorage
|
||||
const sessionId = localStorage.getItem("sessionId");
|
||||
// 2. socket.io instantiation
|
||||
const socketIOInstance = io(SOCKET_BASE_URI, {
|
||||
transports: ["websocket"],
|
||||
withCredentials: true,
|
||||
query: { sessionId },
|
||||
});
|
||||
// 3. Set the instance in global state
|
||||
setState({
|
||||
socketIOInstance,
|
||||
});
|
||||
|
||||
const initializedAirDCPPSocket = new AirDCPPSocket({
|
||||
protocol: `${configuration.protocol}`,
|
||||
hostname: `${configuration.hostname}:${configuration.port}`,
|
||||
username: `${configuration.username}`,
|
||||
password: `${configuration.password}`,
|
||||
});
|
||||
|
||||
// Set up connect and disconnect handlers
|
||||
initializedAirDCPPSocket.onConnected = (sessionInfo) => {
|
||||
// update global state with socket connection status
|
||||
setState({
|
||||
airDCPPSocketConnected: true,
|
||||
});
|
||||
};
|
||||
initializedAirDCPPSocket.onDisconnected = async (reason, code, wasClean) => {
|
||||
// update global state with socket connection status
|
||||
setState({
|
||||
disconnectionInfo: { reason, code, wasClean },
|
||||
airDCPPSocketConnected: false,
|
||||
});
|
||||
};
|
||||
// AirDC++ Socket-related connection and post-connection
|
||||
// Attempt connection
|
||||
const airDCPPSocketConnectionInformation =
|
||||
await initializedAirDCPPSocket.connect();
|
||||
setState({
|
||||
airDCPPSocketConnectionInformation,
|
||||
});
|
||||
|
||||
// Set up event listeners
|
||||
initializedAirDCPPSocket.addListener(
|
||||
`queue`,
|
||||
"queue_bundle_tick",
|
||||
async (downloadProgressData) => {
|
||||
console.log(downloadProgressData);
|
||||
// Socket.io-based session restoration
|
||||
if (!isNil(sessionId)) {
|
||||
// 1. Resume the session
|
||||
socketIOInstance.emit(
|
||||
"call",
|
||||
"socket.resumeSession",
|
||||
{
|
||||
sessionId,
|
||||
},
|
||||
(data) => console.log(data),
|
||||
);
|
||||
initializedAirDCPPSocket.addListener(
|
||||
"queue",
|
||||
"queue_bundle_added",
|
||||
async (data) => {
|
||||
console.log("JEMEN:", data);
|
||||
} else {
|
||||
// 1. Inititalize the session and persist the sessionId to localStorage
|
||||
socketIOInstance.on("sessionInitialized", (sessionId) => {
|
||||
localStorage.setItem("sessionId", sessionId);
|
||||
});
|
||||
}
|
||||
// 2. If a job is in progress, restore the job counts and persist those to global state
|
||||
socketIOInstance.on("RESTORE_JOB_COUNTS_AFTER_SESSION_RESTORATION", (data) => {
|
||||
console.log("Active import in progress detected; restoring counts...");
|
||||
const { completedJobCount, failedJobCount, queueStatus } = data;
|
||||
setState((state) => ({
|
||||
importJobQueue: {
|
||||
...state.importJobQueue,
|
||||
successfulJobCount: completedJobCount,
|
||||
failedJobCount,
|
||||
status: queueStatus,
|
||||
},
|
||||
);
|
||||
}));
|
||||
});
|
||||
|
||||
initializedAirDCPPSocket.addListener(
|
||||
`queue`,
|
||||
"queue_bundle_status",
|
||||
async (bundleData) => {
|
||||
let count = 0;
|
||||
if (bundleData.status.completed && bundleData.status.downloaded) {
|
||||
// dispatch the action for raw import, with the metadata
|
||||
if (count < 1) {
|
||||
console.log(`[AirDCPP]: Download complete.`);
|
||||
/**
|
||||
* Method to init AirDC++ Socket with supplied settings
|
||||
* @param configuration - credentials, and hostname details to init AirDC++ connection
|
||||
* @returns Initialized AirDC++ connection socket instance
|
||||
*/
|
||||
export const initializeAirDCPPSocket = async (configuration): Promise<any> => {
|
||||
try {
|
||||
console.log("[AirDCPP]: Initializing socket...");
|
||||
|
||||
count += 1;
|
||||
const initializedAirDCPPSocket = new AirDCPPSocket({
|
||||
protocol: `${configuration.protocol}`,
|
||||
hostname: `${configuration.hostname}:${configuration.port}`,
|
||||
username: `${configuration.username}`,
|
||||
password: `${configuration.password}`,
|
||||
});
|
||||
|
||||
// Set up connect and disconnect handlers
|
||||
initializedAirDCPPSocket.onConnected = (sessionInfo) => {
|
||||
// update global state with socket connection status
|
||||
setState({
|
||||
airDCPPSocketConnected: true,
|
||||
});
|
||||
};
|
||||
initializedAirDCPPSocket.onDisconnected = async (
|
||||
reason,
|
||||
code,
|
||||
wasClean,
|
||||
) => {
|
||||
// update global state with socket connection status
|
||||
setState({
|
||||
disconnectionInfo: { reason, code, wasClean },
|
||||
airDCPPSocketConnected: false,
|
||||
});
|
||||
};
|
||||
// AirDC++ Socket-related connection and post-connection
|
||||
// Attempt connection
|
||||
const airDCPPSessionInformation = await initializedAirDCPPSocket.connect();
|
||||
setState({
|
||||
airDCPPSessionInformation,
|
||||
});
|
||||
|
||||
// Set up event listeners
|
||||
initializedAirDCPPSocket.addListener(
|
||||
`queue`,
|
||||
"queue_bundle_tick",
|
||||
async (downloadProgressData) => {
|
||||
console.log(downloadProgressData);
|
||||
setState({
|
||||
airDCPPDownloadTick: downloadProgressData,
|
||||
});
|
||||
},
|
||||
);
|
||||
initializedAirDCPPSocket.addListener(
|
||||
"queue",
|
||||
"queue_bundle_added",
|
||||
async (data) => {
|
||||
console.log("JEMEN:", data);
|
||||
},
|
||||
);
|
||||
|
||||
initializedAirDCPPSocket.addListener(
|
||||
`queue`,
|
||||
"queue_bundle_status",
|
||||
async (bundleData) => {
|
||||
let count = 0;
|
||||
if (bundleData.status.completed && bundleData.status.downloaded) {
|
||||
// dispatch the action for raw import, with the metadata
|
||||
if (count < 1) {
|
||||
console.log(`[AirDCPP]: Download complete.`);
|
||||
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
return initializedAirDCPPSocket;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
// 1. get settings from mongo
|
||||
@@ -93,14 +201,19 @@ const { data } = await axios({
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
const directConnectConfiguration = data?.directConnect.client.host;
|
||||
const directConnectConfiguration = data?.directConnect?.client.host;
|
||||
|
||||
// 2. If available, init AirDC++ Socket with those settings
|
||||
if (!isEmpty(directConnectConfiguration)) {
|
||||
initializeAirDCPPSocket(directConnectConfiguration);
|
||||
if (!isNil(directConnectConfiguration)) {
|
||||
const airDCPPSocketInstance = await initializeAirDCPPSocket(
|
||||
directConnectConfiguration,
|
||||
);
|
||||
setState({
|
||||
airDCPPSocketInstance,
|
||||
airDCPPClientConfiguration: directConnectConfiguration,
|
||||
});
|
||||
} else {
|
||||
console.log("problem");
|
||||
}
|
||||
|
||||
console.log("connected?", getState());
|
||||
|
||||
Reference in New Issue
Block a user