Skip to content
This repository was archived by the owner on Dec 5, 2025. It is now read-only.

Commit d09d325

Browse files
authored
Merge pull request #3340 from kodadot/wen-moon
🌕 Integration with Moonsama v0
2 parents bccb944 + c1a74c8 commit d09d325

File tree

20 files changed

+594
-32
lines changed

20 files changed

+594
-32
lines changed

components/Navbar.vue

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,11 @@
5656
<LazyHistoryBrowser
5757
class="custom-navbar-item navbar-link-background is-hidden-touch"
5858
id="NavHistoryBrowser" />
59-
<b-navbar-dropdown arrowless collapsible id="NavCreate">
59+
<b-navbar-dropdown
60+
arrowless
61+
collapsible
62+
id="NavCreate"
63+
v-show="isCreateVisible">
6064
<template #label>
6165
<span>{{ $t('create') }}</span>
6266
</template>
@@ -134,6 +138,7 @@ import PrefixMixin from '~/utils/mixins/prefixMixin'
134138
import Identity from '@/components/shared/format/Identity.vue'
135139
import Search from '@/components/rmrk/Gallery/Search/SearchBar.vue'
136140
import BasicImage from '@/components/shared/view/BasicImage.vue'
141+
import { createVisible } from '@/utils/config/permision.config'
137142
138143
import { identityStore } from '@/utils/idbStore'
139144
import { get } from 'idb-keyval'
@@ -173,6 +178,10 @@ export default class NavbarMenu extends mixins(PrefixMixin) {
173178
return this.$route.name === 'rmrk-u-id'
174179
}
175180
181+
get isCreateVisible(): boolean {
182+
return createVisible(this.urlPrefix)
183+
}
184+
176185
get isTargetPage(): boolean {
177186
return (
178187
this.inCollectionPage ||
Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
<template>
2+
<BaseGalleryItem
3+
v-if="nft"
4+
:image="meta.image"
5+
:animationUrl="meta.animation_url"
6+
:mimeType="mimeType"
7+
:description="meta.description"
8+
:imageVisible="imageVisible"
9+
:isLoading="isLoading">
10+
<template v-slot:top v-if="message">
11+
<b-message class="message-box" type="is-primary">
12+
<div class="columns">
13+
<div class="column is-four-fifths">
14+
<p class="title is-3 has-text-black">{{ $t('mint.success') }} 🎉</p>
15+
<p class="subtitle is-size-5 subtitle-text">
16+
{{ $t('mint.shareWithFriends', [nft.name]) }} △
17+
</p>
18+
</div>
19+
<div class="column">
20+
<Sharing onlyCopyLink />
21+
</div>
22+
</div>
23+
</b-message>
24+
</template>
25+
<template v-slot:main>
26+
<div class="columns">
27+
<div class="column is-6">
28+
<div class="nft-title">
29+
<Name :nft="nft" :isLoading="isLoading" />
30+
</div>
31+
32+
<div v-if="meta.description" class="block">
33+
<p class="label">{{ $t('legend') }}</p>
34+
<VueMarkdown
35+
v-if="!isLoading"
36+
class="is-size-5"
37+
:source="meta.description.replaceAll('\n', ' \n')" />
38+
<b-skeleton
39+
:count="3"
40+
size="is-large"
41+
:active="isLoading"></b-skeleton>
42+
</div>
43+
44+
<div class="block" v-if="meta.attributes && meta.attributes.length">
45+
<Properties :attributes="meta.attributes" fieldKey="trait_type" />
46+
</div>
47+
</div>
48+
49+
<div class="column is-6" v-if="detailVisible">
50+
<b-skeleton
51+
:count="2"
52+
size="is-large"
53+
:active="isLoading"></b-skeleton>
54+
55+
<div class="columns">
56+
<div class="column">
57+
<div class="nft-title">
58+
<Detail :nft="nft" :isLoading="isLoading" />
59+
</div>
60+
</div>
61+
<div
62+
class="column is-flex is-flex-direction-column is-justify-content-space-between">
63+
<template v-if="detailVisible && !nft.burned">
64+
<div class="card bordered mb-4" aria-id="contentIdForA11y3">
65+
<div class="card-content">
66+
<template v-if="hasPrice">
67+
<div class="label">
68+
{{ $t('price') }}
69+
</div>
70+
<div class="price-block__container">
71+
<div class="price-block__original">
72+
<Money :value="nft.price" inline />
73+
</div>
74+
</div>
75+
</template>
76+
<div class="content pt-4">
77+
<p class="subtitle">
78+
<Auth class="mt-4" evm />
79+
</p>
80+
</div>
81+
<Sharing class="mb-4" />
82+
</div>
83+
</div>
84+
</template>
85+
</div>
86+
</div>
87+
<b-skeleton
88+
:count="2"
89+
size="is-large"
90+
:active="isLoading"></b-skeleton>
91+
</div>
92+
</div>
93+
</template>
94+
</BaseGalleryItem>
95+
</template>
96+
97+
<script lang="ts">
98+
import { Emote, NFT, NFTMetadata } from '@/components/rmrk/service/scheme'
99+
import {
100+
fetchNFTMetadata,
101+
getSanitizer,
102+
sanitizeIpfsUrl,
103+
} from '@/components/rmrk/utils'
104+
import { createTokenId, tokenIdToRoute } from '@/components/unique/utils'
105+
import Orientation from '@/utils/directives/DeviceOrientation'
106+
import { emptyObject } from '@/utils/empty'
107+
import isShareMode from '@/utils/isShareMode'
108+
import SubscribeMixin from '@/utils/mixins/subscribeMixin'
109+
import { notificationTypes, showNotification } from '@/utils/notification'
110+
import { get, set } from 'idb-keyval'
111+
import { Component, mixins, Vue } from 'nuxt-property-decorator'
112+
import { processMedia } from '@/utils/gallery/media'
113+
import AuthMixin from '~/utils/mixins/authMixin'
114+
import PrefixMixin from '~/utils/mixins/prefixMixin'
115+
import resolveQueryPath from '~/utils/queryPathResolver'
116+
import { isEmpty } from '@kodadot1/minimark'
117+
import { royaltyOf } from '@/utils/royalty'
118+
119+
@Component<GalleryItem>({
120+
components: {
121+
Auth: () => import('@/components/shared/Auth.vue'),
122+
Name: () => import('@/components/rmrk/Gallery/Item/Name.vue'),
123+
Sharing: () => import('@/components/rmrk/Gallery/Item/Sharing.vue'),
124+
IndexerGuard: () => import('@/components/shared/wrapper/IndexerGuard.vue'),
125+
VueMarkdown: () => import('vue-markdown-render'),
126+
Detail: () => import('@/components/unique/Gallery/Item/Detail.vue'),
127+
DangerModal: () =>
128+
import('@/components/unique/Gallery/Item/DangerModal.vue'),
129+
Properties: () => import('@/components/unique/Gallery/Item/Properties.vue'),
130+
BaseGalleryItem: () =>
131+
import('@/components/shared/gallery/BaseGalleryItem.vue'),
132+
Money: () => import('@/components/shared/format/Money.vue'),
133+
OfferList: () => import('@/components/bsx/Offer/OfferList.vue'),
134+
},
135+
directives: {
136+
orientation: Orientation,
137+
},
138+
})
139+
export default class GalleryItem extends mixins(
140+
SubscribeMixin,
141+
PrefixMixin,
142+
AuthMixin
143+
) {
144+
private id = ''
145+
private collectionId = ''
146+
private nft: NFT = emptyObject<NFT>()
147+
private imageVisible = true
148+
public isLoading = false
149+
public mimeType = ''
150+
public meta: NFTMetadata = emptyObject<NFTMetadata>()
151+
public emotes: Emote[] = []
152+
public message = ''
153+
154+
public created() {
155+
this.checkId()
156+
this.fetchNftData()
157+
}
158+
159+
get tokenId(): [string, string] {
160+
return [this.collectionId, this.id]
161+
}
162+
163+
private async fetchNftData() {
164+
const query = await resolveQueryPath(this.urlPrefix, 'nftById')
165+
const nft = await this.$apollo.query({
166+
query: query.default,
167+
client: this.urlPrefix,
168+
variables: {
169+
id: createTokenId(this.collectionId, this.id),
170+
},
171+
})
172+
const {
173+
data: { nftEntity },
174+
} = nft
175+
176+
if (!nftEntity) {
177+
this.$consola.warn(`No NFT with ID ${this.id}`)
178+
// showNotification(`No NFT with ID ${this.id}`, notificationTypes.warn)
179+
return
180+
}
181+
182+
this.nft = {
183+
...this.nft,
184+
...nftEntity,
185+
}
186+
187+
if (nftEntity.meta) {
188+
this.meta = {
189+
...this.meta,
190+
...nftEntity.meta,
191+
image: sanitizeIpfsUrl(nftEntity.meta.image || ''),
192+
}
193+
}
194+
195+
this.fetchMetadata()
196+
}
197+
198+
onImageError(e: any) {
199+
this.$consola.warn('Image error', e)
200+
}
201+
202+
public async fetchMetadata() {
203+
if (this.nft['metadata']) {
204+
const cachedMeta = await get(this.nft.metadata)
205+
206+
const meta = !isEmpty(cachedMeta)
207+
? cachedMeta
208+
: await fetchNFTMetadata(
209+
this.nft,
210+
getSanitizer(this.nft.metadata, 'cloudflare', 'permafrost')
211+
)
212+
213+
const imageSanitizer = getSanitizer(meta.image, 'cloudflare')
214+
this.meta = {
215+
...meta,
216+
image: imageSanitizer(meta.image),
217+
animation_url: sanitizeIpfsUrl(
218+
meta.animation_url || meta.image,
219+
'pinata'
220+
),
221+
}
222+
223+
if (!this.nft.name && meta.name) {
224+
Vue.set(this.nft, 'name', meta.name)
225+
}
226+
227+
if (this.meta.animation_url && !this.mimeType) {
228+
const { mimeType, imageVisible } = await processMedia(
229+
this.meta.animation_url
230+
)
231+
this.mimeType = mimeType
232+
this.imageVisible = imageVisible
233+
}
234+
235+
if (!cachedMeta && !isEmpty(meta)) {
236+
set(this.nft.metadata, meta)
237+
}
238+
}
239+
}
240+
241+
public checkId() {
242+
if (this.$route.params.id) {
243+
const { id, item } = tokenIdToRoute(this.$route.params.id)
244+
this.id = item
245+
this.collectionId = id
246+
}
247+
}
248+
249+
public toast(message: string): void {
250+
this.$buefy.toast.open(message)
251+
}
252+
253+
get hasPrice(): boolean {
254+
return Number(this.nft.price) > 0
255+
}
256+
257+
get nftRoyalties(): string {
258+
return this.nft.price && this.nft.royalty
259+
? royaltyOf(this.nft.price, this.nft.royalty)
260+
: ''
261+
}
262+
263+
get nftId() {
264+
const { id } = this.nft
265+
return id
266+
}
267+
268+
get detailVisible() {
269+
return !isShareMode
270+
}
271+
272+
protected handleAction(deleted: boolean) {
273+
if (deleted) {
274+
showNotification('INSTANCE REMOVED', notificationTypes.warn)
275+
}
276+
}
277+
}
278+
</script>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import type { ApiPromise } from '@polkadot/api'
2+
3+
export function getMetadata(api: ApiPromise) {
4+
return api.query.nft.instances
5+
}
6+
7+
export function getMarketplaceData(api: ApiPromise) {
8+
return api.query.nft.instances
9+
}
10+
11+
export function getPrice(api: ApiPromise) {
12+
return api.query.marketplace.prices
13+
}
14+
15+
export function getOffers(api: ApiPromise) {
16+
return api.query.marketplace.offers.keys
17+
}
18+
19+
export function getOwner(api: ApiPromise) {
20+
return api.query.uniques.asset
21+
}
22+
23+
export function hasAllPallets(api: ApiPromise): boolean {
24+
return [api.query.uniques, api.query.nft, api.query.marketplace].every(
25+
(pallet) => pallet
26+
)
27+
}

components/rmrk/Gallery/Gallery.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,7 @@ export default class Gallery extends mixins(
308308
Vue.set(this.nfts, i, {
309309
...this.nfts[i],
310310
...meta,
311+
id: this.nfts[i].id,
311312
image:
312313
imageLinks[
313314
fastExtract(

components/shared/ChainSelect.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
v-for="option in options"
1212
:key="option.value"
1313
:value="option.value"
14+
:disabled="option.value === 'moonriver'"
1415
:class="{ 'is-active': selected === option.value }">
1516
{{ option.text }}
1617
</b-dropdown-item>

nuxt.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ export default defineNuxtConfig({
263263
process.env.SUBSQUID_ENDPOINT || URLS.koda.subsquidv6
264264
),
265265
bsx: toApolloEndpoint(URLS.koda.snekk),
266+
moonsama: toApolloEndpoint(URLS.koda.click),
266267
}, // https://github.com/nuxt-community/apollo-module#options
267268
},
268269

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@
6262
"@google/model-viewer": "^1.12.0",
6363
"@keeex/qrcodejs-kx": "^1.0.2",
6464
"@kodadot1/minimark": "^0.0.1-rc.7",
65-
"@kodadot1/sub-api": "0.0.1-rc.3",
66-
"@kodadot1/vuex-options": "0.0.1-rc.8",
65+
"@kodadot1/sub-api": "^0.0.1-rc.3",
66+
"@kodadot1/vuex-options": "0.0.1-rc.10",
6767
"@nuxtjs/apollo": "^4.0.1-rc.5",
6868
"@nuxtjs/i18n": "^7.2.2",
6969
"@polkadot/extension-dapp": "^0.42.6",

0 commit comments

Comments
 (0)