Dark mode 2 #100

Merged
rishighan merged 35 commits from dark-mode-2 into main 2024-02-06 10:58:56 +00:00
15 changed files with 563 additions and 377 deletions
Showing only changes of commit 5873721308 - Show all commits

View File

@@ -19,6 +19,7 @@
"@dnd-kit/sortable": "^7.0.2", "@dnd-kit/sortable": "^7.0.2",
"@dnd-kit/utilities": "^3.2.1", "@dnd-kit/utilities": "^3.2.1",
"@fortawesome/fontawesome-free": "^6.3.0", "@fortawesome/fontawesome-free": "^6.3.0",
"@popperjs/core": "^2.11.8",
"@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-node-resolve": "^15.0.1",
"@tanstack/react-query": "^5.0.5", "@tanstack/react-query": "^5.0.5",
"@tanstack/react-table": "^8.9.3", "@tanstack/react-table": "^8.9.3",
@@ -37,6 +38,7 @@
"filename-parser": "^1.0.2", "filename-parser": "^1.0.2",
"final-form": "^4.20.2", "final-form": "^4.20.2",
"final-form-arrays": "^3.0.2", "final-form-arrays": "^3.0.2",
"focus-trap-react": "^10.2.3",
"history": "^5.3.0", "history": "^5.3.0",
"html-to-text": "^8.1.0", "html-to-text": "^8.1.0",
"immer": "^10.0.3", "immer": "^10.0.3",
@@ -49,13 +51,14 @@
"react": "^18.2.0", "react": "^18.2.0",
"react-collapsible": "^2.9.0", "react-collapsible": "^2.9.0",
"react-comic-viewer": "^0.4.0", "react-comic-viewer": "^0.4.0",
"react-day-picker": "^8.6.0", "react-day-picker": "^8.10.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-fast-compare": "^3.2.0", "react-fast-compare": "^3.2.0",
"react-final-form": "^6.5.9", "react-final-form": "^6.5.9",
"react-final-form-arrays": "^3.1.4", "react-final-form-arrays": "^3.1.4",
"react-loader-spinner": "^4.0.0", "react-loader-spinner": "^4.0.0",
"react-modal": "^3.15.1", "react-modal": "^3.15.1",
"react-popper": "^2.3.0",
"react-router": "^6.9.0", "react-router": "^6.9.0",
"react-router-dom": "^6.9.0", "react-router-dom": "^6.9.0",
"react-select": "^5.8.0", "react-select": "^5.8.0",

View File

@@ -6,14 +6,9 @@ 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 { getLibraryStatistics } from "../../actions/comicinfo.actions";
import { isEmpty, isNil, isUndefined } from "lodash"; import { useQuery } from "@tanstack/react-query";
import Header from "../shared/Header";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import axios from "axios"; import axios from "axios";
import { import { LIBRARY_SERVICE_BASE_URI } from "../../constants/endpoints";
LIBRARY_SERVICE_BASE_URI,
LIBRARY_SERVICE_HOST,
} from "../../constants/endpoints";
export const Dashboard = (): ReactElement => { export const Dashboard = (): ReactElement => {
const { data: recentComics } = useQuery({ const { data: recentComics } = useQuery({
@@ -65,9 +60,7 @@ export const Dashboard = (): ReactElement => {
// ); // );
return ( return (
<div className="container mx-auto max-w-full"> <div className="container mx-auto max-w-full">
<h1>Dashboard</h1>
<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} />

View File

@@ -1,10 +1,11 @@
import React, { ReactElement } from "react"; import React, { ReactElement, useState } from "react";
import { map } from "lodash"; import { map } from "lodash";
import Card from "../shared/Carda"; import Card from "../shared/Carda";
import Header from "../shared/Header"; import Header from "../shared/Header";
import { importToDB } from "../../actions/fileops.actions"; import { importToDB } from "../../actions/fileops.actions";
import ellipsize from "ellipsize"; import ellipsize from "ellipsize";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import axios from "axios"; import axios from "axios";
import rateLimiter from "axios-rate-limit"; import rateLimiter from "axios-rate-limit";
import { setupCache } from "axios-cache-interceptor"; import { setupCache } from "axios-cache-interceptor";
@@ -12,6 +13,8 @@ import { useQuery } from "@tanstack/react-query";
import "keen-slider/keen-slider.min.css"; import "keen-slider/keen-slider.min.css";
import { useKeenSlider } from "keen-slider/react"; import { useKeenSlider } from "keen-slider/react";
import { COMICVINE_SERVICE_URI } from "../../constants/endpoints"; import { COMICVINE_SERVICE_URI } from "../../constants/endpoints";
import { Field, Form } from "react-final-form";
import DatePickerDialog from "../shared/DatePicker";
type PullListProps = { type PullListProps = {
issues: any; issues: any;
@@ -24,6 +27,13 @@ const http = rateLimiter(axios.create(), {
}); });
const cachedAxios = setupCache(axios); const cachedAxios = setupCache(axios);
export const PullList = (): ReactElement => { export const PullList = (): ReactElement => {
// datepicker
const [selected, setSelected] = useState<Date>();
let footer = <p>Please pick a day.</p>;
if (selected) {
footer = <p>You picked {format(selected, "PP")}.</p>;
}
// keen slider // keen slider
const [sliderRef, instanceRef] = useKeenSlider( const [sliderRef, instanceRef] = useKeenSlider(
{ {
@@ -47,15 +57,15 @@ export const PullList = (): ReactElement => {
data: pullList, data: pullList,
isSuccess, isSuccess,
isLoading, isLoading,
isError,
} = useQuery({ } = useQuery({
queryFn: async () => queryFn: async (): any =>
await cachedAxios(`${COMICVINE_SERVICE_URI}/getWeeklyPullList`, { await cachedAxios(`${COMICVINE_SERVICE_URI}/getWeeklyPullList`, {
method: "get", method: "get",
params: { startDate: "2024-2-15", pageSize: "15", currentPage: "1" }, params: { startDate: "2024-2-20", pageSize: "15", currentPage: "1" },
}), }),
queryKey: ["pullList"], queryKey: ["pullList"],
}); });
console.log(pullList?.data.result);
const addToLibrary = (sourceName: string, locgMetadata) => const addToLibrary = (sourceName: string, locgMetadata) =>
importToDB(sourceName, { locg: locgMetadata }); importToDB(sourceName, { locg: locgMetadata });
@@ -65,6 +75,7 @@ export const PullList = (): ReactElement => {
const previous = () => { const previous = () => {
// sliderRef.slickPrev(); // sliderRef.slickPrev();
}; };
return ( return (
<> <>
<div className="content"> <div className="content">
@@ -73,24 +84,30 @@ export const PullList = (): ReactElement => {
subHeaderContent="Pull List aggregated for the week from League Of Comic Geeks" subHeaderContent="Pull List aggregated for the week from League Of Comic Geeks"
iconClassNames="fa-solid fa-binoculars mr-2" iconClassNames="fa-solid fa-binoculars mr-2"
/> />
<div className="field is-grouped"> <div className="flex flex-row gap-5 mb-5">
{/* select week */} {/* select week */}
<div className="control"> <div className="flex flex-row gap-4 my-3">
<div className="select is-small"> <Form
<select> onSubmit={() => {}}
<option>Select Week</option> render={({ handleSubmit }) => (
<option>With options</option> <form>
</select> {/* week selection for pull list */}
</div>
</div> <DatePickerDialog />
</form>
)}
/>
<div>
{/* See all pull list issues */} {/* See all pull list issues */}
<div className="control">
<Link to={"/pull-list/all/"}> <Link to={"/pull-list/all/"}>
<button className="button is-small">View all issues</button> <button className="flex space-x-1 sm:flex-row sm:items-center rounded-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">
View all issues
</button>
</Link> </Link>
</div> </div>
</div> </div>
</div> </div>
</div>
{isSuccess && !isLoading && ( {isSuccess && !isLoading && (
<div ref={sliderRef} className="keen-slider flex flex-row"> <div ref={sliderRef} className="keen-slider flex flex-row">
@@ -103,21 +120,30 @@ export const PullList = (): ReactElement => {
hasDetails hasDetails
title={ellipsize(issue.name, 25)} title={ellipsize(issue.name, 25)}
> >
<div className="px-1 py-1"> <div className="px-1">
<span className="text-xs ">{issue.publisher}</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">
{issue.publisher}
</span>
<div className="flex flex-row justify-end">
<button <button
className="" className="flex space-x-1 mb-2 sm:mt-0 sm:flex-row sm:items-center rounded-lg border border-green-400 dark:border-green-200 bg-green-200 px-2 py-1 text-gray-500 hover:bg-transparent hover:text-green-600 focus:outline-none focus:ring active:text-indigo-500"
onClick={() => addToLibrary("locg", issue)} onClick={() => addToLibrary("locg", issue)}
> >
<i className="icon-[solar--add-square-bold-duotone] w-5 h-5 mr-2"></i>{" "}
Want Want
</button> </button>
</div> </div>
</div>
</Card> </Card>
</div> </div>
); );
})} })}
</div> </div>
)} )}
{isLoading ? <div>Loading...</div> : null}
{isError ? (
<div>An error occurred while retrieving the pull list.</div>
) : null}
</> </>
); );
}; };

View File

@@ -19,6 +19,7 @@ export const VolumeGroups = (props): ReactElement => {
headerContent="Volumes" headerContent="Volumes"
subHeaderContent="Based on ComicVine Volume information" subHeaderContent="Based on ComicVine Volume information"
iconClassNames="fa-solid fa-binoculars mr-2" iconClassNames="fa-solid fa-binoculars mr-2"
link={"/volumes"}
/> />
<div className="grid grid-cols-5 gap-6 mt-3"> <div className="grid grid-cols-5 gap-6 mt-3">
{map(deduplicatedGroups, (data) => { {map(deduplicatedGroups, (data) => {
@@ -36,7 +37,7 @@ export const VolumeGroups = (props): ReactElement => {
{ellipsize(data.volumes.name, 48)} {ellipsize(data.volumes.name, 48)}
</Link> </Link>
</div> </div>
{/* issue count */}
<span className="inline-flex mt-1 items-center bg-slate-50 text-slate-800 text-xs font-medium px-2.5 py-0.5 rounded-md dark:text-slate-600 dark:bg-slate-400"> <span className="inline-flex mt-1 items-center bg-slate-50 text-slate-800 text-xs font-medium px-2.5 py-0.5 rounded-md dark:text-slate-600 dark:bg-slate-400">
<span className="pr-1 pt-1"> <span className="pr-1 pt-1">
<i className="icon-[solar--documents-minimalistic-bold-duotone] w-5 h-5"></i> <i className="icon-[solar--documents-minimalistic-bold-duotone] w-5 h-5"></i>

View File

@@ -15,15 +15,14 @@ export const WantedComicsList = ({
comics, comics,
}: WantedComicsListProps): ReactElement => { }: WantedComicsListProps): ReactElement => {
const navigate = useNavigate(); const navigate = useNavigate();
const navigateToWantedComics = (row) => {
navigate(`/wanted/all`);
};
return ( return (
<> <>
<Header <Header
headerContent="Wanted Comics" headerContent="Wanted Comics"
subHeaderContent="Comics marked as wanted from various sources" subHeaderContent="Comics marked as wanted from various sources"
iconClassNames="fa-solid fa-binoculars mr-2" iconClassNames="fa-solid fa-binoculars mr-2"
link={"/wanted"}
/> />
<div className="grid grid-cols-5 gap-6 mt-3"> <div className="grid grid-cols-5 gap-6 mt-3">
{map( {map(

View File

@@ -248,7 +248,7 @@ export const Library = (): ReactElement => {
<div> <div>
<section> <section>
<header className="bg-slate-200 dark:bg-slate-500"> <header className="bg-slate-200 dark:bg-slate-500">
<div className="px-2 py-2 sm:px-6 sm:py-8 lg:px-8 lg:py-4"> <div className="mx-auto max-w-screen-xl px-2 py-2 sm:px-6 sm:py-8 lg:px-8 lg:py-4">
<div className="sm:flex sm:items-center sm:justify-between"> <div className="sm:flex sm:items-center sm:justify-between">
<div className="text-center sm:text-left"> <div className="text-center sm:text-left">
<h1 className="text-2xl font-bold text-gray-900 dark:text-white sm:text-3xl"> <h1 className="text-2xl font-bold text-gray-900 dark:text-white sm:text-3xl">

View File

@@ -26,7 +26,6 @@ export const Search = ({}: ISearchProps): ReactElement => {
const [searchQuery, setSearchQuery] = useState(""); const [searchQuery, setSearchQuery] = useState("");
const [comicVineMetadata, setComicVineMetadata] = useState({}); const [comicVineMetadata, setComicVineMetadata] = useState({});
const getCVSearchResults = (searchQuery) => { const getCVSearchResults = (searchQuery) => {
console.log(searchQuery);
setSearchQuery(searchQuery.search); setSearchQuery(searchQuery.search);
// queryClient.invalidateQueries({ queryKey: ["comicvineSearchResults"] }); // queryClient.invalidateQueries({ queryKey: ["comicvineSearchResults"] });
}; };
@@ -84,7 +83,6 @@ export const Search = ({}: ISearchProps): ReactElement => {
enabled: !isNil(comicVineMetadata.comicData), enabled: !isNil(comicVineMetadata.comicData),
}); });
console.log(comicVineMetadata);
const addToLibrary = (sourceName: string, comicData) => const addToLibrary = (sourceName: string, comicData) =>
setComicVineMetadata({ sourceName, comicData }); setComicVineMetadata({ sourceName, comicData });
@@ -93,11 +91,24 @@ export const Search = ({}: ISearchProps): ReactElement => {
}; };
return ( return (
<> <div>
<section className="container"> <section>
<div className=""> <header className="bg-slate-200 dark:bg-slate-500">
<h1 className="">Search</h1> <div className="px-2 py-2 sm:px-6 sm:py-8 lg:px-8 lg:py-4">
<div className="sm:flex sm:items-center sm:justify-between">
<div className="text-center sm:text-left">
<h1 className="text-2xl font-bold text-gray-900 dark:text-white sm:text-3xl">
Search
</h1>
<p className="mt-1.5 text-sm text-gray-500 dark:text-white">
Browse your comic book collection.
</p>
</div>
</div>
</div>
</header>
<div className="mx-auto max-w-screen-sm px-4 py-4 sm:px-6 sm:py-8 lg:px-8">
<Form <Form
onSubmit={getCVSearchResults} onSubmit={getCVSearchResults}
initialValues={{ initialValues={{
@@ -105,30 +116,39 @@ export const Search = ({}: ISearchProps): ReactElement => {
}} }}
render={({ handleSubmit, form, submitting, pristine, values }) => ( render={({ handleSubmit, form, submitting, pristine, values }) => (
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<div> <div className="flex flex-row w-full">
<div className="flex flex-row bg-slate-300 dark:bg-slate-500 rounded-l-lg p-2 min-w-full">
<div className="w-10 text-gray-400">
<i className="icon-[solar--magnifer-bold-duotone] h-7 w-7" />
</div>
<Field name="search"> <Field name="search">
{({ input, meta }) => { {({ input, meta }) => {
return ( return (
<input <input
{...input} {...input}
className="input main-search-bar is-large" className="bg-slate-300 dark:bg-slate-500 outline-none text-lg text-gray-700 w-full"
placeholder="Type an issue/volume name" placeholder="Type an issue/volume name"
/> />
); );
}} }}
</Field> </Field>
</div> </div>
<div className="column">
<button type="submit" className="button is-medium"> <button
className="sm:mt-0 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"
>
Search Search
</button> </button>
</div> </div>
</form> </form>
)} )}
/> />
</div>
{!isNil(comicVineSearchResults?.data.results) && {!isNil(comicVineSearchResults?.data.results) &&
!isEmpty(comicVineSearchResults?.data.results) ? ( !isEmpty(comicVineSearchResults?.data.results) ? (
<div className=""> <div className="mx-auto max-w-screen-xl px-4 py-4 sm:px-6 sm:py-8 lg:px-8">
{comicVineSearchResults.data.results.map((result) => { {comicVineSearchResults.data.results.map((result) => {
return isSuccess ? ( return isSuccess ? (
<div key={result.id} className="mb-5"> <div key={result.id} className="mb-5">
@@ -161,9 +181,7 @@ export const Search = ({}: ISearchProps): ReactElement => {
<div className="control"> <div className="control">
<div className="tags has-addons"> <div className="tags has-addons">
<span className="tag is-warning"> <span className="tag is-warning">{result.id}</span>
{result.id}
</span>
</div> </div>
</div> </div>
</div> </div>
@@ -199,22 +217,28 @@ export const Search = ({}: ISearchProps): ReactElement => {
})} })}
</div> </div>
) : ( ) : (
<article className="message is-dark is-half"> <div className="mx-auto mx-auto max-w-screen-md px-4 py-4 sm:px-6 sm:py-8 lg:px-8">
<div className="message-body"> <article
<p className="mb-2"> role="alert"
<span className="tag is-medium is-info is-light"> 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"
Search the ComicVine database >
</span> <div>
<p> Search the ComicVine database</p>
<p>
Note that you need an instance of AirDC++ already running to
use this form to connect to it.
</p>
<p>
Search and add issues, series and trade paperbacks to your Search and add issues, series and trade paperbacks to your
library. Then, download them using the configured AirDC++ or library. Then, download them using the configured AirDC++ or
torrent clients. torrent clients.
</p> </p>
</div> </div>
</article> </article>
)}
</div> </div>
)}
</section> </section>
</> </div>
); );
}; };

View File

@@ -4,80 +4,63 @@ import Card from "../shared/Carda";
import T2Table from "../shared/T2Table"; import T2Table from "../shared/T2Table";
import ellipsize from "ellipsize"; import ellipsize from "ellipsize";
import { convert } from "html-to-text"; import { convert } from "html-to-text";
import { isUndefined } from "lodash"; import { useQuery } from "@tanstack/react-query";
import axios from "axios";
import { SEARCH_SERVICE_BASE_URI } from "../../constants/endpoints";
export const Volumes = (props): ReactElement => { export const Volumes = (props): ReactElement => {
// const volumes = useSelector((state: RootState) => state.fileOps.volumes); // const volumes = useSelector((state: RootState) => state.fileOps.volumes);
useEffect(() => { const {
// dispatch( data: volumes,
// searchIssue( isSuccess,
// { isError,
// query: {}, isLoading,
// }, } = useQuery({
// { queryFn: async () =>
// pagination: { await axios({
// size: 25, url: `${SEARCH_SERVICE_BASE_URI}/searchIssue`,
// from: 0, method: "POST",
// }, data: {
// type: "volumes", query: {},
// trigger: "volumesPage", pagination: {
// }, size: 25,
// ), from: 0,
// ); },
}, []); type: "volumes",
trigger: "volumesPage",
},
}),
queryKey: ["volumes"],
});
console.log(volumes);
const columnData = useMemo( const columnData = useMemo(
() => [ (): any => [
{ {
header: "Volume Details", header: "Volume Details",
id: "volumeDetails", id: "volumeDetails",
minWidth: 450, minWidth: 450,
accessorKey: "_source", accessorKey: "_source",
cell: (row) => { cell: (row): any => {
const foo = row.getValue(); const foo = row.getValue();
return ( return (
<div className="columns"> <div className="flex flex-row gap-3 mt-5">
<div className="column">
<div className="comic-detail issue-metadata">
<dl>
<dd>
<div className="columns mt-2">
<div className="">
<Card <Card
imageUrl={ imageUrl={
foo.sourcedMetadata.comicvine.volumeInformation foo.sourcedMetadata.comicvine.volumeInformation.image
.image.thumb_url .small_url
} }
orientation={"vertical"} orientation={"cover-only"}
hasDetails={false} hasDetails={false}
// cardContainerStyle={{ maxWidth: 200 }}
/> />
</div> <div className="dark:bg-[#647587] bg-slate-200 p-3 rounded-lg h-fit">
<div className="column"> <span className="text-xl mb-1">
<dl> {foo.sourcedMetadata.comicvine.volumeInformation.name}
<dt>
<h6 className="name has-text-weight-medium mb-1">
{
foo.sourcedMetadata.comicvine
.volumeInformation.name
}
</h6>
</dt>
<dd className="is-size-7">
published by{" "}
<span className="has-text-weight-semibold">
{
foo.sourcedMetadata.comicvine
.volumeInformation.publisher.name
}
</span> </span>
</dd> <p>
<dd className="is-size-7">
<span>
{ellipsize( {ellipsize(
convert( convert(
foo.sourcedMetadata.comicvine foo.sourcedMetadata.comicvine.volumeInformation
.volumeInformation.description, .description,
{ {
baseElements: { baseElements: {
selectors: ["p"], selectors: ["p"],
@@ -86,42 +69,17 @@ export const Volumes = (props): ReactElement => {
), ),
120, 120,
)} )}
</span> </p>
</dd>
<dd className="is-size-7 mt-2">
<div className="field is-grouped is-grouped-multiline">
<div className="control">
<span className="tags">
<span className="tag is-success is-light has-text-weight-semibold">
Total Issues
</span>
<span className="tag is-success is-light">
{
foo.sourcedMetadata.comicvine
.volumeInformation.count_of_issues
}
</span>
</span>
</div>
</div>
</dd>
</dl>
</div>
</div>
</dd>
</dl>
</div>
</div> </div>
</div> </div>
); );
}, },
}, },
{ {
header: "Download Status", header: "Other Details",
columns: [ columns: [
{ {
header: "Files", header: "Downloads",
accessorKey: "_source.acquisition.directconnect", accessorKey: "_source.acquisition.directconnect",
align: "right", align: "right",
cell: (props) => { cell: (props) => {
@@ -142,12 +100,34 @@ export const Volumes = (props): ReactElement => {
}, },
}, },
{ {
header: "Type", header: "Publisher",
id: "Air", accessorKey: "_source.sourcedMetadata.comicvine.volumeInformation",
cell: (props): any => {
const row = props.getValue();
return <div className="mt-5 text-md">{row.publisher.name}</div>;
},
}, },
{ {
header: "Type", header: "Issue Count",
id: "dcc", accessorKey:
"_source.sourcedMetadata.comicvine.volumeInformation.count_of_issues",
cell: (props): any => {
const row = props.getValue();
return (
<div className="mt-5">
{/* issue count */}
<span className="inline-flex items-center bg-slate-50 text-slate-800 font-medium px-2.5 py-0.5 rounded-md dark:text-slate-600 dark:bg-slate-400">
<span className="pr-1 pt-1">
<i className="icon-[solar--documents-minimalistic-bold-duotone] w-6 h-6"></i>
</span>
<span className="text-lg text-slate-500 dark:text-slate-900">
{row}
</span>
</span>
</div>
);
},
}, },
], ],
}, },
@@ -155,17 +135,29 @@ export const Volumes = (props): ReactElement => {
[], [],
); );
return ( return (
<section className="container"> <div>
<div className="section"> <section className="">
<div className="header-area"> <header className="bg-slate-200 dark:bg-slate-500">
<h1 className="title">Volumes</h1> <div className="mx-auto max-w-screen-xl px-2 py-2 sm:px-6 sm:py-8 lg:px-8 lg:py-4">
<div className="sm:flex sm:items-center sm:justify-between">
<div className="text-center sm:text-left">
<h1 className="text-2xl font-bold text-gray-900 dark:text-white sm:text-3xl">
Volumes
</h1>
<p className="mt-1.5 text-sm text-gray-500 dark:text-white">
Browse your collection of volumes.
</p>
</div> </div>
{!isUndefined(volumes.hits) && ( </div>
</div>
</header>
{isSuccess ? (
<div> <div>
<div className="library"> <div className="library">
<T2Table <T2Table
sourceData={volumes?.hits} sourceData={volumes?.data.hits.hits}
totalPages={volumes.hits.length} totalPages={volumes?.data.hits.hits.length}
paginationHandlers={{ paginationHandlers={{
nextPage: () => {}, nextPage: () => {},
previousPage: () => {}, previousPage: () => {},
@@ -174,9 +166,13 @@ export const Volumes = (props): ReactElement => {
/> />
</div> </div>
</div> </div>
)} ) : null}
</div> {isError ? (
<div>An error was encountered while retrieving volumes</div>
) : null}
{isLoading ? <>Loading...</> : null}
</section> </section>
</div>
); );
}; };

View File

@@ -1,32 +1,36 @@
import React, { ReactElement, useCallback, useEffect, useMemo } from "react"; import React, { ReactElement, useCallback, useEffect, useMemo } from "react";
import { searchIssue } from "../../actions/fileops.actions";
import SearchBar from "../Library/SearchBar"; import SearchBar from "../Library/SearchBar";
import T2Table from "../shared/T2Table"; import T2Table from "../shared/T2Table";
import { isEmpty, isUndefined } from "lodash";
import MetadataPanel from "../shared/MetadataPanel"; import MetadataPanel from "../shared/MetadataPanel";
import { useQuery } from "@tanstack/react-query";
import axios from "axios";
import { SEARCH_SERVICE_BASE_URI } from "../../constants/endpoints";
export const WantedComics = (props): ReactElement => { export const WantedComics = (props): ReactElement => {
// const wantedComics = useSelector( const {
// (state: RootState) => state.fileOps.wantedComics, data: wantedComics,
// ); isSuccess,
useEffect(() => { isError,
// dispatch( isLoading,
// searchIssue( } = useQuery({
// { queryFn: async () =>
// query: {}, await axios({
// }, url: `${SEARCH_SERVICE_BASE_URI}/searchIssue`,
// { method: "POST",
// pagination: { data: {
// size: 25, query: {},
// from: 0,
// },
// type: "wanted",
// trigger: "wantedComicsPage"
// },
// ),
// );
}, []);
pagination: {
size: 25,
from: 0,
},
type: "wanted",
trigger: "wantedComicsPage",
},
}),
queryKey: ["wantedComics"],
enabled: true,
});
const columnData = [ const columnData = [
{ {
header: "Comic Information", header: "Comic Information",
@@ -36,7 +40,10 @@ export const WantedComics = (props): ReactElement => {
id: "comicDetails", id: "comicDetails",
minWidth: 350, minWidth: 350,
accessorFn: (data) => data, accessorFn: (data) => data,
cell: (value) => <MetadataPanel data={value.getValue()} />, cell: (value) => {
const row = value.getValue()._source;
return row && <MetadataPanel data={row} />;
},
}, },
], ],
}, },
@@ -45,8 +52,8 @@ export const WantedComics = (props): ReactElement => {
columns: [ columns: [
{ {
header: "Files", header: "Files",
accessorKey: "acquisition",
align: "right", align: "right",
accessorKey: "_source.acquisition",
cell: (props) => { cell: (props) => {
const { const {
directconnect: { downloads }, directconnect: { downloads },
@@ -69,7 +76,7 @@ export const WantedComics = (props): ReactElement => {
{ {
header: "Download Details", header: "Download Details",
id: "downloadDetails", id: "downloadDetails",
accessorKey: "acquisition", accessorKey: "_source.acquisition",
cell: (data) => ( cell: (data) => (
<ol> <ol>
{data.getValue().directconnect.downloads.map((download, idx) => { {data.getValue().directconnect.downloads.map((download, idx) => {
@@ -98,23 +105,23 @@ export const WantedComics = (props): ReactElement => {
* @returns void * @returns void
* *
**/ **/
const nextPage = useCallback((pageIndex: number, pageSize: number) => { // const nextPage = useCallback((pageIndex: number, pageSize: number) => {
dispatch( // dispatch(
searchIssue( // searchIssue(
{ // {
query: {}, // query: {},
}, // },
{ // {
pagination: { // pagination: {
size: pageSize, // size: pageSize,
from: pageSize * pageIndex + 1, // from: pageSize * pageIndex + 1,
}, // },
type: "wanted", // type: "wanted",
trigger: "wantedComicsPage", // trigger: "wantedComicsPage",
}, // },
), // ),
); // );
}, []); // }, []);
/** /**
* Pagination control that fetches the previous x (pageSize) items * Pagination control that fetches the previous x (pageSize) items
@@ -123,55 +130,71 @@ export const WantedComics = (props): ReactElement => {
* @param {number} pageSize * @param {number} pageSize
* @returns void * @returns void
**/ **/
const previousPage = useCallback((pageIndex: number, pageSize: number) => { // const previousPage = useCallback((pageIndex: number, pageSize: number) => {
let from = 0; // let from = 0;
if (pageIndex === 2) { // if (pageIndex === 2) {
from = (pageIndex - 1) * pageSize + 2 - 17; // from = (pageIndex - 1) * pageSize + 2 - 17;
} else { // } else {
from = (pageIndex - 1) * pageSize + 2 - 16; // from = (pageIndex - 1) * pageSize + 2 - 16;
} // }
dispatch( // dispatch(
searchIssue( // searchIssue(
{ // {
query: {}, // query: {},
}, // },
{ // {
pagination: { // pagination: {
size: pageSize, // size: pageSize,
from, // from,
}, // },
type: "wanted", // type: "wanted",
trigger: "wantedComicsPage", // trigger: "wantedComicsPage",
}, // },
), // ),
); // );
}, []); // }, []);
return ( return (
<section className="container"> <div className="">
<div className="section"> <section className="">
<div className="header-area"> <header className="bg-slate-200 dark:bg-slate-500">
<h1 className="title">Wanted Comics</h1> <div className="mx-auto max-w-screen-xl px-2 py-2 sm:px-6 sm:py-8 lg:px-8 lg:py-4">
<div className="sm:flex sm:items-center sm:justify-between">
<div className="text-center sm:text-left">
<h1 className="text-2xl font-bold text-gray-900 dark:text-white sm:text-3xl">
Wanted Comics
</h1>
<p className="mt-1.5 text-sm text-gray-500 dark:text-white">
Browse through comics you marked as "wanted."
</p>
</div> </div>
{!isEmpty(wantedComics) && ( </div>
</div>
</header>
{isSuccess ? (
<div> <div>
<div className="library"> <div className="library">
<T2Table <T2Table
sourceData={wantedComics} sourceData={wantedComics?.data.hits.hits}
totalPages={wantedComics.length} totalPages={wantedComics?.data.hits.hits.length}
columns={columnData} columns={columnData}
paginationHandlers={{ paginationHandlers={{
nextPage: nextPage, nextPage: () => {},
previousPage: previousPage, previousPage: () => {},
}} }}
// rowClickHandler={navigateToComicDetail} // rowClickHandler={navigateToComicDetail}
/> />
{/* pagination controls */} {/* pagination controls */}
</div> </div>
</div> </div>
)} ) : null}
</div> {isLoading ? <div>Loading...</div> : null}
{isError ? (
<div>An error occurred while retrieving the pull list.</div>
) : null}
</section> </section>
</div>
); );
}; };

View File

@@ -0,0 +1,103 @@
import React, { ChangeEventHandler, useRef, useState } from "react";
import { format, isValid, parse } from "date-fns";
import FocusTrap from "focus-trap-react";
import { DayPicker, SelectSingleEventHandler } from "react-day-picker";
import { usePopper } from "react-popper";
export default function DatePickerDialog() {
const [selected, setSelected] = useState<Date>();
const [inputValue, setInputValue] = useState<string>("");
const [isPopperOpen, setIsPopperOpen] = useState(false);
const popperRef = useRef<HTMLDivElement>(null);
const buttonRef = useRef<HTMLButtonElement>(null);
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(
null,
);
const popper = usePopper(popperRef.current, popperElement, {
placement: "bottom-start",
});
const closePopper = () => {
setIsPopperOpen(false);
buttonRef?.current?.focus();
};
const handleInputChange: ChangeEventHandler<HTMLInputElement> = (e) => {
setInputValue(e.currentTarget.value);
const date = parse(e.currentTarget.value, "y-MM-dd", new Date());
if (isValid(date)) {
setSelected(date);
} else {
setSelected(undefined);
}
};
const handleButtonClick = () => {
setIsPopperOpen(true);
};
const handleDaySelect: SelectSingleEventHandler = (date) => {
setSelected(date);
if (date) {
setInputValue(format(date, "y-MM-dd"));
closePopper();
} else {
setInputValue("");
}
};
return (
<div>
<div ref={popperRef}>
<input
size={12}
type="text"
placeholder={format(new Date(), "y-MM-dd")}
value={inputValue}
onChange={handleInputChange}
/>
<button
ref={buttonRef}
type="button"
aria-label="Pick a date"
onClick={handleButtonClick}
>
Pick a date
</button>
</div>
{isPopperOpen && (
<FocusTrap
active
focusTrapOptions={{
initialFocus: false,
allowOutsideClick: true,
clickOutsideDeactivates: true,
onDeactivate: closePopper,
fallbackFocus: buttonRef.current || undefined,
}}
>
<div
tabIndex={-1}
style={popper.styles.popper}
className="bg-slate-200 p-2 rounded-lg z-50"
{...popper.attributes.popper}
ref={setPopperElement}
role="dialog"
aria-label="DayPicker calendar"
>
<DayPicker
initialFocus={isPopperOpen}
mode="single"
defaultMonth={selected}
selected={selected}
onSelect={handleDaySelect}
/>
</div>
</FocusTrap>
)}
</div>
);
}

View File

@@ -1,20 +1,29 @@
import React, { ReactElement } from "react"; import React, { ReactElement } from "react";
import { Link } from "react-router-dom";
type IHeaderProps = { type IHeaderProps = {
headerContent: string; headerContent: string;
subHeaderContent: string; subHeaderContent: string;
iconClassNames: string; iconClassNames: string;
link?: string;
}; };
export const Header = (props: IHeaderProps): ReactElement => { export const Header = (props: IHeaderProps): ReactElement => {
return ( return (
<div className="mt-7"> <div className="mt-7">
<div className=""> <div className="">
<a className="" onClick={() => {}}> {props.link ? (
<Link to={props.link}>
<span className="text-xl"> <span className="text-xl">
<i className=""></i> {props.headerContent} <span className="underline">
{props.headerContent}{" "}
<i className="icon-[solar--arrow-right-up-outline] w-4 h-4" />
</span> </span>
</a> </span>
</Link>
) : (
<div className="text-xl">{props.headerContent}</div>
)}
<p className="">{props.subHeaderContent}</p> <p className="">{props.subHeaderContent}</p>
</div> </div>
</div> </div>

View File

@@ -16,6 +16,7 @@ interface IMetadatPanelProps {
containerStyle: any; containerStyle: any;
} }
export const MetadataPanel = (props: IMetadatPanelProps): ReactElement => { export const MetadataPanel = (props: IMetadatPanelProps): ReactElement => {
console.log(props);
const { const {
rawFileDetails, rawFileDetails,
inferredMetadata, inferredMetadata,
@@ -114,7 +115,6 @@ export const MetadataPanel = (props: IMetadatPanelProps): ReactElement => {
</span> </span>
</span> </span>
</dd> </dd>
<dd className="is-size-7"> <dd className="is-size-7">
<span> <span>
{ellipsize( {ellipsize(
@@ -127,42 +127,13 @@ export const MetadataPanel = (props: IMetadatPanelProps): ReactElement => {
)} )}
</span> </span>
</dd> </dd>
<dd className="is-size-7 mt-2"> <dd className="is-size-7 mt-2">
<div className="field is-grouped is-grouped-multiline"> <span className="my-3 mx-2">
<div className="control">
<span className="tags">
<span
className="tag is-success is-light has-text-weight-semibold"
style={props.tagsStyle}
>
{comicvine.volumeInformation.start_year} {comicvine.volumeInformation.start_year}
</span> </span>
<span
className="tag is-success is-light"
style={props.tagsStyle}
>
{comicvine.volumeInformation.count_of_issues} {comicvine.volumeInformation.count_of_issues}
</span>
</span>
</div>
<div className="control">
<div className="tags has-addons">
<span
className="tag is-primary is-light"
style={props.tagsStyle}
>
ComicVine ID ComicVine ID
</span>
<span
className="tag is-info is-light"
style={props.tagsStyle}
>
{comicvine.id} {comicvine.id}
</span>
</div>
</div>
</div>
</dd> </dd>
</dl> </dl>
), ),

View File

@@ -52,12 +52,12 @@ export const Navbar2 = (): ReactElement => {
</li> </li>
<li> <li>
<a <Link
to="/volumes"
className="text-gray-500 transition hover:text-gray-500/75 dark:text-white dark:hover:text-white/75" className="text-gray-500 transition hover:text-gray-500/75 dark:text-white dark:hover:text-white/75"
href="/"
> >
Volumes Volumes
</a> </Link>
</li> </li>
<li> <li>
@@ -69,12 +69,12 @@ export const Navbar2 = (): ReactElement => {
</a> </a>
</li> </li>
<li> <li>
<a <Link
className="text-gray-500 transition hover:text-gray-500/75 dark:text-white dark:hover:text-white/75" className="text-gray-500 transition hover:text-gray-500/75 dark:text-white dark:hover:text-white/75"
href="/search" to="/search"
> >
Comicvine Search Comicvine Search
</a> </Link>
</li> </li>
</ul> </ul>
</nav> </nav>

View File

@@ -14,6 +14,8 @@ import Dashboard from "./components/Dashboard/Dashboard";
import Search from "./components/Search/Search"; import Search from "./components/Search/Search";
import TabulatedContentContainer from "./components/Library/TabulatedContentContainer"; import TabulatedContentContainer from "./components/Library/TabulatedContentContainer";
import { ComicDetailContainer } from "./components/ComicDetail/ComicDetailContainer"; import { ComicDetailContainer } from "./components/ComicDetail/ComicDetailContainer";
import Volumes from "./components/Volumes/Volumes";
import WantedComics from "./components/WantedComics/WantedComics";
const queryClient = new QueryClient(); const queryClient = new QueryClient();
@@ -23,6 +25,7 @@ const router = createBrowserRouter([
element: <App />, element: <App />,
errorElement: <ErrorPage />, errorElement: <ErrorPage />,
children: [ children: [
{ path: "/", element: <Dashboard /> },
{ path: "dashboard", element: <Dashboard /> }, { path: "dashboard", element: <Dashboard /> },
{ path: "settings", element: <Settings /> }, { path: "settings", element: <Settings /> },
{ {
@@ -35,6 +38,8 @@ const router = createBrowserRouter([
}, },
{ path: "import", element: <Import path={"./comics"} /> }, { path: "import", element: <Import path={"./comics"} /> },
{ path: "search", element: <Search /> }, { path: "search", element: <Search /> },
{ path: "volumes", element: <Volumes /> },
{ path: "wanted", element: <WantedComics /> },
], ],
}, },
]); ]);

View File

@@ -1914,6 +1914,11 @@
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
"@popperjs/core@^2.11.8":
version "2.11.8"
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f"
integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
"@radix-ui/number@1.0.1": "@radix-ui/number@1.0.1":
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/@radix-ui/number/-/number-1.0.1.tgz#644161a3557f46ed38a042acf4a770e826021674" resolved "https://registry.yarnpkg.com/@radix-ui/number/-/number-1.0.1.tgz#644161a3557f46ed38a042acf4a770e826021674"
@@ -6012,6 +6017,21 @@ flow-parser@0.*:
resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.227.0.tgz#e50b65be9dc6810438c975e816a68005fbcd5107" resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.227.0.tgz#e50b65be9dc6810438c975e816a68005fbcd5107"
integrity sha512-nOygtGKcX/siZK/lFzpfdHEfOkfGcTW7rNroR1Zsz6T/JxSahPALXVt5qVHq/fgvMJuv096BTKbgxN3PzVBaDA== integrity sha512-nOygtGKcX/siZK/lFzpfdHEfOkfGcTW7rNroR1Zsz6T/JxSahPALXVt5qVHq/fgvMJuv096BTKbgxN3PzVBaDA==
focus-trap-react@^10.2.3:
version "10.2.3"
resolved "https://registry.yarnpkg.com/focus-trap-react/-/focus-trap-react-10.2.3.tgz#a5a2ea7fbb042ffa4337fde72758325ed0fb793a"
integrity sha512-YXBpFu/hIeSu6NnmV2xlXzOYxuWkoOtar9jzgp3lOmjWLWY59C/b8DtDHEAV4SPU07Nd/t+nS/SBNGkhUBFmEw==
dependencies:
focus-trap "^7.5.4"
tabbable "^6.2.0"
focus-trap@^7.5.4:
version "7.5.4"
resolved "https://registry.yarnpkg.com/focus-trap/-/focus-trap-7.5.4.tgz#6c4e342fe1dae6add9c2aa332a6e7a0bbd495ba2"
integrity sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==
dependencies:
tabbable "^6.2.0"
follow-redirects@^1.15.4: follow-redirects@^1.15.4:
version "1.15.5" version "1.15.5"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020"
@@ -8896,7 +8916,7 @@ react-confetti@^6.1.0:
dependencies: dependencies:
tween-functions "^1.2.0" tween-functions "^1.2.0"
react-day-picker@^8.6.0: react-day-picker@^8.10.0:
version "8.10.0" version "8.10.0"
resolved "https://registry.yarnpkg.com/react-day-picker/-/react-day-picker-8.10.0.tgz#729c5b9564967a924213978fb9c0751884a60595" resolved "https://registry.yarnpkg.com/react-day-picker/-/react-day-picker-8.10.0.tgz#729c5b9564967a924213978fb9c0751884a60595"
integrity sha512-mz+qeyrOM7++1NCb1ARXmkjMkzWVh2GL9YiPbRjKe0zHccvekk4HE+0MPOZOrosn8r8zTHIIeOUXTmXRqmkRmg== integrity sha512-mz+qeyrOM7++1NCb1ARXmkjMkzWVh2GL9YiPbRjKe0zHccvekk4HE+0MPOZOrosn8r8zTHIIeOUXTmXRqmkRmg==
@@ -8939,7 +8959,7 @@ react-element-to-jsx-string@^15.0.0:
is-plain-object "5.0.0" is-plain-object "5.0.0"
react-is "18.1.0" react-is "18.1.0"
react-fast-compare@^3.2.0: react-fast-compare@^3.0.1, react-fast-compare@^3.2.0:
version "3.2.2" version "3.2.2"
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49"
integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ== integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==
@@ -9012,6 +9032,14 @@ react-modal@^3.14.3, react-modal@^3.15.1:
react-lifecycles-compat "^3.0.0" react-lifecycles-compat "^3.0.0"
warning "^4.0.3" warning "^4.0.3"
react-popper@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.3.0.tgz#17891c620e1320dce318bad9fede46a5f71c70ba"
integrity sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==
dependencies:
react-fast-compare "^3.0.1"
warning "^4.0.2"
react-refresh@^0.14.0: react-refresh@^0.14.0:
version "0.14.0" version "0.14.0"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e"
@@ -9950,6 +9978,11 @@ synchronous-promise@^2.0.15:
resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.17.tgz#38901319632f946c982152586f2caf8ddc25c032" resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.17.tgz#38901319632f946c982152586f2caf8ddc25c032"
integrity sha512-AsS729u2RHUfEra9xJrE39peJcc2stq2+poBXX8bcM08Y6g9j/i/PUzwNQqkaJde7Ntg1TO7bSREbR5sdosQ+g== integrity sha512-AsS729u2RHUfEra9xJrE39peJcc2stq2+poBXX8bcM08Y6g9j/i/PUzwNQqkaJde7Ntg1TO7bSREbR5sdosQ+g==
tabbable@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.2.0.tgz#732fb62bc0175cfcec257330be187dcfba1f3b97"
integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==
taffydb@2.6.2: taffydb@2.6.2:
version "2.6.2" version "2.6.2"
resolved "https://registry.yarnpkg.com/taffydb/-/taffydb-2.6.2.tgz#7cbcb64b5a141b6a2efc2c5d2c67b4e150b2a268" resolved "https://registry.yarnpkg.com/taffydb/-/taffydb-2.6.2.tgz#7cbcb64b5a141b6a2efc2c5d2c67b4e150b2a268"
@@ -10595,7 +10628,7 @@ walker@^1.0.8:
dependencies: dependencies:
makeerror "1.0.12" makeerror "1.0.12"
warning@^4.0.3: warning@^4.0.2, warning@^4.0.3:
version "4.0.3" version "4.0.3"
resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==