embeddable-nfts
Advanced tools
Comparing version
{ | ||
"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. </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. </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
7931
1.81%7396381
-0.03%24
9.09%