From bed063a0f6afe22be8bfdb35c429234a7c8168ef Mon Sep 17 00:00:00 2001 From: Preschian Febryantara Date: Tue, 21 Nov 2023 16:48:46 +0700 Subject: [PATCH 01/10] feat: unify metadata on gallery detail page --- components/gallery/GalleryItem.vue | 13 +++++-- components/gallery/GalleryItemDescription.vue | 25 +++---------- components/gallery/useGalleryItem.ts | 36 +++++-------------- components/rmrk/service/scheme.ts | 1 + composables/useNft.ts | 29 +++++++++++---- utils/ipfs.ts | 2 +- 6 files changed, 49 insertions(+), 57 deletions(-) diff --git a/components/gallery/GalleryItem.vue b/components/gallery/GalleryItem.vue index fe6cb4fd95..ea90daa566 100644 --- a/components/gallery/GalleryItem.vue +++ b/components/gallery/GalleryItem.vue @@ -58,7 +58,7 @@ class="gallery-item-media is-relative" :src="nftImage" :animation-src="nftAnimation" - :mime-type="nftMimeType" + :mime-type="nftAnimationMimeType || nftMimeType" :title="nftMetadata?.name" is-detail :is-lewd="galleryDescriptionRef?.isLewd" @@ -201,8 +201,15 @@ const galleryDescriptionRef = ref<{ isLewd: boolean } | null>(null) const preferencesStore = usePreferencesStore() const galleryItem = useGalleryItem() -const { nft, nftMetadata, nftImage, nftAnimation, nftMimeType, nftResources } = - galleryItem +const { + nft, + nftMetadata, + nftImage, + nftAnimation, + nftAnimationMimeType, + nftMimeType, + nftResources, +} = galleryItem const collection = computed(() => nft.value?.collection) const triggerBuySuccess = computed(() => preferencesStore.triggerBuySuccess) diff --git a/components/gallery/GalleryItemDescription.vue b/components/gallery/GalleryItemDescription.vue index e75b15a479..96e22253e7 100644 --- a/components/gallery/GalleryItemDescription.vue +++ b/components/gallery/GalleryItemDescription.vue @@ -149,7 +149,7 @@ class="has-text-link" target="_blank" rel="nofollow noopener noreferrer"> - {{ animationMediaMimeType }} + {{ nftAnimationMimeType }} @@ -171,7 +171,6 @@ diff --git a/components/gallery/useGalleryItem.ts b/components/gallery/useGalleryItem.ts index ecc85273f1..4429a0b626 100644 --- a/components/gallery/useGalleryItem.ts +++ b/components/gallery/useGalleryItem.ts @@ -1,5 +1,4 @@ import { sanitizeIpfsUrl } from '@/utils/ipfs' -import { getMimeType } from '@/utils/gallery/media' import { useHistoryStore } from '@/stores/history' import { NftResources, getNftMetadata } from '@/composables/useNft' import useSubscriptionGraphql from '@/composables/useSubscriptionGraphql' @@ -16,37 +15,18 @@ export interface GalleryItem { nftMimeType: Ref nftMetadata: Ref nftAnimation: Ref + nftAnimationMimeType: Ref nftImage: Ref nftResources: Ref } -const whichMimeType = async (data) => { - if (data?.type) { - return data?.type - } - if (data?.animationUrl) { - return await getMimeType(sanitizeIpfsUrl(data.animationUrl)) - } - if (data?.image || data?.mediaUri) { - return await getMimeType(sanitizeIpfsUrl(data?.image || data?.mediaUri)) - } - - return '' -} - -const whichAsset = (data) => { - return { - animation_url: sanitizeIpfsUrl(data.animationUrl || ''), - image: sanitizeIpfsUrl(data.image || data.mediaUri || '', 'image'), - } -} - export const useGalleryItem = (nftId?: string): GalleryItem => { const { $consola } = useNuxtApp() const historyStore = useHistoryStore() const nft = ref() const nftImage = ref('') const nftAnimation = ref('') + const nftAnimationMimeType = ref('') const nftMimeType = ref('') const nftMetadata = ref() const nftResources = ref() @@ -113,13 +93,14 @@ export const useGalleryItem = (nftId?: string): GalleryItem => { } }) - nftMetadata.value = await getNftMetadata(nftEntity, urlPrefix.value) - nftMimeType.value = await whichMimeType(nftMetadata.value) + const metadata = await getNftMetadata(nftEntity, urlPrefix.value, true) + nftMetadata.value = metadata nftResources.value = resources - const asset = whichAsset(nftMetadata.value) - nftImage.value = asset.image - nftAnimation.value = asset.animation_url + nftImage.value = metadata?.image || '' + nftMimeType.value = metadata?.imageMimeType || '' + nftAnimationMimeType.value = metadata.animationUrlMimeType || '' + nftAnimation.value = metadata?.animationUrl || '' historyStore.addHistoryItem({ id: nft.value.id, @@ -139,6 +120,7 @@ export const useGalleryItem = (nftId?: string): GalleryItem => { nft, nftImage, nftAnimation, + nftAnimationMimeType, nftMimeType, nftMetadata, nftResources, diff --git a/components/rmrk/service/scheme.ts b/components/rmrk/service/scheme.ts index ac0b919add..ce56b7df25 100644 --- a/components/rmrk/service/scheme.ts +++ b/components/rmrk/service/scheme.ts @@ -77,6 +77,7 @@ export interface NFTMetadata extends Metadata, ItemResources { image_ar?: string properties?: Record unlockable?: boolean + sn?: string } export type CollectionMetadata = Metadata diff --git a/composables/useNft.ts b/composables/useNft.ts index 8d1ebd068c..ff2b0d7b00 100644 --- a/composables/useNft.ts +++ b/composables/useNft.ts @@ -5,6 +5,7 @@ import { processSingleMetadata } from '@/utils/cachingStrategy' import { getMimeType, isAudio as isAudioMimeType } from '@/utils/gallery/media' import unionBy from 'lodash/unionBy' import type { Ref } from 'vue' +import { kodaImage } from '@/utils/config/ipfs' export type NftResources = { id: string @@ -24,8 +25,14 @@ export type ItemResources = { resources?: NftResources[] } +type baseMimeType = { + imageMimeType?: string + animationUrlMimeType?: string +} + export type NFTWithMetadata = NFT & - NFTMetadata & { meta: BaseNFTMeta } & ItemResources + NFTMetadata & { meta: BaseNFTMeta } & ItemResources & + baseMimeType export type MinimalNFT = { id: string @@ -75,7 +82,7 @@ function getAttributes(nft, metadata) { : attr } -function getGeneralMetadata(nft: T) { +function getGeneralMetadata(nft: T) { return { ...nft, name: addSnSuffixName(nft.name || nft.meta.name, nft.sn) || nft.id, @@ -136,7 +143,7 @@ export async function useNftMimeType< } } -async function getRmrk2Resources(nft: T) { +async function getRmrk2Resources(nft: T) { const thumb = nft.resources?.[0]?.thumb const src = nft.resources?.[0]?.src const image = sanitizeIpfsUrl(thumb || src || '') @@ -149,7 +156,7 @@ async function getRmrk2Resources(nft: T) { } } -async function getProcessMetadata(nft: T) { +async function getProcessMetadata(nft: T) { const metadata = await processSingleMetadata(nft.metadata) const image = sanitizeIpfsUrl( metadata.image || metadata.mediaUri || metadata.thumbnailUri || '', @@ -169,10 +176,20 @@ async function getProcessMetadata(nft: T) { } } -export async function getNftMetadata( +export async function getNftMetadata( nft: T, prefix: string, + unify = false, ) { + if (unify) { + const metadataUrl = new URL(kodaImage) + metadataUrl.pathname = '/metadata' + metadataUrl.searchParams.set('url', sanitizeIpfsUrl(nft.meta.id)) + const metadata = await fetch(metadataUrl.toString()) + + return metadata as unknown as NFTWithMetadata + } + // if subsquid already give us the metadata, we don't need to fetch it again if (nft.meta?.image) { return getGeneralMetadata(nft) @@ -186,7 +203,7 @@ export async function getNftMetadata( return await getProcessMetadata(nft) } -export default function useNftMetadata(nft: T) { +export default function useNftMetadata(nft: T) { const item = ref< T & { name: string diff --git a/utils/ipfs.ts b/utils/ipfs.ts index 5c11be73fc..af0b4ac095 100644 --- a/utils/ipfs.ts +++ b/utils/ipfs.ts @@ -91,7 +91,7 @@ export const sanitizeIpfsUrl = ( return '' } - if (!isIpfsUrl(ipfsUrl) && !ipfsUrl.includes(kodaImage)) { + if (!sanitizeIpfsCid(ipfsUrl) && !ipfsUrl.includes(kodaImage)) { const kodaUrl = new URL('/type/url', kodaImage) kodaUrl.searchParams.set('endpoint', ipfsUrl) From a77e6c93575d5896e549ec5fd8c58e15c855a2ab Mon Sep 17 00:00:00 2001 From: Preschian Febryantara Date: Tue, 21 Nov 2023 17:27:21 +0700 Subject: [PATCH 02/10] refactor: refactor: update useGalleryItem.ts --- components/gallery/useGalleryItem.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/gallery/useGalleryItem.ts b/components/gallery/useGalleryItem.ts index 4429a0b626..1bf966b554 100644 --- a/components/gallery/useGalleryItem.ts +++ b/components/gallery/useGalleryItem.ts @@ -97,10 +97,10 @@ export const useGalleryItem = (nftId?: string): GalleryItem => { nftMetadata.value = metadata nftResources.value = resources - nftImage.value = metadata?.image || '' - nftMimeType.value = metadata?.imageMimeType || '' + nftImage.value = metadata.image || '' + nftMimeType.value = metadata.imageMimeType || '' nftAnimationMimeType.value = metadata.animationUrlMimeType || '' - nftAnimation.value = metadata?.animationUrl || '' + nftAnimation.value = metadata.animationUrl || '' historyStore.addHistoryItem({ id: nft.value.id, From 8b7f0130431e71ba077d0f0358736948a92f08ee Mon Sep 17 00:00:00 2001 From: Preschian Febryantara Date: Wed, 20 Dec 2023 15:43:56 +0700 Subject: [PATCH 03/10] fix(GalleryItem.vue): use nft name as fallback if nftMetadata name is not available --- components/gallery/GalleryItem.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/gallery/GalleryItem.vue b/components/gallery/GalleryItem.vue index 849da650f6..4712c2fcbe 100644 --- a/components/gallery/GalleryItem.vue +++ b/components/gallery/GalleryItem.vue @@ -80,7 +80,7 @@

- {{ nftMetadata?.name }} + {{ nft?.name || nftMetadata?.name }} 「🔥」

From 9bd9846b183c5d74fbb51e8378bef7f3781a7a58 Mon Sep 17 00:00:00 2001 From: Preschian Febryantara Date: Wed, 20 Dec 2023 16:18:47 +0700 Subject: [PATCH 04/10] fix(GalleryItemDescription.vue): add optional chaining to nftMetadata.value.meta.attributes access --- components/gallery/GalleryItemDescription.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/gallery/GalleryItemDescription.vue b/components/gallery/GalleryItemDescription.vue index be62370e92..5606bbde99 100644 --- a/components/gallery/GalleryItemDescription.vue +++ b/components/gallery/GalleryItemDescription.vue @@ -265,7 +265,7 @@ const parentNftUrl = computed(() => { const properties = computed(() => { const attributes = (nftMetadata.value?.attributes || - nftMetadata.value?.meta.attributes || + nftMetadata.value?.meta?.attributes || []) as Array<{ trait_type: string; value: string; key?: string }> return attributes.map(({ trait_type, key, value }) => ({ From 29c731e163e605f37975f4c946e4952cd1f0a827 Mon Sep 17 00:00:00 2001 From: Preschian Febryantara Date: Thu, 4 Jan 2024 09:34:57 +0700 Subject: [PATCH 05/10] feat(useGalleryItem.ts): add support for cf-video in useGalleryItem feat(imageWorker.ts): add new service for fetching MP4 videos --- components/gallery/useGalleryItem.ts | 10 ++++++++++ services/imageWorker.ts | 24 ++++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 services/imageWorker.ts diff --git a/components/gallery/useGalleryItem.ts b/components/gallery/useGalleryItem.ts index 1bf966b554..11756e6167 100644 --- a/components/gallery/useGalleryItem.ts +++ b/components/gallery/useGalleryItem.ts @@ -2,6 +2,7 @@ import { sanitizeIpfsUrl } from '@/utils/ipfs' import { useHistoryStore } from '@/stores/history' import { NftResources, getNftMetadata } from '@/composables/useNft' import useSubscriptionGraphql from '@/composables/useSubscriptionGraphql' +import { getMp4 } from '@/services/imageWorker' import type { NFT } from '@/components/rmrk/service/scheme' import type { NFTWithMetadata } from '@/composables/useNft' import type { Ref } from 'vue' @@ -102,6 +103,15 @@ export const useGalleryItem = (nftId?: string): GalleryItem => { nftAnimationMimeType.value = metadata.animationUrlMimeType || '' nftAnimation.value = metadata.animationUrl || '' + // use cf-video + if (nftAnimationMimeType.value.includes('video')) { + const streams = await getMp4(metadata.animationUrl) + + if (streams.uid && streams.video?.default?.percentComplete === 100) { + nftAnimation.value = streams.video.default.url + } + } + historyStore.addHistoryItem({ id: nft.value.id, name: nft.value.name, diff --git a/services/imageWorker.ts b/services/imageWorker.ts new file mode 100644 index 0000000000..3c71509545 --- /dev/null +++ b/services/imageWorker.ts @@ -0,0 +1,24 @@ +type StreamDownload = { + url: string + uid: string + video?: { + default?: { + url: string + percentComplete: number + } + } +} + +export async function getMp4(url): Promise { + const kodaUrl = new URL(url) + const infura = `https://kodadot1.infura-ipfs.io${kodaUrl.pathname}` + + const video = await $fetch('http://localhost:8787/video/download', { + method: 'POST', + body: { + videoUrl: infura, + }, + }) + + return video as StreamDownload +} From 168aac6caba73585ec52d0795126418f713563d8 Mon Sep 17 00:00:00 2001 From: Preschian Febryantara Date: Thu, 4 Jan 2024 10:06:25 +0700 Subject: [PATCH 06/10] feat(GalleryItem.vue): replace nftImage with computed image property refactor(useGalleryItem.ts): rename getMp4 to getCloudflareMp4 and replace video thumbnail refactor(imageWorker.ts): add thumbnail to StreamDownload type and rename getMp4 to getCloudflareMp4 --- components/gallery/GalleryItem.vue | 14 +++++++++++--- components/gallery/useGalleryItem.ts | 7 ++++--- services/imageWorker.ts | 13 +++++++++++-- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/components/gallery/GalleryItem.vue b/components/gallery/GalleryItem.vue index d74e7b45b2..6a345e16f7 100644 --- a/components/gallery/GalleryItem.vue +++ b/components/gallery/GalleryItem.vue @@ -56,10 +56,10 @@

+ :audio-player-cover="image" />
@@ -263,6 +263,14 @@ const onNFTBought = () => { activeTab.value = tabs.activity } +const image = computed(() => { + if (!nftImage.value) { + return sanitizeIpfsUrl(nft.value?.meta?.image) + } + + return nftImage.value +}) + const getMediaSrc = (src: string | undefined) => src && isFullscreen.value ? toOriginalContentUrl(src) : src diff --git a/components/gallery/useGalleryItem.ts b/components/gallery/useGalleryItem.ts index 11756e6167..5f1bb0e3ed 100644 --- a/components/gallery/useGalleryItem.ts +++ b/components/gallery/useGalleryItem.ts @@ -2,7 +2,7 @@ import { sanitizeIpfsUrl } from '@/utils/ipfs' import { useHistoryStore } from '@/stores/history' import { NftResources, getNftMetadata } from '@/composables/useNft' import useSubscriptionGraphql from '@/composables/useSubscriptionGraphql' -import { getMp4 } from '@/services/imageWorker' +import { getCloudflareMp4 } from '@/services/imageWorker' import type { NFT } from '@/components/rmrk/service/scheme' import type { NFTWithMetadata } from '@/composables/useNft' import type { Ref } from 'vue' @@ -103,12 +103,13 @@ export const useGalleryItem = (nftId?: string): GalleryItem => { nftAnimationMimeType.value = metadata.animationUrlMimeType || '' nftAnimation.value = metadata.animationUrl || '' - // use cf-video + // use cf-video & replace the video thumbnail if (nftAnimationMimeType.value.includes('video')) { - const streams = await getMp4(metadata.animationUrl) + const streams = await getCloudflareMp4(metadata.animationUrl) if (streams.uid && streams.video?.default?.percentComplete === 100) { nftAnimation.value = streams.video.default.url + nftImage.value = streams.detail?.thumbnail || '' } } diff --git a/services/imageWorker.ts b/services/imageWorker.ts index 3c71509545..b5ac709520 100644 --- a/services/imageWorker.ts +++ b/services/imageWorker.ts @@ -1,6 +1,11 @@ +import { kodaImage } from '@/utils/config/ipfs' + type StreamDownload = { url: string uid: string + detail?: { + thumbnail: string + } video?: { default?: { url: string @@ -9,11 +14,15 @@ type StreamDownload = { } } -export async function getMp4(url): Promise { +const workerUrl = new URL(kodaImage) + +export async function getCloudflareMp4(url): Promise { const kodaUrl = new URL(url) const infura = `https://kodadot1.infura-ipfs.io${kodaUrl.pathname}` - const video = await $fetch('http://localhost:8787/video/download', { + workerUrl.pathname = '/video/download' + + const video = await $fetch(workerUrl.toString(), { method: 'POST', body: { videoUrl: infura, From ac724e77367f3458ee6402d4f63c05bab4cad88c Mon Sep 17 00:00:00 2001 From: Preschian Febryantara Date: Fri, 5 Jan 2024 09:39:13 +0700 Subject: [PATCH 07/10] feat(GalleryItem.vue): add SN suffix to NFT title --- components/gallery/GalleryItem.vue | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/components/gallery/GalleryItem.vue b/components/gallery/GalleryItem.vue index 6a345e16f7..ff732ebec8 100644 --- a/components/gallery/GalleryItem.vue +++ b/components/gallery/GalleryItem.vue @@ -80,7 +80,7 @@

- {{ nft?.name || nftMetadata?.name }} + {{ title }} 「🔥」

@@ -292,7 +292,12 @@ onMounted(() => { const { isUnlockable, unlockLink } = useUnlockable(collection) -const title = computed(() => nftMetadata.value?.name || '') +const title = computed(() => + addSnSuffixName( + nft.value?.name || nftMetadata.value?.name || '', + nft.value?.sn, + ), +) const seoDescription = computed( () => convertMarkdownToText(nftMetadata.value?.description) || '', ) From 86ed685390b0382a950e7017f97d70dedc55be5b Mon Sep 17 00:00:00 2001 From: Preschian Febryantara Date: Fri, 5 Jan 2024 09:46:01 +0700 Subject: [PATCH 08/10] refactor(useNft.ts, imageWorker.ts): move getMetadata function from useNft.ts to imageWorker.ts refactor(imageWorker.ts): rearrange code in getCloudflareMp4 function for better readability feat(imageWorker.ts): add getMetadata function to fetch metadata from workerUrl --- composables/useNft.ts | 9 ++------- services/imageWorker.ts | 13 +++++++++++-- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/composables/useNft.ts b/composables/useNft.ts index 6f97d866e0..451f9f4547 100644 --- a/composables/useNft.ts +++ b/composables/useNft.ts @@ -5,7 +5,7 @@ import { processSingleMetadata } from '@/utils/cachingStrategy' import { getMimeType, isAudio as isAudioMimeType } from '@/utils/gallery/media' import unionBy from 'lodash/unionBy' import type { Ref } from 'vue' -import { kodaImage } from '@/utils/config/ipfs' +import { getMetadata } from '@/services/imageWorker' export type NftResources = { id: string @@ -182,12 +182,7 @@ export async function getNftMetadata( unify = false, ) { if (unify) { - const metadataUrl = new URL(kodaImage) - metadataUrl.pathname = '/metadata' - metadataUrl.searchParams.set('url', sanitizeIpfsUrl(nft.meta.id)) - const metadata = await fetch(metadataUrl.toString()) - - return metadata as unknown as NFTWithMetadata + return await getMetadata(sanitizeIpfsUrl(nft.meta.id)) } // if subsquid already give us the metadata, we don't need to fetch it again diff --git a/services/imageWorker.ts b/services/imageWorker.ts index b5ac709520..266855e097 100644 --- a/services/imageWorker.ts +++ b/services/imageWorker.ts @@ -17,11 +17,11 @@ type StreamDownload = { const workerUrl = new URL(kodaImage) export async function getCloudflareMp4(url): Promise { + workerUrl.pathname = '/video/download' + const kodaUrl = new URL(url) const infura = `https://kodadot1.infura-ipfs.io${kodaUrl.pathname}` - workerUrl.pathname = '/video/download' - const video = await $fetch(workerUrl.toString(), { method: 'POST', body: { @@ -31,3 +31,12 @@ export async function getCloudflareMp4(url): Promise { return video as StreamDownload } + +export async function getMetadata(url: string) { + workerUrl.pathname = '/metadata' + workerUrl.searchParams.set('url', url) + + const metadata = await $fetch(workerUrl.toString()) + + return metadata as unknown as NFTWithMetadata +} From 71a5848ff8e3dc365aefa451dc141fdfeac1fd57 Mon Sep 17 00:00:00 2001 From: Preschian Febryantara Date: Fri, 5 Jan 2024 10:14:46 +0700 Subject: [PATCH 09/10] feat(GalleryItemDescription.vue, useGalleryItem.ts): add support for cloudflare stream URLs refactor(GalleryItemDescription.vue): replace nftImage and nftAnimation with mediaUrl and animatedMediaUrl feat(useGalleryItem.ts): add video support for nftMimeType --- components/gallery/GalleryItemDescription.vue | 26 ++++++++++++++++--- components/gallery/useGalleryItem.ts | 9 +++++-- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/components/gallery/GalleryItemDescription.vue b/components/gallery/GalleryItemDescription.vue index 5606bbde99..c5bc758f81 100644 --- a/components/gallery/GalleryItemDescription.vue +++ b/components/gallery/GalleryItemDescription.vue @@ -126,11 +126,11 @@


-
+

{{ $t('tabs.tabDetails.media') }}

-
+

{{ $t('tabs.tabDetails.animatedMedia') }}

@@ -284,6 +284,24 @@ const propertiesTabDisabled = computed(() => { const metadataMimeType = 'application/json' const metadataURL = computed(() => sanitizeIpfsUrl(nft.value?.metadata)) + +const isCloudflareStream = (url: string) => url.includes('cloudflarestream.com') + +const mediaUrl = computed(() => { + if (isCloudflareStream(nftImage.value)) { + return sanitizeIpfsUrl(nft.value.meta?.image) + } + + return nftImage.value +}) + +const animatedMediaUrl = computed(() => { + if (isCloudflareStream(nftAnimation.value)) { + return sanitizeIpfsUrl(nft.value.meta?.animation_url) + } + + return nftAnimation.value +})