From 9740a91fe32a2d34a95741617a4a56e75886de23 Mon Sep 17 00:00:00 2001 From: ibolton336 Date: Fri, 29 Sep 2023 17:26:16 -0400 Subject: [PATCH 1/2] :bug: Fix img fetching logic Signed-off-by: ibolton336 --- .../src/app/components/KeycloakProvider.tsx | 2 +- client/src/app/components/TargetCard.tsx | 45 ++++++++++++------- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/client/src/app/components/KeycloakProvider.tsx b/client/src/app/components/KeycloakProvider.tsx index a523eaea64..daf2ed50f5 100644 --- a/client/src/app/components/KeycloakProvider.tsx +++ b/client/src/app/components/KeycloakProvider.tsx @@ -6,7 +6,7 @@ import { deleteCookie, getCookie, setCookie } from "@app/queries/cookies"; import { AppPlaceholder } from "./AppPlaceholder"; import { Flex, FlexItem, Spinner } from "@patternfly/react-core"; import { ReactKeycloakProvider } from "@react-keycloak/web"; -import React, { Suspense, useEffect } from "react"; +import React, { Suspense } from "react"; interface IKeycloakProviderProps { children: React.ReactNode; diff --git a/client/src/app/components/TargetCard.tsx b/client/src/app/components/TargetCard.tsx index 5044d0af22..8a78a8a972 100644 --- a/client/src/app/components/TargetCard.tsx +++ b/client/src/app/components/TargetCard.tsx @@ -24,7 +24,7 @@ import { SelectVariant, SelectOptionObject, } from "@patternfly/react-core/deprecated"; -import { CubesIcon, GripVerticalIcon } from "@patternfly/react-icons"; +import { GripVerticalIcon } from "@patternfly/react-icons"; import spacing from "@patternfly/react-styles/css/utilities/Spacing/spacing"; import { useTranslation } from "react-i18next"; @@ -32,6 +32,7 @@ import { KebabDropdown } from "./KebabDropdown"; import DefaultRulesetIcon from "@app/images/Icon-Red_Hat-Virtual_server_stack-A-Black-RGB.svg"; import { Target, TargetLabel } from "@app/api/models"; import "./TargetCard.css"; +import axios from "axios"; export interface TargetCardProps { item: Target; @@ -68,6 +69,8 @@ export const TargetCard: React.FC = ({ const { t } = useTranslation(); const [isCardSelected, setCardSelected] = React.useState(cardSelected); + const [imageDataUrl, setImageDataUrl] = React.useState(null); + const prevSelectedLabel = formLabels?.find((formLabel) => { const labelNames = target?.labels?.map((label) => label.name); @@ -105,23 +108,23 @@ export const TargetCard: React.FC = ({ } }; - const getImage = (): React.ComponentType => { - let result: React.ComponentType = CubesIcon; - const imagePath = target?.image?.id - ? `/hub/files/${target?.image.id}` - : DefaultRulesetIcon; - if (target.image) { - result = () => ( - Card logo - ); + const fetchImageAndSetDataUrl = async (imagePath: string) => { + try { + const imageDataResponse = await axios.get(imagePath, { + headers: { + Accept: "application/octet-stream", + }, + }); + const encodedSvg = encodeURIComponent(imageDataResponse.data); + setImageDataUrl(`data:image/svg+xml,${encodedSvg}`); + } catch (error) { + console.error("There was an issue fetching the image:", error); } - - return result; }; + const imagePath = target?.image?.id + ? `/hub/files/${target?.image.id}` + : DefaultRulesetIcon; + fetchImageAndSetDataUrl(imagePath); return ( = ({ variant={EmptyStateVariant.sm} className="select-card__component__empty-state" > - + ( + Card logo + )} + /> {target.name} From 2e4ddcfa74ab212034b141b020d08df2bdfedc32 Mon Sep 17 00:00:00 2001 From: ibolton336 Date: Mon, 2 Oct 2023 12:24:06 -0400 Subject: [PATCH 2/2] Move logic to hook and update based on PR suggestions Signed-off-by: ibolton336 --- .../target-card/hooks/useFetchImageDataUrl.ts | 50 +++++++++++++++++++ .../target-card.css} | 0 .../target-card.tsx} | 34 ++++--------- .../analysis-wizard/set-targets.tsx | 2 +- .../migration-targets/components/dnd/item.tsx | 2 +- 5 files changed, 61 insertions(+), 27 deletions(-) create mode 100644 client/src/app/components/target-card/hooks/useFetchImageDataUrl.ts rename client/src/app/components/{TargetCard.css => target-card/target-card.css} (100%) rename client/src/app/components/{TargetCard.tsx => target-card/target-card.tsx} (86%) diff --git a/client/src/app/components/target-card/hooks/useFetchImageDataUrl.ts b/client/src/app/components/target-card/hooks/useFetchImageDataUrl.ts new file mode 100644 index 0000000000..f381064b38 --- /dev/null +++ b/client/src/app/components/target-card/hooks/useFetchImageDataUrl.ts @@ -0,0 +1,50 @@ +import { useState, useEffect } from "react"; +import axios from "axios"; +import DefaultImage from "@app/images/Icon-Red_Hat-Virtual_server_stack-A-Black-RGB.svg"; +import { Target } from "@app/api/models"; +import { FILES } from "@app/api/rest"; + +const useFetchImageDataUrl = (target: Target) => { + const [imageDataUrl, setImageDataUrl] = useState(null); + + useEffect(() => { + const imagePath = target?.image?.id + ? `${FILES}/${target?.image.id}` + : DefaultImage; + + (async () => { + try { + const response = await axios.get(imagePath, { + headers: { + Accept: "application/octet-stream", + }, + responseType: "arraybuffer", + }); + const contentType = response.headers["content-type"]; + + let imageData; + + if (contentType === "image/svg+xml") { + const text = new TextDecoder().decode(response.data); + imageData = `data:${contentType},${encodeURIComponent(text)}`; + } else { + const base64 = btoa( + new Uint8Array(response.data).reduce( + (data, byte) => data + String.fromCharCode(byte), + "" + ) + ); + imageData = `data:${contentType};base64,${base64}`; + } + + setImageDataUrl(imageData); + } catch (error) { + console.error("There was an issue fetching the image:", error); + } + })(); + }, [target]); + + return imageDataUrl; +}; + +export default useFetchImageDataUrl; diff --git a/client/src/app/components/TargetCard.css b/client/src/app/components/target-card/target-card.css similarity index 100% rename from client/src/app/components/TargetCard.css rename to client/src/app/components/target-card/target-card.css diff --git a/client/src/app/components/TargetCard.tsx b/client/src/app/components/target-card/target-card.tsx similarity index 86% rename from client/src/app/components/TargetCard.tsx rename to client/src/app/components/target-card/target-card.tsx index 8a78a8a972..6914623532 100644 --- a/client/src/app/components/TargetCard.tsx +++ b/client/src/app/components/target-card/target-card.tsx @@ -1,3 +1,4 @@ +import "./target-card.css"; import * as React from "react"; import { EmptyState, @@ -28,11 +29,10 @@ import { GripVerticalIcon } from "@patternfly/react-icons"; import spacing from "@patternfly/react-styles/css/utilities/Spacing/spacing"; import { useTranslation } from "react-i18next"; -import { KebabDropdown } from "./KebabDropdown"; -import DefaultRulesetIcon from "@app/images/Icon-Red_Hat-Virtual_server_stack-A-Black-RGB.svg"; +import { KebabDropdown } from "../KebabDropdown"; +import DefaultImage from "@app/images/Icon-Red_Hat-Virtual_server_stack-A-Black-RGB.svg"; import { Target, TargetLabel } from "@app/api/models"; -import "./TargetCard.css"; -import axios from "axios"; +import useFetchImageDataUrl from "./hooks/useFetchImageDataUrl"; export interface TargetCardProps { item: Target; @@ -68,8 +68,7 @@ export const TargetCard: React.FC = ({ }) => { const { t } = useTranslation(); const [isCardSelected, setCardSelected] = React.useState(cardSelected); - - const [imageDataUrl, setImageDataUrl] = React.useState(null); + const imageDataUrl = useFetchImageDataUrl(target); const prevSelectedLabel = formLabels?.find((formLabel) => { @@ -108,24 +107,6 @@ export const TargetCard: React.FC = ({ } }; - const fetchImageAndSetDataUrl = async (imagePath: string) => { - try { - const imageDataResponse = await axios.get(imagePath, { - headers: { - Accept: "application/octet-stream", - }, - }); - const encodedSvg = encodeURIComponent(imageDataResponse.data); - setImageDataUrl(`data:image/svg+xml,${encodedSvg}`); - } catch (error) { - console.error("There was an issue fetching the image:", error); - } - }; - const imagePath = target?.image?.id - ? `/hub/files/${target?.image.id}` - : DefaultRulesetIcon; - fetchImageAndSetDataUrl(imagePath); - return ( = ({ ( Card logo { + e.currentTarget.src = DefaultImage; + }} /> )} /> diff --git a/client/src/app/pages/applications/analysis-wizard/set-targets.tsx b/client/src/app/pages/applications/analysis-wizard/set-targets.tsx index a5620d3db2..c8cae4a800 100644 --- a/client/src/app/pages/applications/analysis-wizard/set-targets.tsx +++ b/client/src/app/pages/applications/analysis-wizard/set-targets.tsx @@ -10,7 +10,7 @@ import { import { useTranslation } from "react-i18next"; import { useFormContext } from "react-hook-form"; -import { TargetCard } from "@app/components/TargetCard"; +import { TargetCard } from "@app/components/target-card/target-card"; import { AnalysisWizardFormValues } from "./schema"; import { useSetting } from "@app/queries/settings"; import { useFetchTargets } from "@app/queries/targets"; diff --git a/client/src/app/pages/migration-targets/components/dnd/item.tsx b/client/src/app/pages/migration-targets/components/dnd/item.tsx index 30e24f6362..5a21ff52c7 100644 --- a/client/src/app/pages/migration-targets/components/dnd/item.tsx +++ b/client/src/app/pages/migration-targets/components/dnd/item.tsx @@ -1,5 +1,5 @@ import React, { forwardRef } from "react"; -import { TargetCard } from "@app/components/TargetCard"; +import { TargetCard } from "@app/components/target-card/target-card"; import { useFetchTargets } from "@app/queries/targets"; interface ItemProps {