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