]> git.localhorst.tv Git - alttp.git/blob - resources/js/components/tracker/ToggleIcon.js
6dee8180f7be7bfdb3a80dc5819c26b68776f1ee
[alttp.git] / resources / js / components / tracker / ToggleIcon.js
1 import PropTypes from 'prop-types';
2 import React from 'react';
3
4 import ZeldaIcon from '../common/ZeldaIcon';
5 import {
6         addDungeonCheck,
7         decrement,
8         getDungeonBoss,
9         getDungeonRemainingItems,
10         getDungeonPrize,
11         getGTBoss,
12         hasDungeonBoss,
13         hasDungeonPrize,
14         highestActive,
15         increment,
16         removeDungeonCheck,
17         toggleBoolean,
18         toggleBossDefeated,
19 } from '../../helpers/tracker';
20 import { useTracker } from '../../hooks/tracker';
21
22 const ToggleIcon = ({ controller, className, icons, svg, transform }) => {
23         const { setManualState, state } = useTracker();
24         const activeController = controller || ToggleIcon.nullController;
25         const active = activeController.getActive(state, icons);
26         const defaultIcon = activeController.getDefault(state, icons);
27         const icon = active || defaultIcon || icons[0];
28         const classNames = ['toggle-icon'];
29         if (active) {
30                 classNames.push('active');
31         } else {
32                 classNames.push('inactive');
33         }
34         if (className) {
35                 classNames.push(className);
36         }
37         if (svg) {
38                 return <g
39                         className={classNames.join(' ')}
40                         data-icon={icon}
41                         onClick={(e) => {
42                                 activeController.handlePrimary(state, setManualState, icons);
43                                 e.preventDefault();
44                                 e.stopPropagation();
45                         }}
46                         onContextMenu={(e) => {
47                                 activeController.handleSecondary(state, setManualState, icons);
48                                 e.preventDefault();
49                                 e.stopPropagation();
50                         }}
51                         transform={transform}
52                 >
53                         <ZeldaIcon name={icon} svg />
54                 </g>;
55         }
56         return <span
57                 className={classNames.join(' ')}
58                 onClick={(e) => {
59                         activeController.handlePrimary(state, setManualState, icons);
60                         e.preventDefault();
61                         e.stopPropagation();
62                 }}
63                 onContextMenu={(e) => {
64                         activeController.handleSecondary(state, setManualState, icons);
65                         e.preventDefault();
66                         e.stopPropagation();
67                 }}
68         >
69                 <ZeldaIcon name={active || defaultIcon || icons[0]} />
70         </span>;
71 };
72
73 const doNothing = () => { };
74
75 const firstIcon = (state, icons) => icons[0];
76
77 const nextIcon = (state, setState, icons) => {
78         const highest = highestActive(state, icons);
79         const highestIndex = highest ? icons.indexOf(highest) : -1;
80         if (highestIndex + 1 < icons.length) {
81                 setState(toggleBoolean(icons[highestIndex + 1]));
82         } else {
83                 const changes = {};
84                 icons.forEach(icon => {
85                         changes[icon] = false;
86                 });
87                 setState(s => ({ ...s, ...changes }));
88         }
89 };
90
91 const previousIcon = (state, setState, icons) => {
92         const highest = highestActive(state, icons);
93         const highestIndex = highest ? icons.indexOf(highest) : -1;
94         if (highestIndex >= 0) {
95                 setState(toggleBoolean(icons[highestIndex]));
96         } else {
97                 const changes = {};
98                 icons.forEach(icon => {
99                         changes[icon] = true;
100                 });
101                 setState(s => ({ ...s, ...changes }));
102         }
103 };
104
105 const nextString = property => (state, setState, icons) => {
106         const current = state[property] || icons[0];
107         const currentIndex = icons.indexOf(current);
108         const nextIndex = (currentIndex + 1) % icons.length;
109         const next = icons[nextIndex];
110         setState(s => ({ ...s, [property]: next }));
111 };
112
113 const previousString = property => (state, setState, icons) => {
114         const current = state[property] || icons[0];
115         const currentIndex = icons.indexOf(current);
116         const previousIndex = (currentIndex + icons.length - 1) % icons.length;
117         const previous = icons[previousIndex];
118         setState(s => ({ ...s, [property]: previous }));
119 };
120
121 ToggleIcon.bottleController = ctrl => ({
122         getActive: (state, icons) => state[ctrl] ? icons[state[ctrl] - 1] : null,
123         getDefault: () => 'bottle',
124         handlePrimary: (state, setState, icons) => {
125                 if (state[ctrl] === 0) {
126                         // skip over mushroom
127                         setState(s => ({ ...s, [ctrl]: 2 }));
128                 } else {
129                         setState(increment(ctrl, icons.length));
130                 }
131         },
132         handleSecondary: (state, setState, icons) => {
133                 if (state[ctrl] === 2) {
134                         // skip over mushroom
135                         setState(s => ({ ...s, [ctrl]: 0 }));
136                 } else {
137                         setState(decrement(ctrl, icons.length));
138                 }
139         },
140 });
141
142 ToggleIcon.countController = max => ({
143         getActive: highestActive,
144         getDefault: firstIcon,
145         handlePrimary: (state, setState, icons) => {
146                 setState(increment(icons[0], max));
147         },
148         handleSecondary: (state, setState, icons) => {
149                 setState(decrement(icons[0], max));
150         },
151 });
152
153 ToggleIcon.dungeonBossController = (dungeon) => ({
154         getActive: (state) => hasDungeonBoss(state, dungeon) ? getDungeonBoss(state, dungeon) : null,
155         getDefault: (state) => getDungeonBoss(state, dungeon),
156         handlePrimary: dungeon.bosses.length > 1
157                 ? nextString(`${dungeon.id}-boss`)
158                 : (state, setState) => {
159                         setState(toggleBossDefeated(dungeon));
160                 },
161         handleSecondary: dungeon.bosses.length > 1 ?
162                 previousString(`${dungeon.id}-boss`)
163                 : (state, setState) => {
164                         setState(toggleBossDefeated(dungeon));
165                 },
166 });
167
168 ToggleIcon.dungeonCheckController = (dungeon) => ({
169         getActive: (state, icons) => getDungeonRemainingItems(state, dungeon) ? icons[1] : null,
170         getDefault: firstIcon,
171         handlePrimary: (state, setState) => {
172                 setState(addDungeonCheck(dungeon));
173         },
174         handleSecondary: (state, setState) => {
175                 setState(removeDungeonCheck(dungeon));
176         },
177 });
178
179 ToggleIcon.dungeonController = dungeon => ({
180         getActive: (state, icons) => state[`${dungeon.id}-${icons[0]}`] ? icons[0] : null,
181         getDefault: firstIcon,
182         handlePrimary: (state, setState, icons) => {
183                 setState(toggleBoolean(`${dungeon.id}-${icons[0]}`));
184         },
185         handleSecondary: (state, setState, icons) => {
186                 setState(toggleBoolean(`${dungeon.id}-${icons[0]}`));
187         },
188 });
189
190 ToggleIcon.dungeonCountController = (dungeon, max) => ({
191         getActive: (state, icons) => state[`${dungeon.id}-${icons[0]}`] ? icons[0] : null,
192         getDefault: firstIcon,
193         handlePrimary: (state, setState, icons) => {
194                 setState(increment(`${dungeon.id}-${icons[0]}`, max));
195         },
196         handleSecondary: (state, setState, icons) => {
197                 setState(decrement(`${dungeon.id}-${icons[0]}`, max));
198         },
199 });
200
201 ToggleIcon.dungeonPrizeController = (dungeon) => ({
202         getActive: (state) => hasDungeonPrize(state, dungeon) ? getDungeonPrize(state, dungeon) : null,
203         getDefault: (state) => getDungeonPrize(state, dungeon),
204         handlePrimary: nextString(`${dungeon.id}-prize`),
205         handleSecondary: previousString(`${dungeon.id}-prize`),
206 });
207
208 ToggleIcon.gtBossController = (which) => ({
209         getActive: (state) => getGTBoss(state, which),
210         getDefault: (state) => getGTBoss(state, which),
211         handlePrimary: nextString(`gt-${which}-boss`),
212         handleSecondary: previousString(`gt-${which}-boss`),
213 });
214
215 ToggleIcon.medallionController = {
216         getActive: highestActive,
217         getDefault: firstIcon,
218         handlePrimary: nextIcon,
219         handleSecondary: (state, setState, icons) => {
220                 const mm = state['mm-medallion'];
221                 const tr = state['tr-medallion'];
222                 const isMM = mm === icons[0];
223                 const isTR = tr === icons[0];
224                 console.log({ mm, isMM, tr, isTR });
225                 if (!isMM && !isTR) {
226                         // empty: set as MM if mire is unset, else set as TR if TR is unset
227                         if (!mm) {
228                                 setState(s => ({ ...s, 'mm-medallion': icons[0] }));
229                         } else if (!tr) {
230                                 setState(s => ({ ...s, 'tr-medallion': icons[0] }));
231                         }
232                 } else if (isMM && !isTR) {
233                         // MM: if TR is free, switch to TR, otherwise remove MM
234                         if (!tr) {
235                                 setState(s => ({ ...s, 'mm-medallion': null, 'tr-medallion': icons[0] }));
236                         } else {
237                                 setState(s => ({ ...s, 'mm-medallion': null }));
238                         }
239                 } else if (!isMM && isTR) {
240                         // TR: if MM is free, switch to both, otherwise remove TR
241                         if (!mm) {
242                                 setState(s => ({ ...s, 'mm-medallion': icons[0] }));
243                         } else {
244                                 setState(s => ({ ...s, 'tr-medallion': null }));
245                         }
246                 } else {
247                         // both: remove both
248                         setState(s => ({ ...s, 'mm-medallion': null, 'tr-medallion': null }));
249                 }
250         },
251 };
252
253 ToggleIcon.modulusController = ctrl => ({
254         getActive: (state, icons) => icons[(state[ctrl] || 0) % icons.length],
255         getDefault: firstIcon,
256         handlePrimary: (state, setState, icons) => {
257                 setState(increment(ctrl, icons.length));
258         },
259         handleSecondary: (state, setState, icons) => {
260                 setState(decrement(ctrl, icons.length));
261         },
262 });
263
264 ToggleIcon.nullController = {
265         getActive: () => null,
266         getDefault: firstIcon,
267         handlePrimary: doNothing,
268         handleSecondary: doNothing,
269 };
270
271 ToggleIcon.pinController = (pin, removePin) => ({
272         getActive: firstIcon,
273         getDefault: firstIcon,
274         handlePrimary: doNothing,
275         handleSecondary: () => removePin(pin),
276 });
277
278 ToggleIcon.simpleController = {
279         getActive: highestActive,
280         getDefault: firstIcon,
281         handlePrimary: nextIcon,
282         handleSecondary: previousIcon,
283 };
284
285 ToggleIcon.progressiveController = (master, min, max) => ({
286         getActive: (state, icons) => {
287                 const count = Math.max(min, Math.min(max, state[master] || 0));
288                 return count ? icons[count - 1] : null;
289         },
290         getDefault: firstIcon,
291         handlePrimary: (state, setState) => {
292                 setState(increment(master, max, min));
293         },
294         handleSecondary: (state, setState) => {
295                 setState(decrement(master, max, min));
296         },
297 });
298
299 ToggleIcon.propTypes = {
300         active: PropTypes.string,
301         className: PropTypes.string,
302         controller: PropTypes.shape({
303                 handlePrimary: PropTypes.func,
304                 handleSecondary: PropTypes.func,
305         }),
306         icons: PropTypes.arrayOf(PropTypes.string),
307         svg: PropTypes.bool,
308         transform: PropTypes.string,
309 };
310
311 export default ToggleIcon;