Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Ceate linked collection items support #3135

Merged
merged 7 commits into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 6 additions & 8 deletions src/components/CollectionDetailPage/CollectionDetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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 (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -176,7 +176,7 @@ export default function CollectionItem({ onOpenModal, onSetItems, item, collecti
{item.rarity && data.category ? <RarityBadge size="medium" rarity={item.rarity} withTooltip /> : null}
</Table.Cell>
<Table.Cell className={styles.column}>{data.category ? <div>{t(`${item.type}.category.${data.category}`)}</div> : null}</Table.Cell>
{item.type === ItemType.EMOTE && isEmoteData(data) ? (
{isEmote(item) && isEmoteData(data) ? (
<Table.Cell className={styles.column}>
{data.category ? <div>{t(`emote.play_mode.${data.loop ? 'loop' : 'simple'}.text`)}</div> : null}
</Table.Cell>
Expand Down
4 changes: 2 additions & 2 deletions src/components/ItemDetailPage/ItemDetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -347,7 +347,7 @@ export default function ItemDetailPage(props: Props) {
</div>
</div>

{item.type === ItemType.WEARABLE && !isSmart(item) ? (
{isWearable(item) && !isSmart(item) ? (
<div className="card">
<div className="title-card-container">
<div className="title">{t('item_detail_page.representations.title')}</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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.
Expand Down
3 changes: 2 additions & 1 deletion src/components/ItemEditorPage/CenterPanel/CenterPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -272,7 +273,7 @@ export default class CenterPanel extends React.PureComponent<Props, State> {
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 (
Expand Down
8 changes: 4 additions & 4 deletions src/components/ItemEditorPage/LeftPanel/Items/Items.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -77,7 +77,7 @@ export default class Items extends React.PureComponent<Props, State> {

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
Expand Down Expand Up @@ -165,8 +165,8 @@ export default class Items extends React.PureComponent<Props, State> {
}

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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -18,7 +17,7 @@ class SidebarItem extends React.PureComponent<Props> {

renderToggleItem() {
const { item, isVisible, isPlayingEmote } = this.props
if (item.type === ItemType.EMOTE) {
if (isEmote(item)) {
return <Icon className="toggle-emote" name={isVisible && isPlayingEmote ? 'pause' : 'play'} onClick={this.handleClick} />
} else {
return <div className={`toggle ${isVisible ? 'is-visible' : 'is-hidden'}`} onClick={this.handleClick}></div>
Expand Down
7 changes: 4 additions & 3 deletions src/components/ItemEditorPage/RightPanel/RightPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -83,7 +84,7 @@ export default class RightPanel extends React.PureComponent<Props, State> {
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 = []
Expand Down Expand Up @@ -525,7 +526,7 @@ export default class RightPanel extends React.PureComponent<Props, State> {
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 ? (
<Loader size="massive" active />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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<any> | 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<any> | 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()
)
)
}
}
Expand Down
Loading
Loading