✂ Refactoring ComicDetail to make it... readable?

This commit is contained in:
2026-02-26 14:06:37 -05:00
parent 92992449a9
commit 4b8d7b5905
5 changed files with 403 additions and 319 deletions

View File

@@ -1,34 +1,23 @@
import React, { useState, ReactElement, useCallback } from "react"; import React, { useState, ReactElement, useCallback } from "react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import Card from "../shared/Carda"; import Card from "../shared/Carda";
import { ComicVineMatchPanel } from "./ComicVineMatchPanel";
import { RawFileDetails } from "./RawFileDetails"; import { RawFileDetails } from "./RawFileDetails";
import { ComicVineSearchForm } from "./ComicVineSearchForm";
import TabControls from "./TabControls"; import TabControls from "./TabControls";
import { EditMetadataPanel } from "./EditMetadataPanel";
import { Menu } from "./ActionMenu/Menu"; import { Menu } from "./ActionMenu/Menu";
import { ArchiveOperations } from "./Tabs/ArchiveOperations";
import { ComicInfoXML } from "./Tabs/ComicInfoXML";
import AcquisitionPanel from "./AcquisitionPanel";
import TorrentSearchPanel from "./TorrentSearchPanel";
import DownloadsPanel from "./DownloadsPanel";
import { VolumeInformation } from "./Tabs/VolumeInformation";
import { isEmpty, isUndefined, isNil, filter } from "lodash"; import { isEmpty, isUndefined, isNil, filter } from "lodash";
import { components, PlaceholderProps, GroupBase, StylesConfig } from "react-select"; import { components } from "react-select";
import "react-sliding-pane/dist/react-sliding-pane.css"; import "react-sliding-pane/dist/react-sliding-pane.css";
import SlidingPane from "react-sliding-pane"; import SlidingPane from "react-sliding-pane";
import { determineCoverFile } from "../../shared/utils/metadata.utils"; import { determineCoverFile } from "../../shared/utils/metadata.utils";
import axios from "axios";
import { styled } from "styled-components"; import { styled } from "styled-components";
import { COMICVINE_SERVICE_URI } from "../../constants/endpoints";
import { refineQuery } from "filename-parser";
// overridden <SlidingPanel> with some styles - moved outside component to prevent recreation // Extracted modules
import { useComicVineMatching } from "./useComicVineMatching";
import { createTabConfig } from "./tabConfig";
import { actionOptions, customStyles, ActionOption } from "./actionMenuConfig";
import { CVMatchesPanel, EditMetadataPanelWrapper } from "./SlidingPanelContent";
// Styled component - moved outside to prevent recreation
const StyledSlidingPanel = styled(SlidingPane)` const StyledSlidingPanel = styled(SlidingPane)`
background: #ccc; background: #ccc;
`; `;
@@ -89,29 +78,6 @@ interface ComicDetailProps {
comicObjectId?: string; comicObjectId?: string;
} }
interface ComicVineSearchQuery {
inferredIssueDetails: {
name: string;
[key: string]: any;
};
[key: string]: any;
}
interface ComicVineMatch {
score: number;
[key: string]: any;
}
interface ActionOption {
value: string;
label: React.ReactElement;
}
interface ContentForSlidingPanel {
[key: string]: {
content: (props?: any) => React.ReactElement;
};
}
/** /**
* Component for displaying the metadata for a comic in greater detail. * Component for displaying the metadata for a comic in greater detail.
* *
@@ -121,7 +87,6 @@ interface ContentForSlidingPanel {
* <ComicDetail/> * <ComicDetail/>
* ) * )
*/ */
export const ComicDetail = (data: ComicDetailProps): ReactElement => { export const ComicDetail = (data: ComicDetailProps): ReactElement => {
const { const {
data: { data: {
@@ -137,32 +102,21 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
queryClient, queryClient,
comicObjectId: comicObjectIdProp, comicObjectId: comicObjectIdProp,
} = data; } = data;
const [activeTab, setActiveTab] = useState<number | undefined>(undefined); const [activeTab, setActiveTab] = useState<number | undefined>(undefined);
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const [slidingPanelContentId, setSlidingPanelContentId] = useState(""); const [slidingPanelContentId, setSlidingPanelContentId] = useState("");
const [modalIsOpen, setIsOpen] = useState(false); const [modalIsOpen, setIsOpen] = useState(false);
const [comicVineMatches, setComicVineMatches] = useState<ComicVineMatch[]>([]);
const { comicObjectId } = useParams<{ comicObjectId: string }>(); const { comicObjectId } = useParams<{ comicObjectId: string }>();
const { comicVineMatches, prepareAndFetchMatches } = useComicVineMatching();
// const dispatch = useDispatch(); // Modal handlers (currently unused but kept for future use)
const openModal = useCallback((filePath: string) => { const openModal = useCallback((filePath: string) => {
setIsOpen(true); setIsOpen(true);
// dispatch(
// extractComicArchive(filePath, {
// type: "full",
// purpose: "reading",
// imageResizeOptions: {
// baseWidth: 1024,
// },
// }),
// );
}, []); }, []);
const afterOpenModal = useCallback((things: any) => { const afterOpenModal = useCallback((things: any) => {
// references are now sync'd and can be accessed.
// subtitle.style.color = "#f00";
console.log("kolaveri", things); console.log("kolaveri", things);
}, []); }, []);
@@ -170,99 +124,9 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
setIsOpen(false); setIsOpen(false);
}, []); }, []);
// sliding panel init
const contentForSlidingPanel: ContentForSlidingPanel = {
CVMatches: {
content: (props?: any) => (
<>
<div>
<ComicVineSearchForm data={rawFileDetails} />
</div>
<div className="border-slate-500 border rounded-lg p-2 mt-3">
<p className="">Searching for:</p>
{inferredMetadata.issue ? (
<>
<span className="">{inferredMetadata.issue?.name} </span>
<span className=""> # {inferredMetadata.issue?.number} </span>
</>
) : null}
</div>
<ComicVineMatchPanel
props={{
comicVineMatches,
comicObjectId,
queryClient,
onMatchApplied: () => {
setVisible(false);
setActiveTab(1); // Switch to Volume Information tab (id: 1)
},
}}
/>
</>
),
},
editComicBookMetadata: {
content: () => <EditMetadataPanel data={rawFileDetails} />,
},
};
// Actions
const fetchComicVineMatches = async (
searchPayload: any,
issueSearchQuery: ComicVineSearchQuery,
seriesSearchQuery: ComicVineSearchQuery,
) => {
try {
const response = await axios({
url: `${COMICVINE_SERVICE_URI}/volumeBasedSearch`,
method: "POST",
data: {
format: "json",
// hack
query: issueSearchQuery.inferredIssueDetails.name
.replace(/[^a-zA-Z0-9 ]/g, "")
.trim(),
limit: "100",
page: 1,
resources: "volume",
scorerConfiguration: {
searchParams: issueSearchQuery.inferredIssueDetails,
},
rawFileDetails: searchPayload,
},
transformResponse: (r) => {
const matches = JSON.parse(r);
return matches;
// return sortBy(matches, (match) => -match.score);
},
});
let matches: ComicVineMatch[] = [];
if (!isNil(response.data.results) && response.data.results.length === 1) {
matches = response.data.results;
} else {
matches = response.data.map((match: ComicVineMatch) => match);
}
const scoredMatches = matches.sort((a: ComicVineMatch, b: ComicVineMatch) => b.score - a.score);
setComicVineMatches(scoredMatches);
} catch (err) {
console.log(err);
}
};
// Action event handlers // Action event handlers
const openDrawerWithCVMatches = () => { const openDrawerWithCVMatches = () => {
let seriesSearchQuery: ComicVineSearchQuery = {} as ComicVineSearchQuery; prepareAndFetchMatches(rawFileDetails, comicvine);
let issueSearchQuery: ComicVineSearchQuery = {} as ComicVineSearchQuery;
if (!isUndefined(rawFileDetails)) {
issueSearchQuery = refineQuery(rawFileDetails.name) as ComicVineSearchQuery;
} else if (!isEmpty(comicvine) && comicvine?.name) {
issueSearchQuery = refineQuery(comicvine.name) as ComicVineSearchQuery;
}
fetchComicVineMatches(rawFileDetails, issueSearchQuery, seriesSearchQuery);
setSlidingPanelContentId("CVMatches"); setSlidingPanelContentId("CVMatches");
setVisible(true); setVisible(true);
}; };
@@ -272,44 +136,15 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
setVisible(true); setVisible(true);
}, []); }, []);
// Actions menu options and handler // Action menu handler
const CVMatchLabel = (
<span className="inline-flex flex-row items-center gap-2">
<div className="w-6 h-6">
<i className="icon-[solar--magic-stick-3-bold-duotone] w-6 h-6"></i>
</div>
<div>Match on ComicVine</div>
</span>
);
const editLabel = (
<span className="inline-flex flex-row items-center gap-2">
<div className="w-6 h-6">
<i className="icon-[solar--pen-2-bold-duotone] w-6 h-6"></i>
</div>
<div>Edit Metadata</div>
</span>
);
const deleteLabel = (
<span className="inline-flex flex-row items-center gap-2">
<div className="w-6 h-6">
<i className="icon-[solar--trash-bin-trash-bold-duotone] w-6 h-6"></i>
</div>
<div>Delete Comic</div>
</span>
);
const Placeholder = components.Placeholder; const Placeholder = components.Placeholder;
const actionOptions = [
{ value: "match-on-comic-vine", label: CVMatchLabel },
{ value: "edit-metdata", label: editLabel },
{ value: "delete-comic", label: deleteLabel },
];
const filteredActionOptions = filter(actionOptions, (item) => { const filteredActionOptions = filter(actionOptions, (item) => {
if (isUndefined(rawFileDetails)) { if (isUndefined(rawFileDetails)) {
return item.value !== "match-on-comic-vine"; return item.value !== "match-on-comic-vine";
} }
return item; return item;
}); });
const handleActionSelection = (action: ActionOption) => { const handleActionSelection = (action: ActionOption) => {
switch (action.value) { switch (action.value) {
case "match-on-comic-vine": case "match-on-comic-vine":
@@ -323,36 +158,11 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
break; break;
} }
}; };
const customStyles: StylesConfig<ActionOption, false> = {
menu: (base: any) => ({
...base,
backgroundColor: "rgb(156, 163, 175)",
}),
placeholder: (base: any) => ({
...base,
color: "black",
}),
option: (base: any, { isFocused }: any) => ({
...base,
backgroundColor: isFocused ? "gray" : "rgb(156, 163, 175)",
}),
singleValue: (base: any) => ({
...base,
paddingTop: "0.4rem",
}),
control: (base: any) => ({
...base,
backgroundColor: "rgb(156, 163, 175)",
color: "black",
border: "1px solid rgb(156, 163, 175)",
}),
};
// check for the availability of CV metadata // Check for metadata availability
const isComicBookMetadataAvailable = const isComicBookMetadataAvailable =
!isUndefined(comicvine) && !isUndefined(comicvine?.volumeInformation); !isUndefined(comicvine) && !isUndefined(comicvine?.volumeInformation);
// check for the availability of rawFileDetails
const areRawFileDetailsAvailable = const areRawFileDetailsAvailable =
!isUndefined(rawFileDetails) && !isEmpty(rawFileDetails); !isUndefined(rawFileDetails) && !isEmpty(rawFileDetails);
@@ -362,107 +172,51 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
locg, locg,
}); });
// query for airdc++ // Query for airdc++
const airDCPPQuery = { const airDCPPQuery = {
issue: { issue: {
name: issueName, name: issueName,
}, },
}; };
// Tab content and header details // Create tab configuration
const tabGroup = [ const tabGroup = createTabConfig({
{ data: data.data,
id: 1, comicInfo,
name: "Volume Information", isComicBookMetadataAvailable,
icon: ( areRawFileDetailsAvailable,
<i className="h-5 w-5 icon-[solar--book-2-bold] text-slate-500 dark:text-slate-300"></i> airDCPPQuery,
), comicObjectId: _id,
content: isComicBookMetadataAvailable ? ( userSettings,
<VolumeInformation data={data.data} key={1} /> issueName,
) : null, acquisition,
shouldShow: isComicBookMetadataAvailable, });
},
{
id: 2,
name: "ComicInfo.xml",
icon: (
<i className="h-5 w-5 icon-[solar--code-file-bold-duotone] text-slate-500 dark:text-slate-300" />
),
content: (
<div key={2}>
{!isNil(comicInfo) && <ComicInfoXML json={comicInfo} />}
</div>
),
shouldShow: !isEmpty(comicInfo),
},
{
id: 3,
icon: (
<i className="h-5 w-5 icon-[solar--winrar-bold-duotone] text-slate-500 dark:text-slate-300" />
),
name: "Archive Operations",
content: <ArchiveOperations data={data.data} key={3} />,
shouldShow: areRawFileDetailsAvailable,
},
{
id: 4,
icon: (
<i className="h-5 w-5 icon-[solar--folder-path-connect-bold-duotone] text-slate-500 dark:text-slate-300" />
),
name: "DC++ Search",
content: (
<AcquisitionPanel
query={airDCPPQuery}
comicObjectId={_id}
comicObject={data.data}
settings={userSettings}
key={4}
/>
),
shouldShow: true,
},
{
id: 5,
icon: (
<span className="inline-flex flex-row">
<i className="h-5 w-5 icon-[solar--magnet-bold-duotone] text-slate-500 dark:text-slate-300" />
</span>
),
name: "Torrent Search",
content: <TorrentSearchPanel comicObjectId={_id} issueName={issueName} />,
shouldShow: true,
},
{
id: 6,
name: "Downloads",
icon: (
<>
{(acquisition?.directconnect?.downloads?.length || 0) +
(acquisition?.torrent?.length || 0)}
</>
),
content:
!isNil(data.data) && !isEmpty(data.data) ? (
<DownloadsPanel key={5} />
) : (
<div className="column is-three-fifths">
<article className="message is-info">
<div className="message-body is-size-6 is-family-secondary">
AirDC++ is not configured. Please configure it in{" "}
<code>Settings</code>.
</div>
</article>
</div>
),
shouldShow: true,
},
];
// filtered Tabs
const filteredTabs = tabGroup.filter((tab) => tab.shouldShow); const filteredTabs = tabGroup.filter((tab) => tab.shouldShow);
// Determine which cover image to use: // Sliding panel content mapping
// 1. from the locally imported or const renderSlidingPanelContent = () => {
// 2. from the CV-scraped version switch (slidingPanelContentId) {
case "CVMatches":
return (
<CVMatchesPanel
rawFileDetails={rawFileDetails}
inferredMetadata={inferredMetadata}
comicVineMatches={comicVineMatches}
comicObjectId={comicObjectId || _id}
queryClient={queryClient}
onMatchApplied={() => {
setVisible(false);
setActiveTab(1);
}}
/>
);
case "editComicBookMetadata":
return <EditMetadataPanelWrapper rawFileDetails={rawFileDetails} />;
default:
return null;
}
};
return ( return (
<section className="mx-auto max-w-screen-xl px-4 py-4 sm:px-6 sm:py-8 lg:px-8"> <section className="mx-auto max-w-screen-xl px-4 py-4 sm:px-6 sm:py-8 lg:px-8">
@@ -503,25 +257,6 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
/> />
</div> </div>
</RawFileDetails> </RawFileDetails>
{/* <Modal
style={{ content: { marginTop: "2rem" } }}
isOpen={modalIsOpen}
onAfterOpen={afterOpenModal}
onRequestClose={closeModal}
contentLabel="Example Modal"
>
<button onClick={closeModal}>close</button>
{extractedComicBook && (
<ComicViewer
pages={extractedComicBook}
direction="ltr"
className={{
closeButton: "border: 1px solid red;",
}}
/>
)}
</Modal> */}
</div> </div>
)} )}
</div> </div>
@@ -540,8 +275,7 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
title={"Comic Vine Search Matches"} title={"Comic Vine Search Matches"}
width={"600px"} width={"600px"}
> >
{slidingPanelContentId !== "" && {renderSlidingPanelContent()}
contentForSlidingPanel[slidingPanelContentId]?.content()}
</StyledSlidingPanel> </StyledSlidingPanel>
</> </>
)} )}

View File

@@ -0,0 +1,64 @@
import React from "react";
import { ComicVineSearchForm } from "./ComicVineSearchForm";
import { ComicVineMatchPanel } from "./ComicVineMatchPanel";
import { EditMetadataPanel } from "./EditMetadataPanel";
interface InferredIssue {
name?: string;
number?: number;
year?: string;
subtitle?: string;
[key: string]: any;
}
interface CVMatchesPanelProps {
rawFileDetails: any;
inferredMetadata: {
issue?: InferredIssue;
};
comicVineMatches: any[];
comicObjectId: string;
queryClient: any;
onMatchApplied: () => void;
}
export const CVMatchesPanel: React.FC<CVMatchesPanelProps> = ({
rawFileDetails,
inferredMetadata,
comicVineMatches,
comicObjectId,
queryClient,
onMatchApplied,
}) => (
<>
<div>
<ComicVineSearchForm data={rawFileDetails} />
</div>
<div className="border-slate-500 border rounded-lg p-2 mt-3">
<p className="">Searching for:</p>
{inferredMetadata.issue ? (
<>
<span className="">{inferredMetadata.issue?.name} </span>
<span className=""> # {inferredMetadata.issue?.number} </span>
</>
) : null}
</div>
<ComicVineMatchPanel
props={{
comicVineMatches,
comicObjectId,
queryClient,
onMatchApplied,
}}
/>
</>
);
interface EditMetadataPanelWrapperProps {
rawFileDetails: any;
}
export const EditMetadataPanelWrapper: React.FC<EditMetadataPanelWrapperProps> = ({
rawFileDetails,
}) => <EditMetadataPanel data={rawFileDetails} />;

View File

@@ -0,0 +1,65 @@
import React from "react";
import { StylesConfig } from "react-select";
export interface ActionOption {
value: string;
label: React.ReactElement;
}
export const CVMatchLabel = (
<span className="inline-flex flex-row items-center gap-2">
<div className="w-6 h-6">
<i className="icon-[solar--magic-stick-3-bold-duotone] w-6 h-6"></i>
</div>
<div>Match on ComicVine</div>
</span>
);
export const editLabel = (
<span className="inline-flex flex-row items-center gap-2">
<div className="w-6 h-6">
<i className="icon-[solar--pen-2-bold-duotone] w-6 h-6"></i>
</div>
<div>Edit Metadata</div>
</span>
);
export const deleteLabel = (
<span className="inline-flex flex-row items-center gap-2">
<div className="w-6 h-6">
<i className="icon-[solar--trash-bin-trash-bold-duotone] w-6 h-6"></i>
</div>
<div>Delete Comic</div>
</span>
);
export const actionOptions: ActionOption[] = [
{ value: "match-on-comic-vine", label: CVMatchLabel },
{ value: "edit-metdata", label: editLabel },
{ value: "delete-comic", label: deleteLabel },
];
export const customStyles: StylesConfig<ActionOption, false> = {
menu: (base: any) => ({
...base,
backgroundColor: "rgb(156, 163, 175)",
}),
placeholder: (base: any) => ({
...base,
color: "black",
}),
option: (base: any, { isFocused }: any) => ({
...base,
backgroundColor: isFocused ? "gray" : "rgb(156, 163, 175)",
}),
singleValue: (base: any) => ({
...base,
paddingTop: "0.4rem",
}),
control: (base: any) => ({
...base,
backgroundColor: "rgb(156, 163, 175)",
color: "black",
border: "1px solid rgb(156, 163, 175)",
}),
};

View File

@@ -0,0 +1,128 @@
import React from "react";
import { isNil, isEmpty } from "lodash";
import { VolumeInformation } from "./Tabs/VolumeInformation";
import { ComicInfoXML } from "./Tabs/ComicInfoXML";
import { ArchiveOperations } from "./Tabs/ArchiveOperations";
import AcquisitionPanel from "./AcquisitionPanel";
import TorrentSearchPanel from "./TorrentSearchPanel";
import DownloadsPanel from "./DownloadsPanel";
interface TabConfig {
id: number;
name: string;
icon: React.ReactElement;
content: React.ReactElement | null;
shouldShow: boolean;
}
interface TabConfigParams {
data: any;
comicInfo: any;
isComicBookMetadataAvailable: boolean;
areRawFileDetailsAvailable: boolean;
airDCPPQuery: any;
comicObjectId: string;
userSettings: any;
issueName: string;
acquisition?: any;
}
export const createTabConfig = ({
data,
comicInfo,
isComicBookMetadataAvailable,
areRawFileDetailsAvailable,
airDCPPQuery,
comicObjectId,
userSettings,
issueName,
acquisition,
}: TabConfigParams): TabConfig[] => {
return [
{
id: 1,
name: "Volume Information",
icon: (
<i className="h-5 w-5 icon-[solar--book-2-bold] text-slate-500 dark:text-slate-300"></i>
),
content: isComicBookMetadataAvailable ? (
<VolumeInformation data={data} key={1} />
) : null,
shouldShow: isComicBookMetadataAvailable,
},
{
id: 2,
name: "ComicInfo.xml",
icon: (
<i className="h-5 w-5 icon-[solar--code-file-bold-duotone] text-slate-500 dark:text-slate-300" />
),
content: (
<div key={2}>
{!isNil(comicInfo) && <ComicInfoXML json={comicInfo} />}
</div>
),
shouldShow: !isEmpty(comicInfo),
},
{
id: 3,
icon: (
<i className="h-5 w-5 icon-[solar--winrar-bold-duotone] text-slate-500 dark:text-slate-300" />
),
name: "Archive Operations",
content: <ArchiveOperations data={data} key={3} />,
shouldShow: areRawFileDetailsAvailable,
},
{
id: 4,
icon: (
<i className="h-5 w-5 icon-[solar--folder-path-connect-bold-duotone] text-slate-500 dark:text-slate-300" />
),
name: "DC++ Search",
content: (
<AcquisitionPanel
query={airDCPPQuery}
comicObjectId={comicObjectId}
comicObject={data}
settings={userSettings}
key={4}
/>
),
shouldShow: true,
},
{
id: 5,
icon: (
<span className="inline-flex flex-row">
<i className="h-5 w-5 icon-[solar--magnet-bold-duotone] text-slate-500 dark:text-slate-300" />
</span>
),
name: "Torrent Search",
content: <TorrentSearchPanel comicObjectId={comicObjectId} issueName={issueName} />,
shouldShow: true,
},
{
id: 6,
name: "Downloads",
icon: (
<>
{(acquisition?.directconnect?.downloads?.length || 0) +
(acquisition?.torrent?.length || 0)}
</>
),
content:
!isNil(data) && !isEmpty(data) ? (
<DownloadsPanel key={5} />
) : (
<div className="column is-three-fifths">
<article className="message is-info">
<div className="message-body is-size-6 is-family-secondary">
AirDC++ is not configured. Please configure it in{" "}
<code>Settings</code>.
</div>
</article>
</div>
),
shouldShow: true,
},
];
};

View File

@@ -0,0 +1,93 @@
import { useState } from "react";
import axios from "axios";
import { isNil, isUndefined, isEmpty } from "lodash";
import { refineQuery } from "filename-parser";
import { COMICVINE_SERVICE_URI } from "../../constants/endpoints";
interface ComicVineMatch {
score: number;
[key: string]: any;
}
interface ComicVineSearchQuery {
inferredIssueDetails: {
name: string;
[key: string]: any;
};
[key: string]: any;
}
interface RawFileDetails {
name: string;
[key: string]: any;
}
interface ComicVineMetadata {
name?: string;
[key: string]: any;
}
export const useComicVineMatching = () => {
const [comicVineMatches, setComicVineMatches] = useState<ComicVineMatch[]>([]);
const fetchComicVineMatches = async (
searchPayload: any,
issueSearchQuery: ComicVineSearchQuery,
seriesSearchQuery: ComicVineSearchQuery,
) => {
try {
const response = await axios({
url: `${COMICVINE_SERVICE_URI}/volumeBasedSearch`,
method: "POST",
data: {
format: "json",
// hack
query: issueSearchQuery.inferredIssueDetails.name
.replace(/[^a-zA-Z0-9 ]/g, "")
.trim(),
limit: "100",
page: 1,
resources: "volume",
scorerConfiguration: {
searchParams: issueSearchQuery.inferredIssueDetails,
},
rawFileDetails: searchPayload,
},
transformResponse: (r) => {
const matches = JSON.parse(r);
return matches;
},
});
let matches: ComicVineMatch[] = [];
if (!isNil(response.data.results) && response.data.results.length === 1) {
matches = response.data.results;
} else {
matches = response.data.map((match: ComicVineMatch) => match);
}
const scoredMatches = matches.sort((a: ComicVineMatch, b: ComicVineMatch) => b.score - a.score);
setComicVineMatches(scoredMatches);
} catch (err) {
console.log(err);
}
};
const prepareAndFetchMatches = (
rawFileDetails: RawFileDetails | undefined,
comicvine: ComicVineMetadata | undefined,
) => {
let seriesSearchQuery: ComicVineSearchQuery = {} as ComicVineSearchQuery;
let issueSearchQuery: ComicVineSearchQuery = {} as ComicVineSearchQuery;
if (!isUndefined(rawFileDetails)) {
issueSearchQuery = refineQuery(rawFileDetails.name) as ComicVineSearchQuery;
} else if (!isEmpty(comicvine) && comicvine?.name) {
issueSearchQuery = refineQuery(comicvine.name) as ComicVineSearchQuery;
}
fetchComicVineMatches(rawFileDetails, issueSearchQuery, seriesSearchQuery);
};
return {
comicVineMatches,
prepareAndFetchMatches,
};
};