Skip to content

Commit

Permalink
refactor(lld) nfts gallery in newArch
Browse files Browse the repository at this point in the history
  • Loading branch information
LucasWerey committed Apr 30, 2024
1 parent f17a3cb commit c107ebc
Show file tree
Hide file tree
Showing 31 changed files with 359 additions and 395 deletions.
5 changes: 5 additions & 0 deletions .changeset/weak-avocados-serve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ledger-live-desktop": patch
---

Moove and refactor medias from nft gallery to newArch
Empty file.
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React, { memo, useMemo } from "react";
import React, { memo } from "react";
import { useNftCollectionMetadata } from "@ledgerhq/live-nft-react";
import { Account, ProtoNFT } from "@ledgerhq/types-live";
import NFTCollectionContextMenu from "~/renderer/components/ContextMenu/NFTCollectionContextMenu";
import Skeleton from "~/renderer/components/Nft/Skeleton";
import { Skeleton } from "LLD/Collectibles/components";
import styled from "styled-components";
import { IconsLegacy } from "@ledgerhq/react-ui";

const Dots = styled.div`
justify-content: flex-end;
display: flex;
Expand All @@ -20,21 +21,25 @@ const Container = styled.div`
display: flex;
column-gap: 10px;
`;

type Props = {
nft?: ProtoNFT;
fallback?: string;
account?: Account;
showHideMenu?: boolean;
}; // TODO Make me pretty
const CollectionName = ({ nft, fallback, account, showHideMenu }: Props) => {

const CollectionName: React.FC<Props> = ({ nft, fallback, account, showHideMenu }) => {
const { status, metadata } = useNftCollectionMetadata(nft?.contract, nft?.currencyId);
const { tokenName } = metadata || {};
const loading = useMemo(() => status === "loading", [status]);
const loading = status === "loading";
const isComponentReady = account && showHideMenu && nft;

return (
<Skeleton width={80} minHeight={24} barHeight={10} show={loading}>
<Container>
{tokenName || fallback || "-"}
{account && showHideMenu && nft && (
{isComponentReady && (
<NFTCollectionContextMenu
collectionName={tokenName || fallback || "-"}
collectionAddress={nft.contract || ""}
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import React, { useState } from "react";
import { ImageProps } from "LLD/Collectibles/types/Media";
import styled from "styled-components";
import { Skeleton } from "../index";
import Placeholder from "./Placeholder";
/**
* Nb: This image component can be used for small listings, large gallery rendering,
* and even tokens without an image where it will fallback to a generative image
* based on the token metadata and some hue changes.
*
* The text in the fallback image is only visible if we are in `full` mode, since list
* mode is not large enough for the text to be readable.
*/

const Wrapper = styled.div<{
full?: ImageProps["full"];
size?: ImageProps["size"];
loaded: boolean;
square: ImageProps["square"];
maxHeight?: ImageProps["maxHeight"];
objectFit?: ImageProps["objectFit"];
error?: boolean;
}>`
width: ${({ full, size }) => (full ? "100%" : `${size}px`)};
height: ${({ full }) => full && "100%"};
aspect-ratio: ${({ square }) => (square ? "1 / 1" : "initial")};
max-height: ${({ maxHeight }) => maxHeight && `${maxHeight}px`};
border-radius: 4px;
overflow: hidden;
background-size: contain;
display: flex;
align-items: center;
justify-content: center;
& > *:nth-child(1) {
display: ${({ loaded, error }) => (loaded || error ? "none" : "block")};
}
& > img {
display: ${({ loaded, error }) => (loaded || error ? "block" : "none")};
${({ objectFit }) =>
objectFit === "cover"
? `width: 100%;
height: 100%;`
: `max-width: 100%;
max-height: 100%;`}
object-fit: ${p => p.objectFit ?? "cover"};
border-radius: 4px;
user-select: none;
}
`;

const Image: React.FC<ImageProps> = ({
uri,
metadata,
full = false,
size = 32,
tokenId,
maxHeight,
onClick,
square = true,
objectFit = "cover",
setUseFallback,
isFallback,
}) => {
const [loaded, setLoaded] = useState(false);
const [error, setError] = useState(false);
const isImageReady = uri && !error;

return (
<Wrapper
full={full}
size={size}
loaded={loaded}
error={error || !uri}
square={square}
maxHeight={maxHeight}
objectFit={objectFit}
>
<Skeleton full />
{isImageReady ? (
<img
// This prevent a bug where the change of props from isFallback
// would not unbind onError and would not trigger it again in case of error
key={isFallback?.toString()}
onClick={onClick}
onLoad={() => setLoaded(true)}
onError={() => (isFallback ? setError(true) : setUseFallback(true))}
src={uri}
/>
) : (
<Placeholder tokenId={tokenId} metadata={metadata} full={full} />
)}
</Wrapper>
);
};

export default Image;
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
import React from "react";
import React, { memo } from "react";
import { PlaceholderProps } from "LLD/Collectibles/types/Media";
import styled from "styled-components";
import { NFTMetadata } from "@ledgerhq/types-live";
import { centerEllipsis } from "~/renderer/styles/helpers";
import Fallback from "~/renderer/images/nftFallback.jpg";

type Props = {
metadata: NFTMetadata;
tokenId?: string;
full?: boolean;
}; // TODO Figure out if we really need this once we know who creates/processes the media.
const randomHueForTokenId = (tokenId = "") => parseInt(tokenId.substr(-8)) % 360;

function randomHueForTokenId(tokenId = "") {
return parseInt(tokenId.substr(-8)) % 360;
}

const StyledPlaceholder = styled.div<{ tokenId?: string; full?: boolean; metadata?: NFTMetadata }>`
const StyledPlaceholder = styled.div<{
tokenId?: PlaceholderProps["tokenId"];
metadata?: PlaceholderProps["metadata"];
}>`
--hue: ${p => randomHueForTokenId(p.tokenId)};
background-image: url('${Fallback}');
background-image: url("${Fallback}");
background-size: contain;
border-radius: 4px;
width: 100%;
Expand All @@ -27,7 +22,6 @@ const StyledPlaceholder = styled.div<{ tokenId?: string; full?: boolean; metadat
aspect-ratio: 1;
&:after {
display: ${p => (p.full ? "flex" : "none")}
content: "${p => p?.metadata?.nftName || centerEllipsis(p?.tokenId || "-")}";
font-size: 16px;
font-size: 1vw;
Expand All @@ -42,10 +36,11 @@ const StyledPlaceholder = styled.div<{ tokenId?: string; full?: boolean; metadat
height: 100%;
}
`;
class Placeholder extends React.PureComponent<Props> {
render() {
const { metadata, tokenId } = this.props;
return <StyledPlaceholder metadata={metadata} tokenId={tokenId} />;
}
}

const Placeholder = memo<PlaceholderProps>(({ metadata, tokenId }) => (
<StyledPlaceholder metadata={metadata} tokenId={tokenId} />
));

Placeholder.displayName = "Placeholder";

export default Placeholder;
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import React, { useState } from "react";
import { VideoProps } from "LLD/Collectibles/types/Media";
import styled from "styled-components";
import { Skeleton } from "../index";

const Wrapper = styled.div<{
full?: VideoProps["full"];
size?: VideoProps["size"];
loaded: boolean;
square: VideoProps["square"];
maxHeight?: VideoProps["maxHeight"];
objectFit?: VideoProps["objectFit"];
error?: boolean;
}>`
width: ${({ full, size }) => (full ? "100%" : `${size}px`)};
height: ${({ full }) => full && "100%"};
aspect-ratio: ${({ square, error }) => (square || error ? "1 / 1" : "initial")};
max-height: ${({ maxHeight }) => maxHeight && `${maxHeight}px`};
border-radius: 4px;
overflow: hidden;
background-size: contain;
display: flex;
align-items: center;
justify-content: center;
& > *:nth-child(1) {
display: ${({ loaded, error }) => (loaded || error ? "none" : "block")};
}
& > video {
display: ${({ loaded, error }) => (loaded || error ? "block" : "none")};
${({ objectFit }) =>
objectFit === "cover"
? `width: 100%;
height: 100%;`
: `max-width: 100%;
max-height: 100%;`}
object-fit: ${p => p.objectFit ?? "contain"};
border-radius: 4px;
user-select: none;
position: relative;
z-index: 10;
}
`;

const Video: React.FC<VideoProps> = ({
uri,
mediaType,
full = false,
size = 32,
maxHeight,
square = true,
objectFit = "contain",
setUseFallback,
}) => {
const [loaded, setLoaded] = useState(false);

return (
<Wrapper
full={full}
size={size}
loaded={loaded}
square={square}
maxHeight={maxHeight}
objectFit={objectFit}
>
<Skeleton full />
<video
onError={() => {
setUseFallback(true);
}}
onLoadedData={() => setLoaded(true)}
autoPlay
loop
controls
disablePictureInPicture
>
<source src={uri} type={mediaType} />
</video>
</Wrapper>
);
};

export default Video;
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from "react";
import { MediaProps } from "LLD/Collectibles/types/Media";
import useMedia from "LLD/Collectibles/hooks/useMedia";
import Placeholder from "./Placeholder";
import Image from "./Image";
import Video from "./Video";

const Media: React.FC<MediaProps> = props => {
const { useFallback, contentType, uri, mediaType, squareWithDefault, setUseFallback } =
useMedia(props);
const Component = contentType === "video" && !useFallback ? Video : Image;

return uri ? (
<Component
{...props}
uri={uri}
mediaType={mediaType}
square={squareWithDefault}
isFallback={useFallback}
setUseFallback={setUseFallback}
/>
) : (
<Placeholder metadata={props.metadata} tokenId={props.tokenId} />
);
};

export default Media;

0 comments on commit c107ebc

Please sign in to comment.