Skip to content

Commit 14f6d08

Browse files
committed
fix: Play emotes when selecting them in the LeftPanel, also reset emote state when changing collections
1 parent bb1d19a commit 14f6d08

File tree

5 files changed

+560
-258
lines changed

5 files changed

+560
-258
lines changed

src/components/ItemEditorPage/LeftPanel/LeftPanel.container.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { useCallback, useMemo } from 'react'
22
import { useSelector, useDispatch } from 'react-redux'
3+
import { PreviewEmote } from '@dcl/schemas'
34
import { RootState } from 'modules/common/types'
45
import { isLoadingType } from 'decentraland-dapps/dist/modules/loading/selectors'
56
import { isConnected, getAddress } from 'decentraland-dapps/dist/modules/wallet/selectors'
@@ -21,7 +22,7 @@ import {
2122
} from 'modules/item/selectors'
2223
import { fetchCollectionsRequest } from 'modules/collection/actions'
2324
import { getAuthorizedCollections, getPaginationData as getCollectionsPaginationData } from 'modules/collection/selectors'
24-
import { setItems } from 'modules/editor/actions'
25+
import { setEmote, setItems } from 'modules/editor/actions'
2526
import { fetchItemsRequest, fetchOrphanItemRequest, FETCH_ITEMS_REQUEST, FETCH_ORPHAN_ITEM_REQUEST } from 'modules/item/actions'
2627
import { LeftPanelContainerProps } from './LeftPanel.types'
2728
import LeftPanel from './LeftPanel'
@@ -80,6 +81,7 @@ const LeftPanelContainer: React.FC<LeftPanelContainerProps> = props => {
8081
address => dispatch(fetchOrphanItemRequest(address)),
8182
[dispatch]
8283
)
84+
const onResetEmoteToIdle = useCallback(() => dispatch(setEmote(PreviewEmote.IDLE)), [dispatch])
8385

8486
return (
8587
<LeftPanel
@@ -105,6 +107,7 @@ const LeftPanelContainer: React.FC<LeftPanelContainerProps> = props => {
105107
onFetchCollections={onFetchCollections}
106108
onFetchOrphanItems={onFetchOrphanItems}
107109
onFetchOrphanItem={onFetchOrphanItem}
110+
onResetEmoteToIdle={onResetEmoteToIdle}
108111
/>
109112
)
110113
}
Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
import { useCallback, useEffect, useRef, useState } from 'react'
2+
import { Item, ItemType } from 'modules/item/types'
3+
import { INITIAL_PAGE, LEFT_PANEL_PAGE_SIZE } from '../constants'
4+
import { ItemEditorTabs, UseLeftPanelPaginationOptions, UseItemSelectionOptions, UseInitialDataFetchOptions } from './LeftPanel.types'
5+
6+
/**
7+
* Manages pagination state and logic for the LeftPanel component.
8+
*
9+
* Handles:
10+
* - Current page tracking
11+
* - Finding newly created items by iterating through pages
12+
* - Loading random pages for reviewing items
13+
* - Resetting pagination when collection/tab changes
14+
*/
15+
export const useLeftPanelPagination = (options: UseLeftPanelPaginationOptions) => {
16+
const {
17+
address,
18+
selectedCollectionId,
19+
selectedItemId,
20+
orphanItems,
21+
totalItems,
22+
totalCollections,
23+
isConnected,
24+
currentTab,
25+
onFetchResource,
26+
onSetReviewedItems
27+
} = options
28+
29+
const [currentPage, setCurrentPage] = useState(INITIAL_PAGE)
30+
const [visitedPages, setVisitedPages] = useState<number[]>([INITIAL_PAGE])
31+
32+
// Track previous values to detect changes
33+
const prevSelectedCollectionId = useRef(selectedCollectionId)
34+
const prevIsConnected = useRef(isConnected)
35+
const prevCurrentTab = useRef(currentTab)
36+
37+
const isCollectionTabActive = currentTab === ItemEditorTabs.COLLECTIONS && !selectedCollectionId
38+
39+
// Reset pagination when collection or tab changes
40+
useEffect(() => {
41+
const collectionChanged = prevSelectedCollectionId.current !== selectedCollectionId
42+
const connectionChanged = isConnected && !prevIsConnected.current
43+
const tabChanged = prevCurrentTab.current !== currentTab
44+
45+
if (collectionChanged || connectionChanged || tabChanged) {
46+
setCurrentPage(INITIAL_PAGE)
47+
setVisitedPages([INITIAL_PAGE])
48+
}
49+
50+
prevSelectedCollectionId.current = selectedCollectionId
51+
prevIsConnected.current = isConnected
52+
prevCurrentTab.current = currentTab
53+
}, [selectedCollectionId, isConnected, currentTab])
54+
55+
/**
56+
* When a newly created item redirects to the item editor, iterate over the pages
57+
* until finding it. This handles the case where the new item might be on a different page.
58+
*/
59+
useEffect(() => {
60+
if (selectedCollectionId || !address || !selectedItemId || !totalItems) {
61+
return
62+
}
63+
64+
const itemExists = orphanItems.some(item => item.id === selectedItemId)
65+
if (itemExists) {
66+
return
67+
}
68+
69+
const totalPages = Math.ceil(totalItems / LEFT_PANEL_PAGE_SIZE)
70+
const nextPage = Math.min(totalPages, currentPage + 1)
71+
72+
if (!visitedPages.includes(nextPage) && nextPage !== currentPage) {
73+
setCurrentPage(nextPage)
74+
setVisitedPages(prev => [...prev, nextPage])
75+
onFetchResource(nextPage)
76+
}
77+
}, [address, selectedCollectionId, selectedItemId, orphanItems, totalItems, currentPage, visitedPages, onFetchResource])
78+
79+
/**
80+
* Load a specific page
81+
*/
82+
const loadPage = useCallback(
83+
(page: number) => {
84+
setCurrentPage(page)
85+
setVisitedPages([page])
86+
onFetchResource(page)
87+
},
88+
[onFetchResource]
89+
)
90+
91+
/**
92+
* Load a random page for reviewing items.
93+
* Used during third-party item reviews to get a random sample of items.
94+
*/
95+
const loadRandomPage = useCallback(
96+
(currentItems: Item[]) => {
97+
const totalResources = isCollectionTabActive ? totalCollections : totalItems
98+
if (!totalResources) {
99+
onSetReviewedItems(currentItems)
100+
return
101+
}
102+
103+
const totalPages = Math.ceil(totalResources / LEFT_PANEL_PAGE_SIZE)
104+
105+
if (totalPages > visitedPages.length) {
106+
const availablePages = [...Array(totalPages).keys()].map(i => i + 1).filter(page => !visitedPages.includes(page))
107+
108+
if (availablePages.length > 0) {
109+
const randomPage = availablePages[Math.floor(Math.random() * availablePages.length)]
110+
setCurrentPage(randomPage)
111+
setVisitedPages(prev => [...prev, randomPage])
112+
onFetchResource(randomPage)
113+
}
114+
}
115+
116+
onSetReviewedItems(currentItems)
117+
},
118+
[isCollectionTabActive, totalCollections, totalItems, visitedPages, onFetchResource, onSetReviewedItems]
119+
)
120+
121+
/**
122+
* Handle page change from CollectionProvider
123+
*/
124+
const handlePageChange = useCallback((page: number) => {
125+
setCurrentPage(page)
126+
setVisitedPages([page])
127+
}, [])
128+
129+
return {
130+
currentPage,
131+
visitedPages,
132+
loadPage,
133+
loadRandomPage,
134+
handlePageChange,
135+
isCollectionTabActive
136+
}
137+
}
138+
139+
/**
140+
* Manages item selection and visibility for the LeftPanel component.
141+
*
142+
* Handles:
143+
* - Preselecting items when clicking on a collection
144+
* - Clearing visible items when changing collections
145+
* - Managing emote visibility for auto-play functionality
146+
*
147+
* EMOTE HANDLING:
148+
* - Emotes are added to visibleItems to trigger auto-play in the preview
149+
* - Only one emote can be visible at a time (others are filtered out)
150+
* - The Items component (Items.tsx:74-86) handles the play/pause toggle
151+
* via wearableController when clicking on an already-visible emote
152+
* - When switching to a non-emote item, emotes are filtered from visibleItems
153+
*/
154+
export const useItemSelection = (options: UseItemSelectionOptions) => {
155+
const { selectedItem, selectedItemId, selectedCollectionId, visibleItems, onSetItems, onResetEmoteToIdle } = options
156+
157+
// Track previous values to detect changes
158+
const prevSelectedItem = useRef(selectedItem)
159+
const prevSelectedItemId = useRef(selectedItemId)
160+
const prevSelectedCollectionId = useRef(selectedCollectionId)
161+
const isInitialMount = useRef(true)
162+
163+
useEffect(() => {
164+
// Skip initial mount - let componentDidMount equivalent handle it
165+
if (isInitialMount.current) {
166+
isInitialMount.current = false
167+
prevSelectedItem.current = selectedItem
168+
prevSelectedItemId.current = selectedItemId
169+
prevSelectedCollectionId.current = selectedCollectionId
170+
return
171+
}
172+
173+
const collectionChanged = prevSelectedCollectionId.current !== selectedCollectionId
174+
175+
// Clear visible items and reset emote to idle when changing collection
176+
if (collectionChanged) {
177+
onSetItems([])
178+
onResetEmoteToIdle()
179+
prevSelectedCollectionId.current = selectedCollectionId
180+
prevSelectedItem.current = selectedItem
181+
prevSelectedItemId.current = selectedItemId
182+
return
183+
}
184+
185+
// Preselect item when clicking on a collection (new item appears)
186+
// This includes emotes - adding them to visibleItems triggers auto-play
187+
if (!prevSelectedItem.current && selectedItem) {
188+
// For emotes, filter out any existing emotes first (only one can play at a time)
189+
if (selectedItem.type === ItemType.EMOTE) {
190+
const nonEmoteItems = visibleItems.filter(item => item.type !== ItemType.EMOTE)
191+
onSetItems([...nonEmoteItems, selectedItem])
192+
} else {
193+
onSetItems([selectedItem])
194+
}
195+
}
196+
197+
// When switching between items (both previous and current exist and are different)
198+
const itemIdChanged = prevSelectedItemId.current && selectedItemId && prevSelectedItemId.current !== selectedItemId
199+
if (itemIdChanged && selectedItem) {
200+
if (selectedItem.type === ItemType.EMOTE) {
201+
// Switching to an emote: filter out other emotes and add the new one
202+
const nonEmoteItems = visibleItems.filter(item => item.type !== ItemType.EMOTE)
203+
onSetItems([...nonEmoteItems, selectedItem])
204+
} else {
205+
// Switching to a non-emote item: filter out emotes from visible items
206+
const nonEmoteItems = visibleItems.filter(item => item.type !== ItemType.EMOTE)
207+
onSetItems(nonEmoteItems)
208+
}
209+
}
210+
211+
prevSelectedItem.current = selectedItem
212+
prevSelectedItemId.current = selectedItemId
213+
prevSelectedCollectionId.current = selectedCollectionId
214+
}, [selectedItem, selectedItemId, selectedCollectionId, visibleItems, onSetItems])
215+
}
216+
217+
/**
218+
* Handles initial data fetching when the component mounts.
219+
* Also handles fetching orphan items when the address changes.
220+
*/
221+
export const useInitialDataFetch = (options: UseInitialDataFetchOptions) => {
222+
const {
223+
address,
224+
hasUserOrphanItems,
225+
selectedItem,
226+
isReviewing,
227+
selectedCollectionId,
228+
currentTab,
229+
onFetchOrphanItem,
230+
onFetchCollections,
231+
onFetchOrphanItems,
232+
onSetItems
233+
} = options
234+
235+
const hasInitializedRef = useRef(false)
236+
const prevAddressRef = useRef(address)
237+
238+
// Initial mount logic
239+
useEffect(() => {
240+
if (hasInitializedRef.current) {
241+
return
242+
}
243+
hasInitializedRef.current = true
244+
245+
// Fetch initial resources
246+
if (address && !isReviewing && !selectedCollectionId) {
247+
const isCollectionTab = currentTab === ItemEditorTabs.COLLECTIONS && !selectedCollectionId
248+
const fetchFn = isCollectionTab ? onFetchCollections : onFetchOrphanItems
249+
fetchFn(address, { limit: LEFT_PANEL_PAGE_SIZE, page: INITIAL_PAGE })
250+
}
251+
252+
// TODO: Remove this call when there are no users with orphan items
253+
if (address && hasUserOrphanItems === undefined) {
254+
onFetchOrphanItem(address)
255+
}
256+
257+
// Set initial selected item
258+
if (selectedItem) {
259+
onSetItems([selectedItem])
260+
}
261+
}, []) // Empty deps - only run on mount
262+
263+
// Handle address changes for orphan items
264+
useEffect(() => {
265+
if (address && address !== prevAddressRef.current && hasUserOrphanItems === undefined) {
266+
onFetchOrphanItem(address)
267+
}
268+
prevAddressRef.current = address
269+
}, [address, hasUserOrphanItems, onFetchOrphanItem])
270+
}
271+
272+
/**
273+
* Cleanup hook that clears visible items when component unmounts.
274+
*/
275+
export const useCleanup = (onSetItems: (items: Item[]) => void) => {
276+
const onSetItemsRef = useRef(onSetItems)
277+
onSetItemsRef.current = onSetItems
278+
279+
useEffect(() => {
280+
return () => {
281+
onSetItemsRef.current([])
282+
}
283+
}, [])
284+
}

0 commit comments

Comments
 (0)