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