Compare commits

..

35 Commits

Author SHA1 Message Date
d438eb7069 📆 Wired up the datepicker to LoCG pull list 2024-02-06 05:55:45 -05:00
5873721308 🏗️ Continued refactoring of PullList, Volumes etc. 2024-02-04 21:58:15 -05:00
d8a45408cb 🏗️ Abstracted heading/subheading into Header 2024-01-30 06:09:17 -05:00
a3b1e68b06 🛝 Added keen-slider for pull list 2024-01-29 00:34:29 -05:00
6081b817e4 🏗️ Cleaning up useless files 2024-01-28 11:21:08 -05:00
ada803d3cb 🏗️ Refactoring Volume groups and wanted panel 2024-01-24 18:14:49 -05:00
c86d0d8b15 🔍 Fixing CV search page 2024-01-19 17:14:22 -05:00
c25dc40dac 🏗️ Fixed volume group card stacks on Dashboard 2024-01-15 22:52:35 -05:00
a2fe633502 🏗️ Fixed CV-sourced Volume info panel 2024-01-15 01:08:36 -05:00
3ac357e46a 🏗️ Fixed an invalidation query on DC++ download panel 2024-01-12 22:49:20 -05:00
7e7ccff1a1 🏗️ Fixed invalidation of archiveOps 2024-01-11 16:47:11 -05:00
9884da06ef 🏗️ Settings styling tweaks 2024-01-10 21:58:50 -05:00
b75862398d 🔎 Added a check for existing uncompressed archives 2024-01-08 17:29:57 -05:00
a2005fcadb 🔧 Started work on Edit Metadata panel 2024-01-08 15:45:10 -05:00
3b11b648c6 🔧 Fixed # symbol handling in URLs 2024-01-08 13:57:12 -05:00
f3abc07005 🏗️ Fix for encoding # in URIs 2024-01-08 13:45:05 -05:00
4e0e1068fa 🤐 Added a uncompress indicator 2024-01-07 22:12:47 -05:00
09151a99e9 🏗️ Refactored the search form 2024-01-04 09:24:56 -05:00
ad12c05514 🖌️ Cleaned up the form 2024-01-04 09:17:35 -05:00
4b40f7e9c2 🖌️ Styling tweaks to the side panel 2024-01-04 09:03:01 -05:00
9bbb066c38 🤼 Revamped CV match panel UX 2024-01-04 00:39:29 -05:00
d2873893b8 🏗️ Refactored the scored matches 2024-01-02 16:01:54 -05:00
e00e8c17d8 🤼 Cleaning up CV match panel 2023-12-31 23:55:15 -05:00
0ade3f9354 🏗️ Refactored the action menu 2023-12-31 18:16:07 -05:00
b11cd76e37 🧩 CV Match panel refactor WIP 2023-12-30 10:19:24 -05:00
6526e46edc 🃏 Styling the action menu 2023-12-30 09:19:48 -05:00
a33ebf542f 🔧 Tweaked Archive ops further 2023-12-30 01:31:04 -05:00
c8bdb54066 🏗 Fixed the archive ops panel 2023-12-30 00:50:28 -05:00
a25837e2aa 🔧 Fixing table styes 2023-12-29 23:37:07 -05:00
4b2f905dc5 🏗️ Changes to ComicDetail section 2023-12-29 22:24:37 -05:00
9db4bc239e 🏗️ Wired up search with RQ 2023-12-28 22:50:32 -05:00
63ab0784e3 🪑 Many changes to DC++ downloads table 2023-12-27 23:45:48 -05:00
d647feff4d 🪑 Cleaned up the DC++ search results table 2023-12-26 00:51:53 -05:00
8f42afe560 🪑 Styled download panel table 2023-12-25 00:27:35 -05:00
432fe58585 🗂️ Added tab icons and styles 2023-12-21 22:04:06 -05:00
19 changed files with 81 additions and 240 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 976 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 586 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 MiB

View File

@@ -278,9 +278,9 @@ export const AcquisitionPanel = (
)} )}
/> />
) : ( ) : (
<div className=""> <div className="column is-three-fifths">
<article className=""> <article className="message is-info">
<div className=""> <div className="message-body is-size-6 is-family-secondary">
AirDC++ is not configured. Please configure it in{" "} AirDC++ is not configured. Please configure it in{" "}
<code>Settings &gt; AirDC++ &gt; Connection</code>. <code>Settings &gt; AirDC++ &gt; Connection</code>.
</div> </div>

View File

@@ -12,7 +12,6 @@ import { Menu } from "./ActionMenu/Menu";
import { ArchiveOperations } from "./Tabs/ArchiveOperations"; import { ArchiveOperations } from "./Tabs/ArchiveOperations";
import { ComicInfoXML } from "./Tabs/ComicInfoXML"; import { ComicInfoXML } from "./Tabs/ComicInfoXML";
import AcquisitionPanel from "./AcquisitionPanel"; import AcquisitionPanel from "./AcquisitionPanel";
import TorrentSearchPanel from "./TorrentSearchPanel";
import DownloadsPanel from "./DownloadsPanel"; import DownloadsPanel from "./DownloadsPanel";
import { VolumeInformation } from "./Tabs/VolumeInformation"; import { VolumeInformation } from "./Tabs/VolumeInformation";
@@ -351,7 +350,7 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
</span> </span>
), ),
name: "Torrent Search", name: "Torrent Search",
content: <TorrentSearchPanel />, content: <>Torrents</>,
shouldShow: true, shouldShow: true,
}, },
{ {

View File

@@ -16,7 +16,7 @@ export const ComicVineDetails = (props): ReactElement => {
<div className="min-w-fit"> <div className="min-w-fit">
<Card <Card
imageUrl={data.volumeInformation.image.thumb_url} imageUrl={data.volumeInformation.image.thumb_url}
orientation={"cover-only"} orientation={"vertical-2"}
hasDetails={false} hasDetails={false}
// cardContainerStyle={{ maxWidth: 200 }} // cardContainerStyle={{ maxWidth: 200 }}
/> />

View File

@@ -1,77 +0,0 @@
import React, { useCallback, ReactElement, useEffect, useState } from "react";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import axios from "axios";
import { Form, Field } from "react-final-form";
import { PROWLARR_SERVICE_BASE_URI } from "../../constants/endpoints";
export const TorrentSearchPanel = (props): ReactElement => {
const [prowlarrSettingsData, setProwlarrSettingsData] = useState({});
const { data } = useQuery({
queryFn: async () =>
axios({
url: `${PROWLARR_SERVICE_BASE_URI}/search`,
method: "POST",
data: {
port: "9696",
apiKey: "c4f42e265fb044dc81f7e88bd41c3367",
offset: 0,
categories: [7030],
query: "the darkness",
host: "localhost",
limit: 100,
type: "search",
indexerIds: [2],
},
}),
queryKey: ["prowlarrSettingsData"],
});
console.log(data?.data);
return (
<>
<div className="mt-5">
<Form
onSubmit={() => {}}
initialValues={{}}
render={({ handleSubmit, form, submitting, pristine, values }) => (
<form onSubmit={handleSubmit}>
<Field name="issueName">
{({ input, meta }) => {
return (
<div className="max-w-fit">
<div className="flex flex-row bg-slate-300 dark:bg-slate-400 rounded-l-lg">
<div className="w-10 pl-2 pt-1 text-gray-400 dark:text-gray-200">
<i className="icon-[solar--magnifer-bold-duotone] h-7 w-7" />
</div>
<input
{...input}
className="dark:bg-slate-400 bg-slate-300 py-2 px-2 rounded-l-md border-gray-300 h-10 min-w-full dark:text-slate-800 sm:text-md sm:leading-5 focus:outline-none focus:shadow-outline-blue focus:border-blue-300"
placeholder="Enter a search term"
/>
<button
className="sm:mt-0 min-w-fit rounded-r-lg border border-green-400 dark:border-green-200 bg-green-200 px-3 py-1 text-gray-500 hover:bg-transparent hover:text-green-600 focus:outline-none focus:ring active:text-indigo-500"
type="submit"
>
<div className="flex flex-row">
Search Indexer
<div className="h-5 w-5 ml-1">
<i className="h-6 w-6 icon-[solar--magnet-bold-duotone]" />
</div>
</div>
</button>
</div>
</div>
);
}}
</Field>
</form>
)}
/>
</div>
</>
);
};
export default TorrentSearchPanel;

View File

@@ -5,6 +5,7 @@ import { WantedComicsList } from "./WantedComicsList";
import { VolumeGroups } from "./VolumeGroups"; import { VolumeGroups } from "./VolumeGroups";
import { LibraryStatistics } from "./LibraryStatistics"; import { LibraryStatistics } from "./LibraryStatistics";
import { PullList } from "./PullList"; import { PullList } from "./PullList";
import { getLibraryStatistics } from "../../actions/comicinfo.actions";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import axios from "axios"; import axios from "axios";
import { LIBRARY_SERVICE_BASE_URI } from "../../constants/endpoints"; import { LIBRARY_SERVICE_BASE_URI } from "../../constants/endpoints";
@@ -53,23 +54,16 @@ export const Dashboard = (): ReactElement => {
queryKey: ["volumeGroups"], queryKey: ["volumeGroups"],
}); });
const { data: statistics } = useQuery({ //
queryFn: async () => // const libraryStatistics = useSelector(
await axios({ // (state: RootState) => state.comicInfo.libraryStatistics,
url: `${LIBRARY_SERVICE_BASE_URI}/libraryStatistics`, // );
method: "GET",
}),
queryKey: ["libraryStatistics"],
});
return ( return (
<div className="container mx-auto max-w-full"> <div className="container mx-auto max-w-full">
<PullList /> <PullList />
{recentComics && <RecentlyImported comics={recentComics?.data.docs} />} {recentComics && <RecentlyImported comics={recentComics?.data.docs} />}
{/* Wanted comics */} {/* Wanted comics */}
<WantedComicsList comics={wantedComics?.data?.docs} /> <WantedComicsList comics={wantedComics?.data?.docs} />
{/* Library Statistics */}
{statistics && <LibraryStatistics stats={statistics?.data} />}
{/* Volume groups */} {/* Volume groups */}
<VolumeGroups volumeGroups={volumeGroups?.data} /> <VolumeGroups volumeGroups={volumeGroups?.data} />
</div> </div>

View File

@@ -1,99 +1,113 @@
import React, { ReactElement, useEffect } from "react"; import React, { ReactElement, useEffect } from "react";
import prettyBytes from "pretty-bytes"; import prettyBytes from "pretty-bytes";
import { isEmpty, isUndefined, map } from "lodash"; import { isEmpty, isUndefined, map } from "lodash";
import Header from "../shared/Header";
export const LibraryStatistics = ( export const LibraryStatistics = (
props: ILibraryStatisticsProps, props: ILibraryStatisticsProps,
): ReactElement => { ): ReactElement => {
const { stats } = props; // const { stats } = props;
return ( return (
<div className="mt-5"> <div className="mt-5">
<Header <h4 className="title is-4">
headerContent="Your Library In Numbers" <i className="fa-solid fa-chart-simple"></i> Your Library In Numbers
subHeaderContent={ </h4>
<span className="text-md">A brief snapshot of your library.</span> <p className="subtitle is-7">A brief snapshot of your library.</p>
} <div className="columns is-multiline">
iconClassNames="fa-solid fa-binoculars mr-2" <div className="column is-narrow is-two-quarter">
/> <dl className="box">
<dd className="is-size-4">
<div className="mt-3"> <span className="has-text-weight-bold">
<div className="flex flex-row gap-5"> {props.stats.totalDocuments}
<div className="flex flex-col rounded-lg bg-green-100 dark:bg-green-200 px-4 py-6 text-center"> </span>{" "}
<dt className="text-lg font-medium text-gray-500">Library size</dt> files
<dd className="text-3xl text-green-600 md:text-5xl">
{props.stats.totalDocuments} files
</dd> </dd>
<dd> <dd className="is-size-4">
<span className="text-2xl text-green-600"> Library size
<span className="has-text-weight-bold">
{" "}
{props.stats.comicDirectorySize && {props.stats.comicDirectorySize &&
prettyBytes(props.stats.comicDirectorySize)} prettyBytes(props.stats.comicDirectorySize)}
</span> </span>
</dd> </dd>
</div>
{/* comicinfo and comicvine tagged issues */}
<div className="flex flex-col gap-4">
{!isUndefined(props.stats.statistics) && {!isUndefined(props.stats.statistics) &&
!isEmpty(props.stats.statistics[0].issues) && ( !isEmpty(props.stats.statistics[0].issues) && (
<div className="flex flex-col h-fit rounded-lg bg-green-100 dark:bg-green-200 px-4 py-3 text-center"> <dd className="is-size-6">
<span className="text-xl"> <span className="has-text-weight-bold">
{props.stats.statistics[0].issues.length} {props.stats.statistics[0].issues.length}
</span>{" "} </span>{" "}
tagged with ComicVine tagged with ComicVine
</div> </dd>
)} )}
{!isUndefined(props.stats.statistics) && {!isUndefined(props.stats.statistics) &&
!isEmpty(props.stats.statistics[0].issuesWithComicInfoXML) && ( !isEmpty(props.stats.statistics[0].issuesWithComicInfoXML) && (
<div className="flex flex-col h-fit rounded-lg bg-green-100 dark:bg-green-200 px-4 py-3 text-center"> <dd className="is-size-6">
<span className="text-xl"> <span className="has-text-weight-bold">
{props.stats.statistics[0].issuesWithComicInfoXML.length} {props.stats.statistics[0].issuesWithComicInfoXML.length}
</span>{" "} </span>{" "}
with
<span className="tag is-warning has-text-weight-bold mr-2 ml-1"> <span className="tag is-warning has-text-weight-bold mr-2 ml-1">
with ComicInfo.xml ComicInfo.xml
</span> </span>
</div> </dd>
)} )}
</div> </dl>
</div>
<div className=""> <div className="p-3 column is-one-quarter">
{!isUndefined(props.stats.statistics) && <dl className="box">
!isEmpty(props.stats.statistics[0].fileTypes) && <dd className="is-size-6">
map(props.stats.statistics[0].fileTypes, (fileType, idx) => { <span className="has-text-weight-bold"></span> Issues
return ( </dd>
<span <dd className="is-size-6">
key={idx} <span className="has-text-weight-bold">304</span> Volumes
className="flex flex-col mb-4 h-fit text-xl rounded-lg bg-green-100 dark:bg-green-200 px-4 py-3 text-center" </dd>
> <dd className="is-size-6">
{fileType.data.length} {fileType._id} {!isUndefined(props.stats.statistics) &&
</span> !isEmpty(props.stats.statistics[0].fileTypes) &&
); map(props.stats.statistics[0].fileTypes, (fileType, idx) => {
})} return (
</div> <span key={idx}>
<span className="has-text-weight-bold">
{fileType.data.length}
</span>
<span className="tag is-warning has-text-weight-bold mr-2 ml-1">
{fileType._id}
</span>
</span>
);
})}
</dd>
</dl>
</div>
{/* file types */} {/* file types */}
<div className="flex flex-col h-fit text-lg rounded-lg bg-green-100 dark:bg-green-200 px-4 py-3"> <div className="p-3 column is-two-fifths">
{/* publisher with most issues */} {/* publisher with most issues */}
<dl className="box">
{!isUndefined(props.stats.statistics) && {!isUndefined(props.stats.statistics) &&
!isEmpty( !isEmpty(
props.stats.statistics[0].publisherWithMostComicsInLibrary[0], props.stats.statistics[0].publisherWithMostComicsInLibrary[0],
) && ( ) && (
<> <dd className="is-size-6">
<span className=""> <span className="has-text-weight-bold">
{ {
props.stats.statistics[0] props.stats.statistics[0]
.publisherWithMostComicsInLibrary[0]._id .publisherWithMostComicsInLibrary[0]._id
} }
</span> </span>
{" has the most issues "} {" has the most issues "}
<span className=""> <span className="has-text-weight-bold">
{ {
props.stats.statistics[0] props.stats.statistics[0]
.publisherWithMostComicsInLibrary[0].count .publisherWithMostComicsInLibrary[0].count
} }
</span> </span>
</> </dd>
)} )}
</div> <dd className="is-size-6">
<span className="has-text-weight-bold">304</span> Volumes
</dd>
</dl>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -82,17 +82,7 @@ export const PullList = (): ReactElement => {
<div className="content"> <div className="content">
<Header <Header
headerContent="Discover" headerContent="Discover"
subHeaderContent={ subHeaderContent="Pull List aggregated for the week from League Of Comic Geeks"
<span className="text-md">
Pull List aggregated for the week from{" "}
<span className="underline">
<a href="https://leagueofcomicgeeks.com/comics/new-comics">
League Of Comic Geeks
</a>
<i className="icon-[solar--arrow-right-up-outline] w-4 h-4" />
</span>
</span>
}
iconClassNames="fa-solid fa-binoculars mr-2" iconClassNames="fa-solid fa-binoculars mr-2"
link="/pull-list/all/" link="/pull-list/all/"
/> />
@@ -111,10 +101,7 @@ export const PullList = (): ReactElement => {
/> />
{inputValue && ( {inputValue && (
<div className="text-sm"> <div className="text-sm">
Showing pull list for{" "} Showing pull list for <span>{inputValue}</span>
<span className="inline-flex mb-2 items-center bg-slate-50 text-slate-800 text-xs font-medium px-2.5 py-1 rounded-md dark:text-slate-900 dark:bg-slate-400">
{inputValue}
</span>
</div> </div>
)} )}
</div> </div>

View File

@@ -1,62 +0,0 @@
import React from "react";
import { useQuery } from "@tanstack/react-query";
import { Form, Field } from "react-final-form";
import { PROWLARR_SERVICE_BASE_URI } from "../../../constants/endpoints";
import axios from "axios";
export const ProwlarrSettingsForm = (props) => {
const { data } = useQuery({
queryFn: async (): any => {
return await axios({
url: `${PROWLARR_SERVICE_BASE_URI}/getIndexers`,
method: "POST",
data: {
host: "localhost",
port: "9696",
apiKey: "c4f42e265fb044dc81f7e88bd41c3367",
},
});
},
queryKey: ["prowlarrConnectionResult"],
});
console.log(data);
const submitHandler = () => {};
const initialData = {};
return (
<>
Prowlarr Settings.
<Form
onSubmit={submitHandler}
initialValues={initialData}
render={({ handleSubmit }) => (
<form>
<article
role="alert"
className="mt-4 rounded-lg max-w-screen-md border-s-4 border-blue-500 bg-blue-50 p-4 dark:border-s-4 dark:border-blue-600 dark:bg-blue-300 dark:text-slate-600"
>
<div>
<p>Configure Prowlarr integration here.</p>
<p>
Note that you need a Prowlarr instance hosted and running to
configure the integration.
</p>
<p>
See{" "}
<a
className="underline"
href="http://airdcpp.net/docs/installation/installation.html"
>
here
</a>{" "}
for Prowlarr installation instructions for various platforms.
</p>
</div>
</article>
</form>
)}
/>
</>
);
};
export default ProwlarrSettingsForm;

View File

@@ -3,7 +3,6 @@ import { AirDCPPSettingsForm } from "./AirDCPPSettings/AirDCPPSettingsForm";
import { AirDCPPHubsForm } from "./AirDCPPSettings/AirDCPPHubsForm"; import { AirDCPPHubsForm } from "./AirDCPPSettings/AirDCPPHubsForm";
import { QbittorrentConnectionForm } from "./QbittorrentSettings/QbittorrentConnectionForm"; import { QbittorrentConnectionForm } from "./QbittorrentSettings/QbittorrentConnectionForm";
import { SystemSettingsForm } from "./SystemSettings/SystemSettingsForm"; import { SystemSettingsForm } from "./SystemSettings/SystemSettingsForm";
import ProwlarrSettingsForm from "./ProwlarrSettings/ProwlarrSettingsForm";
import { ServiceStatuses } from "../ServiceStatuses/ServiceStatuses"; import { ServiceStatuses } from "../ServiceStatuses/ServiceStatuses";
import settingsObject from "../../constants/settings/settingsMenu.json"; import settingsObject from "../../constants/settings/settingsMenu.json";
import { isUndefined, map } from "lodash"; import { isUndefined, map } from "lodash";
@@ -38,14 +37,6 @@ export const Settings = (props: ISettingsProps): ReactElement => {
</div> </div>
), ),
}, },
{
id: "prwlr-connection",
content: (
<>
<ProwlarrSettingsForm />
</>
),
},
{ {
id: "core-service", id: "core-service",
content: <>a</>, content: <>a</>,

View File

@@ -3,7 +3,7 @@ import { Link } from "react-router-dom";
type IHeaderProps = { type IHeaderProps = {
headerContent: string; headerContent: string;
subHeaderContent: ReactElement; subHeaderContent: string;
iconClassNames: string; iconClassNames: string;
link?: string; link?: string;
}; };

View File

@@ -90,10 +90,3 @@ export const QBITTORRENT_SERVICE_BASE_URI = hostURIBuilder({
port: "3060", port: "3060",
apiPath: `/api/qbittorrent`, apiPath: `/api/qbittorrent`,
}); });
export const PROWLARR_SERVICE_BASE_URI = hostURIBuilder({
protocol: "http",
host: import.meta.env.UNDERLYING_HOSTNAME || "localhost",
port: "3060",
apiPath: `/api/prowlarr`,
});

View File

@@ -57,7 +57,7 @@
"displayName": "Prowlarr", "displayName": "Prowlarr",
"children": [ "children": [
{ {
"id": "prwlr-connection", "id": "prowlarr-connection",
"displayName": "Connection" "displayName": "Connection"
}, },
{ {

View File

@@ -8,6 +8,7 @@ import { ErrorPage } from "./components/shared/ErrorPage";
const rootEl = document.getElementById("root"); const rootEl = document.getElementById("root");
const root = createRoot(rootEl); const root = createRoot(rootEl);
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import Import from "./components/Import/Import"; import Import from "./components/Import/Import";
import Dashboard from "./components/Dashboard/Dashboard"; import Dashboard from "./components/Dashboard/Dashboard";
import Search from "./components/Search/Search"; import Search from "./components/Search/Search";
@@ -46,5 +47,6 @@ const router = createBrowserRouter([
root.render( root.render(
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<RouterProvider router={router} /> <RouterProvider router={router} />
<ReactQueryDevtools initialIsOpen={true} />
</QueryClientProvider>, </QueryClientProvider>,
); );