embeddable-nfts
Version:
Reusable, embeddable webcomponent for OpenSea assets.
322 lines (299 loc) • 8.94 kB
text/typescript
import { css, customElement, html, LitElement, property } from 'lit-element'
import { classMap } from 'lit-html/directives/class-map'
import { styleMap } from 'lit-html/directives/style-map'
import {
Network,
OpenSeaAsset,
OpenSeaCollection,
OpenSeaFungibleToken,
} from 'opensea-js/lib/types'
/* lit-element classes */
import './info-button'
import { toBaseDenomination } from './utils'
import { PriceType, State } from './types'
('nft-card-front')
export class NftCardFrontTemplate extends LitElement {
({ type: Object }) public asset?: OpenSeaAsset
({ type: Boolean }) public horizontal!: boolean
({ type: Object }) public state!: State
({ type: Boolean }) public flippedCard: boolean = false
static get styles() {
return css`
.card-front.is-flipped {
display: none;
}
.card-front {
position: absolute;
backface-visibility: hidden;
background: #ffffff;
border-radius: 5px;
display: grid;
grid-template-columns: 1fr 2fr;
position: relative;
width: 100%;
height: 100%;
transform: translateY(0);
overflow: hidden;
}
.is-vertical {
grid-template-columns: 100%;
grid-template-rows: 60% 40%;
}
.card-front p {
margin: 0;
}
.asset-image-container {
border-right: 1px solid #e2e6ef;
background-size: cover;
box-sizing: border-box;
}
.asset-image {
background-size: contain;
background-position: 50%;
background-repeat: no-repeat;
height: 100%;
box-sizing: border-box;
}
.is-vertical .asset-image-container {
border-bottom: 1px solid #e2e6ef;
border-right: none;
width: 100%;
}
.asset-details-container {
display: grid;
grid-template-rows: auto;
grid-template-columns: 1fr 1fr;
padding: 20px;
align-items: center;
}
.asset-detail {
display: flex;
}
.asset-detail .asset-detail-type {
height: 35px;
font-size: 12px;
margin-right: 10px;
}
.asset-detail .asset-detail-badge {
width: 54px;
height: 30px;
font-size: 12px;
}
.asset-detail-name {
font-weight: 400;
text-align: left;
}
.asset-detail-price {
align-items: flex-end;
font-size: 18px;
font-weight: 400;
display: flex;
flex-flow: row;
justify-content: flex-end;
line-height: 15px;
text-align: right;
padding: 6px 0;
}
.asset-detail-price img {
margin: 0 4px;
}
.asset-detail-price-current img {
width: 15px;
}
.asset-detail-price-previous {
font-size: 14px;
color: rgb(130, 130, 130);
line-height: 10px;
}
.asset-detail-price-previous img {
width: 1ex;
}
.asset-detail-price .value {
margin-left: 5px;
}
.asset-detail-price .previous-value {
font-size: 14px;
color: #828282;
}
.asset-action-buy {
grid-column-start: 1;
grid-column-end: 3;
}
.asset-action-buy button {
width: 100%;
background: #3291e9;
border-radius: 5px;
height: 35px;
color: white;
font-weight: bold;
letter-spacing: 0.5px;
cursor: pointer;
transition: 200ms;
outline: none;
border-style: none;
text-transform: uppercase;
}
.asset-action-buy button:hover {
background: rgb(21, 61, 98);
}
.asset-link {
text-decoration: none;
color: #222222;
}
`
}
private static getAssetImageStyles(collection: OpenSeaCollection) {
// @ts-ignore - since card_display_style is not serialized by opensea sdk yet
const cardDisplayStyle = collection.displayData.card_display_style
return {
padding: cardDisplayStyle === 'padded' ? '10px' : '',
'background-size': `${cardDisplayStyle}`,
}
}
public getAssetPriceTemplate() {
const sellOrder =
this.asset?.sellOrders && this.asset?.sellOrders.length > 0
? this.asset.sellOrders[0]
: null
const currentPriceTemplate =
sellOrder && sellOrder?.paymentTokenContract
? this.getPriceTemplate(
PriceType.Current,
sellOrder?.paymentTokenContract,
sellOrder?.currentPrice?.toNumber() || 0
)
: null
const prevPriceTemplate = this.asset?.lastSale?.paymentToken
? this.getPriceTemplate(
PriceType.Previous,
this.asset?.lastSale?.paymentToken,
+this.asset?.lastSale?.totalPrice
)
: null
return html`
<a class="asset-link" href="${this.asset?.openseaLink}" target="_blank">
${currentPriceTemplate} ${prevPriceTemplate}
</a>
`
}
/**
* Implement `render` to define a template for your element.
*/
public render() {
if (!this.asset) {
return undefined // If there is no asset then we can't render
}
const { openseaLink, collection, name } = this.asset
const { network } = this.state
return html`
<div class="card-front ${classMap({ 'is-vertical': !this.horizontal, 'is-flipped': this.flippedCard })}">
${this.asset.traits.length > 0
? html`
<info-button
style="position: absolute; top: 5px; right: 5px"
@flip-event="${(e: any) => this.eventHandler(e, 'flip')}"
></info-button>
`
: ''}
${this.getAssetImageTemplate()}
<div class="asset-details-container">
<div class="asset-detail">
<div class="asset-detail-type">
<a
class="asset-link"
href="http://${network === Network.Rinkeby
? 'testnets.'
: ''}opensea.io/assets/${collection.slug}"
target="_blank"
>
<pill-element
.imageUrl=${collection.imageUrl}
.label=${collection.name}
textColor="#828282"
border="1px solid #E2E6EF"
></pill-element>
</a>
</div>
<!-- This badge is optional and must be rendered programmatically -->
<!-- <div class="asset-detail-badge">
<pill-element
label="New"
backgroundColor="#23DC7D"
textColor="#FFFFFF"
></pill-element>
</div> -->
</div>
<div class="spacer"></div>
<div class="asset-detail-name">
<a class="asset-link" href="${openseaLink}" target="_blank"
>${name}</a
>
</div>
${this.getAssetPriceTemplate()}
<div class="asset-action-buy">${this.getButtonTemplate()}</div>
</div>
</div>
`
}
/*
* EventHandler - Dispatch event allowing parent to handle click event
* '_event' isn't used here but it's needed to call the handler
*/
public eventHandler(_event: any, type: string) {
const buttonEvent = new CustomEvent('button-event', {
detail: {
type,
},
})
this.dispatchEvent(buttonEvent)
}
private getPriceTemplate(
priceType: PriceType,
paymentToken: OpenSeaFungibleToken,
price: number
) {
return html`
<div class="asset-detail-price asset-detail-price-${priceType}">
${priceType === PriceType.Previous
? html` <div class="previous-value">Prev. </div> `
: null}
${paymentToken.imageUrl
? html`<img src="${paymentToken.imageUrl}" alt="" ></img>`
: html`
<div class="previous-value">
${paymentToken.symbol === 'ETH' ? 'Ξ' : paymentToken.symbol}
</div>
`}
<div class="asset-detail-price-value">
${toBaseDenomination(price, paymentToken.decimals)}
</div>
</div>
`
}
private getAssetImageTemplate() {
if (!this.asset) {
return undefined
}
const { openseaLink, imageUrl, collection } = this.asset
return html`
<div class="asset-image-container">
<a href="${openseaLink}" target="_blank">
<div
class="asset-image"
style=${styleMap({
'background-image': `url(${imageUrl})`,
...NftCardFrontTemplate.getAssetImageStyles(collection),
})}
></div>
</a>
</div>
`
}
private getButtonTemplate() {
return html`
<button @click="${(e: any) => this.eventHandler(e, 'view')}">
buy this item ❯
</button>
`
}
}