Skip to content

Commit e5e3a5d

Browse files
fat: Add migration support to collection window (#3161)
* feat: Add migration support to collection window * feat: Add ItemStatusBadge * fix: Update imports * fix: Revert URL change * fix: Remove unused translations * fix: Add translations
1 parent 5774f59 commit e5e3a5d

File tree

17 files changed

+186
-37
lines changed

17 files changed

+186
-37
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
.status {
2+
text-transform: uppercase;
3+
padding: 4px 8px 4px 8px;
4+
border-radius: 32px;
5+
display: inline-flex;
6+
align-items: center;
7+
gap: 4px;
8+
}
9+
10+
.status :global(.icon) {
11+
height: 18px;
12+
}
13+
14+
.pending_migration {
15+
color: #ffbc5b;
16+
background-color: rgb(255, 188, 91, 0.2);
17+
}
18+
19+
.synced {
20+
color: #65d890;
21+
background-color: rgb(101, 216, 144, 0.2);
22+
}
23+
24+
.unpublished,
25+
.unsynced {
26+
color: #63b4f6;
27+
background-color: rgb(99, 180, 246, 0.2);
28+
}
29+
30+
.pending_mapping,
31+
.under_review {
32+
color: #ecebed;
33+
background-color: rgb(236, 235, 237, 0.2);
34+
}
35+
36+
.icon {
37+
width: 17px;
38+
height: 17px;
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { useMemo } from 'react'
2+
import classNames from 'classnames'
3+
import { Icon } from 'decentraland-ui'
4+
import { t } from 'decentraland-dapps/dist/modules/translation'
5+
import { SyncStatus } from 'modules/item/types'
6+
import mapIcon from '../../icons/map.svg'
7+
import underReviewIcon from '../../icons/under_review.svg'
8+
import { isThirdParty } from 'lib/urn'
9+
import { Props } from './ItemStatusBadge.types'
10+
import styles from './ItemStatusBadge.module.css'
11+
12+
export const ItemStatusBadge = (props: Props) => {
13+
const { status, item } = props
14+
const isMappingMissingFromItem = !item.mappings && isThirdParty(item.urn)
15+
const isMappingMissingInCatalysts = !item.isMappingComplete && isThirdParty(item.urn)
16+
17+
const icon = useMemo(() => {
18+
if (isMappingMissingFromItem) {
19+
return <img src={mapIcon} className={styles.icon} />
20+
} else if (isMappingMissingInCatalysts) {
21+
return <Icon name="warning circle" />
22+
}
23+
switch (status) {
24+
case SyncStatus.UNDER_REVIEW:
25+
return <img src={underReviewIcon} className={styles.icon} />
26+
case SyncStatus.SYNCED:
27+
return <Icon name="check circle" />
28+
case SyncStatus.UNSYNCED:
29+
case SyncStatus.UNPUBLISHED:
30+
return <Icon name="cloud upload" />
31+
}
32+
}, [status])
33+
34+
const text = useMemo(() => {
35+
if (isMappingMissingFromItem) {
36+
return t('item_status.pending_mapping')
37+
} else if (isMappingMissingInCatalysts) {
38+
return t('item_status.pending_migration')
39+
}
40+
return t(`item_status.${status}`)
41+
}, [status])
42+
43+
const style = useMemo(() => {
44+
if (!item.mappings) {
45+
return 'pending_mapping'
46+
} else if (isMappingMissingInCatalysts) {
47+
return 'pending_migration'
48+
}
49+
return status
50+
}, [status])
51+
52+
return (
53+
<div className={classNames(styles[style], styles.status)}>
54+
{icon} {text}
55+
</div>
56+
)
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { SyncStatus, Item } from 'modules/item/types'
2+
3+
export type Props = {
4+
status: SyncStatus
5+
item: Item
6+
}
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './ItemStatusBadge'

src/components/MappingEditor/MappingEditor.module.css

+19
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
/* Input margin */
3636

3737
.compact .any,
38+
.compact .none,
3839
.compact .multiple {
3940
margin-top: 19px;
4041
}
@@ -53,6 +54,24 @@
5354
margin-top: 0px;
5455
}
5556

57+
.mappingType :global(.default.text) {
58+
display: flex !important;
59+
flex-direction: row;
60+
align-items: center;
61+
}
62+
63+
.mappingType :global(.default.text)::before {
64+
content: ' ';
65+
background-size: contain;
66+
background-repeat: no-repeat;
67+
display: block;
68+
background-image: url(../../icons/map.svg);
69+
bottom: 4px;
70+
width: 24px;
71+
height: 24px;
72+
margin-right: 10px;
73+
}
74+
5675
/* Linked contract select class */
5776

5877
.linkedContractSelect :global(.divider.text),

src/components/MappingEditor/MappingEditor.tsx

+8-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ export const MappingEditor = (props: Props) => {
2121
const { mapping, error, loading, disabled, isCompact, onChange } = props
2222

2323
const [mappingType, mappingValue] = useMemo(() => {
24+
if (!mapping) {
25+
return [undefined, '']
26+
}
27+
2428
switch (mapping.type) {
2529
case MappingType.MULTIPLE:
2630
return [MappingType.MULTIPLE, mapping.ids.join(', ')]
@@ -115,13 +119,16 @@ export const MappingEditor = (props: Props) => {
115119
<SelectField
116120
label={isCompact ? undefined : t('mapping_editor.mapping_type_label')}
117121
onChange={handleMappingTypeChange}
122+
placeholder={t('mapping_editor.mapping_type_placeholder')}
118123
value={mappingType}
119124
disabled={disabled}
120125
className={classNames(styles.mappingType, isCompact ? styles.compact : styles.full)}
121126
options={mappingTypeOptions}
122127
/>
123128
<div className={classNames(styles.mappings, isCompact ? styles.compact : styles.full)}>
124-
{mappingType === MappingType.ANY ? (
129+
{mappingType === undefined ? (
130+
<Field loading={loading} className={styles.none} disabled />
131+
) : mappingType === MappingType.ANY ? (
125132
<Field
126133
label={isCompact ? undefined : t('mapping_editor.mapping_value_label')}
127134
loading={loading}

src/components/MappingEditor/MappingEditor.types.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Mapping } from '@dcl/schemas'
22

33
export type Props = {
4-
mapping: Mapping
4+
mapping: Mapping | null
55
loading?: boolean
66
isCompact?: boolean
77
error?: string

src/components/ThirdPartyCollectionDetailPage/CollectionItemHeaderV2/CollectionItemHeaderV2.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export const CollectionItemHeaderV2 = ({ areAllSelected, onSelectedAllClick }: P
6262
<LinkingTooltipContent />
6363
</InfoTooltip>
6464
</Grid.Column>
65-
<Grid.Column width={2}> {t('collection_row.status')} </Grid.Column>
65+
<Grid.Column width={3}> {t('collection_row.status')} </Grid.Column>
6666
<Grid.Column width={1}></Grid.Column>
6767
</Grid.Row>
6868
</Grid>

src/components/ThirdPartyCollectionDetailPage/CollectionItemV2/CollectionItemV2.module.css

-13
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,6 @@
3232
display: flex !important;
3333
}
3434

35-
.statusColumn {
36-
font-size: 13px;
37-
text-transform: uppercase;
38-
}
39-
40-
.statusColumn.synced {
41-
color: #34ce76;
42-
}
43-
44-
.statusColumn.unsynced {
45-
color: #fc9965;
46-
}
47-
4835
.avatarColumn {
4936
align-items: center;
5037
padding-left: 10px !important;

src/components/ThirdPartyCollectionDetailPage/CollectionItemV2/CollectionItemV2.tsx

+11-21
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ import { Link, useHistory } from 'react-router-dom'
77
import { locations } from 'routing/locations'
88
import { preventDefault } from 'lib/event'
99
import { debounce } from 'lib/debounce'
10-
import { SyncStatus } from 'modules/item/types'
1110
import { FromParam } from 'modules/location/types'
1211
import ConfirmDelete from 'components/ConfirmDelete'
12+
import { ItemStatusBadge } from 'components/ItemStatusBadge'
1313
import { buildItemMappings, getMapping } from 'modules/item/utils'
1414
import { MappingEditor } from 'components/MappingEditor'
1515
import { LinkedContract } from 'modules/thirdParty/types'
@@ -51,7 +51,13 @@ export default function CollectionItemV2({
5151
}, [localMapping, contract])
5252

5353
useEffect(() => {
54-
if (mapping && localMapping && contract && !error && !areMappingsEqual(mapping, localMapping)) {
54+
if (!contract || error) {
55+
return
56+
}
57+
58+
if (!mapping && localMapping) {
59+
handleSaveItem(localMapping, contract)
60+
} else if (mapping && localMapping && !areMappingsEqual(mapping, localMapping)) {
5561
handleSaveItem(localMapping, contract)
5662
}
5763
}, [mapping, error, localMapping, contract])
@@ -94,19 +100,6 @@ export default function CollectionItemV2({
94100
onDelete(item)
95101
}, [onDelete, item])
96102

97-
const statusIcon = useMemo(() => {
98-
switch (status) {
99-
case SyncStatus.UNDER_REVIEW:
100-
return <Icon name="clock outline" />
101-
case SyncStatus.SYNCED:
102-
return <Icon name="check circle outline" />
103-
case SyncStatus.UNSYNCED:
104-
return <Icon name="exclamation circle" />
105-
default:
106-
return null
107-
}
108-
}, [status])
109-
110103
return (
111104
<Grid className={classNames(styles.grid, loading && styles.loading)} columns="equal">
112105
{loading && (
@@ -125,13 +118,10 @@ export default function CollectionItemV2({
125118
</div>
126119
</Grid.Column>
127120
<Grid.Column>
128-
{contract && localMapping && (
129-
<MappingEditor disabled={loading} mapping={localMapping} error={error} onChange={setLocalMapping} isCompact />
130-
)}
121+
{contract && <MappingEditor disabled={loading} mapping={localMapping} error={error} onChange={setLocalMapping} isCompact />}
131122
</Grid.Column>
132-
<Grid.Column width={2} className={`${styles.column} ${styles.statusColumn} ${styles[status]}`}>
133-
{statusIcon}
134-
<div>{t(`third_party_collection_detail_page.synced_statuses.${status}`)}</div>
123+
<Grid.Column width={3} className={classNames(styles.column, styles.statusColumn)}>
124+
<ItemStatusBadge status={status} item={item} />
135125
</Grid.Column>
136126
<Grid.Column width={1} className={styles.column}>
137127
<div className={styles.itemActions}>

src/icons/map.svg

+10
Loading

src/icons/under_review.svg

+3
Loading

src/lib/api/builder.ts

+2
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ export type RemoteItem = {
7979
mappings: Partial<Record<ContractNetwork, Record<ContractAddress, Mapping[]>>> | null
8080
local_content_hash: string | null
8181
catalyst_content_hash: string | null
82+
isMappingComplete?: boolean
8283
}
8384

8485
export type RemoteCollection = {
@@ -382,6 +383,7 @@ function fromRemoteItem(remoteItem: RemoteItem) {
382383
catalystContentHash: remoteItem.catalyst_content_hash,
383384
metrics: remoteItem.metrics,
384385
mappings: remoteItem.mappings,
386+
isMappingComplete: remoteItem.isMappingComplete,
385387
createdAt: +new Date(remoteItem.created_at),
386388
updatedAt: +new Date(remoteItem.created_at)
387389
}

src/modules/item/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ export type Item<T = ItemType.WEARABLE> = Omit<BaseItem, 'metrics'> & {
123123
data: T extends ItemType.WEARABLE ? WearableData : EmoteDataADR74
124124
metrics: T extends ItemType.WEARABLE ? ModelMetrics : AnimationMetrics
125125
mappings: Partial<Record<ContractNetwork, Record<ContractAddress, Mapping[]>>> | null
126+
isMappingComplete?: boolean
126127
}
127128

128129
export const isEmoteItemType = (item: Item | Item<ItemType.EMOTE>): item is Item<ItemType.EMOTE> =>

src/modules/translation/languages/en.json

+9
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@
278278
"range": "e.g. [1, 1000]"
279279
},
280280
"mapping_type_label": "Linked to",
281+
"mapping_type_placeholder": "Select",
281282
"mapping_value_label": "Token ID",
282283
"mapping_value_multiple_label": "Token IDs",
283284
"mapping_value_any": "Any token ID",
@@ -2339,5 +2340,13 @@
23392340
"description_dcl": "Meanwhile, Worlds from Decentraland NAMEs will have a dynamic maximum file size limit depending on a user's Decentraland holdings. <b>Each NAME, LAND, and set of 2,000 MANA tokens owned </b> will grant an additional <b>100 Mb of storage</b> on the Worlds content server to be used as you wish.",
23402341
"start_building": "start building",
23412342
"learn_more": "learn more"
2343+
},
2344+
"item_status": {
2345+
"unpublished": "Ready to publish",
2346+
"under_review": "Under Review",
2347+
"synced": "Published",
2348+
"unsynced": "Ready to push changes",
2349+
"pending_migration": "Pending Migration",
2350+
"pending_mapping": "Pending Mapping"
23422351
}
23432352
}

src/modules/translation/languages/es.json

+9
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,7 @@
279279
"range": "p. ej. [1, 1000]"
280280
},
281281
"mapping_type_label": "Vinculado a",
282+
"mapping_type_placeholder": "Seleccionar",
282283
"mapping_value_label": "Token ID",
283284
"mapping_value_multiple_label": "Token IDs",
284285
"mapping_value_any": "Todos los token IDs",
@@ -2357,5 +2358,13 @@
23572358
"description_dcl": "Mientras tanto, los Mundos de los NAMEs de Decentraland tendrán un límite de tamaño máximo dinámico dependiendo de las tenencias del usuario. <b>Cada NAME, LAND y 2,000 tokens MANA poseídos</b> otorgarán un adicional de <b>100 Mb de almacenamiento</b> en el servidor de contenido de Mundos para ser utilizado como desee.",
23582359
"start_building": "comenzar a construir",
23592360
"learn_more": "aprende más"
2361+
},
2362+
"item_status": {
2363+
"unpublished": "Listos para publicar",
2364+
"under_review": "Bajo revisión",
2365+
"synced": "Published",
2366+
"unsynced": "Listo para enviar cambios",
2367+
"pending_migration": "Migración pendiente",
2368+
"pending_mapping": "Mapeo pendiente"
23602369
}
23612370
}

src/modules/translation/languages/zh.json

+9
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@
273273
"range": "例如 [1, 1000]"
274274
},
275275
"mapping_type_label": "链接到",
276+
"mapping_type_placeholder": "选择",
276277
"mapping_value_label": "代币 ID",
277278
"mapping_value_multiple_label": "代币 ID",
278279
"mapping_value_any": "任何代币 ID",
@@ -2339,5 +2340,13 @@
23392340
"description_dcl": "同时,来自Decentraland NAME的世界将具有动态的最大文件大小限制,具体取决于用户的Decentraland持有量。 <b>每个NAME、LAND和2,000个MANA代币的集合所拥有的</b>将在Worlds内容服务器上额外获得<b>100 Mb的存储空间</b>,可随意使用。",
23402341
"start_building": "开始建设",
23412342
"learn_more": "了解更多"
2343+
},
2344+
"item_status": {
2345+
"unpublished": "准备发布",
2346+
"under_review": "审查中",
2347+
"synced": "已发表",
2348+
"unsynced": "已准备好发送更改",
2349+
"pending_migration": "待迁移",
2350+
"pending_mapping": "待定映射"
23422351
}
23432352
}

0 commit comments

Comments
 (0)