Skip to content

Commit

Permalink
Merge pull request #6966 from LedgerHQ/refactor/LIVE-12458
Browse files Browse the repository at this point in the history
refactor(lld): nft detail drawer in newArch
  • Loading branch information
LucasWerey authored Jun 27, 2024
2 parents c1d2bb0 + 493b083 commit 56a9625
Show file tree
Hide file tree
Showing 41 changed files with 1,491 additions and 91 deletions.
5 changes: 5 additions & 0 deletions .changeset/six-mangos-dress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ledger-live-desktop": patch
---

Refactor of the current nft detail drawer. It needed to be more scalable in order to integrate ordinals. Some dead code has been removed as well.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from "react";
import styled from "styled-components";
import { ExternalViewerButton } from "LLD/Collectibles/components/DetailDrawer/components";
import { useTranslation } from "react-i18next";
import Text from "~/renderer/components/Text";
import Button from "~/renderer/components/Button";
import IconSend from "~/renderer/icons/Send";
import { ProtoNFT, Account, NFTMetadata } from "@ledgerhq/types-live";

const NFTActions = styled.div`
display: flex;
flex-direction: row;
margin: 12px 0px;
justify-content: center;
`;

type ActionsProps = {
protoNft: ProtoNFT;
account: Account;
metadata: NFTMetadata;
onNFTSend: () => void;
};

const buttonStyle = {
flex: 1,
justifyContent: "center",
};

const Actions: React.FC<ActionsProps> = ({ protoNft, account, metadata, onNFTSend }) => {
const { t } = useTranslation();

return (
<NFTActions>
<Button style={buttonStyle} mr={4} primary onClick={onNFTSend} center>
<IconSend size={12} />
<Text ml={1} fontSize={3} lineHeight="18px">
{t("NFT.viewer.actions.send")}
</Text>
</Button>
<ExternalViewerButton nft={protoNft} account={account} metadata={metadata as NFTMetadata} />
</NFTActions>
);
};

export default Actions;
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React, { useCallback } from "react";
import { NFTMetadata } from "@ledgerhq/types-live";
import { DetailDrawer } from "LLD/Collectibles/components/DetailDrawer";
import useNftDetailDrawer from "LLD/Collectibles/hooks/useNftDetailDrawer";
import useCollectibles from "LLD/Collectibles/hooks/useCollectibles";
import { NftsDetailDrawerProps } from "LLD/Collectibles/types/Nfts";
import { CollectibleTypeEnum } from "LLD/Collectibles/types/Collectibles";
import Actions from "./Actions";

const NftDetailDrawer = ({ account, tokenId, isOpened, setIsOpened }: NftsDetailDrawerProps) => {
const {
collectionName,
nftName,
tags,
isLoading,
details,
metadata,
contentType,
protoNft,
previewUri,
originalUri,
useFallback,
mediaType,
doNotOpenDrawer,
setUseFallback,
onNFTSend,
} = useNftDetailDrawer(account, tokenId);

const { isPanAndZoomOpen, openCollectiblesPanAndZoom, closeCollectiblesPanAndZoom } =
useCollectibles();

const handleRequestClose = useCallback(() => setIsOpened(false), [setIsOpened]);

if (doNotOpenDrawer) return null;

return (
<DetailDrawer
collectibleType={CollectibleTypeEnum.NFT}
areFieldsLoading={isLoading}
collectibleName={nftName}
contentType={contentType}
collectionName={collectionName}
details={details}
previewUri={previewUri}
originalUri={originalUri}
isPanAndZoomOpen={isPanAndZoomOpen}
mediaType={mediaType}
tags={tags}
useFallback={useFallback}
tokenId={tokenId}
isOpened={isOpened}
closeCollectiblesPanAndZoom={closeCollectiblesPanAndZoom}
handleRequestClose={handleRequestClose}
openCollectiblesPanAndZoom={openCollectiblesPanAndZoom}
setUseFallback={setUseFallback}
>
<DetailDrawer.Actions>
<Actions
protoNft={protoNft}
metadata={metadata as NFTMetadata}
account={account}
onNFTSend={onNFTSend}
/>
</DetailDrawer.Actions>
</DetailDrawer>
);
};

export default NftDetailDrawer;
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import NFTCollectionContextMenu from "~/renderer/components/ContextMenu/NFTColle
import { Skeleton } from "LLD/Collectibles/components";
import styled from "styled-components";
import { IconsLegacy } from "@ledgerhq/react-ui";
import { FieldStatus } from "LLD/Collectibles/types/DetailDrawer";

const Dots = styled.div`
justify-content: flex-end;
Expand Down Expand Up @@ -32,7 +33,7 @@ type Props = {
const CollectionNameComponent: React.FC<Props> = ({ nft, fallback, account, showHideMenu }) => {
const { status, metadata } = useNftCollectionMetadata(nft?.contract, nft?.currencyId);
const { tokenName } = metadata || {};
const loading = status === "loading";
const loading = status === FieldStatus.Loading;
const isComponentReady = account && showHideMenu && nft;

return (
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from "react";
import Text from "~/renderer/components/Text";
import { Skeleton } from "LLD/Collectibles/components/index";
import { HeaderTextProps } from "LLD/Collectibles/types/DetailDrawer";

const CollectionNameComponent: React.FC<HeaderTextProps> = ({ isLoading, text }) => (
<Text ff="Inter|SemiBold" fontSize={5} lineHeight="18px" color="neutral.c70" pb={2}>
<Skeleton show={isLoading} width={100} barHeight={10} minHeight={24}>
{text || "-"}
</Skeleton>
</Text>
);

export const CollectionName = CollectionNameComponent;
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React, { memo } from "react";
import styled from "styled-components";
import { CopyableFieldProps } from "LLD/Collectibles/types/DetailDrawer";
import { GradientHover } from "~/renderer/drawers/OperationDetails/styledComponents";
import CopyWithFeedback from "~/renderer/components/CopyWithFeedback";

const CopyableFieldContainer = styled.div`
display: inline-flex;
position: relative;
max-width: 100%;
${GradientHover} {
display: none;
}
&:hover ${GradientHover} {
display: flex;
& > * {
cursor: pointer;
}
}
`;

const CopyableFieldComponent: React.FC<CopyableFieldProps> = ({ value, children }) => {
return (
<CopyableFieldContainer>
{children}
<GradientHover>
<CopyWithFeedback text={value} />
</GradientHover>
</CopyableFieldContainer>
);
};

CopyableFieldComponent.displayName = "CopyableField";

export const CopyableField = memo<CopyableFieldProps>(CopyableFieldComponent);
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import React, { memo } from "react";
import { Skeleton } from "LLD/Collectibles/components/Skeleton";
import { DetailFieldProps } from "LLD/Collectibles/types/DetailDrawer";
import { CopyableField } from ".";
import Text from "~/renderer/components/Text";
import styled from "styled-components";
import { SplitAddress } from "~/renderer/components/OperationsList/AddressCell";
import { useTranslation } from "react-i18next";

const Separator = styled.div`
width: 100%;
height: 1px;
background-color: ${({ theme }) => theme.colors.palette.text.shade10};
margin: 24px 0px;
`;

const Container = styled.div<{ isHorizontal?: boolean }>`
width: 100%;
display: flex;
flex-direction: ${({ isHorizontal }) => (isHorizontal ? "row" : "column")};
justify-content: space-between;
`;

const ValueBox = styled.div<{ isHorizontal?: boolean }>`
width: 100%;
align-self: end;
display: ${({ isHorizontal }) => (isHorizontal ? "flex" : "block")};
justify-content: ${({ isHorizontal }) => (isHorizontal ? "flex-end" : "flex-start")};
`;

const Pre = styled.span`
white-space: pre-line;
display: block;
unicode-bidi: embed;
line-break: anywhere;
line-height: 15px;
`;

const HashContainer = styled.div`
word-break: break-all;
user-select: text;
width: 100%;
min-width: 100px;
`;

const DetailFieldComponent: React.FC<DetailFieldProps> = ({
label,
value,
hasSeparatorTop,
hasSeparatorBottom,
isHorizontal,
isCopyable,
isLoading,
isHash,
}: DetailFieldProps) => {
const { t } = useTranslation();

if (!value) return null;

return (
<>
{hasSeparatorTop && <Separator />}
<Container isHorizontal={isHorizontal}>
<Text
mb={1}
lineHeight="15.73px"
fontSize={4}
color="palette.text.shade60"
ff="Inter|SemiBold"
>
{t(label)}
</Text>
{value ? (
<Skeleton show={isLoading} width={100} barHeight={10} minHeight={24}>
<ValueBox isHorizontal={isHorizontal}>
<Text
lineHeight="15.73px"
fontSize={4}
color="palette.text.shade100"
ff="Inter|Regular"
>
{isCopyable ? (
<CopyableField value={value}>
{!isHash ? (
value
) : (
<HashContainer>
<SplitAddress value={value} ff="Inter|Regular" />
</HashContainer>
)}
</CopyableField>
) : (
<Pre>{value}</Pre>
)}
</Text>
</ValueBox>
</Skeleton>
) : null}
</Container>
{hasSeparatorBottom && <Separator />}
</>
);
};

export const DetailField = memo<DetailFieldProps>(DetailFieldComponent);
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import React, { useCallback, memo, ReactElement } from "react";
import styled from "styled-components";
import { useHistory } from "react-router-dom";
import { ExternalViewerButtonProps, ItemType } from "LLD/Collectibles/types/DetailDrawer";
import Box from "~/renderer/components/Box";
import Button from "~/renderer/components/Button";
import DropDownSelector, { DropDownItem } from "~/renderer/components/DropDownSelector";
import IconExternal from "~/renderer/icons/ExternalLink";
import useNftLinks from "LLD/Collectibles/hooks/useNftLinks";
import { setDrawer } from "~/renderer/drawers/Provider";
import { Icons } from "@ledgerhq/react-ui";

const Separator = styled.div`
background-color: ${p => p.theme.colors.palette.divider};
height: 1px;
margin-top: 8px;
margin-bottom: 8px;
`;

const Item = memo(styled(DropDownItem)`
width: 100%;
cursor: pointer;
white-space: pre-wrap;
justify-content: space-between;
align-items: center;
display: flex;
`);

type Inner<A> = A extends Array<infer T> ? T : never;
type Item = Inner<ReturnType<typeof useNftLinks>>;

const ExternalViewerButtonComponent: React.FC<ExternalViewerButtonProps> = ({
nft,
account,
metadata,
}: ExternalViewerButtonProps) => {
const history = useHistory();

const onHideCollection = useCallback(() => {
setDrawer();
history.replace(`/account/${account.id}/`);
}, [account.id, history]);

const items = useNftLinks(account, nft, metadata, onHideCollection);

const renderItem = ({ item }: { item: Item }): ReactElement => {
if (item.type === ItemType.Separator) {
return <Separator />;
}

const Icon = item.Icon
? // TODO: the icons have incompatible props (size: string / number)
// eslint-disable-next-line
React.createElement(item.Icon as any, {
size: 16,
})
: null;

return (
<Item id={`external-popout-${item.id}`} horizontal flow={2} onClick={item.callback}>
<Box horizontal>
{Icon && <Box mr={2}>{Icon}</Box>}
{item.label}
</Box>
{item.type === ItemType.ExternalLink && (
<Box ml={4}>
<IconExternal size={16} />
</Box>
)}
</Item>
);
};

return (
<DropDownSelector buttonId="accounts-options-button" items={items} renderItem={renderItem}>
{() => (
<Box horizontal>
<Button
small
primary
flow={1}
style={{
height: 40,
}}
>
<Icons.MoreHorizontal size="S" />
</Button>
</Box>
)}
</DropDownSelector>
);
};

export const ExternalViewerButton = memo<ExternalViewerButtonProps>(ExternalViewerButtonComponent);
Loading

0 comments on commit 56a9625

Please sign in to comment.