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