@rpg-engine/long-bow 0.8.72 → 0.8.74

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.72",
3
+ "version": "0.8.74",
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.10",
87
+ "@rpg-engine/shared": "^0.10.14",
88
88
  "dayjs": "^1.11.2",
89
89
  "font-awesome": "^4.7.0",
90
90
  "fs-extra": "^10.1.0",
@@ -1,4 +1,4 @@
1
- import { ITaskReward, RewardType } from '@rpg-engine/shared';
1
+ import { ITaskReward, RewardType, isMobileOrTablet } from '@rpg-engine/shared';
2
2
  import React from 'react';
3
3
  import styled from 'styled-components';
4
4
  import { uiColors } from '../../constants/uiColors';
@@ -20,6 +20,7 @@ export const DailyRewardsTooltip: React.FC<IDailyRewardsTooltipProps> = ({
20
20
  itemsAtlasIMG,
21
21
  }) => {
22
22
  const [isExpanded, setIsExpanded] = React.useState(true);
23
+ const isMobile = isMobileOrTablet();
23
24
 
24
25
  const sortedRewards = React.useMemo(() => {
25
26
  if (!rewards) return [];
@@ -51,25 +52,27 @@ export const DailyRewardsTooltip: React.FC<IDailyRewardsTooltipProps> = ({
51
52
 
52
53
  {isExpanded && (
53
54
  <CollapsibleContent>
54
- <RewardsList>
55
+ <RewardsList isMobile={isMobile} rewardCount={sortedRewards.length}>
55
56
  {sortedRewards.map((reward: ITaskReward, index: number) => (
56
57
  <RewardItem key={index}>
57
- <SpriteFromAtlas
58
- atlasJSON={itemsAtlasJSON}
59
- atlasIMG={itemsAtlasIMG}
60
- spriteKey={reward.texturePath ?? 'check.png'}
61
- width={24}
62
- height={24}
63
- imgScale={1.75}
64
- />
65
- <ItemContent>
66
- <RewardLabel>
67
- <Ellipsis maxWidth="100%" maxLines={2}>
68
- {formatTaskKey(reward.key ?? reward.type)}:
58
+ <RewardIcon>
59
+ <SpriteFromAtlas
60
+ atlasJSON={itemsAtlasJSON}
61
+ atlasIMG={itemsAtlasIMG}
62
+ spriteKey={reward.texturePath ?? 'check.png'}
63
+ width={isMobile ? 16 : 18}
64
+ height={isMobile ? 16 : 18}
65
+ imgScale={isMobile ? 1.5 : 1.5}
66
+ />
67
+ </RewardIcon>
68
+ <RewardContent>
69
+ <RewardLabel isMobile={isMobile}>
70
+ <Ellipsis maxWidth="100%" maxLines={1}>
71
+ {formatTaskKey(reward.key ?? reward.type)}
69
72
  </Ellipsis>
70
73
  </RewardLabel>
71
- </ItemContent>
72
- <RewardValue>x{reward.quantity}</RewardValue>
74
+ <RewardValue isMobile={isMobile}>×{reward.quantity}</RewardValue>
75
+ </RewardContent>
73
76
  </RewardItem>
74
77
  ))}
75
78
  </RewardsList>
@@ -86,10 +89,13 @@ const TooltipContainer = styled.div`
86
89
  width: 100%;
87
90
  `;
88
91
 
89
- const RewardsList = styled.div`
90
- display: flex;
91
- flex-direction: column;
92
- gap: 8px;
92
+ const RewardsList = styled.div<{ isMobile: boolean; rewardCount: number }>`
93
+ display: grid;
94
+ grid-template-columns: ${props =>
95
+ props.rewardCount > 2 && !props.isMobile ? 'repeat(2, 1fr)' : '1fr'
96
+ };
97
+ gap: ${props => props.isMobile ? '6px' : '8px'};
98
+ column-gap: ${props => props.rewardCount > 2 && !props.isMobile ? '16px' : '8px'};
93
99
  width: 100%;
94
100
  `;
95
101
 
@@ -104,50 +110,75 @@ const CollapsibleHeader = styled.div`
104
110
 
105
111
  const HeaderText = styled.span`
106
112
  color: ${uiColors.yellow} !important;
113
+ font-size: 0.75rem;
114
+ font-weight: 500;
107
115
  `;
108
116
 
109
117
  const ExpandIcon = styled.span`
110
118
  color: ${uiColors.yellow};
111
- font-size: 0.8rem;
119
+ font-size: 0.7rem;
112
120
  margin-left: 8px;
113
121
  `;
114
122
 
115
123
  const CollapsibleContent = styled.div`
116
124
  display: block;
117
- padding-top: 8px;
125
+ padding-top: 6px;
118
126
  width: 100%;
119
127
  `;
120
128
 
121
129
  const RewardItem = styled.div`
122
130
  display: flex;
123
131
  align-items: center;
124
- gap: 12px;
132
+ gap: 4px;
125
133
  width: 100%;
126
- min-width: 200px;
134
+ padding: 2px;
135
+ min-height: 22px;
127
136
  `;
128
137
 
129
- const ItemContent = styled.div`
138
+ const RewardIcon = styled.div`
139
+ flex-shrink: 0;
140
+ width: 20px;
141
+ height: 20px;
130
142
  display: flex;
131
- flex-direction: column;
132
- justify-content: center;
143
+ align-items: flex-start;
144
+ justify-content: flex-start;
145
+ font-style: normal;
146
+ margin-right: 4px;
147
+
148
+ * {
149
+ font-style: normal !important;
150
+ }
151
+
152
+ position: relative;
153
+ left: -0.5rem;
154
+ top: -0.4rem;
133
155
  `;
134
156
 
135
- const RewardLabel = styled.span`
157
+ const RewardContent = styled.div`
158
+ display: flex;
159
+ justify-content: space-between;
160
+ align-items: center;
161
+ flex: 1;
162
+ min-width: 0;
163
+ `;
164
+
165
+ const RewardLabel = styled.span<{ isMobile: boolean }>`
136
166
  color: ${uiColors.yellow};
137
- font-size: 0.9rem;
167
+ font-size: ${props => props.isMobile ? '0.65rem' : '0.7rem'};
138
168
  line-height: 1.2;
139
- padding-top: 15px;
140
- display: inline-flex;
169
+ display: flex;
141
170
  align-items: center;
171
+ flex: 1;
172
+ min-width: 0;
142
173
  `;
143
174
 
144
- const RewardValue = styled.span`
145
- color: ${uiColors.yellow};
146
- font-size: 0.9rem;
147
- margin-left: auto;
175
+ const RewardValue = styled.span<{ isMobile: boolean }>`
176
+ color: ${uiColors.green};
177
+ font-size: ${props => props.isMobile ? '0.6rem' : '0.65rem'};
148
178
  line-height: 1.2;
149
- display: inline-flex;
179
+ display: flex;
150
180
  align-items: center;
151
- min-width: 50px;
152
- text-align: right;
181
+ font-weight: 600;
182
+ flex-shrink: 0;
183
+ margin-left: 8px;
153
184
  `;
@@ -5,13 +5,15 @@ import {
5
5
  TaskType,
6
6
  } from '@rpg-engine/shared';
7
7
  import React from 'react';
8
- import styled from 'styled-components';
8
+ import styled, { keyframes, css } from 'styled-components';
9
9
  import { uiColors } from '../../constants/uiColors';
10
10
  import { Button, ButtonTypes } from '../Button';
11
11
  import { Ellipsis } from '../shared/Ellipsis';
12
12
  import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
13
- import { TaskProgress } from './TaskProgress';
14
- import { formatTaskKey } from './utils/dailyTasks.utils';
13
+ import { DailyRewardsTooltip } from './DailyRewardsTooltip';
14
+ import { TaskProgressDetails } from './TaskProgressDetails';
15
+ import { formatDifficulty, formatTaskKey, getStatusInfo } from './utils/dailyTasks.utils';
16
+
15
17
 
16
18
  interface IDailyTaskItemProps {
17
19
  task: ICharacterDailyTask;
@@ -22,6 +24,7 @@ interface IDailyTaskItemProps {
22
24
  iconAtlasJSON?: Record<string, any>;
23
25
  iconAtlasIMG?: string;
24
26
  isRewardClaimed: boolean;
27
+ isPinned?: boolean;
25
28
  }
26
29
 
27
30
  export const DailyTaskItem: React.FC<IDailyTaskItemProps> = ({
@@ -37,6 +40,8 @@ export const DailyTaskItem: React.FC<IDailyTaskItemProps> = ({
37
40
  const isMobile = isMobileOrTablet();
38
41
 
39
42
  const isCompleted = task.status === TaskStatus.Completed;
43
+ const isInProgress = task.status === TaskStatus.InProgress;
44
+ const isNotStarted = task.status === TaskStatus.NotStarted;
40
45
  const isClaimed = task.claimed || isRewardClaimed;
41
46
 
42
47
  const handleClaimReward = () => {
@@ -44,94 +49,296 @@ export const DailyTaskItem: React.FC<IDailyTaskItemProps> = ({
44
49
  };
45
50
 
46
51
  return (
47
- <TaskContainer>
52
+ <TaskContainer
53
+ isMobile={isMobile}
54
+ isCompleted={isCompleted}
55
+ isInProgress={isInProgress}
56
+ isNotStarted={isNotStarted}
57
+ >
48
58
  <TaskHeader>
49
- {iconAtlasJSON && iconAtlasIMG && (
50
- <NonInteractiveWrapper>
51
- <SpriteFromAtlas
52
- atlasJSON={iconAtlasJSON}
53
- atlasIMG={iconAtlasIMG}
54
- spriteKey={spriteKey}
55
- width={12}
56
- height={12}
57
- imgScale={2.75}
58
- />
59
- </NonInteractiveWrapper>
60
- )}
61
- <TaskContent>
62
- <TaskTitle>
63
- <Ellipsis maxWidth="100%" maxLines={isMobile ? 3 : 2}>
64
- {task.name ?? formatTaskKey(task.key)}
65
- </Ellipsis>
66
- </TaskTitle>
67
- <TaskDescription>
68
- <Ellipsis maxWidth="100%" maxLines={isMobile ? 5 : 3}>
69
- {task.description}
70
- </Ellipsis>
71
- </TaskDescription>
72
- </TaskContent>
59
+ <TaskHeaderLeft>
60
+ {iconAtlasJSON && iconAtlasIMG && (
61
+ <IconWrapper>
62
+ <SpriteFromAtlas
63
+ atlasJSON={iconAtlasJSON}
64
+ atlasIMG={iconAtlasIMG}
65
+ spriteKey={spriteKey}
66
+ width={isMobile ? 10 : 12}
67
+ height={isMobile ? 10 : 12}
68
+ imgScale={2}
69
+
70
+ />
71
+ </IconWrapper>
72
+ )}
73
+ <TaskTitleSection>
74
+ <TaskTitle isMobile={isMobile}>
75
+ <Ellipsis maxWidth="100%" maxLines={1}>
76
+ {task.name ?? formatTaskKey(task.key)}
77
+ </Ellipsis>
78
+ </TaskTitle>
79
+ <TaskMeta isMobile={isMobile}>
80
+ <MetaItem>
81
+ <MetaLabel>Difficulty:</MetaLabel>
82
+ <TaskDifficulty difficulty={task.difficulty}>
83
+ {formatDifficulty(task.difficulty)}
84
+ </TaskDifficulty>
85
+ </MetaItem>
86
+ <MetaDivider>•</MetaDivider>
87
+ <MetaItem>
88
+ <MetaLabel>Status:</MetaLabel>
89
+ <StatusText color={getStatusInfo(task).color}>
90
+ {getStatusInfo(task).text}
91
+ </StatusText>
92
+ </MetaItem>
93
+ </TaskMeta>
94
+ </TaskTitleSection>
95
+ </TaskHeaderLeft>
96
+
97
+ <TaskHeaderRight>
98
+ {isClaimed && <ClaimedBadge isMobile={isMobile}>✓</ClaimedBadge>}
99
+ </TaskHeaderRight>
73
100
  </TaskHeader>
74
101
 
75
- <TaskProgress
76
- task={task}
77
- itemsAtlasJSON={itemsAtlasJSON}
78
- itemsAtlasIMG={itemsAtlasIMG}
79
- />
102
+ <TaskBody>
103
+ <TaskDescription isMobile={isMobile}>
104
+ <Ellipsis maxWidth="100%" maxLines={isMobile ? 2 : 1}>
105
+ {task.description}
106
+ </Ellipsis>
107
+ </TaskDescription>
108
+
109
+ <TaskProgressDetails task={task} />
110
+
111
+ {task.rewards && task.rewards.length > 0 && (
112
+ <RewardsSection>
113
+ <DailyRewardsTooltip
114
+ rewards={task.rewards}
115
+ itemsAtlasJSON={itemsAtlasJSON}
116
+ itemsAtlasIMG={itemsAtlasIMG}
117
+ iconAtlasJSON={iconAtlasJSON}
118
+ iconAtlasIMG={iconAtlasIMG}
119
+ />
120
+ </RewardsSection>
121
+ )}
122
+ </TaskBody>
80
123
 
81
124
  {isCompleted && !isClaimed && (
82
- <CollectWrapper>
125
+ <TaskFooter>
83
126
  <Button
84
127
  buttonType={ButtonTypes.RPGUIButton}
85
128
  onPointerDown={handleClaimReward}
86
129
  >
87
- Collect Reward
130
+ {isMobile ? 'Collect Reward' : 'Collect Reward'}
88
131
  </Button>
89
- </CollectWrapper>
132
+ </TaskFooter>
90
133
  )}
91
-
92
- {isClaimed && <ClaimedText>Reward Claimed</ClaimedText>}
93
134
  </TaskContainer>
94
135
  );
95
136
  };
96
137
 
97
- const TaskContainer = styled.div`
98
- background: rgba(0, 0, 0, 0.5) !important;
99
- border: 2px solid ${uiColors.darkGray} !important;
100
- border-radius: 8px;
101
- padding: 12px;
138
+ const pulseAnimation = keyframes`
139
+ 0% {
140
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
141
+ }
142
+ 50% {
143
+ box-shadow: 0 2px 8px rgba(255, 215, 0, 0.3);
144
+ }
145
+ 100% {
146
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
147
+ }
148
+ `;
149
+
150
+ const TaskContainer = styled.div<{
151
+ isMobile: boolean;
152
+ isCompleted: boolean;
153
+ isInProgress: boolean;
154
+ isNotStarted: boolean;
155
+ isPinned?: boolean;
156
+ }>`
157
+ background: rgba(0, 0, 0, 0.6) !important;
158
+ border: 1px solid ${props =>
159
+ props.isCompleted ? uiColors.green :
160
+ props.isInProgress ? uiColors.yellow :
161
+ uiColors.darkGray
162
+ } !important;
163
+ border-radius: ${props => props.isMobile ? '4px' : '6px'};
164
+ padding: ${props => props.isMobile ? '6px' : '8px'};
102
165
  display: flex;
103
166
  flex-direction: column;
104
- gap: 8px;
105
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
167
+ gap: ${props => props.isMobile ? '4px' : '6px'};
168
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
169
+ transition: all 0.2s ease;
170
+ font-style: normal;
171
+ width: 100%;
172
+ max-width: 100%;
173
+ box-sizing: border-box;
174
+
175
+ /* Task status visual states */
176
+ opacity: ${props =>
177
+ props.isCompleted ? 1 :
178
+ props.isInProgress ? 1 :
179
+ 0.6
180
+ };
181
+
182
+ ${props => props.isNotStarted && `
183
+ filter: grayscale(0.7);
184
+ `}
185
+
186
+ ${props => props.isInProgress && css`
187
+ animation: ${pulseAnimation} 2s ease-in-out infinite;
188
+ `}
189
+
190
+ * {
191
+ font-style: normal !important;
192
+ }
193
+
194
+ &:hover {
195
+ background: ${props =>
196
+ props.isPinned ? 'rgba(255, 215, 0, 0.25)' : 'rgba(0, 0, 0, 0.7)'
197
+ } !important;
198
+ border-color: ${props =>
199
+ props.isPinned ? uiColors.yellow :
200
+ props.isCompleted ? uiColors.green :
201
+ props.isInProgress ? uiColors.yellow :
202
+ uiColors.yellow
203
+ };
204
+ opacity: ${props => props.isNotStarted ? 0.8 : 1};
205
+ }
106
206
  `;
107
207
 
108
208
  const TaskHeader = styled.div`
109
209
  display: flex;
110
210
  width: 100%;
111
- justify-content: center;
112
- border-bottom: 1.3px solid ${uiColors.darkGray};
211
+ justify-content: space-between;
212
+ align-items: flex-start;
213
+ border-bottom: 1px solid ${uiColors.darkGray};
214
+ padding-bottom: 6px;
215
+ min-height: 36px;
113
216
  `;
114
217
 
115
- const NonInteractiveWrapper = styled.div`
116
- pointer-events: none;
117
- user-select: none;
118
- margin-top: 5px;
218
+ const TaskHeaderLeft = styled.div`
219
+ display: flex;
220
+ align-items: flex-start;
221
+ gap: 8px;
222
+ flex: 1;
223
+ `;
224
+
225
+ const TaskHeaderRight = styled.div`
226
+ display: flex;
227
+ align-items: center;
228
+ flex-shrink: 0;
119
229
  `;
120
230
 
121
- const TaskContent = styled.div`
231
+ const TaskTitleSection = styled.div`
122
232
  display: flex;
123
233
  flex-direction: column;
234
+ gap: 4px;
124
235
  flex: 1;
236
+ min-width: 0;
237
+ `;
238
+
239
+ const TaskMeta = styled.div<{ isMobile: boolean }>`
240
+ display: flex;
241
+ align-items: center;
242
+ gap: ${props => props.isMobile ? '6px' : '8px'};
243
+ flex-wrap: wrap;
244
+ `;
245
+
246
+ const MetaItem = styled.div`
247
+ display: flex;
248
+ align-items: center;
249
+ gap: 4px;
250
+ `;
251
+
252
+ const MetaLabel = styled.span`
253
+ color: ${uiColors.lightGray};
254
+ font-size: 0.65rem;
255
+ opacity: 0.8;
125
256
  `;
126
257
 
127
- const TaskTitle = styled.h3`
258
+ const MetaDivider = styled.span`
259
+ color: ${uiColors.darkGray};
260
+ font-size: 0.7rem;
261
+ `;
262
+
263
+ const TaskDifficulty = styled.span<{ difficulty: string }>`
264
+ color: ${props =>
265
+ props.difficulty.toLowerCase() === 'challenge'
266
+ ? uiColors.red
267
+ : uiColors.lightGray};
268
+ font-size: 0.65rem;
269
+ font-weight: 500;
270
+ `;
271
+
272
+ const StatusText = styled.span<{ color: string }>`
273
+ color: ${props => props.color};
274
+ font-size: 0.65rem;
275
+ font-weight: 500;
276
+ `;
277
+
278
+ const ClaimedBadge = styled.div<{ isMobile: boolean }>`
279
+ background: ${uiColors.green};
280
+ color: white;
281
+ border-radius: 50%;
282
+ width: ${props => props.isMobile ? '18px' : '20px'};
283
+ height: ${props => props.isMobile ? '18px' : '20px'};
284
+ display: flex;
285
+ align-items: center;
286
+ justify-content: center;
287
+ font-size: ${props => props.isMobile ? '10px' : '12px'};
288
+ font-weight: bold;
289
+ `;
290
+
291
+ const TaskBody = styled.div`
292
+ display: flex;
293
+ flex-direction: column;
294
+ gap: 8px;
295
+ padding: 6px 0;
296
+ `;
297
+
298
+ const RewardsSection = styled.div`
299
+ margin-top: 4px;
300
+ `;
301
+
302
+ const TaskFooter = styled.div`
303
+ display: flex;
304
+ justify-content: center;
305
+ padding-top: 6px;
306
+ border-top: 1px solid ${uiColors.darkGray};
307
+ `;
308
+
309
+ const IconWrapper = styled.div`
310
+ pointer-events: none;
311
+ user-select: none;
312
+ flex-shrink: 0;
313
+ width: 28px;
314
+ height: 28px;
315
+ display: flex;
316
+ align-items: flex-start;
317
+ justify-content: flex-start;
318
+ margin-top: 2px;
319
+ margin-left: 2px;
320
+ font-style: normal;
321
+
322
+
323
+
324
+ * {
325
+ font-style: normal !important;
326
+ }
327
+ `;
328
+
329
+
330
+ const TaskTitle = styled.h3<{ isMobile: boolean }>`
128
331
  && {
129
332
  color: ${uiColors.yellow};
130
- padding-left: 10px;
131
- text-align: center;
333
+ margin: 0;
334
+ padding: 0;
335
+ text-align: left;
336
+ font-size: ${props => props.isMobile ? '0.85rem' : '0.9rem'};
337
+ line-height: 1.2;
132
338
  text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.7);
133
- flex: 1;
134
- display: block;
339
+ font-weight: 600;
340
+ word-wrap: break-word;
341
+ overflow-wrap: break-word;
135
342
 
136
343
  span {
137
344
  color: inherit;
@@ -139,25 +346,14 @@ const TaskTitle = styled.h3`
139
346
  }
140
347
  `;
141
348
 
142
- const TaskDescription = styled.h3`
143
- font-size: 0.6rem !important;
144
- text-decoration: none !important;
145
- a
349
+ const TaskDescription = styled.div<{ isMobile: boolean }>`
350
+ color: ${uiColors.lightGray};
351
+ font-size: ${props => props.isMobile ? '0.7rem' : '0.75rem'};
352
+ line-height: 1.4;
353
+ margin: 0;
354
+ opacity: 0.9;
355
+ word-wrap: break-word;
356
+ overflow-wrap: break-word;
146
357
  `;
147
358
 
148
- const ClaimedText = styled.span`
149
- color: ${uiColors.green};
150
- font-size: 0.9rem;
151
- text-align: center;
152
- font-weight: bold;
153
- `;
154
359
 
155
- const CollectWrapper = styled.div`
156
- && {
157
- width: 100% !important;
158
- display: flex !important;
159
- justify-content: center !important;
160
- align-items: center !important;
161
- margin: 5px 0 !important;
162
- }
163
- `;