1 | import { css, customElement, html, LitElement, property } from 'lit-element'
|
2 | import { styleMap } from 'lit-html/directives/style-map'
|
3 | import { provider as Web3Provider } from 'web3-core'
|
4 |
|
5 | import { Network, OpenSeaPort } from 'opensea-js'
|
6 | import { OpenSeaAsset } from 'opensea-js/lib/types'
|
7 |
|
8 |
|
9 | import './pill.ts'
|
10 | import './loader.ts'
|
11 | import './nft-card-front.ts'
|
12 | import './nft-card-back.ts'
|
13 | import { ButtonEvent } from './types'
|
14 | import { getProvider, networkFromString } from './utils'
|
15 |
|
16 | const HORIZONTAL_MIN_CARD_HEIGHT = '200px'
|
17 | const VERT_MIN_CARD_HEIGHT = '670px'
|
18 |
|
19 | const VERT_CARD_HEIGHT = '560px'
|
20 | const VERT_CARD_WIDTH = '380px'
|
21 |
|
22 | const VERT_CARD_WIDTH_MOBILE = '80vw'
|
23 |
|
24 | const HORIZONTAL_CARD_HEIGHT = '210px'
|
25 | const HORIZONTAL_CARD_WIDTH = '80vw'
|
26 | const HORIZONTAL_CARD_MAX_WIDTH = '670px'
|
27 |
|
28 | enum OrientationMode {
|
29 | Auto = 'auto',
|
30 | Manual = 'manual',
|
31 | }
|
32 |
|
33 | const MOBILE_BREAK_POINT = 600
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 | @customElement('nft-card')
|
42 | export class NftCard extends LitElement {
|
43 |
|
44 | @property({ type: Boolean }) public horizontal?: boolean
|
45 | @property({ type: Boolean }) public vertical?: boolean
|
46 | @property({ type: String }) public orientationMode?: OrientationMode
|
47 | @property({ type: String }) public tokenAddress: string = ''
|
48 | @property({ type: String }) public contractAddress: string = ''
|
49 | @property({ type: String }) public tokenId: string = ''
|
50 | @property({ type: String }) public width: string = ''
|
51 | @property({ type: String }) public height: string = ''
|
52 | @property({ type: String }) public minHeight: string = ''
|
53 | @property({ type: String }) public maxWidth: string = ''
|
54 | @property({ type: String }) public network: Network = Network.Main
|
55 | @property({ type: String }) public referrerAddress: string = ''
|
56 |
|
57 | @property({ type: Object }) private asset!: OpenSeaAsset
|
58 | @property({ type: Object }) private traitData: object = {}
|
59 | @property({ type: String }) public flippedCard: boolean = false
|
60 | @property({ type: Object }) private provider: Web3Provider = null
|
61 | @property({ type: Object }) private seaport!: OpenSeaPort
|
62 |
|
63 |
|
64 | @property({ type: Boolean }) private loading = true
|
65 | @property({ type: Boolean }) private error = false
|
66 |
|
67 | static get styles() {
|
68 | return css`
|
69 | :host {
|
70 | all: initial;
|
71 | }
|
72 | p {
|
73 | margin: 0;
|
74 | -webkit-font-smoothing: antialiased;
|
75 | }
|
76 | .card {
|
77 | background-color: white;
|
78 | font-family: 'Roboto', sans-serif;
|
79 | -webkit-font-smoothing: antialiased;
|
80 | font-style: normal;
|
81 | font-weight: normal;
|
82 | line-height: normal;
|
83 | border-radius: 5px;
|
84 | perspective: 1000px;
|
85 | margin: auto;
|
86 | }
|
87 | .card-inner {
|
88 | position: relative;
|
89 | width: 100%;
|
90 | height: 100%;
|
91 | text-align: center;
|
92 | transition: transform 0.6s;
|
93 | transform-style: preserve-3d;
|
94 | box-shadow: 0px 1px 6px rgba(0, 0, 0, 0.25);
|
95 | border-radius: 5px;
|
96 | }
|
97 | .flipped-card .card-inner {
|
98 | transform: rotateY(180deg);
|
99 | }
|
100 | .card .error {
|
101 | height: 100%;
|
102 | display: flex;
|
103 | flex-flow: column;
|
104 | justify-content: center;
|
105 | }
|
106 | .card .error-moji {
|
107 | font-size: 50px;
|
108 | }
|
109 | .card .error-message {
|
110 | font-size: 16px;
|
111 | }
|
112 | `
|
113 | }
|
114 |
|
115 | |
116 |
|
117 |
|
118 |
|
119 |
|
120 | public async connectedCallback() {
|
121 | super.connectedCallback()
|
122 | this.tokenAddress = this.contractAddress
|
123 | ? this.contractAddress
|
124 | : this.tokenAddress
|
125 |
|
126 |
|
127 | if (!this.orientationMode) {
|
128 | this.orientationMode =
|
129 | this.width || this.height || this.horizontal || this.vertical
|
130 | ? OrientationMode.Manual
|
131 | : OrientationMode.Auto
|
132 | }
|
133 |
|
134 | this.horizontal = this.horizontal || !this.vertical
|
135 |
|
136 | let vertCardWidth = VERT_CARD_WIDTH
|
137 | if (
|
138 | this.orientationMode === OrientationMode.Auto &&
|
139 | window.innerWidth < MOBILE_BREAK_POINT
|
140 | ) {
|
141 | vertCardWidth = VERT_CARD_WIDTH_MOBILE
|
142 | this.horizontal = false
|
143 | }
|
144 |
|
145 |
|
146 | this.width = this.width
|
147 | ? this.width
|
148 | : this.horizontal
|
149 | ? HORIZONTAL_CARD_WIDTH
|
150 | : vertCardWidth
|
151 | this.height = this.height
|
152 | ? this.height
|
153 | : this.horizontal
|
154 | ? HORIZONTAL_CARD_HEIGHT
|
155 | : VERT_CARD_HEIGHT
|
156 | this.minHeight = this.horizontal
|
157 | ? HORIZONTAL_MIN_CARD_HEIGHT
|
158 | : VERT_MIN_CARD_HEIGHT
|
159 | this.maxWidth = this.horizontal ? HORIZONTAL_CARD_MAX_WIDTH : ''
|
160 |
|
161 | this.provider = getProvider()
|
162 | const networkName = networkFromString(this.network)
|
163 | this.seaport = new OpenSeaPort(this.provider, { networkName })
|
164 |
|
165 | try {
|
166 | this.asset = await this.seaport.api.getAsset({
|
167 | tokenAddress: this.tokenAddress,
|
168 | tokenId: this.tokenId,
|
169 | })
|
170 |
|
171 | this.traitData = {
|
172 | traits: this.asset.traits,
|
173 | collectionTraits: this.asset.collection.traitStats,
|
174 | }
|
175 | } catch (e) {
|
176 | this.error = true
|
177 |
|
178 | console.error(e)
|
179 | }
|
180 |
|
181 | this.loading = false
|
182 |
|
183 |
|
184 | await this.requestUpdate()
|
185 |
|
186 | }
|
187 |
|
188 | public renderErrorTemplate() {
|
189 | return html`
|
190 | <div class="error">
|
191 | <div class="error-moji">¯\\_(ツ)_/¯</div>
|
192 | <div class="error-message">Problem loading asset.</div>
|
193 | </div>
|
194 | `
|
195 | }
|
196 |
|
197 | public renderLoaderTemplate() {
|
198 | return html` <loader-element></loader-element> `
|
199 | }
|
200 |
|
201 | public renderInnerCardTemplate() {
|
202 | return html`
|
203 | <nft-card-front
|
204 | .horizontal=${this.horizontal}
|
205 | @button-event="${this.eventHandler}"
|
206 | .asset=${this.asset}
|
207 | .state=${{
|
208 | network: this.network,
|
209 | }}
|
210 | .flippedCard="${this.flippedCard}"
|
211 | ></nft-card-front>
|
212 | <nft-card-back
|
213 | .horizontal=${this.horizontal}
|
214 | .traitData=${this.traitData}
|
215 | .openseaLink="${this.asset.openseaLink}"
|
216 | @flip-event="${this.eventHandler}"
|
217 | .flippedCard="${this.flippedCard}"
|
218 | ></nft-card-back>
|
219 | `
|
220 | }
|
221 |
|
222 | public render() {
|
223 | return html`
|
224 | <style>
|
225 | @import url('https://fonts.googleapis.com/css?family=Roboto:100,300,400,500&display=swap');
|
226 | </style>
|
227 | <div
|
228 | class="card ${this.flippedCard ? 'flipped-card' : ''}"
|
229 | style=${styleMap({
|
230 | width: this.width,
|
231 | height: this.height,
|
232 | minHeight: this.minHeight,
|
233 | maxWidth: this.maxWidth,
|
234 | })}
|
235 | >
|
236 | <div class="card-inner">
|
237 | ${this.loading
|
238 | ? this.renderLoaderTemplate()
|
239 | : this.error
|
240 | ? this.renderErrorTemplate()
|
241 | : this.renderInnerCardTemplate()}
|
242 | </div>
|
243 | </div>
|
244 | `
|
245 | }
|
246 |
|
247 | private flipCard() {
|
248 | this.flippedCard = !this.flippedCard
|
249 | }
|
250 |
|
251 | private async eventHandler(event: ButtonEvent) {
|
252 | const { detail } = event
|
253 |
|
254 | switch (detail.type) {
|
255 | case 'view':
|
256 | this.goToOpenSea()
|
257 | break
|
258 | case 'flip':
|
259 | this.flipCard()
|
260 | break
|
261 | }
|
262 | }
|
263 |
|
264 | private goToOpenSea() {
|
265 | const url = this.referrerAddress
|
266 | ? `${this.asset.openseaLink}?ref=${this.referrerAddress}`
|
267 | : this.asset.openseaLink
|
268 | window.open(url, '_blank')
|
269 | }
|
270 | }
|