@rpg-engine/long-bow 0.5.27 → 0.5.29
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/Item/Inventory/ItemSlot.d.ts +0 -1
- package/dist/components/Item/Inventory/ItemSlotRarity.d.ts +9 -0
- package/dist/hooks/useTapAndHold.d.ts +10 -0
- package/dist/long-bow.cjs.development.js +179 -166
- 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 +181 -167
- package/dist/long-bow.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Item/Cards/ItemInfo.tsx +2 -1
- package/src/components/Item/Inventory/ItemSlot.tsx +108 -138
- package/src/components/Item/Inventory/ItemSlotRarity.tsx +46 -0
- package/src/components/Item/Inventory/ItemSlotRenderer.tsx +23 -24
- package/src/components/Marketplace/MarketplaceRows.tsx +1 -1
- package/src/hooks/useTapAndHold.ts +30 -0
- package/src/stories/CircullarController.stories.tsx +1 -1
- package/src/stories/Shortcuts.stories.tsx +1 -1
package/package.json
CHANGED
|
@@ -5,7 +5,8 @@ import { uiColors } from '../../../constants/uiColors';
|
|
|
5
5
|
import { uiFonts } from '../../../constants/uiFonts';
|
|
6
6
|
import { SpriteFromAtlas } from '../../shared/SpriteFromAtlas';
|
|
7
7
|
import { ErrorBoundary } from '../Inventory/ErrorBoundary';
|
|
8
|
-
import { EquipmentSlotSpriteByType
|
|
8
|
+
import { EquipmentSlotSpriteByType } from '../Inventory/ItemSlot';
|
|
9
|
+
import { rarityColor } from '../Inventory/ItemSlotRarity';
|
|
9
10
|
|
|
10
11
|
interface IItemInfoProps {
|
|
11
12
|
item: IItem;
|
|
@@ -3,14 +3,13 @@ import {
|
|
|
3
3
|
IItem,
|
|
4
4
|
IItemContainer,
|
|
5
5
|
ItemContainerType,
|
|
6
|
-
ItemRarities,
|
|
7
6
|
ItemSlotType,
|
|
8
7
|
ItemType,
|
|
9
8
|
} from '@rpg-engine/shared';
|
|
10
9
|
|
|
11
10
|
import { observer } from 'mobx-react-lite';
|
|
12
11
|
import React, { useEffect, useRef, useState } from 'react';
|
|
13
|
-
import Draggable from 'react-draggable';
|
|
12
|
+
import Draggable, { DraggableEventHandler } from 'react-draggable';
|
|
14
13
|
import styled from 'styled-components';
|
|
15
14
|
import { IPosition } from '../../../types/eventTypes';
|
|
16
15
|
import { ItemSlotRenderer } from './ItemSlotRenderer';
|
|
@@ -156,6 +155,107 @@ export const ItemSlot: React.FC<IProps> = observer(
|
|
|
156
155
|
}
|
|
157
156
|
};
|
|
158
157
|
|
|
158
|
+
const onDraggableStop: DraggableEventHandler = (e, data) => {
|
|
159
|
+
setDraggingItem(null);
|
|
160
|
+
|
|
161
|
+
const target = e.target as HTMLElement;
|
|
162
|
+
if (target?.id.includes('shortcutSetter') && setItemShortcut && item) {
|
|
163
|
+
const index = parseInt(target.id.split('_')[1]);
|
|
164
|
+
if (!isNaN(index)) {
|
|
165
|
+
setItemShortcut(item, index);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (wasDragged && item && !isSelectingShortcut) {
|
|
170
|
+
//@ts-ignore
|
|
171
|
+
const classes: string[] = Array.from(e.target?.classList);
|
|
172
|
+
|
|
173
|
+
const isOutsideDrop =
|
|
174
|
+
classes.some(elm => {
|
|
175
|
+
return elm.includes('rpgui-content');
|
|
176
|
+
}) || classes.length === 0;
|
|
177
|
+
|
|
178
|
+
if (isOutsideDrop) {
|
|
179
|
+
setDropPosition({
|
|
180
|
+
x: data.x,
|
|
181
|
+
y: data.y,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
setWasDragged(false);
|
|
186
|
+
|
|
187
|
+
const target = dragContainer.current;
|
|
188
|
+
if (!target || !wasDragged) return;
|
|
189
|
+
|
|
190
|
+
const style = window.getComputedStyle(target);
|
|
191
|
+
const matrix = new DOMMatrixReadOnly(style.transform);
|
|
192
|
+
const x = matrix.m41;
|
|
193
|
+
const y = matrix.m42;
|
|
194
|
+
|
|
195
|
+
setDragPosition({ x, y });
|
|
196
|
+
|
|
197
|
+
setTimeout(() => {
|
|
198
|
+
if (checkIfItemCanBeMoved?.()) {
|
|
199
|
+
if (checkIfItemShouldDragEnd && !checkIfItemShouldDragEnd()) return;
|
|
200
|
+
|
|
201
|
+
if (item.stackQty && item.stackQty !== 1 && openQuantitySelector)
|
|
202
|
+
openQuantitySelector(item.stackQty, onSuccessfulDrag);
|
|
203
|
+
else onSuccessfulDrag(item.stackQty);
|
|
204
|
+
} else {
|
|
205
|
+
resetItem();
|
|
206
|
+
setIsFocused(false);
|
|
207
|
+
setDragPosition({ x: 0, y: 0 });
|
|
208
|
+
}
|
|
209
|
+
}, 50);
|
|
210
|
+
} else if (item) {
|
|
211
|
+
let isTouch = false;
|
|
212
|
+
if (
|
|
213
|
+
!isContextMenuDisabled &&
|
|
214
|
+
e.type === 'touchend' &&
|
|
215
|
+
!isSelectingShortcut
|
|
216
|
+
) {
|
|
217
|
+
isTouch = true;
|
|
218
|
+
setIsTooltipMobileVisible(true);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (!isContextMenuDisabled && !isSelectingShortcut && !isTouch) {
|
|
222
|
+
setIsContextMenuVisible(!isContextMenuVisible);
|
|
223
|
+
const event = e as MouseEvent;
|
|
224
|
+
|
|
225
|
+
if (event.clientX && event.clientY) {
|
|
226
|
+
setContextMenuPosition({
|
|
227
|
+
x: event.clientX - 10,
|
|
228
|
+
y: event.clientY - 5,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
onPointerDown(item.type, containerType ?? null, item);
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const onDraggableStart: DraggableEventHandler = () => {
|
|
238
|
+
if (!item || isSelectingShortcut) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (onDragStart && containerType) {
|
|
243
|
+
setDraggingItem(item);
|
|
244
|
+
|
|
245
|
+
onDragStart(item, slotIndex, containerType);
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
const onDraggableProgress: DraggableEventHandler = (_e, data) => {
|
|
250
|
+
if (
|
|
251
|
+
Math.abs(data.x - dragPosition.x) > 5 ||
|
|
252
|
+
Math.abs(data.y - dragPosition.y) > 5
|
|
253
|
+
) {
|
|
254
|
+
setWasDragged(true);
|
|
255
|
+
setIsFocused(true);
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
|
|
159
259
|
return (
|
|
160
260
|
<Container
|
|
161
261
|
item={item}
|
|
@@ -194,113 +294,9 @@ export const ItemSlot: React.FC<IProps> = observer(
|
|
|
194
294
|
defaultClassName={item ? 'draggable' : 'empty-slot'}
|
|
195
295
|
scale={dragScale}
|
|
196
296
|
disabled={onDragStart === undefined || onDragEnd === undefined}
|
|
197
|
-
onStop={
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const target = e.target as HTMLElement;
|
|
201
|
-
if (
|
|
202
|
-
target?.id.includes('shortcutSetter') &&
|
|
203
|
-
setItemShortcut &&
|
|
204
|
-
item
|
|
205
|
-
) {
|
|
206
|
-
const index = parseInt(target.id.split('_')[1]);
|
|
207
|
-
if (!isNaN(index)) {
|
|
208
|
-
setItemShortcut(item, index);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
if (wasDragged && item && !isSelectingShortcut) {
|
|
213
|
-
//@ts-ignore
|
|
214
|
-
const classes: string[] = Array.from(e.target?.classList);
|
|
215
|
-
|
|
216
|
-
const isOutsideDrop =
|
|
217
|
-
classes.some(elm => {
|
|
218
|
-
return elm.includes('rpgui-content');
|
|
219
|
-
}) || classes.length === 0;
|
|
220
|
-
|
|
221
|
-
if (isOutsideDrop) {
|
|
222
|
-
setDropPosition({
|
|
223
|
-
x: data.x,
|
|
224
|
-
y: data.y,
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
setWasDragged(false);
|
|
229
|
-
|
|
230
|
-
const target = dragContainer.current;
|
|
231
|
-
if (!target || !wasDragged) return;
|
|
232
|
-
|
|
233
|
-
const style = window.getComputedStyle(target);
|
|
234
|
-
const matrix = new DOMMatrixReadOnly(style.transform);
|
|
235
|
-
const x = matrix.m41;
|
|
236
|
-
const y = matrix.m42;
|
|
237
|
-
|
|
238
|
-
setDragPosition({ x, y });
|
|
239
|
-
|
|
240
|
-
setTimeout(() => {
|
|
241
|
-
if (checkIfItemCanBeMoved?.()) {
|
|
242
|
-
if (checkIfItemShouldDragEnd && !checkIfItemShouldDragEnd())
|
|
243
|
-
return;
|
|
244
|
-
|
|
245
|
-
if (
|
|
246
|
-
item.stackQty &&
|
|
247
|
-
item.stackQty !== 1 &&
|
|
248
|
-
openQuantitySelector
|
|
249
|
-
)
|
|
250
|
-
openQuantitySelector(item.stackQty, onSuccessfulDrag);
|
|
251
|
-
else onSuccessfulDrag(item.stackQty);
|
|
252
|
-
} else {
|
|
253
|
-
resetItem();
|
|
254
|
-
setIsFocused(false);
|
|
255
|
-
setDragPosition({ x: 0, y: 0 });
|
|
256
|
-
}
|
|
257
|
-
}, 50);
|
|
258
|
-
} else if (item) {
|
|
259
|
-
let isTouch = false;
|
|
260
|
-
if (
|
|
261
|
-
!isContextMenuDisabled &&
|
|
262
|
-
e.type === 'touchend' &&
|
|
263
|
-
!isSelectingShortcut
|
|
264
|
-
) {
|
|
265
|
-
isTouch = true;
|
|
266
|
-
setIsTooltipMobileVisible(true);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
if (!isContextMenuDisabled && !isSelectingShortcut && !isTouch) {
|
|
270
|
-
setIsContextMenuVisible(!isContextMenuVisible);
|
|
271
|
-
const event = e as MouseEvent;
|
|
272
|
-
|
|
273
|
-
if (event.clientX && event.clientY) {
|
|
274
|
-
setContextMenuPosition({
|
|
275
|
-
x: event.clientX - 10,
|
|
276
|
-
y: event.clientY - 5,
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
onPointerDown(item.type, containerType ?? null, item);
|
|
282
|
-
}
|
|
283
|
-
}}
|
|
284
|
-
onStart={() => {
|
|
285
|
-
if (!item || isSelectingShortcut) {
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
if (onDragStart && containerType) {
|
|
290
|
-
setDraggingItem(item);
|
|
291
|
-
|
|
292
|
-
onDragStart(item, slotIndex, containerType);
|
|
293
|
-
}
|
|
294
|
-
}}
|
|
295
|
-
onDrag={(_e, data) => {
|
|
296
|
-
if (
|
|
297
|
-
Math.abs(data.x - dragPosition.x) > 5 ||
|
|
298
|
-
Math.abs(data.y - dragPosition.y) > 5
|
|
299
|
-
) {
|
|
300
|
-
setWasDragged(true);
|
|
301
|
-
setIsFocused(true);
|
|
302
|
-
}
|
|
303
|
-
}}
|
|
297
|
+
onStop={onDraggableStop}
|
|
298
|
+
onStart={onDraggableStart}
|
|
299
|
+
onDrag={onDraggableProgress}
|
|
304
300
|
position={dragPosition}
|
|
305
301
|
cancel=".empty-slot"
|
|
306
302
|
>
|
|
@@ -362,21 +358,6 @@ export const ItemSlot: React.FC<IProps> = observer(
|
|
|
362
358
|
}
|
|
363
359
|
);
|
|
364
360
|
|
|
365
|
-
export const rarityColor = (item: IItem | null) => {
|
|
366
|
-
switch (item?.rarity) {
|
|
367
|
-
case ItemRarities.Uncommon:
|
|
368
|
-
return 'rgba(13, 193, 13, 0.6)';
|
|
369
|
-
case ItemRarities.Rare:
|
|
370
|
-
return 'rgba(8, 104, 187, 0.6)';
|
|
371
|
-
case ItemRarities.Epic:
|
|
372
|
-
return 'rgba(191, 0, 255, 0.6)';
|
|
373
|
-
case ItemRarities.Legendary:
|
|
374
|
-
return 'rgba(255, 191, 0,0.6)';
|
|
375
|
-
default:
|
|
376
|
-
return null;
|
|
377
|
-
}
|
|
378
|
-
};
|
|
379
|
-
|
|
380
361
|
interface ContainerTypes {
|
|
381
362
|
item: IItem | null;
|
|
382
363
|
containerType?: ItemContainerType | null;
|
|
@@ -387,20 +368,9 @@ const Container = styled.div<ContainerTypes>`
|
|
|
387
368
|
margin: 0.1rem;
|
|
388
369
|
|
|
389
370
|
.react-draggable-dragging {
|
|
390
|
-
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
.sprite-from-atlas-img--item {
|
|
395
|
-
position: relative;
|
|
396
|
-
top: 1.5rem;
|
|
397
|
-
left: 1.5rem;
|
|
398
|
-
border-color: ${({ item }) => rarityColor(item)};
|
|
399
|
-
box-shadow: ${({ item }) => `0 0 5px 2px ${rarityColor(item)}`} inset, ${({
|
|
400
|
-
item,
|
|
401
|
-
}) => `0 0 4px 3px ${rarityColor(item)}`};
|
|
402
|
-
//background-color: ${({ item }) => rarityColor(item)};
|
|
371
|
+
opacity: 0;
|
|
403
372
|
}
|
|
373
|
+
|
|
404
374
|
position: relative;
|
|
405
375
|
|
|
406
376
|
&::before {
|
|
@@ -433,7 +403,7 @@ const Container = styled.div<ContainerTypes>`
|
|
|
433
403
|
const ItemContainer = styled.div<{ isFocused?: boolean }>`
|
|
434
404
|
width: 64px;
|
|
435
405
|
height: 64px;
|
|
436
|
-
|
|
406
|
+
|
|
437
407
|
position: relative;
|
|
438
408
|
${props => props.isFocused && 'z-index: 100; pointer-events: none;'};
|
|
439
409
|
`;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { IItem, ItemRarities } from '@rpg-engine/shared';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import styled from 'styled-components';
|
|
4
|
+
|
|
5
|
+
interface IProps {
|
|
6
|
+
item: IItem | null;
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const ItemSlotRarity = ({ children, item }: IProps) => {
|
|
11
|
+
if (!item) return <>{children}</>;
|
|
12
|
+
|
|
13
|
+
return <Container item={item}>{children}</Container>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
interface IContainer {
|
|
17
|
+
item: IItem | null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const rarityColor = (item: IItem | null) => {
|
|
21
|
+
switch (item?.rarity) {
|
|
22
|
+
case ItemRarities.Uncommon:
|
|
23
|
+
return 'rgba(13, 193, 13, 0.6)';
|
|
24
|
+
case ItemRarities.Rare:
|
|
25
|
+
return 'rgba(8, 104, 187, 0.6)';
|
|
26
|
+
case ItemRarities.Epic:
|
|
27
|
+
return 'rgba(191, 0, 255, 0.6)';
|
|
28
|
+
case ItemRarities.Legendary:
|
|
29
|
+
return 'rgba(255, 191, 0,0.6)';
|
|
30
|
+
default:
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
const Container = styled.div<IContainer>`
|
|
35
|
+
.sprite-from-atlas-img--item {
|
|
36
|
+
position: relative;
|
|
37
|
+
top: 1.5rem;
|
|
38
|
+
left: 1.5rem;
|
|
39
|
+
border-color: ${({ item }) => rarityColor(item)};
|
|
40
|
+
box-shadow: ${({ item }) => `0 0 5px 2px ${rarityColor(item)}`} inset, ${({
|
|
41
|
+
item,
|
|
42
|
+
}) => `0 0 4px 3px ${rarityColor(item)}`};
|
|
43
|
+
//background-color: ${({ item }) => rarityColor(item)};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
`;
|
|
@@ -10,6 +10,7 @@ import { SpriteFromAtlas } from '../../shared/SpriteFromAtlas';
|
|
|
10
10
|
import { ErrorBoundary } from './ErrorBoundary';
|
|
11
11
|
import { EquipmentSlotSpriteByType } from './ItemSlot';
|
|
12
12
|
import { onRenderStackInfo } from './ItemSlotQty/ItemSlotQty';
|
|
13
|
+
import { ItemSlotRarity } from './ItemSlotRarity';
|
|
13
14
|
|
|
14
15
|
interface IProps {
|
|
15
16
|
containerType: ItemContainerType | null | undefined;
|
|
@@ -26,29 +27,31 @@ export const ItemSlotRenderer: React.FC<IProps> = ({
|
|
|
26
27
|
slotSpriteMask,
|
|
27
28
|
item,
|
|
28
29
|
}) => {
|
|
29
|
-
const renderItem = (
|
|
30
|
-
if (!
|
|
30
|
+
const renderItem = (item: IItem | null) => {
|
|
31
|
+
if (!item?.texturePath) {
|
|
31
32
|
return null;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
return (
|
|
35
|
-
<ErrorBoundary key={
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
{
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
36
|
+
<ErrorBoundary key={item._id}>
|
|
37
|
+
<ItemSlotRarity item={item}>
|
|
38
|
+
<SpriteFromAtlas
|
|
39
|
+
atlasIMG={atlasIMG}
|
|
40
|
+
atlasJSON={atlasJSON}
|
|
41
|
+
spriteKey={getItemTextureKeyPath(
|
|
42
|
+
{
|
|
43
|
+
key: item.texturePath,
|
|
44
|
+
texturePath: item.texturePath,
|
|
45
|
+
stackQty: item.stackQty || 1,
|
|
46
|
+
isStackable: item.isStackable,
|
|
47
|
+
},
|
|
48
|
+
atlasJSON
|
|
49
|
+
)}
|
|
50
|
+
imgScale={3}
|
|
51
|
+
imgClassname="sprite-from-atlas-img--item"
|
|
52
|
+
/>
|
|
53
|
+
{onRenderStackInfo(item._id, item.stackQty ?? 0)}
|
|
54
|
+
</ItemSlotRarity>
|
|
52
55
|
</ErrorBoundary>
|
|
53
56
|
);
|
|
54
57
|
};
|
|
@@ -81,12 +84,8 @@ export const ItemSlotRenderer: React.FC<IProps> = ({
|
|
|
81
84
|
switch (containerType) {
|
|
82
85
|
case ItemContainerType.Equipment:
|
|
83
86
|
return renderEquipment(itemToRender);
|
|
84
|
-
case ItemContainerType.Inventory:
|
|
85
|
-
return renderItem(itemToRender);
|
|
86
|
-
case ItemContainerType.Depot:
|
|
87
|
-
return renderItem(itemToRender);
|
|
88
87
|
default:
|
|
89
|
-
return
|
|
88
|
+
return renderItem(itemToRender);
|
|
90
89
|
}
|
|
91
90
|
};
|
|
92
91
|
|
|
@@ -9,7 +9,7 @@ import { uiColors } from '../../constants/uiColors';
|
|
|
9
9
|
import { uiFonts } from '../../constants/uiFonts';
|
|
10
10
|
import { Button, ButtonTypes } from '../Button';
|
|
11
11
|
import { ItemInfoWrapper } from '../Item/Cards/ItemInfoWrapper';
|
|
12
|
-
import { rarityColor } from '../Item/Inventory/
|
|
12
|
+
import { rarityColor } from '../Item/Inventory/ItemSlotRarity';
|
|
13
13
|
import { Ellipsis } from '../shared/Ellipsis';
|
|
14
14
|
import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
|
|
15
15
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useCallback, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
interface ITapAndHoldProps {
|
|
4
|
+
onHoldFn: (...args: any[]) => void;
|
|
5
|
+
holdTime?: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const useTapAndHold = ({ onHoldFn: onHold, holdTime = 500 }: ITapAndHoldProps) => {
|
|
9
|
+
const [timer, setTimer] = useState<NodeJS.Timeout | null>(null);
|
|
10
|
+
|
|
11
|
+
const start = useCallback(() => {
|
|
12
|
+
const timeout = setTimeout(onHold, holdTime);
|
|
13
|
+
setTimer(timeout);
|
|
14
|
+
}, [onHold, holdTime]);
|
|
15
|
+
|
|
16
|
+
const clear = useCallback(() => {
|
|
17
|
+
if (timer) {
|
|
18
|
+
clearTimeout(timer);
|
|
19
|
+
setTimer(null);
|
|
20
|
+
}
|
|
21
|
+
}, [timer]);
|
|
22
|
+
|
|
23
|
+
const bind = {
|
|
24
|
+
onTouchStart: start,
|
|
25
|
+
onTouchEnd: clear,
|
|
26
|
+
onTouchCancel: clear,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
return bind;
|
|
30
|
+
};
|