Comicvine integration improvements I #112
@@ -18,6 +18,8 @@
|
|||||||
"@dnd-kit/core": "^6.0.8",
|
"@dnd-kit/core": "^6.0.8",
|
||||||
"@dnd-kit/sortable": "^7.0.2",
|
"@dnd-kit/sortable": "^7.0.2",
|
||||||
"@dnd-kit/utilities": "^3.2.1",
|
"@dnd-kit/utilities": "^3.2.1",
|
||||||
|
"@floating-ui/react": "^0.26.12",
|
||||||
|
"@floating-ui/react-dom": "^2.0.8",
|
||||||
"@fortawesome/fontawesome-free": "^6.3.0",
|
"@fortawesome/fontawesome-free": "^6.3.0",
|
||||||
"@popperjs/core": "^2.11.8",
|
"@popperjs/core": "^2.11.8",
|
||||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||||
@@ -58,7 +60,6 @@
|
|||||||
"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",
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import React, { useCallback, ReactElement, useState } from "react";
|
import React, { ReactElement, useState } from "react";
|
||||||
import { isNil, isEmpty } from "lodash";
|
import { isNil, isEmpty, isUndefined } from "lodash";
|
||||||
import { IExtractedComicBookCoverFile, RootState } from "threetwo-ui-typings";
|
import { IExtractedComicBookCoverFile, RootState } from "threetwo-ui-typings";
|
||||||
|
import { detectIssueTypes } from "../../shared/utils/tradepaperback.utils";
|
||||||
import { Form, Field } from "react-final-form";
|
import { Form, Field } from "react-final-form";
|
||||||
import Card from "../shared/Carda";
|
import Card from "../shared/Carda";
|
||||||
import ellipsize from "ellipsize";
|
import ellipsize from "ellipsize";
|
||||||
import { convert } from "html-to-text";
|
import { convert } from "html-to-text";
|
||||||
|
import PopoverButton from "../shared/PopoverButton";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||||
import {
|
import {
|
||||||
COMICVINE_SERVICE_URI,
|
COMICVINE_SERVICE_URI,
|
||||||
LIBRARY_SERVICE_BASE_URI,
|
LIBRARY_SERVICE_BASE_URI,
|
||||||
@@ -20,9 +21,12 @@ export const Search = ({}: ISearchProps): ReactElement => {
|
|||||||
const formData = {
|
const formData = {
|
||||||
search: "",
|
search: "",
|
||||||
};
|
};
|
||||||
const queryClient = useQueryClient();
|
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
|
||||||
const [comicVineMetadata, setComicVineMetadata] = useState({});
|
const [comicVineMetadata, setComicVineMetadata] = useState({});
|
||||||
|
const [selectedResource, setSelectedResource] = useState("volume");
|
||||||
|
|
||||||
|
const handleResourceChange = (value) => {
|
||||||
|
setSelectedResource(value);
|
||||||
|
};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
mutate,
|
mutate,
|
||||||
@@ -43,7 +47,7 @@ export const Search = ({}: ISearchProps): ReactElement => {
|
|||||||
limit: "10",
|
limit: "10",
|
||||||
offset: "0",
|
offset: "0",
|
||||||
field_list:
|
field_list:
|
||||||
"id,name,deck,api_detail_url,image,description,volume,cover_date,count_of_issues",
|
"id,name,deck,api_detail_url,image,description,volume,cover_date,start_year,count_of_issues",
|
||||||
resources: resource,
|
resources: resource,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -86,6 +90,17 @@ export const Search = ({}: ISearchProps): ReactElement => {
|
|||||||
return { __html: html };
|
return { __html: html };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onSubmit = async (values) => {
|
||||||
|
// Include the selected resource value in the form data
|
||||||
|
const formData = { ...values, resource: selectedResource };
|
||||||
|
try {
|
||||||
|
mutate(formData);
|
||||||
|
// Handle response
|
||||||
|
} catch (error) {
|
||||||
|
// Handle error
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<section>
|
<section>
|
||||||
@@ -106,7 +121,7 @@ export const Search = ({}: ISearchProps): ReactElement => {
|
|||||||
</header>
|
</header>
|
||||||
<div className="mx-auto max-w-screen-sm px-4 py-4 sm:px-6 sm:py-8 lg:px-8">
|
<div className="mx-auto max-w-screen-sm px-4 py-4 sm:px-6 sm:py-8 lg:px-8">
|
||||||
<Form
|
<Form
|
||||||
onSubmit={mutate}
|
onSubmit={onSubmit}
|
||||||
initialValues={{
|
initialValues={{
|
||||||
...formData,
|
...formData,
|
||||||
}}
|
}}
|
||||||
@@ -148,6 +163,8 @@ export const Search = ({}: ISearchProps): ReactElement => {
|
|||||||
{...volumesInput}
|
{...volumesInput}
|
||||||
type="radio"
|
type="radio"
|
||||||
id="volume"
|
id="volume"
|
||||||
|
checked={selectedResource === "volume"}
|
||||||
|
onChange={() => handleResourceChange("volume")}
|
||||||
className="peer hidden"
|
className="peer hidden"
|
||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
@@ -169,6 +186,8 @@ export const Search = ({}: ISearchProps): ReactElement => {
|
|||||||
{...issuesInput}
|
{...issuesInput}
|
||||||
type="radio"
|
type="radio"
|
||||||
id="issue"
|
id="issue"
|
||||||
|
checked={selectedResource === "issue"}
|
||||||
|
onChange={() => handleResourceChange("issue")}
|
||||||
className="peer hidden"
|
className="peer hidden"
|
||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
@@ -186,14 +205,21 @@ export const Search = ({}: ISearchProps): ReactElement => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{isPending && <>Loading results...</>}
|
{isPending && (
|
||||||
|
<div className="max-w-screen-xl px-4 py-4 sm:px-6 sm:py-8 lg:px-8">
|
||||||
|
Loading results...
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{!isEmpty(comicVineSearchResults?.data?.results) ? (
|
{!isEmpty(comicVineSearchResults?.data?.results) ? (
|
||||||
<div className="mx-auto max-w-screen-xl px-4 py-4 sm:px-6 sm:py-8 lg:px-8">
|
<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 result.resource_type === "issue" ? (
|
return result.resource_type === "issue" ? (
|
||||||
<div key={result.id} className="mb-5">
|
<div
|
||||||
|
key={result.id}
|
||||||
|
className="mb-5 dark:bg-slate-400 p-4 rounded-lg"
|
||||||
|
>
|
||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
<div className="mr-5 min-w-[200px] max-w-[25%]">
|
<div className="mr-5 min-w-[80px] max-w-[13%]">
|
||||||
<Card
|
<Card
|
||||||
key={result.id}
|
key={result.id}
|
||||||
orientation={"cover-only"}
|
orientation={"cover-only"}
|
||||||
@@ -221,7 +247,7 @@ export const Search = ({}: ISearchProps): ReactElement => {
|
|||||||
<a href={result.api_detail_url}>
|
<a href={result.api_detail_url}>
|
||||||
{result.api_detail_url}
|
{result.api_detail_url}
|
||||||
</a>
|
</a>
|
||||||
<p>
|
<p className="text-sm">
|
||||||
{ellipsize(
|
{ellipsize(
|
||||||
convert(result.description, {
|
convert(result.description, {
|
||||||
baseElements: {
|
baseElements: {
|
||||||
@@ -245,9 +271,12 @@ export const Search = ({}: ISearchProps): ReactElement => {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
result.resource_type === "volume" && (
|
result.resource_type === "volume" && (
|
||||||
<div key={result.id} className="mb-5">
|
<div
|
||||||
|
key={result.id}
|
||||||
|
className="mb-5 dark:bg-slate-500 p-4 rounded-lg"
|
||||||
|
>
|
||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
<div className="mr-5">
|
<div className="mr-5 min-w-[80px] max-w-[13%]">
|
||||||
<Card
|
<Card
|
||||||
key={result.id}
|
key={result.id}
|
||||||
orientation={"cover-only"}
|
orientation={"cover-only"}
|
||||||
@@ -255,39 +284,63 @@ export const Search = ({}: ISearchProps): ReactElement => {
|
|||||||
hasDetails={false}
|
hasDetails={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="column">
|
<div className="w-3/4">
|
||||||
<div className="text-xl">
|
<div className="text-xl">
|
||||||
{!isEmpty(result.name) ? (
|
{!isEmpty(result.name) ? (
|
||||||
result.name
|
result.name
|
||||||
) : (
|
) : (
|
||||||
<span className="is-size-3">No Name</span>
|
<span className="text-xl">No Name</span>
|
||||||
|
)}
|
||||||
|
{result.start_year && <> ({result.start_year})</>}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row gap-2">
|
||||||
|
{/* issue count */}
|
||||||
|
<div className="my-2">
|
||||||
|
<span className="inline-flex items-center bg-slate-50 text-slate-800 text-xs font-medium px-2 rounded-md dark:text-slate-900 dark:bg-slate-400">
|
||||||
|
<span className="pr-1 pt-1">
|
||||||
|
<i className="icon-[solar--documents-minimalistic-bold-duotone] w-5 h-5"></i>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className="text-md text-slate-500 dark:text-slate-900">
|
||||||
|
{result.count_of_issues} issues
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{/* type: TPB, one-shot, graphic novel etc. */}
|
||||||
|
{!isNil(result.description) &&
|
||||||
|
!isUndefined(result.description) && (
|
||||||
|
<>
|
||||||
|
{!isEmpty(
|
||||||
|
detectIssueTypes(result.description),
|
||||||
|
) && (
|
||||||
|
<div className="my-2">
|
||||||
|
<span className="inline-flex items-center bg-slate-50 text-slate-800 text-xs font-medium px-2 rounded-md dark:text-slate-900 dark:bg-slate-400">
|
||||||
|
<span className="pr-1 pt-1">
|
||||||
|
<i className="icon-[solar--book-2-line-duotone] w-5 h-5"></i>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className="text-md text-slate-500 dark:text-slate-900">
|
||||||
|
{
|
||||||
|
detectIssueTypes(result.description)
|
||||||
|
.displayName
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="field is-grouped mt-1">
|
|
||||||
<div className="control">
|
|
||||||
<div className="tags has-addons">
|
|
||||||
<span className="tag is-light">
|
|
||||||
Number of issues
|
|
||||||
</span>
|
|
||||||
<span className="tag is-info is-light">
|
|
||||||
{result.count_of_issues}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="control">
|
|
||||||
<div className="tags has-addons">
|
|
||||||
<span className="tag is-warning">
|
|
||||||
{result.id}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<span className="tag is-warning">{result.id}</span>
|
||||||
|
<p>
|
||||||
<a href={result.api_detail_url}>
|
<a href={result.api_detail_url}>
|
||||||
{result.api_detail_url}
|
{result.api_detail_url}
|
||||||
</a>
|
</a>
|
||||||
<p>
|
</p>
|
||||||
|
|
||||||
|
{/* description */}
|
||||||
|
<p className="text-sm">
|
||||||
{ellipsize(
|
{ellipsize(
|
||||||
convert(result.description, {
|
convert(result.description, {
|
||||||
baseElements: {
|
baseElements: {
|
||||||
@@ -298,13 +351,8 @@ export const Search = ({}: ISearchProps): ReactElement => {
|
|||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<button
|
<PopoverButton issuesCount={result.count_of_issues} />
|
||||||
className="flex space-x-1 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-2 text-gray-500 hover:bg-transparent hover:text-green-600 focus:outline-none focus:ring active:text-indigo-500"
|
{/* onClick={() => addToLibrary("comicvine", result) */}
|
||||||
onClick={() => addToLibrary("comicvine", result)}
|
|
||||||
>
|
|
||||||
<i className="icon-[solar--add-square-bold-duotone] w-6 h-6 mr-2"></i>{" "}
|
|
||||||
Mark as Wanted
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
import * as React from "react";
|
|
||||||
import { IExtractedComicBookCoverFile } from "threetwo-ui-typings";
|
|
||||||
import {
|
|
||||||
removeLeadingPeriod,
|
|
||||||
escapePoundSymbol,
|
|
||||||
} from "../shared/utils/formatting.utils";
|
|
||||||
import { isUndefined, isEmpty, isNil } from "lodash";
|
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import { LIBRARY_SERVICE_HOST } from "../constants/endpoints";
|
|
||||||
import ellipsize from "ellipsize";
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
comicBookCoversMetadata?: IExtractedComicBookCoverFile;
|
|
||||||
mongoObjId?: number;
|
|
||||||
hasTitle: boolean;
|
|
||||||
title?: string;
|
|
||||||
isHorizontal: boolean;
|
|
||||||
}
|
|
||||||
interface IState {}
|
|
||||||
|
|
||||||
class Card extends React.Component<IProps, IState> {
|
|
||||||
constructor(props: IProps) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
public drawCoverCard = (
|
|
||||||
metadata: IExtractedComicBookCoverFile,
|
|
||||||
): JSX.Element => {
|
|
||||||
const encodedFilePath = encodeURI(
|
|
||||||
`${LIBRARY_SERVICE_HOST}` + removeLeadingPeriod(metadata.path),
|
|
||||||
);
|
|
||||||
const filePath = escapePoundSymbol(encodedFilePath);
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div className="card generic-card">
|
|
||||||
<div className={this.props.isHorizontal ? "is-horizontal" : ""}>
|
|
||||||
<div className="card-image">
|
|
||||||
<figure className="image">
|
|
||||||
<img src={filePath} alt="Placeholder image" />
|
|
||||||
</figure>
|
|
||||||
</div>
|
|
||||||
{this.props.hasTitle && (
|
|
||||||
<div className="card-content">
|
|
||||||
<ul>
|
|
||||||
<Link to={"/comic/details/" + this.props.mongoObjId}>
|
|
||||||
<li className="has-text-weight-semibold">
|
|
||||||
{ellipsize(metadata.name, 18)}
|
|
||||||
</li>
|
|
||||||
</Link>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
public render() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{!isUndefined(this.props.comicBookCoversMetadata) &&
|
|
||||||
!isEmpty(this.props.comicBookCoversMetadata) &&
|
|
||||||
this.drawCoverCard(this.props.comicBookCoversMetadata)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Card;
|
|
||||||
@@ -1,71 +1,42 @@
|
|||||||
import React, { ChangeEventHandler, useRef, useState } from "react";
|
import React, { useRef, useState } from "react";
|
||||||
|
import { format } from "date-fns";
|
||||||
import { format, isValid, parse, parseISO } from "date-fns";
|
|
||||||
import FocusTrap from "focus-trap-react";
|
import FocusTrap from "focus-trap-react";
|
||||||
import { DayPicker, SelectSingleEventHandler } from "react-day-picker";
|
import { ClassNames, DayPicker } from "react-day-picker";
|
||||||
import { usePopper } from "react-popper";
|
import { useFloating, offset, flip, autoUpdate } from "@floating-ui/react-dom";
|
||||||
|
import styles from "react-day-picker/dist/style.module.css";
|
||||||
|
|
||||||
export const DatePickerDialog = (props) => {
|
export const DatePickerDialog = (props) => {
|
||||||
const { setter, apiAction } = props;
|
const { setter, apiAction } = props;
|
||||||
const [selected, setSelected] = useState<Date>();
|
const [selected, setSelected] = useState<Date>();
|
||||||
const [isPopperOpen, setIsPopperOpen] = useState(false);
|
const [isPopperOpen, setIsPopperOpen] = useState(false);
|
||||||
|
|
||||||
const popperRef = useRef<HTMLDivElement>(null);
|
const classNames: ClassNames = {
|
||||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
...styles,
|
||||||
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(
|
head: "custom-head",
|
||||||
null,
|
|
||||||
);
|
|
||||||
|
|
||||||
const customStyles = {
|
|
||||||
container: {
|
|
||||||
// Style for the entire container
|
|
||||||
border: "1px solid #ccc",
|
|
||||||
borderRadius: "4px",
|
|
||||||
padding: "10px",
|
|
||||||
width: "300px",
|
|
||||||
},
|
|
||||||
day: {
|
|
||||||
// Style for individual days
|
|
||||||
|
|
||||||
padding: "5px",
|
|
||||||
margin: "2px",
|
|
||||||
},
|
|
||||||
selected: {
|
|
||||||
// Style for selected days
|
|
||||||
backgroundColor: "#007bff",
|
|
||||||
color: "#fff",
|
|
||||||
},
|
|
||||||
disabled: {
|
|
||||||
// Style for disabled days
|
|
||||||
color: "#ccc",
|
|
||||||
},
|
|
||||||
today: {
|
|
||||||
// Style for today's date
|
|
||||||
backgroundColor: "#f0f0f0",
|
|
||||||
},
|
|
||||||
dayWrapper: {
|
|
||||||
// Style for the wrapper around each day
|
|
||||||
display: "inline-block",
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||||
const popper = usePopper(popperRef.current, popperElement, {
|
const { x, y, reference, floating, strategy, refs, update } = useFloating({
|
||||||
placement: "bottom-start",
|
placement: "bottom-end",
|
||||||
|
middleware: [offset(10), flip()],
|
||||||
|
strategy: "absolute",
|
||||||
});
|
});
|
||||||
|
|
||||||
const closePopper = () => {
|
const closePopper = () => {
|
||||||
setIsPopperOpen(false);
|
setIsPopperOpen(false);
|
||||||
buttonRef?.current?.focus();
|
buttonRef.current?.focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleButtonClick = () => {
|
const handleButtonClick = () => {
|
||||||
setIsPopperOpen(true);
|
setIsPopperOpen(true);
|
||||||
|
if (refs.reference.current && refs.floating.current) {
|
||||||
|
autoUpdate(refs.reference.current, refs.floating.current, update);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDaySelect: SelectSingleEventHandler = (date) => {
|
const handleDaySelect = (date) => {
|
||||||
setSelected(date);
|
setSelected(date);
|
||||||
if (date) {
|
if (date) {
|
||||||
setter(format(date, "M-dd-yyyy"));
|
setter(format(date, "yyyy-MM-dd"));
|
||||||
apiAction();
|
apiAction();
|
||||||
closePopper();
|
closePopper();
|
||||||
} else {
|
} else {
|
||||||
@@ -75,17 +46,14 @@ export const DatePickerDialog = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div ref={popperRef}>
|
<div ref={reference}>
|
||||||
<button
|
<button
|
||||||
ref={buttonRef}
|
ref={buttonRef}
|
||||||
type="button"
|
type="button"
|
||||||
aria-label="Pick a date"
|
aria-label="Pick a date"
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
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-2 py-1 text-gray-500 hover:bg-transparent hover:text-green-600 focus:outline-none focus:ring active:text-indigo-500"
|
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"
|
||||||
>
|
>
|
||||||
<span className="pr-1 pt-0.5 h-8">
|
|
||||||
<span className="icon-[solar--calendar-date-bold-duotone] w-6 h-6"></span>
|
|
||||||
</span>
|
|
||||||
Pick a date
|
Pick a date
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -101,11 +69,14 @@ export const DatePickerDialog = (props) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
tabIndex={-1}
|
ref={floating}
|
||||||
style={popper.styles.popper}
|
style={{
|
||||||
className="bg-slate-200 mt-3 p-2 rounded-lg z-50"
|
position: strategy,
|
||||||
{...popper.attributes.popper}
|
zIndex: "999",
|
||||||
ref={setPopperElement}
|
borderRadius: "10px",
|
||||||
|
boxShadow: "0 4px 6px rgba(0,0,0,0.1)", // Example of adding a shadow
|
||||||
|
}}
|
||||||
|
className="bg-slate-400 dark:bg-slate-500"
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-label="DayPicker calendar"
|
aria-label="DayPicker calendar"
|
||||||
>
|
>
|
||||||
@@ -115,7 +86,7 @@ export const DatePickerDialog = (props) => {
|
|||||||
defaultMonth={selected}
|
defaultMonth={selected}
|
||||||
selected={selected}
|
selected={selected}
|
||||||
onSelect={handleDaySelect}
|
onSelect={handleDaySelect}
|
||||||
styles={customStyles}
|
classNames={classNames}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</FocusTrap>
|
</FocusTrap>
|
||||||
|
|||||||
@@ -1,312 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { SearchBar } from "../GlobalSearchBar/SearchBar";
|
|
||||||
import { DownloadProgressTick } from "../ComicDetail/DownloadProgressTick";
|
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import { isEmpty, isNil, isUndefined } from "lodash";
|
|
||||||
import { format, fromUnixTime } from "date-fns";
|
|
||||||
import { useStore } from "../../store/index";
|
|
||||||
import { useShallow } from "zustand/react/shallow";
|
|
||||||
|
|
||||||
const Navbar: React.FunctionComponent = (props) => {
|
|
||||||
const {
|
|
||||||
airDCPPSocketConnected,
|
|
||||||
airDCPPDisconnectionInfo,
|
|
||||||
airDCPPSessionInformation,
|
|
||||||
airDCPPDownloadTick,
|
|
||||||
importJobQueue,
|
|
||||||
} = useStore(
|
|
||||||
useShallow((state) => ({
|
|
||||||
airDCPPSocketConnected: state.airDCPPSocketConnected,
|
|
||||||
airDCPPDisconnectionInfo: state.airDCPPDisconnectionInfo,
|
|
||||||
airDCPPSessionInformation: state.airDCPPSessionInformation,
|
|
||||||
airDCPPDownloadTick: state.airDCPPDownloadTick,
|
|
||||||
importJobQueue: state.importJobQueue,
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
// const downloadProgressTick = useSelector(
|
|
||||||
// (state: RootState) => state.airdcpp.downloadProgressData,
|
|
||||||
// );
|
|
||||||
//
|
|
||||||
// const airDCPPSocketConnectionStatus = useSelector(
|
|
||||||
// (state: RootState) => state.airdcpp.isAirDCPPSocketConnected,
|
|
||||||
// );
|
|
||||||
// const airDCPPSessionInfo = useSelector(
|
|
||||||
// (state: RootState) => state.airdcpp.airDCPPSessionInfo,
|
|
||||||
// );
|
|
||||||
// const socketDisconnectionReason = useSelector(
|
|
||||||
// (state: RootState) => state.airdcpp.socketDisconnectionReason,
|
|
||||||
// );
|
|
||||||
|
|
||||||
return (
|
|
||||||
<nav className="navbar is-fixed-top">
|
|
||||||
<div className="navbar-brand">
|
|
||||||
<Link to="/dashboard" className="navbar-item">
|
|
||||||
<img
|
|
||||||
src="/src/client/assets/img/threetwo.svg"
|
|
||||||
alt="ThreeTwo! A comic book curator"
|
|
||||||
width="112"
|
|
||||||
height="28"
|
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<a className="navbar-item is-hidden-desktop">
|
|
||||||
<span className="icon">
|
|
||||||
<i className="fas fa-github"></i>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a className="navbar-item is-hidden-desktop">
|
|
||||||
<span className="icon">
|
|
||||||
<i className="fas fa-twitter"></i>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<div className="navbar-burger burger" data-target="navMenubd-example">
|
|
||||||
<span></span>
|
|
||||||
<span></span>
|
|
||||||
<span></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="navMenubd-example" className="navbar-menu">
|
|
||||||
<div className="navbar-start">
|
|
||||||
<Link to="/" className="navbar-item">
|
|
||||||
Dashboard
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<Link to="/import" className="navbar-item">
|
|
||||||
Import
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<Link to="/library" className="navbar-item">
|
|
||||||
Library
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<Link to="/downloads" className="navbar-item">
|
|
||||||
Downloads
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
{/* <SearchBar /> */}
|
|
||||||
|
|
||||||
<Link to="/search" className="navbar-item">
|
|
||||||
Search ComicVine
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="navbar-end">
|
|
||||||
<a className="navbar-item is-hidden-desktop-only"></a>
|
|
||||||
|
|
||||||
<div className="navbar-item has-dropdown is-hoverable">
|
|
||||||
<a className="navbar-link is-arrowless">
|
|
||||||
<i className="fa-solid fa-download"></i>
|
|
||||||
{!isEmpty(airDCPPDownloadTick) && (
|
|
||||||
<div className="pulsating-circle"></div>
|
|
||||||
)}
|
|
||||||
</a>
|
|
||||||
{!isEmpty(airDCPPDownloadTick) ? (
|
|
||||||
<div className="navbar-dropdown is-right is-boxed">
|
|
||||||
<a className="navbar-item">
|
|
||||||
<DownloadProgressTick data={airDCPPDownloadTick} />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{!isUndefined(importJobQueue.status) &&
|
|
||||||
location.hash !== "#/import" ? (
|
|
||||||
<div className="navbar-item has-dropdown is-hoverable">
|
|
||||||
<a className="navbar-link is-arrowless">
|
|
||||||
<i className="fa-solid fa-file-import has-text-warning-dark"></i>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<div className="navbar-dropdown is-right is-boxed">
|
|
||||||
<a className="navbar-item">
|
|
||||||
<ul>
|
|
||||||
{importJobQueue.successfulJobCount > 0 ? (
|
|
||||||
<li className="mb-2">
|
|
||||||
<span className="tag is-success mr-2">
|
|
||||||
{importJobQueue.successfulJobCount}
|
|
||||||
</span>
|
|
||||||
imported.
|
|
||||||
</li>
|
|
||||||
) : null}
|
|
||||||
{importJobQueue.failedJobCount > 0 ? (
|
|
||||||
<li>
|
|
||||||
<span className="tag is-danger mr-2">
|
|
||||||
{importJobQueue.failedJobCount}
|
|
||||||
</span>
|
|
||||||
failed to import.
|
|
||||||
</li>
|
|
||||||
) : null}
|
|
||||||
</ul>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{/* AirDC++ socket connection status */}
|
|
||||||
<div className="navbar-item has-dropdown is-hoverable">
|
|
||||||
{!isUndefined(airDCPPSessionInformation.user) ? (
|
|
||||||
<>
|
|
||||||
<a className="navbar-link is-arrowless has-text-success">
|
|
||||||
<i className="fa-solid fa-bolt"></i>
|
|
||||||
</a>
|
|
||||||
<div className="navbar-dropdown pr-2 pl-2 is-right airdcpp-status is-boxed">
|
|
||||||
{/* AirDC++ Session Information */}
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Last login was{" "}
|
|
||||||
<span className="tag">
|
|
||||||
{format(
|
|
||||||
fromUnixTime(
|
|
||||||
airDCPPSessionInformation?.user.last_login,
|
|
||||||
),
|
|
||||||
"dd MMMM, yyyy",
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
<hr className="navbar-divider" />
|
|
||||||
<p>
|
|
||||||
<span className="tag has-text-success">
|
|
||||||
{airDCPPSessionInformation.user.username}
|
|
||||||
</span>
|
|
||||||
connected to{" "}
|
|
||||||
<span className="tag has-text-success">
|
|
||||||
{airDCPPSessionInformation.system_info.client_version}
|
|
||||||
</span>{" "}
|
|
||||||
with session ID{" "}
|
|
||||||
<span className="tag has-text-success">
|
|
||||||
{airDCPPSessionInformation.session_id}
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<a className="navbar-link is-arrowless has-text-danger">
|
|
||||||
<i className="fa-solid fa-bolt"></i>
|
|
||||||
</a>
|
|
||||||
<div className="navbar-dropdown pr-2 pl-2 is-right is-boxed">
|
|
||||||
<pre>{JSON.stringify(airDCPPDisconnectionInfo, null, 2)}</pre>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="navbar-item has-dropdown is-hoverable is-mega">
|
|
||||||
<div className="navbar-link flex">Blog</div>
|
|
||||||
<div id="blogDropdown" className="navbar-dropdown">
|
|
||||||
<div className="container is-fluid">
|
|
||||||
<div className="columns">
|
|
||||||
<div className="column">
|
|
||||||
<h1 className="title is-6 is-mega-menu-title">
|
|
||||||
Sub Menu Title
|
|
||||||
</h1>
|
|
||||||
<a className="navbar-item" href="/2017/08/03/list-of-tags/">
|
|
||||||
<div className="navbar-content">
|
|
||||||
<p>
|
|
||||||
<small className="has-text-info">03 Aug 2017</small>
|
|
||||||
</p>
|
|
||||||
<p>New feature: list of tags</p>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
<a className="navbar-item" href="/2017/08/03/list-of-tags/">
|
|
||||||
<div className="navbar-content">
|
|
||||||
<p>
|
|
||||||
<small className="has-text-info">03 Aug 2017</small>
|
|
||||||
</p>
|
|
||||||
<p>New feature: list of tags</p>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
<a className="navbar-item" href="/2017/08/03/list-of-tags/">
|
|
||||||
<div className="navbar-content">
|
|
||||||
<p>
|
|
||||||
<small className="has-text-info">03 Aug 2017</small>
|
|
||||||
</p>
|
|
||||||
<p>New feature: list of tags</p>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div className="column">
|
|
||||||
<h1 className="title is-6 is-mega-menu-title">
|
|
||||||
Sub Menu Title
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<a
|
|
||||||
className="navbar-item "
|
|
||||||
href="http://bulma.io/documentation/columns/basics/"
|
|
||||||
>
|
|
||||||
Columns
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div className="column">
|
|
||||||
<h1 className="title is-6 is-mega-menu-title">
|
|
||||||
Sub Menu Title
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<a className="navbar-item" href="/2017/08/03/list-of-tags/">
|
|
||||||
<div className="navbar-content">
|
|
||||||
<p>
|
|
||||||
<small className="has-text-info">03 Aug 2017</small>
|
|
||||||
</p>
|
|
||||||
<p>New feature: list of tags</p>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div className="column">
|
|
||||||
<h1 className="title is-6 is-mega-menu-title">
|
|
||||||
Sub Menu Title
|
|
||||||
</h1>
|
|
||||||
<a
|
|
||||||
className="navbar-item "
|
|
||||||
href="/documentation/overview/start/"
|
|
||||||
>
|
|
||||||
Overview
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr className="navbar-divider" />
|
|
||||||
<div className="navbar-item">
|
|
||||||
<div className="navbar-content">
|
|
||||||
<div className="level is-mobile">
|
|
||||||
<div className="level-left">
|
|
||||||
<div className="level-item">
|
|
||||||
<strong>Stay up to date!</strong>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="level-right">
|
|
||||||
<div className="level-item">
|
|
||||||
<a
|
|
||||||
className="button bd-is-rss is-small"
|
|
||||||
href="http://bulma.io/atom.xml"
|
|
||||||
>
|
|
||||||
<span className="icon is-small">
|
|
||||||
<i className="fa fa-rss"></i>
|
|
||||||
</span>
|
|
||||||
<span>Subscribe</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="navbar-item">
|
|
||||||
<div className="field is-grouped">
|
|
||||||
<p className="control">
|
|
||||||
<Link to="/settings" className="navbar-item">
|
|
||||||
Settings
|
|
||||||
</Link>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Navbar;
|
|
||||||
42
src/client/components/shared/PopoverButton.tsx
Normal file
42
src/client/components/shared/PopoverButton.tsx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { useFloating, offset, flip } from "@floating-ui/react-dom";
|
||||||
|
|
||||||
|
const PopoverButton = ({ issuesCount }) => {
|
||||||
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
|
// Use destructuring to obtain the reference and floating setters, among other values.
|
||||||
|
const { x, y, refs, strategy, floatingStyles } = useFloating({
|
||||||
|
placement: "right",
|
||||||
|
middleware: [offset(8), flip()],
|
||||||
|
strategy: "absolute",
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{/* Apply the reference setter directly to the ref prop */}
|
||||||
|
<button
|
||||||
|
ref={refs.setReference}
|
||||||
|
onMouseEnter={() => setIsVisible(true)}
|
||||||
|
onMouseLeave={() => setIsVisible(false)}
|
||||||
|
onFocus={() => setIsVisible(true)}
|
||||||
|
onBlur={() => setIsVisible(false)}
|
||||||
|
aria-describedby="popover"
|
||||||
|
className="flex space-x-1 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-2 text-gray-500 hover:bg-transparent hover:text-green-600 focus:outline-none focus:ring active:text-indigo-500"
|
||||||
|
>
|
||||||
|
<i className="icon-[solar--add-square-bold-duotone] w-6 h-6 mr-2"></i>{" "}
|
||||||
|
Mark as Wanted
|
||||||
|
</button>
|
||||||
|
{isVisible && (
|
||||||
|
<div
|
||||||
|
ref={refs.setFloating} // Apply the floating setter directly to the ref prop
|
||||||
|
style={floatingStyles}
|
||||||
|
className="text-sm bg-slate-400 p-2 rounded-md"
|
||||||
|
role="tooltip"
|
||||||
|
>
|
||||||
|
Adding this volume will add all {issuesCount} issues to your wanted
|
||||||
|
list.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PopoverButton;
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import React, { createContext } from "react";
|
|
||||||
|
|
||||||
export const SocketIOContext = createContext({});
|
|
||||||
|
|
||||||
export const SocketIOProvider = ({ children, socket }) => {
|
|
||||||
return (
|
|
||||||
<SocketIOContext.Provider value={socket}>
|
|
||||||
{children}
|
|
||||||
</SocketIOContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import React, { useEffect } from "react";
|
|
||||||
import BlazeSlider from "blaze-slider";
|
|
||||||
|
|
||||||
export const useBlazeSlider = (config) => {
|
|
||||||
const sliderRef = React.useRef();
|
|
||||||
const elRef = React.useRef();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// if not already initialized
|
|
||||||
if (!sliderRef.current) {
|
|
||||||
sliderRef.current = new BlazeSlider(elRef.current, config);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return elRef;
|
|
||||||
};
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
import {
|
|
||||||
AIRDCPP_SEARCH_IN_PROGRESS,
|
|
||||||
AIRDCPP_SEARCH_RESULTS_ADDED,
|
|
||||||
AIRDCPP_SEARCH_RESULTS_UPDATED,
|
|
||||||
AIRDCPP_HUB_SEARCHES_SENT,
|
|
||||||
AIRDCPP_RESULT_DOWNLOAD_INITIATED,
|
|
||||||
AIRDCPP_DOWNLOAD_PROGRESS_TICK,
|
|
||||||
AIRDCPP_FILE_DOWNLOAD_COMPLETED,
|
|
||||||
AIRDCPP_BUNDLES_FETCHED,
|
|
||||||
AIRDCPP_TRANSFERS_FETCHED,
|
|
||||||
LIBRARY_ISSUE_BUNDLES,
|
|
||||||
AIRDCPP_SOCKET_CONNECTED,
|
|
||||||
AIRDCPP_SOCKET_DISCONNECTED,
|
|
||||||
} from "../constants/action-types";
|
|
||||||
import { LOCATION_CHANGE } from "redux-first-history";
|
|
||||||
import { isNil, isUndefined } from "lodash";
|
|
||||||
import { difference } from "../shared/utils/object.utils";
|
|
||||||
|
|
||||||
const initialState = {
|
|
||||||
searchResults: [],
|
|
||||||
isAirDCPPSearchInProgress: false,
|
|
||||||
searchInfo: null,
|
|
||||||
searchInstance: null,
|
|
||||||
downloadResult: null,
|
|
||||||
bundleDBImportResult: null,
|
|
||||||
downloadFileStatus: {},
|
|
||||||
bundles: [],
|
|
||||||
transfers: [],
|
|
||||||
isAirDCPPSocketConnected: false,
|
|
||||||
airDCPPSessionInfo: {},
|
|
||||||
socketDisconnectionReason: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
function airdcppReducer(state = initialState, action) {
|
|
||||||
switch (action.type) {
|
|
||||||
case AIRDCPP_SEARCH_RESULTS_ADDED:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
searchResults: [...state.searchResults, action.groupedResult],
|
|
||||||
isAirDCPPSearchInProgress: true,
|
|
||||||
};
|
|
||||||
case AIRDCPP_SEARCH_RESULTS_UPDATED:
|
|
||||||
const bundleToUpdateIndex = state.searchResults.findIndex(
|
|
||||||
(bundle) => bundle.result.id === action.groupedResult.result.id,
|
|
||||||
);
|
|
||||||
const updatedState = [...state.searchResults];
|
|
||||||
|
|
||||||
if (
|
|
||||||
!isNil(
|
|
||||||
difference(updatedState[bundleToUpdateIndex], action.groupedResult),
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
updatedState[bundleToUpdateIndex] = action.groupedResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
searchResults: updatedState,
|
|
||||||
};
|
|
||||||
case AIRDCPP_SEARCH_IN_PROGRESS:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
isAirDCPPSearchInProgress: true,
|
|
||||||
};
|
|
||||||
case AIRDCPP_HUB_SEARCHES_SENT:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
isAirDCPPSearchInProgress: false,
|
|
||||||
searchInfo: action.searchInfo,
|
|
||||||
searchInstance: action.instance,
|
|
||||||
};
|
|
||||||
case AIRDCPP_RESULT_DOWNLOAD_INITIATED:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
downloadResult: action.downloadResult,
|
|
||||||
bundleDBImportResult: action.bundleDBImportResult,
|
|
||||||
};
|
|
||||||
case AIRDCPP_DOWNLOAD_PROGRESS_TICK:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
downloadProgressData: action.downloadProgressData,
|
|
||||||
};
|
|
||||||
case AIRDCPP_BUNDLES_FETCHED:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
bundles: action.bundles,
|
|
||||||
};
|
|
||||||
case LIBRARY_ISSUE_BUNDLES:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
issue_bundles: action.issue_bundles,
|
|
||||||
};
|
|
||||||
case AIRDCPP_FILE_DOWNLOAD_COMPLETED:
|
|
||||||
console.log("COMPLETED", action);
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
};
|
|
||||||
case AIRDCPP_TRANSFERS_FETCHED:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
transfers: action.bundles,
|
|
||||||
};
|
|
||||||
|
|
||||||
case AIRDCPP_SOCKET_CONNECTED:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
isAirDCPPSocketConnected: true,
|
|
||||||
airDCPPSessionInfo: action.data,
|
|
||||||
};
|
|
||||||
|
|
||||||
case AIRDCPP_SOCKET_DISCONNECTED:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
isAirDCPPSocketConnected: false,
|
|
||||||
socketDisconnectionReason: action.data,
|
|
||||||
};
|
|
||||||
case LOCATION_CHANGE:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
searchResults: [],
|
|
||||||
isAirDCPPSearchInProgress: false,
|
|
||||||
searchInfo: null,
|
|
||||||
searchInstance: null,
|
|
||||||
downloadResult: null,
|
|
||||||
bundleDBImportResult: null,
|
|
||||||
// bundles: [],
|
|
||||||
};
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default airdcppReducer;
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
import { isEmpty } from "lodash";
|
|
||||||
import {
|
|
||||||
CV_API_CALL_IN_PROGRESS,
|
|
||||||
CV_SEARCH_SUCCESS,
|
|
||||||
CV_CLEANUP,
|
|
||||||
IMS_COMIC_BOOK_DB_OBJECT_FETCHED,
|
|
||||||
IMS_COMIC_BOOKS_DB_OBJECTS_FETCHED,
|
|
||||||
IMS_COMIC_BOOK_DB_OBJECT_CALL_IN_PROGRESS,
|
|
||||||
CV_ISSUES_METADATA_CALL_IN_PROGRESS,
|
|
||||||
CV_ISSUES_MATCHES_IN_LIBRARY_FETCHED,
|
|
||||||
CV_ISSUES_FOR_VOLUME_IN_LIBRARY_SUCCESS,
|
|
||||||
CV_WEEKLY_PULLLIST_FETCHED,
|
|
||||||
CV_WEEKLY_PULLLIST_CALL_IN_PROGRESS,
|
|
||||||
LIBRARY_STATISTICS_CALL_IN_PROGRESS,
|
|
||||||
LIBRARY_STATISTICS_FETCHED,
|
|
||||||
} from "../constants/action-types";
|
|
||||||
|
|
||||||
const initialState = {
|
|
||||||
pullList: [],
|
|
||||||
libraryStatistics: [],
|
|
||||||
searchResults: [],
|
|
||||||
searchQuery: {},
|
|
||||||
inProgress: false,
|
|
||||||
comicBookDetail: {},
|
|
||||||
comicBooksDetails: [],
|
|
||||||
issuesForVolume: [],
|
|
||||||
IMS_inProgress: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
function comicinfoReducer(state = initialState, action) {
|
|
||||||
switch (action.type) {
|
|
||||||
case CV_API_CALL_IN_PROGRESS:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
inProgress: true,
|
|
||||||
};
|
|
||||||
case CV_SEARCH_SUCCESS:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
searchResults: action.searchResults,
|
|
||||||
searchQuery: action.searchQueryObject,
|
|
||||||
inProgress: false,
|
|
||||||
};
|
|
||||||
case IMS_COMIC_BOOK_DB_OBJECT_CALL_IN_PROGRESS:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
IMS_inProgress: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
case IMS_COMIC_BOOK_DB_OBJECT_FETCHED:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
comicBookDetail: action.comicBookDetail,
|
|
||||||
IMS_inProgress: false,
|
|
||||||
};
|
|
||||||
case IMS_COMIC_BOOKS_DB_OBJECTS_FETCHED:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
comicBooksDetails: action.comicBooks,
|
|
||||||
IMS_inProgress: false,
|
|
||||||
};
|
|
||||||
case CV_CLEANUP:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
searchResults: [],
|
|
||||||
searchQuery: {},
|
|
||||||
issuesForVolume: [],
|
|
||||||
};
|
|
||||||
case CV_ISSUES_METADATA_CALL_IN_PROGRESS:
|
|
||||||
return {
|
|
||||||
inProgress: true,
|
|
||||||
...state,
|
|
||||||
};
|
|
||||||
|
|
||||||
case CV_ISSUES_FOR_VOLUME_IN_LIBRARY_SUCCESS:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
issuesForVolume: action.issues,
|
|
||||||
inProgress: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
case CV_ISSUES_MATCHES_IN_LIBRARY_FETCHED:
|
|
||||||
const updatedState = [...state.issuesForVolume];
|
|
||||||
action.matches.map((match) => {
|
|
||||||
updatedState.map((issue, idx) => {
|
|
||||||
const matches = [];
|
|
||||||
if (!isEmpty(match.hits.hits)) {
|
|
||||||
return match.hits.hits.map((hit) => {
|
|
||||||
if (
|
|
||||||
parseInt(issue.issue_number, 10) ===
|
|
||||||
hit._source.inferredMetadata.issue.number
|
|
||||||
) {
|
|
||||||
matches.push(hit);
|
|
||||||
const updatedIssueResult = { ...issue, matches };
|
|
||||||
updatedState[idx] = updatedIssueResult;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
issuesForVolume: updatedState,
|
|
||||||
};
|
|
||||||
|
|
||||||
case CV_WEEKLY_PULLLIST_CALL_IN_PROGRESS:
|
|
||||||
return {
|
|
||||||
inProgress: true,
|
|
||||||
...state,
|
|
||||||
};
|
|
||||||
case CV_WEEKLY_PULLLIST_FETCHED: {
|
|
||||||
const foo = [];
|
|
||||||
action.data.map((item) => {
|
|
||||||
foo.push({issue: item})
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
inProgress: false,
|
|
||||||
pullList: foo,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case LIBRARY_STATISTICS_CALL_IN_PROGRESS:
|
|
||||||
return {
|
|
||||||
inProgress: true,
|
|
||||||
...state,
|
|
||||||
};
|
|
||||||
case LIBRARY_STATISTICS_FETCHED:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
inProgress: false,
|
|
||||||
libraryStatistics: action.data,
|
|
||||||
};
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default comicinfoReducer;
|
|
||||||
@@ -1,334 +0,0 @@
|
|||||||
import { isUndefined, map } from "lodash";
|
|
||||||
import { LOCATION_CHANGE } from "redux-first-history";
|
|
||||||
import { determineCoverFile } from "../shared/utils/metadata.utils";
|
|
||||||
import {
|
|
||||||
IMS_COMICBOOK_METADATA_FETCHED,
|
|
||||||
IMS_RAW_IMPORT_SUCCESSFUL,
|
|
||||||
IMS_RAW_IMPORT_FAILED,
|
|
||||||
IMS_RECENT_COMICS_FETCHED,
|
|
||||||
IMS_WANTED_COMICS_FETCHED,
|
|
||||||
WANTED_COMICS_FETCHED,
|
|
||||||
IMS_CV_METADATA_IMPORT_SUCCESSFUL,
|
|
||||||
IMS_CV_METADATA_IMPORT_FAILED,
|
|
||||||
IMS_CV_METADATA_IMPORT_CALL_IN_PROGRESS,
|
|
||||||
IMS_COMIC_BOOK_GROUPS_CALL_IN_PROGRESS,
|
|
||||||
IMS_COMIC_BOOK_GROUPS_FETCHED,
|
|
||||||
IMS_COMIC_BOOK_GROUPS_CALL_FAILED,
|
|
||||||
IMS_COMIC_BOOK_ARCHIVE_EXTRACTION_CALL_IN_PROGRESS,
|
|
||||||
LS_IMPORT,
|
|
||||||
LS_COVER_EXTRACTED,
|
|
||||||
LS_COVER_EXTRACTION_FAILED,
|
|
||||||
LS_COMIC_ADDED,
|
|
||||||
IMG_ANALYSIS_CALL_IN_PROGRESS,
|
|
||||||
IMG_ANALYSIS_DATA_FETCH_SUCCESS,
|
|
||||||
SS_SEARCH_RESULTS_FETCHED,
|
|
||||||
SS_SEARCH_IN_PROGRESS,
|
|
||||||
FILEOPS_STATE_RESET,
|
|
||||||
LS_IMPORT_CALL_IN_PROGRESS,
|
|
||||||
SS_SEARCH_FAILED,
|
|
||||||
SS_SEARCH_RESULTS_FETCHED_SPECIAL,
|
|
||||||
VOLUMES_FETCHED,
|
|
||||||
COMICBOOK_EXTRACTION_SUCCESS,
|
|
||||||
LIBRARY_SERVICE_HEALTH,
|
|
||||||
LS_IMPORT_QUEUE_DRAINED,
|
|
||||||
LS_SET_QUEUE_STATUS,
|
|
||||||
RESTORE_JOB_COUNTS_AFTER_SESSION_RESTORATION,
|
|
||||||
LS_IMPORT_JOB_STATISTICS_FETCHED,
|
|
||||||
} from "../constants/action-types";
|
|
||||||
import { removeLeadingPeriod } from "../shared/utils/formatting.utils";
|
|
||||||
import { LIBRARY_SERVICE_HOST } from "../constants/endpoints";
|
|
||||||
|
|
||||||
const initialState = {
|
|
||||||
IMSCallInProgress: false,
|
|
||||||
IMGCallInProgress: false,
|
|
||||||
SSCallInProgress: false,
|
|
||||||
imageAnalysisResults: {},
|
|
||||||
comicBookExtractionInProgress: false,
|
|
||||||
LSQueueImportStatus: undefined,
|
|
||||||
comicBookMetadata: [],
|
|
||||||
comicVolumeGroups: [],
|
|
||||||
isSocketConnected: false,
|
|
||||||
isComicVineMetadataImportInProgress: false,
|
|
||||||
comicVineMetadataImportError: {},
|
|
||||||
rawImportError: {},
|
|
||||||
extractedComicBookArchive: {
|
|
||||||
reading: [],
|
|
||||||
analysis: [],
|
|
||||||
},
|
|
||||||
recentComics: [],
|
|
||||||
wantedComics: [],
|
|
||||||
libraryComics: [],
|
|
||||||
volumes: [],
|
|
||||||
librarySearchResultsFormatted: [],
|
|
||||||
lastQueueJob: "",
|
|
||||||
successfulJobCount: 0,
|
|
||||||
failedJobCount: 0,
|
|
||||||
importJobStatistics: [],
|
|
||||||
libraryQueueResults: [],
|
|
||||||
librarySearchError: {},
|
|
||||||
libraryServiceStatus: {},
|
|
||||||
};
|
|
||||||
function fileOpsReducer(state = initialState, action) {
|
|
||||||
switch (action.type) {
|
|
||||||
case IMS_COMICBOOK_METADATA_FETCHED:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
comicBookMetadata: [...state.comicBookMetadata, action.data],
|
|
||||||
IMSCallInProgress: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
case LS_IMPORT_CALL_IN_PROGRESS: {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
IMSCallInProgress: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case IMS_RAW_IMPORT_SUCCESSFUL:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
rawImportDetails: action.rawImportDetails,
|
|
||||||
};
|
|
||||||
case IMS_RAW_IMPORT_FAILED:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
rawImportErorr: action.rawImportError,
|
|
||||||
};
|
|
||||||
case IMS_RECENT_COMICS_FETCHED:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
recentComics: action.data.docs,
|
|
||||||
};
|
|
||||||
case IMS_WANTED_COMICS_FETCHED:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
wantedComics: action.data,
|
|
||||||
};
|
|
||||||
case IMS_CV_METADATA_IMPORT_SUCCESSFUL:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
isComicVineMetadataImportInProgress: false,
|
|
||||||
comicVineMetadataImportDetails: action.importResult,
|
|
||||||
};
|
|
||||||
case IMS_CV_METADATA_IMPORT_CALL_IN_PROGRESS:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
isComicVineMetadataImportInProgress: true,
|
|
||||||
};
|
|
||||||
case IMS_CV_METADATA_IMPORT_FAILED:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
isComicVineMetadataImportInProgress: false,
|
|
||||||
comicVineMetadataImportError: action.importError,
|
|
||||||
};
|
|
||||||
case IMS_COMIC_BOOK_GROUPS_CALL_IN_PROGRESS: {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
IMSCallInProgress: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case IMS_COMIC_BOOK_GROUPS_FETCHED: {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
comicVolumeGroups: action.data,
|
|
||||||
IMSCallInProgress: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case IMS_COMIC_BOOK_GROUPS_CALL_FAILED: {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
IMSCallInProgress: false,
|
|
||||||
error: action.error,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case IMS_COMIC_BOOK_ARCHIVE_EXTRACTION_CALL_IN_PROGRESS: {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
comicBookExtractionInProgress: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
case LOCATION_CHANGE: {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
extractedComicBookArchive: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
case LS_IMPORT: {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
LSQueueImportStatus: "running",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
case LS_COVER_EXTRACTED: {
|
|
||||||
if (state.recentComics.length === 5) {
|
|
||||||
state.recentComics.pop();
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
successfulJobCount: action.completedJobCount,
|
|
||||||
lastQueueJob: action.importResult.rawFileDetails.name,
|
|
||||||
recentComics: [...state.recentComics, action.importResult],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
case LS_COVER_EXTRACTION_FAILED: {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
failedJobCount: action.failedJobCount,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
case LS_IMPORT_QUEUE_DRAINED: {
|
|
||||||
localStorage.removeItem("sessionId");
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
LSQueueImportStatus: "drained",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
case RESTORE_JOB_COUNTS_AFTER_SESSION_RESTORATION: {
|
|
||||||
console.log("Restoring state for an active import in progress...");
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
successfulJobCount: action.completedJobCount,
|
|
||||||
failedJobCount: action.failedJobCount,
|
|
||||||
LSQueueImportStatus: action.queueStatus,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
case LS_SET_QUEUE_STATUS: {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
LSQueueImportStatus: action.data.queueStatus,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
case LS_IMPORT_JOB_STATISTICS_FETCHED: {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
importJobStatistics: action.data,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
case COMICBOOK_EXTRACTION_SUCCESS: {
|
|
||||||
const comicBookPages: string[] = [];
|
|
||||||
map(action.result.files, (page) => {
|
|
||||||
const pageFilePath = removeLeadingPeriod(page);
|
|
||||||
const imagePath = encodeURI(`${LIBRARY_SERVICE_HOST}${pageFilePath}`);
|
|
||||||
comicBookPages.push(imagePath);
|
|
||||||
});
|
|
||||||
|
|
||||||
switch (action.result.purpose) {
|
|
||||||
case "reading":
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
extractedComicBookArchive: {
|
|
||||||
reading: comicBookPages,
|
|
||||||
},
|
|
||||||
comicBookExtractionInProgress: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
case "analysis":
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
extractedComicBookArchive: {
|
|
||||||
analysis: comicBookPages,
|
|
||||||
},
|
|
||||||
comicBookExtractionInProgress: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case LS_COMIC_ADDED: {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case IMG_ANALYSIS_CALL_IN_PROGRESS: {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
IMGCallInProgress: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case IMG_ANALYSIS_DATA_FETCH_SUCCESS: {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
imageAnalysisResults: action.result,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
case SS_SEARCH_IN_PROGRESS: {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
SSCallInProgress: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
case SS_SEARCH_RESULTS_FETCHED: {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
libraryComics: action.data,
|
|
||||||
SSCallInProgress: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case SS_SEARCH_RESULTS_FETCHED_SPECIAL: {
|
|
||||||
const foo = [];
|
|
||||||
if (!isUndefined(action.data.hits)) {
|
|
||||||
map(action.data.hits, ({ _source }) => {
|
|
||||||
foo.push(_source);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
librarySearchResultsFormatted: foo,
|
|
||||||
SSCallInProgress: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case WANTED_COMICS_FETCHED: {
|
|
||||||
const foo = [];
|
|
||||||
if (!isUndefined(action.data.hits)) {
|
|
||||||
map(action.data.hits, ({ _source }) => {
|
|
||||||
foo.push(_source);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
wantedComics: foo,
|
|
||||||
SSCallInProgress: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
case VOLUMES_FETCHED:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
volumes: action.data,
|
|
||||||
SSCallInProgress: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
case SS_SEARCH_FAILED: {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
librarySearchError: action.data,
|
|
||||||
SSCallInProgress: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case LIBRARY_SERVICE_HEALTH: {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
libraryServiceStatus: action.status,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case FILEOPS_STATE_RESET: {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
imageAnalysisResults: {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default fileOpsReducer;
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import comicinfoReducer from "../reducers/comicinfo.reducer";
|
|
||||||
import fileOpsReducer from "../reducers/fileops.reducer";
|
|
||||||
import airdcppReducer from "../reducers/airdcpp.reducer";
|
|
||||||
// import settingsReducer from "../reducers/settings.reducer";
|
|
||||||
|
|
||||||
export const reducers = {
|
|
||||||
comicInfo: comicinfoReducer,
|
|
||||||
fileOps: fileOpsReducer,
|
|
||||||
airdcpp: airdcppReducer,
|
|
||||||
// settings: settingsReducer,
|
|
||||||
};
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
|
||||||
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
|
|
||||||
import { RootState } from "../store";
|
|
||||||
import { isUndefined } from "lodash";
|
|
||||||
import { SETTINGS_SERVICE_BASE_URI } from "../constants/endpoints";
|
|
||||||
|
|
||||||
export interface InitialState {
|
|
||||||
data: object;
|
|
||||||
inProgress: boolean;
|
|
||||||
dbFlushed: boolean;
|
|
||||||
torrentsList: Array<any>;
|
|
||||||
}
|
|
||||||
const initialState: InitialState = {
|
|
||||||
data: {},
|
|
||||||
inProgress: false,
|
|
||||||
dbFlushed: false,
|
|
||||||
torrentsList: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const settingsSlice = createSlice({
|
|
||||||
name: "settings",
|
|
||||||
initialState,
|
|
||||||
reducers: {
|
|
||||||
SETTINGS_CALL_IN_PROGRESS: (state) => {
|
|
||||||
state.inProgress = true;
|
|
||||||
},
|
|
||||||
|
|
||||||
SETTINGS_OBJECT_FETCHED: (state, action) => {
|
|
||||||
state.data = action.payload;
|
|
||||||
state.inProgress = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
SETTINGS_OBJECT_DELETED: (state, action) => {
|
|
||||||
state.data = action.payload;
|
|
||||||
state.inProgress = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
SETTINGS_DB_FLUSH_SUCCESS: (state, action) => {
|
|
||||||
state.dbFlushed = action.payload;
|
|
||||||
state.inProgress = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
SETTINGS_QBITTORRENT_TORRENTS_LIST_FETCHED: (state, action) => {
|
|
||||||
console.log(state);
|
|
||||||
console.log(action);
|
|
||||||
state.torrentsList = action.payload;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const {
|
|
||||||
SETTINGS_CALL_IN_PROGRESS,
|
|
||||||
SETTINGS_OBJECT_FETCHED,
|
|
||||||
SETTINGS_OBJECT_DELETED,
|
|
||||||
SETTINGS_DB_FLUSH_SUCCESS,
|
|
||||||
SETTINGS_QBITTORRENT_TORRENTS_LIST_FETCHED,
|
|
||||||
} = settingsSlice.actions;
|
|
||||||
|
|
||||||
// Other code such as selectors can use the imported `RootState` type
|
|
||||||
export const torrentsList = (state: RootState) => state.settings.torrentsList;
|
|
||||||
export const qBittorrentSettings = (state: RootState) => {
|
|
||||||
console.log(state);
|
|
||||||
if (!isUndefined(state.settings?.data?.bittorrent)) {
|
|
||||||
return state.settings?.data?.bittorrent.client.host;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
export default settingsSlice.reducer;
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
const socketIOMiddleware = (socket) => {
|
|
||||||
return (store) => (next) => (action) => {
|
|
||||||
if (action.type === "EMIT_SOCKET_EVENT") {
|
|
||||||
const { event, data } = action.payload;
|
|
||||||
socket.emit(event, data);
|
|
||||||
}
|
|
||||||
return next(action);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default socketIOMiddleware;
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import io from "socket.io-client";
|
|
||||||
import { SOCKET_BASE_URI } from "../../constants/endpoints";
|
|
||||||
const sessionId = localStorage.getItem("sessionId");
|
|
||||||
const socketIOConnectionInstance = io(SOCKET_BASE_URI, {
|
|
||||||
transports: ["websocket"],
|
|
||||||
withCredentials: true,
|
|
||||||
query: { sessionId },
|
|
||||||
});
|
|
||||||
|
|
||||||
export default socketIOConnectionInstance;
|
|
||||||
@@ -16,6 +16,7 @@ export const detectIssueTypes = (deck: string): any => {
|
|||||||
const matches = map(issueTypeMatchers, (matcher) => {
|
const matches = map(issueTypeMatchers, (matcher) => {
|
||||||
return getIssueTypeDisplayName(deck, matcher.regex, matcher.displayName);
|
return getIssueTypeDisplayName(deck, matcher.regex, matcher.displayName);
|
||||||
});
|
});
|
||||||
|
|
||||||
return compact(matches)[0];
|
return compact(matches)[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
42
yarn.lock
42
yarn.lock
@@ -1509,7 +1509,7 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@fal-works/esbuild-plugin-global-externals/-/esbuild-plugin-global-externals-2.1.2.tgz#c05ed35ad82df8e6ac616c68b92c2282bd083ba4"
|
resolved "https://registry.yarnpkg.com/@fal-works/esbuild-plugin-global-externals/-/esbuild-plugin-global-externals-2.1.2.tgz#c05ed35ad82df8e6ac616c68b92c2282bd083ba4"
|
||||||
integrity sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ==
|
integrity sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ==
|
||||||
|
|
||||||
"@floating-ui/core@^1.6.0":
|
"@floating-ui/core@^1.0.0", "@floating-ui/core@^1.6.0":
|
||||||
version "1.6.0"
|
version "1.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.0.tgz#fa41b87812a16bf123122bf945946bae3fdf7fc1"
|
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.0.tgz#fa41b87812a16bf123122bf945946bae3fdf7fc1"
|
||||||
integrity sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==
|
integrity sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==
|
||||||
@@ -1524,6 +1524,14 @@
|
|||||||
"@floating-ui/core" "^1.6.0"
|
"@floating-ui/core" "^1.6.0"
|
||||||
"@floating-ui/utils" "^0.2.1"
|
"@floating-ui/utils" "^0.2.1"
|
||||||
|
|
||||||
|
"@floating-ui/dom@^1.6.1":
|
||||||
|
version "1.6.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.3.tgz#954e46c1dd3ad48e49db9ada7218b0985cee75ef"
|
||||||
|
integrity sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==
|
||||||
|
dependencies:
|
||||||
|
"@floating-ui/core" "^1.0.0"
|
||||||
|
"@floating-ui/utils" "^0.2.0"
|
||||||
|
|
||||||
"@floating-ui/react-dom@^2.0.0":
|
"@floating-ui/react-dom@^2.0.0":
|
||||||
version "2.0.7"
|
version "2.0.7"
|
||||||
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.0.7.tgz#873e0a55a25d8ddbbccd159d6ab4a4b98eb05494"
|
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.0.7.tgz#873e0a55a25d8ddbbccd159d6ab4a4b98eb05494"
|
||||||
@@ -1531,7 +1539,23 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@floating-ui/dom" "^1.6.0"
|
"@floating-ui/dom" "^1.6.0"
|
||||||
|
|
||||||
"@floating-ui/utils@^0.2.1":
|
"@floating-ui/react-dom@^2.0.8":
|
||||||
|
version "2.0.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.0.8.tgz#afc24f9756d1b433e1fe0d047c24bd4d9cefaa5d"
|
||||||
|
integrity sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==
|
||||||
|
dependencies:
|
||||||
|
"@floating-ui/dom" "^1.6.1"
|
||||||
|
|
||||||
|
"@floating-ui/react@^0.26.12":
|
||||||
|
version "0.26.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.26.12.tgz#6908f774d8e3167d89b37fd83be975c7e5d8be99"
|
||||||
|
integrity sha512-D09o62HrWdIkstF2kGekIKAC0/N/Dl6wo3CQsnLcOmO3LkW6Ik8uIb3kw8JYkwxNCcg+uJ2bpWUiIijTBep05w==
|
||||||
|
dependencies:
|
||||||
|
"@floating-ui/react-dom" "^2.0.0"
|
||||||
|
"@floating-ui/utils" "^0.2.0"
|
||||||
|
tabbable "^6.0.0"
|
||||||
|
|
||||||
|
"@floating-ui/utils@^0.2.0", "@floating-ui/utils@^0.2.1":
|
||||||
version "0.2.1"
|
version "0.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.1.tgz#16308cea045f0fc777b6ff20a9f25474dd8293d2"
|
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.1.tgz#16308cea045f0fc777b6ff20a9f25474dd8293d2"
|
||||||
integrity sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==
|
integrity sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==
|
||||||
@@ -8997,7 +9021,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.0.1, react-fast-compare@^3.2.0:
|
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==
|
||||||
@@ -9070,14 +9094,6 @@ 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"
|
||||||
@@ -10023,7 +10039,7 @@ 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:
|
tabbable@^6.0.0, tabbable@^6.2.0:
|
||||||
version "6.2.0"
|
version "6.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.2.0.tgz#732fb62bc0175cfcec257330be187dcfba1f3b97"
|
resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.2.0.tgz#732fb62bc0175cfcec257330be187dcfba1f3b97"
|
||||||
integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==
|
integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==
|
||||||
@@ -10673,7 +10689,7 @@ walker@^1.0.8:
|
|||||||
dependencies:
|
dependencies:
|
||||||
makeerror "1.0.12"
|
makeerror "1.0.12"
|
||||||
|
|
||||||
warning@^4.0.2, warning@^4.0.3:
|
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==
|
||||||
|
|||||||
Reference in New Issue
Block a user