🚀 Big News: Socket Acquires Coana to Bring Reachability Analysis to Every Appsec Team.Learn more
Socket
Sign inDemoInstall
Socket

embeddable-nfts

Package Overview
Dependencies
Maintainers
1
Versions
11
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

embeddable-nfts - npm Package Compare versions

Comparing version

to
0.4.2

9

package.json
{
"name": "embeddable-nfts",
"version": "0.4.1",
"version": "0.4.2",
"description": "Resuable, embeddable webcomponent for OpenSea assets.",

@@ -10,3 +10,4 @@ "scripts": {

"lint": "tslint --project . 'src/**/*.ts'",
"lint:fix": "tslint --project . 'src/**/*.ts' --fix"
"lint:fix": "tslint --project . 'src/**/*.ts' --fix",
"tslint-check": "tslint-config-prettier-check ./tslint.json"
},

@@ -47,7 +48,9 @@ "repository": {

"husky": "^4.2.3",
"prettier": "^1.19.1",
"prettier": "1.19.1",
"style-loader": "^1.1.3",
"ts-loader": "^6.2.1",
"tslint": "^6.1.0",
"tslint-config-prettier": "^1.18.0",
"tslint-eslint-rules": "^5.4.0",
"tslint-plugin-prettier": "^2.3.0",
"typescript": "^3.8.3",

@@ -54,0 +57,0 @@ "web3-typescript-typings": "^0.10.2",

@@ -27,3 +27,3 @@ # Embeddable NFTs

```
<script src="https://cdn.jsdelivr.net/gh/ProjectOpenSea/embeddable_nfts/dist/nft-card.min.js"></script>
<script src="https://unpkg.com/embeddable-nfts@0.4.1/dist/nft-card.min.js"></script>
```

@@ -30,0 +30,0 @@

import { ButtonType } from './types'
export const NO_WEB3_ERROR: string = 'You need an Ethereum wallet to interact ' +
'with this marketplace. Unlock your wallet, get MetaMask.io or ' +
'Portis on desktop, or get Trust Wallet or Coinbase Wallet on mobile.'
export const NO_WEB3_ERROR: string =
'You need an Ethereum wallet to interact ' +
'with this marketplace. Unlock your wallet, get MetaMask.io or ' +
'Portis on desktop, or get Trust Wallet or Coinbase Wallet on mobile.'
export const BTN_TEXT: { [index: string]: string } = {
[ButtonType.Manage]: 'manage this item ❯',
[ButtonType.Buy]: 'buy this item ❯',
[ButtonType.View]: 'view on openSea ❯',
[ButtonType.SwitchNetwork]: 'switch to ',
[ButtonType.Unlock]: 'buy this item ❯'
[ButtonType.Manage]: 'manage this item ❯',
[ButtonType.Buy]: 'buy this item ❯',
[ButtonType.View]: 'view on openSea ❯',
[ButtonType.SwitchNetwork]: 'switch to ',
[ButtonType.Unlock]: 'buy this item ❯'
}

@@ -5,40 +5,38 @@ import { css, customElement, html, LitElement } from 'lit-element'

export class InfoButtonTemplate extends LitElement {
static get styles() {
return css`
.asset-action-info {
cursor: pointer;
transition: 200ms;
opacity: 0.4;
backface-visibility: hidden;
}
.asset-action-info #info-icon:hover {
opacity: 1;
}
`
}
static get styles() {
return css`
.asset-action-info {
cursor: pointer;
transition: 200ms;
opacity: 0.4;
backface-visibility: hidden;
}
.asset-action-info #info-icon:hover {
opacity: 1;
}
`
}
public render() {
return (html`
<div class="asset-action-info">
<svg
id="info-btn"
@click="${
(_e: any) => this.dispatchEvent(new CustomEvent('flip-event'))
}"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path d="M0 0h24v24H0z" fill="white" />
<path
id="info-icon"
fill="rgb(82, 87, 89)"
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"
/>
</svg>
</div>
`)
}
public render() {
return html`
<div class="asset-action-info">
<svg
id="info-btn"
@click="${(_e: any) =>
this.dispatchEvent(new CustomEvent('flip-event'))}"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path d="M0 0h24v24H0z" fill="transparent" />
<path
id="info-icon"
fill="rgb(82, 87, 89)"
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"
/>
</svg>
</div>
`
}
}

@@ -5,84 +5,85 @@ import { css, customElement, html, LitElement } from 'lit-element'

export class Loader extends LitElement {
static get styles() {
return css`
@keyframes pulse-opacity {
0% {
opacity: 1;
}
16.666% {
opacity: 1;
}
100% {
opacity: 0;
}
}
.Loading {
transform: translate(-50%,-50%) rotate(30deg);
height: 81px;
width: 90px;
position: absolute;
left: 50%;
top: 50%;
}
.Loading .tri.upwards {
border-top: 0;
border-bottom: 27px solid #fff;
}
.Loading .tri:first-child {
left: 15px;
border-bottom-color: #00c9a4;
}
.Loading .tri:nth-child(2) {
left: 30px;
animation-delay: .1s;
border-top-color: #00ffd0;
}
.Loading .tri:nth-child(3) {
left: 45px;
animation-delay: .2s;
border-bottom-color: #00d7d1;
}
.Loading .tri:nth-child(4) {
left: 45px;
top: 27px;
animation-delay: .3s;
border-top-color: #0095be;
}
.Loading .tri:nth-child(5) {
top: 27px;
left: 30px;
animation-delay: .4s;
border-bottom-color: #007296;
}
.Loading .tri:nth-child(6) {
top: 27px;
left: 15px;
animation-delay: .5s;
border-top-color: #1c1f27;
}
.Loading .tri, .Loading .tri.upwards {
border-left: 15px solid transparent;
border-right: 15px solid transparent;
}
.Loading .tri {
position: absolute;
opacity: 0;
animation: pulse-opacity .6s ease-in infinite;
border-top: 27px solid #fff;
border-bottom: 0;
}
`
}
public render() {
return (html`
<div class="Loading">
<div class="tri upwards"></div>
<div class="tri"></div>
<div class="tri upwards"></div>
<div class="tri"></div>
<div class="tri upwards"></div>
<div class="tri"></div>
</div>`
)
}
static get styles() {
return css`
@keyframes pulse-opacity {
0% {
opacity: 1;
}
16.666% {
opacity: 1;
}
100% {
opacity: 0;
}
}
.Loading {
transform: translate(-50%, -50%) rotate(30deg);
height: 81px;
width: 90px;
position: absolute;
left: 50%;
top: 50%;
}
.Loading .tri.upwards {
border-top: 0;
border-bottom: 27px solid #fff;
}
.Loading .tri:first-child {
left: 15px;
border-bottom-color: #00c9a4;
}
.Loading .tri:nth-child(2) {
left: 30px;
animation-delay: 0.1s;
border-top-color: #00ffd0;
}
.Loading .tri:nth-child(3) {
left: 45px;
animation-delay: 0.2s;
border-bottom-color: #00d7d1;
}
.Loading .tri:nth-child(4) {
left: 45px;
top: 27px;
animation-delay: 0.3s;
border-top-color: #0095be;
}
.Loading .tri:nth-child(5) {
top: 27px;
left: 30px;
animation-delay: 0.4s;
border-bottom-color: #007296;
}
.Loading .tri:nth-child(6) {
top: 27px;
left: 15px;
animation-delay: 0.5s;
border-top-color: #1c1f27;
}
.Loading .tri,
.Loading .tri.upwards {
border-left: 15px solid transparent;
border-right: 15px solid transparent;
}
.Loading .tri {
position: absolute;
opacity: 0;
animation: pulse-opacity 0.6s ease-in infinite;
border-top: 27px solid #fff;
border-bottom: 0;
}
`
}
public render() {
return html`
<div class="Loading">
<div class="tri upwards"></div>
<div class="tri"></div>
<div class="tri upwards"></div>
<div class="tri"></div>
<div class="tri upwards"></div>
<div class="tri"></div>
</div>
`
}
}

@@ -1,2 +0,2 @@

import { LitElement, html, customElement, property, css } from 'lit-element'
import { css, customElement, html, LitElement, property } from 'lit-element'
import { styleMap } from 'lit-html/directives/style-map'

@@ -8,6 +8,3 @@ import { classMap } from 'lit-html/directives/class-map'

import {
formatTraitType,
getTraitType
} from './utils'
import { formatTraitType, getTraitType } from './utils'

@@ -20,4 +17,4 @@ const TRAIT_HEADER_HEIGHT = 42

const rankStyle = {
height: RANK_HEIGHT + 'px',
marginBottom: RANK_MARGIN + 'px'
height: RANK_HEIGHT + 'px',
marginBottom: RANK_MARGIN + 'px'
}

@@ -36,3 +33,3 @@

const boostStyle = {
height: BOOST_HEIGHT + 'px',
height: BOOST_HEIGHT + 'px'
}

@@ -56,13 +53,12 @@

export class NftCardBackTemplate extends LitElement {
@property({ type: Object }) public traitData!: TraitData
@property({ type: Object }) public openseaLink?: string
@property({ type: Boolean }) public loading = true
@property({ type: Boolean }) public horizontal!: boolean
@property({ type: Number }) public cardHeight!: number
@property({ type: Number }) public cardInnerHeight?: number
@property({ type: Number }) public cardWidth!: number
@property({type: Object}) public traitData!: TraitData
@property({type: Object}) public openseaLink?: string
@property({type: Boolean}) public loading = true
@property({type: Boolean}) public horizontal!: boolean
@property({type: Number}) public cardHeight!: number
@property({type: Number}) public cardInnerHeight?: number
@property({type: Number}) public cardWidth!: number
@property({ type: Object }) private traits?: Traits
@property({type: Object}) private traits?: Traits
static get styles() {

@@ -124,2 +120,5 @@ return css`

.trait_property {
display: flex;
flex-flow: column;
justify-content: space-between;
background: #edfbff;

@@ -147,3 +146,3 @@ border: 1px solid #2d9cdb;

color: #2d9cdb;
opacity: .8;
opacity: 0.8;
}

@@ -155,3 +154,3 @@ .trait_property .trait_property-value {

margin: 0;
color: rgba(0,0,0,.87);
color: rgba(0, 0, 0, 0.87);
}

@@ -236,4 +235,4 @@ .trait_ranking {

.remaining-traits {
text-transform: none;
font-weight: bold;
text-transform: none;
font-weight: bold;
margin-top: 10px;

@@ -276,7 +275,7 @@ display: block;

super.connectedCallback()
}
}
public getContainerHeight() {
let containerHeight
const traitHeaderHeight = (TRAIT_HEADER_HEIGHT + TRAIT_HEADER_MARGIN_BOTTOM)
const traitHeaderHeight = TRAIT_HEADER_HEIGHT + TRAIT_HEADER_MARGIN_BOTTOM
if (this.horizontal) {

@@ -287,9 +286,12 @@ containerHeight = this.cardHeight - traitHeaderHeight

// 3 trait headers
containerHeight = this.cardInnerHeight ? ( this.cardInnerHeight - (traitHeaderHeight * 3) ) / 3 : 100 // default container height
containerHeight = this.cardInnerHeight
? (this.cardInnerHeight - traitHeaderHeight * 3) / 3
: 100 // default container height
}
return containerHeight
}
public getRenderNumber(traitType: TraitType, numberOfTraits: number) {
const containerHeight = this.getContainerHeight()
const numRender = Math.round(containerHeight / (traitHeight[traitType])) - 1
const numRender = Math.round(containerHeight / traitHeight[traitType]) - 1
const numRemaining = numberOfTraits - numRender

@@ -307,3 +309,6 @@ return {

const {numRender, numRemaining} = this.getRenderNumber(TraitType.Boost, boosts.length)
const { numRender, numRemaining } = this.getRenderNumber(
TraitType.Boost,
boosts.length
)

@@ -329,4 +334,4 @@ return html`

${boosts.slice(0, numRender).map(
({trait_type, value}) => html`
<div class="trait_boost" style=${styleMap(boostStyle)} >
({ trait_type, value }) => html`
<div class="trait_boost" style=${styleMap(boostStyle)}>
<div class="trait_boost-value">

@@ -349,34 +354,38 @@ <p>+${value}</p>

}
const {numRender, numRemaining} = this.getRenderNumber(TraitType.Stat, stats.length)
const { numRender, numRemaining } = this.getRenderNumber(
TraitType.Stat,
stats.length
)
return html`
<div class="trait-header trait-header-stats">
<div class="trait-icon">
<svg
width="15"
height="100%"
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4.66666 11.3333H7.33332V0.666672H4.66666V11.3333ZM0.666656 11.3333H3.33332V6H0.666656V11.3333ZM8.66666 4V11.3333H11.3333V4H8.66666Z"
fill="black"
/>
</svg>
</div>
<p class="attribute-title">Stats</p>
<div class="trait-header trait-header-stats">
<div class="trait-icon">
<svg
width="15"
height="100%"
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4.66666 11.3333H7.33332V0.666672H4.66666V11.3333ZM0.666656 11.3333H3.33332V6H0.666656V11.3333ZM8.66666 4V11.3333H11.3333V4H8.66666Z"
fill="black"
/>
</svg>
</div>
${stats.slice(0, numRender).map(stat =>
<p class="attribute-title">Stats</p>
</div>
${stats.slice(0, numRender).map(
stat =>
html`
<div class="stat" style=${styleMap(statStyle)}>
<div class="stat-value">${stat.value}</div>
<div class="stat-name">
${formatTraitType(stat.trait_type)}
</div>
<div class="stat" style=${styleMap(statStyle)}>
<div class="stat-value">${stat.value}</div>
<div class="stat-name">
${formatTraitType(stat.trait_type)}
</div>
`
)}
${this.viewMoreTemplate(numRemaining)}
`
</div>
`
)}
${this.viewMoreTemplate(numRemaining)}
`
}

@@ -388,3 +397,6 @@

}
const {numRender, numRemaining} = this.getRenderNumber(TraitType.Ranking, rankings.length)
const { numRender, numRemaining } = this.getRenderNumber(
TraitType.Ranking,
rankings.length
)

@@ -409,3 +421,3 @@ return html`

${rankings.slice(0, numRender).map(
({trait_type, value, max}) => html`
({ trait_type, value, max }) => html`
<div class="trait_ranking" style=${styleMap(rankStyle)}>

@@ -421,3 +433,3 @@ <div class="trait_ranking-header">

class="trait_ranking-bar-fill"
style=${styleMap({width: `${(+value / +(max || 1 )) * 100}%`})}
style=${styleMap({ width: `${(+value / +(max || 1)) * 100}%` })}
></div>

@@ -437,14 +449,37 @@ </div>

const {numRender, numRemaining} = this.getRenderNumber(TraitType.Property, props.length)
const { numRender, numRemaining } = this.getRenderNumber(
TraitType.Property,
props.length
)
return html`${props.slice(0, numRender).map(
({trait_type, value }) =>
html`
<div class="trait_property" style="${styleMap(propStyle)}">
<p class="trait_property-type">${formatTraitType(trait_type)}</p>
<p class="trait_property-value">${value}</p>
return html`
<div class="trait-header">
<div class="trait-icon">
<svg
width="18"
height="100%"
viewBox="0 0 12 8"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M0 2.00001H9.33333V0.666672H0V2.00001ZM0 4.66667H9.33333V3.33334H0V4.66667ZM0 7.33334H9.33333V6H0V7.33334ZM10.6667 7.33334H12V6H10.6667V7.33334ZM10.6667 0.666672V2.00001H12V0.666672H10.6667ZM10.6667 4.66667H12V3.33334H10.6667V4.66667Z"
fill="#1C1F27"
/>
</svg>
</div>
`)}
<p class="attribute-title">Properties</p>
</div>
${props.slice(0, numRender).map(
({ trait_type, value }) =>
html`
<div class="trait_property" style="${styleMap(propStyle)}">
<p class="trait_property-type">${formatTraitType(trait_type)}</p>
<p class="trait_property-value">${value}</p>
</div>
`
)}
${this.viewMoreTemplate(numRemaining)}
`
`
}

@@ -455,28 +490,16 @@

<div class="card-back">
<info-button
style="position: absolute; top: 5px; right: 5px"
@flip-event="${(_e: any) => this.dispatchEvent(new CustomEvent('flip-event', { detail: { type: 'flip' } }))}"
<info-button
style="position: absolute; top: 5px; right: 5px"
@flip-event="${(_e: any) =>
this.dispatchEvent(
new CustomEvent('flip-event', { detail: { type: 'flip' } })
)}"
></info-button>
<div
class="card-back-inner ${classMap({'is-vertical': !this.horizontal})}"
class="card-back-inner ${classMap({
'is-vertical': !this.horizontal
})}"
>
<div class="attribute-container attribute-properties">
<div class="trait-header">
<div class="trait-icon">
<svg
width="18"
height="100%"
viewBox="0 0 12 8"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M0 2.00001H9.33333V0.666672H0V2.00001ZM0 4.66667H9.33333V3.33334H0V4.66667ZM0 7.33334H9.33333V6H0V7.33334ZM10.6667 7.33334H12V6H10.6667V7.33334ZM10.6667 0.666672V2.00001H12V0.666672H10.6667ZM10.6667 4.66667H12V3.33334H10.6667V4.66667Z"
fill="#1C1F27"
/>
</svg>
</div>
<p class="attribute-title">Properties</p>
</div>
${this.traits ? this.getPropsTemplate(this.traits.props) : ''}

@@ -486,6 +509,7 @@ </div>

<div class="attribute-container">
${ this.traits ?
this.traits.rankings.length > 0 ? this.getRankingsTemplate(this.traits.rankings) : this.getStatsTemplate(this.traits.stats)
: ''
}
${this.traits
? this.traits.rankings.length > 0
? this.getRankingsTemplate(this.traits.rankings)
: this.getStatsTemplate(this.traits.stats)
: ''}
</div>

@@ -504,3 +528,7 @@ <div class="attribute-container attribute-boosts">

} else {
return html`<a class="remaining-traits" href="${this.openseaLink}" target="_blank">+${numRemaining} more</a>`
return html`
<a class="remaining-traits" href="${this.openseaLink}" target="_blank"
>+${numRemaining} more</a
>
`
}

@@ -516,3 +544,3 @@ }

}
const {traits: assetTraits, collectionTraits} = traitData
const { traits: assetTraits, collectionTraits } = traitData

@@ -526,3 +554,5 @@ for (const trait of assetTraits) {

value: trait.value,
...(type === TraitType.Ranking ? {max: collectionTraits[name].max as unknown as number} : {}),
...(type === TraitType.Ranking
? { max: (collectionTraits[name].max as unknown) as number }
: {}),
trait_type: trait.trait_type

@@ -529,0 +559,0 @@ })

@@ -20,10 +20,10 @@ import { css, customElement, html, LitElement, property } from 'lit-element'

export class NftCardFrontTemplate extends LitElement {
@property({type: Object}) public asset?: OpenSeaAsset
@property({type: Boolean}) public isOwnedByAccount!: boolean
@property({type: String}) public account!: string
@property({type: Boolean}) public horizontal!: boolean
@property({type: Object}) public state!: State
@property({ type: Object }) public asset?: OpenSeaAsset
@property({ type: Boolean }) public isOwnedByAccount!: boolean
@property({ type: String }) public account!: string
@property({ type: Boolean }) public horizontal!: boolean
@property({ type: Object }) public state!: State
static get styles() {
return css`
static get styles() {
return css`
.card-front {

@@ -105,4 +105,5 @@ position: absolute;

.asset-detail-price img {
margin-left: 5px;
width: 15px;
margin-left: 5px;
width: 15px;
align-self: center;
}

@@ -142,49 +143,60 @@ .asset-detail-price .value {

`
}
}
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}`
}
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
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
const prevPriceTemplate = this.asset?.lastSale?.paymentToken
? this.getPriceTemplate(
PriceType.Previous,
this.asset?.lastSale?.paymentToken,
+this.asset?.lastSale?.totalPrice
)
: null
return (html`
<div class="asset-detail-price">
<a class="asset-link" href="${this.asset?.openseaLink}" target="_blank">
${currentPriceTemplate}
${prevPriceTemplate}
</a>
</div>
`)
return html`
<div class="asset-detail-price">
<a class="asset-link" href="${this.asset?.openseaLink}" target="_blank">
${currentPriceTemplate} ${prevPriceTemplate}
</a>
</div>
`
}
/**
* 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
}
/**
* 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, assetContract, name } = this.asset
const { network } = this.state
const {openseaLink, collection, assetContract, name} = this.asset
const {network} = this.state
return html`
<div class="card-front ${classMap({'is-vertical': !this.horizontal})}">
return html`
<div class="card-front ${classMap({ 'is-vertical': !this.horizontal })}">
<info-button
style="position: absolute; top: 5px; left: 5px"
@flip-event="${(e: any) => this.eventHandler(e, 'flip')}"
style="position: absolute; top: 5px; left: 5px"
@flip-event="${(e: any) => this.eventHandler(e, 'flip')}"
></info-button>

@@ -197,3 +209,9 @@

<div class="asset-detail-type">
<a class="asset-link" href="http://${network === Network.Rinkeby ? 'rinkeby.' : ''}opensea.io/assets/${collection.slug}" target="_blank">
<a
class="asset-link"
href="http://${network === Network.Rinkeby
? 'rinkeby.'
: ''}opensea.io/assets/${collection.slug}"
target="_blank"
>
<pill-element

@@ -218,3 +236,5 @@ .imageUrl=${assetContract.imageUrl}

<div class="asset-detail-name">
<a class="asset-link" href="${openseaLink}" target="_blank">${name}</a>
<a class="asset-link" href="${openseaLink}" target="_blank"
>${name}</a
>
</div>

@@ -228,99 +248,113 @@ ${this.getAssetPriceTemplate()}

`
}
}
/*
* 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)
}
/*
* 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`
private getPriceTemplate(
priceType: PriceType,
paymentToken: OpenSeaFungibleToken,
price: number
) {
return html`
<div class="asset-detail-price">
${priceType === PriceType.Previous ? html`<div class="previous-value">Prev.&nbsp;</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 ${priceType}-value">
${toBaseDenomination(price, paymentToken.decimals)}
</div>
${priceType === PriceType.Previous
? html`
<div class="previous-value">Prev.&nbsp;</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 ${priceType}-value">
${toBaseDenomination(price, paymentToken.decimals)}
</div>
</div>
`
}
private getAssetImageTemplate() {
if (!this.asset) {
return undefined
}
private getAssetImageTemplate() {
if (!this.asset) {
return undefined
}
const {openseaLink, imageUrl, collection} = this.asset
return (html`
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>
`)
}
<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')}"
>
private getButtonTemplate() {
return html`
<button @click="${(e: any) => this.eventHandler(e, 'view')}">
${BTN_TEXT[ButtonType.Buy]}
</button>
`
}
}
// @ts-ignore
private _getButtonTemplate() {
// @ts-ignore
private _getButtonTemplate() {
let btnType: ButtonType
let btnType: ButtonType
if (this.state.hasWeb3) {
if (this.state.isUnlocked) {
if (this.state.isMatchingNetwork) {
if (this.state.isOwnedByAccount) {
// The account owns asset
btnType = ButtonType.Manage
} else {
// Asset is for sale and not owned by currently selected account
btnType = ButtonType.Buy
}
} else {
// Network does not match or connected to unsupported network
btnType = ButtonType.SwitchNetwork // "switchNetwork" + this.state.network
}
} else {
// Wallet is locked or access not granted
btnType = ButtonType.Unlock
}
if (this.state.hasWeb3) {
if (this.state.isUnlocked) {
if (this.state.isMatchingNetwork) {
if (this.state.isOwnedByAccount) {
// The account owns asset
btnType = ButtonType.Manage
} else {
// Asset is for sale and not owned by currently selected account
btnType = ButtonType.Buy
}
} else {
// No injected web3 found
btnType = ButtonType.View
// Network does not match or connected to unsupported network
btnType = ButtonType.SwitchNetwork // "switchNetwork" + this.state.network
}
// If we are informing the user to switch networks we need to append the
// network on which the asset resides
const btnText: string = btnType === ButtonType.SwitchNetwork ? BTN_TEXT[btnType] + this.state.network : BTN_TEXT[btnType]
const btnStyle = btnType === ButtonType.SwitchNetwork ? {
} else {
// Wallet is locked or access not granted
btnType = ButtonType.Unlock
}
} else {
// No injected web3 found
btnType = ButtonType.View
}
// If we are informing the user to switch networks we need to append the
// network on which the asset resides
const btnText: string =
btnType === ButtonType.SwitchNetwork
? BTN_TEXT[btnType] + this.state.network
: BTN_TEXT[btnType]
const btnStyle =
btnType === ButtonType.SwitchNetwork
? {
'background-color': 'rgb(183, 183, 183)',
'cursor': 'not-allowed'
} : null
cursor: 'not-allowed'
}
: null
return html`
return html`
<button

@@ -333,3 +367,3 @@ style=${btnStyle ? styleMap(btnStyle) : ''}

`
}
}
}

@@ -33,4 +33,4 @@ import { css, customElement, html, LitElement, property } from 'lit-element'

enum OrientationMode {
Auto = 'auto',
Manual = 'manual'
Auto = 'auto',
Manual = 'manual'
}

@@ -48,32 +48,35 @@

export class NftCard extends LitElement {
/* User configurable properties */
@property({ type: Boolean }) public horizontal: boolean = true
@property({ type: Boolean }) public orientationMode: OrientationMode =
OrientationMode.Auto
@property({ type: String }) public tokenAddress: string = ''
@property({ type: String }) public contractAddress: string = ''
@property({ type: String }) public tokenId: string = ''
@property({ type: String }) public width: string = ''
@property({ type: String }) public height: string = ''
@property({ type: String }) public minHeight: string = ''
@property({ type: String }) public maxWidth: string = ''
@property({ type: String }) public network: Network = Network.Main
/* User configurable properties */
@property({type: Boolean}) public horizontal: boolean = true
@property({type: Boolean}) public orientationMode: OrientationMode = OrientationMode.Auto
@property({type: String}) public tokenAddress: string = ''
@property({type: String}) public contractAddress: string = ''
@property({type: String}) public tokenId: string = ''
@property({type: String}) public width: string = ''
@property({type: String}) public height: string = ''
@property({type: String}) public minHeight: string = ''
@property({type: String}) public maxWidth: string = ''
@property({type: String}) public network: Network = Network.Main
@property({ type: Object }) private asset!: OpenSeaAsset
@property({ type: Object }) private traitData: object = {}
@property({ type: String }) private account: string = ''
@property({ type: String }) private flippedCard: boolean = false
@property({ type: Object }) private provider: Web3.Provider
@property({ type: Object }) private seaport!: OpenSeaPort
@property({type: Object}) private asset!: OpenSeaAsset
@property({type: Object}) private traitData: object = {}
@property({type: String}) private account: string = ''
@property({type: String}) private flippedCard: boolean = false
@property({type: Object}) private provider: Web3.Provider
@property({type: Object}) private seaport!: OpenSeaPort
// Card state variables
@property({ type: Boolean }) private loading = true
@property({ type: Boolean }) private error = false
@property({ type: Boolean }) private isOwnedByAccount = false
@property({ type: Boolean }) private isUnlocked: boolean = true
@property({ type: Boolean }) private hasWeb3: boolean = false
@property({ type: Boolean }) private isMatchingNetwork: boolean = false
// Card state variables
@property({type: Boolean}) private loading = true
@property({type: Boolean}) private error = false
@property({type: Boolean}) private isOwnedByAccount = false
@property({type: Boolean}) private isUnlocked: boolean = true
@property({type: Boolean}) private hasWeb3: boolean = false
@property({type: Boolean}) private isMatchingNetwork: boolean = false
static get styles() {
return css`
static get styles() {
return css`
:host {
all: initial;
}
p {

@@ -89,2 +92,3 @@ margin: 0;

font-weight: normal;
line-height: normal;
border-radius: 5px;

@@ -120,188 +124,212 @@ perspective: 1000px;

`
}
}
/**
* ConnectedCallback - Invoked when a component is added to the document’s DOM.
* Grabs data from the OpenSea SDK and populates data objects to be passed to
* child components.
*/
public async connectedCallback() {
super.connectedCallback()
this.tokenAddress = this.contractAddress ? this.contractAddress : this.tokenAddress
/**
* ConnectedCallback - Invoked when a component is added to the document’s DOM.
* Grabs data from the OpenSea SDK and populates data objects to be passed to
* child components.
*/
public async connectedCallback() {
super.connectedCallback()
this.tokenAddress = this.contractAddress
? this.contractAddress
: this.tokenAddress
let vertCardWidth
if (window.innerWidth < MOBILE_BREAK_POINT && this.orientationMode === OrientationMode.Auto) {
vertCardWidth = VERT_CARD_WIDTH_MOBILE
this.horizontal = false
} else {
vertCardWidth = VERT_CARD_WIDTH
}
let vertCardWidth
if (
window.innerWidth < MOBILE_BREAK_POINT &&
this.orientationMode === OrientationMode.Auto
) {
vertCardWidth = VERT_CARD_WIDTH_MOBILE
this.horizontal = false
} else {
vertCardWidth = VERT_CARD_WIDTH
}
// Set default dimensions
if (!this.width) {
this.width = this.horizontal ? HORIZONTAL_CARD_WIDTH : vertCardWidth
}
if (!this.height) {
this.height = this.horizontal ? HORIZONTAL_CARD_HEIGHT : VERT_CARD_HEIGHT
}
this.minHeight = this.horizontal ? HORIZONTAL_MIN_CARD_HEIGHT : VERT_MIN_CARD_HEIGHT
this.maxWidth = this.horizontal ? HORIZONTAL_CARD_MAX_WIDTH : ''
// Set default dimensions
if (!this.width) {
this.width = this.horizontal ? HORIZONTAL_CARD_WIDTH : vertCardWidth
}
if (!this.height) {
this.height = this.horizontal ? HORIZONTAL_CARD_HEIGHT : VERT_CARD_HEIGHT
}
this.minHeight = this.horizontal
? HORIZONTAL_MIN_CARD_HEIGHT
: VERT_MIN_CARD_HEIGHT
this.maxWidth = this.horizontal ? HORIZONTAL_CARD_MAX_WIDTH : ''
this.hasWeb3 = !!window.web3
this.hasWeb3 = !!window.web3
// Get the web3 provider
this.provider = getProvider()
// Get the web3 provider
this.provider = getProvider()
this.seaport = new OpenSeaPort(this.provider, {networkName: this.network})
this.seaport = new OpenSeaPort(this.provider, { networkName: this.network })
try {
this.asset = await this.seaport.api.getAsset({ tokenAddress: this.tokenAddress, tokenId: this.tokenId })
try {
this.asset = await this.seaport.api.getAsset({
tokenAddress: this.tokenAddress,
tokenId: this.tokenId
})
this.traitData = {
traits: this.asset.traits,
collectionTraits: this.asset.collection.traitStats
}
this.traitData = {
traits: this.asset.traits,
collectionTraits: this.asset.collection.traitStats
}
} catch (e) {
this.error = true
// Probably could not find the asset
console.error(e)
}
} catch (e) {
this.error = true
// Probably could not find the asset
console.error(e)
}
this.loading = false
this.loading = false
this.isMatchingNetwork =
networkFromId(this.provider.networkVersion) === this.network
this.isMatchingNetwork = networkFromId(this.provider.networkVersion) === this.network
// Tell the component to update with new state
await this.requestUpdate()
// Tell the component to update with new state
await this.requestUpdate()
// Watch for the account to change then update state of component
this.provider.on('accountsChanged', (accounts: string[]) => {
this.account = accounts.length > 0 ? accounts[0] : ''
this.isOwnedByAccount =
this.asset.owner.address.toLowerCase() === this.account.toLowerCase()
})
this.provider.on('networkChanged', (networkId: string) => {
const network = networkFromId(networkId)
this.isMatchingNetwork = network === this.network
})
}
// Watch for the account to change then update state of component
this.provider.on('accountsChanged', (accounts: string[]) => {
this.account = accounts.length > 0 ? accounts[0] : ''
this.isOwnedByAccount = this.asset.owner.address.toLowerCase() === this.account.toLowerCase()
})
this.provider.on('networkChanged', (networkId: string) => {
const network = networkFromId(networkId)
this.isMatchingNetwork = network === this.network
})
public async buyAsset() {
if (this.isUnlocked && this.asset.sellOrders) {
const order = this.asset.sellOrders[0]
await this.seaport.fulfillOrder({
order,
accountAddress: this.account
})
}
}
public async buyAsset() {
if (this.isUnlocked && this.asset.sellOrders) {
const order = this.asset.sellOrders[0]
await this.seaport.fulfillOrder({order, accountAddress: this.account})
}
}
public renderErrorTemplate() {
return html`
<div class="error">
<div class="error-moji">¯\\_(ツ)_/¯</div>
<div class="error-message">Problem loading asset.</div>
</div>
`
}
public renderErrorTemplate() {
return html`
<div class="error">
<div class="error-moji">¯\\_(ツ)_/¯</div>
<div class="error-message">Problem loading asset.</div>
</div>`
}
public renderLoaderTemplate() {
return html`
<loader-element></loader-element>
`
}
public renderLoaderTemplate() {
return html`<loader-element></loader-element>`
}
public renderInnerCardTemplate() {
return html`
<nft-card-front
.horizontal=${this.horizontal}
@button-event="${this.eventHandler}"
.asset=${this.asset}
.state=${{
isOwnedByAccount: this.isOwnedByAccount,
isMatchingNetwork: this.isMatchingNetwork,
isUnlocked: this.isUnlocked,
hasWeb3: this.hasWeb3,
network: this.network
}}
.account=${this.account}
></nft-card-front>
<nft-card-back
.horizontal=${this.horizontal}
.traitData=${this.traitData}
.openseaLink="${this.asset.openseaLink}"
@flip-event="${this.eventHandler}"
></nft-card-back>
`
}
public renderInnerCardTemplate() {
return html`
<nft-card-front
.horizontal=${this.horizontal}
@button-event="${this.eventHandler}"
.asset=${this.asset}
.state=${({
isOwnedByAccount: this.isOwnedByAccount,
isMatchingNetwork: this.isMatchingNetwork,
isUnlocked: this.isUnlocked,
hasWeb3: this.hasWeb3,
network: this.network
})}
.account=${this.account}
></nft-card-front>
<nft-card-back
.horizontal=${this.horizontal}
.traitData=${this.traitData}
.openseaLink="${this.asset.openseaLink}"
@flip-event="${this.eventHandler}"
></nft-card-back>
`
}
public render() {
return html`
<style>
@import url('https://fonts.googleapis.com/css?family=Roboto:100,300,400,500&display=swap');
</style>
<div
class="card ${this.flippedCard ? 'flipped-card' : ''}"
style=${styleMap({
width: this.width,
height: this.height,
minHeight: this.minHeight,
maxWidth: this.maxWidth
})}
>
<div class="card-inner">
${this.loading
? this.renderLoaderTemplate()
: this.error
? this.renderErrorTemplate()
: this.renderInnerCardTemplate()}
</div>
</div>
`
}
public render() {
return html`
<style>
@import url('https://fonts.googleapis.com/css?family=Roboto:100,300,400,500&display=swap');
</style>
<div
class="card ${this.flippedCard ? 'flipped-card' : ''}"
style=${styleMap({width: this.width, height: this.height, minHeight: this.minHeight, maxWidth: this.maxWidth})}
>
private flipCard() {
this.flippedCard = !this.flippedCard
}
<div class="card-inner">
${this.loading ? this.renderLoaderTemplate() :
this.error ? this.renderErrorTemplate() :
this.renderInnerCardTemplate()
}
</div>
`
}
private async eventHandler(event: ButtonEvent) {
const { detail } = event
private flipCard() {
this.flippedCard = !this.flippedCard
switch (detail.type) {
case 'view':
case 'manage':
this.goToOpenSea()
break
case 'unlock':
await this.connectWallet()
break
case 'buy':
await this.buyAsset()
break
case 'flip':
this.flipCard()
break
}
}
private async eventHandler(event: ButtonEvent) {
const { detail } = event
private goToOpenSea() {
window.open(this.asset.openseaLink, '_blank')
}
switch (detail.type) {
case 'view':
case 'manage':
this.goToOpenSea()
break
case 'unlock':
await this.connectWallet()
break
case 'buy':
await this.buyAsset()
break
case 'flip':
this.flipCard()
break
}
}
/**
* async connectWallet - Initializes connection to the injected web3 account
* Pre-Conditions: this.provider has been defined - this method should only
* be called if web3 is available. If web3 is available then this.provider
* must be defined
*/
private async connectWallet() {
if (window.web3) {
// If it is modern dapp then show prompt requesting access
if (window.ethereum) {
const ACCESS_DENIED = 4001
await window.ethereum.enable().catch((error: { code: number }) => {
if (error.code === ACCESS_DENIED) {
this.isUnlocked = false
}
})
}
private goToOpenSea() {
window.open(this.asset.openseaLink, '_blank')
if (this.provider.selectedAddress) {
this.account = this.provider.selectedAddress
this.isOwnedByAccount =
this.asset.owner.address.toLowerCase() === this.account.toLowerCase()
}
} else {
this.isUnlocked = false
alert(NO_WEB3_ERROR)
throw new Error(NO_WEB3_ERROR)
}
/**
* async connectWallet - Initializes connection to the injected web3 account
* Pre-Conditions: this.provider has been defined - this method should only
* be called if web3 is available. If web3 is available then this.provider
* must be defined
*/
private async connectWallet() {
if (window.web3) {
// If it is modern dapp then show prompt requesting access
if (window.ethereum) {
const ACCESS_DENIED = 4001
await window.ethereum.enable().catch((error: {code: number}) => {
if (error.code === ACCESS_DENIED) {
this.isUnlocked = false
}
})
}
if (this.provider.selectedAddress) {
this.account = this.provider.selectedAddress
this.isOwnedByAccount = this.asset.owner.address.toLowerCase() === this.account.toLowerCase()
}
} else {
this.isUnlocked = false
alert(NO_WEB3_ERROR)
throw new Error(NO_WEB3_ERROR)
}
}
}
}

@@ -10,8 +10,8 @@ import { LitElement, html, customElement, property, css } from 'lit-element'

*/
@property({type: String}) public imageUrl = ''
@property({type: String}) public label = ''
@property({type: String}) public backgroundColor = ''
@property({type: String}) public textColor = ''
@property({type: String}) public border = 'none'
@property({type: Object}) public customStyles = {
@property({ type: String }) public imageUrl = ''
@property({ type: String }) public label = ''
@property({ type: String }) public backgroundColor = ''
@property({ type: String }) public textColor = ''
@property({ type: String }) public border = 'none'
@property({ type: Object }) public customStyles = {
backgroundColor: this.backgroundColor,

@@ -73,8 +73,12 @@ color: this.textColor,

style=${styleMap({
backgroundColor: this.backgroundColor,
color: this.textColor,
border: this.border
})}
backgroundColor: this.backgroundColor,
color: this.textColor,
border: this.border
})}
>
${this.imageUrl ? html`<img src="${this.imageUrl}" alt=""/>` : ''}
${this.imageUrl
? html`
<img src="${this.imageUrl}" alt="" />
`
: ''}
<p>${this.label}</p>

@@ -81,0 +85,0 @@ </div>

@@ -7,62 +7,62 @@ import { Network, OpenSeaTraitStats } from 'opensea-js/lib/types'

export enum TraitType {
Property = 'prop',
Stat = 'stat',
Ranking = 'ranking',
Boost = 'boost',
Property = 'prop',
Stat = 'stat',
Ranking = 'ranking',
Boost = 'boost'
}
export interface Traits {
[index: string]: Trait[]
props: Trait[]
stats: Trait[]
rankings: Trait[]
boosts: Trait[]
[index: string]: Trait[]
props: Trait[]
stats: Trait[]
rankings: Trait[]
boosts: Trait[]
}
export interface Trait {
value: string | number
max?: string | number
display_type?: string
trait_type: string
value: string | number
max?: string | number
display_type?: string
trait_type: string
}
export interface TraitData {
traits: Trait[]
collectionTraits: CollectionTraits
traits: Trait[]
collectionTraits: CollectionTraits
}
export interface CollectionTraits {
[index: string]: OpenSeaTraitStats
[index: string]: OpenSeaTraitStats
}
export enum ButtonType {
Manage = 'manage',
Buy = 'buy',
View = 'view',
SwitchNetwork = 'switchNetwork',
Unlock = 'unlock'
Manage = 'manage',
Buy = 'buy',
View = 'view',
SwitchNetwork = 'switchNetwork',
Unlock = 'unlock'
}
export enum PriceType {
Current = 'current',
Previous = 'previous'
Current = 'current',
Previous = 'previous'
}
export interface State {
isOwnedByAccount: boolean
isMatchingNetwork: boolean
isUnlocked: boolean
hasWeb3: boolean
network: Network
isOwnedByAccount: boolean
isMatchingNetwork: boolean
isUnlocked: boolean
hasWeb3: boolean
network: Network
}
export interface CustomWindow extends Window {
ethereum: Web3.Provider
web3: Web3
ethereum: Web3.Provider
web3: Web3
}
export interface ButtonEvent {
detail: {
type: string
}
detail: {
type: string
}
}

@@ -9,11 +9,11 @@ import { CollectionTraits, Trait, TraitType, CustomWindow } from './types'

export const formatTraitType = (traitType: string) =>
traitType.replace(/_/g, ' ')
traitType.replace(/_/g, ' ')
const isBoost = (trait: Trait) =>
trait.display_type && trait.display_type.includes('boost')
trait.display_type && trait.display_type.includes('boost')
const isRanking = (trait: Trait, collectionTraits: CollectionTraits) =>
trait.display_type === null &&
trait.trait_type in collectionTraits &&
'max' in collectionTraits[trait.trait_type]
trait.display_type === null &&
trait.trait_type in collectionTraits &&
'max' in collectionTraits[trait.trait_type]

@@ -39,39 +39,47 @@ /**

const isProperty = (trait: Trait, collectionTraits: CollectionTraits) =>
trait.display_type === null &&
(trait.display_type === null &&
trait.trait_type in collectionTraits &&
!('max' in collectionTraits[trait.trait_type]) ||
!(trait.trait_type in collectionTraits)
!('max' in collectionTraits[trait.trait_type])) ||
!(trait.trait_type in collectionTraits)
export const toBaseDenomination = (value: number, decimals: number) =>
+value.toFixed() / Math.pow(10, decimals)
+value.toFixed() / Math.pow(10, decimals)
export const getTraitType = (trait: Trait, collectionTraits: CollectionTraits) => {
if (isProperty(trait, collectionTraits)) {
return TraitType.Property
}
if (isRanking(trait, collectionTraits)) {
return TraitType.Ranking
}
if (isStat(trait)) {
return TraitType.Stat
}
if (isBoost(trait)) {
return TraitType.Boost
}
return null // Default return statement
export const getTraitType = (
trait: Trait,
collectionTraits: CollectionTraits
) => {
if (isProperty(trait, collectionTraits)) {
return TraitType.Property
}
if (isRanking(trait, collectionTraits)) {
return TraitType.Ranking
}
if (isStat(trait)) {
return TraitType.Stat
}
if (isBoost(trait)) {
return TraitType.Boost
}
return null // Default return statement
}
export const getProvider = () =>
window.ethereum ? window.ethereum : window.web3
? window.web3.currentProvider : new Web3.providers.HttpProvider('https://mainnet.infura.io')
window.ethereum
? window.ethereum
: window.web3
? window.web3.currentProvider
: new Web3.providers.HttpProvider('https://mainnet.infura.io')
// Given the network version this method returns the network name
// Since only Main & Rinkeby are supported we ignore the other networks
export const networkFromId = (id: string) => {
switch (id) {
case '1': return Network.Main
case '4': return Network.Rinkeby
default: return null
}
switch (id) {
case '1':
return Network.Main
case '4':
return Network.Rinkeby
default:
return null
}
}
{
"extends": ["tslint:latest", "tslint-eslint-rules"],
"extends": ["tslint:latest", "tslint-eslint-rules", "tslint-plugin-prettier", "tslint-config-prettier"],
"rules": {
"prettier": true,
"adjacent-overload-signatures": true,

@@ -5,0 +6,0 @@ "arrow-parens": [true, "ban-single-arg-parens"],

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet