@rpg-engine/long-bow 0.8.219 → 0.8.221
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/DraggableContainer.d.ts +0 -6
- package/dist/components/Store/CartView.d.ts +0 -2
- package/dist/components/Store/MetadataCollector.d.ts +2 -2
- package/dist/components/Store/Store.d.ts +10 -28
- package/dist/components/Store/StoreHeader.d.ts +14 -0
- package/dist/components/Store/hooks/useStoreCart.d.ts +2 -0
- package/dist/components/Store/hooks/useStoreMetadata.d.ts +4 -11
- package/dist/components/Store/hooks/useStoreTabs.d.ts +20 -0
- package/dist/components/Store/internal/packToBlueprint.d.ts +2 -0
- package/dist/components/Store/sections/StoreItemsSection.d.ts +5 -3
- package/dist/hooks/useStoreFiltering.d.ts +7 -4
- package/dist/long-bow.cjs.development.js +379 -441
- package/dist/long-bow.cjs.development.js.map +1 -1
- package/dist/long-bow.cjs.production.min.js +1 -1
- package/dist/long-bow.cjs.production.min.js.map +1 -1
- package/dist/long-bow.esm.js +381 -443
- package/dist/long-bow.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/components/DraggableContainer.tsx +0 -24
- package/src/components/Store/CartView.tsx +116 -137
- package/src/components/Store/MetadataCollector.tsx +60 -40
- package/src/components/Store/Store.tsx +75 -285
- package/src/components/Store/StoreHeader.tsx +74 -0
- package/src/components/Store/__test__/MetadataCollector.spec.tsx +94 -164
- package/src/components/Store/__test__/Store.spec.tsx +4 -0
- package/src/components/Store/__test__/useStoreMetadata.spec.tsx +58 -156
- package/src/components/Store/__test__/useStoreTabs.spec.tsx +69 -0
- package/src/components/Store/hooks/useStoreCart.ts +5 -2
- package/src/components/Store/hooks/useStoreMetadata.ts +30 -48
- package/src/components/Store/hooks/useStoreTabs.ts +104 -0
- package/src/components/Store/internal/packToBlueprint.ts +21 -0
- package/src/components/Store/sections/StoreItemsSection.tsx +19 -60
- package/src/components/Store/sections/StorePacksSection.tsx +0 -1
- package/src/components/shared/ScrollableContent/ScrollableContent.tsx +3 -6
- package/src/hooks/useStoreFiltering.spec.tsx +79 -0
- package/src/hooks/useStoreFiltering.ts +27 -9
package/package.json
CHANGED
|
@@ -12,12 +12,6 @@ export interface IDraggableContainerProps {
|
|
|
12
12
|
height?: string;
|
|
13
13
|
minWidth?: string;
|
|
14
14
|
minHeight?: string;
|
|
15
|
-
/** CSS override for width applied below `mobileBreakpoint`. Useful for responsive sizing without JS detection. */
|
|
16
|
-
mobileWidth?: string;
|
|
17
|
-
/** CSS override for height applied below `mobileBreakpoint`. Useful for responsive sizing without JS detection. */
|
|
18
|
-
mobileHeight?: string;
|
|
19
|
-
/** Viewport width in px under which mobile overrides apply. Defaults to 768. */
|
|
20
|
-
mobileBreakpoint?: number;
|
|
21
15
|
className?: string;
|
|
22
16
|
type?: RPGUIContainerTypes;
|
|
23
17
|
title?: string;
|
|
@@ -42,9 +36,6 @@ export const DraggableContainer: React.FC<IDraggableContainerProps> = ({
|
|
|
42
36
|
height,
|
|
43
37
|
minHeight,
|
|
44
38
|
minWidth,
|
|
45
|
-
mobileWidth,
|
|
46
|
-
mobileHeight,
|
|
47
|
-
mobileBreakpoint = 768,
|
|
48
39
|
className,
|
|
49
40
|
type = RPGUIContainerTypes.FramedGold,
|
|
50
41
|
onCloseButton,
|
|
@@ -126,9 +117,6 @@ export const DraggableContainer: React.FC<IDraggableContainerProps> = ({
|
|
|
126
117
|
height={height || 'auto'}
|
|
127
118
|
minWidth={minWidth}
|
|
128
119
|
minHeight={minHeight}
|
|
129
|
-
mobileWidth={mobileWidth}
|
|
130
|
-
mobileHeight={mobileHeight}
|
|
131
|
-
mobileBreakpoint={mobileBreakpoint}
|
|
132
120
|
className={`rpgui-container ${type} ${className}`}
|
|
133
121
|
isFullScreen={isFullScreen}
|
|
134
122
|
opacity={opacity}
|
|
@@ -161,9 +149,6 @@ interface IContainerProps {
|
|
|
161
149
|
height: string;
|
|
162
150
|
minWidth?: string;
|
|
163
151
|
minHeight?: string;
|
|
164
|
-
mobileWidth?: string;
|
|
165
|
-
mobileHeight?: string;
|
|
166
|
-
mobileBreakpoint?: number;
|
|
167
152
|
isFullScreen?: boolean;
|
|
168
153
|
opacity?: number;
|
|
169
154
|
}
|
|
@@ -193,15 +178,6 @@ const Container = styled.div<IContainerProps>`
|
|
|
193
178
|
align-content: flex-start;
|
|
194
179
|
`}
|
|
195
180
|
|
|
196
|
-
${({ mobileWidth, mobileHeight, mobileBreakpoint }) =>
|
|
197
|
-
(mobileWidth || mobileHeight) &&
|
|
198
|
-
css`
|
|
199
|
-
@media (max-width: ${mobileBreakpoint ?? 768}px) {
|
|
200
|
-
${mobileWidth && `width: ${mobileWidth}; min-width: unset;`}
|
|
201
|
-
${mobileHeight && `height: ${mobileHeight}; min-height: unset;`}
|
|
202
|
-
}
|
|
203
|
-
`}
|
|
204
|
-
|
|
205
181
|
&.rpgui-container {
|
|
206
182
|
padding-top: 1.5rem;
|
|
207
183
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { IProductBlueprint, MetadataType } from '@rpg-engine/shared';
|
|
2
2
|
import React, { useState } from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { FaInfoCircle, FaShoppingBag, FaTimes, FaTrash } from 'react-icons/fa';
|
|
4
4
|
import styled from 'styled-components';
|
|
5
5
|
import characterAtlasJSON from '../../mocks/atlas/entities/entities.json';
|
|
6
6
|
import characterAtlasIMG from '../../mocks/atlas/entities/entities.png';
|
|
@@ -27,8 +27,6 @@ export interface ICartViewProps {
|
|
|
27
27
|
paymentMethodLabel?: string;
|
|
28
28
|
trustSignals?: ITrustSignal[];
|
|
29
29
|
onCloseStore?: () => void;
|
|
30
|
-
/** Called when user taps the "Buy DC" nudge — open wallet/DC purchase flow */
|
|
31
|
-
onBuyDC?: () => void;
|
|
32
30
|
/** Fires when user taps the pay button — before the purchase resolves */
|
|
33
31
|
onCheckoutStart?: (items: Array<{ key: string; name: string; quantity: number }>, total: number) => void;
|
|
34
32
|
/** Fires after a successful purchase */
|
|
@@ -69,7 +67,6 @@ export const CartView: React.FC<ICartViewProps> = ({
|
|
|
69
67
|
paymentMethodLabel,
|
|
70
68
|
trustSignals,
|
|
71
69
|
onCloseStore,
|
|
72
|
-
onBuyDC,
|
|
73
70
|
onCheckoutStart,
|
|
74
71
|
onPurchaseSuccess,
|
|
75
72
|
onPurchaseError,
|
|
@@ -118,10 +115,6 @@ export const CartView: React.FC<ICartViewProps> = ({
|
|
|
118
115
|
}
|
|
119
116
|
};
|
|
120
117
|
|
|
121
|
-
// Show DC discount nudge when items have DC pricing and user might benefit
|
|
122
|
-
const hasDCItems = cartItems.some(ci => (ci.item as any).dcPrice);
|
|
123
|
-
const showDCNudge = hasDCItems && onBuyDC;
|
|
124
|
-
|
|
125
118
|
if (purchasedItems) {
|
|
126
119
|
return (
|
|
127
120
|
<PurchaseSuccess
|
|
@@ -150,100 +143,95 @@ export const CartView: React.FC<ICartViewProps> = ({
|
|
|
150
143
|
</CloseButton>
|
|
151
144
|
</Header>
|
|
152
145
|
|
|
153
|
-
<
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
/>
|
|
175
|
-
</ItemIconContainer>
|
|
176
|
-
<ItemDetails>
|
|
177
|
-
<ItemName>{cartItem.item.name}</ItemName>
|
|
178
|
-
{cartItem.metadata?.inputValue && (
|
|
179
|
-
<CartMeta>{cartItem.metadata.inputValue}</CartMeta>
|
|
180
|
-
)}
|
|
181
|
-
<ItemInfo>
|
|
182
|
-
<span>{currencySymbol}{formatPrice((cartItem.item as any).regionalPrice ?? cartItem.item.price)}</span>
|
|
183
|
-
<span>×</span>
|
|
184
|
-
<span>{cartItem.quantity}</span>
|
|
185
|
-
<span>=</span>
|
|
186
|
-
<span>
|
|
187
|
-
{currencySymbol}{formatPrice(((cartItem.item as any).regionalPrice ?? cartItem.item.price) * cartItem.quantity)}
|
|
188
|
-
</span>
|
|
189
|
-
</ItemInfo>
|
|
190
|
-
|
|
191
|
-
{cartItem.metadata && cartItem.item.metadataType && (
|
|
192
|
-
<MetadataDisplay
|
|
193
|
-
type={cartItem.item.metadataType}
|
|
194
|
-
metadata={cartItem.metadata}
|
|
146
|
+
<MainContent>
|
|
147
|
+
<CartItems>
|
|
148
|
+
{cartItems.length === 0 ? (
|
|
149
|
+
<EmptyCart>Your cart is empty</EmptyCart>
|
|
150
|
+
) : (
|
|
151
|
+
cartItems.map(cartItem => {
|
|
152
|
+
const getSpriteKey = (textureKey: string) => {
|
|
153
|
+
return textureKey + '/down/standing/0.png';
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
return (
|
|
157
|
+
<CartItemRow key={cartItem.item.key}>
|
|
158
|
+
<ItemIconContainer>
|
|
159
|
+
<SpriteFromAtlas
|
|
160
|
+
atlasJSON={cartItem.item.metadataType === MetadataType.CharacterSkin ? characterAtlasJSON : atlasJSON}
|
|
161
|
+
atlasIMG={cartItem.item.metadataType === MetadataType.CharacterSkin ? characterAtlasIMG : atlasIMG}
|
|
162
|
+
spriteKey={cartItem.item.metadataType === MetadataType.CharacterSkin && cartItem.metadata?.selectedSkinTextureKey ? getSpriteKey(cartItem.metadata.selectedSkinTextureKey) : cartItem.item.texturePath}
|
|
163
|
+
width={24}
|
|
164
|
+
height={24}
|
|
165
|
+
imgScale={1.5}
|
|
166
|
+
centered
|
|
195
167
|
/>
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
168
|
+
</ItemIconContainer>
|
|
169
|
+
<ItemDetails>
|
|
170
|
+
<ItemName>{cartItem.item.name}</ItemName>
|
|
171
|
+
{cartItem.metadata?.inputValue && (
|
|
172
|
+
<CartMeta>{cartItem.metadata.inputValue}</CartMeta>
|
|
173
|
+
)}
|
|
174
|
+
<ItemInfo>
|
|
175
|
+
<span>{currencySymbol}{formatPrice((cartItem.item as any).regionalPrice ?? cartItem.item.price)}</span>
|
|
176
|
+
<span>×</span>
|
|
177
|
+
<span>{cartItem.quantity}</span>
|
|
178
|
+
<span>=</span>
|
|
179
|
+
<span>
|
|
180
|
+
{currencySymbol}{formatPrice(((cartItem.item as any).regionalPrice ?? cartItem.item.price) * cartItem.quantity)}
|
|
181
|
+
</span>
|
|
182
|
+
</ItemInfo>
|
|
183
|
+
|
|
184
|
+
{cartItem.metadata && cartItem.item.metadataType && (
|
|
185
|
+
<MetadataDisplay
|
|
186
|
+
type={cartItem.item.metadataType}
|
|
187
|
+
metadata={cartItem.metadata}
|
|
188
|
+
/>
|
|
189
|
+
)}
|
|
190
|
+
</ItemDetails>
|
|
191
|
+
|
|
192
|
+
<CTAButton
|
|
193
|
+
icon={<FaTrash />}
|
|
194
|
+
onClick={e => {
|
|
195
|
+
e.stopPropagation();
|
|
196
|
+
onRemoveFromCart(cartItem.item.key);
|
|
197
|
+
}}
|
|
198
|
+
/>
|
|
199
|
+
</CartItemRow>
|
|
200
|
+
);
|
|
201
|
+
})
|
|
202
|
+
)}
|
|
203
|
+
</CartItems>
|
|
211
204
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
<
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
205
|
+
{cartItems.length > 0 && (
|
|
206
|
+
<OrderSummaryPanel>
|
|
207
|
+
<OrderSummaryLabel>Order Summary</OrderSummaryLabel>
|
|
208
|
+
<TotalRow>
|
|
209
|
+
<span>Subtotal:</span>
|
|
210
|
+
<span>{currencySymbol}{formatPrice(total)}</span>
|
|
211
|
+
</TotalRow>
|
|
212
|
+
{dcTotal > 0 && (
|
|
213
|
+
<TotalRow>
|
|
214
|
+
<span>DC:</span>
|
|
215
|
+
<span><MMORPGNumber value={dcTotal} /> DC</span>
|
|
216
|
+
</TotalRow>
|
|
217
|
+
)}
|
|
218
|
+
<TotalRow $isTotal>
|
|
219
|
+
<span>Total:</span>
|
|
220
|
+
<span>{currencySymbol}{formatPrice(total)}</span>
|
|
221
|
+
</TotalRow>
|
|
222
|
+
{paymentMethodLabel && (
|
|
223
|
+
<PaymentMethodRow>
|
|
224
|
+
<span>Paying with:</span>
|
|
225
|
+
<span>{paymentMethodLabel}</span>
|
|
226
|
+
</PaymentMethodRow>
|
|
227
|
+
)}
|
|
228
|
+
</OrderSummaryPanel>
|
|
219
229
|
)}
|
|
230
|
+
</MainContent>
|
|
220
231
|
|
|
232
|
+
<Footer>
|
|
221
233
|
<TrustBar signals={trustSignals} />
|
|
222
|
-
|
|
223
|
-
<TotalInfo>
|
|
224
|
-
<OrderSummaryLabel>Order Summary</OrderSummaryLabel>
|
|
225
|
-
<TotalRow>
|
|
226
|
-
<span>Subtotal:</span>
|
|
227
|
-
<span>{currencySymbol}{formatPrice(total)}</span>
|
|
228
|
-
</TotalRow>
|
|
229
|
-
{dcTotal > 0 && (
|
|
230
|
-
<TotalRow>
|
|
231
|
-
<span>DC:</span>
|
|
232
|
-
<span><MMORPGNumber value={dcTotal} /> DC</span>
|
|
233
|
-
</TotalRow>
|
|
234
|
-
)}
|
|
235
|
-
<TotalRow $isTotal>
|
|
236
|
-
<span>Total:</span>
|
|
237
|
-
<span>{currencySymbol}{formatPrice(total)}</span>
|
|
238
|
-
</TotalRow>
|
|
239
|
-
{paymentMethodLabel && (
|
|
240
|
-
<PaymentMethodRow>
|
|
241
|
-
<span>Paying with:</span>
|
|
242
|
-
<span>{paymentMethodLabel}</span>
|
|
243
|
-
</PaymentMethodRow>
|
|
244
|
-
)}
|
|
245
|
-
{error && <ErrorMessage>{error}</ErrorMessage>}
|
|
246
|
-
</TotalInfo>
|
|
234
|
+
{error && <ErrorMessage>{error}</ErrorMessage>}
|
|
247
235
|
<CTAButton
|
|
248
236
|
icon={<FaShoppingBag />}
|
|
249
237
|
label={isLoading ? 'Processing...' : `Pay ${currencySymbol}${formatPrice(total)}`}
|
|
@@ -260,14 +248,17 @@ const Container = styled.div`
|
|
|
260
248
|
display: flex;
|
|
261
249
|
flex-direction: column;
|
|
262
250
|
width: 100%;
|
|
263
|
-
|
|
251
|
+
height: 100%;
|
|
264
252
|
padding: 1rem;
|
|
253
|
+
overflow: hidden;
|
|
254
|
+
box-sizing: border-box;
|
|
265
255
|
`;
|
|
266
256
|
|
|
267
257
|
const Header = styled.div`
|
|
268
258
|
display: flex;
|
|
269
259
|
justify-content: space-between;
|
|
270
260
|
align-items: center;
|
|
261
|
+
flex-shrink: 0;
|
|
271
262
|
`;
|
|
272
263
|
|
|
273
264
|
const Title = styled.h2`
|
|
@@ -289,12 +280,26 @@ const CloseButton = styled.div`
|
|
|
289
280
|
justify-content: center;
|
|
290
281
|
`;
|
|
291
282
|
|
|
283
|
+
const MainContent = styled.div`
|
|
284
|
+
display: grid;
|
|
285
|
+
grid-template-columns: 1fr 260px;
|
|
286
|
+
gap: 1rem;
|
|
287
|
+
flex: 1;
|
|
288
|
+
min-height: 0;
|
|
289
|
+
margin: 1rem 0;
|
|
290
|
+
|
|
291
|
+
@media (max-width: 700px) {
|
|
292
|
+
grid-template-columns: 1fr;
|
|
293
|
+
gap: 0.75rem;
|
|
294
|
+
}
|
|
295
|
+
`;
|
|
296
|
+
|
|
292
297
|
const CartItems = styled.div`
|
|
293
298
|
display: flex;
|
|
294
299
|
flex-direction: column;
|
|
295
300
|
gap: 0.5rem;
|
|
301
|
+
min-height: 0;
|
|
296
302
|
overflow-y: auto;
|
|
297
|
-
max-height: 250px;
|
|
298
303
|
padding-right: 0.5rem;
|
|
299
304
|
|
|
300
305
|
&::-webkit-scrollbar {
|
|
@@ -307,13 +312,24 @@ const CartItems = styled.div`
|
|
|
307
312
|
&::-webkit-scrollbar-thumb {
|
|
308
313
|
background: #f59e0b;
|
|
309
314
|
border-radius: 4px;
|
|
310
|
-
|
|
315
|
+
|
|
311
316
|
&:hover {
|
|
312
317
|
background: #fbbf24;
|
|
313
318
|
}
|
|
314
319
|
}
|
|
315
320
|
`;
|
|
316
321
|
|
|
322
|
+
const OrderSummaryPanel = styled.div`
|
|
323
|
+
display: flex;
|
|
324
|
+
flex-direction: column;
|
|
325
|
+
gap: 0.5rem;
|
|
326
|
+
padding: 1rem;
|
|
327
|
+
background: rgba(0, 0, 0, 0.25);
|
|
328
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
329
|
+
border-radius: 6px;
|
|
330
|
+
align-self: start;
|
|
331
|
+
`;
|
|
332
|
+
|
|
317
333
|
const EmptyCart = styled.div`
|
|
318
334
|
display: flex;
|
|
319
335
|
align-items: center;
|
|
@@ -380,12 +396,6 @@ const Footer = styled.div`
|
|
|
380
396
|
}
|
|
381
397
|
`;
|
|
382
398
|
|
|
383
|
-
const TotalInfo = styled.div`
|
|
384
|
-
display: flex;
|
|
385
|
-
flex-direction: column;
|
|
386
|
-
gap: 0.5rem;
|
|
387
|
-
`;
|
|
388
|
-
|
|
389
399
|
const OrderSummaryLabel = styled.div`
|
|
390
400
|
font-family: 'Press Start 2P', cursive;
|
|
391
401
|
font-size: 0.55rem;
|
|
@@ -429,37 +439,6 @@ const PaymentMethodRow = styled.div`
|
|
|
429
439
|
}
|
|
430
440
|
`;
|
|
431
441
|
|
|
432
|
-
const DCNudge = styled.div`
|
|
433
|
-
display: flex;
|
|
434
|
-
align-items: center;
|
|
435
|
-
gap: 0.5rem;
|
|
436
|
-
padding: 0.5rem 0.75rem;
|
|
437
|
-
background: rgba(245, 158, 11, 0.1);
|
|
438
|
-
border: 1px solid rgba(245, 158, 11, 0.3);
|
|
439
|
-
border-radius: 4px;
|
|
440
|
-
cursor: pointer;
|
|
441
|
-
font-family: 'Press Start 2P', cursive;
|
|
442
|
-
font-size: 0.45rem;
|
|
443
|
-
color: #fbbf24;
|
|
444
|
-
transition: background 0.15s;
|
|
445
|
-
line-height: 1.4;
|
|
446
|
-
flex-wrap: wrap;
|
|
447
|
-
|
|
448
|
-
&:hover {
|
|
449
|
-
background: rgba(245, 158, 11, 0.18);
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
svg { flex-shrink: 0; font-size: 0.7rem; }
|
|
453
|
-
span { flex: 1; min-width: 140px; }
|
|
454
|
-
`;
|
|
455
|
-
|
|
456
|
-
const DCNudgeLink = styled.span`
|
|
457
|
-
color: #f59e0b;
|
|
458
|
-
white-space: nowrap;
|
|
459
|
-
text-decoration: underline;
|
|
460
|
-
flex: 0 0 auto !important;
|
|
461
|
-
`;
|
|
462
|
-
|
|
463
442
|
const ErrorMessage = styled.div`
|
|
464
443
|
color: #ef4444;
|
|
465
444
|
font-size: 0.875rem;
|
|
@@ -1,48 +1,68 @@
|
|
|
1
|
-
import { MetadataType } from
|
|
2
|
-
import React, { useEffect } from
|
|
3
|
-
import { CharacterSkinSelectionModal } from
|
|
1
|
+
import { MetadataType } from '@rpg-engine/shared';
|
|
2
|
+
import React, { useCallback, useEffect, useRef } from 'react';
|
|
3
|
+
import { CharacterSkinSelectionModal } from '../Character/CharacterSkinSelectionModal';
|
|
4
4
|
|
|
5
5
|
export interface IMetadataCollectorProps {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
metadataType: MetadataType;
|
|
7
|
+
config: Record<string, any>;
|
|
8
|
+
onCollect: (metadata: Record<string, any>) => void;
|
|
9
|
+
onCancel: () => void;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export const MetadataCollector: React.FC<IMetadataCollectorProps> = ({
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
metadataType,
|
|
14
|
+
config,
|
|
15
|
+
onCollect,
|
|
16
|
+
onCancel,
|
|
17
17
|
}) => {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return null;
|
|
18
|
+
const isPendingRef = useRef(true);
|
|
19
|
+
|
|
20
|
+
const finalize = useCallback((callback: () => void) => {
|
|
21
|
+
if (!isPendingRef.current) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
isPendingRef.current = false;
|
|
26
|
+
callback();
|
|
27
|
+
}, []);
|
|
28
|
+
|
|
29
|
+
const handleCollect = useCallback((metadata: Record<string, any>) => {
|
|
30
|
+
finalize(() => onCollect(metadata));
|
|
31
|
+
}, [finalize, onCollect]);
|
|
32
|
+
|
|
33
|
+
const handleCancel = useCallback(() => {
|
|
34
|
+
finalize(onCancel);
|
|
35
|
+
}, [finalize, onCancel]);
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
return () => {
|
|
39
|
+
handleCancel();
|
|
40
|
+
};
|
|
41
|
+
}, [handleCancel]);
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (metadataType === MetadataType.CharacterSkin) {
|
|
45
|
+
return undefined;
|
|
47
46
|
}
|
|
47
|
+
|
|
48
|
+
const timer = window.setTimeout(handleCancel, 0);
|
|
49
|
+
return () => window.clearTimeout(timer);
|
|
50
|
+
}, [handleCancel, metadataType]);
|
|
51
|
+
|
|
52
|
+
if (metadataType === MetadataType.CharacterSkin) {
|
|
53
|
+
return (
|
|
54
|
+
<CharacterSkinSelectionModal
|
|
55
|
+
isOpen
|
|
56
|
+
onClose={handleCancel}
|
|
57
|
+
onConfirm={(selectedSkin: any) => handleCollect({ selectedSkin })}
|
|
58
|
+
availableCharacters={config.availableCharacters || []}
|
|
59
|
+
atlasJSON={config.atlasJSON}
|
|
60
|
+
atlasIMG={config.atlasIMG}
|
|
61
|
+
initialSelectedSkin={config.initialSelectedSkin}
|
|
62
|
+
/>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.warn(`No collector implemented for metadata type: ${metadataType}`);
|
|
67
|
+
return null;
|
|
48
68
|
};
|