From ebbf6c522d592afe1483d684d3a67170b5e81ca5 Mon Sep 17 00:00:00 2001 From: hassnian <44554284+hassnian@users.noreply.github.com> Date: Mon, 28 Oct 2024 17:09:38 +0500 Subject: [PATCH 01/45] add(collectinos): init new edit collection modal --- components/collection/CustomizeModal.vue | 93 --------- components/collection/EditModal.vue | 197 ++++++++++++++++++ .../HeroButtonCustomizeCollection.vue | 37 ---- .../collection/HeroButtonEditCollection.vue | 31 +++ components/collection/HeroButtons.vue | 7 +- components/collection/edit/OverrideFile.vue | 36 ++++ components/collection/edit/Section.vue | 15 ++ locales/en.json | 19 +- 8 files changed, 299 insertions(+), 136 deletions(-) delete mode 100644 components/collection/CustomizeModal.vue create mode 100644 components/collection/EditModal.vue delete mode 100644 components/collection/HeroButtonCustomizeCollection.vue create mode 100644 components/collection/HeroButtonEditCollection.vue create mode 100644 components/collection/edit/OverrideFile.vue create mode 100644 components/collection/edit/Section.vue diff --git a/components/collection/CustomizeModal.vue b/components/collection/CustomizeModal.vue deleted file mode 100644 index 1c548b10b0..0000000000 --- a/components/collection/CustomizeModal.vue +++ /dev/null @@ -1,93 +0,0 @@ - - - - - diff --git a/components/collection/EditModal.vue b/components/collection/EditModal.vue new file mode 100644 index 0000000000..6a1124d5f4 --- /dev/null +++ b/components/collection/EditModal.vue @@ -0,0 +1,197 @@ + + + diff --git a/components/collection/HeroButtonCustomizeCollection.vue b/components/collection/HeroButtonCustomizeCollection.vue deleted file mode 100644 index c26659cda3..0000000000 --- a/components/collection/HeroButtonCustomizeCollection.vue +++ /dev/null @@ -1,37 +0,0 @@ - - - diff --git a/components/collection/HeroButtonEditCollection.vue b/components/collection/HeroButtonEditCollection.vue new file mode 100644 index 0000000000..f12ca4b596 --- /dev/null +++ b/components/collection/HeroButtonEditCollection.vue @@ -0,0 +1,31 @@ + + + diff --git a/components/collection/HeroButtons.vue b/components/collection/HeroButtons.vue index f18837957d..551a905bd4 100644 --- a/components/collection/HeroButtons.vue +++ b/components/collection/HeroButtons.vue @@ -97,9 +97,8 @@ - @@ -172,8 +171,6 @@ const { collection } = useCollectionMinimal({ collectionId, }) const collectionIssuer = computed(() => collection.value?.displayCreator) -const collectionNftCount = computed(() => collection.value?.nftCount) -const collectionMaxCount = computed(() => collection.value?.max) const { twitter } = useIdentity({ address: collectionIssuer, diff --git a/components/collection/edit/OverrideFile.vue b/components/collection/edit/OverrideFile.vue new file mode 100644 index 0000000000..b81f54821b --- /dev/null +++ b/components/collection/edit/OverrideFile.vue @@ -0,0 +1,36 @@ + + + diff --git a/components/collection/edit/Section.vue b/components/collection/edit/Section.vue new file mode 100644 index 0000000000..c15973d30a --- /dev/null +++ b/components/collection/edit/Section.vue @@ -0,0 +1,15 @@ + + + diff --git a/locales/en.json b/locales/en.json index 61277caac1..ab47bf492f 100644 --- a/locales/en.json +++ b/locales/en.json @@ -377,6 +377,23 @@ "youSuccessfullyClaimedNft": "You successfully minted {0} NFT", "yourWalletNeeds": "Your wallet needs a minimum of {0} to claim this drop." }, + "edit": { + "collection": { + "banner": { + "hint": "1440 x 560 size recommended", + "message": "Represents your collection on its page and on collection card", + "title": "Banner" + }, + "drop": "Don't click, just drop!", + "image": { + "hint": "300 x 300 size recommended", + "message": "Visible on collection card and view", + "title": "Logo" + }, + "saveChanges": "Save Changes", + "title": "Edit Collection" + } + }, "email": "Email", "error": "Error", "errors": { @@ -1421,11 +1438,11 @@ "mintingSettings": "Minting Settings", "moreActions": { "addNfts": "Add NFTs", - "customize": "Customize", "deleteCollection": "Delete Collection", "deleteNfts": "Delete NFTs", "deletingNfts": "Deleting NFTs", "download": "Download", + "editCollection": "Edit Collection", "reportCollection": "Report Collection" }, "multipleNFTS": "Multiple NFT's", From b90b894b343ef9f7f20bf83fe36d5ec07b580892 Mon Sep 17 00:00:00 2001 From: hassnian <44554284+hassnian@users.noreply.github.com> Date: Tue, 29 Oct 2024 16:22:56 +0500 Subject: [PATCH 02/45] add(collection): edit collection transaction --- components/collection/EditModal.vue | 77 +++++++++---- .../collection/HeroButtonEditCollection.vue | 4 +- .../mintCollection/constructMeta.ts | 2 +- .../mintToken/constructDirectoryMeta.ts | 2 +- .../transaction/mintToken/constructMeta.ts | 2 +- .../transactionSetCollectionMaxSupply.ts | 108 ++++++++++++++---- composables/transaction/types.ts | 21 +++- composables/transaction/utils.ts | 4 +- composables/useTransaction.ts | 14 ++- locales/en.json | 9 +- services/nftStorage.ts | 2 +- 11 files changed, 178 insertions(+), 67 deletions(-) diff --git a/components/collection/EditModal.vue b/components/collection/EditModal.vue index 6a1124d5f4..ac616d5099 100644 --- a/components/collection/EditModal.vue +++ b/components/collection/EditModal.vue @@ -1,4 +1,11 @@ diff --git a/components/collection/HeroButtonEditCollection.vue b/components/collection/HeroButtonEditCollection.vue index f12ca4b596..1d10bc2f0c 100644 --- a/components/collection/HeroButtonEditCollection.vue +++ b/components/collection/HeroButtonEditCollection.vue @@ -9,7 +9,6 @@ v-model="isModalActive" :collection="collectinoMetadata" :min="collection.nftCount" - @submit="isModalActive = false" /> @@ -24,7 +23,10 @@ const props = defineProps<{ const isModalActive = ref(false) const collectinoMetadata = computed(() => ({ + name: props.collection.meta.name, + description: props.collection.meta.description, image: props.collection.meta.image, + imageType: props.collection.meta.type, banner: props.collection.meta.banner, max: props.collection.max, })) diff --git a/composables/transaction/mintCollection/constructMeta.ts b/composables/transaction/mintCollection/constructMeta.ts index 4ee348d965..5cc97610a7 100644 --- a/composables/transaction/mintCollection/constructMeta.ts +++ b/composables/transaction/mintCollection/constructMeta.ts @@ -21,7 +21,7 @@ export async function constructMeta(item: ActionMintCollection) { undefined, type, ) - const metaHash = await pinJson(meta, imageHash) + const metaHash = await pinJson(meta) if (file) { $consola.log('[UPLOADING FILE]') diff --git a/composables/transaction/mintToken/constructDirectoryMeta.ts b/composables/transaction/mintToken/constructDirectoryMeta.ts index 6d69222334..14294045a8 100644 --- a/composables/transaction/mintToken/constructDirectoryMeta.ts +++ b/composables/transaction/mintToken/constructDirectoryMeta.ts @@ -80,7 +80,7 @@ const batchFiles = (files: File[], maxBatchSize: number): File[][] => { return batches } -const uploadMediaFiles = async (files: File[]): Promise => { +export const uploadMediaFiles = async (files: File[]): Promise => { const MAX_BATCH_SIZE = 100 * 1024 * 1024 // 100 MB in bytes const serialFiles = mapToSerial(files) const fileBatches = batchFiles(serialFiles, MAX_BATCH_SIZE) diff --git a/composables/transaction/mintToken/constructMeta.ts b/composables/transaction/mintToken/constructMeta.ts index 01c047e8f7..d3684630fe 100644 --- a/composables/transaction/mintToken/constructMeta.ts +++ b/composables/transaction/mintToken/constructMeta.ts @@ -57,7 +57,7 @@ export async function constructMeta( file.type, ) - const metaHash = await pinJson(meta, imageHash) + const metaHash = await pinJson(meta) preheatFileFromIPFS(fileHash) uploadDirectWhenMultiple( [file, secondFile], diff --git a/composables/transaction/transactionSetCollectionMaxSupply.ts b/composables/transaction/transactionSetCollectionMaxSupply.ts index 983b959d40..5966ec4e9b 100644 --- a/composables/transaction/transactionSetCollectionMaxSupply.ts +++ b/composables/transaction/transactionSetCollectionMaxSupply.ts @@ -1,28 +1,86 @@ -import type { ActionSetCollectionMaxSupply, ExecuteTransactionParams } from './types' - -export function execSetCollectionMaxSupply( - params: ActionSetCollectionMaxSupply, - api, - executeTransaction: ({ - cb, - arg, - successMessage, - errorMessage, - }: ExecuteTransactionParams) => void, -) { - const collectionId = params.collectionId.toString() - const maxSupply = params.max.toString() - - try { - // item.urlPrefix === 'ahr' - if (params.urlPrefix === 'ahk' || params.urlPrefix === 'ahp') { - executeTransaction({ - cb: api.tx.nfts.setCollectionMaxSupply, - arg: [collectionId, maxSupply], - }) - } +import { createMetadata, unSanitizeIpfsUrl } from '@kodadot1/minimark/utils' +import { uploadMediaFiles } from './mintToken/constructDirectoryMeta' +import type { ActionUpdateCollection, UpdateCollectionParams } from './types' +import { pinFileToIPFS, pinJson } from '@/services/nftStorage' + +const constructMeta = async (item: ActionUpdateCollection) => { + const { name, description, image, banner, imageType } = item.collection + + const mediaFiles = [] as File[] + const isImageFile = image instanceof File + const isBannerFile = banner instanceof File + + let type = imageType + + if (isImageFile) { + mediaFiles.push(image) + type = getImageTypeSafe(image) + } + + if (isBannerFile) { + mediaFiles.push(banner) + } + + const ipfs = { image: '', banner: '' } + + if (isImageFile && isBannerFile) { + const [image, banner] = await uploadMediaFiles(mediaFiles) + ipfs.image = image + ipfs.banner = banner + } + else if (isImageFile) { + ipfs.image = await pinFileToIPFS(image) + ipfs.banner = banner as string + } + else if (isBannerFile) { + ipfs.image = image as string + ipfs.banner = await pinFileToIPFS(banner) + } + else { + ipfs.image = image as string + ipfs.banner = banner as string } - catch (error) { - warningMessage(error) + + const attributes = [] + + const meta = createMetadata( + name, + description, + ipfs.image, + undefined, + attributes, + undefined, + type, + ) + + const metaHash = await pinJson(meta) + + return unSanitizeIpfsUrl(metaHash) +} + +async function execUpdateCollectionStatmine({ item, api, executeTransaction, isLoading, status }: UpdateCollectionParams) { + isLoading.value = true + status.value = 'loader.ipfs' + + const metadata = await constructMeta(item) + + const collectionId = item.collectionId.toString() + + const args = [ + api.tx.nfts.setCollectionMetadata(collectionId, metadata), + api.tx.nfts.setCollectionMaxSupply(collectionId, item.collection.max), + ] + + executeTransaction({ + cb: api.tx.utility.batchAll, + arg: [args], + successMessage: item.successMessage, + errorMessage: item.errorMessage, + }) +} + +export async function execUpdateCollection({ item, ...params }: UpdateCollectionParams) { + if (item.urlPrefix === 'ahk' || item.urlPrefix === 'ahp') { + return execUpdateCollectionStatmine({ item, ...params }) } } diff --git a/composables/transaction/types.ts b/composables/transaction/types.ts index a5ce780e3f..5a9ec51854 100644 --- a/composables/transaction/types.ts +++ b/composables/transaction/types.ts @@ -241,7 +241,7 @@ export interface ActionMintCollection { export enum Collections { DELETE = 'delete', - SET_MAX_SUPPLY = 'setCollectionMaxSupply', + UPDATE_COLLECTION = 'updateCollection', } export type ActionsInteractions = Interaction | ShoppingActions | Collections @@ -267,11 +267,22 @@ export interface ActionBurnMultipleNFTs { errorMessage?: string } -export interface ActionSetCollectionMaxSupply { - interaction: Collections.SET_MAX_SUPPLY +type UpdateCollectionStatemine = { + name: string + description: string + image: File | string + imageType?: string + banner?: File | string | null + max?: number +} + +export type UpdateCollectionParams = BaseUnionMintParams & { api: ApiPromise } + +export interface ActionUpdateCollection { + interaction: Collections.UPDATE_COLLECTION collectionId: string + collection: UpdateCollectionStatemine urlPrefix: string - max: number successMessage?: string | ((blockNumber: string) => string) errorMessage?: string } @@ -288,5 +299,5 @@ export type Actions = | ActionMintCollection | ActionDeleteCollection | ActionBurnMultipleNFTs - | ActionSetCollectionMaxSupply + | ActionUpdateCollection | ActionMintDrop diff --git a/composables/transaction/utils.ts b/composables/transaction/utils.ts index c645cd1040..ed78d9fe55 100644 --- a/composables/transaction/utils.ts +++ b/composables/transaction/utils.ts @@ -17,7 +17,7 @@ import type { ActionMintToken, ActionOffer, ActionSend, - ActionSetCollectionMaxSupply, + ActionUpdateCollection, ActionWithdrawOffer, Actions } from '../transaction/types' import { getPercentSupportFee } from '@/utils/support' @@ -57,7 +57,7 @@ export function isActionValid(action: Actions): boolean { Boolean(action.collection), [Collections.DELETE]: (action: ActionDeleteCollection) => Boolean(action.collectionId), - [Collections.SET_MAX_SUPPLY]: (action: ActionSetCollectionMaxSupply) => + [Collections.UPDATE_COLLECTION]: (action: ActionUpdateCollection) => Boolean(action.collectionId), [NFTs.BURN_MULTIPLE]: (action: ActionBurnMultipleNFTs) => hasContent(action.nftIds), diff --git a/composables/useTransaction.ts b/composables/useTransaction.ts index fc4240c76d..a6e656c66d 100644 --- a/composables/useTransaction.ts +++ b/composables/useTransaction.ts @@ -15,7 +15,7 @@ import { execAcceptOfferTx } from './transaction/transactionOfferAccept' import { execMakingOfferTx } from './transaction/transactionOffer' import { execMintToken } from './transaction/transactionMintToken' import { execMintCollection } from './transaction/transactionMintCollection' -import { execSetCollectionMaxSupply } from './transaction/transactionSetCollectionMaxSupply' +import { execUpdateCollection } from './transaction/transactionSetCollectionMaxSupply' import type { ActionAcceptOffer, ActionBurnMultipleNFTs, @@ -28,7 +28,7 @@ import type { ActionMintDrop, ActionMintToken, ActionSend, - ActionSetCollectionMaxSupply, + ActionUpdateCollection, ActionWithdrawOffer, Actions, ExecuteEvmTransactionParams, @@ -253,12 +253,14 @@ export const executeAction = ({ api as ApiPromise, executeTransaction, ), - [Collections.SET_MAX_SUPPLY]: () => - execSetCollectionMaxSupply( - item as ActionSetCollectionMaxSupply, + [Collections.UPDATE_COLLECTION]: () => + execUpdateCollection({ + item: item as ActionUpdateCollection, api, executeTransaction, - ), + isLoading, + status, + }), [NFTs.BURN_MULTIPLE]: () => execBurnMultiple( item as ActionBurnMultipleNFTs, diff --git a/locales/en.json b/locales/en.json index ab47bf492f..d128cdde35 100644 --- a/locales/en.json +++ b/locales/en.json @@ -381,15 +381,16 @@ "collection": { "banner": { "hint": "1440 x 560 size recommended", - "message": "Represents your collection on its page and on collection card", - "title": "Banner" + "label": "Banner", + "message": "Represents your collection on its page and on collection card" }, "drop": "Don't click, just drop!", "image": { "hint": "300 x 300 size recommended", - "message": "Visible on collection card and view", - "title": "Logo" + "label": "Logo", + "message": "Visible on collection card and view" }, + "modal": "Editing Collection", "saveChanges": "Save Changes", "title": "Edit Collection" } diff --git a/services/nftStorage.ts b/services/nftStorage.ts index 34786b016c..4ad7aac791 100644 --- a/services/nftStorage.ts +++ b/services/nftStorage.ts @@ -27,7 +27,7 @@ type StorageApiResponse = { } } -export const pinJson = async (object: Metadata, _name: string) => { +export const pinJson = async (object: Metadata) => { const { value } = await nftStorageApi('/pinJson', { method: 'POST', body: object, From c7d369c30c0377e0c8b3d2ec9b59f9462834ced1 Mon Sep 17 00:00:00 2001 From: hassnian <44554284+hassnian@users.noreply.github.com> Date: Wed, 30 Oct 2024 08:53:16 +0500 Subject: [PATCH 03/45] add(transactionUpdateCollection.ts): collection banner in metadata --- components/collection/EditModal.vue | 6 ++- ...pply.ts => transactionUpdateCollection.ts} | 42 +++++++++---------- composables/useTransaction.ts | 2 +- locales/en.json | 1 + 4 files changed, 27 insertions(+), 24 deletions(-) rename composables/transaction/{transactionSetCollectionMaxSupply.ts => transactionUpdateCollection.ts} (73%) diff --git a/components/collection/EditModal.vue b/components/collection/EditModal.vue index ac616d5099..2ab8eb4800 100644 --- a/components/collection/EditModal.vue +++ b/components/collection/EditModal.vue @@ -179,6 +179,7 @@ const imageUrl = ref() const bannerUrl = ref() const unlimited = ref(true) +const { $i18n } = useNuxtApp() const { transaction, status, isLoading } = useTransaction() const { urlPrefix } = usePrefix() const route = useRoute() @@ -186,7 +187,7 @@ const route = useRoute() const min = computed(() => props.min || 1) const max = ref(props.collection?.max) -const disabled = computed(() => !image.value && !banner.value && props.collection?.max === max.value) +const disabled = computed(() => !image.value && !banner.value && props.collection?.max === max.value && bannerUrl.value) const editCollection = async () => { if (!props.collection) { @@ -203,10 +204,11 @@ const editCollection = async () => { description: props.collection.description, image: image.value || props.collection.image, imageType: props.collection.imageType, - banner: banner.value || props.collection.banner, + banner: bannerUrl.value ? banner.value || props.collection.banner : undefined, max: max.value, }, urlPrefix: urlPrefix.value, + successMessage: $i18n.t('edit.collection.success'), }) } diff --git a/composables/transaction/transactionSetCollectionMaxSupply.ts b/composables/transaction/transactionUpdateCollection.ts similarity index 73% rename from composables/transaction/transactionSetCollectionMaxSupply.ts rename to composables/transaction/transactionUpdateCollection.ts index 5966ec4e9b..24ab87a3c3 100644 --- a/composables/transaction/transactionSetCollectionMaxSupply.ts +++ b/composables/transaction/transactionUpdateCollection.ts @@ -3,34 +3,24 @@ import { uploadMediaFiles } from './mintToken/constructDirectoryMeta' import type { ActionUpdateCollection, UpdateCollectionParams } from './types' import { pinFileToIPFS, pinJson } from '@/services/nftStorage' -const constructMeta = async (item: ActionUpdateCollection) => { - const { name, description, image, banner, imageType } = item.collection - - const mediaFiles = [] as File[] +const getIpfsMedia = async ({ collection: { image, banner, imageType } }: ActionUpdateCollection) => { const isImageFile = image instanceof File const isBannerFile = banner instanceof File let type = imageType - if (isImageFile) { - mediaFiles.push(image) - type = getImageTypeSafe(image) - } - - if (isBannerFile) { - mediaFiles.push(banner) - } - - const ipfs = { image: '', banner: '' } + const ipfs: { image: string, banner?: string } = { image: '', banner: '' } if (isImageFile && isBannerFile) { - const [image, banner] = await uploadMediaFiles(mediaFiles) - ipfs.image = image - ipfs.banner = banner + const mediaFiles = await uploadMediaFiles([image, banner]) + ipfs.image = mediaFiles[0] + ipfs.banner = mediaFiles[1] + type = getImageTypeSafe(image) } else if (isImageFile) { ipfs.image = await pinFileToIPFS(image) - ipfs.banner = banner as string + ipfs.banner = banner as string | undefined + type = getImageTypeSafe(image) } else if (isBannerFile) { ipfs.image = image as string @@ -38,22 +28,32 @@ const constructMeta = async (item: ActionUpdateCollection) => { } else { ipfs.image = image as string - ipfs.banner = banner as string + ipfs.banner = banner as string | undefined + } + + return { + ...ipfs, + type, } +} + +const constructMeta = async (item: ActionUpdateCollection) => { + const { name, description } = item.collection + const { image, banner, type } = await getIpfsMedia(item) const attributes = [] const meta = createMetadata( name, description, - ipfs.image, + image, undefined, attributes, undefined, type, ) - const metaHash = await pinJson(meta) + const metaHash = await pinJson({ ...meta, banner } as any) return unSanitizeIpfsUrl(metaHash) } diff --git a/composables/useTransaction.ts b/composables/useTransaction.ts index a6e656c66d..710daa5ca6 100644 --- a/composables/useTransaction.ts +++ b/composables/useTransaction.ts @@ -15,7 +15,7 @@ import { execAcceptOfferTx } from './transaction/transactionOfferAccept' import { execMakingOfferTx } from './transaction/transactionOffer' import { execMintToken } from './transaction/transactionMintToken' import { execMintCollection } from './transaction/transactionMintCollection' -import { execUpdateCollection } from './transaction/transactionSetCollectionMaxSupply' +import { execUpdateCollection } from './transaction/transactionUpdateCollection' import type { ActionAcceptOffer, ActionBurnMultipleNFTs, diff --git a/locales/en.json b/locales/en.json index d128cdde35..db3d987960 100644 --- a/locales/en.json +++ b/locales/en.json @@ -392,6 +392,7 @@ }, "modal": "Editing Collection", "saveChanges": "Save Changes", + "success": "Successfully edited collection", "title": "Edit Collection" } }, From 193601f79e2535a2c2f8d69c49ae3d0f4ef682c8 Mon Sep 17 00:00:00 2001 From: hassnian <44554284+hassnian@users.noreply.github.com> Date: Wed, 30 Oct 2024 09:03:27 +0500 Subject: [PATCH 04/45] ref(transactionUpdateCollection.ts): one liner --- composables/transaction/transactionUpdateCollection.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/composables/transaction/transactionUpdateCollection.ts b/composables/transaction/transactionUpdateCollection.ts index 24ab87a3c3..7afebf5a01 100644 --- a/composables/transaction/transactionUpdateCollection.ts +++ b/composables/transaction/transactionUpdateCollection.ts @@ -12,9 +12,7 @@ const getIpfsMedia = async ({ collection: { image, banner, imageType } }: Action const ipfs: { image: string, banner?: string } = { image: '', banner: '' } if (isImageFile && isBannerFile) { - const mediaFiles = await uploadMediaFiles([image, banner]) - ipfs.image = mediaFiles[0] - ipfs.banner = mediaFiles[1] + [ipfs.image, ipfs.banner] = await uploadMediaFiles([image, banner]) type = getImageTypeSafe(image) } else if (isImageFile) { From 3d9ec0c053f2e2fc712476f26e7bbc1abb76cc54 Mon Sep 17 00:00:00 2001 From: hassnian <44554284+hassnian@users.noreply.github.com> Date: Wed, 30 Oct 2024 09:07:18 +0500 Subject: [PATCH 05/45] fix(useTransaction.ts): api param type --- composables/useTransaction.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composables/useTransaction.ts b/composables/useTransaction.ts index 710daa5ca6..2397689a50 100644 --- a/composables/useTransaction.ts +++ b/composables/useTransaction.ts @@ -256,7 +256,7 @@ export const executeAction = ({ [Collections.UPDATE_COLLECTION]: () => execUpdateCollection({ item: item as ActionUpdateCollection, - api, + api: api as ApiPromise, executeTransaction, isLoading, status, From 57c820acabb1b4c2f085b8573519ef29864ef5ef Mon Sep 17 00:00:00 2001 From: hassnian <44554284+hassnian@users.noreply.github.com> Date: Wed, 30 Oct 2024 10:42:48 +0500 Subject: [PATCH 06/45] fix(EditModal.vue): disabled computed --- components/collection/EditModal.vue | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/components/collection/EditModal.vue b/components/collection/EditModal.vue index 2ab8eb4800..2ebf7ae4a8 100644 --- a/components/collection/EditModal.vue +++ b/components/collection/EditModal.vue @@ -187,7 +187,13 @@ const route = useRoute() const min = computed(() => props.min || 1) const max = ref(props.collection?.max) -const disabled = computed(() => !image.value && !banner.value && props.collection?.max === max.value && bannerUrl.value) +const disabled = computed(() => { + const hasImage = imageUrl.value + const hasBannerChanged = (!bannerUrl.value && Boolean(props.collection?.banner)) || Boolean(banner.value) + const hasMaxChanged = max.value !== props.collection?.max + + return !hasImage || (!hasBannerChanged && !hasMaxChanged) +}) const editCollection = async () => { if (!props.collection) { From b1d22cf062efcbfa2d8f4c99454cbf498620ffe4 Mon Sep 17 00:00:00 2001 From: hassnian <44554284+hassnian@users.noreply.github.com> Date: Wed, 30 Oct 2024 14:54:06 +0500 Subject: [PATCH 07/45] add(HeroButtonEditCollection.vue): refresh page on collection data indexed --- components/collection/EditModal.vue | 39 +++++------------- .../collection/HeroButtonEditCollection.vue | 40 +++++++++++++++++++ components/collection/HeroButtons.vue | 3 +- .../collection/utils/useCollectionDetails.ts | 4 +- composables/transaction/types.ts | 4 +- composables/useSubscriptionGraphql.ts | 8 +++- 6 files changed, 63 insertions(+), 35 deletions(-) diff --git a/components/collection/EditModal.vue b/components/collection/EditModal.vue index 2ebf7ae4a8..2e57f2d817 100644 --- a/components/collection/EditModal.vue +++ b/components/collection/EditModal.vue @@ -1,11 +1,4 @@ diff --git a/components/collection/HeroButtons.vue b/components/collection/HeroButtons.vue index 551a905bd4..8b33c51884 100644 --- a/components/collection/HeroButtons.vue +++ b/components/collection/HeroButtons.vue @@ -99,6 +99,7 @@ @@ -167,7 +168,7 @@ const shareCollectionToFarcaster = () => { ) } -const { collection } = useCollectionMinimal({ +const { collection, refetch } = useCollectionMinimal({ collectionId, }) const collectionIssuer = computed(() => collection.value?.displayCreator) diff --git a/components/collection/utils/useCollectionDetails.ts b/components/collection/utils/useCollectionDetails.ts index cf4f48b931..abea520bd2 100644 --- a/components/collection/utils/useCollectionDetails.ts +++ b/components/collection/utils/useCollectionDetails.ts @@ -113,7 +113,7 @@ export const useCollectionMinimal = ({ id: collectionId.value, })) - const { data } = useQuery({ + const { data, refetch } = useQuery({ queryKey: ['collection-minimal', isAssetHub, collectionId], queryFn: async () => collectionId.value @@ -127,7 +127,7 @@ export const useCollectionMinimal = ({ : null, }) - const { drop: collectionDrop, isPending: isDropPending, refetch } = useCollectionDrop(collectionId) + const { drop: collectionDrop, isPending: isDropPending } = useCollectionDrop(collectionId) watch([data, isDropPending], async ([data, dropPending]) => { diff --git a/composables/transaction/types.ts b/composables/transaction/types.ts index 5a9ec51854..ed7679715f 100644 --- a/composables/transaction/types.ts +++ b/composables/transaction/types.ts @@ -276,12 +276,14 @@ type UpdateCollectionStatemine = { max?: number } +export type UpdateCollection = UpdateCollectionStatemine + export type UpdateCollectionParams = BaseUnionMintParams & { api: ApiPromise } export interface ActionUpdateCollection { interaction: Collections.UPDATE_COLLECTION collectionId: string - collection: UpdateCollectionStatemine + collection: UpdateCollection urlPrefix: string successMessage?: string | ((blockNumber: string) => string) errorMessage?: string diff --git a/composables/useSubscriptionGraphql.ts b/composables/useSubscriptionGraphql.ts index 116270f392..d5931defe4 100644 --- a/composables/useSubscriptionGraphql.ts +++ b/composables/useSubscriptionGraphql.ts @@ -8,6 +8,7 @@ export default function ({ onError, pollingInterval = 6000, disabled, + immediate = true, }: { clientName?: string query: string @@ -15,6 +16,7 @@ export default function ({ onError?: (error) => void pollingInterval?: number disabled?: ComputedRef + immediate?: boolean }) { const { client: prefixClient } = usePrefix() const { $consola } = useNuxtApp() @@ -46,8 +48,10 @@ export default function ({ const newResult = response.data as any if (!isEqual(newResult, lastQueryResult)) { - $consola.log(`[Graphql Subscription] New changes: ${JSON.stringify(newResult)}`) - onChange({ data: newResult }) + if (!lastQueryResult ? immediate : true) { + $consola.log(`[Graphql Subscription] New changes: ${JSON.stringify(newResult)}`) + onChange({ data: newResult }) + } lastQueryResult = newResult } } From cecd40330b361fe7e9568d1139bc9c2d05c782ad Mon Sep 17 00:00:00 2001 From: hassnian <44554284+hassnian@users.noreply.github.com> Date: Wed, 30 Oct 2024 14:58:03 +0500 Subject: [PATCH 08/45] fix(EditModal.vue): collection max overriden on image upload --- components/collection/EditModal.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/collection/EditModal.vue b/components/collection/EditModal.vue index 2e57f2d817..025eece664 100644 --- a/components/collection/EditModal.vue +++ b/components/collection/EditModal.vue @@ -218,6 +218,6 @@ watch([image, banner, unlimited], ([image, banner, unlimited]) => { bannerUrl.value = URL.createObjectURL(banner) } - max.value = unlimited ? undefined : props.collection?.max + max.value = unlimited ? undefined : max.value || props.collection?.max }) From 8ec7cbc2c7badaf51ce0581cdca126615c43a9b7 Mon Sep 17 00:00:00 2001 From: hassnian <44554284+hassnian@users.noreply.github.com> Date: Wed, 30 Oct 2024 18:40:22 +0500 Subject: [PATCH 09/45] add(HeroButtons.vue): refresh collection metadata --- .../collection/HeroButtonRefreshMetadata.vue | 22 +++++++++++++++++++ components/collection/HeroButtons.vue | 1 + .../GalleryItemMoreActionBtn.vue | 2 +- locales/en.json | 2 ++ services/oda.ts | 6 +++++ 5 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 components/collection/HeroButtonRefreshMetadata.vue diff --git a/components/collection/HeroButtonRefreshMetadata.vue b/components/collection/HeroButtonRefreshMetadata.vue new file mode 100644 index 0000000000..dd4300b688 --- /dev/null +++ b/components/collection/HeroButtonRefreshMetadata.vue @@ -0,0 +1,22 @@ + + + diff --git a/components/collection/HeroButtons.vue b/components/collection/HeroButtons.vue index 8b33c51884..fb0fa4c508 100644 --- a/components/collection/HeroButtons.vue +++ b/components/collection/HeroButtons.vue @@ -102,6 +102,7 @@ @refetch="refetch" /> + {{ $i18n.t('moreActions.reportCollection') }} diff --git a/components/gallery/GalleryItemButton/GalleryItemMoreActionBtn.vue b/components/gallery/GalleryItemButton/GalleryItemMoreActionBtn.vue index 4304e0f845..8eec658257 100644 --- a/components/gallery/GalleryItemButton/GalleryItemMoreActionBtn.vue +++ b/components/gallery/GalleryItemButton/GalleryItemMoreActionBtn.vue @@ -188,7 +188,7 @@ const unlist = () => { const refreshMetadata = async () => { if (props.nft?.collection?.id && props.nft?.sn) { - toast('Refreshing metadata. Check back in a minute...') + toast($i18n.t('toast.refreshMetdata')) await refreshOdaTokenMetadata(urlPrefix.value, props.nft.collection.id, props.nft.sn) } } diff --git a/locales/en.json b/locales/en.json index db3d987960..c52ca2daf0 100644 --- a/locales/en.json +++ b/locales/en.json @@ -1445,6 +1445,7 @@ "deletingNfts": "Deleting NFTs", "download": "Download", "editCollection": "Edit Collection", + "refereshCollectionMetdata": "Refresh Metadata", "reportCollection": "Report Collection" }, "multipleNFTS": "Multiple NFT's", @@ -1892,6 +1893,7 @@ "downloadImage": "Downloading image", "downloadOnMobile": "To download the image, copy the URL from the address bar and open it in your browser. The page will open in 2 seconds.", "paymentLinkCopy": "Payment link copied to clipboard", + "refreshMetdata": "Refreshing metadata. Check back in a minute...", "unsupportedOperation": "Unsupported action", "uploadFileSizeLimit": "The uploaded file exceeds the {0}MB size limit.", "urlCopy": "URL copied to clipboard ✓" diff --git a/services/oda.ts b/services/oda.ts index c07d14db82..62da733c44 100644 --- a/services/oda.ts +++ b/services/oda.ts @@ -52,6 +52,12 @@ export const refreshOdaTokenMetadata = (chain: Prefix, address: string, tokenId: }) } +export const refreshOdaCollectionTokensMetadata = (chain: Prefix, address: string) => { + return api(`/v1/${chain}/collection/${address}/tokens`, { + method: 'DELETE', + }) +} + type OdaMimeType = { mime_type: string } From fea45277ddebb6fef537ac11cc2f7f053c746fc2 Mon Sep 17 00:00:00 2001 From: hassnian <44554284+hassnian@users.noreply.github.com> Date: Thu, 31 Oct 2024 08:02:17 +0500 Subject: [PATCH 10/45] add(EditModal.vue): into translation keys --- components/collection/EditModal.vue | 8 ++++---- locales/en.json | 4 ++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/components/collection/EditModal.vue b/components/collection/EditModal.vue index 025eece664..5bf8872a65 100644 --- a/components/collection/EditModal.vue +++ b/components/collection/EditModal.vue @@ -69,7 +69,7 @@ class="h-[167px] border border-border-color object-cover" > -

+

{{ $t('edit.collection.banner.hint') }}

@@ -93,7 +93,7 @@ :label="$t('edit.collection.drop')" /> -

+

{{ $t('edit.collection.banner.hint') }}

@@ -101,7 +101,7 @@ @@ -124,7 +124,7 @@ />
- This will update the Maximum items in your collection + {{ $t('edit.collection.max.hint') }}
diff --git a/locales/en.json b/locales/en.json index c52ca2daf0..66493b8a33 100644 --- a/locales/en.json +++ b/locales/en.json @@ -390,6 +390,10 @@ "label": "Logo", "message": "Visible on collection card and view" }, + "max": { + "hit": "This will update the Maximum items in your collection", + "label": "Maximum NFTs in collection" + }, "modal": "Editing Collection", "saveChanges": "Save Changes", "success": "Successfully edited collection", From 5b5f8803b242dbc4769e6b30b6ba01e449c52cce Mon Sep 17 00:00:00 2001 From: hassnian <44554284+hassnian@users.noreply.github.com> Date: Thu, 31 Oct 2024 09:35:24 +0500 Subject: [PATCH 11/45] add(EditModal.vue): capitalize text --- components/collection/EditModal.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/collection/EditModal.vue b/components/collection/EditModal.vue index 5bf8872a65..e6d139bbae 100644 --- a/components/collection/EditModal.vue +++ b/components/collection/EditModal.vue @@ -56,7 +56,7 @@ -

+

{{ $t('edit.collection.banner.message') }}

From dd0d332927e9b794efff8d804076b5d2ed98b6f7 Mon Sep 17 00:00:00 2001 From: hassnian <44554284+hassnian@users.noreply.github.com> Date: Fri, 1 Nov 2024 17:57:43 +0500 Subject: [PATCH 12/45] add(GalleryItem): set collection metadata --- components/common/EditNftModal.vue | 171 ++++++++++++++++++ .../GalleryItemEditNftButton.vue | 57 ++++++ .../GalleryItemMoreActionBtn.vue | 2 + .../transaction/transactionSetNftMetadata.ts | 53 ++++++ composables/transaction/types.ts | 18 +- composables/transaction/utils.ts | 3 + composables/useTransaction.ts | 10 + locales/en.json | 3 +- 8 files changed, 315 insertions(+), 2 deletions(-) create mode 100644 components/common/EditNftModal.vue create mode 100644 components/gallery/GalleryItemButton/GalleryItemEditNftButton.vue create mode 100644 composables/transaction/transactionSetNftMetadata.ts diff --git a/components/common/EditNftModal.vue b/components/common/EditNftModal.vue new file mode 100644 index 0000000000..753a4d345d --- /dev/null +++ b/components/common/EditNftModal.vue @@ -0,0 +1,171 @@ + + +7 diff --git a/components/gallery/GalleryItemButton/GalleryItemEditNftButton.vue b/components/gallery/GalleryItemButton/GalleryItemEditNftButton.vue new file mode 100644 index 0000000000..f15d83f4e3 --- /dev/null +++ b/components/gallery/GalleryItemButton/GalleryItemEditNftButton.vue @@ -0,0 +1,57 @@ + + + diff --git a/components/gallery/GalleryItemButton/GalleryItemMoreActionBtn.vue b/components/gallery/GalleryItemButton/GalleryItemMoreActionBtn.vue index 8eec658257..d9b944c21e 100644 --- a/components/gallery/GalleryItemButton/GalleryItemMoreActionBtn.vue +++ b/components/gallery/GalleryItemButton/GalleryItemMoreActionBtn.vue @@ -48,6 +48,7 @@ Delist
+ Report @@ -63,6 +64,7 @@ import { NeoButton, NeoDropdown, NeoDropdownItem } from '@kodadot1/brick' import { Interaction } from '@kodadot1/minimark/v1' import { useQuery } from '@tanstack/vue-query' +import GalleryItemEditNftButton from './GalleryItemEditNftButton.vue' import { downloadImage } from '@/utils/download' import { sanitizeIpfsUrl, toOriginalContentUrl } from '@/utils/ipfs' import { isMobileDevice } from '@/utils/extension' diff --git a/composables/transaction/transactionSetNftMetadata.ts b/composables/transaction/transactionSetNftMetadata.ts new file mode 100644 index 0000000000..6f758e5a25 --- /dev/null +++ b/composables/transaction/transactionSetNftMetadata.ts @@ -0,0 +1,53 @@ +import { unSanitizeIpfsUrl } from '@kodadot1/minimark/utils' +import { type Metadata } from '@kodadot1/minimark/common' +import type { SetNftMetadataParams, ActionSetNftMetadata } from './types' +import { pinJson, rateLimitedPinFileToIPFS } from '@/services/nftStorage' + +const constructMeta = async (item: ActionSetNftMetadata) => { + const { name, description, attributes, image } = item.metadata + + let imageHash: string | undefined + let type: string | undefined + + if (image instanceof File) { + imageHash = unSanitizeIpfsUrl(await rateLimitedPinFileToIPFS(image)) + type = getImageTypeSafe(image) + } + else { + imageHash = image + type = item.metadata.type + } + + const meta = { + ...item.metadata, + name, + description, + image: imageHash, + attributes, + type, + } as Metadata + + const metaHash = await pinJson(meta) + + return unSanitizeIpfsUrl(metaHash) +} + +async function execSetNftMetadataStatmine({ item, api, executeTransaction, isLoading, status }: SetNftMetadataParams) { + isLoading.value = true + status.value = 'loader.ipfs' + + const metadata = await constructMeta(item) + + executeTransaction({ + cb: api.tx.nfts.setMetadata, + arg: [item.collectionId, item.nftSn, metadata], + successMessage: item.successMessage, + errorMessage: item.errorMessage, + }) +} + +export async function execSetNftMetadata({ item, ...params }: SetNftMetadataParams) { + if (item.urlPrefix === 'ahk' || item.urlPrefix === 'ahp') { + return execSetNftMetadataStatmine({ item, ...params }) + } +} diff --git a/composables/transaction/types.ts b/composables/transaction/types.ts index ed7679715f..ceea971c4f 100644 --- a/composables/transaction/types.ts +++ b/composables/transaction/types.ts @@ -1,4 +1,4 @@ -import type { Attribute } from '@kodadot1/minimark/common' +import type { Attribute, Metadata } from '@kodadot1/minimark/common' import type { Interaction } from '@kodadot1/minimark/v1' import type { ApiPromise } from '@polkadot/api' import type { Prefix } from '@kodadot1/static' @@ -257,6 +257,7 @@ export interface ActionDeleteCollection { export enum NFTs { BURN_MULTIPLE = 'burnMultiple', MINT_DROP = 'mintDrop', + SET_METADATA = 'setMetadata', } export interface ActionBurnMultipleNFTs { @@ -267,6 +268,20 @@ export interface ActionBurnMultipleNFTs { errorMessage?: string } +export type ActionMetadataSetMetadata = Metadata & { image: File | string } + +export interface ActionSetNftMetadata { + interaction: NFTs.SET_METADATA + urlPrefix: string + nftSn: string + collectionId: string + metadata: ActionMetadataSetMetadata + successMessage?: string + errorMessage?: string +} + +export type SetNftMetadataParams = BaseUnionMintParams & { api: ApiPromise } + type UpdateCollectionStatemine = { name: string description: string @@ -302,4 +317,5 @@ export type Actions = | ActionDeleteCollection | ActionBurnMultipleNFTs | ActionUpdateCollection + | ActionSetNftMetadata | ActionMintDrop diff --git a/composables/transaction/utils.ts b/composables/transaction/utils.ts index ed78d9fe55..0f4d7af233 100644 --- a/composables/transaction/utils.ts +++ b/composables/transaction/utils.ts @@ -19,6 +19,7 @@ import type { ActionSend, ActionUpdateCollection, ActionWithdrawOffer, + ActionSetNftMetadata, Actions } from '../transaction/types' import { getPercentSupportFee } from '@/utils/support' @@ -61,6 +62,8 @@ export function isActionValid(action: Actions): boolean { Boolean(action.collectionId), [NFTs.BURN_MULTIPLE]: (action: ActionBurnMultipleNFTs) => hasContent(action.nftIds), + [NFTs.SET_METADATA]: (action: ActionSetNftMetadata) => + hasContent(action.nftSn), [NFTs.MINT_DROP]: (action: ActionMintDrop) => hasContent(action.collectionId), } diff --git a/composables/useTransaction.ts b/composables/useTransaction.ts index 2397689a50..40a99f9c40 100644 --- a/composables/useTransaction.ts +++ b/composables/useTransaction.ts @@ -16,6 +16,7 @@ import { execMakingOfferTx } from './transaction/transactionOffer' import { execMintToken } from './transaction/transactionMintToken' import { execMintCollection } from './transaction/transactionMintCollection' import { execUpdateCollection } from './transaction/transactionUpdateCollection' +import { execSetNftMetadata } from './transaction/transactionSetNftMetadata' import type { ActionAcceptOffer, ActionBurnMultipleNFTs, @@ -29,6 +30,7 @@ import type { ActionMintToken, ActionSend, ActionUpdateCollection, + ActionSetNftMetadata, ActionWithdrawOffer, Actions, ExecuteEvmTransactionParams, @@ -267,6 +269,14 @@ export const executeAction = ({ api as ApiPromise, executeTransaction, ), + [NFTs.SET_METADATA]: () => + execSetNftMetadata({ + item: item as ActionSetNftMetadata, + api: api as ApiPromise, + executeTransaction, + isLoading, + status, + }), [NFTs.MINT_DROP]: () => execMintDrop({ item: item as ActionMintDrop, diff --git a/locales/en.json b/locales/en.json index 66493b8a33..a175cd0f93 100644 --- a/locales/en.json +++ b/locales/en.json @@ -398,7 +398,8 @@ "saveChanges": "Save Changes", "success": "Successfully edited collection", "title": "Edit Collection" - } + }, + "nft": { "title": "Edit NFT" } }, "email": "Email", "error": "Error", From 0991b286e306a5059ce3623ad5b69a872911ba2a Mon Sep 17 00:00:00 2001 From: hassnian <44554284+hassnian@users.noreply.github.com> Date: Sat, 2 Nov 2024 11:54:37 +0500 Subject: [PATCH 13/45] fix(EditNftModal.vue): disabled computed and attributes updating --- components/common/EditNftModal.vue | 13 ++++++++++--- components/rmrk/Create/CustomAttributeInput.vue | 13 +++++-------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/components/common/EditNftModal.vue b/components/common/EditNftModal.vue index 753a4d345d..7da704f96b 100644 --- a/components/common/EditNftModal.vue +++ b/components/common/EditNftModal.vue @@ -132,9 +132,16 @@ const imageUrl = ref() const attributes = ref([]) const disabled = computed(() => { - const hasImage = imageUrl.value + const hasImage = Boolean(imageUrl.value) + const isNameFilled = Boolean(name.value) - return !hasImage + const nameChanged = props.metadata?.name !== name.value + const descriptionChanged = props.metadata?.description !== description.value + const imageChanged = Boolean(image.value) + const attributesChanged = JSON.stringify(attributes.value) !== JSON.stringify(props.metadata?.attributes || []) + + return !hasImage || !isNameFilled + || (!nameChanged && !descriptionChanged && !imageChanged && !attributesChanged) }) const editCollection = async () => { @@ -159,7 +166,7 @@ watch(isModalActive, (value) => { image.value = undefined name.value = props.metadata?.name description.value = props.metadata?.description - attributes.value = props.metadata?.attributes || [] as Attribute[] + attributes.value = JSON.parse(JSON.stringify(props.metadata?.attributes || [])) } }) diff --git a/components/rmrk/Create/CustomAttributeInput.vue b/components/rmrk/Create/CustomAttributeInput.vue index 11eac07341..ffa707c40c 100644 --- a/components/rmrk/Create/CustomAttributeInput.vue +++ b/components/rmrk/Create/CustomAttributeInput.vue @@ -36,6 +36,7 @@ import AttributeInput from './AttributeInput.vue' const props = withDefaults( defineProps<{ + modelValue?: Attribute[] max: number visible?: string hidden?: string @@ -44,11 +45,12 @@ const props = withDefaults( max: 0, visible: 'collapse.collection.attributes.show', hidden: 'collapse.collection.attributes.hide', + modelValue: () => [], }, ) -const emit = defineEmits(['update:modelValue']) -const attributes = ref([]) +const attributes = useVModel(props, 'modelValue') + const disabled = computed( () => props.max > 0 && attributes.value.length === props.max, ) @@ -61,13 +63,8 @@ const addAttribute = () => { }) } } -const removeAttribute = (index: number) => attributes.value.splice(index, 1) -const handleInput = (attributes: Attribute[]) => - emit('update:modelValue', attributes) -watch(attributes.value, () => { - handleInput(attributes.value) -}) +const removeAttribute = (index: number) => attributes.value.splice(index, 1)