]> git.localhorst.tv Git - alttp.git/blob - resources/js/hooks/snes.js
compact keysanity tracker
[alttp.git] / resources / js / hooks / snes.js
1 import PropTypes from 'prop-types';
2 import React from 'react';
3
4 import SettingsDialog from '../components/snes/SettingsDialog';
5 import SNESSocket from '../helpers/SNESSocket';
6
7 const context = React.createContext({});
8
9 export const useSNES = () => React.useContext(context);
10
11 export const SNESProvider = ({ children }) => {
12         const [enabled, setEnabled] = React.useState(false);
13         const [showSettingsDialog, setShowSettingsDialog] = React.useState(false);
14
15         const sock = React.useRef(null);
16
17         const [settings, setSettings] = React.useState({
18                 proto: 'ws',
19                 host: 'localhost',
20                 port: 23074,
21                 device: '',
22         });
23
24         const [status, setStatus] = React.useState({
25                 connected: false,
26                 device: '',
27                 deviceList: [],
28                 error: false,
29         });
30
31         React.useEffect(() => {
32                 if (sock.current) {
33                         sock.current.close();
34                         sock.current = null;
35                 }
36                 if (enabled) {
37                         const tryAttach = () => {
38                                 const { deviceList } = sock.current;
39                                 let device = '';
40                                 if (deviceList.includes(settings.device)) {
41                                         device = settings.device;
42                                 } else if (deviceList.length > 0) {
43                                         device = deviceList[0];
44                                 }
45                                 setStatus(s => ({ ...s, device, deviceList }));
46                                 if (device) {
47                                         sock.current.attachDevice(device);
48                                 }
49                         };
50                         sock.current = new SNESSocket(`${settings.proto}://${settings.host}:${settings.port}`);
51                         sock.current.onclose = () => {
52                                 setStatus({
53                                         connected: false,
54                                         device: '',
55                                         deviceList: [],
56                                         error: false,
57                                 });
58                         };
59                         sock.current.onerror = (e) => {
60                                 setStatus({
61                                         connected: false,
62                                         device: '',
63                                         deviceList: [],
64                                         error: e,
65                                 });
66                         };
67                         sock.current.onopen = () => {
68                                 setStatus({
69                                         connected: true,
70                                         device: '',
71                                         deviceList: [],
72                                         error: false,
73                                 });
74                                 sock.current.requestDeviceList(() => {
75                                         tryAttach();
76                                 });
77                         };
78                         const watchdog = setInterval(() => {
79                                 if (!sock.current.isOpen()) {
80                                         sock.current.open();
81                                         return;
82                                 }
83                                 if (!sock.current.device) {
84                                         sock.current.requestDeviceList(() => {
85                                                 tryAttach();
86                                         });
87                                 }
88                         }, 5000);
89                         return () => {
90                                 clearInterval(watchdog);
91                         };
92                 }
93         }, [enabled, settings]);
94
95         const enable = React.useCallback(() => {
96                 setEnabled(prevEnabled => {
97                         if (prevEnabled) return true;
98                         return true;
99                 });
100         }, []);
101
102         const disable = React.useCallback(() => {
103                 setEnabled(prevEnabled => {
104                         if (!prevEnabled) return false;
105                         return false;
106                 });
107         }, []);
108
109         const openSettings = React.useCallback(() => {
110                 setShowSettingsDialog(true);
111         }, []);
112
113         const closeSettings = React.useCallback(() => {
114                 setShowSettingsDialog(false);
115         }, []);
116
117         const saveSettings = React.useCallback((values) => {
118                 setSettings(s => {
119                         const newSettings = { ...s, ...values };
120                         localStorage.setItem('snes.settings', JSON.stringify(newSettings));
121                         return newSettings;
122                 });
123                 setShowSettingsDialog(false);
124         }, []);
125
126         React.useEffect(() => {
127                 const savedSettings = localStorage.getItem('snes.settings');
128                 if (savedSettings) {
129                         setSettings(JSON.parse(savedSettings));
130                 }
131         }, []);
132
133         const value = React.useMemo(() => {
134                 return { disable, enable, enabled, openSettings, settings, sock, status };
135         }, [disable, enable, enabled, openSettings, settings, sock, status]);
136
137         return <context.Provider value={value}>
138                 {children}
139                 <SettingsDialog
140                         deviceList={status.deviceList}
141                         onHide={closeSettings}
142                         onSubmit={saveSettings}
143                         settings={settings}
144                         show={showSettingsDialog}
145                 />
146         </context.Provider>;
147 };
148
149 SNESProvider.propTypes = {
150         children: PropTypes.node,
151 };