diff --git a/components/rmrk/Gallery/CollectionItem.vue b/components/rmrk/Gallery/CollectionItem.vue index 87d83d72f7..dc3ed5e002 100644 --- a/components/rmrk/Gallery/CollectionItem.vue +++ b/components/rmrk/Gallery/CollectionItem.vue @@ -106,6 +106,7 @@ :events="eventsOfNftCollection" :openOnDefault="isHistoryOpen" hideCollapse + displayItem @setPriceChartData="setPriceChartData" /> diff --git a/components/rmrk/Gallery/Gallery.vue b/components/rmrk/Gallery/Gallery.vue index 2c96d024c4..697b7d847e 100644 --- a/components/rmrk/Gallery/Gallery.vue +++ b/components/rmrk/Gallery/Gallery.vue @@ -262,7 +262,6 @@ export default class Gallery extends mixins( offset: (page - 1) * this.first, }, }) - console.log(result.data) await this.handleResult(result, loadDirection) this.isFetchingData = false return true diff --git a/components/rmrk/Gallery/GalleryItem.vue b/components/rmrk/Gallery/GalleryItem.vue index c48e396e80..e6603220a7 100644 --- a/components/rmrk/Gallery/GalleryItem.vue +++ b/components/rmrk/Gallery/GalleryItem.vue @@ -144,12 +144,15 @@ import { sanitizeIpfsUrl, getSanitizer } from '../utils' import { processMedia } from '@/utils/gallery/media' import { emptyObject } from '@/utils/empty' import { notificationTypes, showNotification } from '@/utils/notification' +import { generateNftImage } from '@/utils/seoImageGenerator' +import { formatBalanceEmptyOnZero } from '@/utils/format/balance' import isShareMode from '@/utils/isShareMode' import nftById from '@/queries/nftById.graphql' import nftByIdMini from '@/queries/nftByIdMinimal.graphql' import nftListIdsByCollection from '@/queries/nftIdListByCollection.graphql' import nftByIdMinimal from '@/queries/rmrk/subsquid/nftByIdMinimal.graphql' + import { fetchNFTMetadata } from '../utils' import { get, set } from 'idb-keyval' import { exist } from './Search/exist' @@ -159,6 +162,21 @@ import { Debounce } from 'vue-debounce-decorator' import AvailableActions from './AvailableActions.vue' @Component({ + name: 'GalleryItem', + head() { + const metaData = { + mime: this.mimeType, + title: this.pageTitle, + description: this.meta.description, + url: this.$route.path, + image: this.image, + video: this.meta.animation_url, + } + return { + title: this.pageTitle, + meta: [...this.$seoMeta(metaData)], + } + }, components: { Auth: () => import('@/components/shared/Auth.vue'), AvailableActions, @@ -201,6 +219,19 @@ export default class GalleryItem extends mixins(PrefixMixin) { return `${this.$route.params.id}${this.$route.hash || ''}` } + get pageTitle(): string { + return `${this.nft.name || ''}` + } + + get image(): string { + return generateNftImage( + this.nft.name, + formatBalanceEmptyOnZero(this.nft.price as string), + this.meta.image as string, + this.mimeType + ) + } + async fetch() { try { const { diff --git a/components/rmrk/Gallery/History.vue b/components/rmrk/Gallery/History.vue index d9c2e9db34..a570b6ea3c 100644 --- a/components/rmrk/Gallery/History.vue +++ b/components/rmrk/Gallery/History.vue @@ -47,7 +47,7 @@ {{ getEventDisplayName(props.row.Type) }} - {{ props.row.Item.name }} + {{ props.row.Item.name || props.row.Item.id }} ({ name: 'GalleryItemPage', - components: { - GalleryItem, - }, - head() { - const title = this.currentlyViewedItem.title - const metaData = { - title, - type: 'profile', - description: this.currentlyViewedItem.description, - url: this.$route.path, - image: this.image, - } - return { - title, - meta: [ - ...this.$seoMeta(metaData), - { - hid: 'og:author', - property: 'og:author', - content: this.currentlyViewedItem.author, - }, - ], - } - }, + components, }) -export default class GalleryItemPage extends Vue { - get currentlyViewedItem() { - return this.$store.getters['history/getCurrentlyViewedItem'] - } - - get image(): string { - return generateNftImage( - this.currentlyViewedItem.name, - formatBalanceEmptyOnZero(this.currentlyViewedItem.price), - this.currentlyViewedItem.image, - this.currentlyViewedItem.mimeType - ) - } -} +export default class GalleryItemPage extends Vue {} diff --git a/plugins/seoMetaGenerator.ts b/plugins/seoMetaGenerator.ts index bdbb3de01d..55f895c7dd 100644 --- a/plugins/seoMetaGenerator.ts +++ b/plugins/seoMetaGenerator.ts @@ -1,4 +1,5 @@ -import type { MetaInfo } from 'vue-meta' +import { MediaType } from '~/components/rmrk/types' +import { resolveMedia } from '~/utils/gallery/media' declare module 'vue/types/vue' { // this.$seoMeta inside Vue components @@ -7,21 +8,52 @@ declare module 'vue/types/vue' { } } -type MetaProperties = { +interface MetaProperties { type?: string url?: string title?: string description?: string image?: string + author?: string + video?: string + mime?: string +} + +interface MetaTag { + hid?: string + name?: string + property?: string + content?: string } export default function ({ app }, inject): void { - const seoMeta = (meta: MetaProperties): MetaInfo['meta'] => { + const getMetaType = (mediaType: MediaType | string | undefined): string => { + switch (mediaType) { + case MediaType.VIDEO: + return 'video:other' + case MediaType.AUDIO: + return 'music:song' + case MediaType.IMAGE: + case MediaType.JSON: + case MediaType.OBJECT: + default: + return 'website' + } + } + + const seoMeta = (meta: MetaProperties): MetaTag[] => { const baseUrl: string = app.$config.baseUrl const title = 'KodaDot - Kusama NFT Market Explorer' const description = 'Creating Carbonless NFTs on Kusama' const image = `${baseUrl}/kodadot_card_root.png` - return [ + const type = resolveMedia(meta?.mime) + + const seoTags: MetaTag[] = [ + { + hid: 'title', + name: 'title', + content: meta?.title ? `${meta.title} | ${title}` : title, + }, { hid: 'description', name: 'description', @@ -30,7 +62,7 @@ export default function ({ app }, inject): void { { hid: 'og:type', property: 'og:type', - content: meta?.type || 'website', + content: getMetaType(type), }, { hid: 'og:url', @@ -40,7 +72,7 @@ export default function ({ app }, inject): void { { hid: 'og:title', property: 'og:title', - content: meta?.title || title, + content: meta?.title ? `${meta.title} | ${title}` : title, }, { hid: 'og:description', @@ -73,6 +105,66 @@ export default function ({ app }, inject): void { content: meta?.image || image, }, ] + + if (type === MediaType.IMAGE) { + const imageMetaTags: MetaTag[] = [ + { + hid: 'og:image:type', + property: 'og:image:type', + content: meta?.mime, + }, + ] + seoTags.push(...imageMetaTags) + } + + if (type === MediaType.VIDEO) { + const videoMetaTags: MetaTag[] = [ + { + hid: 'og:video', + property: 'og:video', + content: meta?.video, + }, + { + hid: 'og:video:width', + property: 'og:video:width', + content: '1280', + }, + { + hid: 'og:video:height', + property: 'og:video:height', + content: '720', + }, + { + hid: 'og:video:type', + property: 'og:video:type', + content: meta?.mime, + }, + { + hid: 'twitter:player:width', + property: 'twitter:player:width', + content: '1280', + }, + { + hid: 'twitter:player:height', + property: 'twitter:player:height', + content: '720', + }, + { + hid: 'twitter:card', + property: 'twitter:card', + content: 'player', + }, + { + hid: 'twitter:player', + name: 'twitter:player', + content: meta?.video, + }, + ] + seoTags.push(...videoMetaTags) + } + + // only return non null, not undefined, not empty string + return seoTags.filter((tag: MetaTag) => tag && tag.content !== '') } inject('seoMeta', seoMeta) } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cabb3fb331..0fbd3539d1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: 5.4 +lockfileVersion: 5.3 specifiers: '@babel/core': ^7.18.2 @@ -391,7 +391,7 @@ packages: resolution: {integrity: sha512-46MJZZo9y3o4kmhBVc7zW7i8dtR1oIK/sdO5NcfcZRhTGYi+KKJRtHNgsU6c4VUcJmUNV/LQdebD/9Dlv4K+Tg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.17.10 + '@babel/types': 7.18.4 '@jridgewell/gen-mapping': 0.1.1 jsesc: 2.5.2 dev: false @@ -1547,8 +1547,8 @@ packages: '@babel/generator': 7.18.2 '@babel/helper-function-name': 7.17.9 '@babel/helper-split-export-declaration': 7.16.7 - '@babel/parser': 7.12.16 - '@babel/types': 7.12.13 + '@babel/parser': 7.18.4 + '@babel/types': 7.18.4 debug: 4.3.4 globals: 11.12.0 lodash: 4.17.21 @@ -2725,7 +2725,7 @@ packages: '@netlify/functions': 1.0.0 '@nuxt/devalue': 2.0.0 '@nuxt/kit': /@nuxt/kit-edge/3.0.0-27470397.9ebea90_g5rf3icusfse4b63pimitbrmgu - '@nuxt/ui-templates': /@nuxt/ui-templates-edge/0.1.1-27576930.52eaf32 + '@nuxt/ui-templates': /@nuxt/ui-templates-edge/0.1.1-27597075.ecafcef '@rollup/plugin-alias': 3.1.9_rollup@2.75.6 '@rollup/plugin-commonjs': 21.1.0_rollup@2.75.6 '@rollup/plugin-inject': 4.0.4_rollup@2.75.6 @@ -2948,8 +2948,8 @@ packages: - webpack dev: true - /@nuxt/ui-templates-edge/0.1.1-27576930.52eaf32: - resolution: {integrity: sha512-eB+HyQUdSGbHdZ+f/pqvvyjvRtFzI4qxU8WGmnRz8nJ9tSIL+ox/RzTDXUiakjfNay7smtMx6txDAKPyq+FC0Q==} + /@nuxt/ui-templates-edge/0.1.1-27597075.ecafcef: + resolution: {integrity: sha512-3+Bu2PT+L7+ZZ6IPl+ib8G0S3UB32PzMs5+zjuqjAjqO3Zsq0Lnw0CfY7TlQVyWA6BQqd8Eq9AORMAEe94PGGA==} dev: true /@nuxt/utils-edge/2.16.0-27358576.777a4b7f: @@ -17312,7 +17312,7 @@ packages: dev: false /yallist/2.1.2: - resolution: {integrity: sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=} + resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} /yallist/3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} diff --git a/queries/subsquid/general/userStatsByAccount.graphql b/queries/subsquid/general/userStatsByAccount.graphql index 9f4b289eb6..f7a021717b 100644 --- a/queries/subsquid/general/userStatsByAccount.graphql +++ b/queries/subsquid/general/userStatsByAccount.graphql @@ -9,13 +9,17 @@ query userStatsByAccount($account: String!) { } created: nftEntitiesConnection( orderBy: blockNumber_DESC - where: { issuer_eq: $account } + where: { issuer_eq: $account, burned_eq: false } ) { totalCount } collected: nftEntitiesConnection( orderBy: blockNumber_DESC - where: { issuer_not_eq: $account, currentOwner_eq: $account } + where: { + issuer_not_eq: $account + currentOwner_eq: $account + burned_eq: false + } ) { totalCount edges { @@ -37,7 +41,11 @@ query userStatsByAccount($account: String!) { sold: nftEntitiesConnection( orderBy: blockNumber_DESC - where: { issuer_eq: $account, currentOwner_not_eq: $account } + where: { + issuer_eq: $account + currentOwner_not_eq: $account + burned_eq: false + } ) { totalCount } diff --git a/queries/userStatsByAccount.graphql b/queries/userStatsByAccount.graphql index d9065d7c5c..ae622bf40c 100644 --- a/queries/userStatsByAccount.graphql +++ b/queries/userStatsByAccount.graphql @@ -10,7 +10,9 @@ query userStatsByAccount($account: String!) { } } - created: nFTEntities(filter: { issuer: { equalTo: $account } }) { + created: nFTEntities( + filter: { issuer: { equalTo: $account }, burned: { distinctFrom: true } } + ) { totalCount } @@ -18,6 +20,7 @@ query userStatsByAccount($account: String!) { filter: { issuer: { notEqualTo: $account } currentOwner: { equalTo: $account } + burned: { distinctFrom: true } } orderBy: BLOCK_NUMBER_DESC first: 1 @@ -34,6 +37,7 @@ query userStatsByAccount($account: String!) { filter: { issuer: { equalTo: $account } currentOwner: { notEqualTo: $account } + burned: { distinctFrom: true } } ) { totalCount diff --git a/store/index.ts b/store/index.ts index 2c0e35a7fd..9d6784d5ca 100644 --- a/store/index.ts +++ b/store/index.ts @@ -3,6 +3,11 @@ import Connector from '@kodadot1/sub-api' import correctFormat from '@/utils/ss58Format' import { GetterTree, MutationTree, Store } from 'vuex' +type VuexAction = { + type: string + payload: string +} + const apiPlugin = (store: Store): void => { const { getInstance: Api } = Connector @@ -44,7 +49,16 @@ const apiPlugin = (store: Store): void => { console.log('[API] disconnected') }) } +const myPlugin = (store: Store): void => { + const { getInstance: Api } = Connector + store.subscribeAction(({ type, payload }: VuexAction) => { + if (type === 'setApiUrl' && payload) { + store.commit('setLoading', true) + Api().connect(payload) + } + }) +} export const state = () => ({ loading: false, keyringLoaded: false, @@ -84,4 +98,4 @@ export const getters: GetterTree = { }, } -export const plugins = [apiPlugin] +export const plugins = [apiPlugin, myPlugin]