diff --git a/src/components/CollectionDetailPage/CollectionDetailPage.tsx b/src/components/CollectionDetailPage/CollectionDetailPage.tsx
index 9a19cf1b2..5da6cb47a 100644
--- a/src/components/CollectionDetailPage/CollectionDetailPage.tsx
+++ b/src/components/CollectionDetailPage/CollectionDetailPage.tsx
@@ -16,7 +16,7 @@ import {
isOwner
} from 'modules/collection/utils'
import { CollectionType } from 'modules/collection/types'
-import { isSmart } from 'modules/item/utils'
+import { isEmote, isSmart, isWearable } from 'modules/item/utils'
import { Item, ItemType, SyncStatus, VIDEO_PATH } from 'modules/item/types'
import CollectionProvider from 'components/CollectionProvider'
import LoggedInDetailPage from 'components/LoggedInDetailPage'
@@ -239,16 +239,14 @@ export default function CollectionDetailPage({
const canMint = canMintCollectionItems(collection, wallet.address)
const isLocked = isCollectionLocked(collection)
const isOnSale = isCollectionOnSale(collection, wallet)
- const hasEmotes = items.some(item => item.type === ItemType.EMOTE)
- const hasWearables = items.some(item => item.type === ItemType.WEARABLE)
- const isEmoteMissingPrice = hasEmotes ? items.some(item => item.type === ItemType.EMOTE && !item.price) : false
- const isWearableMissingPrice = hasWearables ? items.some(item => item.type === ItemType.WEARABLE && !item.price) : false
+ const hasEmotes = items.some(isEmote)
+ const hasWearables = items.some(isWearable)
+ const isEmoteMissingPrice = hasEmotes ? items.some(item => isEmote(item) && !item.price) : false
+ const isWearableMissingPrice = hasWearables ? items.some(item => isWearable(item) && !item.price) : false
const isSmartWearableMissingVideo = hasWearables && items.some(item => isSmart(item) && !(VIDEO_PATH in item.contents))
const hasOnlyEmotes = hasEmotes && !hasWearables
const hasOnlyWearables = hasWearables && !hasEmotes
- const filteredItems = items.filter(item =>
- hasOnlyWearables ? item.type === ItemType.WEARABLE : hasOnlyEmotes ? item.type === ItemType.EMOTE : item.type === tab
- )
+ const filteredItems = items.filter(item => (hasOnlyWearables ? isWearable(item) : hasOnlyEmotes ? isEmote(item) : item.type === tab))
const showShowTabs = hasEmotes && hasWearables
return (
diff --git a/src/components/CollectionDetailPage/CollectionItem/CollectionItem.tsx b/src/components/CollectionDetailPage/CollectionItem/CollectionItem.tsx
index 8369dabfa..308ae407f 100644
--- a/src/components/CollectionDetailPage/CollectionItem/CollectionItem.tsx
+++ b/src/components/CollectionDetailPage/CollectionItem/CollectionItem.tsx
@@ -9,9 +9,9 @@ import { Link, useHistory } from 'react-router-dom'
import { locations } from 'routing/locations'
import { preventDefault } from 'lib/event'
import { extractThirdPartyTokenId, extractTokenId, isThirdParty } from 'lib/urn'
-import { isComplete, isFree, canManageItem, getMaxSupply, isSmart } from 'modules/item/utils'
+import { isComplete, isFree, canManageItem, getMaxSupply, isSmart, isEmote } from 'modules/item/utils'
import { isLocked } from 'modules/collection/utils'
-import { isEmoteData, ItemType, SyncStatus, VIDEO_PATH, WearableData } from 'modules/item/types'
+import { isEmoteData, SyncStatus, VIDEO_PATH, WearableData } from 'modules/item/types'
import { FromParam } from 'modules/location/types'
import ItemStatus from 'components/ItemStatus'
import ItemBadge from 'components/ItemBadge'
@@ -176,7 +176,7 @@ export default function CollectionItem({ onOpenModal, onSetItems, item, collecti
{item.rarity && data.category ? : null}
{data.category ? {t(`${item.type}.category.${data.category}`)}
: null}
- {item.type === ItemType.EMOTE && isEmoteData(data) ? (
+ {isEmote(item) && isEmoteData(data) ? (
{data.category ? {t(`emote.play_mode.${data.loop ? 'loop' : 'simple'}.text`)}
: null}
diff --git a/src/components/ItemDetailPage/ItemDetailPage.tsx b/src/components/ItemDetailPage/ItemDetailPage.tsx
index bb81e9900..40efc569e 100644
--- a/src/components/ItemDetailPage/ItemDetailPage.tsx
+++ b/src/components/ItemDetailPage/ItemDetailPage.tsx
@@ -6,7 +6,7 @@ import { t } from 'decentraland-dapps/dist/modules/translation/utils'
import { Network } from '@dcl/schemas'
import { locations } from 'routing/locations'
import { Collection } from 'modules/collection/types'
-import { getMaxSupply, getMissingBodyShapeType, isFree, resizeImage, getThumbnailURL, isSmart } from 'modules/item/utils'
+import { getMaxSupply, getMissingBodyShapeType, isFree, resizeImage, getThumbnailURL, isSmart, isWearable } from 'modules/item/utils'
import { getCollectionType, isLocked as isCollectionLocked } from 'modules/collection/utils'
import { dataURLToBlob } from 'modules/media/utils'
import { computeHashes } from 'modules/deployment/contentUtils'
@@ -347,7 +347,7 @@ export default function ItemDetailPage(props: Props) {
- {item.type === ItemType.WEARABLE && !isSmart(item) ? (
+ {isWearable(item) && !isSmart(item) ? (
{t('item_detail_page.representations.title')}
diff --git a/src/components/ItemEditorPage/CenterPanel/CenterPanel.container.ts b/src/components/ItemEditorPage/CenterPanel/CenterPanel.container.ts
index 8a6760dff..80a7bd4a0 100644
--- a/src/components/ItemEditorPage/CenterPanel/CenterPanel.container.ts
+++ b/src/components/ItemEditorPage/CenterPanel/CenterPanel.container.ts
@@ -29,7 +29,7 @@ import {
} from 'modules/editor/selectors'
import { fetchCollectionItemsRequest, fetchItemsRequest } from 'modules/item/actions'
import { getEmotes, getItem, hasUserOrphanItems } from 'modules/item/selectors'
-import { ItemType } from 'modules/item/types'
+import { isEmote } from 'modules/item/utils'
import { getSelectedCollectionId, getSelectedItemId } from 'modules/location/selectors'
import { MapStateProps, MapDispatchProps, MapDispatch } from './CenterPanel.types'
import CenterPanel from './CenterPanel'
@@ -53,7 +53,7 @@ const mapState = (state: RootState): MapStateProps => {
const selectedBaseWearablesByBodyShape = getSelectedBaseWearablesByBodyShape(state)
const visibleItems = getVisibleItems(state)
const emote = getEmote(state)
- const isPLayingIdleEmote = !visibleItems.some(item => item.type === ItemType.EMOTE) && emote === PreviewEmote.IDLE
+ const isPLayingIdleEmote = !visibleItems.some(isEmote) && emote === PreviewEmote.IDLE
/* The library react-dropzone doesn't work as expected when an Iframe is present in the current view.
This way, we're getting when the CreateSingleItemModal is open to disable the drag and drop events in the Iframe
and the library react-dropzone works as expected in the CreateSingleItemModal.
diff --git a/src/components/ItemEditorPage/CenterPanel/CenterPanel.tsx b/src/components/ItemEditorPage/CenterPanel/CenterPanel.tsx
index 05a2ee95a..6ea0f56f8 100644
--- a/src/components/ItemEditorPage/CenterPanel/CenterPanel.tsx
+++ b/src/components/ItemEditorPage/CenterPanel/CenterPanel.tsx
@@ -20,6 +20,7 @@ import { isDevelopment } from 'lib/environment'
import { extractThirdPartyTokenId, extractTokenId, isThirdParty } from 'lib/urn'
import { isTPCollection } from 'modules/collection/utils'
import { ItemType } from 'modules/item/types'
+import { isEmote } from 'modules/item/utils'
import { toBase64, toHex } from 'modules/editor/utils'
import { getSkinColors, getEyeColors, getHairColors } from 'modules/editor/avatar'
import BuilderIcon from 'components/Icon'
@@ -272,7 +273,7 @@ export default class CenterPanel extends React.PureComponent
{
wearableController
} = this.props
const { isShowingAvatarAttributes, showSceneBoundaries, isLoading } = this.state
- const isRenderingAnEmote = visibleItems.some(item => item.type === ItemType.EMOTE) && selectedItem?.type === ItemType.EMOTE
+ const isRenderingAnEmote = visibleItems.some(isEmote) && selectedItem?.type === ItemType.EMOTE
const zoom = emote === PreviewEmote.JUMP ? 1 : undefined
return (
diff --git a/src/components/ItemEditorPage/LeftPanel/Items/Items.tsx b/src/components/ItemEditorPage/LeftPanel/Items/Items.tsx
index 170c423a3..2cbaff5c7 100644
--- a/src/components/ItemEditorPage/LeftPanel/Items/Items.tsx
+++ b/src/components/ItemEditorPage/LeftPanel/Items/Items.tsx
@@ -16,7 +16,7 @@ import {
} from 'decentraland-ui'
import { extractThirdPartyTokenId, extractTokenId, isThirdParty } from 'lib/urn'
import { Item, ItemType } from 'modules/item/types'
-import { hasBodyShape } from 'modules/item/utils'
+import { hasBodyShape, isEmote, isWearable } from 'modules/item/utils'
import { TP_TRESHOLD_TO_REVIEW } from 'modules/collection/constants'
import { LEFT_PANEL_PAGE_SIZE } from '../../constants'
import Collapsable from 'components/Collapsable'
@@ -77,7 +77,7 @@ export default class Items extends React.PureComponent {
let newVisibleItemIds = visibleItems.filter(_item => _item.id !== item.id)
- if (item.type === ItemType.EMOTE) {
+ if (isEmote(item)) {
if (this.isVisible(item)) {
if (isPlayingEmote) {
wearableController?.emote.pause() as void
@@ -165,8 +165,8 @@ export default class Items extends React.PureComponent {
}
renderSidebarCategory = (items: Item[]) => {
- const wearableItems = items.filter(item => item.type === ItemType.WEARABLE)
- const emoteItems = items.filter(item => item.type === ItemType.EMOTE)
+ const wearableItems = items.filter(isWearable)
+ const emoteItems = items.filter(isEmote)
if (wearableItems.length === 0 || emoteItems.length === 0) {
return items.map(this.renderSidebarItem)
diff --git a/src/components/ItemEditorPage/LeftPanel/Items/SidebarItem/SidebarItem.tsx b/src/components/ItemEditorPage/LeftPanel/Items/SidebarItem/SidebarItem.tsx
index 10a1d109e..2ad9efa73 100644
--- a/src/components/ItemEditorPage/LeftPanel/Items/SidebarItem/SidebarItem.tsx
+++ b/src/components/ItemEditorPage/LeftPanel/Items/SidebarItem/SidebarItem.tsx
@@ -4,8 +4,7 @@ import { Icon, Popup } from 'decentraland-ui'
import { t } from 'decentraland-dapps/dist/modules/translation/utils'
import ItemImage from 'components/ItemImage'
import ItemStatus from 'components/ItemStatus'
-import { ItemType } from 'modules/item/types'
-import { getMissingBodyShapeType, hasBodyShape } from 'modules/item/utils'
+import { getMissingBodyShapeType, hasBodyShape, isEmote } from 'modules/item/utils'
import { locations } from 'routing/locations'
import { Props } from './SidebarItem.types'
import './SidebarItem.css'
@@ -18,7 +17,7 @@ class SidebarItem extends React.PureComponent {
renderToggleItem() {
const { item, isVisible, isPlayingEmote } = this.props
- if (item.type === ItemType.EMOTE) {
+ if (isEmote(item)) {
return
} else {
return
diff --git a/src/components/ItemEditorPage/RightPanel/RightPanel.tsx b/src/components/ItemEditorPage/RightPanel/RightPanel.tsx
index e6d17a283..f15a25583 100644
--- a/src/components/ItemEditorPage/RightPanel/RightPanel.tsx
+++ b/src/components/ItemEditorPage/RightPanel/RightPanel.tsx
@@ -16,7 +16,8 @@ import {
getHideableBodyPartCategories,
getHideableWearableCategories,
isSmart,
- hasVideo
+ hasVideo,
+ isWearable
} from 'modules/item/utils'
import { isLocked } from 'modules/collection/utils'
import { computeHashes } from 'modules/deployment/contentUtils'
@@ -83,7 +84,7 @@ export default class RightPanel extends React.PureComponent {
setItem(item: Item) {
const data = item.data
- if (item.type === ItemType.WEARABLE && data.replaces?.length) {
+ if (isWearable(item) && data.replaces?.length) {
// Move all items that are in replaces array to hides array
data.hides = data.hides.concat(data.replaces)
data.replaces = []
@@ -525,7 +526,7 @@ export default class RightPanel extends React.PureComponent {
const isItemLocked = collection && isLocked(collection)
const canEditItemMetadata = this.canEditItemMetadata(item)
- const categories = item ? (item.type === ItemType.WEARABLE ? getWearableCategories(item.contents) : getEmoteCategories()) : []
+ const categories = item ? (isWearable(item) ? getWearableCategories(item.contents) : getEmoteCategories()) : []
return isLoading ? (
diff --git a/src/components/Modals/CreateAndEditMultipleItemsModal/CreateAndEditMultipleItemsModal.tsx b/src/components/Modals/CreateAndEditMultipleItemsModal/CreateAndEditMultipleItemsModal.tsx
index ceec6c901..38c0ff844 100644
--- a/src/components/Modals/CreateAndEditMultipleItemsModal/CreateAndEditMultipleItemsModal.tsx
+++ b/src/components/Modals/CreateAndEditMultipleItemsModal/CreateAndEditMultipleItemsModal.tsx
@@ -23,7 +23,16 @@ import { T, t } from 'decentraland-dapps/dist/modules/translation/utils'
import { config } from 'config'
import { EngineType, getModelData } from 'lib/getModelData'
import { getExtension, toMB } from 'lib/file'
-import { buildThirdPartyURN, DecodedURN, decodeURN, URNType } from 'lib/urn'
+import {
+ buildThirdPartyURN,
+ buildThirdPartyV2URN,
+ decodedCollectionsUrnAreEqual,
+ DecodedURN,
+ decodeURN,
+ isThirdPartyCollectionDecodedUrn,
+ isThirdPartyV2CollectionDecodedUrn,
+ URNType
+} from 'lib/urn'
import { convertImageIntoWearableThumbnail, dataURLToBlob, getImageType } from 'modules/media/utils'
import { ImageType } from 'modules/media/types'
import { MultipleItemsSaveState } from 'modules/ui/createMultipleItems/reducer'
@@ -165,33 +174,41 @@ export default class CreateAndEditMultipleItemsModal extends React.PureComponent
// Generate or set the correct URN for the items taking into consideration the selected collection
const decodedCollectionUrn: DecodedURN | null = collection?.urn ? decodeURN(collection.urn) : null
+ // Check if the collection is a third party collection
if (
decodedCollectionUrn &&
- decodedCollectionUrn.type === URNType.COLLECTIONS_THIRDPARTY &&
- decodedCollectionUrn.thirdPartyCollectionId
+ (isThirdPartyCollectionDecodedUrn(decodedCollectionUrn) || isThirdPartyV2CollectionDecodedUrn(decodedCollectionUrn))
) {
const decodedUrn: DecodedURN | null = loadedFile.wearable.id ? decodeURN(loadedFile.wearable.id) : null
- if (loadedFile.wearable.id && decodedUrn && decodedUrn.type === URNType.COLLECTIONS_THIRDPARTY) {
- const { thirdPartyName, thirdPartyCollectionId } = decodedUrn
-
- if (
- (thirdPartyCollectionId && thirdPartyCollectionId !== decodedCollectionUrn.thirdPartyCollectionId) ||
- (thirdPartyName && thirdPartyName !== decodedCollectionUrn.thirdPartyName)
- ) {
- throw new Error(t('create_and_edit_multiple_items_modal.invalid_urn'))
- }
- if (decodedUrn.thirdPartyTokenId) {
- itemFactory.withUrn(
- buildThirdPartyURN(
- decodedCollectionUrn.thirdPartyName,
- decodedCollectionUrn.thirdPartyCollectionId,
- decodedUrn.thirdPartyTokenId
- )
+ const thirdPartyTokenId =
+ loadedFile.wearable.id &&
+ decodedUrn &&
+ (decodedUrn.type === URNType.COLLECTIONS_THIRDPARTY || decodedUrn.type === URNType.COLLECTIONS_THIRDPARTY_V2)
+ ? decodedUrn.thirdPartyTokenId ?? null
+ : null
+
+ // Check if the decoded collections match a the collection level
+ if (decodedUrn && !decodedCollectionsUrnAreEqual(decodedCollectionUrn, decodedUrn)) {
+ throw new Error(t('create_and_edit_multiple_items_modal.invalid_urn'))
+ }
+
+ // Build the third party item URN in accordance ot the collection URN
+ if (isThirdPartyCollectionDecodedUrn(decodedCollectionUrn)) {
+ itemFactory.withUrn(
+ buildThirdPartyURN(
+ decodedCollectionUrn.thirdPartyName,
+ decodedCollectionUrn.thirdPartyCollectionId,
+ thirdPartyTokenId ?? uuid.v4()
)
- }
+ )
} else {
itemFactory.withUrn(
- buildThirdPartyURN(decodedCollectionUrn.thirdPartyName, decodedCollectionUrn.thirdPartyCollectionId, uuid.v4())
+ buildThirdPartyV2URN(
+ decodedCollectionUrn.thirdPartyLinkedCollectionName,
+ decodedCollectionUrn.linkedCollectionNetwork,
+ decodedCollectionUrn.linkedCollectionAddress,
+ thirdPartyTokenId ?? uuid.v4()
+ )
)
}
}
diff --git a/src/components/Modals/CreateSingleItemModal/CreateSingleItemModal.tsx b/src/components/Modals/CreateSingleItemModal/CreateSingleItemModal.tsx
index af5f9969b..3194dd173 100644
--- a/src/components/Modals/CreateSingleItemModal/CreateSingleItemModal.tsx
+++ b/src/components/Modals/CreateSingleItemModal/CreateSingleItemModal.tsx
@@ -1,6 +1,14 @@
import * as React from 'react'
import uuid from 'uuid'
+import { ethers } from 'ethers'
import { BodyPartCategory, BodyShape, EmoteCategory, EmoteDataADR74, Rarity, PreviewProjection, WearableCategory } from '@dcl/schemas'
+import {
+ MAX_EMOTE_FILE_SIZE,
+ MAX_SKIN_FILE_SIZE,
+ MAX_THUMBNAIL_FILE_SIZE,
+ MAX_WEARABLE_FILE_SIZE,
+ MAX_SMART_WEARABLE_FILE_SIZE
+} from '@dcl/builder-client/dist/files/constants'
import {
ModalNavigation,
Row,
@@ -46,16 +54,29 @@ import {
getEmoteCategories,
getEmotePlayModes,
getBodyShapeTypeFromContents,
- isSmart
+ isSmart,
+ isWearable
} from 'modules/item/utils'
import { EngineType, getItemData, getModelData } from 'lib/getModelData'
import { getExtension, toMB } from 'lib/file'
-import { buildThirdPartyURN, DecodedURN, decodeURN, isThirdParty, URNType } from 'lib/urn'
+import {
+ buildThirdPartyURN,
+ buildThirdPartyV2URN,
+ DecodedURN,
+ decodeURN,
+ isThirdParty,
+ isThirdPartyCollectionDecodedUrn,
+ isThirdPartyV2CollectionDecodedUrn
+} from 'lib/urn'
import ItemDropdown from 'components/ItemDropdown'
import Icon from 'components/Icon'
import ItemVideo from 'components/ItemVideo'
import ItemRequiredPermission from 'components/ItemRequiredPermission'
import ItemProperties from 'components/ItemProperties'
+import { calculateFileSize, calculateModelFinalSize } from 'modules/item/export'
+import { MAX_THUMBNAIL_SIZE } from 'modules/assetPack/utils'
+import { Authorization } from 'lib/api/auth'
+import { BUILDER_SERVER_URL, BuilderAPI } from 'lib/api/builder'
import EditPriceAndBeneficiaryModal from '../EditPriceAndBeneficiaryModal'
import ImportStep from './ImportStep/ImportStep'
import EditThumbnailStep from './EditThumbnailStep/EditThumbnailStep'
@@ -72,18 +93,6 @@ import {
ITEM_LOADED_CHECK_DELAY
} from './CreateSingleItemModal.types'
import './CreateSingleItemModal.css'
-import { calculateFileSize, calculateModelFinalSize } from 'modules/item/export'
-import { MAX_THUMBNAIL_SIZE } from 'modules/assetPack/utils'
-import { Authorization } from 'lib/api/auth'
-import { BUILDER_SERVER_URL, BuilderAPI } from 'lib/api/builder'
-import {
- MAX_EMOTE_FILE_SIZE,
- MAX_SKIN_FILE_SIZE,
- MAX_THUMBNAIL_FILE_SIZE,
- MAX_WEARABLE_FILE_SIZE,
- MAX_SMART_WEARABLE_FILE_SIZE
-} from '@dcl/builder-client/dist/files/constants'
-
export default class CreateSingleItemModal extends React.PureComponent {
state: State = this.getInitialState()
thumbnailInput = React.createRef()
@@ -223,7 +232,7 @@ export default class CreateSingleItemModal extends React.PureComponent {
- const { address, collection } = this.props
+ const { address, collection, onSave } = this.props
const {
id,
name,
@@ -244,12 +253,15 @@ export default class CreateSingleItemModal extends React.PureComponent | null = collection?.urn ? decodeURN(collection.urn) : null
let urn: string | undefined
- if (
- decodedCollectionUrn &&
- decodedCollectionUrn.type === URNType.COLLECTIONS_THIRDPARTY &&
- decodedCollectionUrn.thirdPartyCollectionId
- ) {
+ if (decodedCollectionUrn && isThirdPartyCollectionDecodedUrn(decodedCollectionUrn)) {
urn = buildThirdPartyURN(decodedCollectionUrn.thirdPartyName, decodedCollectionUrn.thirdPartyCollectionId, uuid.v4())
+ } else if (decodedCollectionUrn && isThirdPartyV2CollectionDecodedUrn(decodedCollectionUrn)) {
+ urn = buildThirdPartyV2URN(
+ decodedCollectionUrn.thirdPartyLinkedCollectionName,
+ decodedCollectionUrn.linkedCollectionNetwork,
+ decodedCollectionUrn.linkedCollectionContractAddress,
+ uuid.v4()
+ )
}
// create item to save
@@ -278,7 +290,7 @@ export default class CreateSingleItemModal extends React.PureComponent = {
id,
name,
urn,
@@ -303,8 +315,15 @@ export default class CreateSingleItemModal extends React.PureComponent {
@@ -416,7 +437,6 @@ export default class CreateSingleItemModal extends React.PureComponent
}
filter={this.filterItemsByBodyShape}
onChange={this.handleItemChange}
isDisabled={isAddingRepresentation}
@@ -1180,8 +1200,9 @@ export default class CreateSingleItemModal extends React.PureComponent
diff --git a/src/components/Modals/CreateSingleItemModal/CreateSingleItemModal.types.ts b/src/components/Modals/CreateSingleItemModal/CreateSingleItemModal.types.ts
index 86a598869..9e95f37ab 100644
--- a/src/components/Modals/CreateSingleItemModal/CreateSingleItemModal.types.ts
+++ b/src/components/Modals/CreateSingleItemModal/CreateSingleItemModal.types.ts
@@ -40,7 +40,7 @@ export type StateData = {
metrics: Metrics
contents: Record
isRepresentation: boolean
- item: Item
+ item: Item
collectionId: string
isLoading: boolean
error: string
diff --git a/src/components/Modals/EditPriceAndBeneficiaryModal/EditPriceAndBeneficiaryModal.tsx b/src/components/Modals/EditPriceAndBeneficiaryModal/EditPriceAndBeneficiaryModal.tsx
index 26358f7b5..1bf5e0f68 100644
--- a/src/components/Modals/EditPriceAndBeneficiaryModal/EditPriceAndBeneficiaryModal.tsx
+++ b/src/components/Modals/EditPriceAndBeneficiaryModal/EditPriceAndBeneficiaryModal.tsx
@@ -22,7 +22,7 @@ import { toFixedMANAValue } from 'decentraland-dapps/dist/lib/mana'
import Info from 'components/Info'
import { isValid } from 'lib/address'
-import { Item } from 'modules/item/types'
+import { Item, ItemType } from 'modules/item/types'
import { Props, State } from './EditPriceAndBeneficiaryModal.types'
import './EditPriceAndBeneficiaryModal.css'
@@ -80,13 +80,13 @@ export default class EditPriceAndBeneficiaryModal extends React.PureComponent = {
...item,
price: priceInWei,
beneficiary
}
// Send itemSortedContents if this modal was opened from CreateSingleItem modal.
- onSave(newItem, itemSortedContents ?? {})
+ onSave(newItem as Item, itemSortedContents ?? {})
}
}
diff --git a/src/components/Modals/EditPriceAndBeneficiaryModal/EditPriceAndBeneficiaryModal.types.ts b/src/components/Modals/EditPriceAndBeneficiaryModal/EditPriceAndBeneficiaryModal.types.ts
index 679a8c716..8a0c47fc4 100644
--- a/src/components/Modals/EditPriceAndBeneficiaryModal/EditPriceAndBeneficiaryModal.types.ts
+++ b/src/components/Modals/EditPriceAndBeneficiaryModal/EditPriceAndBeneficiaryModal.types.ts
@@ -1,6 +1,6 @@
import { Dispatch } from 'redux'
import { ModalProps } from 'decentraland-dapps/dist/providers/ModalProvider/ModalProvider.types'
-import { Item } from 'modules/item/types'
+import { Item, ItemType } from 'modules/item/types'
import {
saveItemRequest,
SaveItemRequestAction,
@@ -9,7 +9,7 @@ import {
} from 'modules/item/actions'
export type Props = ModalProps & {
- item: Item
+ item: Item
error: string | null
isLoading: boolean
metadata: EditPriceAndBeneficiaryModalMetadata
diff --git a/src/components/ThirdPartyCollectionDetailPage/CollectionItem/CollectionItem.tsx b/src/components/ThirdPartyCollectionDetailPage/CollectionItem/CollectionItem.tsx
index 983d399a8..ef530bb58 100644
--- a/src/components/ThirdPartyCollectionDetailPage/CollectionItem/CollectionItem.tsx
+++ b/src/components/ThirdPartyCollectionDetailPage/CollectionItem/CollectionItem.tsx
@@ -4,7 +4,7 @@ import { Grid, Dropdown, Icon, Button, Checkbox, CheckboxProps, Popup } from 'de
import { Link, useHistory } from 'react-router-dom'
import { locations } from 'routing/locations'
import { preventDefault } from 'lib/event'
-import { decodeURN, URNType } from 'lib/urn'
+import { decodeURN, isThirdPartyCollectionDecodedUrn, isThirdPartyV2CollectionDecodedUrn } from 'lib/urn'
import ItemStatus from 'components/ItemStatus'
import { SyncStatus } from 'modules/item/types'
import { FromParam } from 'modules/location/types'
@@ -47,8 +47,14 @@ export default function CollectionItem({ item, status, selected, onSelect, onOpe
return ''
}
- const decodedURN = decodeURN(item.urn)
- return decodedURN.type === URNType.COLLECTIONS_THIRDPARTY ? decodedURN.thirdPartyTokenId : ''
+ try {
+ const decodedURN = decodeURN(item.urn)
+ return isThirdPartyCollectionDecodedUrn(decodedURN) || isThirdPartyV2CollectionDecodedUrn(decodedURN)
+ ? decodedURN.thirdPartyTokenId ?? ''
+ : ''
+ } catch (error) {
+ return ''
+ }
}, [item])
const statusIcon = useMemo(() => {
diff --git a/src/modules/editor/utils.ts b/src/modules/editor/utils.ts
index fa5a2d2f5..03ae7bc45 100644
--- a/src/modules/editor/utils.ts
+++ b/src/modules/editor/utils.ts
@@ -8,7 +8,7 @@ import { getContentsStorageUrl } from 'lib/api/builder'
import { capitalize } from 'lib/text'
import { Color4 } from 'lib/colors'
import { Vector3 } from 'modules/models/types'
-import { getSkinHiddenCategories } from 'modules/item/utils'
+import { getSkinHiddenCategories, isEmote } from 'modules/item/utils'
import { EntityDefinition, ComponentDefinition, ComponentType, SceneSDK6 } from 'modules/scene/types'
import { injectScript } from 'routing/utils'
import { base64ArrayBuffer } from './base64'
@@ -342,7 +342,7 @@ export function toEmote(item: Item): EmoteDefinition {
* @param item - an Item
*/
export function toBase64(item: Item | Item): string {
- const wearable = item.type === ItemType.EMOTE ? toEmote(item as Item) : toWearable(item as Item)
+ const wearable = isEmote(item) ? toEmote(item) : toWearable(item)
const stringified = JSON.stringify(wearable)
const sanitized = stringified.replace(/[^\x20-\x7F]/g, '')
return btoa(sanitized)
diff --git a/src/modules/item/reducer.ts b/src/modules/item/reducer.ts
index 3fc9966b9..58001ae11 100644
--- a/src/modules/item/reducer.ts
+++ b/src/modules/item/reducer.ts
@@ -110,7 +110,15 @@ import {
} from 'modules/thirdParty/actions'
import { toItemObject } from './utils'
import { Item, BlockchainRarity } from './types'
-import { buildCatalystItemURN, buildThirdPartyURN, decodeURN, URNType } from 'lib/urn'
+import {
+ buildCatalystItemURN,
+ buildThirdPartyURN,
+ buildThirdPartyV2URN,
+ decodeURN,
+ isThirdPartyCollectionDecodedUrn,
+ isThirdPartyV2CollectionDecodedUrn,
+ URNType
+} from 'lib/urn'
import { CLOSE_MODAL, CloseModalAction } from 'decentraland-dapps/dist/modules/modal/actions'
export type ItemPaginationData = {
@@ -509,15 +517,18 @@ export function itemReducer(state: ItemState = INITIAL_STATE, action: ItemReduce
if (item.collectionId === collection.id && item.urn) {
let newItemURN: string
const itemURN = decodeURN(item.urn)
- if (collectionURN.type === URNType.COLLECTIONS_THIRDPARTY) {
- if (itemURN.type !== URNType.COLLECTIONS_THIRDPARTY) {
+ if (isThirdPartyCollectionDecodedUrn(collectionURN) || isThirdPartyV2CollectionDecodedUrn(collectionURN)) {
+ if (!isThirdPartyCollectionDecodedUrn(itemURN) && !isThirdPartyV2CollectionDecodedUrn(itemURN)) {
throw new Error(`The item ${item.id} is not part of a third-party collection but it should be`)
}
- newItemURN = buildThirdPartyURN(
- collectionURN.thirdPartyName,
- collectionURN.thirdPartyCollectionId!,
- itemURN.thirdPartyTokenId
- )
+ newItemURN = isThirdPartyCollectionDecodedUrn(collectionURN)
+ ? buildThirdPartyURN(collectionURN.thirdPartyName, collectionURN.thirdPartyCollectionId, itemURN.thirdPartyTokenId)
+ : buildThirdPartyV2URN(
+ collectionURN.thirdPartyLinkedCollectionName,
+ collectionURN.linkedCollectionNetwork,
+ collectionURN.linkedCollectionContractAddress,
+ itemURN.thirdPartyTokenId
+ )
} else if (collectionURN.type === URNType.COLLECTIONS_V2) {
if (itemURN.type !== URNType.COLLECTIONS_V2) {
throw new Error(`The item ${item.id} is not part of a decentraland collection but it should be`)
diff --git a/src/modules/item/sagas.ts b/src/modules/item/sagas.ts
index 1453f5d74..d7b2bbefd 100644
--- a/src/modules/item/sagas.ts
+++ b/src/modules/item/sagas.ts
@@ -131,17 +131,7 @@ import { getCatalystContentUrl } from 'lib/api/peer'
import { downloadZip } from 'lib/zip'
import { isErrorWithCode } from 'lib/error'
import { calculateModelFinalSize, calculateFileSize, reHashOlderContents } from './export'
-import {
- Item,
- BlockchainRarity,
- CatalystItem,
- BodyShapeType,
- IMAGE_PATH,
- THUMBNAIL_PATH,
- WearableData,
- ItemType,
- VIDEO_PATH
-} from './types'
+import { Item, BlockchainRarity, CatalystItem, BodyShapeType, IMAGE_PATH, THUMBNAIL_PATH, WearableData, VIDEO_PATH } from './types'
import { getData as getItemsById, getItems, getEntityByItemId, getCollectionItems, getItem, getPaginationData } from './selectors'
import {
ItemEmoteTooBigError,
@@ -151,7 +141,17 @@ import {
ThumbnailFileTooBigError,
VideoFileTooBigError
} from './errors'
-import { buildZipContents, getMetadata, groupsOf, isValidText, generateCatalystImage, MAX_VIDEO_FILE_SIZE, isSmart } from './utils'
+import {
+ buildZipContents,
+ getMetadata,
+ groupsOf,
+ isValidText,
+ generateCatalystImage,
+ MAX_VIDEO_FILE_SIZE,
+ isSmart,
+ isWearable,
+ isEmote
+} from './utils'
import { ItemPaginationData } from './reducer'
import { getSuccessfulDeletedItemToast, getSuccessfulMoveItemToAnotherCollectionToast } from './toasts'
@@ -362,10 +362,7 @@ export function* itemSaga(legacyBuilder: LegacyBuilderAPI, builder: BuilderClien
const oldItem: Item | undefined = yield select(getItem, actionItem.id)
const rarityChanged = oldItem && oldItem.rarity !== item.rarity
const shouldValidateCategoryChanged =
- !!oldItem &&
- oldItem.type === ItemType.WEARABLE &&
- oldItem.data.category !== item.data.category &&
- oldItem.data.category === WearableCategory.SKIN
+ !!oldItem && isWearable(oldItem) && oldItem.data.category !== item.data.category && oldItem.data.category === WearableCategory.SKIN
if (!isValidText(item.name) || !isValidText(item.description)) {
throw new Error(t('sagas.item.invalid_character'))
@@ -428,11 +425,11 @@ export function* itemSaga(legacyBuilder: LegacyBuilderAPI, builder: BuilderClien
}
}
- const isEmote = item.type === ItemType.EMOTE
- const isSkin = !isEmote && item.data.category === WearableCategory.SKIN
+ const isEmoteItem = isEmote(item)
+ const isSkin = !isEmoteItem && item.data.category === WearableCategory.SKIN
const isSmartWearable = isSmart(item)
- if (isEmote && finalModelSize > MAX_EMOTE_FILE_SIZE) {
+ if (isEmoteItem && finalModelSize > MAX_EMOTE_FILE_SIZE) {
throw new ItemEmoteTooBigError()
}
@@ -444,7 +441,7 @@ export function* itemSaga(legacyBuilder: LegacyBuilderAPI, builder: BuilderClien
throw new ItemSmartWearableTooBigError()
}
- if (!isSkin && !isSmartWearable && !isEmote && finalModelSize > MAX_WEARABLE_FILE_SIZE) {
+ if (!isSkin && !isSmartWearable && !isEmoteItem && finalModelSize > MAX_WEARABLE_FILE_SIZE) {
throw new ItemWearableTooBigError()
}
}
@@ -489,7 +486,7 @@ export function* itemSaga(legacyBuilder: LegacyBuilderAPI, builder: BuilderClien
if (ItemModals.some(modal => openModals[modal])) {
yield put(closeAllModals())
} else if (openModals['CreateSingleItemModal']) {
- if (location.pathname === locations.collectionDetail(collectionId) && item.type === ItemType.EMOTE) {
+ if (location.pathname === locations.collectionDetail(collectionId) && isEmote(item)) {
// Redirect to the item editor
yield put(setItems([item]))
history.push(locations.itemEditor({ collectionId, itemId: item.id, newItem: item.name }), { fromParam: FromParam.COLLECTIONS })
diff --git a/src/modules/item/selectors.ts b/src/modules/item/selectors.ts
index 145b957e0..99a767361 100644
--- a/src/modules/item/selectors.ts
+++ b/src/modules/item/selectors.ts
@@ -18,8 +18,8 @@ import { isEqual } from 'lib/address'
import { buildCatalystItemURN, isThirdParty } from '../../lib/urn'
import { DOWNLOAD_ITEM_REQUEST } from './actions'
import { ItemState } from './reducer'
-import { Item, SyncStatus, BlockchainRarity, CatalystItem, ItemType, VIDEO_PATH } from './types'
-import { areSynced, canSeeItem, isOwner, isSmart } from './utils'
+import { Item, SyncStatus, BlockchainRarity, CatalystItem, VIDEO_PATH } from './types'
+import { areSynced, canSeeItem, isEmote, isOwner, isSmart, isWearable } from './utils'
import { getSearch } from 'connected-react-router'
export const getState = (state: RootState) => state.item
@@ -84,11 +84,9 @@ export const getRarities = (state: RootState): BlockchainRarity[] => {
return getState(state).rarities
}
-export const getWearables = createSelector(getItems, items =>
- items.filter(item => item.type === ItemType.WEARABLE)
-)
+export const getWearables = createSelector(getItems, items => items.filter(isWearable))
-export const getEmotes = createSelector(getItems, items => items.filter(item => item.type === ItemType.EMOTE))
+export const getEmotes = createSelector(getItems, items => items.filter(isEmote))
export const getItemsByURN = createSelector, Record>(
state => getItems(state),