[chore] moving things around in prep for graphql

This commit is contained in:
2025-11-27 00:28:53 -05:00
parent 90d6562f45
commit dc014a08ce
7 changed files with 307 additions and 236 deletions

View File

@@ -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}

View File

@@ -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

View File

@@ -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>
</> </>
); );

View File

@@ -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);

View File

@@ -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>
)} )}

View File

@@ -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>
); );

View File

@@ -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}