import React, { useState, useEffect, useCallback, ReactElement } from "react"; import PropTypes from "prop-types"; import { useParams } from "react-router-dom"; import Card from "./Carda"; import MatchResult from "./MatchResult"; import ComicVineSearchForm from "./ComicVineSearchForm"; import AcquisitionPanel from "./AcquisitionPanel"; import DownloadsPanel from "./DownloadsPanel"; import SlidingPane from "react-sliding-pane"; import Select, { components } from "react-select"; import "react-sliding-pane/dist/react-sliding-pane.css"; import "react-loader-spinner/dist/loader/css/react-spinner-loader.css"; import Loader from "react-loader-spinner"; import { isEmpty, isUndefined, isNil } from "lodash"; import { RootState } from "threetwo-ui-typings"; import { fetchComicVineMatches } from "../actions/fileops.actions"; import { getComicBookDetailById } from "../actions/comicinfo.actions"; import { detectTradePaperbacks } from "../shared/utils/tradepaperback.utils"; import dayjs from "dayjs"; const prettyBytes = require("pretty-bytes"); // https://codesandbox.io/s/dndkit-sortable-image-grid-py6ve?file=/src/Grid.jsx import { closestCenter, DndContext, DragOverlay, KeyboardSensor, PointerSensor, TouchSensor, useSensor, useSensors, } from "@dnd-kit/core"; import { arrayMove, SortableContext, sortableKeyboardCoordinates, verticalListSortingStrategy, } from "@dnd-kit/sortable"; import { SortableItem } from "./SortableItem"; import { Item } from "./Item"; import { useDispatch, useSelector } from "react-redux"; import { removeLeadingPeriod, escapePoundSymbol, } from "../shared/utils/formatting.utils"; type ComicDetailProps = {}; /** * Component for displaying the metadata for a comic in greater detail. * * @component * @example * return ( * * ) */ export const ComicDetail = ({}: ComicDetailProps): ReactElement => { const [page, setPage] = useState(1); const [visible, setVisible] = useState(false); const [slidingPanelContentId, setSlidingPanelContentId] = useState(""); const [activeId, setActiveId] = useState(null); const [items, setItems] = useState(["1", "2", "3"]); const sensors = useSensors( useSensor(PointerSensor), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates, }), useSensor(TouchSensor), ); const comicVineSearchResults = useSelector( (state: RootState) => state.comicInfo.searchResults, ); const comicVineSearchQueryObject = useSelector( (state: RootState) => state.comicInfo.searchQuery, ); const comicVineAPICallProgress = useSelector( (state: RootState) => state.comicInfo.inProgress, ); const comicBookDetailData = useSelector( (state: RootState) => state.comicInfo.comicBookDetail, ); const extractedComicBookArchive = useSelector( (state: RootState) => state.fileOps.extractedComicBookArchive, ); const { comicObjectId } = useParams<{ comicObjectId: string }>(); const dispatch = useDispatch(); useEffect(() => { dispatch(getComicBookDetailById(comicObjectId)); }, [page, dispatch]); // sliding panel init const contentForSlidingPanel = { CVMatches: { content: () => { return ( <> {!isEmpty(comicVineSearchQueryObject) && !isUndefined(comicVineSearchQueryObject) ? (

Searching against:

Title { comicVineSearchQueryObject.issue.searchParams .searchTerms.name }
Number { comicVineSearchQueryObject.issue.searchParams .searchTerms.number }
) : (
)}
{!isEmpty(comicVineSearchResults) && ( )}
); }, }, editComicArchive: { content: () => <>, }, }; function handleDragStart(event) { const { active } = event; setActiveId(active.id); } function handleDragEnd(event) { const { active, over } = event; if (active.id !== over.id) { setItems((items) => { const oldIndex = items.indexOf(active.id); const newIndex = items.indexOf(over.id); return arrayMove(items, oldIndex, newIndex); }); } setActiveId(null); } const openDrawerWithCVMatches = useCallback(() => { dispatch(fetchComicVineMatches(comicBookDetailData)); setSlidingPanelContentId("CVMatches"); setVisible(true); }, [dispatch, comicBookDetailData]); const [active, setActive] = useState(1); const createDescriptionMarkup = (html) => { return { __html: html }; }; const isComicBookMetadataAvailable = comicBookDetailData.sourcedMetadata && !isUndefined(comicBookDetailData.sourcedMetadata.comicvine) && !isEmpty(comicBookDetailData.sourcedMetadata); // Tab content and header details const tabGroup = [ { id: 1, name: "Volume Information", icon: , content: isComicBookMetadataAvailable ? (
Is a part of{" "} { comicBookDetailData.sourcedMetadata.comicvine .volumeInformation.name }
Published by {" "} { comicBookDetailData.sourcedMetadata.comicvine .volumeInformation.publisher.name }
Total issues in this volume: { comicBookDetailData.sourcedMetadata.comicvine .volumeInformation.count_of_issues }
) : null, }, { id: 2, icon: , name: "Archive Operations", content:
, }, { id: 3, icon: , name: "Acquisition", content: ( ), }, { id: 4, icon: null, name: !isNil(comicBookDetailData) && !isEmpty(comicBookDetailData) ? ( Downloads ) : ( "Downloads" ), content: !isNil(comicBookDetailData) && !isEmpty(comicBookDetailData) && ( ), }, ]; // Tabs const MetadataTabGroup = () => { return ( <>
{tabGroup.map(({ id, content }) => { return active === id ? content : null; })} ); }; const RawFileDetails = (props) => (
Raw File Details
{props.data.containedIn + "/" + props.data.name + props.data.extension}
Size {prettyBytes(props.data.fileSize)}
Extension {props.data.extension}
); const ComicVineDetails = (props) => (
ComicVine Metadata
Last scraped on{" "} {dayjs(props.updatedAt).format("MMM D YYYY [at] h:mm a")}
{props.data.name}
{props.data.number}
{!isEmpty( detectTradePaperbacks( comicBookDetailData.sourcedMetadata.comicvine.volumeInformation .description, ), ) ? (
Detected Type Trade Paperback
) : (
Type {props.data.resource_type}
)}
ComicVine Issue ID {props.data.id}
); ComicVineDetails.propTypes = { updatedAt: PropTypes.string, data: PropTypes.shape({ name: PropTypes.string, number: PropTypes.string, resource_type: PropTypes.string, id: PropTypes.number, }), }; RawFileDetails.propTypes = { data: PropTypes.shape({ containedIn: PropTypes.string, name: PropTypes.string, fileSize: PropTypes.number, path: PropTypes.string, extension: PropTypes.string, cover: PropTypes.shape({ filePath: PropTypes.string, }), }), }; // Determine which cover image to use: // 1. from the locally imported or // 2. from the CV-scraped version let imagePath = ""; let comicBookTitle = ""; if (!isNil(comicBookDetailData.rawFileDetails)) { const encodedFilePath = encodeURI( "http://localhost:3000" + removeLeadingPeriod(comicBookDetailData.rawFileDetails.cover.filePath), ); imagePath = escapePoundSymbol(encodedFilePath); comicBookTitle = comicBookDetailData.rawFileDetails.name; } else if ( !isNil(comicBookDetailData.sourcedMetadata) && !isNil(comicBookDetailData.sourcedMetadata.comicvine) ) { imagePath = comicBookDetailData.sourcedMetadata.comicvine.image.small_url; comicBookTitle = comicBookDetailData.sourcedMetadata.comicvine.name; } // Actions menu options and handler const CVMatchLabel = ( Match on ComicVine ); const editArchive = ( Edit Comic Archive ); const editLabel = ( Edit Metadata ); const deleteLabel = ( Delete Comic ); const Placeholder = (props) => { return ; }; const actionOptions = [ { value: "match-on-comic-vine", label: CVMatchLabel }, { value: "edit-comic-archive", label: editArchive }, { value: "edit-metdata", label: editLabel }, { value: "delete-comic", label: deleteLabel }, ]; const handleActionSelection = (action) => { switch (action.value) { case "match-on-comic-vine": openDrawerWithCVMatches(); break; default: console.log("No valid action selected."); break; } }; return (
{items.map((id) => ( ))} {activeId ? : null} {!isNil(comicBookDetailData) && !isEmpty(comicBookDetailData) && ( <>

{comicBookTitle}

{/* raw file details */}
{!isNil(comicBookDetailData.rawFileDetails) && ( <> )} {/* comic vine scraped metadata */} {!isNil(comicBookDetailData.sourcedMetadata.comicvine) && ( )}
{/* action dropdown */}