@rpg-engine/long-bow 0.8.45 → 0.8.47

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 (30) hide show
  1. package/dist/components/DailyTasks/DailyRewardsTooltip.d.ts +11 -0
  2. package/dist/components/DailyTasks/DailyTaskItem.d.ts +13 -0
  3. package/dist/components/DailyTasks/DailyTasks.d.ts +13 -0
  4. package/dist/components/DailyTasks/GlobalDailyProgress.d.ts +8 -0
  5. package/dist/components/DailyTasks/TaskProgress.d.ts +11 -0
  6. package/dist/components/DailyTasks/TaskProgressDetails.d.ts +7 -0
  7. package/dist/components/DailyTasks/utils/dailyTasks.utils.d.ts +8 -0
  8. package/dist/components/ReadOnlyCheckItem.d.ts +7 -0
  9. package/dist/constants/uiColors.d.ts +1 -0
  10. package/dist/long-bow.cjs.development.js +1 -0
  11. package/dist/long-bow.cjs.development.js.map +1 -1
  12. package/dist/long-bow.cjs.production.min.js.map +1 -1
  13. package/dist/long-bow.esm.js +1 -0
  14. package/dist/long-bow.esm.js.map +1 -1
  15. package/dist/mocks/dailyTasks.mocks.d.ts +2 -0
  16. package/dist/stories/Features/dailyTasks/DailyTasks.stories.d.ts +1 -0
  17. package/package.json +2 -2
  18. package/src/components/DailyTasks/DailyRewardsTooltip.tsx +158 -0
  19. package/src/components/DailyTasks/DailyTaskItem.tsx +161 -0
  20. package/src/components/DailyTasks/DailyTasks.tsx +151 -0
  21. package/src/components/DailyTasks/GlobalDailyProgress.tsx +132 -0
  22. package/src/components/DailyTasks/TaskProgress.tsx +92 -0
  23. package/src/components/DailyTasks/TaskProgressDetails.tsx +128 -0
  24. package/src/components/DailyTasks/utils/dailyTasks.utils.ts +96 -0
  25. package/src/components/ReadOnlyCheckItem.tsx +43 -0
  26. package/src/constants/uiColors.ts +1 -0
  27. package/src/mocks/dailyTasks.mocks.ts +212 -0
  28. package/src/stories/Features/dailyTasks/DailyTasks.stories.tsx +145 -0
  29. package/dist/stories/Character/character/CharacterSkinSelectionModal.stories.d.ts +0 -15
  30. package/src/stories/Character/character/CharacterSkinSelectionModal.stories.tsx +0 -188
@@ -0,0 +1,2 @@
1
+ import { ICharacterDailyTask } from "@rpg-engine/shared";
2
+ export declare const mockTasks: ICharacterDailyTask[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpg-engine/long-bow",
3
- "version": "0.8.45",
3
+ "version": "0.8.47",
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.9.104",
87
+ "@rpg-engine/shared": "^0.9.113",
88
88
  "dayjs": "^1.11.2",
89
89
  "font-awesome": "^4.7.0",
90
90
  "fs-extra": "^10.1.0",
@@ -0,0 +1,158 @@
1
+ import { ITaskReward, RewardType } from '@rpg-engine/shared';
2
+ import React from 'react';
3
+ import styled from 'styled-components';
4
+ import { uiColors } from '../../constants/uiColors';
5
+ import { Ellipsis } from '../shared/Ellipsis';
6
+ import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
7
+ import { formatTaskKey } from './utils/dailyTasks.utils';
8
+
9
+ interface IDailyRewardsTooltipProps {
10
+ rewards: ITaskReward[];
11
+ itemsAtlasJSON: Record<string, any>;
12
+ itemsAtlasIMG: string;
13
+ iconAtlasJSON?: Record<string, any>;
14
+ iconAtlasIMG?: string;
15
+ }
16
+
17
+ export const DailyRewardsTooltip: React.FC<IDailyRewardsTooltipProps> = ({
18
+ rewards,
19
+ itemsAtlasJSON,
20
+ itemsAtlasIMG,
21
+ }) => {
22
+ const [isExpanded, setIsExpanded] = React.useState(true);
23
+
24
+ const sortedRewards = React.useMemo(() => {
25
+ if (!rewards) return [];
26
+
27
+ const REWARD_PRIORITY = {
28
+ [RewardType.Item]: 1,
29
+ [RewardType.Gold]: 2,
30
+ [RewardType.Experience]: 3,
31
+ } as const;
32
+
33
+ const getPriority = (type: RewardType) =>
34
+ REWARD_PRIORITY[type] ?? Number.MAX_SAFE_INTEGER;
35
+
36
+ return [...rewards].sort((a, b) => {
37
+ return getPriority(a.type) - getPriority(b.type);
38
+ });
39
+ }, [rewards]);
40
+
41
+ const toggleExpand = () => {
42
+ setIsExpanded(!isExpanded);
43
+ };
44
+
45
+ return (
46
+ <TooltipContainer>
47
+ <CollapsibleHeader onClick={toggleExpand}>
48
+ <HeaderText>Rewards?</HeaderText>
49
+ <ExpandIcon>{isExpanded ? '▼' : '▶'}</ExpandIcon>
50
+ </CollapsibleHeader>
51
+
52
+ {isExpanded && (
53
+ <CollapsibleContent>
54
+ <RewardsList>
55
+ {sortedRewards.map((reward: ITaskReward, index: number) => (
56
+ <RewardItem key={index}>
57
+ <SpriteFromAtlas
58
+ atlasJSON={itemsAtlasJSON}
59
+ atlasIMG={itemsAtlasIMG}
60
+ spriteKey={
61
+ reward.type === RewardType.Gold
62
+ ? 'others/gold-coin-qty-6.png'
63
+ : reward.type === RewardType.Experience
64
+ ? 'others/royal-chalice.png'
65
+ : reward.texturePath || 'others/no-image.png'
66
+ }
67
+ width={24}
68
+ height={24}
69
+ imgScale={1.75}
70
+ />
71
+ <ItemContent>
72
+ <RewardLabel>
73
+ <Ellipsis maxWidth="100%" maxLines={2}>
74
+ {formatTaskKey(reward.key ?? '')}:
75
+ </Ellipsis>
76
+ </RewardLabel>
77
+ </ItemContent>
78
+ <RewardValue>x{reward.quantity}</RewardValue>
79
+ </RewardItem>
80
+ ))}
81
+ </RewardsList>
82
+ </CollapsibleContent>
83
+ )}
84
+ </TooltipContainer>
85
+ );
86
+ };
87
+
88
+ const TooltipContainer = styled.div`
89
+ position: relative;
90
+ display: flex;
91
+ flex-direction: column;
92
+ width: 100%;
93
+ `;
94
+
95
+ const RewardsList = styled.div`
96
+ display: flex;
97
+ flex-direction: column;
98
+ gap: 8px;
99
+ width: 100%;
100
+ `;
101
+
102
+ const CollapsibleHeader = styled.div`
103
+ display: flex;
104
+ justify-content: space-between;
105
+ align-items: center;
106
+ cursor: pointer;
107
+ border-bottom: 1px solid ${uiColors.darkGray};
108
+ width: 100%;
109
+ `;
110
+
111
+ const HeaderText = styled.span`
112
+ color: ${uiColors.yellow} !important;
113
+ `;
114
+
115
+ const ExpandIcon = styled.span`
116
+ color: ${uiColors.yellow};
117
+ font-size: 0.8rem;
118
+ margin-left: 8px;
119
+ `;
120
+
121
+ const CollapsibleContent = styled.div`
122
+ display: block;
123
+ padding-top: 8px;
124
+ width: 100%;
125
+ `;
126
+
127
+ const RewardItem = styled.div`
128
+ display: flex;
129
+ align-items: center;
130
+ gap: 12px;
131
+ width: 100%;
132
+ min-width: 200px;
133
+ `;
134
+
135
+ const ItemContent = styled.div`
136
+ display: flex;
137
+ flex-direction: column;
138
+ justify-content: center;
139
+ `;
140
+
141
+ const RewardLabel = styled.span`
142
+ color: ${uiColors.yellow};
143
+ font-size: 0.9rem;
144
+ line-height: 1.2;
145
+ display: inline-flex;
146
+ align-items: center;
147
+ `;
148
+
149
+ const RewardValue = styled.span`
150
+ color: ${uiColors.yellow};
151
+ font-size: 0.9rem;
152
+ margin-left: auto;
153
+ line-height: 1.2;
154
+ display: inline-flex;
155
+ align-items: center;
156
+ min-width: 50px;
157
+ text-align: right;
158
+ `;
@@ -0,0 +1,161 @@
1
+ import {
2
+ ICharacterDailyTask,
3
+ isMobileOrTablet,
4
+ TaskStatus,
5
+ TaskType,
6
+ } from '@rpg-engine/shared';
7
+ import React from 'react';
8
+ import styled from 'styled-components';
9
+ import { uiColors } from '../../constants/uiColors';
10
+ import { Button, ButtonTypes } from '../Button';
11
+ import { Ellipsis } from '../shared/Ellipsis';
12
+ import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
13
+ import { TaskProgress } from './TaskProgress';
14
+ import { formatTaskKey } from './utils/dailyTasks.utils';
15
+
16
+ interface IDailyTaskItemProps {
17
+ task: ICharacterDailyTask;
18
+ spriteKey: string;
19
+ onClaimReward: (taskKey: string, taskType: TaskType) => void;
20
+ itemsAtlasJSON: Record<string, any>;
21
+ itemsAtlasIMG: string;
22
+ iconAtlasJSON?: Record<string, any>;
23
+ iconAtlasIMG?: string;
24
+ }
25
+
26
+ export const DailyTaskItem: React.FC<IDailyTaskItemProps> = ({
27
+ task,
28
+ spriteKey,
29
+ onClaimReward,
30
+ itemsAtlasJSON,
31
+ itemsAtlasIMG,
32
+ iconAtlasJSON,
33
+ iconAtlasIMG,
34
+ }) => {
35
+ const isMobile = isMobileOrTablet();
36
+
37
+ const isCompleted = task.status === TaskStatus.Completed;
38
+ const isClaimed = task.claimed;
39
+
40
+ const handleClaimReward = () => {
41
+ onClaimReward(task.key, task.type);
42
+ };
43
+
44
+ return (
45
+ <TaskContainer>
46
+ <TaskHeader>
47
+ {iconAtlasJSON && iconAtlasIMG && (
48
+ <NonInteractiveWrapper>
49
+ <SpriteFromAtlas
50
+ atlasJSON={iconAtlasJSON}
51
+ atlasIMG={iconAtlasIMG}
52
+ spriteKey={spriteKey}
53
+ width={12}
54
+ height={12}
55
+ imgScale={2.75}
56
+ />
57
+ </NonInteractiveWrapper>
58
+ )}
59
+ <TaskContent>
60
+ <TaskTitle>
61
+ <Ellipsis maxWidth="100%" maxLines={isMobile ? 3 : 2}>
62
+ {task.name ?? formatTaskKey(task.key)}
63
+ </Ellipsis>
64
+ </TaskTitle>
65
+ <TaskDescription>
66
+ <Ellipsis maxWidth="100%" maxLines={isMobile ? 5 : 3}>
67
+ {task.description}
68
+ </Ellipsis>
69
+ </TaskDescription>
70
+ </TaskContent>
71
+ </TaskHeader>
72
+
73
+ <TaskProgress
74
+ task={task}
75
+ itemsAtlasJSON={itemsAtlasJSON}
76
+ itemsAtlasIMG={itemsAtlasIMG}
77
+ />
78
+
79
+ {isCompleted && !isClaimed && (
80
+ <CollectWrapper>
81
+ <Button
82
+ buttonType={ButtonTypes.RPGUIButton}
83
+ onPointerDown={handleClaimReward}
84
+ >
85
+ Collect Reward
86
+ </Button>
87
+ </CollectWrapper>
88
+ )}
89
+
90
+ {isClaimed && <ClaimedText>Reward Claimed</ClaimedText>}
91
+ </TaskContainer>
92
+ );
93
+ };
94
+
95
+ const TaskContainer = styled.div`
96
+ background: rgba(0, 0, 0, 0.5) !important;
97
+ border: 2px solid ${uiColors.darkGray} !important;
98
+ border-radius: 8px;
99
+ padding: 12px;
100
+ display: flex;
101
+ flex-direction: column;
102
+ gap: 8px;
103
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
104
+ `;
105
+
106
+ const TaskHeader = styled.div`
107
+ display: flex;
108
+ width: 100%;
109
+ justify-content: center;
110
+ border-bottom: 1.3px solid ${uiColors.darkGray};
111
+ `;
112
+
113
+ const NonInteractiveWrapper = styled.div`
114
+ pointer-events: none;
115
+ user-select: none;
116
+ margin-top: 5px;
117
+ `;
118
+
119
+ const TaskContent = styled.div`
120
+ display: flex;
121
+ flex-direction: column;
122
+ flex: 1;
123
+ `;
124
+
125
+ const TaskTitle = styled.h3`
126
+ && {
127
+ color: ${uiColors.yellow};
128
+ padding-left: 10px;
129
+ text-align: center;
130
+ text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.7);
131
+ flex: 1;
132
+ display: block;
133
+
134
+ span {
135
+ color: inherit;
136
+ }
137
+ }
138
+ `;
139
+
140
+ const TaskDescription = styled.h3`
141
+ font-size: 0.6rem !important;
142
+ text-decoration: none !important;
143
+ a
144
+ `;
145
+
146
+ const ClaimedText = styled.span`
147
+ color: ${uiColors.green};
148
+ font-size: 0.9rem;
149
+ text-align: center;
150
+ font-weight: bold;
151
+ `;
152
+
153
+ const CollectWrapper = styled.div`
154
+ && {
155
+ width: 100% !important;
156
+ display: flex !important;
157
+ justify-content: center !important;
158
+ align-items: center !important;
159
+ margin: 5px 0 !important;
160
+ }
161
+ `;
@@ -0,0 +1,151 @@
1
+ import {
2
+ ICharacterDailyTask,
3
+ ITaskIdentifier,
4
+ TaskStatus,
5
+ TaskType,
6
+ } from '@rpg-engine/shared';
7
+ import React from 'react';
8
+ import styled from 'styled-components';
9
+ import { uiColors } from '../../constants/uiColors';
10
+ import { useResponsiveSize } from '../CraftBook/hooks/useResponsiveSize';
11
+ import { DraggableContainer } from '../DraggableContainer';
12
+ import { RPGUIContainerTypes } from '../RPGUI/RPGUIContainer';
13
+ import { DailyTaskItem } from './DailyTaskItem';
14
+ import { GlobalDailyProgress } from './GlobalDailyProgress';
15
+ import { getTaskIcon } from './utils/dailyTasks.utils';
16
+
17
+ export interface IDailyTasksProps {
18
+ tasks: ICharacterDailyTask[];
19
+ onClaimReward: (task: ITaskIdentifier) => void;
20
+ onClose: () => void;
21
+ scale?: number;
22
+ itemsAtlasJSON: Record<string, any>;
23
+ itemsAtlasIMG: string;
24
+ iconAtlasJSON?: Record<string, any>;
25
+ iconAtlasIMG?: string;
26
+ }
27
+
28
+ export const DailyTasks: React.FC<IDailyTasksProps> = ({
29
+ tasks,
30
+ onClaimReward,
31
+ onClose,
32
+ scale = 1,
33
+ itemsAtlasJSON,
34
+ itemsAtlasIMG,
35
+ iconAtlasJSON,
36
+ iconAtlasIMG,
37
+ }): JSX.Element | null => {
38
+ const [localTasks, setLocalTasks] = React.useState(tasks);
39
+ const size = useResponsiveSize(scale);
40
+
41
+ const handleClaimReward = (taskKey: string, type: TaskType) => {
42
+ setLocalTasks(prevTasks =>
43
+ prevTasks.map(task =>
44
+ task.key === taskKey ? { ...task, claimed: true } : task
45
+ )
46
+ );
47
+ onClaimReward({ type, taskKey });
48
+ };
49
+
50
+ const handleClaimAllRewards = () => {
51
+ localTasks.forEach(task => {
52
+ if (task.status === TaskStatus.Completed && !task.claimed) {
53
+ const { type, key } = task;
54
+ onClaimReward({ type, taskKey: key });
55
+ }
56
+ });
57
+ };
58
+
59
+ if (!size) return null;
60
+
61
+ return (
62
+ <TasksContainer
63
+ type={RPGUIContainerTypes.Framed}
64
+ onCloseButton={onClose}
65
+ cancelDrag=".tasks-container"
66
+ scale={scale}
67
+ width={size.width}
68
+ height={size.height}
69
+ >
70
+ <TaskTitle>Daily Tasks</TaskTitle>
71
+ <Container>
72
+ <TasksList className="tasks-container">
73
+ <GlobalDailyProgress
74
+ tasks={localTasks}
75
+ onClaimAllRewards={handleClaimAllRewards}
76
+ />
77
+ {localTasks.map(task => (
78
+ <DailyTaskItem
79
+ key={task.key}
80
+ task={task}
81
+ spriteKey={getTaskIcon(task.type, task.difficulty)}
82
+ onClaimReward={handleClaimReward}
83
+ itemsAtlasJSON={itemsAtlasJSON}
84
+ itemsAtlasIMG={itemsAtlasIMG}
85
+ iconAtlasJSON={iconAtlasJSON}
86
+ iconAtlasIMG={iconAtlasIMG}
87
+ />
88
+ ))}
89
+ </TasksList>
90
+ </Container>
91
+ </TasksContainer>
92
+ );
93
+ };
94
+
95
+ const TasksContainer = styled(DraggableContainer)`
96
+ min-width: 450px;
97
+ max-width: 550px;
98
+ margin: 0 auto;
99
+
100
+ .rpgui-container-title {
101
+ width: 100%;
102
+ display: flex;
103
+ justify-content: center;
104
+ align-items: center;
105
+ text-align: center;
106
+ }
107
+
108
+ .rpgui-container {
109
+ padding: 0 !important;
110
+ overflow: hidden !important;
111
+ background-color: rgba(30, 30, 30, 0.9) !important;
112
+ }
113
+ `;
114
+
115
+ const Container = styled.div`
116
+ width: 100%;
117
+ max-width: 100%;
118
+ margin: 0 auto;
119
+ padding: 0.125rem;
120
+ overflow-y: auto;
121
+
122
+ box-sizing: border-box;
123
+
124
+ @media (min-width: 320px) {
125
+ padding: 0.25rem;
126
+ }
127
+
128
+ @media (min-width: 360px) {
129
+ padding: 0.5rem;
130
+ }
131
+
132
+ @media (min-width: 480px) {
133
+ padding: 0.75rem;
134
+ }
135
+ `;
136
+
137
+ const TasksList = styled.div`
138
+ display: flex;
139
+ flex-direction: column;
140
+ gap: 12px;
141
+ padding: 15px;
142
+ max-height: 70vh;
143
+ `;
144
+
145
+ const TaskTitle = styled.h2`
146
+ color: ${uiColors.yellow} !important;
147
+ text-align: center;
148
+ padding-right: 30px !important;
149
+ font-size: 1.4rem !important;
150
+ width: 100%;
151
+ `;
@@ -0,0 +1,132 @@
1
+ import {
2
+ ICharacterDailyTask,
3
+ ICollectGlobalRewardRequest,
4
+ TaskStatus,
5
+ } from '@rpg-engine/shared';
6
+ import React from 'react';
7
+ import styled from 'styled-components';
8
+ import { uiColors } from '../../constants/uiColors';
9
+ import { Button, ButtonTypes } from '../Button';
10
+
11
+ interface IGlobalTaskProgressProps {
12
+ tasks: ICharacterDailyTask[];
13
+ onClaimAllRewards: (tasks: ICollectGlobalRewardRequest) => void;
14
+ }
15
+
16
+ export const GlobalDailyProgress: React.FC<IGlobalTaskProgressProps> = ({
17
+ tasks,
18
+ onClaimAllRewards,
19
+ }) => {
20
+ const totalTasks = tasks.length;
21
+ const completedTasks = tasks.filter(
22
+ task => task.status === TaskStatus.Completed
23
+ ).length;
24
+
25
+ const claimedTasks = tasks.filter(task => task.claimed === true).length;
26
+
27
+ const allCompleted = completedTasks === totalTasks;
28
+ const allClaimed = claimedTasks === totalTasks;
29
+ const [isClaimed, setIsClaimed] = React.useState(false);
30
+
31
+ const handleClaimAll = (tasks: ICollectGlobalRewardRequest) => {
32
+ onClaimAllRewards(tasks);
33
+ setIsClaimed(true);
34
+ };
35
+
36
+ return (
37
+ <GlobalProgressContainer>
38
+ <HeaderContainer>
39
+ <GlobeIcon>🌍</GlobeIcon>
40
+ <ProgressText>
41
+ Global Tasks Completed: {completedTasks}/{totalTasks}
42
+ </ProgressText>
43
+ </HeaderContainer>
44
+ <ProgressBar>
45
+ <ProgressFill percentage={(completedTasks / totalTasks) * 100} />
46
+ </ProgressBar>
47
+ {totalTasks > 0 && allCompleted && allClaimed && (
48
+ <>
49
+ {isClaimed ? (
50
+ <ClaimedText>Global Rewards Claimed</ClaimedText>
51
+ ) : (
52
+ <CollectWrapper>
53
+ <Button
54
+ buttonType={ButtonTypes.RPGUIButton}
55
+ onPointerDown={handleClaimAll}
56
+ >
57
+ Collect Global Rewards
58
+ </Button>
59
+ </CollectWrapper>
60
+ )}
61
+ </>
62
+ )}
63
+ </GlobalProgressContainer>
64
+ );
65
+ };
66
+
67
+ const GlobalProgressContainer = styled.div`
68
+ background: rgba(0, 0, 0, 0.5) !important;
69
+ border: 2px solid ${uiColors.darkGray} !important;
70
+ border-radius: 8px;
71
+ padding: 12px;
72
+ display: flex;
73
+ flex-direction: column;
74
+ gap: 12px;
75
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
76
+ `;
77
+
78
+ const HeaderContainer = styled.div`
79
+ display: flex;
80
+ align-items: center;
81
+ gap: 8px;
82
+ margin-bottom: 8px;
83
+ `;
84
+
85
+ const GlobeIcon = styled.span`
86
+ font-size: 1.5rem !important;
87
+ line-height: 1;
88
+ color: ${uiColors.blue};
89
+ display: flex;
90
+ align-items: center;
91
+ justify-content: center;
92
+ `;
93
+
94
+ const ProgressText = styled.div`
95
+ color: ${uiColors.white};
96
+ text-align: center !important;
97
+ margin-top: 8px;
98
+ line-height: 1.2;
99
+ `;
100
+
101
+ const ProgressBar = styled.div`
102
+ width: 100%;
103
+ height: 8px;
104
+ background: ${uiColors.darkGray};
105
+ border-radius: 4px;
106
+ overflow: hidden;
107
+ `;
108
+
109
+ const ProgressFill = styled.div<{ percentage: number }>`
110
+ width: ${props => props.percentage}%;
111
+ height: 100%;
112
+ background: ${uiColors.green};
113
+ transition: width 0.3s ease;
114
+ `;
115
+
116
+ const ClaimedText = styled.span`
117
+ color: ${uiColors.green};
118
+ font-size: 0.9rem;
119
+ text-align: center;
120
+ margin-top: 8px;
121
+ font-weight: bold;
122
+ `;
123
+
124
+ const CollectWrapper = styled.div`
125
+ && {
126
+ width: 100% !important;
127
+ display: flex !important;
128
+ justify-content: center !important;
129
+ align-items: center !important;
130
+ margin: 5px 0 !important;
131
+ }
132
+ `;