diff --git a/Dockerfile b/Dockerfile index 324fee3..9576429 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,17 +3,20 @@ LABEL maintainer="Rishi Ghan " WORKDIR /threetwo +# Copy package.json and lock file first to leverage Docker cache COPY package.json ./ COPY yarn.lock ./ -COPY nodemon.json ./ -COPY jsdoc.json ./ -# RUN apt-get update && apt-get install -y git python3 build-essential autoconf automake g++ libpng-dev make -RUN apk --no-cache add g++ make libpng-dev git python3 libc6-compat autoconf automake libjpeg-turbo-dev libpng-dev mesa-dev mesa libxi build-base gcc libtool nasm -RUN yarn --ignore-engines +# Install build dependencies necessary for native modules +RUN apk --no-cache add g++ make libpng-dev git python3 autoconf automake libjpeg-turbo-dev mesa-dev mesa libxi build-base gcc libtool nasm +# Install node modules +RUN yarn install --ignore-engines +# Copy the rest of the application COPY . . + EXPOSE 5173 -ENTRYPOINT [ "npm", "start" ] \ No newline at end of file +# Use yarn start if you want to stick with yarn, or change to npm start if you prefer npm +ENTRYPOINT [ "yarn", "start" ] diff --git a/package.json b/package.json index f598687..af9cc00 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,8 @@ "@dnd-kit/core": "^6.0.8", "@dnd-kit/sortable": "^7.0.2", "@dnd-kit/utilities": "^3.2.1", + "@floating-ui/react": "^0.26.12", + "@floating-ui/react-dom": "^2.0.8", "@fortawesome/fontawesome-free": "^6.3.0", "@popperjs/core": "^2.11.8", "@rollup/plugin-node-resolve": "^15.0.1", @@ -27,7 +29,7 @@ "@types/react-router-dom": "^5.3.3", "@vitejs/plugin-react": "^4.2.1", "airdcpp-apisocket": "^2.5.0-beta.2", - "axios": "^1.3.4", + "axios": "^1.6.8", "axios-cache-interceptor": "^1.0.1", "axios-rate-limit": "^1.3.0", "babel-plugin-styled-components": "^2.1.4", @@ -41,6 +43,9 @@ "focus-trap-react": "^10.2.3", "history": "^5.3.0", "html-to-text": "^8.1.0", + "i18next": "^23.11.1", + "i18next-browser-languagedetector": "^7.2.1", + "i18next-http-backend": "^2.5.0", "immer": "^10.0.3", "jsdoc": "^3.6.10", "keen-slider": "^6.8.6", @@ -56,9 +61,9 @@ "react-fast-compare": "^3.2.0", "react-final-form": "^6.5.9", "react-final-form-arrays": "^3.1.4", + "react-i18next": "^14.1.0", "react-loader-spinner": "^4.0.0", "react-modal": "^3.15.1", - "react-popper": "^2.3.0", "react-router": "^6.9.0", "react-router-dom": "^6.9.0", "react-select": "^5.8.0", @@ -69,7 +74,9 @@ "socket.io-client": "^4.3.2", "styled-components": "^6.1.0", "threetwo-ui-typings": "^1.0.14", - "vite": "^5.0.13", + + "vite": "^5.2.7", + "vite-plugin-html": "^3.2.0", "websocket": "^1.0.34", "zustand": "^4.4.6" @@ -117,7 +124,7 @@ "prettier": "^2.2.1", "react-refresh": "^0.14.0", "rimraf": "^4.1.3", - "sass": "^1.69.5", + "sass": "^1.77.0", "storybook": "^7.3.2", "tailwindcss": "^3.4.1", "tui-jsdoc-template": "^1.2.2", diff --git a/src/client/components/ComicDetail/AcquisitionPanel.tsx b/src/client/components/ComicDetail/AcquisitionPanel.tsx index d8fd82c..b54ea3f 100644 --- a/src/client/components/ComicDetail/AcquisitionPanel.tsx +++ b/src/client/components/ComicDetail/AcquisitionPanel.tsx @@ -10,6 +10,7 @@ import { useStore } from "../../store"; import { useShallow } from "zustand/react/shallow"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import axios from "axios"; +import { AIRDCPP_SERVICE_BASE_URI } from "../../constants/endpoints"; interface IAcquisitionPanelProps { query: any; @@ -21,17 +22,9 @@ interface IAcquisitionPanelProps { export const AcquisitionPanel = ( props: IAcquisitionPanelProps, ): ReactElement => { - const { - airDCPPSocketInstance, - airDCPPClientConfiguration, - airDCPPSessionInformation, - airDCPPDownloadTick, - } = useStore( + const { socketIOInstance } = useStore( useShallow((state) => ({ - airDCPPSocketInstance: state.airDCPPSocketInstance, - airDCPPClientConfiguration: state.airDCPPClientConfiguration, - airDCPPSessionInformation: state.airDCPPSessionInformation, - airDCPPDownloadTick: state.airDCPPDownloadTick, + socketIOInstance: state.socketIOInstance, })), ); @@ -40,20 +33,54 @@ export const AcquisitionPanel = ( hub_urls: string[] | undefined | null; priority: PriorityEnum; } + interface SearchResult { + result: { + id: number; + }; + search_id: number; + // Add other properties as needed + } + const handleSearch = (searchQuery) => { + // Use the already connected socket instance to emit events + socketIOInstance.emit("initiateSearch", searchQuery); + }; + + const { + data: settings, + isLoading, + isError, + } = useQuery({ + queryKey: ["settings"], + queryFn: async () => + await axios({ + url: "http://localhost:3000/api/settings/getAllSettings", + method: "GET", + }), + }); /** * Get the hubs list from an AirDCPP Socket */ const { data: hubs } = useQuery({ queryKey: ["hubs"], - queryFn: async () => await airDCPPSocketInstance.get(`hubs`), + queryFn: async () => + await axios({ + url: `${AIRDCPP_SERVICE_BASE_URI}/getHubs`, + method: "POST", + data: { + host: settings?.data.directConnect?.client?.host, + }, + }), + enabled: !isEmpty(settings?.data.directConnect?.client?.host), }); const { comicObjectId } = props; const issueName = props.query.issue.name || ""; const sanitizedIssueName = issueName.replace(/[^a-zA-Z0-9 ]/g, " "); const [dcppQuery, setDcppQuery] = useState({}); - const [airDCPPSearchResults, setAirDCPPSearchResults] = useState([]); + const [airDCPPSearchResults, setAirDCPPSearchResults] = useState< + SearchResult[] + >([]); const [airDCPPSearchStatus, setAirDCPPSearchStatus] = useState(false); const [airDCPPSearchInstance, setAirDCPPSearchInstance] = useState({}); const [airDCPPSearchInfo, setAirDCPPSearchInfo] = useState({}); @@ -61,7 +88,7 @@ export const AcquisitionPanel = ( // Construct a AirDC++ query based on metadata inferred, upon component mount // Pre-populate the search input with the search string, so that - // All the user has to do is hit "Search AirDC++" + // all the user has to do is hit "Search AirDC++" to perform a search useEffect(() => { // AirDC++ search query const dcppSearchQuery = { @@ -69,7 +96,7 @@ export const AcquisitionPanel = ( pattern: `${sanitizedIssueName.replace(/#/g, "")}`, extensions: ["cbz", "cbr", "cb7"], }, - hub_urls: map(hubs, (item) => item.value), + hub_urls: map(hubs?.data, (item) => item.value), priority: 5, }; setDcppQuery(dcppSearchQuery); @@ -80,80 +107,55 @@ export const AcquisitionPanel = ( * @param {SearchData} data - a SearchData query * @param {any} ADCPPSocket - an intialized AirDC++ socket instance */ - const search = async (data: SearchData, ADCPPSocket: any) => { - try { - if (!ADCPPSocket.isConnected()) { - await ADCPPSocket(); - } - const instance: SearchInstance = await ADCPPSocket.post("search"); - setAirDCPPSearchStatus(true); - - // We want to get notified about every new result in order to make the user experience better - await ADCPPSocket.addListener( - `search`, - "search_result_added", - async (groupedResult) => { - // ...add the received result in the UI - // (it's probably a good idea to have some kind of throttling for the UI updates as there can be thousands of results) - setAirDCPPSearchResults((state) => [...state, groupedResult]); + const search = async (searchData: any) => { + setAirDCPPSearchResults([]); + socketIOInstance.emit( + "call", + "socket.search", + { + query: searchData, + config: { + protocol: `ws`, + hostname: `localhost:5600`, + username: `user`, + password: `pass`, }, - instance.id, - ); - - // We also want to update the existing items in our list when new hits arrive for the previously listed files/directories - await ADCPPSocket.addListener( - `search`, - "search_result_updated", - async (groupedResult) => { - // ...update properties of the existing result in the UI - const bundleToUpdateIndex = airDCPPSearchResults?.findIndex( - (bundle) => bundle.result.id === groupedResult.result.id, - ); - const updatedState = [...airDCPPSearchResults]; - if ( - !isNil(difference(updatedState[bundleToUpdateIndex], groupedResult)) - ) { - updatedState[bundleToUpdateIndex] = groupedResult; - } - setAirDCPPSearchResults((state) => [...state, ...updatedState]); - }, - instance.id, - ); - - // We need to show something to the user in case the search won't yield any results so that he won't be waiting forever) - // Wait for 5 seconds for any results to arrive after the searches were sent to the hubs - await ADCPPSocket.addListener( - `search`, - "search_hub_searches_sent", - async (searchInfo) => { - await sleep(5000); - - // Check the number of received results (in real use cases we should know that even without calling the API) - const currentInstance = await ADCPPSocket.get( - `search/${instance.id}`, - ); - setAirDCPPSearchInstance(currentInstance); - setAirDCPPSearchInfo(searchInfo); - if (currentInstance.result_count === 0) { - // ...nothing was received, show an informative message to the user - console.log("No more search results."); - } - - // The search can now be considered to be "complete" - // If there's an "in progress" indicator in the UI, that could also be disabled here - setAirDCPPSearchInstance(instance); - setAirDCPPSearchStatus(false); - }, - instance.id, - ); - // Finally, perform the actual search - await ADCPPSocket.post(`search/${instance.id}/hub_search`, data); - } catch (error) { - console.log(error); - throw error; - } + }, + (data: any) => console.log(data), + ); }; + socketIOInstance.on("searchResultAdded", (data: SearchResult) => { + setAirDCPPSearchResults((previousState) => { + const exists = previousState.some( + (item) => data.result.id === item.result.id, + ); + if (!exists) { + return [...previousState, data]; + } + return previousState; + }); + }); + + socketIOInstance.on("searchResultUpdated", (groupedResult: SearchResult) => { + // ...update properties of the existing result in the UI + const bundleToUpdateIndex = airDCPPSearchResults?.findIndex( + (bundle) => bundle.result.id === groupedResult.result.id, + ); + const updatedState = [...airDCPPSearchResults]; + if (!isNil(difference(updatedState[bundleToUpdateIndex], groupedResult))) { + updatedState[bundleToUpdateIndex] = groupedResult; + } + setAirDCPPSearchResults((state) => [...state, ...updatedState]); + }); + + socketIOInstance.on("searchInitiated", (data) => { + setAirDCPPSearchInstance(data.instance); + }); + socketIOInstance.on("searchesSent", (data) => { + setAirDCPPSearchInfo(data.searchInfo); + }); + /** * Method to download a bundle associated with a search result from AirDC++ * @param {Number} searchInstanceId - description @@ -162,7 +164,7 @@ export const AcquisitionPanel = ( * @param {String} name - description * @param {Number} size - description * @param {any} type - description - * @param {any} ADCPPSocket - description + * @param {any} config - description * @returns {void} - description */ const download = async ( @@ -172,50 +174,22 @@ export const AcquisitionPanel = ( name: String, size: Number, type: any, - ADCPPSocket: any, + config: any, ): void => { - try { - if (!ADCPPSocket.isConnected()) { - await ADCPPSocket.connect(); - } - let bundleDBImportResult = {}; - const downloadResult = await ADCPPSocket.post( - `search/${searchInstanceId}/results/${resultId}/download`, - ); - - if (!isNil(downloadResult)) { - bundleDBImportResult = await axios({ - method: "POST", - url: `http://localhost:3000/api/library/applyAirDCPPDownloadMetadata`, - headers: { - "Content-Type": "application/json; charset=utf-8", - }, - data: { - bundleId: downloadResult.bundle_info.id, - comicObjectId, - name, - size, - type, - }, - }); - console.log(bundleDBImportResult?.data); - queryClient.invalidateQueries({ queryKey: ["comicBookMetadata"] }); - - // dispatch({ - // type: AIRDCPP_RESULT_DOWNLOAD_INITIATED, - // downloadResult, - // bundleDBImportResult, - // }); - // - // dispatch({ - // type: IMS_COMIC_BOOK_DB_OBJECT_FETCHED, - // comicBookDetail: bundleDBImportResult.data, - // IMS_inProgress: false, - // }); - } - } catch (error) { - throw error; - } + socketIOInstance.emit( + "call", + "socket.download", + { + searchInstanceId, + resultId, + comicObjectId, + name, + size, + type, + config, + }, + (data: any) => console.log(data), + ); }; const getDCPPSearchResults = async (searchQuery) => { const manualQuery = { @@ -223,17 +197,17 @@ export const AcquisitionPanel = ( pattern: `${searchQuery.issueName}`, extensions: ["cbz", "cbr", "cb7"], }, - hub_urls: map(hubs, (hub) => hub.hub_url), + hub_urls: map(hubs?.data, (hub) => hub.hub_url), priority: 5, }; - search(manualQuery, airDCPPSocketInstance); + search(manualQuery); }; return ( <>
- {!isEmpty(airDCPPSocketInstance) ? ( + {!isEmpty(hubs?.data) ? (
- {hubs.map((value, idx) => ( + {hubs?.data.map((value, idx) => ( {value.identity.name} @@ -358,7 +332,7 @@ export const AcquisitionPanel = ( - {map(airDCPPSearchResults, ({ result }, idx) => { + {map(airDCPPSearchResults, ({ result, search_id }, idx) => { return ( diff --git a/src/client/components/ComicDetail/DownloadsPanel.tsx b/src/client/components/ComicDetail/DownloadsPanel.tsx index eb385c3..2a4a9d8 100644 --- a/src/client/components/ComicDetail/DownloadsPanel.tsx +++ b/src/client/components/ComicDetail/DownloadsPanel.tsx @@ -59,7 +59,6 @@ export const DownloadsPanel = ( }, }), }); - const getBundles = async (comicObject) => { if (comicObject?.data.acquisition.directconnect) { const filteredBundles = @@ -94,10 +93,6 @@ export const DownloadsPanel = ( return (
- {!isEmpty(airDCPPSocketInstance) && - !isEmpty(bundles) && - activeTab === "directconnect" && } -
{activeTab === "torrents" && } + {!isEmpty(airDCPPSocketInstance) && + !isEmpty(bundles) && + activeTab === "directconnect" && }
); }; diff --git a/src/client/components/ComicDetail/TorrentSearchPanel.tsx b/src/client/components/ComicDetail/TorrentSearchPanel.tsx index f296e6a..8e8a035 100644 --- a/src/client/components/ComicDetail/TorrentSearchPanel.tsx +++ b/src/client/components/ComicDetail/TorrentSearchPanel.tsx @@ -23,15 +23,17 @@ export const TorrentSearchPanel = (props) => { url: `${PROWLARR_SERVICE_BASE_URI}/search`, method: "POST", data: { - port: "9696", - apiKey: "c4f42e265fb044dc81f7e88bd41c3367", - offset: 0, - categories: [7030], - query: searchTerm.issueName, - host: "localhost", - limit: 100, - type: "search", - indexerIds: [2], + prowlarrQuery: { + port: "9696", + apiKey: "c4f42e265fb044dc81f7e88bd41c3367", + offset: 0, + categories: [7030], + query: searchTerm.issueName, + host: "localhost", + limit: 100, + type: "search", + indexerIds: [2], + }, }, }); }, diff --git a/src/client/components/Dashboard/Dashboard.tsx b/src/client/components/Dashboard/Dashboard.tsx index 04098a8..4192426 100644 --- a/src/client/components/Dashboard/Dashboard.tsx +++ b/src/client/components/Dashboard/Dashboard.tsx @@ -21,13 +21,15 @@ export const Dashboard = (): ReactElement => { limit: 5, sort: { updatedAt: "-1" }, }, - predicate: { "acquisition.source.wanted": false }, + predicate: { + wanted: { $exists: false }, + }, comicStatus: "recent", }, }), queryKey: ["recentComics"], }); - + // Wanted Comics const { data: wantedComics } = useQuery({ queryFn: async () => await axios({ @@ -39,7 +41,9 @@ export const Dashboard = (): ReactElement => { limit: 5, sort: { updatedAt: "-1" }, }, - predicate: { "acquisition.source.wanted": true }, + predicate: { + wanted: { $exists: true, $ne: null }, + }, }, }), queryKey: ["wantedComics"], diff --git a/src/client/components/Dashboard/RecentlyImported.tsx b/src/client/components/Dashboard/RecentlyImported.tsx index da14c40..47a4e79 100644 --- a/src/client/components/Dashboard/RecentlyImported.tsx +++ b/src/client/components/Dashboard/RecentlyImported.tsx @@ -18,6 +18,7 @@ type RecentlyImportedProps = { export const RecentlyImported = ( comics: RecentlyImportedProps, ): ReactElement => { + console.log(comics); return (
{ @@ -45,11 +44,14 @@ export const RecentlyImported = ( comicInfo, locg, }); - const { issue, coverURL, icon } = determineExternalMetadata(name, { - comicvine, - comicInfo, - locg, - }); + const { issue, coverURL, icon } = determineExternalMetadata( + source, + { + comicvine, + comicInfo, + locg, + }, + ); const isComicVineMetadataAvailable = !isUndefined(comicvine) && !isUndefined(comicvine.volumeInformation); diff --git a/src/client/components/Dashboard/WantedComicsList.tsx b/src/client/components/Dashboard/WantedComicsList.tsx index cff9968..61ba8e1 100644 --- a/src/client/components/Dashboard/WantedComicsList.tsx +++ b/src/client/components/Dashboard/WantedComicsList.tsx @@ -31,10 +31,9 @@ export const WantedComicsList = ({ _id, rawFileDetails, sourcedMetadata: { comicvine, comicInfo, locg }, + wanted, }) => { - const isComicBookMetadataAvailable = - !isUndefined(comicvine) && - !isUndefined(comicvine.volumeInformation); + const isComicBookMetadataAvailable = !isUndefined(comicvine); const consolidatedComicMetadata = { rawFileDetails, comicvine, @@ -42,12 +41,15 @@ export const WantedComicsList = ({ locg, }; - const { issueName, url } = determineCoverFile( - consolidatedComicMetadata, - ); + const { + issueName, + url, + publisher = null, + } = determineCoverFile(consolidatedComicMetadata); const titleElement = ( {ellipsize(issueName, 20)} +

{publisher}

); return ( @@ -59,28 +61,42 @@ export const WantedComicsList = ({ title={issueName ? titleElement : No Name} >
- {/* Issue type */} - {isComicBookMetadataAvailable && - !isNil( - detectIssueTypes(comicvine.volumeInformation.description), - ) ? ( -
- - - - +
+ {/* Issue type */} + {isComicBookMetadataAvailable && + !isNil(detectIssueTypes(comicvine.description)) ? ( +
+ + + + - - { - detectIssueTypes( - comicvine.volumeInformation.description, - ).displayName - } + + { + detectIssueTypes(comicvine.description) + .displayName + } + - -
- ) : null} +
+ ) : null} + {/* issues marked as wanted, part of this volume */} + {wanted?.markEntireVolumeWanted ? ( +
sagla volume pahije
+ ) : ( +
+ + + + + + {wanted.issues.length} + + +
+ )} +
{/* comicVine metadata presence */} {isComicBookMetadataAvailable && ( { const formData = { search: "", }; - const queryClient = useQueryClient(); - const [searchQuery, setSearchQuery] = useState(""); const [comicVineMetadata, setComicVineMetadata] = useState({}); - const getCVSearchResults = (searchQuery) => { - setSearchQuery(searchQuery.search); + const [selectedResource, setSelectedResource] = useState("volume"); + const { t } = useTranslation(); + const handleResourceChange = (value) => { + setSelectedResource(value); }; const { + mutate, data: comicVineSearchResults, - isLoading, + isPending, isSuccess, - } = useQuery({ - queryFn: async () => - await axios({ + } = useMutation({ + mutationFn: async (data: { search: string; resource: string }) => { + const { search, resource } = data; + return await axios({ url: `${COMICVINE_SERVICE_URI}/search`, method: "GET", params: { api_key: "a5fa0663683df8145a85d694b5da4b87e1c92c69", - query: searchQuery, + query: search, format: "json", limit: "10", offset: "0", field_list: - "id,name,deck,api_detail_url,image,description,volume,cover_date", - resources: "issue", + "id,name,deck,api_detail_url,image,description,volume,cover_date,start_year,count_of_issues,publisher,issue_number", + resources: resource, }, - }), - queryKey: ["comicvineSearchResults", searchQuery], - enabled: !isNil(searchQuery), + }); + }, }); // add to library - const { data: additionResult } = useQuery({ - queryFn: async () => - await axios({ + const { data: additionResult, mutate: addToWantedList } = useMutation({ + mutationFn: async ({ + source, + comicObject, + markEntireVolumeWanted, + resourceType, + }) => { + console.log("jigni", comicObject); + let volumeInformation = {}; + let issues = []; + switch (resourceType) { + case "issue": + const { id, api_detail_url, image, cover_date, issue_number } = + comicObject; + // Add issue metadata + issues.push({ + id, + api_detail_url, + image, + coverDate: cover_date, + issueNumber: issue_number, + }); + // Get volume metadata from CV + const response = await axios({ + url: `${COMICVINE_SERVICE_URI}/getVolumes`, + method: "POST", + data: { + volumeURI: comicObject.volume.api_detail_url, + fieldList: + "id,name,deck,api_detail_url,image,description,start_year,year,count_of_issues,publisher,first_issue,last_issue", + }, + }); + // set volume metadata key + volumeInformation = response.data?.results; + break; + + case "volume": + const { + id: volumeId, + api_detail_url: apiUrl, + image: volumeImage, + name, + publisher, + } = comicObject; + volumeInformation = { + id: volumeId, + url: apiUrl, + image: volumeImage, + name, + publisher, + }; + break; + + default: + console.log("Invalid resource type."); + break; + } + // Add to wanted list + return await axios({ url: `${LIBRARY_SERVICE_BASE_URI}/rawImportToDb`, method: "POST", data: { importType: "new", payload: { - rawFileDetails: { - name: "", - }, importStatus: { - isImported: true, + isImported: false, // wanted, but not acquired yet. tagged: false, matchedResult: { score: "0", }, }, - sourcedMetadata: - { comicvine: comicVineMetadata?.comicData } || null, - acquisition: { source: { wanted: true, name: "comicvine" } }, + wanted: { + source, + markEntireVolumeWanted, + issues, + volume: volumeInformation, + }, + sourcedMetadata: { comicvine: volumeInformation }, }, }, - }), - queryKey: ["additionResult"], - enabled: !isNil(comicVineMetadata.comicData), + }); + }, }); const addToLibrary = (sourceName: string, comicData) => @@ -87,6 +147,15 @@ export const Search = ({}: ISearchProps): ReactElement => { return { __html: html }; }; + const onSubmit = async (values) => { + const formData = { ...values, resource: selectedResource }; + try { + mutate(formData); + } catch (error) { + // Handle error + } + }; + return (
@@ -107,7 +176,7 @@ export const Search = ({}: ISearchProps): ReactElement => {
{ Search
+ {/* resource type selection: volume, issue etc. */} +
+ + {({ input: volumesInput, meta }) => ( +
+
+ handleResourceChange("volume")} + className="peer hidden" + /> + +
+
+ )} +
+ + + {({ input: issuesInput, meta }) => ( +
+
+ handleResourceChange("issue")} + className="peer hidden" + /> + +
+
+ )} +
+
)} />
- {isLoading && <>Loading kaka...} - {!isNil(comicVineSearchResults?.data.results) && - !isEmpty(comicVineSearchResults?.data.results) ? ( + {isPending && ( +
+ Loading results... +
+ )} + {!isEmpty(comicVineSearchResults?.data?.results) ? (
{comicVineSearchResults.data.results.map((result) => { - return isSuccess ? ( -
+ return result.resource_type === "issue" ? ( +
-
+
{ hasDetails={false} />
-
+
{!isEmpty(result.volume.name) ? ( result.volume.name @@ -167,27 +290,19 @@ export const Search = ({}: ISearchProps): ReactElement => { No Name )}
-
-
-
- Cover date - - {dayjs(result.cover_date).format("MMM D, YYYY")} - -
-
+ {result.cover_date && ( +

+ Cover date + {dayjs(result.cover_date).format("MMM D, YYYY")} +

+ )} -
-
- {result.id} -
-
-
+

{result.id}

{result.api_detail_url} -

+

{ellipsize( convert(result.description, { baseElements: { @@ -198,19 +313,129 @@ export const Search = ({}: ISearchProps): ReactElement => { )}

- + + addToWantedList({ + source: "comicvine", + comicObject: result, + markEntireVolumeWanted: false, + resourceType: "issue", + }) + } + />
) : ( -
Loading
+ result.resource_type === "volume" && ( +
+
+
+ +
+
+
+ {!isEmpty(result.name) ? ( + result.name + ) : ( + No Name + )} + {result.start_year && <> ({result.start_year})} +
+ +
+ {/* issue count */} + {result.count_of_issues && ( +
+ + + + + + + {t("issueWithCount", { + count: result.count_of_issues, + })} + + +
+ )} + {/* type: TPB, one-shot, graphic novel etc. */} + {!isNil(result.description) && + !isUndefined(result.description) && ( + <> + {!isEmpty( + detectIssueTypes(result.description), + ) && ( +
+ + + + + + + { + detectIssueTypes(result.description) + .displayName + } + + +
+ )} + + )} +
+ + {result.id} +

+ + {result.api_detail_url} + +

+ + {/* description */} +

+ {ellipsize( + convert(result.description, { + baseElements: { + selectors: ["p", "div"], + }, + }), + 320, + )} +

+
+ + addToWantedList({ + source: "comicvine", + comicObject: result, + markEntireVolumeWanted: true, + resourceType: "volume", + }) + } + /> +
+
+
+
+ ) ); })}
diff --git a/src/client/components/Settings/AirDCPPSettings/AirDCPPHubsForm.tsx b/src/client/components/Settings/AirDCPPSettings/AirDCPPHubsForm.tsx index 64efcc4..21e61d6 100644 --- a/src/client/components/Settings/AirDCPPSettings/AirDCPPHubsForm.tsx +++ b/src/client/components/Settings/AirDCPPSettings/AirDCPPHubsForm.tsx @@ -3,20 +3,11 @@ import { Form, Field } from "react-final-form"; import { isEmpty, isNil, isUndefined } from "lodash"; import Select from "react-select"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; -import { useStore } from "../../../store"; import axios from "axios"; +import { AIRDCPP_SERVICE_BASE_URI } from "../../../constants/endpoints"; export const AirDCPPHubsForm = (): ReactElement => { const queryClient = useQueryClient(); - const { - airDCPPSocketInstance, - airDCPPClientConfiguration, - airDCPPSessionInformation, - } = useStore((state) => ({ - airDCPPSocketInstance: state.airDCPPSocketInstance, - airDCPPClientConfiguration: state.airDCPPClientConfiguration, - airDCPPSessionInformation: state.airDCPPSessionInformation, - })); const { data: settings, @@ -36,11 +27,19 @@ export const AirDCPPHubsForm = (): ReactElement => { */ const { data: hubs } = useQuery({ queryKey: ["hubs"], - queryFn: async () => await airDCPPSocketInstance.get(`hubs`), + queryFn: async () => + await axios({ + url: `${AIRDCPP_SERVICE_BASE_URI}/getHubs`, + method: "POST", + data: { + host: settings?.data.directConnect?.client?.host, + }, + }), + enabled: !isEmpty(settings?.data.directConnect?.client?.host), }); let hubList = {}; if (!isNil(hubs)) { - hubList = hubs.map(({ hub_url, identity }) => ({ + hubList = hubs?.data.map(({ hub_url, identity }) => ({ value: hub_url, label: identity.name, })); @@ -101,7 +100,10 @@ export const AirDCPPHubsForm = (): ReactElement => { /> ) : ( <> -
+
No configured hubs detected in AirDC++.
Configure to a hub in AirDC++ and then select a default hub here. diff --git a/src/client/components/Settings/AirDCPPSettings/AirDCPPSettingsForm.tsx b/src/client/components/Settings/AirDCPPSettings/AirDCPPSettingsForm.tsx index d41e652..44f1794 100644 --- a/src/client/components/Settings/AirDCPPSettings/AirDCPPSettingsForm.tsx +++ b/src/client/components/Settings/AirDCPPSettings/AirDCPPSettingsForm.tsx @@ -1,76 +1,65 @@ -import React, { ReactElement, useCallback } from "react"; +import React, { useState, useEffect } from "react"; import { AirDCPPSettingsConfirmation } from "./AirDCPPSettingsConfirmation"; -import { isUndefined, isEmpty } from "lodash"; import { ConnectionForm } from "../../shared/ConnectionForm/ConnectionForm"; -import { initializeAirDCPPSocket, useStore } from "../../../store/index"; -import { useShallow } from "zustand/react/shallow"; -import { useMutation } from "@tanstack/react-query"; +import { useMutation, useQuery } from "@tanstack/react-query"; import axios from "axios"; +import { + AIRDCPP_SERVICE_BASE_URI, + SETTINGS_SERVICE_BASE_URI, +} from "../../../constants/endpoints"; -export const AirDCPPSettingsForm = (): ReactElement => { - // cherry-picking selectors for: - // 1. initial values for the form - // 2. If initial values are present, get the socket information to display - const { setState } = useStore; - const { - airDCPPSocketConnected, - airDCPPDisconnectionInfo, - airDCPPSessionInformation, - airDCPPClientConfiguration, - airDCPPSocketInstance, - setAirDCPPSocketInstance, - } = useStore( - useShallow((state) => ({ - airDCPPSocketConnected: state.airDCPPSocketConnected, - airDCPPDisconnectionInfo: state.airDCPPDisconnectionInfo, - airDCPPClientConfiguration: state.airDCPPClientConfiguration, - airDCPPSessionInformation: state.airDCPPSessionInformation, - airDCPPSocketInstance: state.airDCPPSocketInstance, - setAirDCPPSocketInstance: state.setAirDCPPSocketInstance, - })), - ); +export const AirDCPPSettingsForm = () => { + const [airDCPPSessionInformation, setAirDCPPSessionInformation] = + useState(null); + // Fetching all settings + const { data: settingsData, isSuccess: settingsSuccess } = useQuery({ + queryKey: ["airDCPPSettings"], + queryFn: () => axios.get(`${SETTINGS_SERVICE_BASE_URI}/getAllSettings`), + }); - /** - * Mutation to update settings and subsequently initialize - * AirDC++ socket with those settings - */ + // Fetch session information + const fetchSessionInfo = (host) => { + return axios.post(`${AIRDCPP_SERVICE_BASE_URI}/initialize`, { host }); + }; + + // Use effect to trigger side effects on settings fetch success + useEffect(() => { + if (settingsSuccess && settingsData?.data?.directConnect?.client?.host) { + const host = settingsData.data.directConnect.client.host; + fetchSessionInfo(host).then((response) => { + setAirDCPPSessionInformation(response.data); + }); + } + }, [settingsSuccess, settingsData]); + + // Handle setting update and subsequent AirDC++ initialization const { mutate } = useMutation({ - mutationFn: async (values) => - await axios({ - url: `http://localhost:3000/api/settings/saveSettings`, - method: "POST", - data: { settingsPayload: values, settingsKey: "directConnect" }, - }), - onSuccess: async (values) => { - const { - data: { - directConnect: { - client: { host }, - }, - }, - } = values; - const dcppSocketInstance = await initializeAirDCPPSocket(host); - setState({ - airDCPPClientConfiguration: host, - airDCPPSocketInstance: dcppSocketInstance, + mutationFn: (values) => { + console.log(values); + return axios.post("http://localhost:3000/api/settings/saveSettings", { + settingsPayload: values, + settingsKey: "directConnect", }); }, + onSuccess: async (response) => { + const host = response?.data?.directConnect?.client?.host; + if (host) { + const response = await fetchSessionInfo(host); + setAirDCPPSessionInformation(response.data); + // setState({ airDCPPClientConfiguration: host }); + } + }, }); - const deleteSettingsMutation = useMutation( - async () => - await axios.post("http://localhost:3000/api/settings/saveSettings", { - settingsPayload: {}, - settingsKey: "directConnect", - }), + + const deleteSettingsMutation = useMutation(() => + axios.post("http://localhost:3000/api/settings/saveSettings", { + settingsPayload: {}, + settingsKey: "directConnect", + }), ); - // const removeSettings = useCallback(async () => { - // // airDCPPSettings.setSettings({}); - // }, []); - // - const initFormData = !isUndefined(airDCPPClientConfiguration) - ? airDCPPClientConfiguration - : {}; + const initFormData = settingsData?.data?.directConnect?.client?.host ?? {}; + return ( <> { formHeading={"Configure AirDC++"} /> - {!isEmpty(airDCPPSessionInformation) ? ( + {airDCPPSessionInformation && ( - ) : null} + )} - {!isEmpty(airDCPPClientConfiguration) ? ( + {settingsData?.data && (

- as

- ) : null} + )} ); }; diff --git a/src/client/components/Settings/SystemSettings/SystemSettingsForm.tsx b/src/client/components/Settings/SystemSettings/SystemSettingsForm.tsx index 68e41aa..9081822 100644 --- a/src/client/components/Settings/SystemSettings/SystemSettingsForm.tsx +++ b/src/client/components/Settings/SystemSettings/SystemSettingsForm.tsx @@ -48,13 +48,11 @@ export const SystemSettingsForm = (): ReactElement => {
diff --git a/src/client/components/VolumeDetail/VolumeDetail.tsx b/src/client/components/VolumeDetail/VolumeDetail.tsx index d81b1cc..e5c7fd6 100644 --- a/src/client/components/VolumeDetail/VolumeDetail.tsx +++ b/src/client/components/VolumeDetail/VolumeDetail.tsx @@ -1,30 +1,25 @@ -import { isEmpty, isUndefined, map, partialRight, pick } from "lodash"; -import React, { useEffect, ReactElement, useState, useCallback } from "react"; -import { useDispatch, useSelector } from "react-redux"; +import { isEmpty, isNil, isUndefined, map, partialRight, pick } from "lodash"; +import React, { ReactElement, useState, useCallback } from "react"; import { useParams } from "react-router"; -import { - getComicBookDetailById, - getIssuesForSeries, - analyzeLibrary, -} from "../../actions/comicinfo.actions"; +import { analyzeLibrary } from "../../actions/comicinfo.actions"; +import { useQuery, useMutation, QueryClient } from "@tanstack/react-query"; import PotentialLibraryMatches from "./PotentialLibraryMatches"; -import Masonry from "react-masonry-css"; import { Card } from "../shared/Carda"; import SlidingPane from "react-sliding-pane"; import { convert } from "html-to-text"; import ellipsize from "ellipsize"; +import { + COMICVINE_SERVICE_URI, + LIBRARY_SERVICE_BASE_URI, +} from "../../constants/endpoints"; +import axios from "axios"; const VolumeDetails = (props): ReactElement => { - const breakpointColumnsObj = { - default: 6, - 1100: 4, - 700: 3, - 500: 2, - }; // sliding panel config const [visible, setVisible] = useState(false); const [slidingPanelContentId, setSlidingPanelContentId] = useState(""); const [matches, setMatches] = useState([]); + const [storyArcsData, setStoryArcsData] = useState([]); const [active, setActive] = useState(1); // sliding panel init @@ -33,7 +28,9 @@ const VolumeDetails = (props): ReactElement => { content: () => { const ids = map(matches, partialRight(pick, "_id")); const matchIds = ids.map((id: any) => id._id); - return ; + { + /* return ; */ + } }, }, }; @@ -45,68 +42,145 @@ const VolumeDetails = (props): ReactElement => { setVisible(true); }, []); - const analyzeIssues = useCallback((issues) => { - dispatch(analyzeLibrary(issues)); - }, []); - - const comicBookDetails = useSelector( - (state: RootState) => state.comicInfo.comicBookDetail, - ); - const issuesForVolume = useSelector( - (state: RootState) => state.comicInfo.issuesForVolume, - ); - - const dispatch = useDispatch(); - useEffect(() => { - dispatch(getIssuesForSeries(comicObjectId)); - dispatch(getComicBookDetailById(comicObjectId)); - }, []); + // const analyzeIssues = useCallback((issues) => { + // dispatch(analyzeLibrary(issues)); + // }, []); + // const { comicObjectId } = useParams<{ comicObjectId: string }>(); + const { data: comicObject, isSuccess: isComicObjectFetchedSuccessfully } = + useQuery({ + queryFn: async () => + axios({ + url: `${LIBRARY_SERVICE_BASE_URI}/getComicBookById`, + method: "POST", + data: { + id: comicObjectId, + }, + }), + queryKey: ["comicObject"], + }); + + // get issues for a series + const { + data: issuesForSeries, + isSuccess, + isFetching, + } = useQuery({ + queryFn: async () => + await axios({ + url: `${COMICVINE_SERVICE_URI}/getIssuesForVolume`, + method: "POST", + data: { + volumeId: + comicObject?.data?.sourcedMetadata.comicvine.volumeInformation.id, + }, + }), + queryKey: ["issuesForSeries", comicObject?.data], + enabled: !isUndefined(comicObject?.data), + }); + // get story arcs + const useGetStoryArcs = () => { + return useMutation({ + mutationFn: async (comicObject) => + axios({ + url: `${COMICVINE_SERVICE_URI}/getResource`, + method: "POST", + data: { + comicObject, + resource: "issue", + filter: `id:${comicObject?.sourcedMetadata.comicvine.id}`, + }, + }), + onSuccess: (data) => { + setStoryArcsData(data?.data.results); + }, + }); + }; + + const { + mutate: getStoryArcs, + isIdle, + isError, + data, + error, + status, + } = useGetStoryArcs(); + const IssuesInVolume = () => ( <> - {!isUndefined(issuesForVolume) ? ( -
analyzeIssues(issuesForVolume)}> + {!isUndefined(issuesForSeries) ? ( +
analyzeIssues(issuesForSeries)}> Analyze Library
) : null} - - {!isUndefined(issuesForVolume) && !isEmpty(issuesForVolume) - ? issuesForVolume.map((issue) => { - return ( + <> + {isSuccess && + issuesForSeries.data.map((issue) => { + return ( + <> - openPotentialLibraryMatchesPanel(issue.matches) - } - > - - {issue.issue_number} - - {!isEmpty(issue.matches) ? ( - <> - - - - - ) : null} - - ); - }) - : "loading"} - + imageUrl={issue.image.small_url} + orientation={"cover-only"} + hasDetails={false} + /> + + {issue.issue_number} + + {!isEmpty(issue.matches) ? ( + <> + + + + + ) : null} + + ); + })} + + + ); + + const Issues = () => ( + <> +
+
+ You can add a single issue or the whole volume, and it will be added + to the list of `Wanted` items. +
+
+
+ {isSuccess && + issuesForSeries?.data.map((issue) => { + return ( +
+
+
+ +
+
+

{issue.name}

+

+ {convert(issue.description, { + baseElements: { + selectors: ["p"], + }, + })} +

+
+
+
+ ); + })} +
); @@ -115,20 +189,44 @@ const VolumeDetails = (props): ReactElement => { { id: 1, name: "Issues in Volume", - icon: , - content: , + icon: , + content: , }, { id: 2, - icon: , + icon: ( + + ), name: "Characters", - content:
asdasd
, + content:
Characters
, }, { id: 3, - icon: , - name: "Arcs", - content:
asdasd
, + icon: ( + + ), + name: "Story Arcs", + content: ( +
+ + {status === "pending" && <>{status}} + {!isEmpty(storyArcsData) && status === "success" && ( + <> +
    + {storyArcsData.map((storyArc) => { + return ( +
  • + {storyArc?.name} +
  • + ); + })} +
+ + )} +
+ ), }, ]; @@ -136,21 +234,26 @@ const VolumeDetails = (props): ReactElement => { const MetadataTabGroup = () => { return ( <> -
- + ))} + +
{tabGroup.map(({ id, content }) => { return active === id ? content : null; @@ -158,97 +261,103 @@ const VolumeDetails = (props): ReactElement => { ); }; - - if ( - !isUndefined(comicBookDetails.sourcedMetadata) && - !isUndefined(comicBookDetails.sourcedMetadata.comicvine.volumeInformation) - ) { + if (isComicObjectFetchedSuccessfully && !isUndefined(comicObject.data)) { + const { sourcedMetadata } = comicObject.data; return ( -
-
- {/* Title */} -

- {comicBookDetails.sourcedMetadata.comicvine.volumeInformation.name} -

-
- {/* Volume cover */} -
+ <> +
+
+
+
+

+ Volumes +

+ +

+ Browse your collection of volumes. +

+
+
+
+
+
+
+
+ {/* Volume cover */} -
-
-
- {/* Comicvine Id */} -
-
- ComicVine Id - - { - comicBookDetails.sourcedMetadata.comicvine - .volumeInformation.id - } - -
-
- {/* Publisher */} -
-
- Publisher - - { - comicBookDetails.sourcedMetadata.comicvine - .volumeInformation.publisher.name - } - -
-
-
- - {/* Deck */}
- {!isEmpty( - comicBookDetails.sourcedMetadata.comicvine.volumeInformation - .description, - ) - ? ellipsize( - convert( - comicBookDetails.sourcedMetadata.comicvine - .volumeInformation.description, +
+ {/* Title */} + + {sourcedMetadata.comicvine.volumeInformation.name} + + {/* Comicvine Id */} +
+
+ ComicVine Id + + {sourcedMetadata.comicvine.volumeInformation.id} + +
+
+ {/* Publisher */} +
+
+ Publisher + { - baseElements: { - selectors: ["p"], + sourcedMetadata.comicvine.volumeInformation.publisher + .name + } + +
+
+
+ + {/* Deck */} +
+ {!isEmpty( + sourcedMetadata.comicvine.volumeInformation.description, + ) + ? ellipsize( + convert( + sourcedMetadata.comicvine.volumeInformation + .description, + { + baseElements: { + selectors: ["p"], + }, }, - }, - ), - 300, - ) - : null} + ), + 300, + ) + : null} +
+ + {/*
{JSON.stringify(issuesForVolume, undefined, 2)}
*/}
- - {/*
{JSON.stringify(issuesForVolume, undefined, 2)}
*/} +
- -
- setVisible(false)} - title={"Potential Matches in Library"} - width={"600px"} - > - {slidingPanelContentId !== "" && - contentForSlidingPanel[slidingPanelContentId].content()} - -
+ setVisible(false)} + title={"Potential Matches in Library"} + width={"600px"} + > + {slidingPanelContentId !== "" && + contentForSlidingPanel[slidingPanelContentId].content()} + +
+ ); } else { return <>; diff --git a/src/client/components/Volumes/Volumes.tsx b/src/client/components/Volumes/Volumes.tsx index b7b65fb..c4021fb 100644 --- a/src/client/components/Volumes/Volumes.tsx +++ b/src/client/components/Volumes/Volumes.tsx @@ -4,6 +4,7 @@ import Card from "../shared/Carda"; import T2Table from "../shared/T2Table"; import ellipsize from "ellipsize"; import { convert } from "html-to-text"; +import { Link } from "react-router-dom"; import { useQuery } from "@tanstack/react-query"; import axios from "axios"; import { SEARCH_SERVICE_BASE_URI } from "../../constants/endpoints"; @@ -39,35 +40,39 @@ export const Volumes = (props): ReactElement => { header: "Volume Details", id: "volumeDetails", minWidth: 450, - accessorKey: "_source", + accessorFn: (row) => row, cell: (row): any => { - const foo = row.getValue(); + const comicObject = row.getValue(); + const { + _source: { sourcedMetadata }, + } = comicObject; + console.log("jaggu", row.getValue()); return (
- -
- - {foo.sourcedMetadata.comicvine.volumeInformation.name} - + + + +
+
+ {sourcedMetadata.comicvine.volumeInformation.name} +

{ellipsize( convert( - foo.sourcedMetadata.comicvine.volumeInformation - .description, + sourcedMetadata.comicvine.volumeInformation.description, { baseElements: { selectors: ["p"], }, }, ), - 120, + 180, )}

@@ -162,6 +167,7 @@ export const Volumes = (props): ReactElement => { nextPage: () => {}, previousPage: () => {}, }} + rowClickHandler={() => {}} columns={columnData} />
diff --git a/src/client/components/shared/Card.tsx b/src/client/components/shared/Card.tsx deleted file mode 100644 index b4db0ed..0000000 --- a/src/client/components/shared/Card.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import * as React from "react"; -import { IExtractedComicBookCoverFile } from "threetwo-ui-typings"; -import { - removeLeadingPeriod, - escapePoundSymbol, -} from "../shared/utils/formatting.utils"; -import { isUndefined, isEmpty, isNil } from "lodash"; -import { Link } from "react-router-dom"; -import { LIBRARY_SERVICE_HOST } from "../constants/endpoints"; -import ellipsize from "ellipsize"; - -interface IProps { - comicBookCoversMetadata?: IExtractedComicBookCoverFile; - mongoObjId?: number; - hasTitle: boolean; - title?: string; - isHorizontal: boolean; -} -interface IState {} - -class Card extends React.Component { - constructor(props: IProps) { - super(props); - } - - public drawCoverCard = ( - metadata: IExtractedComicBookCoverFile, - ): JSX.Element => { - const encodedFilePath = encodeURI( - `${LIBRARY_SERVICE_HOST}` + removeLeadingPeriod(metadata.path), - ); - const filePath = escapePoundSymbol(encodedFilePath); - return ( -
-
-
-
-
- Placeholder image -
-
- {this.props.hasTitle && ( -
-
    - -
  • - {ellipsize(metadata.name, 18)} -
  • - -
-
- )} -
-
-
- ); - }; - - public render() { - return ( - <> - {!isUndefined(this.props.comicBookCoversMetadata) && - !isEmpty(this.props.comicBookCoversMetadata) && - this.drawCoverCard(this.props.comicBookCoversMetadata)} - - ); - } -} - -export default Card; diff --git a/src/client/components/shared/DatePicker.tsx b/src/client/components/shared/DatePicker.tsx index 221bd6a..4a94d92 100644 --- a/src/client/components/shared/DatePicker.tsx +++ b/src/client/components/shared/DatePicker.tsx @@ -1,71 +1,42 @@ -import React, { ChangeEventHandler, useRef, useState } from "react"; - -import { format, isValid, parse, parseISO } from "date-fns"; +import React, { useRef, useState } from "react"; +import { format } from "date-fns"; import FocusTrap from "focus-trap-react"; -import { DayPicker, SelectSingleEventHandler } from "react-day-picker"; -import { usePopper } from "react-popper"; +import { ClassNames, DayPicker } from "react-day-picker"; +import { useFloating, offset, flip, autoUpdate } from "@floating-ui/react-dom"; +import styles from "react-day-picker/dist/style.module.css"; export const DatePickerDialog = (props) => { const { setter, apiAction } = props; const [selected, setSelected] = useState(); const [isPopperOpen, setIsPopperOpen] = useState(false); - const popperRef = useRef(null); - const buttonRef = useRef(null); - const [popperElement, setPopperElement] = useState( - null, - ); - - const customStyles = { - container: { - // Style for the entire container - border: "1px solid #ccc", - borderRadius: "4px", - padding: "10px", - width: "300px", - }, - day: { - // Style for individual days - - padding: "5px", - margin: "2px", - }, - selected: { - // Style for selected days - backgroundColor: "#007bff", - color: "#fff", - }, - disabled: { - // Style for disabled days - color: "#ccc", - }, - today: { - // Style for today's date - backgroundColor: "#f0f0f0", - }, - dayWrapper: { - // Style for the wrapper around each day - display: "inline-block", - }, + const classNames: ClassNames = { + ...styles, + head: "custom-head", }; - - const popper = usePopper(popperRef.current, popperElement, { - placement: "bottom-start", + const buttonRef = useRef(null); + const { x, y, reference, floating, strategy, refs, update } = useFloating({ + placement: "bottom-end", + middleware: [offset(10), flip()], + strategy: "absolute", }); const closePopper = () => { setIsPopperOpen(false); - buttonRef?.current?.focus(); + buttonRef.current?.focus(); }; const handleButtonClick = () => { setIsPopperOpen(true); + if (refs.reference.current && refs.floating.current) { + autoUpdate(refs.reference.current, refs.floating.current, update); + } }; - const handleDaySelect: SelectSingleEventHandler = (date) => { + const handleDaySelect = (date) => { setSelected(date); if (date) { - setter(format(date, "M-dd-yyyy")); + setter(format(date, "yyyy-MM-dd")); apiAction(); closePopper(); } else { @@ -75,17 +46,14 @@ export const DatePickerDialog = (props) => { return (
-
+
@@ -101,11 +69,14 @@ export const DatePickerDialog = (props) => { }} >
@@ -115,7 +86,7 @@ export const DatePickerDialog = (props) => { defaultMonth={selected} selected={selected} onSelect={handleDaySelect} - styles={customStyles} + classNames={classNames} />
diff --git a/src/client/components/shared/Navbar.tsx b/src/client/components/shared/Navbar.tsx deleted file mode 100644 index 1fdeeed..0000000 --- a/src/client/components/shared/Navbar.tsx +++ /dev/null @@ -1,312 +0,0 @@ -import React from "react"; -import { SearchBar } from "../GlobalSearchBar/SearchBar"; -import { DownloadProgressTick } from "../ComicDetail/DownloadProgressTick"; -import { Link } from "react-router-dom"; -import { isEmpty, isNil, isUndefined } from "lodash"; -import { format, fromUnixTime } from "date-fns"; -import { useStore } from "../../store/index"; -import { useShallow } from "zustand/react/shallow"; - -const Navbar: React.FunctionComponent = (props) => { - const { - airDCPPSocketConnected, - airDCPPDisconnectionInfo, - airDCPPSessionInformation, - airDCPPDownloadTick, - importJobQueue, - } = useStore( - useShallow((state) => ({ - airDCPPSocketConnected: state.airDCPPSocketConnected, - airDCPPDisconnectionInfo: state.airDCPPDisconnectionInfo, - airDCPPSessionInformation: state.airDCPPSessionInformation, - airDCPPDownloadTick: state.airDCPPDownloadTick, - importJobQueue: state.importJobQueue, - })), - ); - // const downloadProgressTick = useSelector( - // (state: RootState) => state.airdcpp.downloadProgressData, - // ); - // - // const airDCPPSocketConnectionStatus = useSelector( - // (state: RootState) => state.airdcpp.isAirDCPPSocketConnected, - // ); - // const airDCPPSessionInfo = useSelector( - // (state: RootState) => state.airdcpp.airDCPPSessionInfo, - // ); - // const socketDisconnectionReason = useSelector( - // (state: RootState) => state.airdcpp.socketDisconnectionReason, - // ); - - return ( - - ); -}; - -export default Navbar; diff --git a/src/client/components/shared/Navbar2.tsx b/src/client/components/shared/Navbar2.tsx index 6b6e2e3..09af1f8 100644 --- a/src/client/components/shared/Navbar2.tsx +++ b/src/client/components/shared/Navbar2.tsx @@ -98,7 +98,7 @@ export const Navbar2 = (): ReactElement => {
  • {/* Light/Dark Mode toggle */}
    - Light + Dark - Dark + Light
  • diff --git a/src/client/components/shared/PopoverButton.tsx b/src/client/components/shared/PopoverButton.tsx new file mode 100644 index 0000000..1649ad2 --- /dev/null +++ b/src/client/components/shared/PopoverButton.tsx @@ -0,0 +1,45 @@ +import React, { useState } from "react"; +import { useFloating, offset, flip } from "@floating-ui/react-dom"; +import { useTranslation } from "react-i18next"; +import "../../shared/utils/i18n.util"; // Ensure you import your i18n configuration + +const PopoverButton = ({ content, clickHandler }) => { + const [isVisible, setIsVisible] = useState(false); + // Use destructuring to obtain the reference and floating setters, among other values. + const { x, y, refs, strategy, floatingStyles } = useFloating({ + placement: "right", + middleware: [offset(8), flip()], + strategy: "absolute", + }); + const { t } = useTranslation(); + return ( +
    + {/* Apply the reference setter directly to the ref prop */} + + {isVisible && ( +
    + {content} +
    + )} +
    + ); +}; + +export default PopoverButton; diff --git a/src/client/constants/endpoints.ts b/src/client/constants/endpoints.ts index 2fc2125..4bf983b 100644 --- a/src/client/constants/endpoints.ts +++ b/src/client/constants/endpoints.ts @@ -104,3 +104,10 @@ export const TORRENT_JOB_SERVICE_BASE_URI = hostURIBuilder({ port: "3000", apiPath: `/api/torrentjobs`, }); + +export const AIRDCPP_SERVICE_BASE_URI = hostURIBuilder({ + protocol: "http", + host: import.meta.env.UNDERLYING_HOSTNAME || "localhost", + port: "3000", + apiPath: `/api/airdcpp`, +}); diff --git a/src/client/context/SocketIOContext.tsx b/src/client/context/SocketIOContext.tsx deleted file mode 100644 index a75f521..0000000 --- a/src/client/context/SocketIOContext.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React, { createContext } from "react"; - -export const SocketIOContext = createContext({}); - -export const SocketIOProvider = ({ children, socket }) => { - return ( - - {children} - - ); -}; diff --git a/src/client/hooks/useBlazeSlider.tsx b/src/client/hooks/useBlazeSlider.tsx deleted file mode 100644 index d891ccb..0000000 --- a/src/client/hooks/useBlazeSlider.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React, { useEffect } from "react"; -import BlazeSlider from "blaze-slider"; - -export const useBlazeSlider = (config) => { - const sliderRef = React.useRef(); - const elRef = React.useRef(); - - useEffect(() => { - // if not already initialized - if (!sliderRef.current) { - sliderRef.current = new BlazeSlider(elRef.current, config); - } - }, []); - - return elRef; -}; diff --git a/src/client/index.tsx b/src/client/index.tsx index 7c4c686..c4fd879 100644 --- a/src/client/index.tsx +++ b/src/client/index.tsx @@ -7,6 +7,7 @@ import Settings from "./components/Settings/Settings"; import { ErrorPage } from "./components/shared/ErrorPage"; const rootEl = document.getElementById("root"); const root = createRoot(rootEl); +import i18n from "./shared/utils/i18n.util"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import Import from "./components/Import/Import"; import Dashboard from "./components/Dashboard/Dashboard"; @@ -14,6 +15,7 @@ import Search from "./components/Search/Search"; import TabulatedContentContainer from "./components/Library/TabulatedContentContainer"; import { ComicDetailContainer } from "./components/ComicDetail/ComicDetailContainer"; import Volumes from "./components/Volumes/Volumes"; +import VolumeDetails from "./components/VolumeDetail/VolumeDetail"; import WantedComics from "./components/WantedComics/WantedComics"; const queryClient = new QueryClient(); @@ -37,6 +39,7 @@ const router = createBrowserRouter([ }, { path: "import", element: }, { path: "search", element: }, + { path: "volume/details/:comicObjectId", element: }, { path: "volumes", element: }, { path: "wanted", element: }, ], diff --git a/src/client/locales/en/translation.json b/src/client/locales/en/translation.json new file mode 100644 index 0000000..28dee31 --- /dev/null +++ b/src/client/locales/en/translation.json @@ -0,0 +1,4 @@ +{ + "issueWithCount_one": "{{count}} issue", + "issueWithCount_other": "{{count}} issues" +} diff --git a/src/client/reducers/airdcpp.reducer.ts b/src/client/reducers/airdcpp.reducer.ts deleted file mode 100644 index 883b442..0000000 --- a/src/client/reducers/airdcpp.reducer.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { - AIRDCPP_SEARCH_IN_PROGRESS, - AIRDCPP_SEARCH_RESULTS_ADDED, - AIRDCPP_SEARCH_RESULTS_UPDATED, - AIRDCPP_HUB_SEARCHES_SENT, - AIRDCPP_RESULT_DOWNLOAD_INITIATED, - AIRDCPP_DOWNLOAD_PROGRESS_TICK, - AIRDCPP_FILE_DOWNLOAD_COMPLETED, - AIRDCPP_BUNDLES_FETCHED, - AIRDCPP_TRANSFERS_FETCHED, - LIBRARY_ISSUE_BUNDLES, - AIRDCPP_SOCKET_CONNECTED, - AIRDCPP_SOCKET_DISCONNECTED, -} from "../constants/action-types"; -import { LOCATION_CHANGE } from "redux-first-history"; -import { isNil, isUndefined } from "lodash"; -import { difference } from "../shared/utils/object.utils"; - -const initialState = { - searchResults: [], - isAirDCPPSearchInProgress: false, - searchInfo: null, - searchInstance: null, - downloadResult: null, - bundleDBImportResult: null, - downloadFileStatus: {}, - bundles: [], - transfers: [], - isAirDCPPSocketConnected: false, - airDCPPSessionInfo: {}, - socketDisconnectionReason: {}, -}; - -function airdcppReducer(state = initialState, action) { - switch (action.type) { - case AIRDCPP_SEARCH_RESULTS_ADDED: - return { - ...state, - searchResults: [...state.searchResults, action.groupedResult], - isAirDCPPSearchInProgress: true, - }; - case AIRDCPP_SEARCH_RESULTS_UPDATED: - const bundleToUpdateIndex = state.searchResults.findIndex( - (bundle) => bundle.result.id === action.groupedResult.result.id, - ); - const updatedState = [...state.searchResults]; - - if ( - !isNil( - difference(updatedState[bundleToUpdateIndex], action.groupedResult), - ) - ) { - updatedState[bundleToUpdateIndex] = action.groupedResult; - } - - return { - ...state, - searchResults: updatedState, - }; - case AIRDCPP_SEARCH_IN_PROGRESS: - return { - ...state, - isAirDCPPSearchInProgress: true, - }; - case AIRDCPP_HUB_SEARCHES_SENT: - return { - ...state, - isAirDCPPSearchInProgress: false, - searchInfo: action.searchInfo, - searchInstance: action.instance, - }; - case AIRDCPP_RESULT_DOWNLOAD_INITIATED: - return { - ...state, - downloadResult: action.downloadResult, - bundleDBImportResult: action.bundleDBImportResult, - }; - case AIRDCPP_DOWNLOAD_PROGRESS_TICK: - return { - ...state, - downloadProgressData: action.downloadProgressData, - }; - case AIRDCPP_BUNDLES_FETCHED: - return { - ...state, - bundles: action.bundles, - }; - case LIBRARY_ISSUE_BUNDLES: - return { - ...state, - issue_bundles: action.issue_bundles, - }; - case AIRDCPP_FILE_DOWNLOAD_COMPLETED: - console.log("COMPLETED", action); - return { - ...state, - }; - case AIRDCPP_TRANSFERS_FETCHED: - return { - ...state, - transfers: action.bundles, - }; - - case AIRDCPP_SOCKET_CONNECTED: - return { - ...state, - isAirDCPPSocketConnected: true, - airDCPPSessionInfo: action.data, - }; - - case AIRDCPP_SOCKET_DISCONNECTED: - return { - ...state, - isAirDCPPSocketConnected: false, - socketDisconnectionReason: action.data, - }; - case LOCATION_CHANGE: - return { - ...state, - searchResults: [], - isAirDCPPSearchInProgress: false, - searchInfo: null, - searchInstance: null, - downloadResult: null, - bundleDBImportResult: null, - // bundles: [], - }; - default: - return state; - } -} - -export default airdcppReducer; diff --git a/src/client/reducers/comicinfo.reducer.ts b/src/client/reducers/comicinfo.reducer.ts deleted file mode 100644 index c3d8809..0000000 --- a/src/client/reducers/comicinfo.reducer.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { isEmpty } from "lodash"; -import { - CV_API_CALL_IN_PROGRESS, - CV_SEARCH_SUCCESS, - CV_CLEANUP, - IMS_COMIC_BOOK_DB_OBJECT_FETCHED, - IMS_COMIC_BOOKS_DB_OBJECTS_FETCHED, - IMS_COMIC_BOOK_DB_OBJECT_CALL_IN_PROGRESS, - CV_ISSUES_METADATA_CALL_IN_PROGRESS, - CV_ISSUES_MATCHES_IN_LIBRARY_FETCHED, - CV_ISSUES_FOR_VOLUME_IN_LIBRARY_SUCCESS, - CV_WEEKLY_PULLLIST_FETCHED, - CV_WEEKLY_PULLLIST_CALL_IN_PROGRESS, - LIBRARY_STATISTICS_CALL_IN_PROGRESS, - LIBRARY_STATISTICS_FETCHED, -} from "../constants/action-types"; - -const initialState = { - pullList: [], - libraryStatistics: [], - searchResults: [], - searchQuery: {}, - inProgress: false, - comicBookDetail: {}, - comicBooksDetails: [], - issuesForVolume: [], - IMS_inProgress: false, -}; - -function comicinfoReducer(state = initialState, action) { - switch (action.type) { - case CV_API_CALL_IN_PROGRESS: - return { - ...state, - inProgress: true, - }; - case CV_SEARCH_SUCCESS: - return { - ...state, - searchResults: action.searchResults, - searchQuery: action.searchQueryObject, - inProgress: false, - }; - case IMS_COMIC_BOOK_DB_OBJECT_CALL_IN_PROGRESS: - return { - ...state, - IMS_inProgress: true, - }; - - case IMS_COMIC_BOOK_DB_OBJECT_FETCHED: - return { - ...state, - comicBookDetail: action.comicBookDetail, - IMS_inProgress: false, - }; - case IMS_COMIC_BOOKS_DB_OBJECTS_FETCHED: - return { - ...state, - comicBooksDetails: action.comicBooks, - IMS_inProgress: false, - }; - case CV_CLEANUP: - return { - ...state, - searchResults: [], - searchQuery: {}, - issuesForVolume: [], - }; - case CV_ISSUES_METADATA_CALL_IN_PROGRESS: - return { - inProgress: true, - ...state, - }; - - case CV_ISSUES_FOR_VOLUME_IN_LIBRARY_SUCCESS: - return { - ...state, - issuesForVolume: action.issues, - inProgress: false, - }; - - case CV_ISSUES_MATCHES_IN_LIBRARY_FETCHED: - const updatedState = [...state.issuesForVolume]; - action.matches.map((match) => { - updatedState.map((issue, idx) => { - const matches = []; - if (!isEmpty(match.hits.hits)) { - return match.hits.hits.map((hit) => { - if ( - parseInt(issue.issue_number, 10) === - hit._source.inferredMetadata.issue.number - ) { - matches.push(hit); - const updatedIssueResult = { ...issue, matches }; - updatedState[idx] = updatedIssueResult; - } - }); - } - }); - }); - return { - ...state, - issuesForVolume: updatedState, - }; - - case CV_WEEKLY_PULLLIST_CALL_IN_PROGRESS: - return { - inProgress: true, - ...state, - }; - case CV_WEEKLY_PULLLIST_FETCHED: { - const foo = []; - action.data.map((item) => { - foo.push({issue: item}) - }); - return { - ...state, - inProgress: false, - pullList: foo, - }; - } - case LIBRARY_STATISTICS_CALL_IN_PROGRESS: - return { - inProgress: true, - ...state, - }; - case LIBRARY_STATISTICS_FETCHED: - return { - ...state, - inProgress: false, - libraryStatistics: action.data, - }; - default: - return state; - } -} - -export default comicinfoReducer; diff --git a/src/client/reducers/fileops.reducer.ts b/src/client/reducers/fileops.reducer.ts deleted file mode 100644 index 6c8a17d..0000000 --- a/src/client/reducers/fileops.reducer.ts +++ /dev/null @@ -1,334 +0,0 @@ -import { isUndefined, map } from "lodash"; -import { LOCATION_CHANGE } from "redux-first-history"; -import { determineCoverFile } from "../shared/utils/metadata.utils"; -import { - IMS_COMICBOOK_METADATA_FETCHED, - IMS_RAW_IMPORT_SUCCESSFUL, - IMS_RAW_IMPORT_FAILED, - IMS_RECENT_COMICS_FETCHED, - IMS_WANTED_COMICS_FETCHED, - WANTED_COMICS_FETCHED, - IMS_CV_METADATA_IMPORT_SUCCESSFUL, - IMS_CV_METADATA_IMPORT_FAILED, - IMS_CV_METADATA_IMPORT_CALL_IN_PROGRESS, - IMS_COMIC_BOOK_GROUPS_CALL_IN_PROGRESS, - IMS_COMIC_BOOK_GROUPS_FETCHED, - IMS_COMIC_BOOK_GROUPS_CALL_FAILED, - IMS_COMIC_BOOK_ARCHIVE_EXTRACTION_CALL_IN_PROGRESS, - LS_IMPORT, - LS_COVER_EXTRACTED, - LS_COVER_EXTRACTION_FAILED, - LS_COMIC_ADDED, - IMG_ANALYSIS_CALL_IN_PROGRESS, - IMG_ANALYSIS_DATA_FETCH_SUCCESS, - SS_SEARCH_RESULTS_FETCHED, - SS_SEARCH_IN_PROGRESS, - FILEOPS_STATE_RESET, - LS_IMPORT_CALL_IN_PROGRESS, - SS_SEARCH_FAILED, - SS_SEARCH_RESULTS_FETCHED_SPECIAL, - VOLUMES_FETCHED, - COMICBOOK_EXTRACTION_SUCCESS, - LIBRARY_SERVICE_HEALTH, - LS_IMPORT_QUEUE_DRAINED, - LS_SET_QUEUE_STATUS, - RESTORE_JOB_COUNTS_AFTER_SESSION_RESTORATION, - LS_IMPORT_JOB_STATISTICS_FETCHED, -} from "../constants/action-types"; -import { removeLeadingPeriod } from "../shared/utils/formatting.utils"; -import { LIBRARY_SERVICE_HOST } from "../constants/endpoints"; - -const initialState = { - IMSCallInProgress: false, - IMGCallInProgress: false, - SSCallInProgress: false, - imageAnalysisResults: {}, - comicBookExtractionInProgress: false, - LSQueueImportStatus: undefined, - comicBookMetadata: [], - comicVolumeGroups: [], - isSocketConnected: false, - isComicVineMetadataImportInProgress: false, - comicVineMetadataImportError: {}, - rawImportError: {}, - extractedComicBookArchive: { - reading: [], - analysis: [], - }, - recentComics: [], - wantedComics: [], - libraryComics: [], - volumes: [], - librarySearchResultsFormatted: [], - lastQueueJob: "", - successfulJobCount: 0, - failedJobCount: 0, - importJobStatistics: [], - libraryQueueResults: [], - librarySearchError: {}, - libraryServiceStatus: {}, -}; -function fileOpsReducer(state = initialState, action) { - switch (action.type) { - case IMS_COMICBOOK_METADATA_FETCHED: - return { - ...state, - comicBookMetadata: [...state.comicBookMetadata, action.data], - IMSCallInProgress: false, - }; - - case LS_IMPORT_CALL_IN_PROGRESS: { - return { - ...state, - IMSCallInProgress: true, - }; - } - case IMS_RAW_IMPORT_SUCCESSFUL: - return { - ...state, - rawImportDetails: action.rawImportDetails, - }; - case IMS_RAW_IMPORT_FAILED: - return { - ...state, - rawImportErorr: action.rawImportError, - }; - case IMS_RECENT_COMICS_FETCHED: - return { - ...state, - recentComics: action.data.docs, - }; - case IMS_WANTED_COMICS_FETCHED: - return { - ...state, - wantedComics: action.data, - }; - case IMS_CV_METADATA_IMPORT_SUCCESSFUL: - return { - ...state, - isComicVineMetadataImportInProgress: false, - comicVineMetadataImportDetails: action.importResult, - }; - case IMS_CV_METADATA_IMPORT_CALL_IN_PROGRESS: - return { - ...state, - isComicVineMetadataImportInProgress: true, - }; - case IMS_CV_METADATA_IMPORT_FAILED: - return { - ...state, - isComicVineMetadataImportInProgress: false, - comicVineMetadataImportError: action.importError, - }; - case IMS_COMIC_BOOK_GROUPS_CALL_IN_PROGRESS: { - return { - ...state, - IMSCallInProgress: true, - }; - } - case IMS_COMIC_BOOK_GROUPS_FETCHED: { - return { - ...state, - comicVolumeGroups: action.data, - IMSCallInProgress: false, - }; - } - case IMS_COMIC_BOOK_GROUPS_CALL_FAILED: { - return { - ...state, - IMSCallInProgress: false, - error: action.error, - }; - } - case IMS_COMIC_BOOK_ARCHIVE_EXTRACTION_CALL_IN_PROGRESS: { - return { - ...state, - comicBookExtractionInProgress: true, - }; - } - - case LOCATION_CHANGE: { - return { - ...state, - extractedComicBookArchive: [], - }; - } - - case LS_IMPORT: { - return { - ...state, - LSQueueImportStatus: "running", - }; - } - - case LS_COVER_EXTRACTED: { - if (state.recentComics.length === 5) { - state.recentComics.pop(); - } - return { - ...state, - successfulJobCount: action.completedJobCount, - lastQueueJob: action.importResult.rawFileDetails.name, - recentComics: [...state.recentComics, action.importResult], - }; - } - - case LS_COVER_EXTRACTION_FAILED: { - return { - ...state, - failedJobCount: action.failedJobCount, - }; - } - - case LS_IMPORT_QUEUE_DRAINED: { - localStorage.removeItem("sessionId"); - return { - ...state, - LSQueueImportStatus: "drained", - }; - } - - case RESTORE_JOB_COUNTS_AFTER_SESSION_RESTORATION: { - console.log("Restoring state for an active import in progress..."); - return { - ...state, - successfulJobCount: action.completedJobCount, - failedJobCount: action.failedJobCount, - LSQueueImportStatus: action.queueStatus, - }; - } - - case LS_SET_QUEUE_STATUS: { - return { - ...state, - LSQueueImportStatus: action.data.queueStatus, - }; - } - - case LS_IMPORT_JOB_STATISTICS_FETCHED: { - return { - ...state, - importJobStatistics: action.data, - }; - } - - case COMICBOOK_EXTRACTION_SUCCESS: { - const comicBookPages: string[] = []; - map(action.result.files, (page) => { - const pageFilePath = removeLeadingPeriod(page); - const imagePath = encodeURI(`${LIBRARY_SERVICE_HOST}${pageFilePath}`); - comicBookPages.push(imagePath); - }); - - switch (action.result.purpose) { - case "reading": - return { - ...state, - extractedComicBookArchive: { - reading: comicBookPages, - }, - comicBookExtractionInProgress: false, - }; - - case "analysis": - return { - ...state, - extractedComicBookArchive: { - analysis: comicBookPages, - }, - comicBookExtractionInProgress: false, - }; - } - } - - case LS_COMIC_ADDED: { - return { - ...state, - }; - } - case IMG_ANALYSIS_CALL_IN_PROGRESS: { - return { - ...state, - IMGCallInProgress: true, - }; - } - case IMG_ANALYSIS_DATA_FETCH_SUCCESS: { - return { - ...state, - imageAnalysisResults: action.result, - }; - } - - case SS_SEARCH_IN_PROGRESS: { - return { - ...state, - SSCallInProgress: true, - }; - } - - case SS_SEARCH_RESULTS_FETCHED: { - return { - ...state, - libraryComics: action.data, - SSCallInProgress: false, - }; - } - case SS_SEARCH_RESULTS_FETCHED_SPECIAL: { - const foo = []; - if (!isUndefined(action.data.hits)) { - map(action.data.hits, ({ _source }) => { - foo.push(_source); - }); - } - return { - ...state, - librarySearchResultsFormatted: foo, - SSCallInProgress: false, - }; - } - case WANTED_COMICS_FETCHED: { - const foo = []; - if (!isUndefined(action.data.hits)) { - map(action.data.hits, ({ _source }) => { - foo.push(_source); - }); - } - return { - ...state, - wantedComics: foo, - SSCallInProgress: false, - }; - } - - case VOLUMES_FETCHED: - return { - ...state, - volumes: action.data, - SSCallInProgress: false, - }; - - case SS_SEARCH_FAILED: { - return { - ...state, - librarySearchError: action.data, - SSCallInProgress: false, - }; - } - case LIBRARY_SERVICE_HEALTH: { - return { - ...state, - libraryServiceStatus: action.status, - }; - } - case FILEOPS_STATE_RESET: { - return { - ...state, - imageAnalysisResults: {}, - }; - } - default: - return state; - } -} - -export default fileOpsReducer; diff --git a/src/client/reducers/index.js b/src/client/reducers/index.js deleted file mode 100644 index 897e6f0..0000000 --- a/src/client/reducers/index.js +++ /dev/null @@ -1,11 +0,0 @@ -import comicinfoReducer from "../reducers/comicinfo.reducer"; -import fileOpsReducer from "../reducers/fileops.reducer"; -import airdcppReducer from "../reducers/airdcpp.reducer"; -// import settingsReducer from "../reducers/settings.reducer"; - -export const reducers = { - comicInfo: comicinfoReducer, - fileOps: fileOpsReducer, - airdcpp: airdcppReducer, - // settings: settingsReducer, -}; diff --git a/src/client/reducers/settings.reducer.ts b/src/client/reducers/settings.reducer.ts deleted file mode 100644 index c04ceb0..0000000 --- a/src/client/reducers/settings.reducer.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { createSlice, PayloadAction } from "@reduxjs/toolkit"; -import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; -import { RootState } from "../store"; -import { isUndefined } from "lodash"; -import { SETTINGS_SERVICE_BASE_URI } from "../constants/endpoints"; - -export interface InitialState { - data: object; - inProgress: boolean; - dbFlushed: boolean; - torrentsList: Array; -} -const initialState: InitialState = { - data: {}, - inProgress: false, - dbFlushed: false, - torrentsList: [], -}; - -export const settingsSlice = createSlice({ - name: "settings", - initialState, - reducers: { - SETTINGS_CALL_IN_PROGRESS: (state) => { - state.inProgress = true; - }, - - SETTINGS_OBJECT_FETCHED: (state, action) => { - state.data = action.payload; - state.inProgress = false; - }, - - SETTINGS_OBJECT_DELETED: (state, action) => { - state.data = action.payload; - state.inProgress = false; - }, - - SETTINGS_DB_FLUSH_SUCCESS: (state, action) => { - state.dbFlushed = action.payload; - state.inProgress = false; - }, - - SETTINGS_QBITTORRENT_TORRENTS_LIST_FETCHED: (state, action) => { - console.log(state); - console.log(action); - state.torrentsList = action.payload; - }, - }, -}); - -export const { - SETTINGS_CALL_IN_PROGRESS, - SETTINGS_OBJECT_FETCHED, - SETTINGS_OBJECT_DELETED, - SETTINGS_DB_FLUSH_SUCCESS, - SETTINGS_QBITTORRENT_TORRENTS_LIST_FETCHED, -} = settingsSlice.actions; - -// Other code such as selectors can use the imported `RootState` type -export const torrentsList = (state: RootState) => state.settings.torrentsList; -export const qBittorrentSettings = (state: RootState) => { - console.log(state); - if (!isUndefined(state.settings?.data?.bittorrent)) { - return state.settings?.data?.bittorrent.client.host; - } -}; -export default settingsSlice.reducer; diff --git a/src/client/shared/middleware/SocketIOMiddleware.tsx b/src/client/shared/middleware/SocketIOMiddleware.tsx deleted file mode 100644 index b6e1e39..0000000 --- a/src/client/shared/middleware/SocketIOMiddleware.tsx +++ /dev/null @@ -1,11 +0,0 @@ -const socketIOMiddleware = (socket) => { - return (store) => (next) => (action) => { - if (action.type === "EMIT_SOCKET_EVENT") { - const { event, data } = action.payload; - socket.emit(event, data); - } - return next(action); - }; -}; - -export default socketIOMiddleware; diff --git a/src/client/shared/socket.io/instance.tsx b/src/client/shared/socket.io/instance.tsx deleted file mode 100644 index deb6514..0000000 --- a/src/client/shared/socket.io/instance.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import io from "socket.io-client"; -import { SOCKET_BASE_URI } from "../../constants/endpoints"; -const sessionId = localStorage.getItem("sessionId"); -const socketIOConnectionInstance = io(SOCKET_BASE_URI, { - transports: ["websocket"], - withCredentials: true, - query: { sessionId }, -}); - -export default socketIOConnectionInstance; diff --git a/src/client/shared/utils/i18n.util.ts b/src/client/shared/utils/i18n.util.ts new file mode 100644 index 0000000..f08e4a9 --- /dev/null +++ b/src/client/shared/utils/i18n.util.ts @@ -0,0 +1,25 @@ +// i18n.js +import i18n from "i18next"; +import { initReactI18next } from "react-i18next"; +import HttpBackend from "i18next-http-backend"; +import LanguageDetector from "i18next-browser-languagedetector"; + +i18n + // Learn more about options: https://www.i18next.com/overview/configuration-options + .use(HttpBackend) // Load translations over http + .use(LanguageDetector) // Detect language automatically + .use(initReactI18next) // Pass i18n instance to react-i18next + .init({ + lng: "en", // Specify the language + fallbackLng: "en", + debug: true, + interpolation: { + escapeValue: false, // Not needed for React + }, + backend: { + // path where resources get loaded from + loadPath: "./src/client/locales/en/translation.json", + }, + }); + +export default i18n; diff --git a/src/client/shared/utils/metadata.utils.ts b/src/client/shared/utils/metadata.utils.ts index f26f724..a261ab5 100644 --- a/src/client/shared/utils/metadata.utils.ts +++ b/src/client/shared/utils/metadata.utils.ts @@ -1,11 +1,12 @@ -import { filter, isEmpty, isUndefined, min, minBy } from "lodash"; +import { filter, isEmpty, isNil, isUndefined, min, minBy } from "lodash"; import { LIBRARY_SERVICE_HOST } from "../../constants/endpoints"; import { escapePoundSymbol } from "./formatting.utils"; -export const determineCoverFile = (data) => { +export const determineCoverFile = (data): any => { /* For a payload like this: const foo = { rawFileDetails: {}, // #1 + wanted: {}, comicInfo: {}, comicvine: {}, // #2 locg: {}, // #3 @@ -19,36 +20,44 @@ export const determineCoverFile = (data) => { issueName: "", publisher: "", }, + wanted: { + objectReference: "wanted", + priority: 2, + url: "", + issueName: "", + publisher: "", + }, comicvine: { objectReference: "comicvine", - priority: 2, + priority: 3, url: "", issueName: "", publisher: "", }, locg: { objectReference: "locg", - priority: 3, + priority: 4, url: "", issueName: "", publisher: "", }, }; - if ( - !isUndefined(data.comicvine) && - !isUndefined(data.comicvine.volumeInformation) - ) { - coverFile.comicvine.url = data.comicvine.image.small_url; + // comicvine + if (!isEmpty(data.comicvine)) { + coverFile.comicvine.url = data?.comicvine?.image.small_url; coverFile.comicvine.issueName = data.comicvine.name; - coverFile.comicvine.publisher = data.comicvine.volumeInformation.publisher; + coverFile.comicvine.publisher = data.comicvine.publisher.name; } - if (!isEmpty(data.rawFileDetails.cover)) { + // rawFileDetails + if (!isEmpty(data.rawFileDetails)) { const encodedFilePath = encodeURI( `${LIBRARY_SERVICE_HOST}/${data.rawFileDetails.cover.filePath}`, ); coverFile.rawFile.url = escapePoundSymbol(encodedFilePath); coverFile.rawFile.issueName = data.rawFileDetails.name; } + // wanted + if (!isUndefined(data.locg)) { coverFile.locg.url = data.locg.cover; coverFile.locg.issueName = data.locg.name; @@ -69,25 +78,30 @@ export const determineCoverFile = (data) => { export const determineExternalMetadata = ( metadataSource: string, - source: any -) => { - switch (metadataSource) { - case "comicvine": - return { - coverURL: source.comicvine.image.small_url, - issue: source.comicvine.name, - icon: "cvlogo.svg", - }; - case "locg": - return { - coverURL: source.locg.cover, - issue: source.locg.name, - icon: "locglogo.svg", - }; - case undefined: - return {}; + source: any, +): any => { + if (!isNil(source)) { + switch (metadataSource) { + case "comicvine": + return { + coverURL: + source.comicvine?.image.small_url || + source.comicvine.volumeInformation?.image.small_url, + issue: source.comicvine.name, + icon: "cvlogo.svg", + }; + case "locg": + return { + coverURL: source.locg.cover, + issue: source.locg.name, + icon: "locglogo.svg", + }; + case undefined: + return {}; - default: - break; + default: + break; + } } -}; \ No newline at end of file + return null; +}; diff --git a/src/client/shared/utils/tradepaperback.utils.ts b/src/client/shared/utils/tradepaperback.utils.ts index 1dd8b4a..729bfc0 100644 --- a/src/client/shared/utils/tradepaperback.utils.ts +++ b/src/client/shared/utils/tradepaperback.utils.ts @@ -16,6 +16,7 @@ export const detectIssueTypes = (deck: string): any => { const matches = map(issueTypeMatchers, (matcher) => { return getIssueTypeDisplayName(deck, matcher.regex, matcher.displayName); }); + return compact(matches)[0]; }; diff --git a/src/client/store/index.ts b/src/client/store/index.ts index d5eb125..f7e6f0d 100644 --- a/src/client/store/index.ts +++ b/src/client/store/index.ts @@ -3,31 +3,15 @@ import { isNil } 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"; import { QueryClient } from "@tanstack/react-query"; /* 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: {}, - airDCPPSessionInformation: {}, - setAirDCPPSocketConnectionStatus: () => - set((value) => ({ - airDCPPSocketConnected: value, - })), - airDCPPDownloadTick: {}, - airDCPPTransfers: {}, // Socket.io state socketIOInstance: {}, - // ComicVine Scraping status comicvine: { scrapingStatus: "", @@ -126,11 +110,12 @@ socketIOInstance.on("RESTORE_JOB_COUNTS_AFTER_SESSION_RESTORATION", (data) => { // by the LS_COVER_EXTRACTED/LS_COVER_EXTRACTION_FAILED events socketIOInstance.on("LS_COVER_EXTRACTED", (data) => { const { completedJobCount, importResult } = data; + console.log(importResult); setState((state) => ({ importJobQueue: { ...state.importJobQueue, successfulJobCount: completedJobCount, - mostRecentImport: importResult.rawFileDetails.name, + mostRecentImport: importResult.data.rawFileDetails.name, }, })); }); @@ -154,7 +139,6 @@ socketIOInstance.on("LS_IMPORT_QUEUE_DRAINED", (data) => { status: "drained", }, })); - console.log("a", queryClient); queryClient.invalidateQueries({ queryKey: ["allImportJobResults"] }); }); @@ -167,105 +151,3 @@ socketIOInstance.on("CV_SCRAPING_STATUS", (data) => { }, })); }); - -/** - * 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 => { - try { - console.log("[AirDCPP]: Initializing socket..."); - - 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 -const { data } = await axios({ - url: "http://localhost:3000/api/settings/getAllSettings", - method: "GET", -}); - -const directConnectConfiguration = data?.directConnect?.client.host; - -// 2. If available, init AirDC++ Socket with those settings -if (!isNil(directConnectConfiguration)) { - const airDCPPSocketInstance = await initializeAirDCPPSocket( - directConnectConfiguration, - ); - setState({ - airDCPPSocketInstance, - airDCPPClientConfiguration: directConnectConfiguration, - }); -} else { - console.log("problem"); -} diff --git a/yarn.lock b/yarn.lock index 5cf1e64..1a76a23 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1024,6 +1024,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.23.9": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.4.tgz#de795accd698007a66ba44add6cc86542aff1edd" + integrity sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.22.15", "@babel/template@^7.23.9", "@babel/template@^7.3.3": version "7.23.9" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.23.9.tgz#f881d0487cba2828d3259dcb9ef5005a9731011a" @@ -1247,230 +1254,230 @@ esquery "^1.5.0" jsdoc-type-pratt-parser "~4.0.0" -"@esbuild/aix-ppc64@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz#d1bc06aedb6936b3b6d313bf809a5a40387d2b7f" - integrity sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA== +"@esbuild/aix-ppc64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz#a70f4ac11c6a1dfc18b8bbb13284155d933b9537" + integrity sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g== "@esbuild/android-arm64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz#984b4f9c8d0377443cc2dfcef266d02244593622" integrity sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ== -"@esbuild/android-arm64@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz#7ad65a36cfdb7e0d429c353e00f680d737c2aed4" - integrity sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA== +"@esbuild/android-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz#db1c9202a5bc92ea04c7b6840f1bbe09ebf9e6b9" + integrity sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg== "@esbuild/android-arm@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.18.20.tgz#fedb265bc3a589c84cc11f810804f234947c3682" integrity sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw== -"@esbuild/android-arm@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.12.tgz#b0c26536f37776162ca8bde25e42040c203f2824" - integrity sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w== +"@esbuild/android-arm@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.20.2.tgz#3b488c49aee9d491c2c8f98a909b785870d6e995" + integrity sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w== "@esbuild/android-x64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.18.20.tgz#35cf419c4cfc8babe8893d296cd990e9e9f756f2" integrity sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg== -"@esbuild/android-x64@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.12.tgz#cb13e2211282012194d89bf3bfe7721273473b3d" - integrity sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew== +"@esbuild/android-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.20.2.tgz#3b1628029e5576249d2b2d766696e50768449f98" + integrity sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg== "@esbuild/darwin-arm64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz#08172cbeccf95fbc383399a7f39cfbddaeb0d7c1" integrity sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA== -"@esbuild/darwin-arm64@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz#cbee41e988020d4b516e9d9e44dd29200996275e" - integrity sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g== +"@esbuild/darwin-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz#6e8517a045ddd86ae30c6608c8475ebc0c4000bb" + integrity sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA== "@esbuild/darwin-x64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz#d70d5790d8bf475556b67d0f8b7c5bdff053d85d" integrity sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ== -"@esbuild/darwin-x64@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz#e37d9633246d52aecf491ee916ece709f9d5f4cd" - integrity sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A== +"@esbuild/darwin-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz#90ed098e1f9dd8a9381695b207e1cff45540a0d0" + integrity sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA== "@esbuild/freebsd-arm64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz#98755cd12707f93f210e2494d6a4b51b96977f54" integrity sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw== -"@esbuild/freebsd-arm64@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz#1ee4d8b682ed363b08af74d1ea2b2b4dbba76487" - integrity sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA== +"@esbuild/freebsd-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz#d71502d1ee89a1130327e890364666c760a2a911" + integrity sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw== "@esbuild/freebsd-x64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz#c1eb2bff03915f87c29cece4c1a7fa1f423b066e" integrity sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ== -"@esbuild/freebsd-x64@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz#37a693553d42ff77cd7126764b535fb6cc28a11c" - integrity sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg== +"@esbuild/freebsd-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz#aa5ea58d9c1dd9af688b8b6f63ef0d3d60cea53c" + integrity sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw== "@esbuild/linux-arm64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz#bad4238bd8f4fc25b5a021280c770ab5fc3a02a0" integrity sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA== -"@esbuild/linux-arm64@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz#be9b145985ec6c57470e0e051d887b09dddb2d4b" - integrity sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA== +"@esbuild/linux-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz#055b63725df678379b0f6db9d0fa85463755b2e5" + integrity sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A== "@esbuild/linux-arm@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz#3e617c61f33508a27150ee417543c8ab5acc73b0" integrity sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg== -"@esbuild/linux-arm@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz#207ecd982a8db95f7b5279207d0ff2331acf5eef" - integrity sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w== +"@esbuild/linux-arm@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz#76b3b98cb1f87936fbc37f073efabad49dcd889c" + integrity sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg== "@esbuild/linux-ia32@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz#699391cccba9aee6019b7f9892eb99219f1570a7" integrity sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA== -"@esbuild/linux-ia32@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz#d0d86b5ca1562523dc284a6723293a52d5860601" - integrity sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA== +"@esbuild/linux-ia32@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz#c0e5e787c285264e5dfc7a79f04b8b4eefdad7fa" + integrity sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig== "@esbuild/linux-loong64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz#e6fccb7aac178dd2ffb9860465ac89d7f23b977d" integrity sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg== -"@esbuild/linux-loong64@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz#9a37f87fec4b8408e682b528391fa22afd952299" - integrity sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA== +"@esbuild/linux-loong64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz#a6184e62bd7cdc63e0c0448b83801001653219c5" + integrity sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ== "@esbuild/linux-mips64el@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz#eeff3a937de9c2310de30622a957ad1bd9183231" integrity sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ== -"@esbuild/linux-mips64el@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz#4ddebd4e6eeba20b509d8e74c8e30d8ace0b89ec" - integrity sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w== +"@esbuild/linux-mips64el@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz#d08e39ce86f45ef8fc88549d29c62b8acf5649aa" + integrity sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA== "@esbuild/linux-ppc64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz#2f7156bde20b01527993e6881435ad79ba9599fb" integrity sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA== -"@esbuild/linux-ppc64@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz#adb67dadb73656849f63cd522f5ecb351dd8dee8" - integrity sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg== +"@esbuild/linux-ppc64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz#8d252f0b7756ffd6d1cbde5ea67ff8fd20437f20" + integrity sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg== "@esbuild/linux-riscv64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz#6628389f210123d8b4743045af8caa7d4ddfc7a6" integrity sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A== -"@esbuild/linux-riscv64@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz#11bc0698bf0a2abf8727f1c7ace2112612c15adf" - integrity sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg== +"@esbuild/linux-riscv64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz#19f6dcdb14409dae607f66ca1181dd4e9db81300" + integrity sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg== "@esbuild/linux-s390x@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz#255e81fb289b101026131858ab99fba63dcf0071" integrity sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ== -"@esbuild/linux-s390x@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz#e86fb8ffba7c5c92ba91fc3b27ed5a70196c3cc8" - integrity sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg== +"@esbuild/linux-s390x@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz#3c830c90f1a5d7dd1473d5595ea4ebb920988685" + integrity sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ== "@esbuild/linux-x64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz#c7690b3417af318a9b6f96df3031a8865176d338" integrity sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w== -"@esbuild/linux-x64@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz#5f37cfdc705aea687dfe5dfbec086a05acfe9c78" - integrity sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg== +"@esbuild/linux-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz#86eca35203afc0d9de0694c64ec0ab0a378f6fff" + integrity sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw== "@esbuild/netbsd-x64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz#30e8cd8a3dded63975e2df2438ca109601ebe0d1" integrity sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A== -"@esbuild/netbsd-x64@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz#29da566a75324e0d0dd7e47519ba2f7ef168657b" - integrity sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA== +"@esbuild/netbsd-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz#e771c8eb0e0f6e1877ffd4220036b98aed5915e6" + integrity sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ== "@esbuild/openbsd-x64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz#7812af31b205055874c8082ea9cf9ab0da6217ae" integrity sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg== -"@esbuild/openbsd-x64@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz#306c0acbdb5a99c95be98bdd1d47c916e7dc3ff0" - integrity sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw== +"@esbuild/openbsd-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz#9a795ae4b4e37e674f0f4d716f3e226dd7c39baf" + integrity sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ== "@esbuild/sunos-x64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz#d5c275c3b4e73c9b0ecd38d1ca62c020f887ab9d" integrity sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ== -"@esbuild/sunos-x64@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz#0933eaab9af8b9b2c930236f62aae3fc593faf30" - integrity sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA== +"@esbuild/sunos-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz#7df23b61a497b8ac189def6e25a95673caedb03f" + integrity sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w== "@esbuild/win32-arm64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz#73bc7f5a9f8a77805f357fab97f290d0e4820ac9" integrity sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg== -"@esbuild/win32-arm64@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz#773bdbaa1971b36db2f6560088639ccd1e6773ae" - integrity sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A== +"@esbuild/win32-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz#f1ae5abf9ca052ae11c1bc806fb4c0f519bacf90" + integrity sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ== "@esbuild/win32-ia32@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz#ec93cbf0ef1085cc12e71e0d661d20569ff42102" integrity sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g== -"@esbuild/win32-ia32@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz#000516cad06354cc84a73f0943a4aa690ef6fd67" - integrity sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ== +"@esbuild/win32-ia32@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz#241fe62c34d8e8461cd708277813e1d0ba55ce23" + integrity sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ== "@esbuild/win32-x64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz#786c5f41f043b07afb1af37683d7c33668858f6d" integrity sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ== -"@esbuild/win32-x64@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz#c57c8afbb4054a3ab8317591a0b7320360b444ae" - integrity sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA== +"@esbuild/win32-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz#9c907b21e30a52db959ba4f80bb01a0cc403d5cc" + integrity sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ== "@eslint-community/eslint-utils@^4.2.0": version "4.4.0" @@ -1509,7 +1516,7 @@ resolved "https://registry.yarnpkg.com/@fal-works/esbuild-plugin-global-externals/-/esbuild-plugin-global-externals-2.1.2.tgz#c05ed35ad82df8e6ac616c68b92c2282bd083ba4" integrity sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ== -"@floating-ui/core@^1.6.0": +"@floating-ui/core@^1.0.0", "@floating-ui/core@^1.6.0": version "1.6.0" resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.0.tgz#fa41b87812a16bf123122bf945946bae3fdf7fc1" integrity sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g== @@ -1524,6 +1531,14 @@ "@floating-ui/core" "^1.6.0" "@floating-ui/utils" "^0.2.1" +"@floating-ui/dom@^1.6.1": + version "1.6.3" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.3.tgz#954e46c1dd3ad48e49db9ada7218b0985cee75ef" + integrity sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw== + dependencies: + "@floating-ui/core" "^1.0.0" + "@floating-ui/utils" "^0.2.0" + "@floating-ui/react-dom@^2.0.0": version "2.0.7" resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.0.7.tgz#873e0a55a25d8ddbbccd159d6ab4a4b98eb05494" @@ -1531,7 +1546,23 @@ dependencies: "@floating-ui/dom" "^1.6.0" -"@floating-ui/utils@^0.2.1": +"@floating-ui/react-dom@^2.0.8": + version "2.0.8" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.0.8.tgz#afc24f9756d1b433e1fe0d047c24bd4d9cefaa5d" + integrity sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw== + dependencies: + "@floating-ui/dom" "^1.6.1" + +"@floating-ui/react@^0.26.12": + version "0.26.12" + resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.26.12.tgz#6908f774d8e3167d89b37fd83be975c7e5d8be99" + integrity sha512-D09o62HrWdIkstF2kGekIKAC0/N/Dl6wo3CQsnLcOmO3LkW6Ik8uIb3kw8JYkwxNCcg+uJ2bpWUiIijTBep05w== + dependencies: + "@floating-ui/react-dom" "^2.0.0" + "@floating-ui/utils" "^0.2.0" + tabbable "^6.0.0" + +"@floating-ui/utils@^0.2.0", "@floating-ui/utils@^0.2.1": version "0.2.1" resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.1.tgz#16308cea045f0fc777b6ff20a9f25474dd8293d2" integrity sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q== @@ -2276,70 +2307,80 @@ estree-walker "^2.0.2" picomatch "^2.3.1" -"@rollup/rollup-android-arm-eabi@4.9.6": - version "4.9.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.6.tgz#66b8d9cb2b3a474d115500f9ebaf43e2126fe496" - integrity sha512-MVNXSSYN6QXOulbHpLMKYi60ppyO13W9my1qogeiAqtjb2yR4LSmfU2+POvDkLzhjYLXz9Rf9+9a3zFHW1Lecg== +"@rollup/rollup-android-arm-eabi@4.13.2": + version "4.13.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.2.tgz#fbf098f49d96a8cac9056f22f5fd80906ef3af85" + integrity sha512-3XFIDKWMFZrMnao1mJhnOT1h2g0169Os848NhhmGweEcfJ4rCi+3yMCOLG4zA61rbJdkcrM/DjVZm9Hg5p5w7g== -"@rollup/rollup-android-arm64@4.9.6": - version "4.9.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.6.tgz#46327d5b86420d2307946bec1535fdf00356e47d" - integrity sha512-T14aNLpqJ5wzKNf5jEDpv5zgyIqcpn1MlwCrUXLrwoADr2RkWA0vOWP4XxbO9aiO3dvMCQICZdKeDrFl7UMClw== +"@rollup/rollup-android-arm64@4.13.2": + version "4.13.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.2.tgz#0d2448251040fce19a98eee505dff5b3c8ec9b98" + integrity sha512-GdxxXbAuM7Y/YQM9/TwwP+L0omeE/lJAR1J+olu36c3LqqZEBdsIWeQ91KBe6nxwOnb06Xh7JS2U5ooWU5/LgQ== -"@rollup/rollup-darwin-arm64@4.9.6": - version "4.9.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.6.tgz#166987224d2f8b1e2fd28ee90c447d52271d5e90" - integrity sha512-CqNNAyhRkTbo8VVZ5R85X73H3R5NX9ONnKbXuHisGWC0qRbTTxnF1U4V9NafzJbgGM0sHZpdO83pLPzq8uOZFw== +"@rollup/rollup-darwin-arm64@4.13.2": + version "4.13.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.2.tgz#78db4d4da5b1b84c22adbe25c8a4961b3f22d3af" + integrity sha512-mCMlpzlBgOTdaFs83I4XRr8wNPveJiJX1RLfv4hggyIVhfB5mJfN4P8Z6yKh+oE4Luz+qq1P3kVdWrCKcMYrrA== -"@rollup/rollup-darwin-x64@4.9.6": - version "4.9.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.6.tgz#a2e6e096f74ccea6e2f174454c26aef6bcdd1274" - integrity sha512-zRDtdJuRvA1dc9Mp6BWYqAsU5oeLixdfUvkTHuiYOHwqYuQ4YgSmi6+/lPvSsqc/I0Omw3DdICx4Tfacdzmhog== +"@rollup/rollup-darwin-x64@4.13.2": + version "4.13.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.2.tgz#fcc05af54379f8ee5c7e954987d4514c6fd0fb42" + integrity sha512-yUoEvnH0FBef/NbB1u6d3HNGyruAKnN74LrPAfDQL3O32e3k3OSfLrPgSJmgb3PJrBZWfPyt6m4ZhAFa2nZp2A== -"@rollup/rollup-linux-arm-gnueabihf@4.9.6": - version "4.9.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.6.tgz#09fcd4c55a2d6160c5865fec708a8e5287f30515" - integrity sha512-oNk8YXDDnNyG4qlNb6is1ojTOGL/tRhbbKeE/YuccItzerEZT68Z9gHrY3ROh7axDc974+zYAPxK5SH0j/G+QQ== +"@rollup/rollup-linux-arm-gnueabihf@4.13.2": + version "4.13.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.2.tgz#2ce200efa1ef4a56ee2af7b453edc74a259d7d31" + integrity sha512-GYbLs5ErswU/Xs7aGXqzc3RrdEjKdmoCrgzhJWyFL0r5fL3qd1NPcDKDowDnmcoSiGJeU68/Vy+OMUluRxPiLQ== -"@rollup/rollup-linux-arm64-gnu@4.9.6": - version "4.9.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.6.tgz#19a3c0b6315c747ca9acf86e9b710cc2440f83c9" - integrity sha512-Z3O60yxPtuCYobrtzjo0wlmvDdx2qZfeAWTyfOjEDqd08kthDKexLpV97KfAeUXPosENKd8uyJMRDfFMxcYkDQ== +"@rollup/rollup-linux-arm64-gnu@4.13.2": + version "4.13.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.2.tgz#5a24aac882bff9abfda3f45f6f1db2166c342a4a" + integrity sha512-L1+D8/wqGnKQIlh4Zre9i4R4b4noxzH5DDciyahX4oOz62CphY7WDWqJoQ66zNR4oScLNOqQJfNSIAe/6TPUmQ== -"@rollup/rollup-linux-arm64-musl@4.9.6": - version "4.9.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.6.tgz#94aaf95fdaf2ad9335983a4552759f98e6b2e850" - integrity sha512-gpiG0qQJNdYEVad+1iAsGAbgAnZ8j07FapmnIAQgODKcOTjLEWM9sRb+MbQyVsYCnA0Im6M6QIq6ax7liws6eQ== +"@rollup/rollup-linux-arm64-musl@4.13.2": + version "4.13.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.2.tgz#f1fb4c6f961d3f3397231a99e621d199200e4ea9" + integrity sha512-tK5eoKFkXdz6vjfkSTCupUzCo40xueTOiOO6PeEIadlNBkadH1wNOH8ILCPIl8by/Gmb5AGAeQOFeLev7iZDOA== -"@rollup/rollup-linux-riscv64-gnu@4.9.6": - version "4.9.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.6.tgz#160510e63f4b12618af4013bddf1761cf9fc9880" - integrity sha512-+uCOcvVmFUYvVDr27aiyun9WgZk0tXe7ThuzoUTAukZJOwS5MrGbmSlNOhx1j80GdpqbOty05XqSl5w4dQvcOA== +"@rollup/rollup-linux-powerpc64le-gnu@4.13.2": + version "4.13.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.13.2.tgz#46b2463d94ac3af3e0f7a2947b695397bc13b755" + integrity sha512-zvXvAUGGEYi6tYhcDmb9wlOckVbuD+7z3mzInCSTACJ4DQrdSLPNUeDIcAQW39M3q6PDquqLWu7pnO39uSMRzQ== -"@rollup/rollup-linux-x64-gnu@4.9.6": - version "4.9.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.6.tgz#5ac5d068ce0726bd0a96ca260d5bd93721c0cb98" - integrity sha512-HUNqM32dGzfBKuaDUBqFB7tP6VMN74eLZ33Q9Y1TBqRDn+qDonkAUyKWwF9BR9unV7QUzffLnz9GrnKvMqC/fw== +"@rollup/rollup-linux-riscv64-gnu@4.13.2": + version "4.13.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.2.tgz#47b932ee59a5395a3a341b0493e361d9e6032cf2" + integrity sha512-C3GSKvMtdudHCN5HdmAMSRYR2kkhgdOfye4w0xzyii7lebVr4riCgmM6lRiSCnJn2w1Xz7ZZzHKuLrjx5620kw== -"@rollup/rollup-linux-x64-musl@4.9.6": - version "4.9.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.6.tgz#bafa759ab43e8eab9edf242a8259ffb4f2a57a5d" - integrity sha512-ch7M+9Tr5R4FK40FHQk8VnML0Szi2KRujUgHXd/HjuH9ifH72GUmw6lStZBo3c3GB82vHa0ZoUfjfcM7JiiMrQ== +"@rollup/rollup-linux-s390x-gnu@4.13.2": + version "4.13.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.13.2.tgz#8e14a1b3c3b9a4440c70a9c1ba12d32aa21f9712" + integrity sha512-l4U0KDFwzD36j7HdfJ5/TveEQ1fUTjFFQP5qIt9gBqBgu1G8/kCaq5Ok05kd5TG9F8Lltf3MoYsUMw3rNlJ0Yg== -"@rollup/rollup-win32-arm64-msvc@4.9.6": - version "4.9.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.6.tgz#1cc3416682e5a20d8f088f26657e6e47f8db468e" - integrity sha512-VD6qnR99dhmTQ1mJhIzXsRcTBvTjbfbGGwKAHcu+52cVl15AC/kplkhxzW/uT0Xl62Y/meBKDZvoJSJN+vTeGA== +"@rollup/rollup-linux-x64-gnu@4.13.2": + version "4.13.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.2.tgz#270e939194b66df77bcb33dd9a5ddf7784bd7997" + integrity sha512-xXMLUAMzrtsvh3cZ448vbXqlUa7ZL8z0MwHp63K2IIID2+DeP5iWIT6g1SN7hg1VxPzqx0xZdiDM9l4n9LRU1A== -"@rollup/rollup-win32-ia32-msvc@4.9.6": - version "4.9.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.6.tgz#7d2251e1aa5e8a1e47c86891fe4547a939503461" - integrity sha512-J9AFDq/xiRI58eR2NIDfyVmTYGyIZmRcvcAoJ48oDld/NTR8wyiPUu2X/v1navJ+N/FGg68LEbX3Ejd6l8B7MQ== +"@rollup/rollup-linux-x64-musl@4.13.2": + version "4.13.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.2.tgz#e8dd0f3c2046acbda2934490b36552e856a3bc6a" + integrity sha512-M/JYAWickafUijWPai4ehrjzVPKRCyDb1SLuO+ZyPfoXgeCEAlgPkNXewFZx0zcnoIe3ay4UjXIMdXQXOZXWqA== -"@rollup/rollup-win32-x64-msvc@4.9.6": - version "4.9.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.6.tgz#2c1fb69e02a3f1506f52698cfdc3a8b6386df9a6" - integrity sha512-jqzNLhNDvIZOrt69Ce4UjGRpXJBzhUBzawMwnaDAwyHriki3XollsewxWzOzz+4yOFDkuJHtTsZFwMxhYJWmLQ== +"@rollup/rollup-win32-arm64-msvc@4.13.2": + version "4.13.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.2.tgz#f8b65a4a7e7a6b383e7b14439129b2f474ff123c" + integrity sha512-2YWwoVg9KRkIKaXSh0mz3NmfurpmYoBBTAXA9qt7VXk0Xy12PoOP40EFuau+ajgALbbhi4uTj3tSG3tVseCjuA== + +"@rollup/rollup-win32-ia32-msvc@4.13.2": + version "4.13.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.2.tgz#bc1c5a4fbc4337d6cb15da80a4de95fd53ab3573" + integrity sha512-2FSsE9aQ6OWD20E498NYKEQLneShWes0NGMPQwxWOdws35qQXH+FplabOSP5zEe1pVjurSDOGEVCE2agFwSEsw== + +"@rollup/rollup-win32-x64-msvc@4.13.2": + version "4.13.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.2.tgz#851959c4c1c3c6647aba1f388198c8243aed6917" + integrity sha512-7h7J2nokcdPePdKykd8wtc8QqqkqxIrUz7MHj6aNr8waBRU//NLDVnNjQnqQO6fqtjrtCdftpbTuOKAyrAQETQ== "@rooks/use-did-update@^4.10.0": version "4.11.2" @@ -3955,12 +3996,12 @@ axios-rate-limit@^1.3.0: resolved "https://registry.yarnpkg.com/axios-rate-limit/-/axios-rate-limit-1.3.0.tgz#03241d24c231c47432dab6e8234cfde819253c2e" integrity sha512-cKR5wTbU/CeeyF1xVl5hl6FlYsmzDVqxlN4rGtfO5x7J83UxKDckudsW0yW21/ZJRcO0Qrfm3fUFbhEbWTLayw== -axios@^1.3.4: - version "1.6.7" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.7.tgz#7b48c2e27c96f9c68a2f8f31e2ab19f59b06b0a7" - integrity sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA== +axios@^1.6.8: + version "1.6.8" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.8.tgz#66d294951f5d988a00e87a0ffb955316a619ea66" + integrity sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ== dependencies: - follow-redirects "^1.15.4" + follow-redirects "^1.15.6" form-data "^4.0.0" proxy-from-env "^1.1.0" @@ -4667,6 +4708,13 @@ create-jest@^29.7.0: jest-util "^29.7.0" prompts "^2.0.1" +cross-fetch@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-4.0.0.tgz#f037aef1580bb3a1a35164ea2a848ba81b445983" + integrity sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g== + dependencies: + node-fetch "^2.6.12" + cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -5380,34 +5428,34 @@ esbuild@^0.18.0: "@esbuild/win32-ia32" "0.18.20" "@esbuild/win32-x64" "0.18.20" -esbuild@^0.19.3: - version "0.19.12" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.19.12.tgz#dc82ee5dc79e82f5a5c3b4323a2a641827db3e04" - integrity sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg== +esbuild@^0.20.1: + version "0.20.2" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.20.2.tgz#9d6b2386561766ee6b5a55196c6d766d28c87ea1" + integrity sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g== optionalDependencies: - "@esbuild/aix-ppc64" "0.19.12" - "@esbuild/android-arm" "0.19.12" - "@esbuild/android-arm64" "0.19.12" - "@esbuild/android-x64" "0.19.12" - "@esbuild/darwin-arm64" "0.19.12" - "@esbuild/darwin-x64" "0.19.12" - "@esbuild/freebsd-arm64" "0.19.12" - "@esbuild/freebsd-x64" "0.19.12" - "@esbuild/linux-arm" "0.19.12" - "@esbuild/linux-arm64" "0.19.12" - "@esbuild/linux-ia32" "0.19.12" - "@esbuild/linux-loong64" "0.19.12" - "@esbuild/linux-mips64el" "0.19.12" - "@esbuild/linux-ppc64" "0.19.12" - "@esbuild/linux-riscv64" "0.19.12" - "@esbuild/linux-s390x" "0.19.12" - "@esbuild/linux-x64" "0.19.12" - "@esbuild/netbsd-x64" "0.19.12" - "@esbuild/openbsd-x64" "0.19.12" - "@esbuild/sunos-x64" "0.19.12" - "@esbuild/win32-arm64" "0.19.12" - "@esbuild/win32-ia32" "0.19.12" - "@esbuild/win32-x64" "0.19.12" + "@esbuild/aix-ppc64" "0.20.2" + "@esbuild/android-arm" "0.20.2" + "@esbuild/android-arm64" "0.20.2" + "@esbuild/android-x64" "0.20.2" + "@esbuild/darwin-arm64" "0.20.2" + "@esbuild/darwin-x64" "0.20.2" + "@esbuild/freebsd-arm64" "0.20.2" + "@esbuild/freebsd-x64" "0.20.2" + "@esbuild/linux-arm" "0.20.2" + "@esbuild/linux-arm64" "0.20.2" + "@esbuild/linux-ia32" "0.20.2" + "@esbuild/linux-loong64" "0.20.2" + "@esbuild/linux-mips64el" "0.20.2" + "@esbuild/linux-ppc64" "0.20.2" + "@esbuild/linux-riscv64" "0.20.2" + "@esbuild/linux-s390x" "0.20.2" + "@esbuild/linux-x64" "0.20.2" + "@esbuild/netbsd-x64" "0.20.2" + "@esbuild/openbsd-x64" "0.20.2" + "@esbuild/sunos-x64" "0.20.2" + "@esbuild/win32-arm64" "0.20.2" + "@esbuild/win32-ia32" "0.20.2" + "@esbuild/win32-x64" "0.20.2" escalade@^3.1.1: version "3.1.1" @@ -6033,7 +6081,9 @@ focus-trap@^7.5.4: dependencies: tabbable "^6.2.0" -follow-redirects@^1.15.4: + +follow-redirects@^1.15.6: + version "1.15.6" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== @@ -6454,6 +6504,13 @@ html-minifier-terser@^6.1.0: relateurl "^0.2.7" terser "^5.10.0" +html-parse-stringify@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2" + integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg== + dependencies: + void-elements "3.1.0" + html-tags@^3.1.0: version "3.3.1" resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.3.1.tgz#a04026a18c882e4bba8a01a3d39cfe465d40b5ce" @@ -6522,6 +6579,27 @@ human-signals@^5.0.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28" integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ== +i18next-browser-languagedetector@^7.2.1: + version "7.2.1" + resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.2.1.tgz#1968196d437b4c8db847410c7c33554f6c448f6f" + integrity sha512-h/pM34bcH6tbz8WgGXcmWauNpQupCGr25XPp9cZwZInR9XHSjIFDYp1SIok7zSPsTOMxdvuLyu86V+g2Kycnfw== + dependencies: + "@babel/runtime" "^7.23.2" + +i18next-http-backend@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/i18next-http-backend/-/i18next-http-backend-2.5.0.tgz#8396a7df30bfe722eff7a65f629df32a61720414" + integrity sha512-Z/aQsGZk1gSxt2/DztXk92DuDD20J+rNudT7ZCdTrNOiK8uQppfvdjq9+DFQfpAnFPn3VZS+KQIr1S/W1KxhpQ== + dependencies: + cross-fetch "4.0.0" + +i18next@^23.11.1: + version "23.11.1" + resolved "https://registry.yarnpkg.com/i18next/-/i18next-23.11.1.tgz#8e384b6ad7d6ba70c40cb86e020438251a5ff8b1" + integrity sha512-mXw4A24BiPZKRsbb9ewgSvjYd6fxFCNwJyfK6nYfSTIAX2GkCWcb598m3DFkDZmqADatvuASrKo6qwORz3VwTQ== + dependencies: + "@babel/runtime" "^7.23.2" + iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -8101,7 +8179,7 @@ node-fetch-native@^1.6.1: resolved "https://registry.yarnpkg.com/node-fetch-native/-/node-fetch-native-1.6.1.tgz#f95c74917d3cebc794cdae0cd2a9c7594aad0cb4" integrity sha512-bW9T/uJDPAJB2YNYEpWzE54U5O3MQidXsOyTfnbKYtTtFexRvGzb1waphBN4ZwP6EcIvYYEOwW0b72BpAqydTw== -node-fetch@^2.0.0: +node-fetch@^2.0.0, node-fetch@^2.6.12: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== @@ -8659,6 +8737,15 @@ postcss@^8.4.23, postcss@^8.4.32: picocolors "^1.0.0" source-map-js "^1.0.2" +postcss@^8.4.38: + version "8.4.38" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e" + integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A== + dependencies: + nanoid "^3.3.7" + picocolors "^1.0.0" + source-map-js "^1.2.0" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -8950,7 +9037,7 @@ react-element-to-jsx-string@^15.0.0: is-plain-object "5.0.0" react-is "18.1.0" -react-fast-compare@^3.0.1, react-fast-compare@^3.2.0: +react-fast-compare@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49" integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ== @@ -8976,6 +9063,14 @@ react-full-screen@1.1.0: dependencies: fscreen "^1.0.2" +react-i18next@^14.1.0: + version "14.1.0" + resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-14.1.0.tgz#44da74fbffd416f5d0c5307ef31735cf10cc91d9" + integrity sha512-3KwX6LHpbvGQ+sBEntjV4sYW3Zovjjl3fpoHbUwSgFHf0uRBcbeCBLR5al6ikncI5+W0EFb71QXZmfop+J6NrQ== + dependencies: + "@babel/runtime" "^7.23.9" + html-parse-stringify "^3.0.1" + react-icons@4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.3.1.tgz#2fa92aebbbc71f43d2db2ed1aed07361124e91ca" @@ -9023,14 +9118,6 @@ react-modal@^3.14.3, react-modal@^3.15.1: react-lifecycles-compat "^3.0.0" warning "^4.0.3" -react-popper@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.3.0.tgz#17891c620e1320dce318bad9fede46a5f71c70ba" - integrity sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q== - dependencies: - react-fast-compare "^3.0.1" - warning "^4.0.2" - react-refresh@^0.14.0: version "0.14.0" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e" @@ -9422,26 +9509,28 @@ rimraf@~2.6.2: optionalDependencies: fsevents "~2.3.2" -rollup@^4.2.0: - version "4.9.6" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.9.6.tgz#4515facb0318ecca254a2ee1315e22e09efc50a0" - integrity sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg== +rollup@^4.13.0: + version "4.13.2" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.13.2.tgz#ac57d2dc48e8f5562f5a6daadb9caee590069262" + integrity sha512-MIlLgsdMprDBXC+4hsPgzWUasLO9CE4zOkj/u6j+Z6j5A4zRY+CtiXAdJyPtgCsc42g658Aeh1DlrdVEJhsL2g== dependencies: "@types/estree" "1.0.5" optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.9.6" - "@rollup/rollup-android-arm64" "4.9.6" - "@rollup/rollup-darwin-arm64" "4.9.6" - "@rollup/rollup-darwin-x64" "4.9.6" - "@rollup/rollup-linux-arm-gnueabihf" "4.9.6" - "@rollup/rollup-linux-arm64-gnu" "4.9.6" - "@rollup/rollup-linux-arm64-musl" "4.9.6" - "@rollup/rollup-linux-riscv64-gnu" "4.9.6" - "@rollup/rollup-linux-x64-gnu" "4.9.6" - "@rollup/rollup-linux-x64-musl" "4.9.6" - "@rollup/rollup-win32-arm64-msvc" "4.9.6" - "@rollup/rollup-win32-ia32-msvc" "4.9.6" - "@rollup/rollup-win32-x64-msvc" "4.9.6" + "@rollup/rollup-android-arm-eabi" "4.13.2" + "@rollup/rollup-android-arm64" "4.13.2" + "@rollup/rollup-darwin-arm64" "4.13.2" + "@rollup/rollup-darwin-x64" "4.13.2" + "@rollup/rollup-linux-arm-gnueabihf" "4.13.2" + "@rollup/rollup-linux-arm64-gnu" "4.13.2" + "@rollup/rollup-linux-arm64-musl" "4.13.2" + "@rollup/rollup-linux-powerpc64le-gnu" "4.13.2" + "@rollup/rollup-linux-riscv64-gnu" "4.13.2" + "@rollup/rollup-linux-s390x-gnu" "4.13.2" + "@rollup/rollup-linux-x64-gnu" "4.13.2" + "@rollup/rollup-linux-x64-musl" "4.13.2" + "@rollup/rollup-win32-arm64-msvc" "4.13.2" + "@rollup/rollup-win32-ia32-msvc" "4.13.2" + "@rollup/rollup-win32-x64-msvc" "4.13.2" fsevents "~2.3.2" run-parallel@^1.1.9: @@ -9485,10 +9574,10 @@ safe-regex-test@^1.0.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sass@^1.69.5: - version "1.70.0" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.70.0.tgz#761197419d97b5358cb25f9dd38c176a8a270a75" - integrity sha512-uUxNQ3zAHeAx5nRFskBnrWzDUJrrvpCPD5FNAoRvTi0WwremlheES3tg+56PaVtCs5QDRX5CBLxxKMDJMEa1WQ== +sass@^1.77.0: + version "1.77.0" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.77.0.tgz#e736c69aff9fae4a4e6dae60a979eee9c942f321" + integrity sha512-eGj4HNfXqBWtSnvItNkn7B6icqH14i3CiCGbzMKs3BAPTq62pp9NBYsBgyN4cA+qssqo9r26lW4JSvlaUUWbgw== dependencies: chokidar ">=3.0.0 <4.0.0" immutable "^4.0.0" @@ -9672,6 +9761,11 @@ socket.io-parser@~4.2.4: resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== +source-map-js@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" + integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== + source-map-support@0.5.13: version "0.5.13" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" @@ -9969,7 +10063,7 @@ synchronous-promise@^2.0.15: resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.17.tgz#38901319632f946c982152586f2caf8ddc25c032" integrity sha512-AsS729u2RHUfEra9xJrE39peJcc2stq2+poBXX8bcM08Y6g9j/i/PUzwNQqkaJde7Ntg1TO7bSREbR5sdosQ+g== -tabbable@^6.2.0: +tabbable@^6.0.0, tabbable@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.2.0.tgz#732fb62bc0175cfcec257330be187dcfba1f3b97" integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew== @@ -10596,14 +10690,16 @@ vite-plugin-html@^3.2.0: node-html-parser "^5.3.3" pathe "^0.2.0" -vite@^5.0.13: - version "5.0.13" - resolved "https://registry.yarnpkg.com/vite/-/vite-5.0.13.tgz#605865b0e482506163e3f04f91665238f3be8cf1" - integrity sha512-/9ovhv2M2dGTuA+dY93B9trfyWMDRQw2jdVBhHNP6wr0oF34wG2i/N55801iZIpgUpnHDm4F/FabGQLyc+eOgg== + +vite@^5.2.7: + version "5.2.7" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.2.7.tgz#e1b8a985eb54fcb9467d7f7f009d87485016df6e" + integrity sha512-k14PWOKLI6pMaSzAuGtT+Cf0YmIx12z9YGon39onaJNy8DLBfBJrzg9FQEmkAM5lpHBZs9wksWAsyF/HkpEwJA== + dependencies: - esbuild "^0.19.3" - postcss "^8.4.32" - rollup "^4.2.0" + esbuild "^0.20.1" + postcss "^8.4.38" + rollup "^4.13.0" optionalDependencies: fsevents "~2.3.3" @@ -10612,6 +10708,11 @@ voca@^1.4.0: resolved "https://registry.yarnpkg.com/voca/-/voca-1.4.1.tgz#9f84939e1aaca80c39158049d3321c67c6d2ed0a" integrity sha512-NJC/BzESaHT1p4B5k4JykxedeltmNbau4cummStd4RjFojgq/kLew5TzYge9N2geeWyI2w8T30wUET5v+F7ZHA== +void-elements@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" + integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w== + walker@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" @@ -10619,7 +10720,7 @@ walker@^1.0.8: dependencies: makeerror "1.0.12" -warning@^4.0.2, warning@^4.0.3: +warning@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==