@rpg-engine/long-bow 0.8.35 → 0.8.37

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpg-engine/long-bow",
3
- "version": "0.8.35",
3
+ "version": "0.8.37",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
@@ -84,7 +84,7 @@
84
84
  "dependencies": {
85
85
  "@capacitor/core": "^6.1.0",
86
86
  "@rollup/plugin-image": "^2.1.1",
87
- "@rpg-engine/shared": "^0.10.0",
87
+ "@rpg-engine/shared": "^0.9.104",
88
88
  "dayjs": "^1.11.2",
89
89
  "font-awesome": "^4.7.0",
90
90
  "fs-extra": "^10.1.0",
@@ -6,6 +6,7 @@ import {
6
6
  IInformationCenterItem,
7
7
  IInformationCenterNPC,
8
8
  IVideoGuide,
9
+ isMobileOrTablet,
9
10
  } from '@rpg-engine/shared';
10
11
  import { DraggableContainer } from '../DraggableContainer';
11
12
  import { InternalTabs } from '../InternalTabs/InternalTabs';
@@ -45,6 +46,7 @@ export const InformationCenter: React.FC<IInformationCenterProps> = ({
45
46
  initialSearchQuery = '',
46
47
  }) => {
47
48
  const [activeTab, setActiveTab] = useState('bestiary');
49
+ const isMobile = isMobileOrTablet();
48
50
 
49
51
  if (loading) {
50
52
  return <LoadingMessage>Loading...</LoadingMessage>;
@@ -103,6 +105,8 @@ export const InformationCenter: React.FC<IInformationCenterProps> = ({
103
105
  <DraggableContainer
104
106
  title="Information Center"
105
107
  type={RPGUIContainerTypes.Framed}
108
+ width={isMobile ? '95%' : '80%'}
109
+ minWidth="300px"
106
110
  >
107
111
  <Container>
108
112
  <InternalTabs
@@ -122,9 +126,23 @@ export const InformationCenter: React.FC<IInformationCenterProps> = ({
122
126
 
123
127
  const Container = styled.div`
124
128
  width: 100%;
125
- max-width: 800px;
129
+ max-width: 100%;
126
130
  margin: 0 auto;
127
- padding: 1rem;
131
+ padding: 0.125rem;
132
+ overflow: hidden;
133
+ box-sizing: border-box;
134
+
135
+ @media (min-width: 320px) {
136
+ padding: 0.25rem;
137
+ }
138
+
139
+ @media (min-width: 360px) {
140
+ padding: 0.5rem;
141
+ }
142
+
143
+ @media (min-width: 480px) {
144
+ padding: 0.75rem;
145
+ }
128
146
  `;
129
147
 
130
148
  const LoadingMessage = styled.div`
@@ -1,5 +1,6 @@
1
- import React from 'react';
1
+ import React, { useState } from 'react';
2
2
  import styled from 'styled-components';
3
+ import { UI_BREAKPOINT_MOBILE } from '../../constants/uiBreakpoints';
3
4
  import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
4
5
 
5
6
  interface IInformationCenterCellProps {
@@ -25,13 +26,46 @@ export const InformationCenterCell: React.FC<IInformationCenterCellProps> = ({
25
26
  onMouseMove,
26
27
  onTouchStart,
27
28
  }) => {
29
+ const [touchStartY, setTouchStartY] = useState<number | null>(null);
30
+ const [touchStartX, setTouchStartX] = useState<number | null>(null);
31
+ const [isTouchScrolling, setIsTouchScrolling] = useState(false);
32
+
33
+ const handleTouchStart = (e: React.TouchEvent) => {
34
+ setTouchStartY(e.touches[0].clientY);
35
+ setTouchStartX(e.touches[0].clientX);
36
+ setIsTouchScrolling(false);
37
+ };
38
+
39
+ const handleTouchMove = (e: React.TouchEvent) => {
40
+ if (touchStartY !== null && touchStartX !== null) {
41
+ const touchDiffY = Math.abs(e.touches[0].clientY - touchStartY);
42
+ const touchDiffX = Math.abs(e.touches[0].clientX - touchStartX);
43
+
44
+ // If user moved finger more than 15px in any direction, consider it scrolling
45
+ if (touchDiffY > 15 || touchDiffX > 15) {
46
+ setIsTouchScrolling(true);
47
+ e.stopPropagation(); // Prevent parent elements from handling this touch
48
+ }
49
+ }
50
+ };
51
+
52
+ const handleTouchEnd = (e: React.TouchEvent) => {
53
+ if (!isTouchScrolling && onTouchStart) {
54
+ onTouchStart(e);
55
+ }
56
+ setTouchStartY(null);
57
+ setTouchStartX(null);
58
+ };
59
+
28
60
  return (
29
61
  <CellContainer
30
62
  onClick={onClick}
31
63
  onMouseEnter={onMouseEnter}
32
64
  onMouseLeave={onMouseLeave}
33
65
  onMouseMove={onMouseMove}
34
- onTouchStart={onTouchStart}
66
+ onTouchStart={handleTouchStart}
67
+ onTouchMove={handleTouchMove}
68
+ onTouchEnd={handleTouchEnd}
35
69
  >
36
70
  <SpriteContainer>
37
71
  <SpriteFromAtlas
@@ -43,7 +77,9 @@ export const InformationCenterCell: React.FC<IInformationCenterCellProps> = ({
43
77
  imgScale={1}
44
78
  />
45
79
  </SpriteContainer>
46
- <CellName>{name}</CellName>
80
+ <CellNameContainer>
81
+ <CellName>{name}</CellName>
82
+ </CellNameContainer>
47
83
  </CellContainer>
48
84
  );
49
85
  };
@@ -51,7 +87,7 @@ export const InformationCenterCell: React.FC<IInformationCenterCellProps> = ({
51
87
  const CellContainer = styled.div`
52
88
  position: relative;
53
89
  background: rgba(0, 0, 0, 0.2);
54
- padding: 0.75rem;
90
+ padding: 0.2rem;
55
91
  border-radius: 4px;
56
92
  display: flex;
57
93
  flex-direction: column;
@@ -61,9 +97,22 @@ const CellContainer = styled.div`
61
97
  transition: background-color 0.2s ease;
62
98
  width: 100%;
63
99
  height: 100%;
64
- min-width: 120px;
65
- min-height: 120px;
100
+ min-height: 90px;
66
101
  box-sizing: border-box;
102
+ gap: 0.2rem;
103
+ margin: 0;
104
+
105
+ @media (min-width: 360px) {
106
+ padding: 0.3rem;
107
+ gap: 0.3rem;
108
+ margin: 1px;
109
+ width: calc(100% - 2px);
110
+ min-height: 100px;
111
+ }
112
+
113
+ @media (min-width: ${UI_BREAKPOINT_MOBILE}) {
114
+ min-height: 110px;
115
+ }
67
116
 
68
117
  &:hover {
69
118
  background: rgba(0, 0, 0, 0.3);
@@ -71,16 +120,34 @@ const CellContainer = styled.div`
71
120
  `;
72
121
 
73
122
  const SpriteContainer = styled.div`
74
- margin-bottom: 0.5rem;
75
123
  display: flex;
76
124
  justify-content: center;
77
125
  align-items: center;
78
- width: 64px;
79
- height: 64px;
126
+ width: 32px;
127
+ height: 32px;
80
128
  position: relative;
81
129
  background: rgba(0, 0, 0, 0.3);
82
130
  border-radius: 4px;
83
131
  flex-shrink: 0;
132
+ overflow: hidden;
133
+
134
+ padding: 1rem;
135
+ margin-top: 1rem;
136
+
137
+ @media (min-width: 360px) {
138
+ width: 36px;
139
+ height: 36px;
140
+ }
141
+
142
+ @media (min-width: 480px) {
143
+ width: 42px;
144
+ height: 42px;
145
+ }
146
+
147
+ @media (min-width: ${UI_BREAKPOINT_MOBILE}) {
148
+ width: 48px;
149
+ height: 48px;
150
+ }
84
151
 
85
152
  .sprite-from-atlas-img {
86
153
  position: absolute;
@@ -91,13 +158,44 @@ const SpriteContainer = styled.div`
91
158
  }
92
159
  `;
93
160
 
161
+ const CellNameContainer = styled.div`
162
+ display: flex;
163
+ flex-direction: column;
164
+ align-items: center;
165
+ justify-content: center;
166
+ flex: 1;
167
+ width: 100%;
168
+ padding-top: 0.1rem;
169
+ `;
170
+
94
171
  const CellName = styled.h3`
95
- font-size: 0.7rem;
172
+ font-size: 0.5rem;
96
173
  color: #fef08a;
97
174
  margin: 0;
98
175
  text-align: center;
99
176
  font-family: 'Press Start 2P', cursive;
100
- line-height: 1.2;
177
+ line-height: 1.1;
101
178
  word-break: break-word;
102
179
  max-width: 100%;
180
+ overflow-wrap: break-word;
181
+ hyphens: auto;
182
+ width: 100%;
183
+ padding: 0 0.125rem;
184
+ box-sizing: border-box;
185
+
186
+ @media (min-width: 320px) {
187
+ padding: 0 0.25rem;
188
+ }
189
+
190
+ @media (min-width: 360px) {
191
+ font-size: 0.55rem;
192
+ }
193
+
194
+ @media (min-width: 480px) {
195
+ font-size: 0.6rem;
196
+ }
197
+
198
+ @media (min-width: ${UI_BREAKPOINT_MOBILE}) {
199
+ font-size: 0.65rem;
200
+ }
103
201
  `;
@@ -201,7 +201,7 @@ export const InformationCenterBestiarySection = ({
201
201
  selectedSubtype,
202
202
  selectedAttackType,
203
203
  ]}
204
- itemHeight="180px"
204
+ itemHeight={isMobile ? '150px' : '180px'}
205
205
  />
206
206
  {!isMobile && tooltipState && tooltipState.item && (
207
207
  <Portal>
@@ -153,7 +153,7 @@ export const InformationCenterItemsSection: React.FC<IItemsSectionProps> = ({
153
153
  dependencies={[selectedItemCategory, selectedTier]}
154
154
  tabId={tabId}
155
155
  layout="grid"
156
- itemHeight="180px"
156
+ itemHeight={isMobile ? '150px' : '180px'}
157
157
  />
158
158
  {!isMobile && tooltipState && tooltipState.item && (
159
159
  <Portal>
@@ -2,6 +2,7 @@ import {
2
2
  IVideoGuide,
3
3
  VideoGuideCategory,
4
4
  VideoGuideLanguage,
5
+ isMobileOrTablet,
5
6
  } from '@rpg-engine/shared';
6
7
  import React, { useMemo, useState } from 'react';
7
8
  import styled from 'styled-components';
@@ -26,6 +27,7 @@ export const InformationCenterTutorialsSection: React.FC<ITutorialsSectionProps>
26
27
  initialSearchQuery,
27
28
  tabId,
28
29
  }) => {
30
+ const isMobile = isMobileOrTablet();
29
31
  const [searchQuery, setSearchQuery] = useState(initialSearchQuery);
30
32
  const [selectedCategory, setSelectedCategory] = useState<string>('all');
31
33
  const [, setCurrentPage] = useState(1);
@@ -81,7 +83,7 @@ export const InformationCenterTutorialsSection: React.FC<ITutorialsSectionProps>
81
83
  </Ellipsis>
82
84
  </GuideTitle>
83
85
  <GuideDescription>
84
- <Ellipsis maxWidth="100%" maxLines={5}>
86
+ <Ellipsis maxWidth="100%" maxLines={isMobile ? 3 : 5}>
85
87
  {guide.description}
86
88
  </Ellipsis>
87
89
  </GuideDescription>
@@ -145,9 +147,9 @@ export const InformationCenterTutorialsSection: React.FC<ITutorialsSectionProps>
145
147
  dependencies={[selectedCategory]}
146
148
  tabId={tabId}
147
149
  layout="grid"
148
- gridColumns={GRID_COLUMNS}
150
+ gridColumns={isMobile ? 1 : GRID_COLUMNS}
149
151
  itemsPerPage={ITEMS_PER_PAGE}
150
- itemHeight="320px"
152
+ itemHeight={isMobile ? '280px' : '320px'}
151
153
  />
152
154
  );
153
155
  };
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import styled from 'styled-components';
3
+ import { UI_BREAKPOINT_MOBILE } from '../../constants/uiBreakpoints';
3
4
 
4
5
  interface TabItem {
5
6
  id: string;
@@ -73,14 +74,27 @@ const TabButton = styled.button<{
73
74
  hoverColor: string;
74
75
  }>`
75
76
  flex: 1;
76
- padding: 0.5rem 1rem;
77
- font-size: 0.875rem;
77
+ padding: 0.25rem 0.5rem;
78
+ font-size: 0.75rem;
78
79
  font-weight: 500;
79
80
  border-right: 1px solid ${props => props.borderColor};
80
81
  background-color: ${props =>
81
82
  props.active ? props.activeColor : 'transparent'};
82
83
  color: ${props =>
83
84
  props.active ? props.activeTextColor : props.inactiveColor};
85
+ white-space: nowrap;
86
+ overflow: hidden;
87
+ text-overflow: ellipsis;
88
+
89
+ @media (min-width: 480px) {
90
+ padding: 0.375rem 0.75rem;
91
+ font-size: 0.8125rem;
92
+ }
93
+
94
+ @media (min-width: ${UI_BREAKPOINT_MOBILE}) {
95
+ padding: 0.5rem 1rem;
96
+ font-size: 0.875rem;
97
+ }
84
98
 
85
99
  &:last-child {
86
100
  border-right: none;
@@ -92,4 +106,6 @@ const TabButton = styled.button<{
92
106
  }
93
107
  `;
94
108
 
95
- const ContentWrapper = styled.div``;
109
+ const ContentWrapper = styled.div`
110
+ width: 100%;
111
+ `;
@@ -1,4 +1,4 @@
1
- import { IItem } from '@rpg-engine/shared';
1
+ import { IItem, ItemQualityLevel } from '@rpg-engine/shared';
2
2
  import React from 'react';
3
3
  import styled from 'styled-components';
4
4
  import { uiColors } from '../../../constants/uiColors';
@@ -6,6 +6,7 @@ import { uiFonts } from '../../../constants/uiFonts';
6
6
  import { SpriteFromAtlas } from '../../shared/SpriteFromAtlas';
7
7
  import { ErrorBoundary } from '../Inventory/ErrorBoundary';
8
8
  import { EquipmentSlotSpriteByType } from '../Inventory/ItemSlot';
9
+ import { qualityColorHex } from '../Inventory/ItemSlotQuality';
9
10
  import { rarityColor } from '../Inventory/ItemSlotRarity';
10
11
 
11
12
  interface IItemInfoProps {
@@ -191,8 +192,24 @@ const Container = styled.div<{ item: IItem }>`
191
192
  padding: 0.5rem;
192
193
  font-size: ${uiFonts.size.small};
193
194
  border: 3px solid ${({ item }) => rarityColor(item) ?? uiColors.lightGray};
195
+ box-shadow: ${({ item }) => `0 0 5px 2px ${rarityColor(item)}`};
194
196
  height: max-content;
195
197
  width: 18rem;
198
+ position: relative;
199
+
200
+ ${({ item }) =>
201
+ item?.quality && item.quality !== ItemQualityLevel.Normal &&
202
+ `
203
+ &::before {
204
+ content: '★';
205
+ position: absolute;
206
+ top: 0.2rem;
207
+ left: 0.5rem;
208
+ font-size: 1.2rem;
209
+ color: ${qualityColorHex(item)};
210
+ text-shadow: 0 0 3px black;
211
+ }
212
+ `}
196
213
 
197
214
  @media (max-width: 640px) {
198
215
  width: 80vw;
@@ -6,7 +6,7 @@ import {
6
6
  ItemContainerType,
7
7
  ItemSlotType,
8
8
  ItemSubType,
9
- ItemType,
9
+ ItemType
10
10
  } from '@rpg-engine/shared';
11
11
 
12
12
  import { observer } from 'mobx-react-lite';
@@ -15,10 +15,11 @@ import Draggable, { DraggableEventHandler } from 'react-draggable';
15
15
  import styled from 'styled-components';
16
16
  import useTouchTarget from '../../../hooks/useTouchTarget';
17
17
  import { IPosition } from '../../../types/eventTypes';
18
- import { rarityColor } from './ItemSlotRarity';
19
- import { ItemSlotRenderer } from './ItemSlotRenderer';
20
18
  import { useItemSlotDetails } from './context/ItemSlotDetailsContext';
21
19
  import { useItemSlotDragging } from './context/ItemSlotDraggingContext';
20
+ import { qualityColorHex } from './ItemSlotQuality';
21
+ import { rarityColor } from './ItemSlotRarity';
22
+ import { ItemSlotRenderer } from './ItemSlotRenderer';
22
23
 
23
24
  export const EquipmentSlotSpriteByType: any = {
24
25
  Neck: 'accessories/corruption-necklace.png',
@@ -539,6 +540,17 @@ const Container = styled.div<ContainerTypes>`
539
540
  ${({ item }) => `0 0 4px 3px ${rarityColor(item)}`};
540
541
  }
541
542
 
543
+ .quality-star {
544
+ position: absolute;
545
+ top: 0.5rem;
546
+ left: 0.5rem;
547
+ font-size: 1.2rem;
548
+ z-index: 2;
549
+ text-shadow: 0 0 3px black;
550
+ pointer-events: none;
551
+ color: ${({ item }) => qualityColorHex(item)};
552
+ }
553
+
542
554
  &::before {
543
555
  content: '';
544
556
  position: absolute;
@@ -0,0 +1,18 @@
1
+ import { IItem, ItemQualityLevel } from '@rpg-engine/shared';
2
+
3
+ export const qualityColorHex = (item: IItem | null) => {
4
+ switch (item?.quality) {
5
+ case ItemQualityLevel.HighQuality:
6
+ return '#00bfff';
7
+ case ItemQualityLevel.Exceptional:
8
+ return '#ff8c00';
9
+ case ItemQualityLevel.Mastercrafted:
10
+ return '#ff00ff';
11
+ case ItemQualityLevel.Ancient:
12
+ return '#ffd700';
13
+ case ItemQualityLevel.Mythic:
14
+ return '#ff0080';
15
+ default:
16
+ return 'transparent';
17
+ }
18
+ };
@@ -1,8 +1,9 @@
1
1
  import {
2
+ getItemTextureKeyPath,
2
3
  IItem,
3
4
  ItemContainerType,
5
+ ItemQualityLevel,
4
6
  ItemSlotType,
5
- getItemTextureKeyPath,
6
7
  } from '@rpg-engine/shared';
7
8
  import React from 'react';
8
9
  import { v4 as uuidv4 } from 'uuid';
@@ -46,6 +47,9 @@ export const ItemSlotRenderer: React.FC<IProps> = ({
46
47
 
47
48
  return (
48
49
  <ErrorBoundary key={item._id}>
50
+ {item.quality && item.quality !== ItemQualityLevel.Normal && (
51
+ <div className="quality-star">★</div>
52
+ )}
49
53
  <SpriteFromAtlas
50
54
  atlasIMG={atlasIMG}
51
55
  atlasJSON={atlasJSON}
@@ -101,5 +105,5 @@ export const ItemSlotRenderer: React.FC<IProps> = ({
101
105
  }
102
106
  };
103
107
 
104
- return <>{onRenderSlot(item)}</>;
108
+ return onRenderSlot(item);
105
109
  };