Refactor asset management components to enhance structure and improve data handling; update styles for better UI consistency
This commit is contained in:
parent
4c13d31931
commit
4939c19c12
|
@ -1,7 +1,6 @@
|
|||
import React, { Suspense, useEffect } from "react";
|
||||
import assetImage from "../../assets/image/image.png";
|
||||
import React, { Suspense } from "react";
|
||||
import { FilledStarsIconSmall } from "../../components/icons/marketPlaceIcons";
|
||||
import { Canvas, useThree } from "@react-three/fiber";
|
||||
import { Canvas } from "@react-three/fiber";
|
||||
import { ContactShadows, OrbitControls, Text } from "@react-three/drei";
|
||||
import GltfLoader from "./GltfLoader";
|
||||
import * as THREE from "three";
|
||||
|
@ -14,17 +13,26 @@ interface SelectedCard {
|
|||
rating: number;
|
||||
views: number;
|
||||
description: string;
|
||||
AssetID: string;
|
||||
}
|
||||
|
||||
// Define the props type for AssetPreview
|
||||
interface AssetPreviewProps {
|
||||
selectedCard: SelectedCard;
|
||||
modelUrl: string;
|
||||
setSelectedCard: React.Dispatch<React.SetStateAction<SelectedCard | null>>; // Type for setter function
|
||||
}
|
||||
|
||||
const savedTheme: string | null = localStorage.getItem("theme");
|
||||
|
||||
function Ui() {
|
||||
return (
|
||||
<Text color="#6f42c1" anchorX="center" anchorY="middle" scale={0.3}>
|
||||
<Text
|
||||
color={savedTheme === "dark" ? "#d2baff" : "#6f42c1"}
|
||||
anchorX="center"
|
||||
anchorY="middle"
|
||||
scale={0.3}
|
||||
>
|
||||
Loading preview...
|
||||
</Text>
|
||||
);
|
||||
|
@ -33,16 +41,8 @@ function Ui() {
|
|||
const AssetPreview: React.FC<AssetPreviewProps> = ({
|
||||
selectedCard,
|
||||
setSelectedCard,
|
||||
modelUrl,
|
||||
}) => {
|
||||
// Ensure rating is a valid number between 0 and 5
|
||||
const rating = Math.max(
|
||||
0,
|
||||
Math.min(5, isNaN(selectedCard.rating) ? 0 : selectedCard.rating)
|
||||
);
|
||||
|
||||
// Ensure that the rating is a valid positive integer for array length
|
||||
const starsArray = Array.from({ length: rating }, (_, index) => index);
|
||||
|
||||
return (
|
||||
<div className="assetPreview-wrapper">
|
||||
<div className="assetPreview">
|
||||
|
@ -53,18 +53,19 @@ const AssetPreview: React.FC<AssetPreviewProps> = ({
|
|||
<Canvas
|
||||
flat
|
||||
shadows
|
||||
color="#FFFFFF"
|
||||
camera={{ fov: 75 }}
|
||||
gl={{
|
||||
preserveDrawingBuffer: true,
|
||||
}}
|
||||
onCreated={({ scene }) => {
|
||||
scene.background = new THREE.Color(0xffffff);
|
||||
scene.background = new THREE.Color(
|
||||
savedTheme === "dark" ? 0x19191d : 0xfcfdfd
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Suspense fallback={<Ui />}>
|
||||
{selectedCard.assetName && (
|
||||
<GltfLoader fromServer={selectedCard.assetName} />
|
||||
{selectedCard.assetName && modelUrl && (
|
||||
<GltfLoader fromServer={modelUrl} />
|
||||
)}
|
||||
<OrbitControls minPolarAngle={0} maxPolarAngle={Math.PI / 2} />
|
||||
<ContactShadows
|
||||
|
@ -113,9 +114,9 @@ const AssetPreview: React.FC<AssetPreviewProps> = ({
|
|||
</div>
|
||||
|
||||
{/* close button */}
|
||||
<div className="closeButton" onClick={() => setSelectedCard(null)}>
|
||||
<button className="closeButton" onClick={() => setSelectedCard(null)}>
|
||||
{`<-back`}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
import React, { useEffect } from "react";
|
||||
import React from "react";
|
||||
import {
|
||||
CommentsIcon,
|
||||
DownloadIcon,
|
||||
EyeIconBig,
|
||||
FilledStarsIconSmall,
|
||||
StarsIconSmall,
|
||||
VerifiedIcon,
|
||||
} from "../../components/icons/marketPlaceIcons";
|
||||
|
||||
import assetImage from "../../assets/image/image.png";
|
||||
import { getAssetDownload } from "../../services/marketplace/getAssetDownload";
|
||||
|
||||
interface CardProps {
|
||||
assetName: string;
|
||||
|
@ -19,6 +16,7 @@ interface CardProps {
|
|||
views: number;
|
||||
image: string;
|
||||
description: string;
|
||||
AssetID: string;
|
||||
onSelectCard: (cardData: {
|
||||
assetName: string;
|
||||
uploadedOn: number;
|
||||
|
@ -26,6 +24,7 @@ interface CardProps {
|
|||
rating: number;
|
||||
views: number;
|
||||
description: string;
|
||||
AssetID: string;
|
||||
}) => void;
|
||||
}
|
||||
|
||||
|
@ -38,9 +37,18 @@ const Card: React.FC<CardProps> = ({
|
|||
image,
|
||||
description,
|
||||
onSelectCard,
|
||||
AssetID,
|
||||
}) => {
|
||||
const handleCardSelect = () => {
|
||||
onSelectCard({ assetName, uploadedOn, price, rating, views, description });
|
||||
onSelectCard({
|
||||
assetName,
|
||||
uploadedOn,
|
||||
price,
|
||||
rating,
|
||||
views,
|
||||
description,
|
||||
AssetID,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -81,22 +89,22 @@ const Card: React.FC<CardProps> = ({
|
|||
<div className="stars-container">
|
||||
<div className="stars-wrapper">
|
||||
{[...Array(5)].map((_, index) => (
|
||||
<>
|
||||
<React.Fragment key={index} >
|
||||
{index < 3 ? (
|
||||
<FilledStarsIconSmall key={index} />
|
||||
<FilledStarsIconSmall />
|
||||
) : (
|
||||
<StarsIconSmall key={index} />
|
||||
<StarsIconSmall />
|
||||
)}
|
||||
</>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
<div className="units">
|
||||
₹ {price}/<span>unit</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="buy-now-button" onClick={handleCardSelect}>
|
||||
<button className="buy-now-button" onClick={handleCardSelect}>
|
||||
Buy now
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React, { useState } from "react";
|
||||
import Card from "./Card";
|
||||
import AssetPreview from "./AssetPreview";
|
||||
import { fetchGltfUrl } from "../../services/marketplace/fetchGltfUrl";
|
||||
|
||||
interface ModelData {
|
||||
CreatedBy: string;
|
||||
|
@ -15,12 +16,14 @@ interface ModelData {
|
|||
uploadDate: number;
|
||||
_id: string;
|
||||
price: number;
|
||||
AssetID: string;
|
||||
}
|
||||
interface ModelsProps {
|
||||
models: ModelData[];
|
||||
}
|
||||
|
||||
const CardsContainer: React.FC<ModelsProps> = ({ models }) => {
|
||||
const [modelUrl, setModelUrl] = useState<string>("");
|
||||
const [selectedCard, setSelectedCard] = useState<{
|
||||
assetName: string;
|
||||
uploadedOn: number;
|
||||
|
@ -28,19 +31,23 @@ const CardsContainer: React.FC<ModelsProps> = ({ models }) => {
|
|||
rating: number;
|
||||
views: number;
|
||||
description: string;
|
||||
AssetID: string;
|
||||
} | null>(null);
|
||||
|
||||
const handleCardSelect = (cardData: {
|
||||
const handleCardSelect = async (cardData: {
|
||||
assetName: string;
|
||||
uploadedOn: number;
|
||||
price: number;
|
||||
rating: number;
|
||||
views: number;
|
||||
description: string;
|
||||
AssetID: string;
|
||||
}) => {
|
||||
setSelectedCard(cardData);
|
||||
const res = await fetchGltfUrl(cardData.assetName, cardData.AssetID);
|
||||
console.log("res: ", res);
|
||||
setModelUrl(res.url);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="cards-container-wrapper">
|
||||
<div className="cards-container-container">
|
||||
|
@ -56,6 +63,7 @@ const CardsContainer: React.FC<ModelsProps> = ({ models }) => {
|
|||
rating={4.5}
|
||||
views={800}
|
||||
onSelectCard={handleCardSelect}
|
||||
AssetID={assetDetail.AssetID}
|
||||
image={assetDetail.thumbnail}
|
||||
description={assetDetail.description}
|
||||
/>
|
||||
|
@ -66,6 +74,7 @@ const CardsContainer: React.FC<ModelsProps> = ({ models }) => {
|
|||
<AssetPreview
|
||||
selectedCard={selectedCard}
|
||||
setSelectedCard={setSelectedCard}
|
||||
modelUrl={modelUrl}
|
||||
/>
|
||||
)}
|
||||
{/* </RenderOverlay> */}
|
||||
|
|
|
@ -16,6 +16,7 @@ interface ModelData {
|
|||
uploadDate: number;
|
||||
_id: string;
|
||||
price: number;
|
||||
AssetID: string;
|
||||
}
|
||||
|
||||
interface ModelsProps {
|
||||
|
@ -44,7 +45,7 @@ const FilterSearch: React.FC<ModelsProps> = ({
|
|||
const descending = [...models].sort((a, b) => b.filename.localeCompare(a.filename));
|
||||
setModels(descending);
|
||||
}
|
||||
}, [activeOption]);
|
||||
}, [activeOption, models, setModels]);
|
||||
|
||||
const handleSearch = (val: string) => {
|
||||
const filteredModel = filteredModels.filter((model) =>
|
||||
|
@ -73,13 +74,13 @@ const FilterSearch: React.FC<ModelsProps> = ({
|
|||
<div className="label">Rating</div>
|
||||
<div className="stars">
|
||||
{[0, 1, 2, 3, 4].map((i) => (
|
||||
<div
|
||||
<button
|
||||
key={i}
|
||||
onClick={() => handleStarClick(i)}
|
||||
className={`star-wrapper ${i < rating ? "filled" : "empty"}`}
|
||||
>
|
||||
<StarsIcon />
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -3,7 +3,6 @@ import { useFrame } from "@react-three/fiber";
|
|||
import { Stage, useGLTF } from "@react-three/drei";
|
||||
import { AnimationMixer, AnimationAction, Object3D } from "three";
|
||||
import * as THREE from "three";
|
||||
import { fetchGltfUrl } from "../../services/marketplace/fetchGltfUrl";
|
||||
|
||||
interface GltfLoaderProps {
|
||||
glbdata?: boolean;
|
||||
|
@ -13,7 +12,6 @@ interface GltfLoaderProps {
|
|||
setSelectedAnimation?: (animation: string) => void;
|
||||
}
|
||||
|
||||
// const getGLTFUrl = (url: string) => url; // Placeholder for your actual function
|
||||
|
||||
const GltfLoader: React.FC<GltfLoaderProps> = ({
|
||||
glbdata,
|
||||
|
@ -21,12 +19,10 @@ const GltfLoader: React.FC<GltfLoaderProps> = ({
|
|||
setAnimations,
|
||||
selectedAnimation,
|
||||
}) => {
|
||||
const modelUrl: any = fromServer ? fetchGltfUrl(fromServer) : glbdata;
|
||||
const { scene, animations } = useGLTF(modelUrl ?? "") as {
|
||||
const { scene, animations } = useGLTF(fromServer ?? "") as {
|
||||
scene: Object3D;
|
||||
animations: THREE.AnimationClip[];
|
||||
};
|
||||
|
||||
const mixer = useRef<AnimationMixer | null>(
|
||||
scene ? new AnimationMixer(scene) : null
|
||||
);
|
||||
|
|
|
@ -16,6 +16,7 @@ interface ModelData {
|
|||
uploadDate: number;
|
||||
_id: string;
|
||||
price: number;
|
||||
AssetID: string;
|
||||
}
|
||||
const MarketPlace = () => {
|
||||
const [models, setModels] = useState<ModelData[]>([]);
|
||||
|
|
|
@ -1,7 +1,26 @@
|
|||
let BackEnd_url = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
|
||||
export const fetchGltfUrl = (filename: string) => {
|
||||
if (filename) {
|
||||
return `${BackEnd_url}/api/v1/getAssetFile/${filename}`;
|
||||
export const fetchGltfUrl = async (filename: string, AssetID: string) => {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${BackEnd_url}/api/v2/assetDetails/${filename}/${AssetID}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
// body: JSON.stringify(assetData),
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch asset details");
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
return result;
|
||||
} catch (error: any) {
|
||||
//
|
||||
throw new Error(error.message);
|
||||
}
|
||||
return null; // or handle the case when filename is not provided
|
||||
};
|
||||
}
|
|
@ -2,9 +2,6 @@
|
|||
@use "../../abstracts/mixins.scss" as *;
|
||||
|
||||
.marketplace-wrapper {
|
||||
// transform: scale(0.65);
|
||||
/* Start at 90% width */
|
||||
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
z-index: #{$z-index-marketplace};
|
||||
|
@ -14,7 +11,6 @@
|
|||
top: 0;
|
||||
padding: 10px;
|
||||
padding-top: 100px;
|
||||
// animation: growWidth 0.4s ease-in-out 0.5s forwards;
|
||||
|
||||
.marketplace-container {
|
||||
position: relative;
|
||||
|
@ -43,8 +39,6 @@
|
|||
width: 100%;
|
||||
|
||||
.skeleton-content {
|
||||
|
||||
|
||||
width: calc(25% - 14px) !important;
|
||||
height: 100%;
|
||||
border-radius: #{$border-radius-xlarge};
|
||||
|
@ -79,7 +73,6 @@
|
|||
}
|
||||
|
||||
.button {
|
||||
|
||||
width: 100%;
|
||||
height: 35px;
|
||||
border-radius: 20px;
|
||||
|
@ -143,8 +136,7 @@
|
|||
|
||||
.star-wrapper.filled {
|
||||
svg {
|
||||
|
||||
fill: #F3A50C;
|
||||
fill: #f3a50c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -190,10 +182,6 @@
|
|||
justify-content: center;
|
||||
gap: 6px;
|
||||
|
||||
.assets-container {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.icon {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
|
@ -208,7 +196,7 @@
|
|||
.image-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
max-height: 180px;
|
||||
height: 180px;
|
||||
justify-content: center;
|
||||
border-radius: #{$border-radius-medium};
|
||||
overflow: hidden;
|
||||
|
@ -224,7 +212,7 @@
|
|||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0;
|
||||
|
||||
height: auto;
|
||||
.name-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -299,7 +287,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
.assetPreview-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
@ -307,15 +294,18 @@
|
|||
top: 0;
|
||||
left: 0;
|
||||
z-index: 3;
|
||||
padding: 0 10px;
|
||||
|
||||
.assetPreview {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: var(--background-color);
|
||||
backdrop-filter: blur(18px);
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
overflow: hidden;
|
||||
border-radius: #{$border-radius-extra-large};
|
||||
outline: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
// Image Preview Section
|
||||
|
@ -352,8 +342,8 @@
|
|||
min-width: 26px;
|
||||
border-radius: #{$border-radius-circle};
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--background-color);
|
||||
background: var(--accent-color);
|
||||
color: var(--text-button-color);
|
||||
background: var(--background-color-button);
|
||||
}
|
||||
|
||||
.organization-details {
|
||||
|
@ -361,9 +351,7 @@
|
|||
flex-direction: column;
|
||||
|
||||
.organization-name {
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
|
||||
font-weight: #{$bold-weight};
|
||||
font-size: $regular;
|
||||
}
|
||||
|
@ -380,35 +368,31 @@
|
|||
margin-top: 20px;
|
||||
|
||||
.asset-name {
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
font-weight: #{$bold-weight};
|
||||
font-size: $large;
|
||||
font-size: var(--font-size-large);
|
||||
}
|
||||
|
||||
.asset-description {
|
||||
margin-bottom: 20px;
|
||||
color: #666;
|
||||
color: var(--input-text-color);
|
||||
}
|
||||
|
||||
.asset-review {
|
||||
width: fit-content;
|
||||
padding: 5px 10px;
|
||||
padding: 5px 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
outline: 1px solid #909090cc;
|
||||
border-radius: #{$border-radius-small};
|
||||
outline: 1px solid var(--border-color);
|
||||
border-radius: #{$border-radius-large};
|
||||
|
||||
.asset-rating {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
margin-right: 10px;
|
||||
font-weight: bold;
|
||||
position: relative;
|
||||
|
||||
font-weight: #{$bold-weight};
|
||||
font-size: $regular;
|
||||
|
||||
|
@ -442,25 +426,25 @@
|
|||
}
|
||||
|
||||
.button {
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
border-radius: #{$border-radius-small};
|
||||
color: var(--text-button-color);
|
||||
padding: 8px 26px;
|
||||
border-radius: #{$border-radius-extra-large};
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
|
||||
&:first-child {
|
||||
outline: 1px solid var(--accent-color);
|
||||
color: var(--accent-color);
|
||||
outline: 1px solid var(--background-color-button);
|
||||
color: var(--highlight-text-color);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
background: var(--accent-color);
|
||||
color: var(--background-color);
|
||||
background: var(--background-color-button);
|
||||
color: var(--text-button-color);
|
||||
}
|
||||
}
|
||||
|
||||
.closeButton {
|
||||
color: var(--accent-color);
|
||||
color: var(--highlight-text-color);
|
||||
position: absolute;
|
||||
top: 18px;
|
||||
left: 18px;
|
||||
|
@ -468,4 +452,4 @@
|
|||
cursor: pointer;
|
||||
font-size: var(--font-size-large);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue