@rpg-engine/long-bow 0.8.141 → 0.8.145

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.
Files changed (62) hide show
  1. package/dist/components/Marketplace/BlueprintSearchModal.d.ts +17 -0
  2. package/dist/components/Marketplace/BuyOrderDetailsModal.d.ts +17 -0
  3. package/dist/components/Marketplace/BuyOrderPanel.d.ts +24 -0
  4. package/dist/components/Marketplace/BuyOrderRows.d.ts +13 -0
  5. package/dist/components/Marketplace/BuyPanel.d.ts +9 -1
  6. package/dist/components/Marketplace/HistoryPanel.d.ts +18 -0
  7. package/dist/components/Marketplace/ManagmentPanel.d.ts +3 -2
  8. package/dist/components/Marketplace/Marketplace.d.ts +35 -2
  9. package/dist/components/Marketplace/MarketplaceSettingsPanel.d.ts +2 -1
  10. package/dist/components/Store/PaymentMethodModal.d.ts +1 -0
  11. package/dist/components/shared/LabelPill/LabelPill.d.ts +9 -0
  12. package/dist/components/shared/LabelPill/index.d.ts +1 -0
  13. package/dist/components/shared/SegmentedToggle/SegmentedToggle.d.ts +12 -0
  14. package/dist/components/shared/SegmentedToggle/index.d.ts +1 -0
  15. package/dist/index.d.ts +4 -0
  16. package/dist/long-bow.cjs.development.js +11529 -1288
  17. package/dist/long-bow.cjs.development.js.map +1 -1
  18. package/dist/long-bow.cjs.production.min.js +1 -1
  19. package/dist/long-bow.cjs.production.min.js.map +1 -1
  20. package/dist/long-bow.esm.js +11518 -1290
  21. package/dist/long-bow.esm.js.map +1 -1
  22. package/dist/stories/Features/marketplace/BlueprintSearchModal.stories.d.ts +1 -0
  23. package/dist/stories/Features/marketplace/BuyOrderPanel.stories.d.ts +1 -0
  24. package/dist/stories/Features/marketplace/BuyOrderRows.stories.d.ts +1 -0
  25. package/dist/stories/Features/marketplace/HistoryPanel.stories.d.ts +1 -0
  26. package/dist/stories/Features/trading/MarketplaceRows.stories.d.ts +2 -1
  27. package/dist/stories/UI/buttonsAndInputs/SegmentedToggle.stories.d.ts +6 -0
  28. package/dist/stories/UI/text/LabelPill.stories.d.ts +7 -0
  29. package/dist/utils/atlasUtils.d.ts +2 -0
  30. package/package.json +2 -2
  31. package/src/components/Marketplace/BlueprintSearchModal.tsx +418 -0
  32. package/src/components/Marketplace/BuyOrderDetailsModal.tsx +307 -0
  33. package/src/components/Marketplace/BuyOrderPanel.tsx +266 -0
  34. package/src/components/Marketplace/BuyOrderRows.tsx +287 -0
  35. package/src/components/Marketplace/BuyPanel.tsx +406 -166
  36. package/src/components/Marketplace/HistoryPanel.tsx +422 -0
  37. package/src/components/Marketplace/ManagmentPanel.tsx +13 -15
  38. package/src/components/Marketplace/Marketplace.tsx +181 -30
  39. package/src/components/Marketplace/MarketplaceBuyModal.tsx +1 -0
  40. package/src/components/Marketplace/MarketplaceRows.tsx +41 -10
  41. package/src/components/Marketplace/MarketplaceSettingsPanel.tsx +4 -3
  42. package/src/components/Store/CartView.tsx +11 -0
  43. package/src/components/Store/PaymentMethodModal.tsx +26 -9
  44. package/src/components/shared/LabelPill/LabelPill.tsx +45 -0
  45. package/src/components/shared/LabelPill/index.ts +1 -0
  46. package/src/components/shared/SegmentedToggle/SegmentedToggle.tsx +61 -0
  47. package/src/components/shared/SegmentedToggle/index.ts +1 -0
  48. package/src/components/shared/SpriteFromAtlas.tsx +7 -2
  49. package/src/index.tsx +4 -0
  50. package/src/mocks/atlas/items/items.json +33998 -25238
  51. package/src/mocks/atlas/items/items.png +0 -0
  52. package/src/mocks/itemContainer.mocks.ts +31 -0
  53. package/src/stories/Features/marketplace/BlueprintSearchModal.stories.tsx +145 -0
  54. package/src/stories/Features/marketplace/BuyOrderPanel.stories.tsx +207 -0
  55. package/src/stories/Features/marketplace/BuyOrderRows.stories.tsx +116 -0
  56. package/src/stories/Features/marketplace/HistoryPanel.stories.tsx +157 -0
  57. package/src/stories/Features/trading/Marketplace.stories.tsx +107 -0
  58. package/src/stories/Features/trading/MarketplaceRows.stories.tsx +11 -0
  59. package/src/stories/UI/buttonsAndInputs/SegmentedToggle.stories.tsx +54 -0
  60. package/src/stories/UI/text/LabelPill.stories.tsx +43 -0
  61. package/src/utils/__test__/atlasUtils.spec.ts +26 -0
  62. package/src/utils/atlasUtils.ts +80 -0
@@ -1,3 +1,10 @@
1
+ import {
2
+ IMarketplaceBlueprintSummary,
3
+ IMarketplaceBuyOrderItem,
4
+ IMarketplaceTransaction,
5
+ MarketplaceBuyOrderStatus,
6
+ MarketplaceTransactionType,
7
+ } from '@rpg-engine/shared';
1
8
  import { Meta, Story } from '@storybook/react';
2
9
  import React from 'react';
3
10
  import { RPGUIRoot } from '../../..';
@@ -14,8 +21,73 @@ const meta: Meta = {
14
21
 
15
22
  export default meta;
16
23
 
24
+ const now = new Date();
25
+ const daysAgo = (days: number): Date =>
26
+ new Date(now.getTime() - days * 24 * 60 * 60 * 1000);
27
+
28
+ const mockBlueprints: IMarketplaceBlueprintSummary[] = [
29
+ { key: 'items/broad-sword', name: 'Broad Sword', type: 'Weapon', subType: 'Sword', tier: 1, textureAtlas: 'items', texturePath: 'swords/broad-sword.png' },
30
+ { key: 'items/angelic-sword', name: 'Angelic Sword', type: 'Weapon', subType: 'Sword', tier: 5, textureAtlas: 'items', texturePath: 'swords/angelic-sword.png', rarity: 'Epic' },
31
+ { key: 'items/leather-armor', name: 'Leather Armor', type: 'Armor', subType: 'Body', tier: 1, textureAtlas: 'items', texturePath: 'armors/leather-armor.png' },
32
+ { key: 'items/abyssal-tide-staff', name: 'Abyssal Tide Staff', type: 'Weapon', subType: 'Staff', tier: 3, textureAtlas: 'items', texturePath: 'staffs/abyssal-tide-staff.png', rarity: 'Rare' },
33
+ { key: 'items/wooden-shield', name: 'Wooden Shield', type: 'Armor', subType: 'Shield', tier: 1, textureAtlas: 'items', texturePath: 'shields/wooden-shield.png' },
34
+ { key: 'items/fire-wand', name: 'Fire Wand', type: 'Weapon', subType: 'Wand', tier: 2, textureAtlas: 'items', texturePath: 'staffs/fire-wand.png' },
35
+ { key: 'items/barbarian-helmet', name: 'Barbarian Helmet', type: 'Armor', subType: 'Head', tier: 2, textureAtlas: 'items', texturePath: 'helmets/barbarian-helmet.png' },
36
+ { key: 'items/greater-life-potion', name: 'Greater Life Potion', type: 'Consumable', subType: 'Potion', tier: 1, textureAtlas: 'items', texturePath: 'potions/greater-life-potion.png' },
37
+ ];
38
+
39
+ const mockYourBuyOrders: IMarketplaceBuyOrderItem[] = [
40
+ { _id: 'bo-1', owner: 'player-1', itemBlueprintKey: 'items/abyssal-edge', itemRarity: 'Rare', maxPrice: 500, escrowedGold: 500, fee: 25, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(3), updatedAt: daysAgo(3) },
41
+ { _id: 'bo-2', owner: 'player-1', itemBlueprintKey: 'items/angelic-sword', maxPrice: 2000, escrowedGold: 2000, fee: 100, status: MarketplaceBuyOrderStatus.Fulfilled, fulfilledBy: 'DarkKnight42', createdAt: daysAgo(14), updatedAt: daysAgo(1) },
42
+ { _id: 'bo-3', owner: 'player-1', itemBlueprintKey: 'items/leather-armor', maxPrice: 300, escrowedGold: 0, fee: 15, status: MarketplaceBuyOrderStatus.Expired, createdAt: daysAgo(35), updatedAt: daysAgo(7) },
43
+ ];
44
+
45
+ const mockOpenBuyOrders: IMarketplaceBuyOrderItem[] = [
46
+ { _id: 'obo-1', owner: 'player-2', itemBlueprintKey: 'items/abyssal-tide-staff', itemRarity: 'Epic', maxPrice: 1500, escrowedGold: 1500, fee: 75, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(1), updatedAt: daysAgo(1) },
47
+ { _id: 'obo-2', owner: 'player-3', itemBlueprintKey: 'items/wooden-shield', maxPrice: 200, escrowedGold: 200, fee: 10, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(5), updatedAt: daysAgo(5) },
48
+ { _id: 'obo-3', owner: 'player-4', itemBlueprintKey: 'items/fire-wand', itemRarity: 'Rare', maxPrice: 800, escrowedGold: 800, fee: 40, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(2), updatedAt: daysAgo(2) },
49
+ ];
50
+
51
+ const mockTransactions: IMarketplaceTransaction[] = [
52
+ { owner: 'player-1', type: MarketplaceTransactionType.Purchase, goldAmount: 450, itemKey: 'items/broad-sword', itemName: 'Broad Sword', counterpartName: 'MerchantKing', currency: 'gold', createdAt: daysAgo(1), updatedAt: daysAgo(1) },
53
+ { owner: 'player-1', type: MarketplaceTransactionType.Sale, goldAmount: 1200, itemKey: 'items/angelic-sword', itemName: 'Angelic Sword', counterpartName: 'ShadowBlade', currency: 'dc', createdAt: daysAgo(2), updatedAt: daysAgo(2) },
54
+ { owner: 'player-1', type: MarketplaceTransactionType.Purchase, goldAmount: 275000, itemKey: 'items/angelic-sword', itemName: 'Angelic Sword', counterpartName: 'DCBuyer', currency: 'dc', createdAt: daysAgo(3), updatedAt: daysAgo(3) },
55
+ { owner: 'player-1', type: MarketplaceTransactionType.ListingFee, goldAmount: 60, itemKey: 'items/angelic-sword', itemName: 'Angelic Sword', createdAt: daysAgo(2), updatedAt: daysAgo(2) },
56
+ { owner: 'player-1', type: MarketplaceTransactionType.Withdrawal, goldAmount: 3200, createdAt: daysAgo(4), updatedAt: daysAgo(4) },
57
+ { owner: 'player-1', type: MarketplaceTransactionType.Expired, goldAmount: 0, itemKey: 'items/leather-armor', itemName: 'Leather Armor', createdAt: daysAgo(7), updatedAt: daysAgo(7) },
58
+ { owner: 'player-1', type: MarketplaceTransactionType.BuyOrderPlaced, goldAmount: 500, itemKey: 'items/broad-sword', itemName: 'Broad Sword', createdAt: daysAgo(3), updatedAt: daysAgo(3) },
59
+ { owner: 'player-1', type: MarketplaceTransactionType.BuyOrderFulfilled, goldAmount: 2000, itemKey: 'items/angelic-sword', itemName: 'Angelic Sword', counterpartName: 'DarkKnight42', createdAt: daysAgo(1), updatedAt: daysAgo(1) },
60
+ { owner: 'player-1', type: MarketplaceTransactionType.BuyOrderCancelled, goldAmount: 300, itemKey: 'items/leather-armor', itemName: 'Leather Armor', createdAt: daysAgo(10), updatedAt: daysAgo(10) },
61
+ ];
62
+
17
63
  const Template: Story = () => {
18
64
  const [page, setPage] = React.useState(1);
65
+ const [selectedBlueprint, setSelectedBlueprint] = React.useState<IMarketplaceBlueprintSummary | undefined>();
66
+ const [quantity, setQuantity] = React.useState(1);
67
+ const [maxPrice, setMaxPrice] = React.useState(0);
68
+ const [selectedRarity, setSelectedRarity] = React.useState('');
69
+ const [blueprintResults, setBlueprintResults] = React.useState<IMarketplaceBlueprintSummary[]>([]);
70
+ const [blueprintLoading, setBlueprintLoading] = React.useState(false);
71
+ const [historyType, setHistoryType] = React.useState('All');
72
+
73
+ const handleBlueprintSearch = (request: any) => {
74
+ console.log('blueprint search:', request);
75
+ setBlueprintLoading(true);
76
+ // Simulate async search
77
+ setTimeout(() => {
78
+ const filtered = mockBlueprints.filter(bp => {
79
+ if (request.name && !bp.name.toLowerCase().includes(request.name.toLowerCase())) return false;
80
+ if (request.itemType && bp.type !== request.itemType) return false;
81
+ return true;
82
+ });
83
+ setBlueprintResults(filtered);
84
+ setBlueprintLoading(false);
85
+ }, 300);
86
+ };
87
+
88
+ const filteredTransactions = historyType === 'All'
89
+ ? mockTransactions
90
+ : mockTransactions.filter(tx => tx.type === historyType);
19
91
 
20
92
  return (
21
93
  <RPGUIRoot>
@@ -38,6 +110,7 @@ const Template: Story = () => {
38
110
  }))}
39
111
  equipmentSet={equipmentSetMock}
40
112
  onMarketPlaceItemBuy={tradeId => console.log(tradeId)}
113
+ onFulfillBuyOrder={buyOrderId => console.log('fulfill buy order:', buyOrderId)}
41
114
  availableGold={0}
42
115
  selectedItemToSell={null}
43
116
  onSelectedItemToSellRemove={() => console.log('click')}
@@ -51,6 +124,40 @@ const Template: Story = () => {
51
124
  currentPage={page}
52
125
  itemsPerPage={30}
53
126
  onPageChange={setPage}
127
+ // Buy Order props
128
+ buyOrderSelectedBlueprint={selectedBlueprint}
129
+ buyOrderQuantity={quantity}
130
+ buyOrderMaxPrice={maxPrice}
131
+ buyOrderSelectedRarity={selectedRarity}
132
+ onBuyOrderQuantityChange={setQuantity}
133
+ onBuyOrderMaxPriceChange={setMaxPrice}
134
+ onBuyOrderRarityChange={setSelectedRarity}
135
+ onPlaceBuyOrder={() => console.log('place buy order:', selectedBlueprint?.name, quantity, maxPrice, selectedRarity)}
136
+ yourBuyOrders={mockYourBuyOrders}
137
+ yourBuyOrdersTotal={mockYourBuyOrders.length}
138
+ yourBuyOrdersPage={1}
139
+ onYourBuyOrdersPageChange={p => console.log('your orders page:', p)}
140
+ onCancelBuyOrder={id => console.log('cancel order:', id)}
141
+ openBuyOrders={mockOpenBuyOrders}
142
+ openBuyOrdersTotal={mockOpenBuyOrders.length}
143
+ openBuyOrdersPage={1}
144
+ onOpenBuyOrdersPageChange={(p: number) => console.log('open orders page:', p)}
145
+ // Blueprint Search props
146
+ onBlueprintSearch={handleBlueprintSearch}
147
+ onBlueprintSelect={setSelectedBlueprint}
148
+ blueprintSearchResults={blueprintResults}
149
+ blueprintSearchTotalCount={blueprintResults.length}
150
+ blueprintSearchCurrentPage={1}
151
+ blueprintSearchIsLoading={blueprintLoading}
152
+ // History props
153
+ historyTransactions={filteredTransactions}
154
+ historyTotalCount={filteredTransactions.length}
155
+ historyCurrentPage={1}
156
+ historyItemsPerPage={10}
157
+ historySelectedType={historyType}
158
+ onHistoryTypeChange={setHistoryType}
159
+ onHistoryPageChange={p => console.log('history page:', p)}
160
+ onActiveTabChange={tab => console.log('tab changed:', tab)}
54
161
  />
55
162
  </RPGUIRoot>
56
163
  );
@@ -25,3 +25,14 @@ const Template: Story = () => (
25
25
  );
26
26
 
27
27
  export const Default = Template.bind({});
28
+
29
+ export const HighStackQuantity: Story = () => (
30
+ <RPGUIRoot>
31
+ <MarketplaceRows
32
+ atlasIMG={atlasIMG}
33
+ atlasJSON={atlasJSON}
34
+ itemPrice={10}
35
+ item={{ ...items[0], stackQty: 99999 }}
36
+ />
37
+ </RPGUIRoot>
38
+ );
@@ -0,0 +1,54 @@
1
+ import { Meta, Story } from '@storybook/react';
2
+ import React from 'react';
3
+ import { RPGUIRoot } from '../../../components/RPGUI/RPGUIRoot';
4
+ import {
5
+ ISegmentedToggleProps,
6
+ SegmentedToggle,
7
+ } from '../../../components/shared/SegmentedToggle';
8
+
9
+ const meta: Meta = {
10
+ title: 'UI/Buttons & Inputs/SegmentedToggle',
11
+ component: SegmentedToggle,
12
+ decorators: [
13
+ StoryComponent => (
14
+ <RPGUIRoot>
15
+ <div style={{ padding: '2rem', background: '#333' }}>
16
+ <StoryComponent />
17
+ </div>
18
+ </RPGUIRoot>
19
+ ),
20
+ ],
21
+ };
22
+
23
+ export default meta;
24
+
25
+ const Template: Story<ISegmentedToggleProps> = args => {
26
+ const [activeId, setActiveId] = React.useState(args.activeId);
27
+
28
+ return (
29
+ <SegmentedToggle
30
+ {...args}
31
+ activeId={activeId}
32
+ onChange={setActiveId}
33
+ />
34
+ );
35
+ };
36
+
37
+ export const MarketplaceModes = Template.bind({});
38
+ MarketplaceModes.args = {
39
+ activeId: 'all',
40
+ options: [
41
+ { id: 'all', label: 'All' },
42
+ { id: 'sell', label: 'Sell Offers' },
43
+ { id: 'buy', label: 'Buy Requests' },
44
+ ],
45
+ };
46
+
47
+ export const ShortOptions = Template.bind({});
48
+ ShortOptions.args = {
49
+ activeId: 'gold',
50
+ options: [
51
+ { id: 'gold', label: 'Gold' },
52
+ { id: 'dc', label: 'DC' },
53
+ ],
54
+ };
@@ -0,0 +1,43 @@
1
+ import { Meta, Story } from '@storybook/react';
2
+ import React from 'react';
3
+ import { RPGUIRoot } from '../../../components/RPGUI/RPGUIRoot';
4
+ import { ILabelPillProps, LabelPill } from '../../../components/shared/LabelPill';
5
+
6
+ const meta: Meta = {
7
+ title: 'UI/Text/LabelPill',
8
+ component: LabelPill,
9
+ decorators: [
10
+ StoryComponent => (
11
+ <RPGUIRoot>
12
+ <div style={{ padding: '2rem', background: '#333' }}>
13
+ <StoryComponent />
14
+ </div>
15
+ </RPGUIRoot>
16
+ ),
17
+ ],
18
+ };
19
+
20
+ export default meta;
21
+
22
+ const Template: Story<ILabelPillProps> = args => <LabelPill {...args} />;
23
+
24
+ export const Default = Template.bind({});
25
+ Default.args = {
26
+ children: 'Default',
27
+ };
28
+
29
+ export const BuyRequest = Template.bind({});
30
+ BuyRequest.args = {
31
+ children: 'Buy Request',
32
+ background: 'rgba(34, 197, 94, 0.16)',
33
+ borderColor: 'rgba(34, 197, 94, 0.4)',
34
+ color: '#bbf7d0',
35
+ };
36
+
37
+ export const Status = Template.bind({});
38
+ Status.args = {
39
+ children: 'Active',
40
+ background: '#22c55e',
41
+ borderColor: 'transparent',
42
+ color: '#fff',
43
+ };
@@ -0,0 +1,26 @@
1
+ import atlasJSON from '../../mocks/atlas/items/items.json';
2
+ import { resolveAtlasSpriteKey } from '../atlasUtils';
3
+
4
+ describe('resolveAtlasSpriteKey', () => {
5
+ it('returns exact atlas matches unchanged', () => {
6
+ expect(resolveAtlasSpriteKey(atlasJSON, 'swords/angelic-sword.png')).toBe(
7
+ 'swords/angelic-sword.png'
8
+ );
9
+ });
10
+
11
+ it('matches a missing category by sprite base name', () => {
12
+ expect(resolveAtlasSpriteKey(atlasJSON, 'items/angelic-sword')).toBe(
13
+ 'swords/angelic-sword.png'
14
+ );
15
+ });
16
+
17
+ it('matches a malformed story texture path by sprite base name', () => {
18
+ expect(resolveAtlasSpriteKey(atlasJSON, 'armor/wooden-shield.png')).toBe(
19
+ 'shields/wooden-shield.png'
20
+ );
21
+ });
22
+
23
+ it('returns null when no atlas sprite can be resolved', () => {
24
+ expect(resolveAtlasSpriteKey(atlasJSON, 'items/iron-sword')).toBeNull();
25
+ });
26
+ });
@@ -0,0 +1,80 @@
1
+ export const NO_IMAGE_SPRITE_KEY = 'others/no-image.png';
2
+
3
+ const atlasBaseNameLookupCache = new WeakMap<object, Map<string, string>>();
4
+
5
+ const getBaseName = (spriteKey: string): string | null => {
6
+ const normalizedKey = spriteKey.trim();
7
+ if (!normalizedKey) {
8
+ return null;
9
+ }
10
+
11
+ const fileName = normalizedKey.split('/').pop();
12
+ if (!fileName) {
13
+ return null;
14
+ }
15
+
16
+ return fileName.replace(/\.png$/, '');
17
+ };
18
+
19
+ const getAtlasBaseNameLookup = (atlasJSON: any): Map<string, string> => {
20
+ if (!atlasJSON || typeof atlasJSON !== 'object') {
21
+ return new Map();
22
+ }
23
+
24
+ const cachedLookup = atlasBaseNameLookupCache.get(atlasJSON);
25
+ if (cachedLookup) {
26
+ return cachedLookup;
27
+ }
28
+
29
+ const frames = atlasJSON?.frames ?? {};
30
+ const lookup = new Map<string, string>();
31
+
32
+ Object.keys(frames).forEach((frameKey) => {
33
+ const baseName = getBaseName(frameKey);
34
+ if (baseName && !lookup.has(baseName)) {
35
+ lookup.set(baseName, frameKey);
36
+ }
37
+ });
38
+
39
+ atlasBaseNameLookupCache.set(atlasJSON, lookup);
40
+
41
+ return lookup;
42
+ };
43
+
44
+ export const resolveAtlasSpriteKey = (
45
+ atlasJSON: any,
46
+ spriteKey?: string | null
47
+ ): string | null => {
48
+ if (!spriteKey) {
49
+ return null;
50
+ }
51
+
52
+ const normalizedKey = spriteKey.trim();
53
+ if (!normalizedKey) {
54
+ return null;
55
+ }
56
+
57
+ const frames = atlasJSON?.frames;
58
+ if (!frames) {
59
+ return null;
60
+ }
61
+
62
+ if (frames[normalizedKey]) {
63
+ return normalizedKey;
64
+ }
65
+
66
+ const withPngExtension = normalizedKey.endsWith('.png')
67
+ ? normalizedKey
68
+ : `${normalizedKey}.png`;
69
+
70
+ if (frames[withPngExtension]) {
71
+ return withPngExtension;
72
+ }
73
+
74
+ const baseName = getBaseName(normalizedKey);
75
+ if (!baseName) {
76
+ return null;
77
+ }
78
+
79
+ return getAtlasBaseNameLookup(atlasJSON).get(baseName) ?? null;
80
+ };