🔎 Scaffold for ES-powered issue matching in library first draft

This commit is contained in:
2022-02-02 18:17:52 -08:00
parent 1d317abbdb
commit bcfc174829
9 changed files with 275 additions and 57 deletions

View File

@@ -12,8 +12,8 @@ import {
IMS_COMIC_BOOK_ARCHIVE_EXTRACTION_SUCCESS,
IMS_COMIC_BOOK_ARCHIVE_EXTRACTION_CALL_IN_PROGRESS,
CV_ISSUES_METADATA_CALL_IN_PROGRESS,
CV_ISSUES_METADATA_FETCH_SUCCESS,
CV_CLEANUP,
IMS_COMIC_BOOKS_DB_OBJECTS_FETCHED,
} from "../constants/action-types";
import {
COMICBOOKINFO_SERVICE_URI,
@@ -103,6 +103,25 @@ export const getComicBookDetailById =
});
};
export const getComicBooksDetailsByIds =
(comicBookObjectIds: Array<string>) => async (dispatch) => {
dispatch({
type: IMS_COMIC_BOOK_DB_OBJECT_CALL_IN_PROGRESS,
IMS_inProgress: true,
});
const result = await axios.request({
url: `${IMPORT_SERVICE_BASE_URI}/getComicBooksByIds`,
method: "POST",
data: {
ids: comicBookObjectIds,
},
});
dispatch({
type: IMS_COMIC_BOOKS_DB_OBJECTS_FETCHED,
comicBooks: result.data,
});
};
export const applyComicVineMatch =
(match, comicObjectId) => async (dispatch) => {
dispatch({

View File

@@ -5,6 +5,8 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts";
@import "../../../../node_modules/@fortawesome/fontawesome-free/scss/solid.scss";
$bg-color: yellow;
$border-color: red;
$volume-color: #f2f1f9;
$size-8: 0.9rem;
.is-size-8 {
@@ -129,7 +131,7 @@ $size-8: 0.9rem;
box-shadow: 1px 8px 23px 7px rgba(0, 0, 0, 0.12);
.green-border {
border:2px dotted #999;
border:2px dotted #168B64;
border-radius: 0.3rem;
}
@@ -346,13 +348,18 @@ $size-8: 0.9rem;
// Volume detail
.volume-details {
.is-volume-related {
$tag-background-color: $volume-color;
}
.issues-container {
display: -webkit-box; /* Not needed if autoprefixing */
display: -ms-flexbox; /* Not needed if autoprefixing */
display: flex;
// margin-left: -30px; /* gutter size offset */
width: auto;
.issues-column {
padding: 10px;
max-width:102px;
margin: 10px;
background-clip: padding-box;
& > div {
/* change div to reference your elements you put in <Masonry> */
@@ -362,6 +369,12 @@ $size-8: 0.9rem;
}
}
// Potential issue matches in library slideout panel
.potential-matches-container {
.generic-card {
max-width: 90px;
}
}
// comicvine search results
.search-results-container {

View File

@@ -41,6 +41,7 @@ type ComicDetailProps = {};
*/
export const ComicDetail = ({}: ComicDetailProps): ReactElement => {
const [active, setActive] = useState(1);
const [page, setPage] = useState(1);
const [visible, setVisible] = useState(false);
const [slidingPanelContentId, setSlidingPanelContentId] = useState("");
@@ -116,8 +117,6 @@ export const ComicDetail = ({}: ComicDetailProps): ReactElement => {
},
};
const [active, setActive] = useState(1);
const isComicBookMetadataAvailable =
comicBookDetailData.sourcedMetadata &&
!isUndefined(comicBookDetailData.sourcedMetadata.comicvine) &&

View File

@@ -24,7 +24,7 @@ export const AcquisitionPanel = (
): ReactElement => {
const volumeName =
props.comicBookMetadata.sourcedMetadata.comicvine.volumeInformation.name;
const sanitizedVolumeName = volumeName.replace(/[^a-zA-Z0-9 ]/g, "");
const sanitizedVolumeName = volumeName.replace(/[^a-zA-Z0-9 ]/g, " ");
const issueName = props.comicBookMetadata.sourcedMetadata.comicvine.name;
// Selectors for picking state

View File

@@ -15,7 +15,6 @@ const handleBrokenImage = (e) => {
};
export const MatchResult = (props: MatchResultProps) => {
console.log(props);
const dispatch = useDispatch();
const applyCVMatch = useCallback(
(match, comicObjectId) => {

View File

@@ -0,0 +1,77 @@
import { isArray, map } from "lodash";
import React, { useEffect, ReactElement } from "react";
import { useDispatch, useSelector } from "react-redux";
import { getComicBooksDetailsByIds } from "../../actions/comicinfo.actions";
import { Card } from "../Carda";
import ellipsize from "ellipsize";
import { IMPORT_SERVICE_HOST } from "../../constants/endpoints";
import { escapePoundSymbol } from "../../shared/utils/formatting.utils";
const PotentialLibraryMatches = (props): ReactElement => {
const dispatch = useDispatch();
const comicBooks = useSelector(
(state: RootState) => state.comicInfo.comicBooksDetails,
);
useEffect(() => {
dispatch(getComicBooksDetailsByIds(props.matches));
}, []);
return (
<div className="potential-matches-container mt-10">
{isArray(comicBooks) ? (
map(comicBooks, (match) => {
const encodedFilePath = encodeURI(
`${IMPORT_SERVICE_HOST}/${match.rawFileDetails.cover.filePath}`,
);
const filePath = escapePoundSymbol(encodedFilePath);
return (
<>
{/* <pre>{JSON.stringify(match, undefined, 2)}</pre> */}
<div className="columns">
<div className="column is-one-fifth">
<Card
imageUrl={filePath}
orientation={"vertical"}
hasDetails={false}
/>
</div>
<div className="search-result-details column">
<div className="is-size-5">{match.rawFileDetails.name}</div>
<span className="subtitle is-size-7">
{match.rawFileDetails.cover.filePath}
</span>
<div className="field is-grouped is-grouped-multiline mt-4">
<div className="control">
<div className="tags has-addons">
<span className="tag">File Type</span>
<span className="tag is-primary">
{match.rawFileDetails.extension}
</span>
</div>
</div>
<div className="control">
<div className="tags has-addons">
<span className="tag">File Size</span>
<span className="tag is-warning">
{match.rawFileDetails.fileSize}
</span>
</div>
</div>
</div>
{/* <div className="is-size-7">
{ellipsize(issueDescription, 300)}
</div> */}
</div>
</div>
</>
);
})
) : (
<div>asdasd</div>
)}
</div>
);
};
export default PotentialLibraryMatches;

View File

@@ -1,4 +1,4 @@
import { isEmpty, isNil, isUndefined, map } from "lodash";
import { isEmpty, isUndefined, map, partialRight, pick } from "lodash";
import React, { useEffect, ReactElement, useState, useCallback } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useParams } from "react-router";
@@ -6,35 +6,33 @@ import {
getComicBookDetailById,
findIssuesForSeriesInLibrary,
} from "../../actions/comicinfo.actions";
import PotentialLibraryMatches from "./PotentialLibraryMatches";
import Masonry from "react-masonry-css";
import { Card } from "../Carda";
import SlidingPane from "react-sliding-pane";
import { convert } from "html-to-text";
import ellipsize from "ellipsize";
const VolumeDetails = (props): ReactElement => {
const breakpointColumnsObj = {
default: 5,
default: 6,
1100: 4,
700: 2,
600: 1,
700: 3,
600: 2,
};
// sliding panel config
const [visible, setVisible] = useState(false);
const [slidingPanelContentId, setSlidingPanelContentId] = useState("");
const [matches, setMatches] = useState([]);
const [active, setActive] = useState(1);
// sliding panel init
const contentForSlidingPanel = {
potentialMatchesInLibrary: {
content: () => {
console.log(matches);
return (
<div className="mt-10">
{map(matches, (match) => (
<pre>{JSON.stringify(match, undefined, 2)}</pre>
))}
</div>
);
const ids = map(matches, partialRight(pick, "_id"));
const matchIds = ids.map((id:any) => id._id);
return <PotentialLibraryMatches matches={matchIds} />;
},
},
};
@@ -61,6 +59,90 @@ const VolumeDetails = (props): ReactElement => {
const { comicObjectId } = useParams<{ comicObjectId: string }>();
const IssuesInVolume = () => (
<Masonry
breakpointCols={breakpointColumnsObj}
className="issues-container"
columnClassName="issues-column"
>
{!isUndefined(issuesForVolume) && !isEmpty(issuesForVolume)
? issuesForVolume.map((issue) => {
return (
<Card
key={issue.issue.id}
imageUrl={issue.issue.image.thumb_url}
orientation={"vertical"}
hasDetails={!isEmpty(issue.matches) ? true : false}
borderColorClass={!isEmpty(issue.matches) ? "green-border" : ""}
backgroundColor={!isEmpty(issue.matches) ? "#e0f5d0" : ""}
onClick={() => openPotentialLibraryMatchesPanel(issue.matches)}
>
{!isEmpty(issue.matches) ? (
<>
<span className="icon has-text-success">
<i className="fa-regular fa-clone"></i>
</span>
<span className="is-primary is-size-7">
{issue.issue.issue_number}
</span>
</>
) : null}
</Card>
);
})
: "loading"}
</Masonry>
);
// Tab content and header details
const tabGroup = [
{
id: 1,
name: "Issues in Volume",
icon: <i className="fa-solid fa-layer-group"></i>,
content: <IssuesInVolume key={1} />,
},
{
id: 2,
icon: <i className="fa-regular fa-mask"></i>,
name: "Characters",
content: <div key={2}>asdasd</div>,
},
{
id: 3,
icon: <i className="fa-solid fa-scroll"></i>,
name: "Arcs",
content: <div key={3}>asdasd</div>,
},
];
// Tabs
const MetadataTabGroup = () => {
return (
<>
<div className="tabs">
<ul>
{tabGroup.map(({ id, name, icon }) => (
<li
key={id}
className={id === active ? "is-active" : ""}
onClick={() => setActive(id)}
>
<a>
<span className="icon is-small">{icon}</span>
{name}
</a>
</li>
))}
</ul>
</div>
{tabGroup.map(({ id, content }) => {
return active === id ? content : null;
})}
</>
);
};
if (
!isUndefined(comicBookDetails.sourcedMetadata) &&
!isUndefined(comicBookDetails.sourcedMetadata.comicvine)
@@ -68,11 +150,13 @@ const VolumeDetails = (props): ReactElement => {
return (
<div className="container volume-details">
<div className="section">
{/* Title */}
<h1 className="title">
{comicBookDetails.sourcedMetadata.comicvine.volumeInformation.name}
</h1>
<div className="columns is-multiline">
<div className="column is-narrow is-full">
{/* Volume cover */}
<div className="column is-narrow">
<Card
imageUrl={
comicBookDetails.sourcedMetadata.comicvine.volumeInformation
@@ -82,44 +166,60 @@ const VolumeDetails = (props): ReactElement => {
hasDetails={false}
/>
</div>
<Masonry
breakpointCols={breakpointColumnsObj}
className="issues-container"
columnClassName="issues-column"
>
{!isUndefined(issuesForVolume) && !isEmpty(issuesForVolume)
? issuesForVolume.map((issue) => {
return (
<>
<Card
key={issue.issue.id}
imageUrl={issue.issue.image.thumb_url}
orientation={"vertical"}
hasDetails={!isEmpty(issue.matches) ? true : false}
borderColorClass={
!isEmpty(issue.matches) ? "green-border" : ""
}
backgroundColor={
!isEmpty(issue.matches) ? "#e0f5d0" : ""
}
onClick={() =>
openPotentialLibraryMatchesPanel(issue.matches)
}
>
{!isEmpty(issue.matches) ? (
<span className="icon is-success">
<i className="fa-solid fa-hands-asl-interpreting"></i>
</span>
) : null}
</Card>
{/* { JSON.stringify(issue, undefined, 2)} */}
</>
);
})
: "loading"}
</Masonry>
<div className="column is-three-fifths">
<div className="field is-grouped mt-2">
{/* Comicvine Id */}
<div className="control">
<div className="tags has-addons">
<span className="tag">ComicVine Id</span>
<span className="tag is-info is-light">
{
comicBookDetails.sourcedMetadata.comicvine
.volumeInformation.id
}
</span>
</div>
</div>
{/* Publisher */}
<div className="control">
<div className="tags has-addons">
<span className="tag is-warning is-light">Publisher</span>
<span className="tag is-volume-related">
{
comicBookDetails.sourcedMetadata.comicvine
.volumeInformation.publisher.name
}
</span>
</div>
</div>
</div>
{/* Deck */}
<div>
{!isEmpty(
comicBookDetails.sourcedMetadata.comicvine.volumeInformation
.description,
)
? ellipsize(
convert(
comicBookDetails.sourcedMetadata.comicvine
.volumeInformation.description,
{
baseElements: {
selectors: ["p"],
},
},
),
300,
)
: null}
</div>
</div>
{/* <pre>{JSON.stringify(issuesForVolume, undefined, 2)}</pre> */}
</div>
<MetadataTabGroup />
</div>
<SlidingPane

View File

@@ -25,8 +25,10 @@ export const IMS_CV_METADATA_IMPORT_FAILED = "IMS_CV_METADATA_IMPORT_FAILED";
export const IMS_RECENT_COMICS_FETCHED = "IMS_RECENT_COMICS_FETCHED";
export const IMS_DATA_FETCH_ERROR = "IMS_DATA_FETCH_ERROR";
// Single or multiple comic book mongo objects
export const IMS_COMIC_BOOK_DB_OBJECT_FETCHED =
"IMS_COMIC_BOOK_DB_OBJECT_FETCHED";
export const IMS_COMIC_BOOKS_DB_OBJECTS_FETCHED = "IMS_COMIC_BOOKS_DB_OBJECTS_FETCHED";
export const IMS_COMIC_BOOK_DB_OBJECT_CALL_IN_PROGRESS =
"IMS_COMIC_BOOK_DB_OBJECT_CALL_IN_PROGRESS";
export const IMS_COMIC_BOOK_DB_OBJECT_CALL_FAILED =

View File

@@ -3,6 +3,7 @@ import {
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_METADATA_FETCH_SUCCESS,
@@ -14,6 +15,7 @@ const initialState = {
searchQuery: {},
inProgress: false,
comicBookDetail: {},
comicBooksDetails: [],
issuesForVolume: [],
IMS_inProgress: false,
};
@@ -44,6 +46,13 @@ function comicinfoReducer(state = initialState, action) {
comicBookDetail: action.comicBookDetail,
IMS_inProgress: false,
};
case IMS_COMIC_BOOKS_DB_OBJECTS_FETCHED:
console.log(action);
return {
...state,
comicBooksDetails: action.comicBooks,
IMS_inProgress: false,
};
case CV_CLEANUP:
return {
...state,