Skip to content

Commit

Permalink
9-nftrarity-summary-section-improvements (#10)
Browse files Browse the repository at this point in the history
* fix: changed the copy for each file

Signed-off-by: michalrozekariane <[email protected]>

* feat: processZipFile rebuilded + new media files logic implemented

Signed-off-by: michalrozekariane <[email protected]>

* feat: rarity inspector set up and adjusted for further calculateRarityFromData

Signed-off-by: michalrozekariane <[email protected]>

* feat: comments added + array for supported image types

Signed-off-by: michalrozekariane <[email protected]>

* feat: gallery view implemented + modal children logic added

Signed-off-by: michalrozekariane <[email protected]>

* feat: add total number of nfts label & loader for dropzone

Signed-off-by: michalrozekariane <[email protected]>

* fix: cursor pointer added for nft card

Signed-off-by: michalrozekariane <[email protected]>

* Feat: Add details modal

Signed-off-by: Stanislaw Mazowiecki <[email protected]>

* Fix: Change copyright title

Signed-off-by: Stanislaw Mazowiecki <[email protected]>

* fix: NFTDetails children deleted because it was unneeded

Signed-off-by: michalrozekariane <[email protected]>

* fix: loading state is now properly set to false while we get an error

Signed-off-by: michalrozekariane <[email protected]>

* feat: add missing copy to loader file

Signed-off-by: michalrozekariane <[email protected]>

* fix: console log removed

Signed-off-by: michalrozekariane <[email protected]>

* Fix: Some css fixes

Signed-off-by: Stanislaw Mazowiecki <[email protected]>

* chore: add rarity field to metadata array

Signed-off-by: michalrozekariane <[email protected]>

* fix: after review

Signed-off-by: michalrozekariane <[email protected]>

* Feat: Make itemWrapper only display component

Signed-off-by: Stanislaw Mazowiecki <[email protected]>

* Fix: Infinite loading fix

Signed-off-by: Stanislaw Mazowiecki <[email protected]>

* chore: rarity logic for NFTStatsDisplay component

Signed-off-by: michalrozekariane <[email protected]>

* feat: add NFTStatsDisplay component

Signed-off-by: michalrozekariane <[email protected]>

* fix: rarityRank fix for nft details modal

Signed-off-by: michalrozekariane <[email protected]>

* feat: code refactor

Signed-off-by: michalrozekariane <[email protected]>

* feat: rarity code refactor

Signed-off-by: michalrozekariane <[email protected]>

* feat: featured attribues cards content redesign

Signed-off-by: michalrozekariane <[email protected]>

* feat: dictionary changes & small improvements

Signed-off-by: michalrozekariane <[email protected]>

* feat: charts colors changes

Signed-off-by: michalrozekariane <[email protected]>

* feat: global scrollbar styles

Signed-off-by: michalrozekariane <[email protected]>

* fix: dialog scrollbar fixes

Signed-off-by: michalrozekariane <[email protected]>

* fix: overflow scroll dialog property

Signed-off-by: michalrozekariane <[email protected]>

---------

Signed-off-by: michalrozekariane <[email protected]>
Signed-off-by: Stanislaw Mazowiecki <[email protected]>
Co-authored-by: Stanislaw Mazowiecki <[email protected]>
  • Loading branch information
1 parent 41ffd53 commit a254339
Show file tree
Hide file tree
Showing 15 changed files with 212 additions and 64 deletions.
2 changes: 1 addition & 1 deletion src/components/pages/DropzonePage/DropzonePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ export default function DropzonePage() {
<div className="my-10">
<NFTStatsDisplay metadata={metadata} />
<h3 className="ml-4">
{dictionary.nftTable.totalNftsNumber}: <span className="font-bold">{metadata.length}</span>
{dictionary.nftGallery.totalNftsNumber}: <span className="font-bold">{metadata.length}</span>
</h3>
<NFTGallery metadataRows={sortedMetadataWithRarity} />
</div>
Expand Down
56 changes: 31 additions & 25 deletions src/components/pages/DropzonePage/NFTItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,38 +20,40 @@
import { MetadataObject } from 'hedera-nft-utilities';
import { truncateString } from '@/utils/helpers/truncateString';
import { dictionary } from '@/libs/en';
import { useEffect, useState } from 'react';
import { cn } from '@/utils/helpers/cn';
import { Button } from '@/components/ui/button';
import { getProperImageURL } from '@/utils/helpers/getProperImageURL';
import { ImageWithLoading } from '@/components/shared/ImageWithLoading';
import { AttributeOccurrence } from '@/utils/types/attributeOccurrence';

const TRUNCATE_NAME_NUMBER = 13;

interface NFTItemProps {
metadataObject: MetadataObject;
metadataLength: number;
index: number;
isModalOpen: boolean;
setIsModalOpen: (_isOpen: boolean) => void;
rarityRank: number;
featuredCard: boolean;
attribute?: AttributeOccurrence;
usesCount?: number;
}

export const NFTItem = ({ metadataObject, index, isModalOpen, setIsModalOpen, rarityRank }: NFTItemProps) => {
const [hoverActive, setHoverActive] = useState(false);
export const NFTItem = ({
metadataObject,
metadataLength,
index,
setIsModalOpen,
rarityRank,
featuredCard = false,
attribute,
usesCount,
}: NFTItemProps) => {
const name = metadataObject.name as string;
const image = getProperImageURL(metadataObject.image as string);

useEffect(() => {
isModalOpen && setHoverActive(false);
}, [isModalOpen]);

const showButton = !isModalOpen && hoverActive;
const { trait, value } = attribute || {};

return (
<div
key={index}
onMouseEnter={() => setHoverActive(true)}
onMouseLeave={() => setHoverActive(false)}
onClick={() => setIsModalOpen(true)}
className="group relative flex cursor-pointer flex-col items-center overflow-hidden rounded-lg shadow-cardShadow transition duration-200 md:hover:scale-105"
>
Expand All @@ -60,21 +62,25 @@ export const NFTItem = ({ metadataObject, index, isModalOpen, setIsModalOpen, ra
</div>
<div className="flex w-full flex-col justify-between rounded-b-lg bg-white p-4 text-left sm:flex-col">
<div className="flex w-full flex-row justify-between">
<span>
{dictionary.nftTable.headers.number} {index + 1}
</span>
<span className="font-semibold">{truncateString(name, TRUNCATE_NAME_NUMBER)}</span>
<span>{featuredCard ? trait : `${dictionary.nftGallery.headers.number} ${index + 1}`}</span>
<span className="font-semibold">{featuredCard ? value : truncateString(name, TRUNCATE_NAME_NUMBER)}</span>
</div>
<div className="mt-2">
{dictionary.nftTable.rarityRank}: {rarityRank}
{featuredCard ? (
<div>
<span>{dictionary.nftGallery.nftCard.usedIn}: </span>
<span>
{usesCount}/{metadataLength} {dictionary.nftGallery.nftCard.nfts}
</span>
</div>
) : (
<span>
{dictionary.nftGallery.rarityRank}: {rarityRank}
</span>
)}
</div>
</div>
<div
className={cn(
showButton ? 'translate-y-0' : 'translate-y-full',
'absolute bottom-0 w-full bg-white text-center transition duration-300 ease-in-out',
)}
>
<div className="absolute bottom-0 w-full translate-y-full bg-white text-center transition-transform duration-300 ease-in-out group-hover:translate-y-0">
<Button className="w-full rounded-none rounded-b-lg">{dictionary.modal.details}</Button>
</div>
</div>
Expand Down
13 changes: 11 additions & 2 deletions src/components/pages/DropzonePage/NFTItemWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { useCallback, useState } from 'react';
import { NFTDetails } from '@/components/pages/NFTDetailsDialog/Dialog/NFTDetails';
import { MetadataObject, TraitOccurrence } from 'hedera-nft-utilities';
import { MetadataRow } from '@/utils/types/metadataRow';
import { AttributeOccurrence } from '@/utils/types/attributeOccurrence';

export const NFTItemWrapper = ({
index,
Expand All @@ -33,6 +34,9 @@ export const NFTItemWrapper = ({
traitOccurrence,
hasNextPrevButtons = true,
rarityRank,
featuredCard = false,
usesCount,
attribute,
}: {
index: number;
metadataObject: MetadataObject;
Expand All @@ -43,6 +47,9 @@ export const NFTItemWrapper = ({
traitOccurrence: TraitOccurrence[];
hasNextPrevButtons?: boolean;
rarityRank: number;
featuredCard?: boolean;
usesCount?: number;
attribute?: AttributeOccurrence;
}) => {
const [activeId, setActiveId] = useState(index);
const [isModalOpen, setIsModalOpen] = useState(false);
Expand All @@ -68,11 +75,13 @@ export const NFTItemWrapper = ({
rarityRank={activeId === index ? rarityRank : (metadataRows?.[activeId].rarityRank as number)}
/>
<NFTItem
key={index}
metadataObject={metadataObject}
metadataLength={metadataLength}
index={index}
isModalOpen={isModalOpen}
setIsModalOpen={setIsModalOpen}
featuredCard={featuredCard}
attribute={attribute}
usesCount={usesCount}
rarityRank={activeId === index ? rarityRank : (metadataRows?.[activeId].rarityRank as number)}
/>
</>
Expand Down
49 changes: 27 additions & 22 deletions src/components/pages/DropzonePage/NFTStatsDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,29 +20,30 @@
import { MetadataRow } from '@/utils/types/metadataRow';
import { NFTItemWrapper } from '@/components/pages/DropzonePage/NFTItemWrapper';
import { RarityCurveCalculation } from '@/components/pages/NFTDetailsDialog/Charts/RarityCurveCalculation';
import { findNFTRarity } from '@/components/pages/DropzonePage/findNFTRarity';
import { findMostAndLeastCommonAttribute } from '@/components/pages/DropzonePage/findMostAndLeastCommonAttribute';
import { findNFTsWithAttribute } from '@/components/pages/DropzonePage/findNFTsWithAttribute';
import { calculateTraitOccurrenceFromData } from 'hedera-nft-utilities/src/rarity';
import { dictionary } from '@/libs/en';
import { useMemo } from 'react';
import { useNFTRarityData } from '@/components/pages/DropzonePage/useNFTRarityData';

interface NFTStatsDisplayProps {
metadata: MetadataRow[];
}

export const NFTStatsDisplay: React.FC<NFTStatsDisplayProps> = ({ metadata }) => {
const traitOccurrence = useMemo(() => calculateTraitOccurrenceFromData(metadata.map((m) => m.metadata)), [metadata]);
const mostRareNFT = findNFTRarity(metadata, 'most-rare');
const leastRareNFT = findNFTRarity(metadata, 'least-rare');
const { mostCommon, leastCommon } = findMostAndLeastCommonAttribute(metadata);
const { nftsWithAttribute: nftsWithLeastRareAttribute, count: countMostCommon } = findNFTsWithAttribute(metadata, mostCommon);
const { nftsWithAttribute: nftsWithMostRareAttribute, count: countLeastCommon } = findNFTsWithAttribute(metadata, leastCommon);
const {
traitOccurrence,
mostRareNFT,
leastRareNFT,
mostRareAttribute,
leastRareAttribute,
nftsWithLeastRareAttribute,
nftsWithMostRareAttribute,
countLeastRare,
countMostRare,
} = useNFTRarityData(metadata);

return (
<>
<div className="mt-6 grid grid-cols-1 gap-4 px-4 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4">
<div className="w-[75%] text-center">
<div className="mt-6 grid grid-cols-1 gap-4 px-4 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-4">
<div className="mx-auto w-[75%] text-center">
<NFTItemWrapper
index={mostRareNFT.rarity.NFT - 1}
metadataObject={mostRareNFT.metadata}
Expand All @@ -54,10 +55,10 @@ export const NFTStatsDisplay: React.FC<NFTStatsDisplayProps> = ({ metadata }) =>
metadataRows={metadata}
rarityRank={mostRareNFT.rarityRank}
/>
<p className="p-2 font-semibold">{dictionary.nftStatsDisplay.mostRareNFT}</p>
<p className="mt-2 p-2 font-semibold uppercase xl:whitespace-nowrap">{dictionary.nftStatsDisplay.mostRareNFT}</p>
</div>

<div className="w-[75%] text-center">
<div className="mx-auto w-[75%] text-center">
<NFTItemWrapper
index={leastRareNFT.rarity.NFT - 1}
metadataObject={leastRareNFT.metadata}
Expand All @@ -69,9 +70,9 @@ export const NFTStatsDisplay: React.FC<NFTStatsDisplayProps> = ({ metadata }) =>
metadataRows={metadata}
rarityRank={leastRareNFT.rarityRank}
/>
<p className="p-2 font-semibold">{dictionary.nftStatsDisplay.mostCommonNFT}</p>
<p className="mt-2 p-2 font-semibold uppercase xl:whitespace-nowrap">{dictionary.nftStatsDisplay.mostCommonNFT}</p>
</div>
<div className="w-[75%] text-center">
<div className="mx-auto w-[75%] text-center">
<NFTItemWrapper
index={nftsWithMostRareAttribute[0].rarity.NFT - 1}
metadataObject={nftsWithMostRareAttribute[0].metadata}
Expand All @@ -82,11 +83,13 @@ export const NFTStatsDisplay: React.FC<NFTStatsDisplayProps> = ({ metadata }) =>
hasNextPrevButtons={false}
metadataRows={metadata}
rarityRank={nftsWithMostRareAttribute[0].rarityRank}
attribute={mostRareAttribute}
usesCount={countMostRare}
featuredCard
/>
<p className="p-2 font-semibold">{dictionary.nftStatsDisplay.mostRareAttribute}</p>
<p className="p-2 font-semibold">{dictionary.nftStatsDisplay.usedIn(countLeastCommon)}</p>
<p className="mt-2 p-2 font-semibold uppercase xl:whitespace-nowrap">{dictionary.nftStatsDisplay.mostRareAttribute}</p>
</div>
<div className="w-[75%] text-center">
<div className="mx-auto w-[75%] text-center">
<NFTItemWrapper
index={nftsWithLeastRareAttribute[0].rarity.NFT - 1}
metadataObject={nftsWithLeastRareAttribute[0].metadata}
Expand All @@ -97,9 +100,11 @@ export const NFTStatsDisplay: React.FC<NFTStatsDisplayProps> = ({ metadata }) =>
hasNextPrevButtons={false}
metadataRows={metadata}
rarityRank={nftsWithLeastRareAttribute[0].rarityRank}
attribute={leastRareAttribute}
usesCount={countLeastRare}
featuredCard
/>
<p className="p-2 font-semibold">{dictionary.nftStatsDisplay.mostCommonAttribute}</p>
<p className="p-2 font-semibold">{dictionary.nftStatsDisplay.usedIn(countMostCommon)}</p>
<p className="mt-2 p-2 font-semibold uppercase xl:whitespace-nowrap">{dictionary.nftStatsDisplay.mostCommonAttribute}</p>
</div>
</div>
<div className="mx-auto my-10 w-1/2">
Expand Down
55 changes: 55 additions & 0 deletions src/components/pages/DropzonePage/findMostAndLeastRareAttribute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*-
*
* NFT Rarity Inspector
*
* Copyright (C) 2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { AttributeOccurrence } from '@/utils/types/attributeOccurrence';
import { MetadataRow } from '@/utils/types/metadataRow';

export const findMostAndLeastRareAttribute = (
metadata: MetadataRow[],
): { leastRareAttribute: AttributeOccurrence; mostRareAttribute: AttributeOccurrence } => {
const attributeOccurrences: Record<string, number> = {};

metadata.forEach((nft) => {
const attributes = nft.metadata['attributes'];
if (Array.isArray(attributes)) {
attributes.forEach((attribute) => {
const key = `${attribute.trait_type}:${attribute.value}`;
attributeOccurrences[key] = (attributeOccurrences[key] || 0) + 1;
});
}
});

let leastRare: AttributeOccurrence | null = null;
let mostRare: AttributeOccurrence | null = null;

Object.entries(attributeOccurrences).forEach(([key, count]) => {
const [trait, value] = key.split(':');
if (!leastRare || count > leastRare.occurrence) {
leastRare = { trait, value, occurrence: count };
}
if (!mostRare || count < mostRare.occurrence) {
mostRare = { trait, value, occurrence: count };
}
});

return {
leastRareAttribute: leastRare || { trait: '', value: '', occurrence: 0 },
mostRareAttribute: mostRare || { trait: '', value: '', occurrence: 0 },
};
};
46 changes: 46 additions & 0 deletions src/components/pages/DropzonePage/useNFTRarityData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*-
*
* NFT Rarity Inspector
*
* Copyright (C) 2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { useMemo } from 'react';
import { MetadataRow } from '@/utils/types/metadataRow';
import { calculateTraitOccurrenceFromData } from 'hedera-nft-utilities/src/rarity';
import { findNFTRarity } from '@/components/pages/DropzonePage/findNFTRarity';
import { findMostAndLeastRareAttribute } from '@/components/pages/DropzonePage/findMostAndLeastRareAttribute';
import { findNFTsWithAttribute } from '@/components/pages/DropzonePage/findNFTsWithAttribute';

export const useNFTRarityData = (metadata: MetadataRow[]) => {
const traitOccurrence = useMemo(() => calculateTraitOccurrenceFromData(metadata.map((m) => m.metadata)), [metadata]);
const mostRareNFT = findNFTRarity(metadata, 'most-rare');
const leastRareNFT = findNFTRarity(metadata, 'least-rare');
const { mostRareAttribute, leastRareAttribute } = findMostAndLeastRareAttribute(metadata);
const { nftsWithAttribute: nftsWithLeastRareAttribute, count: countLeastRare } = findNFTsWithAttribute(metadata, leastRareAttribute);
const { nftsWithAttribute: nftsWithMostRareAttribute, count: countMostRare } = findNFTsWithAttribute(metadata, mostRareAttribute);

return {
traitOccurrence,
mostRareNFT,
leastRareNFT,
mostRareAttribute,
leastRareAttribute,
nftsWithLeastRareAttribute,
nftsWithMostRareAttribute,
countLeastRare,
countMostRare,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export const AttributesOccurenceBarChart: React.FC<{ attributesData: AttributeWi
datasets: [
{
data: attributesData.map((item) => item),
backgroundColor: 'rgba(255, 99, 132, 0.5)',
backgroundColor: 'darkgray',
},
],
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,10 @@ import {
} from 'chart.js';
import { Line } from 'react-chartjs-2';
import { YAxisDescription } from '@/components/pages/NFTDetailsDialog/Charts/YAxisDescription';

ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title);

const indicatingLinesColor = '#3b95f6c2';
const darkPink = 'rgba(255, 99, 132, 0.5)';
const pink = 'rgb(255, 99, 132)';
const indicatingLinesColor = '#f00';
const defaultColor = ChartJS.defaults.color.toString();
const tooltipTitleFontSize = 16;
const tooltipFooterFontSize = 14;
Expand Down Expand Up @@ -167,11 +166,11 @@ export const RarityCurveChart: React.FC<{
datasets: [
{
data: probabilityDistribution,
borderColor: pink,
backgroundColor: darkPink,
borderColor: 'black',
backgroundColor: 'gray',
pointRadius: createDot(),
pointBackgroundColor: indicatingLinesColor,
pointBorderColor: indicatingLinesColor,
pointBackgroundColor: 'red',
pointBorderColor: 'black',
},
],
}}
Expand Down
2 changes: 1 addition & 1 deletion src/components/pages/NFTDetailsDialog/Dialog/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ interface FooterProps {

export const Footer = ({ activeId, handlePrevious, handleNext, metadataLength }: FooterProps) => {
return (
<DialogFooter className="flex flex-row items-center gap-1">
<DialogFooter className="mt-4 flex flex-row items-center gap-1 px-7">
<Button className="w-full md:w-[100px]" disabled={activeId === 0} onClick={handlePrevious}>
{dictionary.modal.previousButton}
</Button>
Expand Down
Loading

0 comments on commit a254339

Please sign in to comment.