[chore] moving things around in prep for graphql
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import React, { ReactElement } from "react";
|
import React, { ReactElement } from "react";
|
||||||
import Select from "react-select";
|
import Select from "react-select";
|
||||||
|
|
||||||
export const Menu = (props): ReactElement => {
|
export const Menu = (props: any): ReactElement => {
|
||||||
const {
|
const {
|
||||||
filteredActionOptions,
|
filteredActionOptions,
|
||||||
customStyles,
|
customStyles,
|
||||||
@@ -13,11 +13,11 @@ export const Menu = (props): ReactElement => {
|
|||||||
<Select
|
<Select
|
||||||
components={{ Placeholder }}
|
components={{ Placeholder }}
|
||||||
placeholder={
|
placeholder={
|
||||||
<span className="inline-flex flex-row items-center gap-2 pt-1">
|
<span className="inline-flex flex-row items-center gap-1.5 pt-1">
|
||||||
<div className="w-6 h-6">
|
<div className="w-4 h-4">
|
||||||
<i className="icon-[solar--cursor-bold-duotone] w-6 h-6"></i>
|
<i className="icon-[solar--cursor-bold-duotone] w-4 h-4"></i>
|
||||||
</div>
|
</div>
|
||||||
<div>Select An Action</div>
|
<div className="text-sm">Select An Action</div>
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
styles={customStyles}
|
styles={customStyles}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import { VolumeInformation } from "./Tabs/VolumeInformation";
|
|||||||
|
|
||||||
import { isEmpty, isUndefined, isNil, filter } from "lodash";
|
import { isEmpty, isUndefined, isNil, filter } from "lodash";
|
||||||
import { components } from "react-select";
|
import { components } from "react-select";
|
||||||
import { RootState } from "threetwo-ui-typings";
|
|
||||||
|
|
||||||
import "react-sliding-pane/dist/react-sliding-pane.css";
|
import "react-sliding-pane/dist/react-sliding-pane.css";
|
||||||
import "react-loader-spinner/dist/loader/css/react-spinner-loader.css";
|
import "react-loader-spinner/dist/loader/css/react-spinner-loader.css";
|
||||||
@@ -37,20 +36,30 @@ import { refineQuery } from "filename-parser";
|
|||||||
interface ComicDetailProps {
|
interface ComicDetailProps {
|
||||||
data: {
|
data: {
|
||||||
_id: string;
|
_id: string;
|
||||||
rawFileDetails: {};
|
rawFileDetails?: any;
|
||||||
inferredMetadata: {
|
inferredMetadata?: {
|
||||||
issue: {};
|
issue?: {
|
||||||
|
year?: string;
|
||||||
|
name?: string;
|
||||||
|
number?: number;
|
||||||
|
subtitle?: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
sourcedMetadata: {
|
sourcedMetadata: {
|
||||||
comicvine: {};
|
comicvine?: any;
|
||||||
locg: {};
|
locg?: any;
|
||||||
comicInfo: {};
|
comicInfo?: any;
|
||||||
|
};
|
||||||
|
acquisition?: {
|
||||||
|
directconnect?: {
|
||||||
|
downloads?: any[];
|
||||||
|
};
|
||||||
|
torrent?: any[];
|
||||||
};
|
};
|
||||||
acquisition: {};
|
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
};
|
};
|
||||||
userSettings: {};
|
userSettings?: any;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Component for displaying the metadata for a comic in greater detail.
|
* Component for displaying the metadata for a comic in greater detail.
|
||||||
@@ -102,7 +111,7 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
|
|||||||
const StyledSlidingPanel = styled(SlidingPane)`
|
const StyledSlidingPanel = styled(SlidingPane)`
|
||||||
background: #ccc;
|
background: #ccc;
|
||||||
`;
|
`;
|
||||||
const afterOpenModal = useCallback((things) => {
|
const afterOpenModal = useCallback((things: any) => {
|
||||||
// references are now sync'd and can be accessed.
|
// references are now sync'd and can be accessed.
|
||||||
// subtitle.style.color = "#f00";
|
// subtitle.style.color = "#f00";
|
||||||
console.log("kolaveri", things);
|
console.log("kolaveri", things);
|
||||||
@@ -113,9 +122,9 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// sliding panel init
|
// sliding panel init
|
||||||
const contentForSlidingPanel = {
|
const contentForSlidingPanel: Record<string, { content: (props?: any) => JSX.Element }> = {
|
||||||
CVMatches: {
|
CVMatches: {
|
||||||
content: (props) => (
|
content: (props?: any) => (
|
||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
<ComicVineSearchForm data={rawFileDetails} />
|
<ComicVineSearchForm data={rawFileDetails} />
|
||||||
@@ -123,7 +132,7 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
|
|||||||
|
|
||||||
<div className="border-slate-500 border rounded-lg p-2 mt-3">
|
<div className="border-slate-500 border rounded-lg p-2 mt-3">
|
||||||
<p className="">Searching for:</p>
|
<p className="">Searching for:</p>
|
||||||
{inferredMetadata.issue ? (
|
{inferredMetadata?.issue ? (
|
||||||
<>
|
<>
|
||||||
<span className="">{inferredMetadata.issue.name} </span>
|
<span className="">{inferredMetadata.issue.name} </span>
|
||||||
<span className=""> # {inferredMetadata.issue.number} </span>
|
<span className=""> # {inferredMetadata.issue.number} </span>
|
||||||
@@ -148,9 +157,9 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
|
|||||||
// Actions
|
// Actions
|
||||||
|
|
||||||
const fetchComicVineMatches = async (
|
const fetchComicVineMatches = async (
|
||||||
searchPayload,
|
searchPayload: any,
|
||||||
issueSearchQuery,
|
issueSearchQuery: any,
|
||||||
seriesSearchQuery,
|
seriesSearchQuery: any,
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const response = await axios({
|
const response = await axios({
|
||||||
@@ -170,7 +179,7 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
|
|||||||
},
|
},
|
||||||
rawFileDetails: searchPayload,
|
rawFileDetails: searchPayload,
|
||||||
},
|
},
|
||||||
transformResponse: (r) => {
|
transformResponse: (r: string) => {
|
||||||
const matches = JSON.parse(r);
|
const matches = JSON.parse(r);
|
||||||
return matches;
|
return matches;
|
||||||
// return sortBy(matches, (match) => -match.score);
|
// return sortBy(matches, (match) => -match.score);
|
||||||
@@ -180,9 +189,9 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
|
|||||||
if (!isNil(response.data.results) && response.data.results.length === 1) {
|
if (!isNil(response.data.results) && response.data.results.length === 1) {
|
||||||
matches = response.data.results;
|
matches = response.data.results;
|
||||||
} else {
|
} else {
|
||||||
matches = response.data.map((match) => match);
|
matches = response.data.map((match: any) => match);
|
||||||
}
|
}
|
||||||
const scoredMatches = matches.sort((a, b) => b.score - a.score);
|
const scoredMatches = matches.sort((a: any, b: any) => b.score - a.score);
|
||||||
setComicVineMatches(scoredMatches);
|
setComicVineMatches(scoredMatches);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
@@ -191,13 +200,13 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
|
|||||||
|
|
||||||
// Action event handlers
|
// Action event handlers
|
||||||
const openDrawerWithCVMatches = () => {
|
const openDrawerWithCVMatches = () => {
|
||||||
let seriesSearchQuery: IComicVineSearchQuery = {} as IComicVineSearchQuery;
|
let seriesSearchQuery: any = {};
|
||||||
let issueSearchQuery: IComicVineSearchQuery = {} as IComicVineSearchQuery;
|
let issueSearchQuery: any = {};
|
||||||
|
|
||||||
if (!isUndefined(rawFileDetails)) {
|
if (!isUndefined(rawFileDetails)) {
|
||||||
issueSearchQuery = refineQuery(rawFileDetails.name);
|
issueSearchQuery = refineQuery((rawFileDetails as any).name);
|
||||||
} else if (!isEmpty(comicvine)) {
|
} else if (!isEmpty(comicvine)) {
|
||||||
issueSearchQuery = refineQuery(comicvine.name);
|
issueSearchQuery = refineQuery((comicvine as any).name);
|
||||||
}
|
}
|
||||||
fetchComicVineMatches(rawFileDetails, issueSearchQuery, seriesSearchQuery);
|
fetchComicVineMatches(rawFileDetails, issueSearchQuery, seriesSearchQuery);
|
||||||
setSlidingPanelContentId("CVMatches");
|
setSlidingPanelContentId("CVMatches");
|
||||||
@@ -211,30 +220,30 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
|
|||||||
|
|
||||||
// Actions menu options and handler
|
// Actions menu options and handler
|
||||||
const CVMatchLabel = (
|
const CVMatchLabel = (
|
||||||
<span className="inline-flex flex-row items-center gap-2">
|
<span className="inline-flex flex-row items-center gap-1.5">
|
||||||
<div className="w-6 h-6">
|
<div className="w-4 h-4">
|
||||||
<i className="icon-[solar--magic-stick-3-bold-duotone] w-6 h-6"></i>
|
<i className="icon-[solar--magic-stick-3-bold-duotone] w-4 h-4"></i>
|
||||||
</div>
|
</div>
|
||||||
<div>Match on ComicVine</div>
|
<div className="text-sm">Match on ComicVine</div>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
const editLabel = (
|
const editLabel = (
|
||||||
<span className="inline-flex flex-row items-center gap-2">
|
<span className="inline-flex flex-row items-center gap-1.5">
|
||||||
<div className="w-6 h-6">
|
<div className="w-4 h-4">
|
||||||
<i className="icon-[solar--pen-2-bold-duotone] w-6 h-6"></i>
|
<i className="icon-[solar--pen-2-bold-duotone] w-4 h-4"></i>
|
||||||
</div>
|
</div>
|
||||||
<div>Edit Metadata</div>
|
<div className="text-sm">Edit Metadata</div>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
const deleteLabel = (
|
const deleteLabel = (
|
||||||
<span className="inline-flex flex-row items-center gap-2">
|
<span className="inline-flex flex-row items-center gap-1.5">
|
||||||
<div className="w-6 h-6">
|
<div className="w-4 h-4">
|
||||||
<i className="icon-[solar--trash-bin-trash-bold-duotone] w-6 h-6"></i>
|
<i className="icon-[solar--trash-bin-trash-bold-duotone] w-4 h-4"></i>
|
||||||
</div>
|
</div>
|
||||||
<div>Delete Comic</div>
|
<div className="text-sm">Delete Comic</div>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
const Placeholder = (props) => {
|
const Placeholder = (props: any) => {
|
||||||
return <components.Placeholder {...props} />;
|
return <components.Placeholder {...props} />;
|
||||||
};
|
};
|
||||||
const actionOptions = [
|
const actionOptions = [
|
||||||
@@ -249,7 +258,7 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
|
|||||||
}
|
}
|
||||||
return item;
|
return item;
|
||||||
});
|
});
|
||||||
const handleActionSelection = (action) => {
|
const handleActionSelection = (action: any) => {
|
||||||
switch (action.value) {
|
switch (action.value) {
|
||||||
case "match-on-comic-vine":
|
case "match-on-comic-vine":
|
||||||
openDrawerWithCVMatches();
|
openDrawerWithCVMatches();
|
||||||
@@ -263,23 +272,23 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
const customStyles = {
|
const customStyles = {
|
||||||
menu: (base) => ({
|
menu: (base: any) => ({
|
||||||
...base,
|
...base,
|
||||||
backgroundColor: "rgb(156, 163, 175)",
|
backgroundColor: "rgb(156, 163, 175)",
|
||||||
}),
|
}),
|
||||||
placeholder: (base) => ({
|
placeholder: (base: any) => ({
|
||||||
...base,
|
...base,
|
||||||
color: "black",
|
color: "black",
|
||||||
}),
|
}),
|
||||||
option: (base, { data, isDisabled, isFocused, isSelected }) => ({
|
option: (base: any, { data, isDisabled, isFocused, isSelected }: any) => ({
|
||||||
...base,
|
...base,
|
||||||
backgroundColor: isFocused ? "gray" : "rgb(156, 163, 175)",
|
backgroundColor: isFocused ? "gray" : "rgb(156, 163, 175)",
|
||||||
}),
|
}),
|
||||||
singleValue: (base) => ({
|
singleValue: (base: any) => ({
|
||||||
...base,
|
...base,
|
||||||
paddingTop: "0.4rem",
|
paddingTop: "0.4rem",
|
||||||
}),
|
}),
|
||||||
control: (base) => ({
|
control: (base: any) => ({
|
||||||
...base,
|
...base,
|
||||||
backgroundColor: "rgb(156, 163, 175)",
|
backgroundColor: "rgb(156, 163, 175)",
|
||||||
color: "black",
|
color: "black",
|
||||||
@@ -289,7 +298,7 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
|
|||||||
|
|
||||||
// check for the availability of CV metadata
|
// check for the availability of CV metadata
|
||||||
const isComicBookMetadataAvailable =
|
const isComicBookMetadataAvailable =
|
||||||
!isUndefined(comicvine) && !isUndefined(comicvine.volumeInformation);
|
!isUndefined(comicvine) && !isUndefined((comicvine as any)?.volumeInformation);
|
||||||
|
|
||||||
// check for the availability of rawFileDetails
|
// check for the availability of rawFileDetails
|
||||||
const areRawFileDetailsAvailable =
|
const areRawFileDetailsAvailable =
|
||||||
@@ -354,7 +363,7 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
|
|||||||
query={airDCPPQuery}
|
query={airDCPPQuery}
|
||||||
comicObjectId={_id}
|
comicObjectId={_id}
|
||||||
comicObject={data.data}
|
comicObject={data.data}
|
||||||
userSettings={userSettings}
|
settings={userSettings}
|
||||||
key={4}
|
key={4}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
@@ -376,8 +385,8 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
|
|||||||
name: "Downloads",
|
name: "Downloads",
|
||||||
icon: (
|
icon: (
|
||||||
<>
|
<>
|
||||||
{acquisition?.directconnect?.downloads?.length +
|
{(acquisition?.directconnect?.downloads?.length || 0) +
|
||||||
acquisition?.torrent.length}
|
(acquisition?.torrent?.length || 0)}
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
content:
|
content:
|
||||||
@@ -414,11 +423,12 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
|
|||||||
imageUrl={url}
|
imageUrl={url}
|
||||||
orientation={"cover-only"}
|
orientation={"cover-only"}
|
||||||
hasDetails={false}
|
hasDetails={false}
|
||||||
|
cardContainerStyle={{ maxWidth: "290px", width: "100%" }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* raw file details */}
|
{/* raw file details */}
|
||||||
{!isUndefined(rawFileDetails) &&
|
{!isUndefined(rawFileDetails) &&
|
||||||
!isEmpty(rawFileDetails.cover) && (
|
!isEmpty((rawFileDetails as any)?.cover) && (
|
||||||
<div className="grid">
|
<div className="grid">
|
||||||
<RawFileDetails
|
<RawFileDetails
|
||||||
data={{
|
data={{
|
||||||
@@ -468,7 +478,7 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
|
|||||||
|
|
||||||
<TabControls
|
<TabControls
|
||||||
filteredTabs={filteredTabs}
|
filteredTabs={filteredTabs}
|
||||||
downloadCount={acquisition?.directconnect?.downloads?.length}
|
downloadCount={acquisition?.directconnect?.downloads?.length || 0}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<StyledSlidingPanel
|
<StyledSlidingPanel
|
||||||
|
|||||||
@@ -30,94 +30,125 @@ interface RawFileDetailsProps {
|
|||||||
children?: any;
|
children?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RawFileDetails = (props: RawFileDetailsProps): ReactElement => {
|
export const RawFileDetails = (props: RawFileDetailsProps): ReactElement | null => {
|
||||||
const { rawFileDetails, inferredMetadata, created_at, updated_at } =
|
const { rawFileDetails, inferredMetadata, created_at, updated_at } =
|
||||||
props.data;
|
props.data || {};
|
||||||
|
if (!rawFileDetails) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="max-w-2xl ml-5">
|
<div className="max-w-2xl ml-5">
|
||||||
<div className="px-4 sm:px-6">
|
{/* Title */}
|
||||||
|
<div className="px-4 sm:px-6 mb-6">
|
||||||
<p className="text-gray-500 dark:text-gray-400">
|
<p className="text-gray-500 dark:text-gray-400">
|
||||||
<span className="text-xl">{rawFileDetails.name}</span>
|
<span className="text-xl font-semibold">{rawFileDetails?.name}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="px-4 py-5 sm:px-6">
|
|
||||||
<dl className="grid grid-cols-1 gap-x-4 gap-y-4 sm:grid-cols-2">
|
{/* File Binary Details Section */}
|
||||||
<div className="sm:col-span-1">
|
<div className="mb-8 px-4 pb-8 border-b border-gray-200 dark:border-gray-700">
|
||||||
<dt className="text-sm font-medium text-gray-500 dark:text-gray-400">
|
<div className="mb-4">
|
||||||
Raw File Details
|
<h3 className="text-sm font-medium text-gray-600 dark:text-gray-300 uppercase tracking-wide flex items-center gap-1">
|
||||||
</dt>
|
<i className="icon-[solar--document-bold-duotone] w-5 h-5"></i>
|
||||||
<dd className="mt-1 text-sm text-gray-900 dark:text-gray-400">
|
File Binary Details
|
||||||
{rawFileDetails.containedIn +
|
</h3>
|
||||||
"/" +
|
</div>
|
||||||
rawFileDetails.name +
|
<div className="pl-6">
|
||||||
rawFileDetails.extension}
|
<dl className="space-y-4">
|
||||||
</dd>
|
<div>
|
||||||
</div>
|
<dt className="text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wide mb-1">
|
||||||
<div className="sm:col-span-1">
|
File Path
|
||||||
<dt className="text-sm font-medium text-gray-500 dark:text-gray-400">
|
</dt>
|
||||||
Inferred Issue Metadata
|
<dd className="text-sm text-gray-900 dark:text-gray-300 font-mono break-all">
|
||||||
</dt>
|
{rawFileDetails?.containedIn}/{rawFileDetails?.name}{rawFileDetails?.extension}
|
||||||
<dd className="mt-1 text-sm text-gray-900 dark:text-gray-400">
|
</dd>
|
||||||
Series Name: {inferredMetadata.issue.name}
|
</div>
|
||||||
{!isEmpty(inferredMetadata.issue.number) ? (
|
|
||||||
<span className="tag is-primary is-light">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
{inferredMetadata.issue.number}
|
<div>
|
||||||
</span>
|
<dt className="text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wide mb-1">
|
||||||
) : null}
|
MIME Type
|
||||||
</dd>
|
</dt>
|
||||||
</div>
|
<dd className="text-sm text-gray-700 dark:text-gray-300 flex items-center gap-1">
|
||||||
<div className="sm:col-span-1">
|
|
||||||
<dt className="text-sm font-medium text-gray-500 dark:text-gray-400">
|
|
||||||
MIMEType
|
|
||||||
</dt>
|
|
||||||
<dd className="mt-1 text-sm text-gray-500 dark:text-slate-900">
|
|
||||||
{/* File extension */}
|
|
||||||
<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="pt-1">
|
|
||||||
<i className="icon-[solar--zip-file-bold-duotone] w-5 h-5"></i>
|
<i className="icon-[solar--zip-file-bold-duotone] w-5 h-5"></i>
|
||||||
</span>
|
{rawFileDetails?.mimeType}
|
||||||
|
</dd>
|
||||||
<span className="text-md text-slate-500 dark:text-slate-900">
|
</div>
|
||||||
{rawFileDetails.mimeType}
|
|
||||||
</span>
|
<div>
|
||||||
</span>
|
<dt className="text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wide mb-1">
|
||||||
</dd>
|
File Size
|
||||||
</div>
|
</dt>
|
||||||
<div className="sm:col-span-1">
|
<dd className="text-sm text-gray-700 dark:text-gray-300 flex items-center gap-1">
|
||||||
<dt className="text-sm font-medium text-gray-500 dark:text-gray-400">
|
|
||||||
File Size
|
|
||||||
</dt>
|
|
||||||
<dd className="mt-1 text-sm text-gray-500 dark:text-slate-900">
|
|
||||||
{/* size */}
|
|
||||||
<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--mirror-right-bold-duotone] w-5 h-5"></i>
|
<i className="icon-[solar--mirror-right-bold-duotone] w-5 h-5"></i>
|
||||||
</span>
|
{rawFileDetails?.fileSize ? prettyBytes(rawFileDetails.fileSize) : 'N/A'}
|
||||||
|
</dd>
|
||||||
<span className="text-md text-slate-500 dark:text-slate-900">
|
</div>
|
||||||
{prettyBytes(rawFileDetails.fileSize)}
|
</div>
|
||||||
</span>
|
</dl>
|
||||||
</span>
|
</div>
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
<div className="sm:col-span-2">
|
|
||||||
<dt className="text-sm font-medium text-gray-500 dark:text-gray-400">
|
|
||||||
Import Details
|
|
||||||
</dt>
|
|
||||||
<dd className="mt-1 text-sm text-gray-900 dark:text-gray-400">
|
|
||||||
{format(parseISO(created_at), "dd MMMM, yyyy")},{" "}
|
|
||||||
{format(parseISO(created_at), "h aaaa")}
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
<div className="sm:col-span-2">
|
|
||||||
<dt className="text-sm font-medium text-gray-500 dark:text-gray-400">
|
|
||||||
Actions
|
|
||||||
</dt>
|
|
||||||
<dd className="mt-1 text-sm text-gray-900">{props.children}</dd>
|
|
||||||
</div>
|
|
||||||
</dl>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Import Details Section */}
|
||||||
|
<div className="mb-8 px-4">
|
||||||
|
<div className="mb-4">
|
||||||
|
<h3 className="text-sm font-medium text-gray-600 dark:text-gray-300 uppercase tracking-wide flex items-center gap-1">
|
||||||
|
<i className="icon-[solar--import-bold-duotone] w-5 h-5"></i>
|
||||||
|
Import Details
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div className="pl-6">
|
||||||
|
<dl className="space-y-4">
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<dt className="text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wide mb-1">
|
||||||
|
Imported On
|
||||||
|
</dt>
|
||||||
|
<dd className="text-sm text-gray-700 dark:text-gray-300">
|
||||||
|
{created_at ? format(parseISO(created_at), "dd MMMM, yyyy 'at' h:mm aaaa") : 'N/A'}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<dt className="text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wide mb-1">
|
||||||
|
Last Updated
|
||||||
|
</dt>
|
||||||
|
<dd className="text-sm text-gray-700 dark:text-gray-300">
|
||||||
|
{updated_at ? format(parseISO(updated_at), "dd MMMM, yyyy 'at' h:mm aaaa") : 'N/A'}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{inferredMetadata?.issue && (
|
||||||
|
<div>
|
||||||
|
<dt className="text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wide mb-1">
|
||||||
|
Inferred Metadata
|
||||||
|
</dt>
|
||||||
|
<dd className="text-sm text-gray-700 dark:text-gray-300">
|
||||||
|
<span className="font-medium">{inferredMetadata.issue.name}</span>
|
||||||
|
{!isEmpty(inferredMetadata.issue.number) && (
|
||||||
|
<span className="ml-2 text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
#{inferredMetadata.issue.number}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Actions Section */}
|
||||||
|
{props.children && (
|
||||||
|
<div className="px-4">
|
||||||
|
<div className="mb-3">
|
||||||
|
<h4 className="text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wide">
|
||||||
|
Actions
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div>{props.children}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -14,12 +14,16 @@ import { useStore } from "../../../store";
|
|||||||
import { useShallow } from "zustand/react/shallow";
|
import { useShallow } from "zustand/react/shallow";
|
||||||
import { escapePoundSymbol } from "../../../shared/utils/formatting.utils";
|
import { escapePoundSymbol } from "../../../shared/utils/formatting.utils";
|
||||||
|
|
||||||
export const ArchiveOperations = (props): ReactElement => {
|
interface ArchiveOperationsProps {
|
||||||
|
data: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ArchiveOperations = (props: ArchiveOperationsProps): ReactElement => {
|
||||||
const { data } = props;
|
const { data } = props;
|
||||||
|
|
||||||
const { socketIOInstance } = useStore(
|
const { getSocket } = useStore(
|
||||||
useShallow((state) => ({
|
useShallow((state) => ({
|
||||||
socketIOInstance: state.socketIOInstance,
|
getSocket: state.getSocket,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
@@ -27,21 +31,32 @@ export const ArchiveOperations = (props): ReactElement => {
|
|||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [slidingPanelContentId, setSlidingPanelContentId] = useState("");
|
const [slidingPanelContentId, setSlidingPanelContentId] = useState("");
|
||||||
// current image
|
// current image
|
||||||
const [currentImage, setCurrentImage] = useState([]);
|
const [currentImage, setCurrentImage] = useState<string>("");
|
||||||
const [uncompressedArchive, setUncompressedArchive] = useState([]);
|
const [uncompressedArchive, setUncompressedArchive] = useState<string[]>([]);
|
||||||
const [imageAnalysisResult, setImageAnalysisResult] = useState({});
|
const [imageAnalysisResult, setImageAnalysisResult] = useState<any>({});
|
||||||
const [shouldRefetchComicBookData, setShouldRefetchComicBookData] =
|
const [shouldRefetchComicBookData, setShouldRefetchComicBookData] =
|
||||||
useState(false);
|
useState(false);
|
||||||
const constructImagePaths = (data): Array<string> => {
|
const constructImagePaths = (data: string[]): Array<string> => {
|
||||||
return data?.map((path: string) =>
|
return data?.map((path: string) =>
|
||||||
escapePoundSymbol(encodeURI(`${LIBRARY_SERVICE_HOST}/${path}`)),
|
escapePoundSymbol(encodeURI(`${LIBRARY_SERVICE_HOST}/${path}`)),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Listen to the uncompression complete event and orchestrate the final payload
|
// Listen to the uncompression complete event and orchestrate the final payload
|
||||||
socketIOInstance.on("LS_UNCOMPRESSION_JOB_COMPLETE", (data) => {
|
useEffect(() => {
|
||||||
setUncompressedArchive(constructImagePaths(data?.uncompressedArchive));
|
const socket = getSocket("/");
|
||||||
});
|
|
||||||
|
const handleUncompressionComplete = (data: any) => {
|
||||||
|
setUncompressedArchive(constructImagePaths(data?.uncompressedArchive));
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.on("LS_UNCOMPRESSION_JOB_COMPLETE", handleUncompressionComplete);
|
||||||
|
|
||||||
|
// Cleanup listener on unmount
|
||||||
|
return () => {
|
||||||
|
socket.off("LS_UNCOMPRESSION_JOB_COMPLETE", handleUncompressionComplete);
|
||||||
|
};
|
||||||
|
}, [getSocket]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let isMounted = true;
|
let isMounted = true;
|
||||||
@@ -58,7 +73,7 @@ export const ArchiveOperations = (props): ReactElement => {
|
|||||||
},
|
},
|
||||||
transformResponse: async (responseData) => {
|
transformResponse: async (responseData) => {
|
||||||
const parsedData = JSON.parse(responseData);
|
const parsedData = JSON.parse(responseData);
|
||||||
const paths = parsedData.map((pathObject) => {
|
const paths = parsedData.map((pathObject: any) => {
|
||||||
return `${pathObject.containedIn}/${pathObject.name}${pathObject.extension}`;
|
return `${pathObject.containedIn}/${pathObject.name}${pathObject.extension}`;
|
||||||
});
|
});
|
||||||
const uncompressedArchive = constructImagePaths(paths);
|
const uncompressedArchive = constructImagePaths(paths);
|
||||||
@@ -131,7 +146,7 @@ export const ArchiveOperations = (props): ReactElement => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// sliding panel init
|
// sliding panel init
|
||||||
const contentForSlidingPanel = {
|
const contentForSlidingPanel: Record<string, { content: () => JSX.Element }> = {
|
||||||
imageAnalysis: {
|
imageAnalysis: {
|
||||||
content: () => {
|
content: () => {
|
||||||
return (
|
return (
|
||||||
@@ -143,7 +158,7 @@ export const ArchiveOperations = (props): ReactElement => {
|
|||||||
</pre>
|
</pre>
|
||||||
) : null}
|
) : null}
|
||||||
<pre className="font-hasklig mt-3 text-sm">
|
<pre className="font-hasklig mt-3 text-sm">
|
||||||
{JSON.stringify(imageAnalysisResult.analyzedData, null, 2)}
|
{JSON.stringify(imageAnalysisResult?.analyzedData, null, 2)}
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -152,7 +167,7 @@ export const ArchiveOperations = (props): ReactElement => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// sliding panel handlers
|
// sliding panel handlers
|
||||||
const openImageAnalysisPanel = useCallback((imageFilePath) => {
|
const openImageAnalysisPanel = useCallback((imageFilePath: string) => {
|
||||||
setSlidingPanelContentId("imageAnalysis");
|
setSlidingPanelContentId("imageAnalysis");
|
||||||
analyzeImage(imageFilePath);
|
analyzeImage(imageFilePath);
|
||||||
setCurrentImage(imageFilePath);
|
setCurrentImage(imageFilePath);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { ReactElement, useState } from "react";
|
import React, { ReactElement, useState, useEffect } from "react";
|
||||||
import { map } from "lodash";
|
import { map } from "lodash";
|
||||||
import Card from "../shared/Carda";
|
import Card from "../shared/Carda";
|
||||||
import Header from "../shared/Header";
|
import Header from "../shared/Header";
|
||||||
@@ -34,24 +34,35 @@ export const PullList = (): ReactElement => {
|
|||||||
format(date, "yyyy/M/dd"),
|
format(date, "yyyy/M/dd"),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Responsive slides per view
|
||||||
|
const [slidesPerView, setSlidesPerView] = useState(1);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// keen slider
|
// keen slider
|
||||||
const [sliderRef, instanceRef] = useKeenSlider(
|
const [sliderRef, instanceRef] = useKeenSlider({
|
||||||
{
|
loop: true,
|
||||||
loop: true,
|
mode: "free-snap",
|
||||||
slides: {
|
slides: {
|
||||||
origin: "auto",
|
perView: slidesPerView,
|
||||||
number: 15,
|
spacing: 15,
|
||||||
perView: 5,
|
|
||||||
spacing: 15,
|
|
||||||
},
|
|
||||||
slideChanged() {
|
|
||||||
console.log("slide changed");
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
[
|
slideChanged() {
|
||||||
// add plugins here
|
console.log("slide changed");
|
||||||
],
|
},
|
||||||
);
|
});
|
||||||
|
|
||||||
|
// Update slider when slidesPerView changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (instanceRef.current) {
|
||||||
|
instanceRef.current.update({
|
||||||
|
slides: {
|
||||||
|
perView: slidesPerView,
|
||||||
|
spacing: 15,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [slidesPerView, instanceRef]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: pullList,
|
data: pullList,
|
||||||
@@ -80,79 +91,81 @@ export const PullList = (): ReactElement => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="content">
|
<div className="content">
|
||||||
<Header
|
<div className="mx-auto">
|
||||||
headerContent="Discover"
|
<Header
|
||||||
subHeaderContent={
|
headerContent="Discover"
|
||||||
<span className="text-md">
|
subHeaderContent={
|
||||||
Pull List aggregated for the week from{" "}
|
<span className="text-md">
|
||||||
<span className="underline">
|
Pull List aggregated for the week from{" "}
|
||||||
<a href="https://leagueofcomicgeeks.com">
|
<span className="underline">
|
||||||
League Of Comic Geeks
|
<a href="https://leagueofcomicgeeks.com">
|
||||||
</a>
|
League Of Comic Geeks
|
||||||
<i className="icon-[solar--arrow-right-up-outline] w-4 h-4" />
|
</a>
|
||||||
|
<i className="icon-[solar--arrow-right-up-outline] w-4 h-4" />
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
}
|
||||||
}
|
iconClassNames="fa-solid fa-binoculars mr-2"
|
||||||
iconClassNames="fa-solid fa-binoculars mr-2"
|
link="/pull-list/all/"
|
||||||
link="/pull-list/all/"
|
/>
|
||||||
/>
|
<div className="flex flex-row gap-5 mb-3">
|
||||||
<div className="flex flex-row gap-5 mb-3">
|
{/* select week */}
|
||||||
{/* select week */}
|
<div className="flex flex-row gap-4 my-3">
|
||||||
<div className="flex flex-row gap-4 my-3">
|
<Form
|
||||||
<Form
|
onSubmit={() => {}}
|
||||||
onSubmit={() => {}}
|
render={({ handleSubmit }) => (
|
||||||
render={({ handleSubmit }) => (
|
<form>
|
||||||
<form>
|
<div className="flex flex-col gap-2">
|
||||||
<div className="flex flex-col gap-2">
|
{/* week selection for pull list */}
|
||||||
{/* week selection for pull list */}
|
<DatePickerDialog
|
||||||
<DatePickerDialog
|
inputValue={inputValue}
|
||||||
inputValue={inputValue}
|
setter={setInputValue}
|
||||||
setter={setInputValue}
|
/>
|
||||||
/>
|
{inputValue && (
|
||||||
{inputValue && (
|
<div className="text-sm">
|
||||||
<div className="text-sm">
|
Showing pull list for{" "}
|
||||||
Showing pull list for{" "}
|
<span className="inline-flex mb-2 items-center bg-slate-50 text-slate-800 text-xs font-medium px-2.5 py-1 rounded-md dark:text-slate-900 dark:bg-slate-400">
|
||||||
<span className="inline-flex mb-2 items-center bg-slate-50 text-slate-800 text-xs font-medium px-2.5 py-1 rounded-md dark:text-slate-900 dark:bg-slate-400">
|
{inputValue}
|
||||||
{inputValue}
|
</span>
|
||||||
</span>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
</div>
|
||||||
</div>
|
</form>
|
||||||
</form>
|
)}
|
||||||
)}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isSuccess && !isLoading && (
|
{isSuccess && !isLoading && (
|
||||||
<div ref={sliderRef} className="keen-slider flex flex-row">
|
<div ref={sliderRef} className="keen-slider">
|
||||||
{map(pullList?.data.result, (issue, idx) => {
|
{map(pullList?.data.result, (issue, idx) => {
|
||||||
return (
|
return (
|
||||||
<div key={idx} className="keen-slider__slide">
|
<div key={idx} className="keen-slider__slide">
|
||||||
<Card
|
<Card
|
||||||
orientation={"vertical-2"}
|
orientation={"vertical-2"}
|
||||||
imageUrl={issue.coverImageUrl}
|
imageUrl={issue.coverImageUrl}
|
||||||
hasDetails
|
hasDetails
|
||||||
title={ellipsize(issue.issueName, 25)}
|
title={ellipsize(issue.issueName, 25)}
|
||||||
>
|
>
|
||||||
<div className="px-1">
|
<div className="px-1">
|
||||||
<span className="inline-flex mb-2 items-center bg-slate-50 text-slate-800 text-xs font-medium px-2.5 py-1 rounded-md dark:text-slate-900 dark:bg-slate-400">
|
<span className="inline-flex mb-2 items-center bg-slate-50 text-slate-800 text-xs font-medium px-2.5 py-1 rounded-md dark:text-slate-900 dark:bg-slate-400">
|
||||||
{issue.publicationDate}
|
{issue.publicationDate}
|
||||||
</span>
|
</span>
|
||||||
<div className="flex flex-row justify-end">
|
<div className="flex flex-row justify-end">
|
||||||
<button
|
<button
|
||||||
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"
|
className="flex space-x-1 mb-2 sm:mt-0 sm:flex-row sm:items-center rounded-lg border border-green-400 dark:border-green-200 bg-green-200 px-2 py-1 text-gray-500 hover:bg-transparent hover:text-green-600 focus:outline-none focus:ring active:text-indigo-500"
|
||||||
onClick={() => addToLibrary("locg", issue)}
|
onClick={() => addToLibrary("locg", issue)}
|
||||||
>
|
>
|
||||||
<i className="icon-[solar--add-square-bold-duotone] w-5 h-5 mr-2"></i>{" "}
|
<i className="icon-[solar--add-square-bold-duotone] w-5 h-5 mr-2"></i>{" "}
|
||||||
Want
|
Want
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
</div>
|
</Card>
|
||||||
);
|
</div>
|
||||||
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -21,12 +21,13 @@ export const RecentlyImported = (
|
|||||||
console.log(comics);
|
console.log(comics);
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Header
|
<div className="mx-auto" style={{ maxWidth: '1400px' }}>
|
||||||
headerContent="Recently Imported"
|
<Header
|
||||||
subHeaderContent="Recent Library activity such as imports, tagging, etc."
|
headerContent="Recently Imported"
|
||||||
iconClassNames="fa-solid fa-binoculars mr-2"
|
subHeaderContent="Recent Library activity such as imports, tagging, etc."
|
||||||
/>
|
iconClassNames="fa-solid fa-binoculars mr-2"
|
||||||
<div className="grid grid-cols-5 gap-6 mt-3">
|
/>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-5 xl:grid-cols-5 gap-6 mt-3">
|
||||||
{comics?.comics.map(
|
{comics?.comics.map(
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
@@ -125,6 +126,7 @@ export const RecentlyImported = (
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ interface ICardProps {
|
|||||||
borderColorClass?: string;
|
borderColorClass?: string;
|
||||||
backgroundColor?: string;
|
backgroundColor?: string;
|
||||||
onClick?: (event: React.MouseEvent<HTMLElement>) => void;
|
onClick?: (event: React.MouseEvent<HTMLElement>) => void;
|
||||||
cardContainerStyle?: PropTypes.object;
|
cardContainerStyle?: any;
|
||||||
imageStyle?: PropTypes.object;
|
imageStyle?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderCard = (props: ICardProps): ReactElement => {
|
const renderCard = (props: ICardProps): ReactElement => {
|
||||||
@@ -156,7 +156,7 @@ const renderCard = (props: ICardProps): ReactElement => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`rounded-lg overflow-hidden shadow-md bg-white dark:bg-slate-800 ${
|
className={`rounded-2xl overflow-hidden shadow-md bg-white dark:bg-slate-800 ${
|
||||||
props.cardContainerStyle?.height ? "" : "aspect-[2/3]"
|
props.cardContainerStyle?.height ? "" : "aspect-[2/3]"
|
||||||
}`}
|
}`}
|
||||||
style={containerStyle}
|
style={containerStyle}
|
||||||
|
|||||||
Reference in New Issue
Block a user