import React, { ReactElement, useMemo, useState } from "react"; import { isEmpty, isNil } from "lodash"; import { Drawer } from "vaul"; import ComicVineDetails from "../ComicVineDetails"; interface ComicVineMetadata { volumeInformation?: Record; name?: string; number?: string; resource_type?: string; id?: number; } interface SourcedMetadata { comicvine?: ComicVineMetadata; locg?: Record; comicInfo?: unknown; metron?: unknown; gcd?: unknown; [key: string]: unknown; } interface VolumeInformationData { sourcedMetadata?: SourcedMetadata; inferredMetadata?: { issue?: unknown }; updatedAt?: string; } interface VolumeInformationProps { data: VolumeInformationData; onReconcile?: () => void; } /** Sources stored under `sourcedMetadata` — excludes `inferredMetadata`, which is checked separately. */ const SOURCED_METADATA_KEYS = [ "comicvine", "locg", "comicInfo", "metron", "gcd", ]; const SOURCE_LABELS: Record = { comicvine: "ComicVine", locg: "League of Comic Geeks", comicInfo: "ComicInfo.xml", metron: "Metron", gcd: "Grand Comics Database", inferredMetadata: "Local File", }; const SOURCE_ICONS: Record = { comicvine: "icon-[solar--database-bold]", locg: "icon-[solar--users-group-rounded-outline]", comicInfo: "icon-[solar--file-text-outline]", metron: "icon-[solar--planet-outline]", gcd: "icon-[solar--book-outline]", inferredMetadata: "icon-[solar--folder-outline]", }; const MetadataSourceChips = ({ sources, }: { sources: string[]; }): ReactElement => { const [isSheetOpen, setSheetOpen] = useState(false); return ( <>
{sources.length} metadata sources detected
{sources.map((source) => ( {SOURCE_LABELS[source] ?? source} ))}
Reconcile metadata sources
{/* Reconciliation UI goes here */}
); }; /** * Displays volume metadata for a comic. * * - When multiple sources are present, renders a chip bar listing each source * with a "Reconcile sources" action to merge them. * - When exactly one source is present and it is ComicVine, renders the full * ComicVine detail panel directly. * * @param props.data - Comic data containing sourced and inferred metadata. * @param props.onReconcile - Called when the user triggers source reconciliation. */ export const VolumeInformation = ( props: VolumeInformationProps, ): ReactElement => { const { data } = props; const presentSources = useMemo(() => { const sources = SOURCED_METADATA_KEYS.filter((key) => { const val = (data?.sourcedMetadata ?? {})[key]; if (isNil(val) || isEmpty(val)) return false; // locg returns an object even when empty; require at least one non-null value if (key === "locg") return Object.values(val as Record).some( (v) => !isNil(v) && v !== "", ); return true; }); if ( !isNil(data?.inferredMetadata?.issue) && !isEmpty(data?.inferredMetadata?.issue) ) { sources.push("inferredMetadata"); } return sources; }, [data?.sourcedMetadata, data?.inferredMetadata]); return (
{presentSources.length > 1 && ( )} {presentSources.length === 1 && data.sourcedMetadata?.comicvine?.volumeInformation && ( )}
); }; export default VolumeInformation;