@rpg-engine/long-bow 0.3.51 → 0.3.52

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 (39) hide show
  1. package/dist/components/CircularController/CircularController.d.ts +7 -3
  2. package/dist/components/Item/Inventory/ItemContainer.d.ts +4 -0
  3. package/dist/components/Item/Inventory/ItemSlot.d.ts +1 -0
  4. package/dist/components/Shortcuts/Shortcuts.d.ts +21 -0
  5. package/dist/components/Shortcuts/ShortcutsSetter.d.ts +12 -0
  6. package/dist/components/Shortcuts/SingleShortcut.d.ts +1 -0
  7. package/dist/components/Spellbook/Spellbook.d.ts +5 -2
  8. package/dist/components/Spellbook/constants.d.ts +3 -3
  9. package/dist/index.d.ts +1 -1
  10. package/dist/long-bow.cjs.development.js +271 -120
  11. package/dist/long-bow.cjs.development.js.map +1 -1
  12. package/dist/long-bow.cjs.production.min.js +1 -1
  13. package/dist/long-bow.cjs.production.min.js.map +1 -1
  14. package/dist/long-bow.esm.js +273 -120
  15. package/dist/long-bow.esm.js.map +1 -1
  16. package/dist/stories/{QuickSpells.stories.d.ts → Shortcuts.stories.d.ts} +2 -2
  17. package/package.json +1 -1
  18. package/src/components/Abstractions/SlotsContainer.tsx +2 -2
  19. package/src/components/CircularController/CircularController.tsx +118 -36
  20. package/src/components/Item/Inventory/ItemContainer.tsx +39 -4
  21. package/src/components/Item/Inventory/ItemSlot.tsx +38 -3
  22. package/src/components/Shortcuts/Shortcuts.tsx +138 -0
  23. package/src/components/Shortcuts/ShortcutsSetter.tsx +127 -0
  24. package/src/components/Shortcuts/SingleShortcut.ts +61 -0
  25. package/src/components/Spellbook/Spellbook.tsx +15 -8
  26. package/src/components/Spellbook/constants.ts +5 -9
  27. package/src/components/TradingMenu/TradingMenu.tsx +2 -2
  28. package/src/components/TradingMenu/items.mock.ts +59 -0
  29. package/src/index.tsx +1 -1
  30. package/src/mocks/itemContainer.mocks.ts +22 -20
  31. package/src/stories/CircullarController.stories.tsx +9 -5
  32. package/src/stories/ItemContainer.stories.tsx +70 -1
  33. package/src/stories/Shortcuts.stories.tsx +39 -0
  34. package/src/stories/Spellbook.stories.tsx +35 -38
  35. package/dist/components/Spellbook/QuickSpells.d.ts +0 -10
  36. package/dist/components/Spellbook/SpellbookShortcuts.d.ts +0 -10
  37. package/src/components/Spellbook/QuickSpells.tsx +0 -120
  38. package/src/components/Spellbook/SpellbookShortcuts.tsx +0 -77
  39. package/src/stories/QuickSpells.stories.tsx +0 -38
@@ -1,5 +1,5 @@
1
1
  import { Meta } from '@storybook/react';
2
- import { QuickSpellsProps } from '../components/Spellbook/QuickSpells';
2
+ import { ShortcutsProps } from '../components/Shortcuts/Shortcuts';
3
3
  declare const meta: Meta;
4
4
  export default meta;
5
- export declare const Default: import("@storybook/csf").AnnotatedStoryFn<import("@storybook/react").ReactFramework, QuickSpellsProps>;
5
+ export declare const Default: import("@storybook/csf").AnnotatedStoryFn<import("@storybook/react").ReactFramework, ShortcutsProps>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpg-engine/long-bow",
3
- "version": "0.3.51",
3
+ "version": "0.3.52",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
@@ -29,8 +29,8 @@ export const SlotsContainer: React.FC<IProps> = ({
29
29
  onClose();
30
30
  }
31
31
  }}
32
- width="330px"
33
- cancelDrag=".item-container-body"
32
+ width="400px"
33
+ cancelDrag=".item-container-body, #shortcuts_list"
34
34
  onPositionChange={({ x, y }) => {
35
35
  if (onPositionChange) {
36
36
  onPositionChange({ x, y });
@@ -1,23 +1,36 @@
1
- import { IRawSpell } from '@rpg-engine/shared';
1
+ import {
2
+ getItemTextureKeyPath,
3
+ IItem,
4
+ IItemContainer,
5
+ IRawSpell,
6
+ } from '@rpg-engine/shared';
2
7
  import React from 'react';
3
8
  import styled from 'styled-components';
4
9
  import { uiColors } from '../../constants/uiColors';
5
- import { SpellShortcut } from '../Spellbook/QuickSpells';
10
+ import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
11
+ import { IShortcut, ShortcutType } from '../Shortcuts/Shortcuts';
12
+ import { SingleShortcut } from '../Shortcuts/SingleShortcut';
6
13
 
7
14
  export type CircularControllerProps = {
8
15
  onActionClick: () => void;
9
16
  onCancelClick: () => void;
10
- onSpellClick: (spellKey: string) => void;
17
+ onShortcutClick: (index: number) => void;
11
18
  mana: number;
12
- spells: IRawSpell[];
19
+ shortcuts: IShortcut[];
20
+ inventory?: IItemContainer | null;
21
+ atlasIMG: any;
22
+ atlasJSON: any;
13
23
  };
14
24
 
15
25
  export const CircularController: React.FC<CircularControllerProps> = ({
16
26
  onActionClick,
17
27
  onCancelClick,
18
- onSpellClick,
28
+ onShortcutClick,
19
29
  mana,
20
- spells,
30
+ shortcuts,
31
+ inventory,
32
+ atlasIMG,
33
+ atlasJSON,
21
34
  }) => {
22
35
  const onTouchStart = (e: React.TouchEvent<HTMLButtonElement>) => {
23
36
  const target = e.target as HTMLButtonElement;
@@ -35,33 +48,91 @@ export const CircularController: React.FC<CircularControllerProps> = ({
35
48
  action();
36
49
  };
37
50
 
51
+ const renderShortcut = (i: number) => {
52
+ let variant = '';
53
+
54
+ if (i === 0) variant = 'top';
55
+ else if (i >= 3) variant = `bottom-${i - 3}`;
56
+
57
+ const onShortcutClickBinded =
58
+ shortcuts[i]?.type !== ShortcutType.None
59
+ ? onShortcutClick.bind(null, i)
60
+ : () => {};
61
+
62
+ if (shortcuts[i]?.type === ShortcutType.Item) {
63
+ const payload = shortcuts[i]?.payload as IItem | undefined;
64
+
65
+ let itemsFromEquipment: (IItem | undefined | null)[] = [];
66
+
67
+ if (inventory) {
68
+ Object.keys(inventory.slots).forEach(i => {
69
+ const index = parseInt(i);
70
+
71
+ if (inventory.slots[index]?.key === payload?.key) {
72
+ itemsFromEquipment.push(inventory.slots[index]);
73
+ }
74
+ });
75
+ }
76
+
77
+ const totalQty = itemsFromEquipment.reduce(
78
+ (acc, item) => acc + (item?.stackQty || 1),
79
+ 0
80
+ );
81
+
82
+ return (
83
+ <StyledShortcut
84
+ key={i}
85
+ onTouchStart={onTouchStart}
86
+ onTouchEnd={onTouchEnd.bind(null, onShortcutClickBinded)}
87
+ disabled={false}
88
+ className={variant}
89
+ >
90
+ {payload && (
91
+ <SpriteFromAtlas
92
+ atlasIMG={atlasIMG}
93
+ atlasJSON={atlasJSON}
94
+ spriteKey={getItemTextureKeyPath(
95
+ {
96
+ key: payload.texturePath,
97
+ texturePath: payload.texturePath,
98
+ stackQty: payload.stackQty || 1,
99
+ },
100
+ atlasJSON
101
+ )}
102
+ width={32}
103
+ height={32}
104
+ imgScale={1.4}
105
+ imgStyle={{ left: '4px' }}
106
+ />
107
+ )}
108
+ <span className="qty">{totalQty}</span>
109
+ </StyledShortcut>
110
+ );
111
+ }
112
+
113
+ const payload = shortcuts[i]?.payload as IRawSpell | undefined;
114
+
115
+ return (
116
+ <StyledShortcut
117
+ key={i}
118
+ onTouchStart={onTouchStart}
119
+ onTouchEnd={onTouchEnd.bind(null, onShortcutClickBinded)}
120
+ disabled={mana < (payload?.manaCost ?? 0)}
121
+ className={variant}
122
+ >
123
+ <span className="mana">{payload && payload.manaCost}</span>
124
+ <span className="magicWords">
125
+ {payload?.magicWords.split(' ').map(word => word[0])}
126
+ </span>
127
+ </StyledShortcut>
128
+ );
129
+ };
130
+
38
131
  return (
39
132
  <ButtonsContainer>
40
- <SpellsContainer>
41
- {Array.from({ length: 4 }).map((_, i) => {
42
- const variant = i === 0 ? 'top' : i === 3 ? 'bottom' : '';
43
- const spell = spells[i];
44
-
45
- const onSpellClickBinded = spell
46
- ? onSpellClick.bind(null, spell.key)
47
- : () => {};
48
-
49
- return (
50
- <StyledShortcut
51
- key={i}
52
- disabled={mana < spell?.manaCost}
53
- onTouchStart={onTouchStart}
54
- onTouchEnd={onTouchEnd.bind(null, onSpellClickBinded)}
55
- className={variant}
56
- >
57
- <span className="mana">{spell?.key && spell?.manaCost}</span>
58
- <span className="magicWords">
59
- {spell?.magicWords.split(' ').map(word => word[0])}
60
- </span>
61
- </StyledShortcut>
62
- );
63
- })}
64
- </SpellsContainer>
133
+ <ShortcutsContainer>
134
+ {Array.from({ length: 6 }).map((_, i) => renderShortcut(i))}
135
+ </ShortcutsContainer>
65
136
  <Button
66
137
  onTouchStart={onTouchStart}
67
138
  onTouchEnd={onTouchEnd.bind(null, onActionClick)}
@@ -93,6 +164,7 @@ const Button = styled.button`
93
164
  justify-content: center;
94
165
  position: relative;
95
166
  transition: background-color 0.1s;
167
+ margin-top: -3rem;
96
168
 
97
169
  &.active {
98
170
  background-color: ${uiColors.gray};
@@ -125,28 +197,38 @@ const ButtonsContainer = styled.div`
125
197
  gap: 0.5rem;
126
198
  `;
127
199
 
128
- const SpellsContainer = styled.div`
200
+ const ShortcutsContainer = styled.div`
129
201
  display: flex;
130
202
  align-items: center;
131
203
  justify-content: center;
132
- gap: 0.4rem;
204
+ gap: 0.5rem;
133
205
  flex-direction: column;
206
+ margin-top: 3rem;
134
207
 
135
208
  .top {
136
209
  transform: translate(93%, 25%);
137
210
  }
138
211
 
139
- .bottom {
212
+ .bottom-0 {
140
213
  transform: translate(93%, -25%);
141
214
  }
215
+
216
+ .bottom-1 {
217
+ transform: translate(-120%, calc(-5.5rem));
218
+ }
219
+
220
+ .bottom-2 {
221
+ transform: translate(-30%, calc(-5.5rem - 25%));
222
+ }
142
223
  `;
143
224
 
144
- const StyledShortcut = styled(SpellShortcut)`
225
+ const StyledShortcut = styled(SingleShortcut)`
145
226
  width: 2.5rem;
146
227
  height: 2.5rem;
147
228
  transition: background-color 0.1s;
148
229
 
149
- .mana {
230
+ .mana,
231
+ .qty {
150
232
  font-size: 0.5rem;
151
233
  }
152
234
 
@@ -1,10 +1,17 @@
1
- import { IItem, IItemContainer, ItemContainerType } from '@rpg-engine/shared';
1
+ import {
2
+ IItem,
3
+ IItemContainer,
4
+ ItemContainerType,
5
+ ItemType,
6
+ } from '@rpg-engine/shared';
2
7
  import React, { useState } from 'react';
3
8
  import styled from 'styled-components';
4
9
  import { SlotsContainer } from '../../Abstractions/SlotsContainer';
5
10
  import { ItemQuantitySelector } from './ItemQuantitySelector';
6
11
 
7
12
  import { IPosition } from '../../../types/eventTypes';
13
+ import { IShortcut } from '../../Shortcuts/Shortcuts';
14
+ import { ShortcutsSetter } from '../../Shortcuts/ShortcutsSetter';
8
15
  import { ItemSlot } from './ItemSlot';
9
16
 
10
17
  export interface IItemContainerProps {
@@ -37,6 +44,9 @@ export interface IItemContainerProps {
37
44
  atlasIMG: any;
38
45
  disableContextMenu?: boolean;
39
46
  initialPosition?: { x: number; y: number };
47
+ shortcuts?: IShortcut[];
48
+ setItemShortcut?: (key: string, index: number) => void;
49
+ removeShortcut?: (index: number) => void;
40
50
  }
41
51
 
42
52
  export const ItemContainer: React.FC<IItemContainerProps> = ({
@@ -57,12 +67,16 @@ export const ItemContainer: React.FC<IItemContainerProps> = ({
57
67
  initialPosition,
58
68
  checkIfItemShouldDragEnd,
59
69
  dragScale,
70
+ shortcuts,
71
+ setItemShortcut,
72
+ removeShortcut,
60
73
  }) => {
61
74
  const [quantitySelect, setQuantitySelect] = useState({
62
75
  isOpen: false,
63
76
  maxQuantity: 1,
64
77
  callback: (_quantity: number) => {},
65
78
  });
79
+ const [settingShortcutIndex, setSettingShortcutIndex] = useState(-1);
66
80
 
67
81
  const onRenderSlots = () => {
68
82
  const slots = [];
@@ -78,8 +92,17 @@ export const ItemContainer: React.FC<IItemContainerProps> = ({
78
92
  onMouseOver={(event, slotIndex, item) => {
79
93
  if (onMouseOver) onMouseOver(event, slotIndex, item);
80
94
  }}
81
- onClick={(ItemType, ContainerType, item) => {
82
- if (onItemClick) onItemClick(item, ItemType, ContainerType);
95
+ onClick={(itemType, containerType, item) => {
96
+ if (settingShortcutIndex !== -1) {
97
+ setSettingShortcutIndex(-1);
98
+
99
+ if (
100
+ itemType === ItemType.Consumable ||
101
+ itemType === ItemType.Tool
102
+ ) {
103
+ setItemShortcut?.(item.key, settingShortcutIndex);
104
+ }
105
+ } else if (onItemClick) onItemClick(item, itemType, containerType);
83
106
  }}
84
107
  onSelected={(optionId: string, item: IItem) => {
85
108
  if (onSelected) onSelected(optionId, item);
@@ -110,6 +133,7 @@ export const ItemContainer: React.FC<IItemContainerProps> = ({
110
133
  }}
111
134
  atlasIMG={atlasIMG}
112
135
  atlasJSON={atlasJSON}
136
+ isSelectingShortcut={settingShortcutIndex !== -1}
113
137
  />
114
138
  );
115
139
  }
@@ -123,6 +147,18 @@ export const ItemContainer: React.FC<IItemContainerProps> = ({
123
147
  onClose={onClose}
124
148
  initialPosition={initialPosition}
125
149
  >
150
+ {type === ItemContainerType.Inventory &&
151
+ shortcuts &&
152
+ removeShortcut && (
153
+ <ShortcutsSetter
154
+ setSettingShortcutIndex={setSettingShortcutIndex}
155
+ settingShortcutIndex={settingShortcutIndex}
156
+ shortcuts={shortcuts}
157
+ removeShortcut={removeShortcut}
158
+ atlasIMG={atlasIMG}
159
+ atlasJSON={atlasJSON}
160
+ />
161
+ )}
126
162
  <ItemsContainer className="item-container-body">
127
163
  {onRenderSlots()}
128
164
  </ItemsContainer>
@@ -155,7 +191,6 @@ export const ItemContainer: React.FC<IItemContainerProps> = ({
155
191
  };
156
192
 
157
193
  const ItemsContainer = styled.div`
158
- max-width: 280px;
159
194
  display: flex;
160
195
  justify-content: center;
161
196
  flex-wrap: wrap;
@@ -74,6 +74,7 @@ interface IProps {
74
74
  atlasJSON: any;
75
75
  atlasIMG: any;
76
76
  isContextMenuDisabled?: boolean;
77
+ isSelectingShortcut?: boolean;
77
78
  }
78
79
 
79
80
  export const ItemSlot: React.FC<IProps> = observer(
@@ -97,6 +98,7 @@ export const ItemSlot: React.FC<IProps> = observer(
97
98
  openQuantitySelector,
98
99
  checkIfItemShouldDragEnd,
99
100
  dragScale,
101
+ isSelectingShortcut,
100
102
  }) => {
101
103
  const [isTooltipVisible, setTooltipVisible] = useState(false);
102
104
 
@@ -117,6 +119,7 @@ export const ItemSlot: React.FC<IProps> = observer(
117
119
  setIsFocused(false);
118
120
 
119
121
  if (item) {
122
+ console.log(item);
120
123
  setContextActions(generateContextMenu(item, containerType));
121
124
  }
122
125
  }, [item]);
@@ -278,12 +281,17 @@ export const ItemSlot: React.FC<IProps> = observer(
278
281
  .elementFromPoint(clientX, clientY)
279
282
  ?.dispatchEvent(simulatedEvent);
280
283
  }}
284
+ isSelectingShortcut={
285
+ isSelectingShortcut &&
286
+ (item?.type === ItemType.Consumable || item?.type === ItemType.Tool)
287
+ }
281
288
  >
282
289
  <Draggable
290
+ axis={isSelectingShortcut ? 'none' : 'both'}
283
291
  defaultClassName={item ? 'draggable' : 'empty-slot'}
284
292
  scale={dragScale}
285
293
  onStop={(e, data) => {
286
- if (wasDragged && item) {
294
+ if (wasDragged && item && !isSelectingShortcut) {
287
295
  //@ts-ignore
288
296
  const classes: string[] = Array.from(e.target?.classList);
289
297
 
@@ -330,14 +338,14 @@ export const ItemSlot: React.FC<IProps> = observer(
330
338
  }
331
339
  }, 100);
332
340
  } else if (item) {
333
- if (!isContextMenuDisabled)
341
+ if (!isContextMenuDisabled && !isSelectingShortcut)
334
342
  setIsContextMenuVisible(!isContextMenuVisible);
335
343
 
336
344
  onClick(item.type, containerType, item);
337
345
  }
338
346
  }}
339
347
  onStart={() => {
340
- if (!item) {
348
+ if (!item || isSelectingShortcut) {
341
349
  return;
342
350
  }
343
351
 
@@ -417,6 +425,7 @@ const rarityColor = (item: IItem | null) => {
417
425
 
418
426
  interface ContainerTypes {
419
427
  item: IItem | null;
428
+ isSelectingShortcut?: boolean;
420
429
  }
421
430
 
422
431
  const Container = styled.div<ContainerTypes>`
@@ -432,6 +441,32 @@ const Container = styled.div<ContainerTypes>`
432
441
  //background-color: ${({ item }) => rarityColor(item)};
433
442
  }
434
443
  position: relative;
444
+
445
+ &::before {
446
+ content: '';
447
+ position: absolute;
448
+ top: 0;
449
+ left: 0;
450
+ width: 100%;
451
+ height: 100%;
452
+ z-index: 1;
453
+ border-radius: 12px;
454
+ pointer-events: none;
455
+ animation: ${({ isSelectingShortcut }) =>
456
+ isSelectingShortcut ? 'bg-color-change 1s infinite' : 'none'};
457
+
458
+ @keyframes bg-color-change {
459
+ 0% {
460
+ background-color: rgba(255 255 255 / 0.5);
461
+ }
462
+ 50% {
463
+ background-color: transparent;
464
+ }
465
+ 100% {
466
+ background-color: rgba(255 255 255 / 0.5);
467
+ }
468
+ }
469
+ }
435
470
  `;
436
471
 
437
472
  const ItemContainer = styled.div<{ isFocused?: boolean }>`
@@ -0,0 +1,138 @@
1
+ import {
2
+ getItemTextureKeyPath,
3
+ IItem,
4
+ IItemContainer,
5
+ IRawSpell,
6
+ } from '@rpg-engine/shared';
7
+ import React, { useEffect } from 'react';
8
+ import styled from 'styled-components';
9
+ import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
10
+ import { SingleShortcut } from './SingleShortcut';
11
+
12
+ export enum ShortcutType {
13
+ Spell,
14
+ Item,
15
+ None,
16
+ }
17
+
18
+ export interface IShortcut {
19
+ type: ShortcutType;
20
+ payload: IRawSpell | IItem | null;
21
+ }
22
+
23
+ export type ShortcutsProps = {
24
+ shortcuts: IShortcut[];
25
+ onShortcutCast: (index: number) => void;
26
+ mana: number;
27
+ isBlockedCastingByKeyboard?: boolean;
28
+ inventory?: IItemContainer | null;
29
+ atlasJSON: any;
30
+ atlasIMG: any;
31
+ };
32
+
33
+ export const Shortcuts: React.FC<ShortcutsProps> = ({
34
+ shortcuts,
35
+ onShortcutCast,
36
+ mana,
37
+ isBlockedCastingByKeyboard = false,
38
+ atlasJSON,
39
+ atlasIMG,
40
+ inventory,
41
+ }) => {
42
+ useEffect(() => {
43
+ const handleKeyDown = (e: KeyboardEvent) => {
44
+ if (isBlockedCastingByKeyboard) return;
45
+
46
+ const shortcutIndex = Number(e.key) - 1;
47
+ if (shortcutIndex >= 0 && shortcutIndex <= 5) {
48
+ onShortcutCast(shortcutIndex);
49
+ }
50
+ };
51
+
52
+ window.addEventListener('keydown', handleKeyDown);
53
+
54
+ return () => {
55
+ window.removeEventListener('keydown', handleKeyDown);
56
+ };
57
+ }, [shortcuts, isBlockedCastingByKeyboard]);
58
+
59
+ return (
60
+ <List>
61
+ {Array.from({ length: 6 }).map((_, i) => {
62
+ if (shortcuts[i]?.type === ShortcutType.Item) {
63
+ const payload = shortcuts[i]?.payload as IItem | undefined;
64
+
65
+ let itemsFromEquipment: (IItem | undefined | null)[] = [];
66
+
67
+ if (inventory) {
68
+ Object.keys(inventory.slots).forEach(i => {
69
+ const index = parseInt(i);
70
+
71
+ if (inventory.slots[index]?.key === payload?.key) {
72
+ itemsFromEquipment.push(inventory.slots[index]);
73
+ }
74
+ });
75
+ }
76
+
77
+ const totalQty = itemsFromEquipment.reduce(
78
+ (acc, item) => acc + (item?.stackQty || 1),
79
+ 0
80
+ );
81
+
82
+ return (
83
+ <SingleShortcut
84
+ key={i}
85
+ onClick={onShortcutCast.bind(null, i)}
86
+ disabled={false}
87
+ >
88
+ {payload && (
89
+ <SpriteFromAtlas
90
+ atlasIMG={atlasIMG}
91
+ atlasJSON={atlasJSON}
92
+ spriteKey={getItemTextureKeyPath(
93
+ {
94
+ key: payload.texturePath,
95
+ texturePath: payload.texturePath,
96
+ stackQty: payload.stackQty || 1,
97
+ },
98
+ atlasJSON
99
+ )}
100
+ width={32}
101
+ height={32}
102
+ />
103
+ )}
104
+ <span className="qty">{totalQty}</span>
105
+ <span className="keyboard">{i + 1}</span>
106
+ </SingleShortcut>
107
+ );
108
+ }
109
+
110
+ const payload = shortcuts[i]?.payload as IRawSpell | undefined;
111
+
112
+ return (
113
+ <SingleShortcut
114
+ key={i}
115
+ onClick={onShortcutCast.bind(null, i)}
116
+ disabled={mana < (payload?.manaCost ?? 0)}
117
+ >
118
+ <span className="mana">{payload && payload.manaCost}</span>
119
+ <span className="magicWords">
120
+ {payload?.magicWords.split(' ').map(word => word[0])}
121
+ </span>
122
+ <span className="keyboard">{i + 1}</span>
123
+ </SingleShortcut>
124
+ );
125
+ })}
126
+ </List>
127
+ );
128
+ };
129
+
130
+ const List = styled.p`
131
+ width: 100%;
132
+ display: flex;
133
+ align-items: center;
134
+ justify-content: center;
135
+ gap: 0.5rem;
136
+ box-sizing: border-box;
137
+ margin: 0 !important;
138
+ `;
@@ -0,0 +1,127 @@
1
+ import { getItemTextureKeyPath, IItem, IRawSpell } from '@rpg-engine/shared';
2
+ import React from 'react';
3
+ import styled from 'styled-components';
4
+ import { uiColors } from '../../constants/uiColors';
5
+ import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
6
+ import { IShortcut, ShortcutType } from './Shortcuts';
7
+
8
+ type ShortcutsSetterProps = {
9
+ setSettingShortcutIndex: (index: number) => void;
10
+ settingShortcutIndex: number;
11
+ shortcuts: IShortcut[];
12
+ removeShortcut: (index: number) => void;
13
+ atlasJSON: any;
14
+ atlasIMG: any;
15
+ };
16
+
17
+ export const ShortcutsSetter: React.FC<ShortcutsSetterProps> = ({
18
+ setSettingShortcutIndex,
19
+ settingShortcutIndex,
20
+ shortcuts,
21
+ removeShortcut,
22
+ atlasJSON,
23
+ atlasIMG,
24
+ }) => {
25
+ const getContent = (index: number) => {
26
+ if (shortcuts[index]?.type === ShortcutType.Item) {
27
+ const payload = shortcuts[index]?.payload as IItem | undefined;
28
+
29
+ if (!payload) return null;
30
+
31
+ return (
32
+ <SpriteFromAtlas
33
+ atlasIMG={atlasIMG}
34
+ atlasJSON={atlasJSON}
35
+ spriteKey={getItemTextureKeyPath(
36
+ {
37
+ key: payload.texturePath,
38
+ texturePath: payload.texturePath,
39
+ stackQty: payload.stackQty || 1,
40
+ },
41
+ atlasJSON
42
+ )}
43
+ width={32}
44
+ height={32}
45
+ imgScale={1.6}
46
+ imgStyle={{ left: '5px' }}
47
+ />
48
+ );
49
+ }
50
+
51
+ const payload = shortcuts[index]?.payload as IRawSpell | undefined;
52
+
53
+ return <span>{payload?.magicWords.split(' ').map(word => word[0])}</span>;
54
+ };
55
+
56
+ return (
57
+ <Container>
58
+ <p>Shortcuts:</p>
59
+ <List id="shortcuts_list">
60
+ {Array.from({ length: 6 }).map((_, i) => (
61
+ <Shortcut
62
+ key={i}
63
+ onClick={() => {
64
+ removeShortcut(i);
65
+ if (!shortcuts[i] || shortcuts[i].type === ShortcutType.None)
66
+ setSettingShortcutIndex(i);
67
+ }}
68
+ disabled={settingShortcutIndex !== -1 && settingShortcutIndex !== i}
69
+ isBeingSet={settingShortcutIndex === i}
70
+ >
71
+ {getContent(i)}
72
+ </Shortcut>
73
+ ))}
74
+ </List>
75
+ </Container>
76
+ );
77
+ };
78
+
79
+ const Container = styled.div`
80
+ p {
81
+ margin: 0;
82
+ margin-left: 0.5rem;
83
+ }
84
+ `;
85
+
86
+ const Shortcut = styled.button<{ isBeingSet?: boolean }>`
87
+ width: 2.6rem;
88
+ height: 2.6rem;
89
+ background-color: ${uiColors.lightGray};
90
+ border: 2px solid
91
+ ${({ isBeingSet }) => (isBeingSet ? uiColors.yellow : uiColors.darkGray)};
92
+ border-radius: 50%;
93
+ text-transform: uppercase;
94
+ font-size: 0.7rem;
95
+ font-weight: bold;
96
+ display: flex;
97
+ align-items: center;
98
+ justify-content: center;
99
+
100
+ span {
101
+ margin-top: 4px;
102
+ }
103
+
104
+ &:hover,
105
+ &:focus {
106
+ background-color: ${uiColors.darkGray};
107
+ }
108
+
109
+ &:active {
110
+ background-color: ${uiColors.gray};
111
+ }
112
+
113
+ &:disabled {
114
+ opacity: 0.5;
115
+ }
116
+ `;
117
+
118
+ const List = styled.div`
119
+ width: 100%;
120
+ display: flex;
121
+ align-items: center;
122
+ gap: 0.5rem;
123
+ padding-bottom: 0.5rem;
124
+ padding-left: 0.5rem;
125
+ box-sizing: border-box;
126
+ margin: 0 !important;
127
+ `;