🛝 Added keen-slider for pull list

This commit is contained in:
2024-01-29 00:34:29 -05:00
parent 6081b817e4
commit a3b1e68b06
7 changed files with 171 additions and 297 deletions

View File

@@ -30,7 +30,6 @@
"axios-cache-interceptor": "^1.0.1",
"axios-rate-limit": "^1.3.0",
"babel-plugin-styled-components": "^2.1.4",
"blaze-slider": "^1.9.3",
"date-fns": "^2.28.0",
"dayjs": "^1.10.6",
"ellipsize": "^0.5.1",
@@ -42,6 +41,7 @@
"html-to-text": "^8.1.0",
"immer": "^10.0.3",
"jsdoc": "^3.6.10",
"keen-slider": "^6.8.6",
"lodash": "^4.17.21",
"pretty-bytes": "^5.6.0",
"prop-types": "^15.8.1",
@@ -55,7 +55,6 @@
"react-final-form": "^6.5.9",
"react-final-form-arrays": "^3.1.4",
"react-loader-spinner": "^4.0.0",
"react-masonry-css": "^1.0.16",
"react-modal": "^3.15.1",
"react-router": "^6.9.0",
"react-router-dom": "^6.9.0",

View File

@@ -112,111 +112,10 @@ export const Dashboard = (): ReactElement => {
// );
return (
<div className="container mx-auto max-w-full">
<section>
<h1>Dashboard</h1>
<div className="grid grid-cols-5 gap-6">
{recentComics?.data.docs.map(
(
{
_id,
rawFileDetails,
sourcedMetadata: { comicvine, comicInfo, locg },
inferredMetadata,
acquisition: {
source: { name },
},
},
idx,
) => {
const { issueName, url } = determineCoverFile({
rawFileDetails,
comicvine,
comicInfo,
locg,
});
const { issue, coverURL, icon } = determineExternalMetadata(
name,
{
comicvine,
comicInfo,
locg,
},
);
const isComicVineMetadataAvailable =
!isUndefined(comicvine) &&
!isUndefined(comicvine.volumeInformation);
<h1>Dashboard</h1>
<PullList />
return (
<Card
orientation="vertical-2"
key={idx}
imageUrl={`${LIBRARY_SERVICE_HOST}/${rawFileDetails.cover.filePath}`}
title={inferredMetadata.issue.name}
hasDetails
>
<div>
<dd className="text-sm my-1 flex flex-row gap-1">
{/* Issue number */}
<span className="inline-flex items-center bg-slate-50 text-slate-800 text-xs font-medium px-2.5 py-0.5 rounded-md dark:text-slate-900 dark:bg-slate-400">
<span className="pr-1 pt-1">
<i className="icon-[solar--hashtag-outline]"></i>
</span>
<span className="text-md text-slate-900">
{inferredMetadata.issue.number}
</span>
</span>
{/* File extension */}
<span className="inline-flex items-center bg-slate-50 text-slate-800 text-xs font-medium px-2.5 py-0.5 rounded-md dark:text-slate-900 dark:bg-slate-400">
<span className="pr-1 pt-1">
<i className="icon-[solar--file-bold-duotone] w-4 h-4"></i>
</span>
<span className="text-md text-slate-500 dark:text-slate-900">
{rawFileDetails.extension}
</span>
</span>
{/* Uncompressed status */}
{rawFileDetails?.archive?.uncompressed ? (
<span className="inline-flex items-center bg-slate-50 text-slate-800 text-xs font-medium px-2.5 py-0.5 rounded-md dark:text-slate-900 dark:bg-slate-400">
<span className="pr-1 pt-1">
<i className="icon-[solar--bookmark-bold-duotone] w-4 h-4"></i>
</span>
</span>
) : null}
</dd>
</div>
<div className="flex flex-row items-center gap-1 mt-2 pb-1">
<div className="sm:inline-flex sm:shrink-0 sm:items-center sm:gap-2">
{/* ComicInfo.xml presence */}
{!isNil(comicInfo) && !isEmpty(comicInfo) && (
<div mt-1>
<i className="h-7 w-7 icon-[solar--code-file-bold-duotone] text-yellow-500 dark:text-yellow-300"></i>
</div>
)}
{/* ComicVine metadata presence */}
{isComicVineMetadataAvailable && (
<span className="w-7 h-7">
<img
src="/src/client/assets/img/cvlogo.svg"
alt={"ComicVine metadata detected."}
/>
</span>
)}
</div>
{/* Raw file presence */}
{isNil(rawFileDetails) && (
<span className="h-6 w-5 sm:shrink-0 sm:items-center sm:gap-2">
<i className="icon-[solar--file-corrupted-outline] h-5 w-5" />
</span>
)}
</div>
</Card>
);
},
)}
</div>
</section>
{recentComics && <RecentlyImported comics={recentComics?.data.docs} />}
{/* Wanted comics */}
<WantedComicsList comics={wantedComics?.data?.docs} />
{/* Volume groups */}

View File

@@ -2,35 +2,64 @@ import { isNil, map } from "lodash";
import React, { createRef, ReactElement, useCallback, useEffect } from "react";
import Card from "../shared/Carda";
import Header from "../shared/Header";
import { useDispatch, useSelector } from "react-redux";
import { getWeeklyPullList } from "../../actions/comicinfo.actions";
import { importToDB } from "../../actions/fileops.actions";
import ellipsize from "ellipsize";
import Slider from "react-slick";
import "slick-carousel/slick/slick.css";
import "slick-carousel/slick/slick-theme.css";
import { Link } from "react-router-dom";
import axios from "axios";
import rateLimiter from "axios-rate-limit";
import { setupCache } from "axios-cache-interceptor";
import { useQuery } from "@tanstack/react-query";
import "keen-slider/keen-slider.min.css";
import { useKeenSlider } from "keen-slider/react";
import { COMICVINE_SERVICE_URI } from "../../constants/endpoints";
type PullListProps = {
issues: any;
};
export const PullList = ({ issues }: PullListProps): ReactElement => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(
getWeeklyPullList({
startDate: "2023-9-9",
pageSize: "15",
currentPage: "1",
}),
);
}, []);
const addToLibrary = useCallback(
(sourceName: string, locgMetadata) =>
dispatch(importToDB(sourceName, { locg: locgMetadata })),
[],
const http = rateLimiter(axios.create(), {
maxRequests: 1,
perMilliseconds: 1000,
maxRPS: 1,
});
const cachedAxios = setupCache(axios);
export const PullList = (): ReactElement => {
// blaze slider
const [sliderRef, instanceRef] = useKeenSlider(
{
loop: true,
slides: {
origin: "auto",
number: 15,
perView: 5,
spacing: 15,
},
slideChanged() {
console.log("slide changed");
},
},
[
// add plugins here
],
);
const {
data: pullList,
isSuccess,
isLoading,
} = useQuery({
queryFn: async () =>
await cachedAxios(`${COMICVINE_SERVICE_URI}/getWeeklyPullList`, {
method: "get",
params: { startDate: "2023-9-9", pageSize: "15", currentPage: "1" },
}),
queryKey: ["pullList"],
});
console.log(pullList?.data.result);
const addToLibrary = (sourceName: string, locgMetadata) =>
importToDB(sourceName, { locg: locgMetadata });
/*
const foo = {
coverFile: {}, // pointer to which cover file to use
@@ -43,47 +72,13 @@ export const PullList = ({ issues }: PullListProps): ReactElement => {
};
*/
const pullList = useSelector((state: RootState) => state.comicInfo.pullList);
let sliderRef = createRef();
const settings = {
dots: false,
infinite: false,
speed: 500,
slidesToShow: 5,
slidesToScroll: 5,
initialSlide: 0,
responsive: [
{
breakpoint: 1024,
settings: {
slidesToShow: 3,
slidesToScroll: 3,
infinite: false,
},
},
{
breakpoint: 600,
settings: {
slidesToShow: 2,
slidesToScroll: 2,
initialSlide: 0,
},
},
{
breakpoint: 480,
settings: {
slidesToShow: 1,
slidesToScroll: 1,
},
},
],
};
// const pullList = useSelector((state: RootState) => state.comicInfo.pullList);
const next = () => {
sliderRef.slickNext();
// sliderRef.slickNext();
};
const previous = () => {
sliderRef.slickPrev();
// sliderRef.slickPrev();
};
return (
<>
@@ -109,53 +104,35 @@ export const PullList = ({ issues }: PullListProps): ReactElement => {
<button className="button is-small">View all issues</button>
</Link>
</div>
<div className="field has-addons">
<div className="control">
<button className="button is-rounded is-small" onClick={previous}>
<i className="fa-solid fa-caret-left"></i>
</button>
</div>
<div className="control">
<button className="button is-rounded is-small" onClick={next}>
<i className="fa-solid fa-caret-right"></i>
</button>
</div>
</div>
</div>
</div>
<Slider {...settings} ref={(c) => (sliderRef = c)}>
{!isNil(pullList) &&
pullList &&
map(pullList, ({ issue }, idx) => {
{isSuccess && !isLoading && (
<div ref={sliderRef} className="keen-slider flex flex-row">
{map(pullList?.data.result, (issue, idx) => {
return (
<Card
key={idx}
orientation={"vertical"}
imageUrl={issue.cover}
hasDetails
title={ellipsize(issue.name, 18)}
cardContainerStyle={{
marginRight: 22,
boxShadow: "-2px 4px 15px -6px rgba(0,0,0,0.57)",
}}
>
<div className="content">
<div className="control">
<span className="tag">{issue.publisher}</span>
</div>
<div className="mt-2">
<div key={idx} className="keen-slider__slide">
<Card
orientation={"vertical-2"}
imageUrl={issue.cover}
hasDetails
title={ellipsize(issue.name, 18)}
>
<div className="">
<span className="">{issue.publisher}</span>
<button
className="button is-small is-success is-outlined is-light"
className=""
onClick={() => addToLibrary("locg", issue)}
>
<i className="fa-solid fa-plus"></i> Want
Want
</button>
</div>
</div>
</Card>
</Card>
</div>
);
})}
</Slider>
</div>
)}
</>
);
};

View File

@@ -4,47 +4,40 @@ import { Link } from "react-router-dom";
import ellipsize from "ellipsize";
import { isEmpty, isNil, isUndefined, map } from "lodash";
import { detectIssueTypes } from "../../shared/utils/tradepaperback.utils";
import Masonry from "react-masonry-css";
import {
determineCoverFile,
determineExternalMetadata,
} from "../../shared/utils/metadata.utils";
import { LIBRARY_SERVICE_HOST } from "../../constants/endpoints";
type RecentlyImportedProps = {
comicBookCovers: any;
comics: any;
};
export const RecentlyImported = ({
comicBookCovers,
}: RecentlyImportedProps): ReactElement => {
const breakpointColumnsObj = {
default: 5,
1100: 4,
700: 2,
600: 2,
};
export const RecentlyImported = (
comics: RecentlyImportedProps,
): ReactElement => {
console.log(comics);
return (
<>
<div className="content mt-5">
<h4 className="title is-4">
<i className="fa-solid fa-file-arrow-down"></i> Recently Imported
</h4>
<p className="subtitle is-7">
<div className="mt-7">
<div className="">
<a className="" onClick={() => {}}>
<span className="text-xl">
<i className=""></i> Recently Imported
</span>
</a>
<p className="">
Recent Library activity such as imports, tagging, etc.
</p>
</div>
<Masonry
breakpointCols={breakpointColumnsObj}
className="recent-comics-container"
columnClassName="recent-comics-column"
>
{map(
comicBookCovers,
<div className="grid grid-cols-5 gap-6 mt-3">
{comics?.comics.map(
(
{
_id,
rawFileDetails,
sourcedMetadata: { comicvine, comicInfo, locg },
inferredMetadata,
acquisition: {
source: { name },
},
@@ -62,85 +55,80 @@ export const RecentlyImported = ({
comicInfo,
locg,
});
const isComicBookMetadataAvailable =
const isComicVineMetadataAvailable =
!isUndefined(comicvine) &&
!isUndefined(comicvine.volumeInformation);
const titleElement = (
<Link to={"/comic/details/" + _id}>
{ellipsize(issueName, 20)}
</Link>
);
return (
<React.Fragment key={_id}>
<Card
orientation={"vertical"}
imageUrl={url}
hasDetails
title={issueName ? titleElement : null}
>
<div className="content is-flex is-flex-direction-row">
{/* Raw file presence */}
{isNil(rawFileDetails) && (
<span className="icon custom-icon is-small has-text-danger mr-2">
<img src="/src/client/assets/img/missing-file.svg" />
<Card
orientation="vertical-2"
key={idx}
imageUrl={`${LIBRARY_SERVICE_HOST}/${rawFileDetails.cover.filePath}`}
title={inferredMetadata.issue.name}
hasDetails
>
<div>
<dd className="text-sm my-1 flex flex-row gap-1">
{/* Issue number */}
<span className="inline-flex items-center bg-slate-50 text-slate-800 text-xs font-medium px-2.5 py-0.5 rounded-md dark:text-slate-900 dark:bg-slate-400">
<span className="pr-1 pt-1">
<i className="icon-[solar--hashtag-outline]"></i>
</span>
)}
<span className="text-md text-slate-900">
{inferredMetadata.issue.number}
</span>
</span>
{/* File extension */}
<span className="inline-flex items-center bg-slate-50 text-slate-800 text-xs font-medium px-2.5 py-0.5 rounded-md dark:text-slate-900 dark:bg-slate-400">
<span className="pr-1 pt-1">
<i className="icon-[solar--file-bold-duotone] w-4 h-4"></i>
</span>
<span className="text-md text-slate-500 dark:text-slate-900">
{rawFileDetails.extension}
</span>
</span>
{/* Uncompressed status */}
{rawFileDetails?.archive?.uncompressed ? (
<span className="inline-flex items-center bg-slate-50 text-slate-800 text-xs font-medium px-2.5 py-0.5 rounded-md dark:text-slate-900 dark:bg-slate-400">
<span className="pr-1 pt-1">
<i className="icon-[solar--bookmark-bold-duotone] w-4 h-4"></i>
</span>
</span>
) : null}
</dd>
</div>
<div className="flex flex-row items-center gap-1 mt-2 pb-1">
<div className="sm:inline-flex sm:shrink-0 sm:items-center sm:gap-2">
{/* ComicInfo.xml presence */}
{!isNil(comicInfo) && !isEmpty(comicInfo) && (
<span className="icon custom-icon is-small has-text-danger">
<img
src="/src/client/assets/img/comicinfoxml.svg"
alt={"ComicInfo.xml file detected."}
/>
</span>
<div mt-1>
<i className="h-7 w-7 icon-[solar--code-file-bold-duotone] text-yellow-500 dark:text-yellow-300"></i>
</div>
)}
{/* ComicVine metadata presence */}
{isComicBookMetadataAvailable && (
<span className="icon custom-icon">
{isComicVineMetadataAvailable && (
<span className="w-7 h-7">
<img
src="/src/client/assets/img/cvlogo.svg"
alt={"ComicVine metadata detected."}
/>
</span>
)}
{/* Issue type */}
{isComicBookMetadataAvailable &&
!isNil(
detectIssueTypes(comicvine.volumeInformation.description),
) ? (
<span className="tag is-warning">
{
detectIssueTypes(
comicvine.volumeInformation.description,
).displayName
}
</span>
) : null}
</div>
</Card>
{/* metadata card */}
{!isNil(name) && (
<Card orientation="horizontal" hasDetails imageUrl={coverURL}>
<dd className="is-size-9">
<dl>
<span className="icon custom-icon">
<img src={`/src/client/assets/img/${icon}`} />
</span>
</dl>
<dl>
<span className="small-tag">
{ellipsize(issue, 15)}
</span>
</dl>
</dd>
</Card>
)}
</React.Fragment>
{/* Raw file presence */}
{isNil(rawFileDetails) && (
<span className="h-6 w-5 sm:shrink-0 sm:items-center sm:gap-2">
<i className="icon-[solar--file-corrupted-outline] h-5 w-5" />
</span>
)}
</div>
</Card>
);
},
)}
</Masonry>
</>
</div>
</div>
);
};
};

View File

@@ -83,7 +83,7 @@ const renderCard = (props: ICardProps): ReactElement => {
case "vertical-2":
return (
<div className="block rounded-md w-fit h-fit shadow-md shadow-white-400 bg-gray-200 dark:bg-slate-500">
<div className="block rounded-md w-64 h-fit shadow-md shadow-white-400 bg-gray-200 dark:bg-slate-500">
<img
alt="Home"
src={props.imageUrl}

View File

@@ -0,0 +1,16 @@
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;
};

View File

@@ -4111,11 +4111,6 @@ bl@^4.0.3, bl@^4.1.0:
inherits "^2.0.4"
readable-stream "^3.4.0"
blaze-slider@^1.9.3:
version "1.9.3"
resolved "https://registry.yarnpkg.com/blaze-slider/-/blaze-slider-1.9.3.tgz#cc3b5579072be984dfb8ced226c0e27a411f7bcc"
integrity sha512-u8i4AhbjICF6gpyKeMEg65kupT2JoN+E7yOJjOt3ocUtS+r4SKYU79DPNSBV2Zb61fix4ChIxfPyyRUSmLao5g==
bluebird@^3.7.2:
version "3.7.2"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
@@ -7525,6 +7520,11 @@ jsonfile@^6.0.1:
object.assign "^4.1.4"
object.values "^1.1.6"
keen-slider@^6.8.6:
version "6.8.6"
resolved "https://registry.yarnpkg.com/keen-slider/-/keen-slider-6.8.6.tgz#17bb7b219860f291a5ea5fde4496b737bf407a83"
integrity sha512-dcEQ7GDBpCjUQA8XZeWh3oBBLLmyn8aoeIQFGL/NTVkoEOsmlnXqA4QykUm/SncolAZYGsEk/PfUhLZ7mwMM2w==
keyv@^4.5.3:
version "4.5.4"
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
@@ -9002,11 +9002,6 @@ react-loader-spinner@^4.0.0:
dependencies:
prop-types "^15.7.2"
react-masonry-css@^1.0.16:
version "1.0.16"
resolved "https://registry.yarnpkg.com/react-masonry-css/-/react-masonry-css-1.0.16.tgz#72b28b4ae3484e250534700860597553a10f1a2c"
integrity sha512-KSW0hR2VQmltt/qAa3eXOctQDyOu7+ZBevtKgpNDSzT7k5LA/0XntNa9z9HKCdz3QlxmJHglTZ18e4sX4V8zZQ==
react-modal@^3.14.3, react-modal@^3.15.1:
version "3.16.1"
resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.16.1.tgz#34018528fc206561b1a5467fc3beeaddafb39b2b"