@rpg-engine/long-bow 0.8.74 → 0.8.76
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/long-bow.cjs.development.js +45 -29
- 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 +45 -29
- package/dist/long-bow.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/components/DailyTasks/DailyTaskItem.tsx +32 -35
- package/src/components/DailyTasks/DailyTasks.tsx +40 -23
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
TaskType,
|
|
6
6
|
} from '@rpg-engine/shared';
|
|
7
7
|
import React from 'react';
|
|
8
|
-
import styled, {
|
|
8
|
+
import styled, { css, keyframes } from 'styled-components';
|
|
9
9
|
import { uiColors } from '../../constants/uiColors';
|
|
10
10
|
import { Button, ButtonTypes } from '../Button';
|
|
11
11
|
import { Ellipsis } from '../shared/Ellipsis';
|
|
@@ -36,6 +36,7 @@ export const DailyTaskItem: React.FC<IDailyTaskItemProps> = ({
|
|
|
36
36
|
iconAtlasJSON,
|
|
37
37
|
iconAtlasIMG,
|
|
38
38
|
isRewardClaimed,
|
|
39
|
+
isPinned,
|
|
39
40
|
}) => {
|
|
40
41
|
const isMobile = isMobileOrTablet();
|
|
41
42
|
|
|
@@ -43,19 +44,22 @@ export const DailyTaskItem: React.FC<IDailyTaskItemProps> = ({
|
|
|
43
44
|
const isInProgress = task.status === TaskStatus.InProgress;
|
|
44
45
|
const isNotStarted = task.status === TaskStatus.NotStarted;
|
|
45
46
|
const isClaimed = task.claimed || isRewardClaimed;
|
|
47
|
+
const isChallenge = task.difficulty?.toLowerCase() === 'challenge';
|
|
46
48
|
|
|
47
49
|
const handleClaimReward = () => {
|
|
48
50
|
onClaimReward(task.key, task.type);
|
|
49
51
|
};
|
|
50
52
|
|
|
51
53
|
return (
|
|
52
|
-
<TaskContainer
|
|
54
|
+
<TaskContainer
|
|
53
55
|
isMobile={isMobile}
|
|
54
56
|
isCompleted={isCompleted}
|
|
55
57
|
isInProgress={isInProgress}
|
|
56
58
|
isNotStarted={isNotStarted}
|
|
59
|
+
isPinned={isPinned}
|
|
60
|
+
isChallenge={isChallenge}
|
|
57
61
|
>
|
|
58
|
-
<TaskHeader>
|
|
62
|
+
<TaskHeader isMobile={isMobile}>
|
|
59
63
|
<TaskHeaderLeft>
|
|
60
64
|
{iconAtlasJSON && iconAtlasIMG && (
|
|
61
65
|
<IconWrapper>
|
|
@@ -99,7 +103,7 @@ export const DailyTaskItem: React.FC<IDailyTaskItemProps> = ({
|
|
|
99
103
|
</TaskHeaderRight>
|
|
100
104
|
</TaskHeader>
|
|
101
105
|
|
|
102
|
-
<TaskBody>
|
|
106
|
+
<TaskBody isMobile={isMobile}>
|
|
103
107
|
<TaskDescription isMobile={isMobile}>
|
|
104
108
|
<Ellipsis maxWidth="100%" maxLines={isMobile ? 2 : 1}>
|
|
105
109
|
{task.description}
|
|
@@ -122,7 +126,7 @@ export const DailyTaskItem: React.FC<IDailyTaskItemProps> = ({
|
|
|
122
126
|
</TaskBody>
|
|
123
127
|
|
|
124
128
|
{isCompleted && !isClaimed && (
|
|
125
|
-
<TaskFooter>
|
|
129
|
+
<TaskFooter isMobile={isMobile}>
|
|
126
130
|
<Button
|
|
127
131
|
buttonType={ButtonTypes.RPGUIButton}
|
|
128
132
|
onPointerDown={handleClaimReward}
|
|
@@ -147,24 +151,25 @@ const pulseAnimation = keyframes`
|
|
|
147
151
|
}
|
|
148
152
|
`;
|
|
149
153
|
|
|
150
|
-
const TaskContainer = styled.div<{
|
|
154
|
+
const TaskContainer = styled.div<{
|
|
151
155
|
isMobile: boolean;
|
|
152
156
|
isCompleted: boolean;
|
|
153
157
|
isInProgress: boolean;
|
|
154
158
|
isNotStarted: boolean;
|
|
155
159
|
isPinned?: boolean;
|
|
160
|
+
isChallenge?: boolean;
|
|
156
161
|
}>`
|
|
157
|
-
background:
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
uiColors.darkGray
|
|
162
|
+
background: ${props =>
|
|
163
|
+
props.isChallenge ? 'rgb(209, 39, 42)' :
|
|
164
|
+
props.isPinned ? 'rgba(255, 215, 0, 0.1)' :
|
|
165
|
+
'rgba(0, 0, 0, 0.6)'
|
|
162
166
|
} !important;
|
|
167
|
+
border: 1px solid ${uiColors.darkGray} !important;
|
|
163
168
|
border-radius: ${props => props.isMobile ? '4px' : '6px'};
|
|
164
|
-
padding: ${props => props.isMobile ? '
|
|
169
|
+
padding: ${props => props.isMobile ? '10px' : '14px'};
|
|
165
170
|
display: flex;
|
|
166
171
|
flex-direction: column;
|
|
167
|
-
gap: ${props => props.isMobile ? '
|
|
172
|
+
gap: ${props => props.isMobile ? '8px' : '12px'};
|
|
168
173
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
|
|
169
174
|
transition: all 0.2s ease;
|
|
170
175
|
font-style: normal;
|
|
@@ -173,11 +178,7 @@ const TaskContainer = styled.div<{
|
|
|
173
178
|
box-sizing: border-box;
|
|
174
179
|
|
|
175
180
|
/* Task status visual states */
|
|
176
|
-
opacity:
|
|
177
|
-
props.isCompleted ? 1 :
|
|
178
|
-
props.isInProgress ? 1 :
|
|
179
|
-
0.6
|
|
180
|
-
};
|
|
181
|
+
opacity: 1;
|
|
181
182
|
|
|
182
183
|
${props => props.isNotStarted && `
|
|
183
184
|
filter: grayscale(0.7);
|
|
@@ -192,26 +193,21 @@ const TaskContainer = styled.div<{
|
|
|
192
193
|
}
|
|
193
194
|
|
|
194
195
|
&:hover {
|
|
195
|
-
background: ${props =>
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
border-color: ${
|
|
199
|
-
|
|
200
|
-
props.isCompleted ? uiColors.green :
|
|
201
|
-
props.isInProgress ? uiColors.yellow :
|
|
202
|
-
uiColors.yellow
|
|
203
|
-
};
|
|
204
|
-
opacity: ${props => props.isNotStarted ? 0.8 : 1};
|
|
196
|
+
background: ${props =>
|
|
197
|
+
props.isPinned ? 'rgba(255, 215, 0, 0.25)' : 'rgba(0, 0, 0, 0.7)'
|
|
198
|
+
} !important;
|
|
199
|
+
border-color: ${uiColors.yellow};
|
|
200
|
+
opacity: 1;
|
|
205
201
|
}
|
|
206
202
|
`;
|
|
207
203
|
|
|
208
|
-
const TaskHeader = styled.div
|
|
204
|
+
const TaskHeader = styled.div<{ isMobile?: boolean }>`
|
|
209
205
|
display: flex;
|
|
210
206
|
width: 100%;
|
|
211
207
|
justify-content: space-between;
|
|
212
208
|
align-items: flex-start;
|
|
213
209
|
border-bottom: 1px solid ${uiColors.darkGray};
|
|
214
|
-
padding-bottom:
|
|
210
|
+
padding-bottom: ${props => props.isMobile ? '8px' : '10px'};
|
|
215
211
|
min-height: 36px;
|
|
216
212
|
`;
|
|
217
213
|
|
|
@@ -270,7 +266,7 @@ const TaskDifficulty = styled.span<{ difficulty: string }>`
|
|
|
270
266
|
`;
|
|
271
267
|
|
|
272
268
|
const StatusText = styled.span<{ color: string }>`
|
|
273
|
-
color: ${props => props.color};
|
|
269
|
+
color: ${props => props.color} !important;
|
|
274
270
|
font-size: 0.65rem;
|
|
275
271
|
font-weight: 500;
|
|
276
272
|
`;
|
|
@@ -286,23 +282,24 @@ const ClaimedBadge = styled.div<{ isMobile: boolean }>`
|
|
|
286
282
|
justify-content: center;
|
|
287
283
|
font-size: ${props => props.isMobile ? '10px' : '12px'};
|
|
288
284
|
font-weight: bold;
|
|
285
|
+
margin-left: 8px;
|
|
289
286
|
`;
|
|
290
287
|
|
|
291
|
-
const TaskBody = styled.div
|
|
288
|
+
const TaskBody = styled.div<{ isMobile?: boolean }>`
|
|
292
289
|
display: flex;
|
|
293
290
|
flex-direction: column;
|
|
294
291
|
gap: 8px;
|
|
295
|
-
padding: 6px 0;
|
|
292
|
+
padding: ${props => props.isMobile ? '4px 0' : '6px 0'};
|
|
296
293
|
`;
|
|
297
294
|
|
|
298
295
|
const RewardsSection = styled.div`
|
|
299
296
|
margin-top: 4px;
|
|
300
297
|
`;
|
|
301
298
|
|
|
302
|
-
const TaskFooter = styled.div
|
|
299
|
+
const TaskFooter = styled.div<{ isMobile?: boolean }>`
|
|
303
300
|
display: flex;
|
|
304
301
|
justify-content: center;
|
|
305
|
-
padding-top:
|
|
302
|
+
padding-top: ${props => props.isMobile ? '8px' : '10px'};
|
|
306
303
|
border-top: 1px solid ${uiColors.darkGray};
|
|
307
304
|
`;
|
|
308
305
|
|
|
@@ -2,18 +2,18 @@ import {
|
|
|
2
2
|
ICharacterDailyTask,
|
|
3
3
|
ICollectGlobalRewardRequest,
|
|
4
4
|
ICollectRewardRequest,
|
|
5
|
-
TaskType,
|
|
6
5
|
TaskStatus,
|
|
6
|
+
TaskType,
|
|
7
7
|
isMobileOrTablet,
|
|
8
8
|
} from '@rpg-engine/shared';
|
|
9
|
-
import React, {
|
|
9
|
+
import React, { useMemo, useState } from 'react';
|
|
10
|
+
import { FaClipboardList, FaThumbtack } from 'react-icons/fa';
|
|
10
11
|
import styled from 'styled-components';
|
|
11
|
-
import { FaThumbtack, FaClipboardList } from 'react-icons/fa';
|
|
12
12
|
import { uiColors } from '../../constants/uiColors';
|
|
13
13
|
import { useLocalStorage } from '../../hooks/useLocalStorage';
|
|
14
14
|
import { DraggableContainer } from '../DraggableContainer';
|
|
15
|
-
import { RPGUIContainerTypes } from '../RPGUI/RPGUIContainer';
|
|
16
15
|
import { Dropdown, IOptionsProps as DropdownOption } from '../Dropdown';
|
|
16
|
+
import { RPGUIContainerTypes } from '../RPGUI/RPGUIContainer';
|
|
17
17
|
import { SearchBar } from '../shared/SearchBar/SearchBar';
|
|
18
18
|
import { DailyTaskItem } from './DailyTaskItem';
|
|
19
19
|
import { GlobalDailyProgress } from './GlobalDailyProgress';
|
|
@@ -50,7 +50,7 @@ export const DailyTasks: React.FC<IDailyTasksProps> = ({
|
|
|
50
50
|
const [globalRewardClaimedLocal, setGlobalRewardClaimedLocal] = useState(
|
|
51
51
|
false
|
|
52
52
|
);
|
|
53
|
-
|
|
53
|
+
|
|
54
54
|
// Search and filter state
|
|
55
55
|
const [searchQuery, setSearchQuery] = useState('');
|
|
56
56
|
const [selectedStatus, setSelectedStatus] = useState('all');
|
|
@@ -72,15 +72,15 @@ export const DailyTasks: React.FC<IDailyTasksProps> = ({
|
|
|
72
72
|
const isTaskRewardClaimed = (taskKey: string): boolean => {
|
|
73
73
|
return claimedTasks.includes(taskKey);
|
|
74
74
|
};
|
|
75
|
-
|
|
75
|
+
|
|
76
76
|
const togglePinTask = (taskKey: string) => {
|
|
77
|
-
setPinnedTasks(prev =>
|
|
77
|
+
setPinnedTasks(prev =>
|
|
78
78
|
prev.includes(taskKey)
|
|
79
79
|
? prev.filter(key => key !== taskKey)
|
|
80
80
|
: [...prev, taskKey]
|
|
81
81
|
);
|
|
82
82
|
};
|
|
83
|
-
|
|
83
|
+
|
|
84
84
|
// Filter options using Store pattern
|
|
85
85
|
const statusOptions: DropdownOption[] = [
|
|
86
86
|
{ id: 0, value: 'all', option: 'All' },
|
|
@@ -89,32 +89,45 @@ export const DailyTasks: React.FC<IDailyTasksProps> = ({
|
|
|
89
89
|
{ id: 3, value: 'inprogress', option: 'In Progress' },
|
|
90
90
|
{ id: 4, value: 'notstarted', option: 'Not Started' },
|
|
91
91
|
];
|
|
92
|
-
|
|
92
|
+
|
|
93
93
|
// Filtered tasks using InformationCenter pattern
|
|
94
94
|
const filteredTasks = useMemo(() => {
|
|
95
95
|
let filtered = localTasks.filter(task => {
|
|
96
|
-
const matchesSearch =
|
|
96
|
+
const matchesSearch =
|
|
97
97
|
task.name?.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
98
98
|
task.description?.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
99
99
|
task.key.toLowerCase().includes(searchQuery.toLowerCase());
|
|
100
|
-
|
|
101
|
-
const matchesStatus =
|
|
100
|
+
|
|
101
|
+
const matchesStatus =
|
|
102
102
|
selectedStatus === 'all' ||
|
|
103
103
|
(selectedStatus === 'pinned' && pinnedTasks.includes(task.key)) ||
|
|
104
104
|
(selectedStatus === 'completed' && task.status === TaskStatus.Completed) ||
|
|
105
105
|
(selectedStatus === 'inprogress' && task.status === TaskStatus.InProgress) ||
|
|
106
106
|
(selectedStatus === 'notstarted' && task.status === TaskStatus.NotStarted);
|
|
107
|
-
|
|
107
|
+
|
|
108
108
|
return matchesSearch && matchesStatus;
|
|
109
109
|
});
|
|
110
|
-
|
|
111
|
-
// Sort
|
|
110
|
+
|
|
111
|
+
// Sort by relevance: pinned first, then by status (in progress, completed, not started)
|
|
112
112
|
return filtered.sort((a, b) => {
|
|
113
113
|
const aIsPinned = pinnedTasks.includes(a.key);
|
|
114
114
|
const bIsPinned = pinnedTasks.includes(b.key);
|
|
115
|
+
|
|
116
|
+
// Pinned tasks always come first
|
|
115
117
|
if (aIsPinned && !bIsPinned) return -1;
|
|
116
118
|
if (!aIsPinned && bIsPinned) return 1;
|
|
117
|
-
|
|
119
|
+
|
|
120
|
+
// If both are pinned or both are not pinned, sort by status priority
|
|
121
|
+
const statusPriority = {
|
|
122
|
+
[TaskStatus.InProgress]: 1,
|
|
123
|
+
[TaskStatus.Completed]: 2,
|
|
124
|
+
[TaskStatus.NotStarted]: 3,
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const aPriority = statusPriority[a.status] || 4;
|
|
128
|
+
const bPriority = statusPriority[b.status] || 4;
|
|
129
|
+
|
|
130
|
+
return aPriority - bPriority;
|
|
118
131
|
});
|
|
119
132
|
}, [localTasks, searchQuery, selectedStatus, pinnedTasks]);
|
|
120
133
|
|
|
@@ -139,7 +152,7 @@ export const DailyTasks: React.FC<IDailyTasksProps> = ({
|
|
|
139
152
|
}
|
|
140
153
|
/>
|
|
141
154
|
</GlobalProgressFixed>
|
|
142
|
-
|
|
155
|
+
|
|
143
156
|
<SearchHeader>
|
|
144
157
|
<SearchBarContainer>
|
|
145
158
|
<SearchBar
|
|
@@ -156,7 +169,7 @@ export const DailyTasks: React.FC<IDailyTasksProps> = ({
|
|
|
156
169
|
/>
|
|
157
170
|
</DropdownContainer>
|
|
158
171
|
</SearchHeader>
|
|
159
|
-
|
|
172
|
+
|
|
160
173
|
<TasksList className="tasks-container" isMobile={isMobile}>
|
|
161
174
|
{filteredTasks.length > 0 ? (
|
|
162
175
|
filteredTasks.map(task => (
|
|
@@ -249,7 +262,7 @@ const TasksList = styled.div<{ isMobile: boolean }>`
|
|
|
249
262
|
display: flex;
|
|
250
263
|
flex-direction: column;
|
|
251
264
|
gap: ${props => props.isMobile ? '8px' : '12px'};
|
|
252
|
-
padding: ${props => props.isMobile ? '8px' : '12px'};
|
|
265
|
+
padding: ${props => props.isMobile ? '8px 8px 40px 8px' : '12px 12px 40px 12px'};
|
|
253
266
|
overflow-y: auto;
|
|
254
267
|
flex: 1;
|
|
255
268
|
background-color: transparent;
|
|
@@ -262,14 +275,18 @@ const TasksList = styled.div<{ isMobile: boolean }>`
|
|
|
262
275
|
|
|
263
276
|
const GlobalProgressFixed = styled.div`
|
|
264
277
|
flex-shrink: 0;
|
|
265
|
-
padding:
|
|
278
|
+
padding: 12px;
|
|
266
279
|
background-color: transparent;
|
|
267
280
|
`;
|
|
268
281
|
|
|
269
282
|
const SearchHeader = styled.div`
|
|
270
283
|
display: flex;
|
|
271
|
-
gap: 12px;
|
|
272
|
-
|
|
284
|
+
gap: 12px;
|
|
285
|
+
justify-content: center;
|
|
286
|
+
align-items: center;
|
|
287
|
+
margin-left: 12px;
|
|
288
|
+
margin-right: 12px;
|
|
289
|
+
padding: 0.25rem;
|
|
273
290
|
border-bottom: 1px solid ${uiColors.darkGray};
|
|
274
291
|
background: rgba(0, 0, 0, 0.3);
|
|
275
292
|
`;
|
|
@@ -291,7 +308,7 @@ const TaskWrapper = styled.div`
|
|
|
291
308
|
const PinButton = styled.button<{ isPinned: boolean }>`
|
|
292
309
|
position: absolute;
|
|
293
310
|
top: 8px;
|
|
294
|
-
right:
|
|
311
|
+
right: 32px;
|
|
295
312
|
background: rgba(0, 0, 0, 0.7);
|
|
296
313
|
color: ${props => props.isPinned ? uiColors.yellow : uiColors.lightGray};
|
|
297
314
|
border: 1px solid ${props => props.isPinned ? uiColors.yellow : 'transparent'};
|