diff --git a/src/client/components/Dashboard/RecentlyImported.tsx b/src/client/components/Dashboard/RecentlyImported.tsx index cc6176d..f6438b4 100644 --- a/src/client/components/Dashboard/RecentlyImported.tsx +++ b/src/client/components/Dashboard/RecentlyImported.tsx @@ -42,6 +42,7 @@ export const RecentlyImported = ( sourcedMetadata, canonicalMetadata, inferredMetadata, + importStatus, } = comic; // Parse sourced metadata (GraphQL returns as strings) @@ -63,7 +64,7 @@ export const RecentlyImported = ( !isUndefined(comicvine) && !isUndefined(comicvine.volumeInformation); const hasComicInfo = !isNil(comicInfo) && !isEmpty(comicInfo); - const isMissingFile = isNil(rawFileDetails); + const isMissingFile = importStatus?.isRawFileMissing === true; const cardState = isMissingFile ? "missing" : (hasComicInfo || isComicVineMetadataAvailable) ? "scraped" : "imported"; @@ -131,7 +132,7 @@ export const RecentlyImported = ( )} {/* Raw file presence */} - {isNil(rawFileDetails) && ( + {isMissingFile && ( diff --git a/src/client/components/Library/Library.tsx b/src/client/components/Library/Library.tsx index 08c7cc2..be41e5c 100644 --- a/src/client/components/Library/Library.tsx +++ b/src/client/components/Library/Library.tsx @@ -314,7 +314,7 @@ export const Library = (): ReactElement => { columns={missingFilesColumns} sourceData={missingFilesData?.getComicBooks?.docs ?? []} rowClickHandler={navigateToMissingComicDetail} - getRowClassName={() => "bg-card-missing/40"} + getRowClassName={() => "bg-card-missing/40 hover:bg-card-missing/20"} paginationHandlers={{ nextPage: () => {}, previousPage: () => {} }} > @@ -328,6 +328,11 @@ export const Library = (): ReactElement => { columns={columns} sourceData={searchResults?.hits.hits} rowClickHandler={navigateToComicDetail} + getRowClassName={(row) => + missingIdSet.has(row.original._id) + ? "bg-card-missing/40 hover:bg-card-missing/20" + : "hover:bg-slate-100/30 dark:hover:bg-slate-700/20" + } paginationHandlers={{ nextPage, previousPage }} >
diff --git a/src/client/components/shared/Carda.tsx b/src/client/components/shared/Carda.tsx index 381238c..48563ad 100644 --- a/src/client/components/shared/Carda.tsx +++ b/src/client/components/shared/Carda.tsx @@ -104,11 +104,22 @@ const renderCard = (props: ICardProps): ReactElement => { case "vertical-2": return (
- Home +
+ {props.imageUrl ? ( + Home + ) : ( +
+ )} + {props.cardState === "missing" && ( +
+ +
+ )} +
{props.title ? (
diff --git a/src/client/components/shared/MetadataPanel.tsx b/src/client/components/shared/MetadataPanel.tsx index dba1092..3d24a92 100644 --- a/src/client/components/shared/MetadataPanel.tsx +++ b/src/client/components/shared/MetadataPanel.tsx @@ -34,11 +34,10 @@ export const MetadataPanel = (props: IMetadatPanelProps): ReactElement => { { name: "rawFileDetails", content: () => ( -
+
- {isMissing && ( - - )}

{issueName}

@@ -87,6 +86,13 @@ export const MetadataPanel = (props: IMetadatPanelProps): ReactElement => { )} + {/* Missing file Icon */} + {isMissing && ( + + + + )} + {/* Uncompressed version available? */} {rawFileDetails.archive?.uncompressed && ( @@ -188,7 +194,6 @@ export const MetadataPanel = (props: IMetadatPanelProps): ReactElement => { return (
- { const { sourceData, @@ -31,6 +50,27 @@ export const T2Table = (tableOptions: T2TableProps): ReactElement => { getRowClassName, } = tableOptions; + const sentinelRef = useRef(null); + const firstHeaderRowRef = useRef(null); + const [isSticky, setIsSticky] = useState(false); + const [firstRowHeight, setFirstRowHeight] = useState(0); + + useEffect(() => { + const sentinel = sentinelRef.current; + if (!sentinel) return; + const observer = new IntersectionObserver( + ([entry]) => setIsSticky(!entry.isIntersecting), + { threshold: 0 }, + ); + observer.observe(sentinel); + return () => observer.disconnect(); + }, []); + + useLayoutEffect(() => { + if (firstHeaderRowRef.current) + setFirstRowHeight(firstHeaderRowRef.current.getBoundingClientRect().height); + }, []); + const [{ pageIndex, pageSize }, setPagination] = useState({ pageIndex: 1, pageSize: 15, @@ -44,10 +84,7 @@ export const T2Table = (tableOptions: T2TableProps): ReactElement => { [pageIndex, pageSize], ); - /** - * Pagination control to move forward one page - * @returns void - */ + /** Advances to the next page and notifies the parent via {@link T2TableProps.paginationHandlers}. */ const goToNextPage = () => { setPagination({ pageIndex: pageIndex + 1, @@ -56,10 +93,7 @@ export const T2Table = (tableOptions: T2TableProps): ReactElement => { nextPage(pageIndex, pageSize); }; - /** - * Pagination control to move backward one page - * @returns void - **/ + /** Goes back one page and notifies the parent via {@link T2TableProps.paginationHandlers}. */ const goToPreviousPage = () => { setPagination({ pageIndex: pageIndex - 1, @@ -115,26 +149,33 @@ export const T2Table = (tableOptions: T2TableProps): ReactElement => {
+
- - {table.getHeaderGroups().map((headerGroup, groupIndex) => ( - - {headerGroup.headers.map((header, index) => ( - - ))} - - ))} + + {table.getHeaderGroups().map((headerGroup, groupIndex) => { + return ( + + {headerGroup.headers.map((header) => ( + + ))} + + ); + })} @@ -142,7 +183,7 @@ export const T2Table = (tableOptions: T2TableProps): ReactElement => { rowClickHandler(row)} - className={`border-b border-gray-200 dark:border-slate-700 hover:bg-slate-100/30 dark:hover:bg-slate-700/20 transition-colors cursor-pointer ${getRowClassName ? getRowClassName(row) : ""}`} + className={`border-b border-gray-200 dark:border-slate-700 transition-colors cursor-pointer ${getRowClassName ? getRowClassName(row) : "hover:bg-slate-100/30 dark:hover:bg-slate-700/20"}`} > {row.getVisibleCells().map((cell) => (
- {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext(), - )} -
+ {header.isPlaceholder + ? null + : flexRender(header.column.columnDef.header, header.getContext())} +
diff --git a/src/client/graphql/generated.ts b/src/client/graphql/generated.ts index 65e28ec..a9771c9 100644 --- a/src/client/graphql/generated.ts +++ b/src/client/graphql/generated.ts @@ -28,6 +28,16 @@ export type AcquisitionSourceInput = { wanted?: InputMaybe; }; +export type AddTorrentInput = { + comicObjectId: Scalars['ID']['input']; + torrentToDownload: Scalars['String']['input']; +}; + +export type AddTorrentResult = { + __typename?: 'AddTorrentResult'; + result?: Maybe; +}; + export type AppSettings = { __typename?: 'AppSettings'; bittorrent?: Maybe; @@ -405,6 +415,7 @@ export type ImportStats = { export type ImportStatus = { __typename?: 'ImportStatus'; isImported?: Maybe; + isRawFileMissing?: Maybe; matchedResult?: Maybe; tagged?: Maybe; }; @@ -609,6 +620,8 @@ export type Mutation = { __typename?: 'Mutation'; /** Placeholder for future mutations */ _empty?: Maybe; + /** Add a torrent to qBittorrent */ + addTorrent?: Maybe; analyzeImage: ImageAnalysisResult; applyComicVineMatch: Comic; bulkResolveMetadata: Array; @@ -626,6 +639,11 @@ export type Mutation = { }; +export type MutationAddTorrentArgs = { + input: AddTorrentInput; +}; + + export type MutationAnalyzeImageArgs = { imageFilePath: Scalars['String']['input']; }; @@ -768,6 +786,7 @@ export type PublisherStats = { export type Query = { __typename?: 'Query'; + _empty?: Maybe; analyzeMetadataConflicts: Array; bundles: Array; comic?: Maybe; @@ -1265,7 +1284,7 @@ export type GetRecentComicsQueryVariables = Exact<{ }>; -export type GetRecentComicsQuery = { __typename?: 'Query', comics: { __typename?: 'ComicConnection', totalCount: number, comics: Array<{ __typename?: 'Comic', id: string, createdAt?: string | null, updatedAt?: string | null, inferredMetadata?: { __typename?: 'InferredMetadata', issue?: { __typename?: 'Issue', name?: string | null, number?: number | null, year?: string | null, subtitle?: string | null } | null } | null, rawFileDetails?: { __typename?: 'RawFileDetails', name?: string | null, extension?: string | null, cover?: { __typename?: 'Cover', filePath?: string | null } | null, archive?: { __typename?: 'Archive', uncompressed?: boolean | null } | null } | null, sourcedMetadata?: { __typename?: 'SourcedMetadata', comicvine?: string | null, comicInfo?: string | null, locg?: { __typename?: 'LOCGMetadata', name?: string | null, publisher?: string | null, cover?: string | null } | null } | null, canonicalMetadata?: { __typename?: 'CanonicalMetadata', title?: { __typename?: 'MetadataField', value?: string | null } | null, series?: { __typename?: 'MetadataField', value?: string | null } | null, issueNumber?: { __typename?: 'MetadataField', value?: string | null } | null, publisher?: { __typename?: 'MetadataField', value?: string | null } | null } | null }> } }; +export type GetRecentComicsQuery = { __typename?: 'Query', comics: { __typename?: 'ComicConnection', totalCount: number, comics: Array<{ __typename?: 'Comic', id: string, createdAt?: string | null, updatedAt?: string | null, inferredMetadata?: { __typename?: 'InferredMetadata', issue?: { __typename?: 'Issue', name?: string | null, number?: number | null, year?: string | null, subtitle?: string | null } | null } | null, rawFileDetails?: { __typename?: 'RawFileDetails', name?: string | null, extension?: string | null, cover?: { __typename?: 'Cover', filePath?: string | null } | null, archive?: { __typename?: 'Archive', uncompressed?: boolean | null } | null } | null, sourcedMetadata?: { __typename?: 'SourcedMetadata', comicvine?: string | null, comicInfo?: string | null, locg?: { __typename?: 'LOCGMetadata', name?: string | null, publisher?: string | null, cover?: string | null } | null } | null, canonicalMetadata?: { __typename?: 'CanonicalMetadata', title?: { __typename?: 'MetadataField', value?: string | null } | null, series?: { __typename?: 'MetadataField', value?: string | null } | null, issueNumber?: { __typename?: 'MetadataField', value?: string | null } | null, publisher?: { __typename?: 'MetadataField', value?: string | null } | null } | null, importStatus?: { __typename?: 'ImportStatus', isRawFileMissing?: boolean | null } | null }> } }; export type GetWantedComicsQueryVariables = Exact<{ paginationOptions: PaginationOptionsInput; @@ -1706,6 +1725,9 @@ export const GetRecentComicsDocument = ` value } } + importStatus { + isRawFileMissing + } createdAt updatedAt } diff --git a/src/client/graphql/queries/dashboard.graphql b/src/client/graphql/queries/dashboard.graphql index b52de40..032efd0 100644 --- a/src/client/graphql/queries/dashboard.graphql +++ b/src/client/graphql/queries/dashboard.graphql @@ -93,6 +93,9 @@ query GetRecentComics($limit: Int) { value } } + importStatus { + isRawFileMissing + } createdAt updatedAt }