Skip to content

Commit

Permalink
✨feat(lld): UI of rare sats table
Browse files Browse the repository at this point in the history
  • Loading branch information
LucasWerey committed Sep 9, 2024
1 parent 917045c commit bd17b01
Show file tree
Hide file tree
Showing 18 changed files with 2,350 additions and 54 deletions.
5 changes: 5 additions & 0 deletions .changeset/friendly-rings-laugh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ledger-live-desktop": patch
---

add the ui of rare sats table for ordinals
Original file line number Diff line number Diff line change
@@ -1,33 +1,73 @@
import React from "react";
import { Icons } from "@ledgerhq/react-ui";
import { IconProps } from "../../types/Collection";

export const mappingKeysWithIconAndName = {
alpha: { icon: <Icons.OrdinalsAlpha />, name: "Alpha" },
black_epic: { icon: <Icons.OrdinalsBlackEpic />, name: "Black Epic" },
black_legendary: { icon: <Icons.OrdinalsBlackLegendary />, name: "Black Legendary" },
black_mythic: { icon: <Icons.OrdinalsBlackMythic />, name: "Black Mythic" },
black_rare: { icon: <Icons.OrdinalsBlackRare />, name: "Black Rare" },
black_uncommon: { icon: <Icons.OrdinalsBlackUncommon />, name: "Black Uncommon" },
block_9: { icon: <Icons.OrdinalsBlock9 />, name: "Block 9" },
block_9_450x: { icon: <Icons.OrdinalsBlock9450X />, name: "Block 9 450x" },
block_78: { icon: <Icons.OrdinalsBlock78 />, name: "Block 78" },
block_286: { icon: <Icons.OrdinalsBlock286 />, name: "Block 286" },
block_666: { icon: <Icons.OrdinalsBlock666 />, name: "Block 666" },
common: { icon: <Icons.OrdinalsCommon />, name: "Common" },
epic: { icon: <Icons.OrdinalsEpic />, name: "Epic" },
first_tx: { icon: <Icons.OrdinalsFirstTx />, name: "First Transaction" },
hitman: { icon: <Icons.OrdinalsHitman />, name: "Hitman" },
jpeg: { icon: <Icons.OrdinalsJpeg />, name: "JPEG" },
legacy: { icon: <Icons.OrdinalsLegacy />, name: "Legacy" },
legendary: { icon: <Icons.OrdinalsLegendary />, name: "Legendary" },
mythic: { icon: <Icons.OrdinalsMythic />, name: "Mythic" },
nakamoto: { icon: <Icons.OrdinalsNakamoto />, name: "Nakamoto" },
omega: { icon: <Icons.OrdinalsOmega />, name: "Omega" },
paliblock: { icon: <Icons.OrdinalsPaliblockPalindrome />, name: "PaliBlock Palindrome" },
palindrome: { icon: <Icons.OrdinalsPalindrome />, name: "Palindrome" },
palinception: { icon: <Icons.OrdinalsPalinception />, name: "Palinception" },
pizza: { icon: <Icons.OrdinalsPizza />, name: "Pizza" },
rare: { icon: <Icons.OrdinalsRare />, name: "Rare" },
uncommon: { icon: <Icons.OrdinalsUncommon />, name: "Uncommon" },
vintage: { icon: <Icons.OrdinalsVintage />, name: "Vintage" },
alpha: { icon: (props: IconProps) => <Icons.OrdinalsAlpha {...props} />, name: "Alpha" },
black_epic: {
icon: (props: IconProps) => <Icons.OrdinalsBlackEpic {...props} />,
name: "Black Epic",
},
black_legendary: {
icon: (props: IconProps) => <Icons.OrdinalsBlackLegendary {...props} />,
name: "Black Legendary",
},
black_mythic: {
icon: (props: IconProps) => <Icons.OrdinalsBlackMythic {...props} />,
name: "Black Mythic",
},
black_rare: {
icon: (props: IconProps) => <Icons.OrdinalsBlackRare {...props} />,
name: "Black Rare",
},
black_uncommon: {
icon: (props: IconProps) => <Icons.OrdinalsBlackUncommon {...props} />,
name: "Black Uncommon",
},
block_9: { icon: (props: IconProps) => <Icons.OrdinalsBlock9 {...props} />, name: "Block 9" },
block_9_450x: {
icon: (props: IconProps) => <Icons.OrdinalsBlock9450X {...props} />,
name: "Block 9 450x",
},
block_78: { icon: (props: IconProps) => <Icons.OrdinalsBlock78 {...props} />, name: "Block 78" },
block_286: {
icon: (props: IconProps) => <Icons.OrdinalsBlock286 {...props} />,
name: "Block 286",
},
block_666: {
icon: (props: IconProps) => <Icons.OrdinalsBlock666 {...props} />,
name: "Block 666",
},
common: { icon: (props: IconProps) => <Icons.OrdinalsCommon {...props} />, name: "Common" },
epic: { icon: (props: IconProps) => <Icons.OrdinalsEpic {...props} />, name: "Epic" },
first_tx: {
icon: (props: IconProps) => <Icons.OrdinalsFirstTx {...props} />,
name: "First Transaction",
},
hitman: { icon: (props: IconProps) => <Icons.OrdinalsHitman {...props} />, name: "Hitman" },
jpeg: { icon: (props: IconProps) => <Icons.OrdinalsJpeg {...props} />, name: "JPEG" },
legacy: { icon: (props: IconProps) => <Icons.OrdinalsLegacy {...props} />, name: "Legacy" },
legendary: {
icon: (props: IconProps) => <Icons.OrdinalsLegendary {...props} />,
name: "Legendary",
},
mythic: { icon: (props: IconProps) => <Icons.OrdinalsMythic {...props} />, name: "Mythic" },
nakamoto: { icon: (props: IconProps) => <Icons.OrdinalsNakamoto {...props} />, name: "Nakamoto" },
omega: { icon: (props: IconProps) => <Icons.OrdinalsOmega {...props} />, name: "Omega" },
paliblock: {
icon: (props: IconProps) => <Icons.OrdinalsPaliblockPalindrome {...props} />,
name: "PaliBlock Palindrome",
},
palindrome: {
icon: (props: IconProps) => <Icons.OrdinalsPalindrome {...props} />,
name: "Palindrome",
},
palinception: {
icon: (props: IconProps) => <Icons.OrdinalsPalinception {...props} />,
name: "Palinception",
},
pizza: { icon: (props: IconProps) => <Icons.OrdinalsPizza {...props} />, name: "Pizza" },
rare: { icon: (props: IconProps) => <Icons.OrdinalsRare {...props} />, name: "Rare" },
uncommon: { icon: (props: IconProps) => <Icons.OrdinalsUncommon {...props} />, name: "Uncommon" },
vintage: { icon: (props: IconProps) => <Icons.OrdinalsVintage {...props} />, name: "Vintage" },
};
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from "react";
import RowLayout from "LLD/features/Collectibles/Ordinals/components/RareSats/RowLayout";
import IconContainer from "LLD/features/Collectibles/components/Collection/TableRow/IconContainer";
import TokenTitle from "LLD/features/Collectibles/components/Collection/TableRow/TokenTitle";
import { RareSat } from "LLD/features/Collectibles/types/Ordinals";
import { Text, Flex } from "@ledgerhq/react-ui";

const Item = ({
icons,
name,
year,
count,
utxo_size,
isMultipleRow,
}: RareSat & { isMultipleRow: boolean }) => {
const firstColumn = (
<Flex columnGap={2}>
{icons && <IconContainer icons={Object.values(icons)} />}
<TokenTitle tokenName={[name]} complementaryData={count} isLoading={false} />
</Flex>
);
const secondColumn = (
<Text variant="bodyLineHeight" fontSize={12} color="neutral.c70">
{year}
</Text>
);
const thirdColumn = (
<Text variant="bodyLineHeight" fontSize={12} color="neutral.c70">
{utxo_size}
</Text>
);

return (
<RowLayout
isMultipleRow={isMultipleRow}
firstColumnElement={firstColumn}
secondColumnElement={secondColumn}
thirdColumnElement={thirdColumn}
/>
);
};

export default Item;
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React from "react";
import { Flex } from "@ledgerhq/react-ui";
import styled from "styled-components";

type Props = {
firstColumnElement: JSX.Element;
secondColumnElement: JSX.Element;
thirdColumnElement?: JSX.Element;
bgColor?: string;
isMultipleRow?: boolean;
};

const Container = styled(Flex)`
&:first-child {
border-top: 1px solid ${p => p.theme.colors.palette.text.shade10};
padding-top: 15px;
}
&:last-child {
padding-bottom: 15px;
}
`;

const RowLayout: React.FC<Props> = ({
firstColumnElement,
secondColumnElement,
thirdColumnElement,
bgColor,
isMultipleRow,
}) => {
const verticalPadding = isMultipleRow ? 1 : 3;
return (
<Container
py={verticalPadding}
px={4}
flexDirection="row"
maxHeight={64}
alignItems="center"
bg={bgColor}
>
<Flex flex={1}>{firstColumnElement}</Flex>
<Flex flex={1} flexDirection="row" columnGap={20}>
<Flex flex={1} justifyContent="flex-end">
{secondColumnElement}
</Flex>
<Flex flex={0.2} justifyContent="flex-end">
{thirdColumnElement}
</Flex>
</Flex>
</Container>
);
};

export default RowLayout;
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from "react";
import { Text } from "@ledgerhq/react-ui";
import { useTranslation } from "react-i18next";
import RowLayout from "./RowLayout";

export const TableHeader = () => {
const { t } = useTranslation();

const Column = (translationKey: string) => (
<Text variant="bodyLineHeight" fontSize={12} color="neutral.c70">
{t(translationKey)}
</Text>
);

const firstColumn = Column("ordinals.rareSats.table.type");
const secondColumn = Column("ordinals.rareSats.table.year");
const thirdColumn = Column("ordinals.rareSats.table.utxo");

return (
<RowLayout
firstColumnElement={firstColumn}
secondColumnElement={secondColumn}
thirdColumnElement={thirdColumn}
bgColor="opacityDefault.c05"
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { mappingKeysWithIconAndName } from "../Icons";
import { MappingKeys } from "LLD/features/Collectibles/types/Ordinals";
import { IconProps } from "LLD/features/Collectibles/types/Collection";
import { RareSat } from "LLD/features/Collectibles/types/Ordinals";
import {
Satributes,
Subrange,
SatRange,
MockedRareSat,
Sat,
Icons,
} from "LLD/features/Collectibles/types/RareSats";

export const processSatType = (
type: string,
satributes: Satributes,
icons: Icons,
displayNames: string[],
totalCount: number,
) => {
const attribute = satributes[type as MappingKeys];
if (attribute && attribute.count) {
displayNames.push(type);
if (mappingKeysWithIconAndName[type as MappingKeys]) {
icons[type] = mappingKeysWithIconAndName[type as MappingKeys].icon;
}
totalCount = attribute.count;
}
return { displayNames, totalCount };
};

export const processSatTypes = (satTypes: string[], satributes: Satributes) => {
let displayNames: string[] = [];
let totalCount = 0;
const icons: { [key: string]: ({ size, color, style }: IconProps) => JSX.Element } = {};

satTypes.forEach(type => {
const result = processSatType(type, satributes, icons, displayNames, totalCount);
displayNames = result.displayNames;
totalCount = result.totalCount;
});

return { displayNames, totalCount, icons };
};

export const processSubrange = (
subrange: Subrange,
satributes: Satributes,
year: string,
value: number,
) => {
const { sat_types } = subrange;
const { displayNames, totalCount, icons } = processSatTypes(sat_types, satributes);

const name = displayNames
.map(dn => mappingKeysWithIconAndName[dn.toLowerCase().replace(" ", "_") as MappingKeys]?.name)
.filter(Boolean)
.join(" / ");

return {
count: totalCount.toString() + (totalCount === 1 ? " sat" : " sats"),
display_name: displayNames.join(" / "),
year,
utxo_size: value.toString(),
icons,
name,
};
};

export const processSatRanges = (satRanges: SatRange[], satributes: Satributes) => {
return satRanges.flatMap(range => {
const { year, value, subranges } = range;
return subranges.flatMap(subrange => processSubrange(subrange, satributes, year, value));
});
};

export const processRareSat = (sat: Sat) => {
const { extra_metadata } = sat;
const satributes = extra_metadata.utxo_details.satributes as Satributes;
const satRanges = extra_metadata.utxo_details.sat_ranges;
return processSatRanges(satRanges, satributes);
};

export const processRareSats = (rareSats: MockedRareSat[]) => {
return rareSats.flatMap(item => item.nfts.flatMap(processRareSat));
};

export const groupRareSats = (processedRareSats: RareSat[]) => {
return processedRareSats.reduce(
(acc, sat) => {
if (!acc[sat.utxo_size]) {
acc[sat.utxo_size] = [];
}
acc[sat.utxo_size].push(sat);
return acc;
},
{} as Record<string, RareSat[]>,
);
};

export const finalizeRareSats = (groupedRareSats: Record<string, RareSat[]>) => {
return Object.entries(groupedRareSats).map(([utxo_size, sats]) => ({
utxo_size,
sats,
isMultipleRow: sats.length > 1,
}));
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from "react";
import { useRareSatsModel } from "./useRareSatsModel";
import TableContainer from "~/renderer/components/TableContainer";
import TableHeader from "LLD/features/Collectibles/components/Collection/TableHeader";
import Item from "./Item";
import { TableHeaderTitleKey } from "LLD/features/Collectibles/types/Collection";
import { Box, Flex } from "@ledgerhq/react-ui";
import { TableHeader as TableHeaderContainer } from "./TableHeader";

type ViewProps = ReturnType<typeof useRareSatsModel>;

function View({ rareSats }: ViewProps) {
return (
<Box>
<TableContainer id="oridinals-inscriptions">
<TableHeader titleKey={TableHeaderTitleKey.RareSats} />
<TableHeaderContainer />
<Flex flexDirection="column">
{rareSats
? rareSats.map(rareSatGroup => (
<Flex key={rareSatGroup.utxo_size} flexDirection="column">
{rareSatGroup.sats.map(rareSat => (
<Item
key={rareSat.name}
{...rareSat}
isMultipleRow={rareSatGroup.isMultipleRow}
/>
))}
</Flex>
))
: null}
{/** wait for design */}
</Flex>
</TableContainer>
</Box>
);
}

const RareSats = () => {
return <View {...useRareSatsModel({})} />;
};

export default RareSats;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { mockedRareSats } from "LLD/features/Collectibles/__integration__/mockedRareSats";
import { processRareSats, groupRareSats, finalizeRareSats } from "./helpers";

type RareSatsProps = {};

export const useRareSatsModel = (_props: RareSatsProps) => {
const processedRareSats = processRareSats(mockedRareSats);
const groupedRareSats = groupRareSats(processedRareSats);
const finalRareSats = finalizeRareSats(groupedRareSats);

return { rareSats: finalRareSats };
};
Loading

0 comments on commit bd17b01

Please sign in to comment.