💅🏼 Prettifying search results
This commit is contained in:
@@ -41,6 +41,8 @@
|
|||||||
"final-form": "^4.20.2",
|
"final-form": "^4.20.2",
|
||||||
"final-form-arrays": "^3.0.2",
|
"final-form-arrays": "^3.0.2",
|
||||||
"focus-trap-react": "^10.2.3",
|
"focus-trap-react": "^10.2.3",
|
||||||
|
"graphql": "^16.0.0",
|
||||||
|
"graphql-request": "^7.2.0",
|
||||||
"history": "^5.3.0",
|
"history": "^5.3.0",
|
||||||
"html-to-text": "^8.1.0",
|
"html-to-text": "^8.1.0",
|
||||||
"i18next": "^23.11.1",
|
"i18next": "^23.11.1",
|
||||||
|
|||||||
@@ -266,55 +266,80 @@ export const Search = ({}: ISearchProps): ReactElement => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!isEmpty(comicVineSearchResults?.data?.results) ? (
|
{!isEmpty(comicVineSearchResults?.data?.results) ? (
|
||||||
<div className="mx-auto max-w-screen-xl px-4 py-4 sm:px-6 sm:py-8 lg:px-8">
|
<div className="mx-auto w-full sm:w-[90vw] md:w-[80vw] lg:w-[70vw] max-w-6xl px-4 py-6">
|
||||||
{comicVineSearchResults.data.results.map((result) => {
|
{comicVineSearchResults.data.results.map((result) => {
|
||||||
return result.resource_type === "issue" ? (
|
return result.resource_type === "issue" ? (
|
||||||
<div
|
<div
|
||||||
key={result.id}
|
key={result.id}
|
||||||
className="mb-5 dark:bg-slate-400 p-4 rounded-lg"
|
className="relative flex items-start gap-4 py-6 border-b border-slate-300 dark:border-slate-700"
|
||||||
>
|
>
|
||||||
<div className="flex flex-row">
|
{/* IMAGE */}
|
||||||
<div className="mr-5 min-w-[80px] max-w-[13%]">
|
<Card
|
||||||
<Card
|
orientation="cover-only"
|
||||||
key={result.id}
|
imageUrl={result?.image?.small_url}
|
||||||
orientation={"cover-only"}
|
hasDetails={false}
|
||||||
imageUrl={result.image.small_url}
|
cardContainerStyle={{
|
||||||
hasDetails={false}
|
width: "120px",
|
||||||
/>
|
maxWidth: "150px",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* RIGHT-SIDE CONTENT */}
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
{/* TITLE */}
|
||||||
|
<div className="text-base font-semibold text-gray-900 dark:text-white truncate">
|
||||||
|
{result.volume?.name || <span>No Name</span>}
|
||||||
</div>
|
</div>
|
||||||
<div className="w-3/4">
|
|
||||||
<div className="text-xl">
|
{/* SUBMETA */}
|
||||||
{!isEmpty(result.volume.name) ? (
|
<div className="flex flex-wrap gap-2 mt-2">
|
||||||
result.volume.name
|
{/* Cover Date Token */}
|
||||||
) : (
|
|
||||||
<span className="is-size-3">No Name</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{result.cover_date && (
|
{result.cover_date && (
|
||||||
<p>
|
<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="tag is-light">Cover date</span>
|
<span className="pr-1 pt-1">
|
||||||
{dayjs(result.cover_date).format("MMM D, YYYY")}
|
<i className="icon-[solar--calendar-bold-duotone] w-4 h-4"></i>
|
||||||
</p>
|
</span>
|
||||||
|
<span className="text-xs text-slate-500 dark:text-slate-900">
|
||||||
|
{dayjs(result.cover_date).format("MMM YYYY")}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<p className="tag is-warning">{result.id}</p>
|
{/* ID Token */}
|
||||||
|
<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-bold-duotone] w-4 h-4"></i>
|
||||||
|
</span>
|
||||||
|
<span className="text-xs text-slate-500 dark:text-slate-900">
|
||||||
|
{result.id}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<a href={result.api_detail_url}>
|
{/* LINK */}
|
||||||
{result.api_detail_url}
|
<a
|
||||||
</a>
|
href={result.api_detail_url}
|
||||||
<p className="text-sm">
|
className="text-xs text-blue-500 underline mt-1 inline-block break-all"
|
||||||
|
>
|
||||||
|
{result.api_detail_url}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
{/* DESCRIPTION */}
|
||||||
|
{result.description && (
|
||||||
|
<p className="text-sm text-slate-600 dark:text-slate-200 mt-2 line-clamp-3">
|
||||||
{ellipsize(
|
{ellipsize(
|
||||||
convert(result.description, {
|
convert(result.description ?? "", {
|
||||||
baseElements: {
|
baseElements: { selectors: ["p", "div"] },
|
||||||
selectors: ["p", "div"],
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
320,
|
300,
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-2">
|
)}
|
||||||
|
{/* CTA BUTTON */}
|
||||||
|
{result.volume.name ? (
|
||||||
|
<div className="mt-4 justify-self-end">
|
||||||
<PopoverButton
|
<PopoverButton
|
||||||
content={`This will add ${result.volume.name} to your wanted list.`}
|
content={`This will add ${result?.volume?.name} to your wanted list.`}
|
||||||
clickHandler={() =>
|
clickHandler={() =>
|
||||||
addToWantedList({
|
addToWantedList({
|
||||||
source: "comicvine",
|
source: "comicvine",
|
||||||
@@ -325,114 +350,106 @@ export const Search = ({}: ISearchProps): ReactElement => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
result.resource_type === "volume" && (
|
result.resource_type === "volume" && (
|
||||||
<div
|
<div
|
||||||
key={result.id}
|
key={result.id}
|
||||||
className="mb-5 dark:bg-slate-500 p-4 rounded-lg"
|
className="flex gap-4 py-4 border-b border-slate-300 dark:border-slate-700"
|
||||||
>
|
>
|
||||||
<div className="flex flex-row">
|
{/* LEFT COLUMN: COVER */}
|
||||||
<div className="mr-5 min-w-[80px] max-w-[13%]">
|
<Card
|
||||||
<Card
|
orientation="cover-only"
|
||||||
key={result.id}
|
imageUrl={result.image.small_url}
|
||||||
orientation={"cover-only"}
|
hasDetails={false}
|
||||||
imageUrl={result.image.small_url}
|
cardContainerStyle={{
|
||||||
hasDetails={false}
|
width: "120px",
|
||||||
/>
|
maxWidth: "150px",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* RIGHT COLUMN */}
|
||||||
|
<div className="flex-1 min-w-0 flex flex-col">
|
||||||
|
{/* TITLE */}
|
||||||
|
<div className="text-lg font-bold text-gray-900 dark:text-white">
|
||||||
|
{result.name || <span>No Name</span>}
|
||||||
|
{result.start_year && <> ({result.start_year})</>}
|
||||||
</div>
|
</div>
|
||||||
<div className="w-3/4">
|
|
||||||
<div className="text-xl">
|
|
||||||
{!isEmpty(result.name) ? (
|
|
||||||
result.name
|
|
||||||
) : (
|
|
||||||
<span className="text-xl">No Name</span>
|
|
||||||
)}
|
|
||||||
{result.start_year && <> ({result.start_year})</>}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-row gap-2">
|
{/* TOKENS */}
|
||||||
{/* issue count */}
|
<div className="flex flex-wrap gap-2 mt-2">
|
||||||
{result.count_of_issues && (
|
{/* ISSUE COUNT */}
|
||||||
<div className="my-2">
|
{result.count_of_issues && (
|
||||||
<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="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">
|
<span className="pr-1 pt-1">
|
||||||
<i className="icon-[solar--documents-minimalistic-bold-duotone] w-5 h-5"></i>
|
<i className="icon-[solar--documents-minimalistic-bold-duotone] w-4 h-4" />
|
||||||
</span>
|
</span>
|
||||||
|
<span>
|
||||||
|
{t("issueWithCount", {
|
||||||
|
count: result.count_of_issues,
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
|
||||||
<span className="text-md text-slate-500 dark:text-slate-900">
|
{/* FORMAT DETECTED */}
|
||||||
{t("issueWithCount", {
|
{result.description &&
|
||||||
count: result.count_of_issues,
|
!isEmpty(detectIssueTypes(result.description)) && (
|
||||||
})}
|
<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>
|
<span className="pr-1 pt-1">
|
||||||
|
<i className="icon-[solar--book-2-line-duotone] w-4 h-4" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
<span>
|
||||||
|
{
|
||||||
|
detectIssueTypes(result.description)
|
||||||
|
.displayName
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
{/* type: TPB, one-shot, graphic novel etc. */}
|
|
||||||
{!isNil(result.description) &&
|
|
||||||
!isUndefined(result.description) && (
|
|
||||||
<>
|
|
||||||
{!isEmpty(
|
|
||||||
detectIssueTypes(result.description),
|
|
||||||
) && (
|
|
||||||
<div className="my-2">
|
|
||||||
<span className="inline-flex items-center bg-slate-50 text-slate-800 text-xs font-medium px-2 rounded-md dark:text-slate-900 dark:bg-slate-400">
|
|
||||||
<span className="pr-1 pt-1">
|
|
||||||
<i className="icon-[solar--book-2-line-duotone] w-5 h-5"></i>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span className="text-md text-slate-500 dark:text-slate-900">
|
{/* ID */}
|
||||||
{
|
<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">
|
||||||
detectIssueTypes(result.description)
|
<span className="pr-1 pt-1">
|
||||||
.displayName
|
<i className="icon-[solar--hashtag-bold-duotone] w-4 h-4" />
|
||||||
}
|
</span>
|
||||||
</span>
|
<span>{result.id}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<span className="tag is-warning">{result.id}</span>
|
{/* LINK */}
|
||||||
<p>
|
<a
|
||||||
<a href={result.api_detail_url}>
|
href={result.api_detail_url}
|
||||||
{result.api_detail_url}
|
className="text-sm text-blue-500 underline mt-2 break-all"
|
||||||
</a>
|
>
|
||||||
</p>
|
{result.api_detail_url}
|
||||||
|
</a>
|
||||||
|
|
||||||
{/* description */}
|
{/* DESCRIPTION */}
|
||||||
<p className="text-sm">
|
{result.description && (
|
||||||
|
<p className="text-sm mt-2 text-slate-700 dark:text-slate-200 break-words line-clamp-3">
|
||||||
{ellipsize(
|
{ellipsize(
|
||||||
convert(result.description, {
|
convert(result.description, {
|
||||||
baseElements: {
|
baseElements: { selectors: ["p", "div"] },
|
||||||
selectors: ["p", "div"],
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
320,
|
320,
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-2">
|
)}
|
||||||
<PopoverButton
|
|
||||||
content={`Adding this volume will add ${t(
|
<PopoverButton
|
||||||
"issueWithCount",
|
content={`This will add ${result.volume.name} to your wanted list.`}
|
||||||
{
|
clickHandler={() =>
|
||||||
count: result.count_of_issues,
|
addToWantedList({
|
||||||
},
|
source: "comicvine",
|
||||||
)} to your wanted list.`}
|
comicObject: result,
|
||||||
clickHandler={() =>
|
markEntireVolumeWanted: false,
|
||||||
addToWantedList({
|
resourceType: "issue",
|
||||||
source: "comicvine",
|
})
|
||||||
comicObject: result,
|
}
|
||||||
markEntireVolumeWanted: true,
|
/>
|
||||||
resourceType: "volume",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,37 +1,87 @@
|
|||||||
import React, { ReactElement, useCallback, useEffect, useMemo } from "react";
|
import React from "react";
|
||||||
import SearchBar from "../Library/SearchBar";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { gql, GraphQLClient } from "graphql-request";
|
||||||
import T2Table from "../shared/T2Table";
|
import T2Table from "../shared/T2Table";
|
||||||
import MetadataPanel from "../shared/MetadataPanel";
|
import MetadataPanel from "../shared/MetadataPanel";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
|
||||||
import axios from "axios";
|
|
||||||
import { SEARCH_SERVICE_BASE_URI } from "../../constants/endpoints";
|
|
||||||
|
|
||||||
export const WantedComics = (props): ReactElement => {
|
/**
|
||||||
const {
|
* GraphQL client for interfacing with Moleculer Apollo server.
|
||||||
data: wantedComics,
|
*/
|
||||||
isSuccess,
|
const client = new GraphQLClient("http://localhost:3000/graphql");
|
||||||
isFetched,
|
|
||||||
isError,
|
|
||||||
isLoading,
|
|
||||||
} = useQuery({
|
|
||||||
queryFn: async () =>
|
|
||||||
await axios({
|
|
||||||
url: `${SEARCH_SERVICE_BASE_URI}/searchIssue`,
|
|
||||||
method: "POST",
|
|
||||||
data: {
|
|
||||||
query: {},
|
|
||||||
|
|
||||||
pagination: {
|
/**
|
||||||
size: 25,
|
* GraphQL query to fetch wanted comics.
|
||||||
from: 0,
|
*/
|
||||||
},
|
const WANTED_COMICS_QUERY = gql`
|
||||||
type: "wanted",
|
query {
|
||||||
trigger: "wantedComicsPage",
|
wantedComics(limit: 25, offset: 0) {
|
||||||
},
|
total
|
||||||
}),
|
comics
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shape of an individual comic returned by the backend.
|
||||||
|
*/
|
||||||
|
type Comic = {
|
||||||
|
_id: string;
|
||||||
|
sourcedMetadata?: {
|
||||||
|
comicvine?: {
|
||||||
|
name?: string;
|
||||||
|
start_year?: string;
|
||||||
|
publisher?: {
|
||||||
|
name?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
acquisition?: {
|
||||||
|
directconnect?: {
|
||||||
|
downloads?: Array<{
|
||||||
|
name: string;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shape of the GraphQL response returned for wanted comics.
|
||||||
|
*/
|
||||||
|
type WantedComicsResponse = {
|
||||||
|
wantedComics: {
|
||||||
|
total: number;
|
||||||
|
comics: Comic[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* React component rendering the "Wanted Comics" table using T2Table.
|
||||||
|
* Fetches data from GraphQL backend via graphql-request + TanStack Query.
|
||||||
|
*
|
||||||
|
* @component
|
||||||
|
* @returns {JSX.Element} React component
|
||||||
|
*/
|
||||||
|
const WantedComics = (): JSX.Element => {
|
||||||
|
const { data, isLoading, isError, isSuccess, error } = useQuery<
|
||||||
|
WantedComicsResponse["wantedComics"]
|
||||||
|
>({
|
||||||
queryKey: ["wantedComics"],
|
queryKey: ["wantedComics"],
|
||||||
enabled: true,
|
queryFn: async () => {
|
||||||
|
const res = await client.request<WantedComicsResponse>(
|
||||||
|
WANTED_COMICS_QUERY,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!res?.wantedComics?.comics) {
|
||||||
|
throw new Error("No comics returned");
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.wantedComics;
|
||||||
|
},
|
||||||
|
retry: false,
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
refetchOnReconnect: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const columnData = [
|
const columnData = [
|
||||||
{
|
{
|
||||||
header: "Comic Information",
|
header: "Comic Information",
|
||||||
@@ -40,11 +90,11 @@ export const WantedComics = (props): ReactElement => {
|
|||||||
header: "Details",
|
header: "Details",
|
||||||
id: "comicDetails",
|
id: "comicDetails",
|
||||||
minWidth: 350,
|
minWidth: 350,
|
||||||
accessorFn: (data) => data,
|
accessorFn: (data: Comic) => data,
|
||||||
cell: (value) => {
|
cell: (value: any) => {
|
||||||
console.log("ASDASd", value);
|
const row = value.getValue();
|
||||||
const row = value.getValue()._source;
|
console.log("Comic row data:", row);
|
||||||
return row && <MetadataPanel data={row} />;
|
return row ? <MetadataPanel data={row} /> : null;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -55,148 +105,73 @@ export const WantedComics = (props): ReactElement => {
|
|||||||
{
|
{
|
||||||
header: "Files",
|
header: "Files",
|
||||||
align: "right",
|
align: "right",
|
||||||
accessorKey: "_source.acquisition",
|
accessorFn: (row: Comic) =>
|
||||||
cell: (props) => {
|
row?.acquisition?.directconnect?.downloads || [],
|
||||||
const {
|
cell: (props: any) => {
|
||||||
directconnect: { downloads },
|
const downloads = props.getValue();
|
||||||
} = props.getValue();
|
return downloads?.length > 0 ? (
|
||||||
return (
|
<span className="tag is-warning">{downloads.length}</span>
|
||||||
<div
|
) : null;
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
// flexDirection: "column",
|
|
||||||
justifyContent: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{downloads.length > 0 ? (
|
|
||||||
<span className="tag is-warning">{downloads.length}</span>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: "Download Details",
|
header: "Download Details",
|
||||||
id: "downloadDetails",
|
id: "downloadDetails",
|
||||||
accessorKey: "_source.acquisition",
|
accessorFn: (row: Comic) =>
|
||||||
cell: (data) => (
|
row?.acquisition?.directconnect?.downloads || [],
|
||||||
|
cell: (data: any) => (
|
||||||
<ol>
|
<ol>
|
||||||
{data.getValue().directconnect.downloads.map((download, idx) => {
|
{data.getValue()?.map((download: any, idx: number) => (
|
||||||
return (
|
<li className="is-size-7" key={idx}>
|
||||||
<li className="is-size-7" key={idx}>
|
{download.name}
|
||||||
{download.name}
|
</li>
|
||||||
</li>
|
))}
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ol>
|
</ol>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
header: "Type",
|
|
||||||
id: "dcc",
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
|
||||||
* Pagination control that fetches the next x (pageSize) items
|
|
||||||
* based on the y (pageIndex) offset from the Elasticsearch index
|
|
||||||
* @param {number} pageIndex
|
|
||||||
* @param {number} pageSize
|
|
||||||
* @returns void
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
// const nextPage = useCallback((pageIndex: number, pageSize: number) => {
|
|
||||||
// dispatch(
|
|
||||||
// searchIssue(
|
|
||||||
// {
|
|
||||||
// query: {},
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// pagination: {
|
|
||||||
// size: pageSize,
|
|
||||||
// from: pageSize * pageIndex + 1,
|
|
||||||
// },
|
|
||||||
// type: "wanted",
|
|
||||||
// trigger: "wantedComicsPage",
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// }, []);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pagination control that fetches the previous x (pageSize) items
|
|
||||||
* based on the y (pageIndex) offset from the Elasticsearch index
|
|
||||||
* @param {number} pageIndex
|
|
||||||
* @param {number} pageSize
|
|
||||||
* @returns void
|
|
||||||
**/
|
|
||||||
// const previousPage = useCallback((pageIndex: number, pageSize: number) => {
|
|
||||||
// let from = 0;
|
|
||||||
// if (pageIndex === 2) {
|
|
||||||
// from = (pageIndex - 1) * pageSize + 2 - 17;
|
|
||||||
// } else {
|
|
||||||
// from = (pageIndex - 1) * pageSize + 2 - 16;
|
|
||||||
// }
|
|
||||||
// dispatch(
|
|
||||||
// searchIssue(
|
|
||||||
// {
|
|
||||||
// query: {},
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// pagination: {
|
|
||||||
// size: pageSize,
|
|
||||||
// from,
|
|
||||||
// },
|
|
||||||
// type: "wanted",
|
|
||||||
// trigger: "wantedComicsPage",
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// }, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="">
|
<section>
|
||||||
<section className="">
|
<header className="bg-slate-200 dark:bg-slate-500">
|
||||||
<header className="bg-slate-200 dark:bg-slate-500">
|
<div className="mx-auto max-w-screen-xl px-2 py-2 sm:px-6 sm:py-8 lg:px-8 lg:py-4">
|
||||||
<div className="mx-auto max-w-screen-xl px-2 py-2 sm:px-6 sm:py-8 lg:px-8 lg:py-4">
|
<div className="sm:flex sm:items-center sm:justify-between">
|
||||||
<div className="sm:flex sm:items-center sm:justify-between">
|
<div className="text-center sm:text-left">
|
||||||
<div className="text-center sm:text-left">
|
<h1 className="text-2xl font-bold text-gray-900 dark:text-white sm:text-3xl">
|
||||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white sm:text-3xl">
|
Wanted Comics
|
||||||
Wanted Comics
|
</h1>
|
||||||
</h1>
|
<p className="mt-1.5 text-sm text-gray-500 dark:text-white">
|
||||||
|
Browse through comics you marked as "wanted."
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
<p className="mt-1.5 text-sm text-gray-500 dark:text-white">
|
{isLoading && (
|
||||||
Browse through comics you marked as "wanted."
|
<div className="animate-pulse p-4 space-y-4">
|
||||||
</p>
|
{Array.from({ length: 5 }).map((_, idx) => (
|
||||||
</div>
|
<div
|
||||||
</div>
|
key={idx}
|
||||||
</div>
|
className="h-24 bg-slate-300 dark:bg-slate-600 rounded-md"
|
||||||
</header>
|
/>
|
||||||
{isSuccess && wantedComics?.data.hits?.hits ? (
|
))}
|
||||||
<div>
|
</div>
|
||||||
<div className="library">
|
)}
|
||||||
<T2Table
|
{isError && <div>Error fetching wanted comics. {error?.message}</div>}
|
||||||
sourceData={wantedComics?.data.hits.hits}
|
{isSuccess && data?.comics?.length > 0 ? (
|
||||||
totalPages={wantedComics?.data.hits.hits.length}
|
<T2Table
|
||||||
columns={columnData}
|
sourceData={data.comics}
|
||||||
paginationHandlers={{
|
totalPages={data.comics.length}
|
||||||
nextPage: () => {},
|
columns={columnData}
|
||||||
previousPage: () => {},
|
paginationHandlers={{}}
|
||||||
}}
|
/>
|
||||||
// rowClickHandler={navigateToComicDetail}
|
) : isSuccess ? (
|
||||||
/>
|
<div>No comics found.</div>
|
||||||
{/* pagination controls */}
|
) : null}
|
||||||
</div>
|
</section>
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
{isLoading ? <div>Loading...</div> : null}
|
|
||||||
{isError ? (
|
|
||||||
<div>An error occurred while retrieving the pull list.</div>
|
|
||||||
) : null}
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -140,14 +140,31 @@ const renderCard = (props: ICardProps): ReactElement => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
case "cover-only":
|
case "cover-only":
|
||||||
|
const containerStyle = {
|
||||||
|
width: props.cardContainerStyle?.width || "100%",
|
||||||
|
height: props.cardContainerStyle?.height || "auto",
|
||||||
|
maxWidth: props.cardContainerStyle?.maxWidth || "none",
|
||||||
|
...props.cardContainerStyle,
|
||||||
|
};
|
||||||
|
|
||||||
|
const imageStyle = {
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
objectFit: "cover",
|
||||||
|
...props.imageStyle,
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div
|
||||||
{/* thumbnail */}
|
className={`rounded-lg overflow-hidden shadow-md bg-white dark:bg-slate-800 ${
|
||||||
<div className="rounded-lg shadow-lg overflow-hidden w-fit h-fit">
|
props.cardContainerStyle?.height ? "" : "aspect-[2/3]"
|
||||||
<img src={props.imageUrl} />
|
}`}
|
||||||
</div>
|
style={containerStyle}
|
||||||
</>
|
>
|
||||||
|
<img src={props.imageUrl} alt="Comic cover" style={imageStyle} />
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
case "card-with-info-panel":
|
case "card-with-info-panel":
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -1,32 +1,54 @@
|
|||||||
import React, { ReactElement } from "react";
|
import React, { ReactElement } from "react";
|
||||||
import PropTypes from "prop-types";
|
|
||||||
import ellipsize from "ellipsize";
|
import ellipsize from "ellipsize";
|
||||||
import prettyBytes from "pretty-bytes";
|
import prettyBytes from "pretty-bytes";
|
||||||
import { Card } from "../shared/Carda";
|
import { Card } from "../shared/Carda";
|
||||||
import { convert } from "html-to-text";
|
import { convert } from "html-to-text";
|
||||||
import { determineCoverFile } from "../../shared/utils/metadata.utils";
|
import { determineCoverFile } from "../../shared/utils/metadata.utils";
|
||||||
import { find, isUndefined } from "lodash";
|
import { find, isUndefined } from "lodash";
|
||||||
|
import { o } from "react-router/dist/development/fog-of-war-BLArG-qZ";
|
||||||
|
|
||||||
interface IMetadatPanelProps {
|
/**
|
||||||
value: any;
|
* Props for the MetadataPanel component.
|
||||||
children: any;
|
*/
|
||||||
imageStyle: any;
|
interface MetadataPanelProps {
|
||||||
titleStyle: any;
|
/**
|
||||||
tagsStyle: any;
|
* Comic metadata object passed into the panel.
|
||||||
containerStyle: any;
|
*/
|
||||||
|
data: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional custom styling for the cover image.
|
||||||
|
*/
|
||||||
|
imageStyle?: React.CSSProperties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional custom styling for the title section.
|
||||||
|
*/
|
||||||
|
titleStyle?: React.CSSProperties;
|
||||||
}
|
}
|
||||||
export const MetadataPanel = (props: IMetadatPanelProps): ReactElement => {
|
|
||||||
|
/**
|
||||||
|
* MetadataPanel component
|
||||||
|
*
|
||||||
|
* Displays structured comic metadata based on the best available source
|
||||||
|
* (raw file data, ComicVine, or League of Comic Geeks).
|
||||||
|
*
|
||||||
|
* @component
|
||||||
|
* @param {MetadataPanelProps} props
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
export const MetadataPanel = (props: MetadataPanelProps): ReactElement => {
|
||||||
const {
|
const {
|
||||||
rawFileDetails,
|
rawFileDetails,
|
||||||
inferredMetadata,
|
inferredMetadata,
|
||||||
sourcedMetadata: { comicvine, locg },
|
sourcedMetadata: { comicvine, locg },
|
||||||
} = props.data;
|
} = props.data;
|
||||||
|
|
||||||
const { issueName, url, objectReference } = determineCoverFile({
|
const { issueName, url, objectReference } = determineCoverFile({
|
||||||
comicvine,
|
comicvine,
|
||||||
locg,
|
locg,
|
||||||
rawFileDetails,
|
rawFileDetails,
|
||||||
});
|
});
|
||||||
|
|
||||||
const metadataContentPanel = [
|
const metadataContentPanel = [
|
||||||
{
|
{
|
||||||
name: "rawFileDetails",
|
name: "rawFileDetails",
|
||||||
@@ -43,48 +65,29 @@ export const MetadataPanel = (props: IMetadatPanelProps): ReactElement => {
|
|||||||
</span>
|
</span>
|
||||||
</dd>
|
</dd>
|
||||||
|
|
||||||
{/* Issue number */}
|
|
||||||
{inferredMetadata.issue.number && (
|
{inferredMetadata.issue.number && (
|
||||||
<dd className="my-2">
|
<dd className="my-2">
|
||||||
<span className="inline-flex items-center bg-slate-50 text-slate-800 text-xs font-medium px-2 rounded-md dark:text-slate-900 dark:bg-slate-400">
|
<span className="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--hashtag-outline] w-3.5 h-3.5 pr-1 pt-1"></i>
|
||||||
<i className="icon-[solar--hashtag-outline] w-3.5 h-3.5"></i>
|
<span>{inferredMetadata.issue.number}</span>
|
||||||
</span>
|
|
||||||
<span className="text-md text-slate-900 dark:text-slate-900">
|
|
||||||
{inferredMetadata.issue.number}
|
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
</dd>
|
</dd>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<dd className="flex flex-row gap-2 w-max">
|
<dd className="flex flex-row gap-2 w-max">
|
||||||
{/* 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="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--zip-file-bold-duotone] w-5 h-5 pr-1 pt-1" />
|
||||||
<i className="icon-[solar--zip-file-bold-duotone] w-5 h-5"></i>
|
{rawFileDetails.mimeType}
|
||||||
</span>
|
|
||||||
|
|
||||||
<span className="text-md text-slate-500 dark:text-slate-900">
|
|
||||||
{rawFileDetails.mimeType}
|
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{/* 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="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 pr-1 pt-1" />
|
||||||
<i className="icon-[solar--mirror-right-bold-duotone] w-5 h-5"></i>
|
{prettyBytes(rawFileDetails.fileSize)}
|
||||||
</span>
|
|
||||||
|
|
||||||
<span className="text-md text-slate-500 dark:text-slate-900">
|
|
||||||
{prettyBytes(rawFileDetails.fileSize)}
|
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{/* Uncompressed version available? */}
|
|
||||||
{rawFileDetails.archive?.uncompressed && (
|
{rawFileDetails.archive?.uncompressed && (
|
||||||
<span className="inline-flex items-center bg-slate-50 text-slate-800 text-xs px-2 rounded-md dark:text-slate-900 dark:bg-slate-400">
|
<span className="inline-flex items-center bg-slate-50 text-slate-800 text-xs px-2 rounded-md dark:text-slate-900 dark:bg-slate-400">
|
||||||
<span className="pr-1 pt-1">
|
<i className="icon-[solar--bookmark-bold-duotone] w-3.5 h-3.5 pr-1 pt-1" />
|
||||||
<i className="icon-[solar--bookmark-bold-duotone] w-3.5 h-3.5"></i>
|
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</dd>
|
</dd>
|
||||||
@@ -94,49 +97,51 @@ export const MetadataPanel = (props: IMetadatPanelProps): ReactElement => {
|
|||||||
|
|
||||||
{
|
{
|
||||||
name: "comicvine",
|
name: "comicvine",
|
||||||
content: () =>
|
content: () => {
|
||||||
!isUndefined(comicvine) &&
|
console.log("comicvine:", comicvine);
|
||||||
!isUndefined(comicvine.volumeInformation) && (
|
console.log("volumeInformation:", comicvine?.volumeInformation);
|
||||||
<dl>
|
return (
|
||||||
<dt>
|
!isUndefined(comicvine?.volumeInformation) && (
|
||||||
<h6
|
<dl>
|
||||||
className="name has-text-weight-medium mb-1"
|
<dt>
|
||||||
style={props.titleStyle}
|
<h6
|
||||||
>
|
className="name has-text-weight-medium mb-1"
|
||||||
{ellipsize(issueName, 18)}
|
style={props.titleStyle}
|
||||||
</h6>
|
>
|
||||||
</dt>
|
{ellipsize(issueName, 18)}
|
||||||
<dd>
|
</h6>
|
||||||
<span className="is-size-7">
|
</dt>
|
||||||
Is a part of{" "}
|
<dd>
|
||||||
<span className="has-text-weight-semibold">
|
<span className="is-size-7">
|
||||||
{comicvine.volumeInformation.name}
|
Is a part of{" "}
|
||||||
|
<span className="has-text-weight-semibold">
|
||||||
|
{comicvine.volumeInformation.name}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</dd>
|
||||||
</dd>
|
<dd className="is-size-7">
|
||||||
<dd className="is-size-7">
|
<span>
|
||||||
<span>
|
{ellipsize(
|
||||||
{ellipsize(
|
convert(comicvine.description || "", {
|
||||||
convert(comicvine.description, {
|
baseElements: { selectors: ["p"] },
|
||||||
baseElements: {
|
}),
|
||||||
selectors: ["p"],
|
120,
|
||||||
},
|
)}
|
||||||
}),
|
</span>
|
||||||
120,
|
</dd>
|
||||||
)}
|
<dd className="is-size-7 mt-2">
|
||||||
</span>
|
<span className="my-3 mx-2">
|
||||||
</dd>
|
{comicvine.volumeInformation.start_year}
|
||||||
<dd className="is-size-7 mt-2">
|
</span>
|
||||||
<span className="my-3 mx-2">
|
{comicvine.volumeInformation.count_of_issues}
|
||||||
{comicvine.volumeInformation.start_year}
|
ComicVine ID: {comicvine.id}
|
||||||
</span>
|
</dd>
|
||||||
{comicvine.volumeInformation.count_of_issues}
|
</dl>
|
||||||
ComicVine ID
|
)
|
||||||
{comicvine.id}
|
);
|
||||||
</dd>
|
},
|
||||||
</dl>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: "locg",
|
name: "locg",
|
||||||
content: () => (
|
content: () => (
|
||||||
@@ -147,23 +152,22 @@ export const MetadataPanel = (props: IMetadatPanelProps): ReactElement => {
|
|||||||
</h6>
|
</h6>
|
||||||
</dt>
|
</dt>
|
||||||
<dd className="is-size-7">
|
<dd className="is-size-7">
|
||||||
<span>{ellipsize(locg.description, 120)}</span>
|
<span>{ellipsize(locg?.description || "", 120)}</span>
|
||||||
</dd>
|
</dd>
|
||||||
|
|
||||||
<dd className="is-size-7 mt-2">
|
<dd className="is-size-7 mt-2">
|
||||||
<div className="field is-grouped is-grouped-multiline">
|
<div className="field is-grouped is-grouped-multiline">
|
||||||
<div className="control">
|
<div className="control">
|
||||||
<span className="tags">
|
<span className="tags">
|
||||||
<span className="tag is-success is-light has-text-weight-semibold">
|
<span className="tag is-success is-light has-text-weight-semibold">
|
||||||
{locg.price}
|
{locg?.price}
|
||||||
</span>
|
</span>
|
||||||
<span className="tag is-success is-light">{locg.pulls}</span>
|
<span className="tag is-success is-light">{locg?.pulls}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="control">
|
<div className="control">
|
||||||
<div className="tags has-addons">
|
<div className="tags has-addons">
|
||||||
<span className="tag is-primary is-light">rating</span>
|
<span className="tag is-primary is-light">rating</span>
|
||||||
<span className="tag is-info is-light">{locg.rating}</span>
|
<span className="tag is-info is-light">{locg?.rating}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -173,20 +177,18 @@ export const MetadataPanel = (props: IMetadatPanelProps): ReactElement => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// Find the panel to display
|
|
||||||
const metadataPanel = find(metadataContentPanel, {
|
const metadataPanel = find(metadataContentPanel, {
|
||||||
name: objectReference,
|
name: objectReference,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-5 my-3">
|
<div className="flex gap-5 my-3">
|
||||||
<Card
|
<Card
|
||||||
imageUrl={url}
|
imageUrl={url}
|
||||||
orientation={"cover-only"}
|
orientation="cover-only"
|
||||||
hasDetails={false}
|
hasDetails={false}
|
||||||
imageStyle={props.imageStyle}
|
imageStyle={props.imageStyle}
|
||||||
/>
|
/>
|
||||||
<div>{metadataPanel.content()}</div>
|
<div>{metadataPanel?.content()}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const PopoverButton = ({ content, clickHandler }) => {
|
|||||||
onFocus={() => setIsVisible(true)}
|
onFocus={() => setIsVisible(true)}
|
||||||
onBlur={() => setIsVisible(false)}
|
onBlur={() => setIsVisible(false)}
|
||||||
aria-describedby="popover"
|
aria-describedby="popover"
|
||||||
className="flex space-x-1 sm:mt-0 sm:flex-row sm:items-center rounded-lg border border-green-400 dark:border-green-200 bg-green-200 px-2 py-2 text-gray-500 hover:bg-transparent hover:text-green-600 focus:outline-none focus:ring active:text-indigo-500"
|
className="flex text-sm space-x-1 sm:mt-0 sm:flex-row sm:items-center rounded-lg border border-green-400 dark:border-green-200 bg-green-200 px-1.5 py-1 text-gray-500 hover:bg-transparent hover:text-green-600 focus:outline-none focus:ring active:text-indigo-500"
|
||||||
onClick={clickHandler}
|
onClick={clickHandler}
|
||||||
>
|
>
|
||||||
<i className="icon-[solar--add-square-bold-duotone] w-6 h-6 mr-2"></i>{" "}
|
<i className="icon-[solar--add-square-bold-duotone] w-6 h-6 mr-2"></i>{" "}
|
||||||
@@ -32,7 +32,7 @@ const PopoverButton = ({ content, clickHandler }) => {
|
|||||||
<div
|
<div
|
||||||
ref={refs.setFloating} // Apply the floating setter directly to the ref prop
|
ref={refs.setFloating} // Apply the floating setter directly to the ref prop
|
||||||
style={floatingStyles}
|
style={floatingStyles}
|
||||||
className="text-sm bg-slate-400 p-2 rounded-md"
|
className="text-xs bg-slate-400 p-1.5 rounded-md"
|
||||||
role="tooltip"
|
role="tooltip"
|
||||||
>
|
>
|
||||||
{content}
|
{content}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export const T2Table = (tableOptions: T2TableProps): ReactElement => {
|
|||||||
columns,
|
columns,
|
||||||
manualPagination: true,
|
manualPagination: true,
|
||||||
getCoreRowModel: getCoreRowModel(),
|
getCoreRowModel: getCoreRowModel(),
|
||||||
pageCount: sourceData.length ?? -1,
|
pageCount: sourceData?.length ?? -1,
|
||||||
state: {
|
state: {
|
||||||
pagination,
|
pagination,
|
||||||
},
|
},
|
||||||
|
|||||||
17
yarn.lock
17
yarn.lock
@@ -1614,6 +1614,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-6.5.1.tgz#55cc8410abf1003b726324661ce5b0d1c10de258"
|
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-6.5.1.tgz#55cc8410abf1003b726324661ce5b0d1c10de258"
|
||||||
integrity sha512-CNy5vSwN3fsUStPRLX7fUYojyuzoEMSXPl7zSLJ8TgtRfjv24LOnOWKT2zYwaHZCJGkdyRnTmstR0P+Ah503Gw==
|
integrity sha512-CNy5vSwN3fsUStPRLX7fUYojyuzoEMSXPl7zSLJ8TgtRfjv24LOnOWKT2zYwaHZCJGkdyRnTmstR0P+Ah503Gw==
|
||||||
|
|
||||||
|
"@graphql-typed-document-node/core@^3.2.0":
|
||||||
|
version "3.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.2.0.tgz#5f3d96ec6b2354ad6d8a28bf216a1d97b5426861"
|
||||||
|
integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==
|
||||||
|
|
||||||
"@humanwhocodes/config-array@^0.11.13":
|
"@humanwhocodes/config-array@^0.11.13":
|
||||||
version "0.11.14"
|
version "0.11.14"
|
||||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b"
|
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b"
|
||||||
@@ -6521,6 +6526,18 @@ graphemer@^1.4.0:
|
|||||||
resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6"
|
resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6"
|
||||||
integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==
|
integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==
|
||||||
|
|
||||||
|
graphql-request@^7.2.0:
|
||||||
|
version "7.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-7.2.0.tgz#af4aa25f27a087dd4fc93a4ff54a0f59c4487269"
|
||||||
|
integrity sha512-0GR7eQHBFYz372u9lxS16cOtEekFlZYB2qOyq8wDvzRmdRSJ0mgUVX1tzNcIzk3G+4NY+mGtSz411wZdeDF/+A==
|
||||||
|
dependencies:
|
||||||
|
"@graphql-typed-document-node/core" "^3.2.0"
|
||||||
|
|
||||||
|
graphql@^16.0.0:
|
||||||
|
version "16.11.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.11.0.tgz#96d17f66370678027fdf59b2d4c20b4efaa8a633"
|
||||||
|
integrity sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==
|
||||||
|
|
||||||
gunzip-maybe@^1.4.2:
|
gunzip-maybe@^1.4.2:
|
||||||
version "1.4.2"
|
version "1.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/gunzip-maybe/-/gunzip-maybe-1.4.2.tgz#b913564ae3be0eda6f3de36464837a9cd94b98ac"
|
resolved "https://registry.yarnpkg.com/gunzip-maybe/-/gunzip-maybe-1.4.2.tgz#b913564ae3be0eda6f3de36464837a9cd94b98ac"
|
||||||
|
|||||||
Reference in New Issue
Block a user