🔨 Fixes to ComicDetail container

This commit is contained in:
2026-02-25 20:52:40 -05:00
parent f9aac5e19f
commit 59afeded6a
2 changed files with 101 additions and 49 deletions

View File

@@ -17,16 +17,11 @@ import DownloadsPanel from "./DownloadsPanel";
import { VolumeInformation } from "./Tabs/VolumeInformation"; import { VolumeInformation } from "./Tabs/VolumeInformation";
import { isEmpty, isUndefined, isNil, filter } from "lodash"; import { isEmpty, isUndefined, isNil, filter } from "lodash";
import { components } from "react-select"; import { components, PlaceholderProps, GroupBase, StylesConfig } from "react-select";
import { RootState } from "threetwo-ui-typings";
import "react-sliding-pane/dist/react-sliding-pane.css"; import "react-sliding-pane/dist/react-sliding-pane.css";
import Loader from "react-loader-spinner";
import SlidingPane from "react-sliding-pane"; import SlidingPane from "react-sliding-pane";
import Modal from "react-modal";
import ComicViewer from "react-comic-viewer";
import { extractComicArchive } from "../../actions/fileops.actions";
import { determineCoverFile } from "../../shared/utils/metadata.utils"; import { determineCoverFile } from "../../shared/utils/metadata.utils";
import axios from "axios"; import axios from "axios";
import { styled } from "styled-components"; import { styled } from "styled-components";
@@ -38,23 +33,82 @@ const StyledSlidingPanel = styled(SlidingPane)`
background: #ccc; background: #ccc;
`; `;
interface RawFileDetails {
name: string;
cover?: {
filePath?: string;
};
containedIn?: string;
fileSize?: number;
path?: string;
extension?: string;
mimeType?: string;
[key: string]: any;
}
interface InferredIssue {
name?: string;
number?: number;
year?: string;
subtitle?: string;
[key: string]: any;
}
interface ComicVineMetadata {
name?: string;
volumeInformation?: any;
[key: string]: any;
}
interface Acquisition {
directconnect?: {
downloads?: any[];
};
torrent?: any[];
[key: string]: any;
}
interface ComicDetailProps { interface ComicDetailProps {
data: { data: {
_id: string; _id: string;
rawFileDetails: {}; rawFileDetails?: RawFileDetails;
inferredMetadata: { inferredMetadata: {
issue: {}; issue?: InferredIssue;
}; };
sourcedMetadata: { sourcedMetadata: {
comicvine: {}; comicvine?: ComicVineMetadata;
locg: {}; locg?: any;
comicInfo: {}; comicInfo?: any;
}; };
acquisition: {}; acquisition?: Acquisition;
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
}; };
userSettings: {}; userSettings?: any;
}
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.
@@ -83,7 +137,7 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
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([]); const [comicVineMatches, setComicVineMatches] = useState<ComicVineMatch[]>([]);
const { comicObjectId } = useParams<{ comicObjectId: string }>(); const { comicObjectId } = useParams<{ comicObjectId: string }>();
@@ -113,9 +167,9 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
}, []); }, []);
// sliding panel init // sliding panel init
const contentForSlidingPanel = { const contentForSlidingPanel: ContentForSlidingPanel = {
CVMatches: { CVMatches: {
content: (props) => ( content: (props?: any) => (
<> <>
<div> <div>
<ComicVineSearchForm data={rawFileDetails} /> <ComicVineSearchForm data={rawFileDetails} />
@@ -125,8 +179,8 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
<p className="">Searching for:</p> <p className="">Searching for:</p>
{inferredMetadata.issue ? ( {inferredMetadata.issue ? (
<> <>
<span className="">{inferredMetadata.issue.name} </span> <span className="">{inferredMetadata.issue?.name} </span>
<span className=""> # {inferredMetadata.issue.number} </span> <span className=""> # {inferredMetadata.issue?.number} </span>
</> </>
) : null} ) : null}
</div> </div>
@@ -148,9 +202,9 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
// Actions // Actions
const fetchComicVineMatches = async ( const fetchComicVineMatches = async (
searchPayload, searchPayload: any,
issueSearchQuery, issueSearchQuery: ComicVineSearchQuery,
seriesSearchQuery, seriesSearchQuery: ComicVineSearchQuery,
) => { ) => {
try { try {
const response = await axios({ const response = await axios({
@@ -176,13 +230,13 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
// return sortBy(matches, (match) => -match.score); // return sortBy(matches, (match) => -match.score);
}, },
}); });
let matches: any = []; let matches: ComicVineMatch[] = [];
if (!isNil(response.data.results) && response.data.results.length === 1) { if (!isNil(response.data.results) && response.data.results.length === 1) {
matches = response.data.results; matches = response.data.results;
} else { } else {
matches = response.data.map((match) => match); matches = response.data.map((match: ComicVineMatch) => match);
} }
const scoredMatches = matches.sort((a, b) => b.score - a.score); const scoredMatches = matches.sort((a: ComicVineMatch, b: ComicVineMatch) => b.score - a.score);
setComicVineMatches(scoredMatches); setComicVineMatches(scoredMatches);
} catch (err) { } catch (err) {
console.log(err); console.log(err);
@@ -191,13 +245,13 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
// Action event handlers // Action event handlers
const openDrawerWithCVMatches = () => { const openDrawerWithCVMatches = () => {
let seriesSearchQuery: IComicVineSearchQuery = {} as IComicVineSearchQuery; let seriesSearchQuery: ComicVineSearchQuery = {} as ComicVineSearchQuery;
let issueSearchQuery: IComicVineSearchQuery = {} as IComicVineSearchQuery; let issueSearchQuery: ComicVineSearchQuery = {} as ComicVineSearchQuery;
if (!isUndefined(rawFileDetails)) { if (!isUndefined(rawFileDetails)) {
issueSearchQuery = refineQuery(rawFileDetails.name); issueSearchQuery = refineQuery(rawFileDetails.name) as ComicVineSearchQuery;
} else if (!isEmpty(comicvine)) { } else if (!isEmpty(comicvine) && comicvine?.name) {
issueSearchQuery = refineQuery(comicvine.name); issueSearchQuery = refineQuery(comicvine.name) as ComicVineSearchQuery;
} }
fetchComicVineMatches(rawFileDetails, issueSearchQuery, seriesSearchQuery); fetchComicVineMatches(rawFileDetails, issueSearchQuery, seriesSearchQuery);
setSlidingPanelContentId("CVMatches"); setSlidingPanelContentId("CVMatches");
@@ -234,9 +288,7 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
<div>Delete Comic</div> <div>Delete Comic</div>
</span> </span>
); );
const Placeholder = (props) => { const Placeholder = components.Placeholder;
return <components.Placeholder {...props} />;
};
const actionOptions = [ const actionOptions = [
{ value: "match-on-comic-vine", label: CVMatchLabel }, { value: "match-on-comic-vine", label: CVMatchLabel },
{ value: "edit-metdata", label: editLabel }, { value: "edit-metdata", label: editLabel },
@@ -249,7 +301,7 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
} }
return item; return item;
}); });
const handleActionSelection = (action) => { const handleActionSelection = (action: ActionOption) => {
switch (action.value) { switch (action.value) {
case "match-on-comic-vine": case "match-on-comic-vine":
openDrawerWithCVMatches(); openDrawerWithCVMatches();
@@ -262,24 +314,24 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
break; break;
} }
}; };
const customStyles = { const customStyles: StylesConfig<ActionOption, false> = {
menu: (base) => ({ menu: (base: any) => ({
...base, ...base,
backgroundColor: "rgb(156, 163, 175)", backgroundColor: "rgb(156, 163, 175)",
}), }),
placeholder: (base) => ({ placeholder: (base: any) => ({
...base, ...base,
color: "black", color: "black",
}), }),
option: (base, { data, isDisabled, isFocused, isSelected }) => ({ option: (base: any, { isFocused }: any) => ({
...base, ...base,
backgroundColor: isFocused ? "gray" : "rgb(156, 163, 175)", backgroundColor: isFocused ? "gray" : "rgb(156, 163, 175)",
}), }),
singleValue: (base) => ({ singleValue: (base: any) => ({
...base, ...base,
paddingTop: "0.4rem", paddingTop: "0.4rem",
}), }),
control: (base) => ({ control: (base: any) => ({
...base, ...base,
backgroundColor: "rgb(156, 163, 175)", backgroundColor: "rgb(156, 163, 175)",
color: "black", color: "black",
@@ -289,7 +341,7 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
// check for the availability of CV metadata // check for the availability of CV metadata
const isComicBookMetadataAvailable = const isComicBookMetadataAvailable =
!isUndefined(comicvine) && !isUndefined(comicvine.volumeInformation); !isUndefined(comicvine) && !isUndefined(comicvine?.volumeInformation);
// check for the availability of rawFileDetails // check for the availability of rawFileDetails
const areRawFileDetailsAvailable = const areRawFileDetailsAvailable =
@@ -354,7 +406,7 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
query={airDCPPQuery} query={airDCPPQuery}
comicObjectId={_id} comicObjectId={_id}
comicObject={data.data} comicObject={data.data}
userSettings={userSettings} settings={userSettings}
key={4} key={4}
/> />
), ),
@@ -376,8 +428,8 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
name: "Downloads", name: "Downloads",
icon: ( icon: (
<> <>
{acquisition?.directconnect?.downloads?.length + {(acquisition?.directconnect?.downloads?.length || 0) +
acquisition?.torrent.length} (acquisition?.torrent?.length || 0)}
</> </>
), ),
content: content:
@@ -404,7 +456,7 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
// 2. from the CV-scraped version // 2. from the CV-scraped version
return ( return (
<section className="container mx-auto px-4 sm:px-6 lg:px-8"> <section className="mx-auto max-w-screen-xl px-4 py-4 sm:px-6 sm:py-8 lg:px-8">
<div className="section"> <div className="section">
{!isNil(data) && !isEmpty(data) && ( {!isNil(data) && !isEmpty(data) && (
<> <>
@@ -418,7 +470,7 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
{/* raw file details */} {/* raw file details */}
{!isUndefined(rawFileDetails) && {!isUndefined(rawFileDetails) &&
!isEmpty(rawFileDetails.cover) && ( !isEmpty(rawFileDetails?.cover) && (
<div className="grid"> <div className="grid">
<RawFileDetails <RawFileDetails
data={{ data={{
@@ -468,7 +520,7 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
<TabControls <TabControls
filteredTabs={filteredTabs} filteredTabs={filteredTabs}
downloadCount={acquisition?.directconnect?.downloads?.length} downloadCount={acquisition?.directconnect?.downloads?.length || 0}
/> />
<StyledSlidingPanel <StyledSlidingPanel
@@ -478,7 +530,7 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
width={"600px"} width={"600px"}
> >
{slidingPanelContentId !== "" && {slidingPanelContentId !== "" &&
contentForSlidingPanel[slidingPanelContentId].content()} contentForSlidingPanel[slidingPanelContentId]?.content()}
</StyledSlidingPanel> </StyledSlidingPanel>
</> </>
)} )}

View File

@@ -4,8 +4,8 @@ import React, { ReactElement } from "react";
export const ComicInfoXML = (data: { json: any }): ReactElement => { export const ComicInfoXML = (data: { json: any }): ReactElement => {
const { json } = data; const { json } = data;
return ( return (
<div className="flex md:w-4/5 lg:w-78"> <div className="flex w-3/4">
<dl className="dark:bg-yellow-600 bg-yellow-200 p-3 rounded-lg"> <dl className="dark:bg-yellow-600 bg-yellow-200 p-3 rounded-lg w-full">
<dt> <dt>
<p className="text-lg">{json.series?.[0]}</p> <p className="text-lg">{json.series?.[0]}</p>
</dt> </dt>