]> git.localhorst.tv Git - alttp.git/commitdiff
snes settings
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Mon, 18 Mar 2024 15:01:12 +0000 (16:01 +0100)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Mon, 18 Mar 2024 15:01:12 +0000 (16:01 +0100)
resources/js/components/snes/SettingsDialog.js [new file with mode: 0644]
resources/js/components/snes/SettingsForm.js [new file with mode: 0644]
resources/js/components/twitch-bot/GuessingGameAutoTracking.js
resources/js/hooks/snes.js
resources/js/i18n/de.js
resources/js/i18n/en.js

diff --git a/resources/js/components/snes/SettingsDialog.js b/resources/js/components/snes/SettingsDialog.js
new file mode 100644 (file)
index 0000000..0f47d8f
--- /dev/null
@@ -0,0 +1,41 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Modal } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import SettingsForm from './SettingsForm';
+
+const SettingsDialog = ({
+       deviceList,
+       onHide,
+       onSubmit,
+       settings,
+       show,
+}) => {
+       const { t } = useTranslation();
+
+       return <Modal className="snes-settings-dialog" onHide={onHide} show={show}>
+               <Modal.Header closeButton>
+                       <Modal.Title>
+                               {t('snes.settings')}
+                       </Modal.Title>
+               </Modal.Header>
+               <SettingsForm
+                       deviceList={deviceList}
+                       onCancel={onHide}
+                       onSubmit={onSubmit}
+                       settings={settings}
+               />
+       </Modal>;
+};
+
+SettingsDialog.propTypes = {
+       deviceList: PropTypes.arrayOf(PropTypes.string),
+       onHide: PropTypes.func,
+       onSubmit: PropTypes.func,
+       settings: PropTypes.shape({
+       }),
+       show: PropTypes.bool,
+};
+
+export default SettingsDialog;
diff --git a/resources/js/components/snes/SettingsForm.js b/resources/js/components/snes/SettingsForm.js
new file mode 100644 (file)
index 0000000..7e05108
--- /dev/null
@@ -0,0 +1,161 @@
+import { withFormik } from 'formik';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button, Col, Form, Modal, Row } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import yup from '../../schema/yup';
+
+const SettingsForm = ({
+       deviceList,
+       errors,
+       handleBlur,
+       handleChange,
+       handleSubmit,
+       onCancel,
+       settings,
+       touched,
+       values,
+}) => {
+       const { t } = useTranslation();
+
+       return <Form noValidate onSubmit={handleSubmit}>
+               <Modal.Body>
+                       <Row>
+                               <Form.Group as={Col} sm={3} controlId="snes.proto">
+                                       <Form.Label>{t('snes.proto')}</Form.Label>
+                                       <Form.Select
+                                               isInvalid={!!(touched.proto && errors.proto)}
+                                               name="proto"
+                                               onBlur={handleBlur}
+                                               onChange={handleChange}
+                                               value={values.proto || 'ws'}
+                                       >
+                                               <option value="ws">ws://</option>
+                                               <option value="wss">wss://</option>
+                                       </Form.Select>
+                                       {touched.proto && errors.proto ?
+                                               <Form.Control.Feedback type="invalid">
+                                                       {t(errors.proto)}
+                                               </Form.Control.Feedback>
+                                       : null}
+                               </Form.Group>
+                               <Form.Group as={Col} sm={6} controlId="snes.host">
+                                       <Form.Label>{t('snes.host')}</Form.Label>
+                                       <Form.Control
+                                               isInvalid={!!(touched.host && errors.host)}
+                                               name="host"
+                                               onBlur={handleBlur}
+                                               onChange={handleChange}
+                                               type="text"
+                                               value={values.host || 'localhost'}
+                                       />
+                                       {touched.host && errors.host ?
+                                               <Form.Control.Feedback type="invalid">
+                                                       {t(errors.host)}
+                                               </Form.Control.Feedback>
+                                       : null}
+                               </Form.Group>
+                               <Form.Group as={Col} sm={3} controlId="snes.port">
+                                       <Form.Label>{t('snes.port')}</Form.Label>
+                                       <Form.Control
+                                               isInvalid={!!(touched.port && errors.port)}
+                                               min="1"
+                                               max="65665"
+                                               name="port"
+                                               onBlur={handleBlur}
+                                               onChange={handleChange}
+                                               type="number"
+                                               value={values.port || 8080}
+                                       />
+                                       {touched.port && errors.port ?
+                                               <Form.Control.Feedback type="invalid">
+                                                       {t(errors.port)}
+                                               </Form.Control.Feedback>
+                                       : null}
+                               </Form.Group>
+                               <Form.Group as={Col} sm={12} controlId="snes.device">
+                                       <Form.Label>{t('snes.device')}</Form.Label>
+                                       <Form.Select
+                                               isInvalid={!!(touched.device && errors.device)}
+                                               name="device"
+                                               onBlur={handleBlur}
+                                               onChange={handleChange}
+                                               value={values.device || ''}
+                                       >
+                                               <option value="">Auto</option>
+                                               {settings.device && !deviceList.includes(settings.device) ?
+                                                       <option value={settings.device}>{settings.device}</option>
+                                               : null}
+                                               {deviceList.map(device =>
+                                                       <option key={device} value={device}>{device}</option>
+                                               )}
+                                       </Form.Select>
+                                       {touched.device && errors.device ?
+                                               <Form.Control.Feedback type="invalid">
+                                                       {t(errors.device)}
+                                               </Form.Control.Feedback>
+                                       : null}
+                               </Form.Group>
+                       </Row>
+               </Modal.Body>
+               <Modal.Footer>
+                       {onCancel ?
+                               <Button onClick={onCancel} variant="secondary">
+                                       {t('button.cancel')}
+                               </Button>
+                       : null}
+                       <Button type="submit" variant="primary">
+                               {t('button.save')}
+                       </Button>
+               </Modal.Footer>
+       </Form>;
+};
+
+SettingsForm.propTypes = {
+       deviceList: PropTypes.arrayOf(PropTypes.string),
+       errors: PropTypes.shape({
+               device: PropTypes.string,
+               host: PropTypes.string,
+               port: PropTypes.string,
+               proto: PropTypes.string,
+       }),
+       handleBlur: PropTypes.func,
+       handleChange: PropTypes.func,
+       handleSubmit: PropTypes.func,
+       onCancel: PropTypes.func,
+       settings: PropTypes.shape({
+               device: PropTypes.string,
+               host: PropTypes.string,
+               port: PropTypes.number,
+               proto: PropTypes.string,
+       }),
+       touched: PropTypes.shape({
+               device: PropTypes.bool,
+               host: PropTypes.bool,
+               port: PropTypes.bool,
+               proto: PropTypes.bool,
+       }),
+       values: PropTypes.shape({
+               device: PropTypes.string,
+               host: PropTypes.string,
+               port: PropTypes.number,
+               proto: PropTypes.string,
+       }),
+};
+
+export default withFormik({
+       displayName: 'SettingsForm',
+       enableReinitialize: true,
+       handleSubmit: async (values, actions) => {
+               const { onSubmit } = actions.props;
+               onSubmit(values);
+       },
+       mapPropsToValues: ({ settings }) => settings,
+       validationSchema: yup.object().shape({
+               device: yup.string(),
+               host: yup.string(),
+               port: yup.number().min(1).max(65665),
+               proto: yup.string(),
+       }),
+})(SettingsForm);
index fae142cb9b1628068f1affb3368cc97071953aad..093e1214aea24cf39b1050550f0a1e5ecd96851a 100644 (file)
@@ -1,5 +1,6 @@
 import PropTypes from 'prop-types';
 import React from 'react';
+import { Button } from 'react-bootstrap';
 import { useTranslation } from 'react-i18next';
 
 import Icon from '../common/Icon';
@@ -90,6 +91,7 @@ const GuessingGameAutoTracking = ({ onSolve, onStart, onStop }) => {
        const {
                disable: disableSNES,
                enable: enableSNES,
+               openSettings,
                sock,
                status,
        } = useSNES();
@@ -329,6 +331,15 @@ const GuessingGameAutoTracking = ({ onSolve, onStart, onStop }) => {
                                title={t(`autoTracking.statusMsg.${statusMsg}`, { device: status.device  })}
                        />
                : null}
+               <Button
+                       className="me-2"
+                       onClick={openSettings}
+                       size="sm"
+                       title={t('snes.settings')}
+                       variant="outline-secondary"
+               >
+                       <Icon.SETTINGS title="" />
+               </Button>
                <ToggleSwitch
                        onChange={toggle}
                        title={t('autoTracking.heading')}
index 05e84b2e496d488452a6e53fcfbc32e553cae1f2..57bea3a333805a9530a5b367505cb1fdc1aa3268 100644 (file)
@@ -1,6 +1,7 @@
 import PropTypes from 'prop-types';
 import React from 'react';
 
+import SettingsDialog from '../components/snes/SettingsDialog';
 import SNESSocket from '../helpers/SNESSocket';
 
 const context = React.createContext({});
@@ -9,6 +10,7 @@ export const useSNES = () => React.useContext(context);
 
 export const SNESProvider = ({ children }) => {
        const [enabled, setEnabled] = React.useState(false);
+       const [showSettingsDialog, setShowSettingsDialog] = React.useState(false);
 
        const sock = React.useRef(null);
 
@@ -104,6 +106,23 @@ export const SNESProvider = ({ children }) => {
                });
        }, []);
 
+       const openSettings = React.useCallback(() => {
+               setShowSettingsDialog(true);
+       }, []);
+
+       const closeSettings = React.useCallback(() => {
+               setShowSettingsDialog(false);
+       }, []);
+
+       const saveSettings = React.useCallback((values) => {
+               setSettings(s => {
+                       const newSettings = { ...s, ...values };
+                       localStorage.setItem('snes.settings', JSON.stringify(newSettings));
+                       return newSettings;
+               });
+               setShowSettingsDialog(false);
+       }, []);
+
        React.useEffect(() => {
                const savedSettings = localStorage.getItem('snes.settings');
                if (savedSettings) {
@@ -112,11 +131,18 @@ export const SNESProvider = ({ children }) => {
        }, []);
 
        const value = React.useMemo(() => {
-               return { disable, enable, enabled, settings, sock, status };
-       }, [disable, enable, enabled, settings, sock, status]);
+               return { disable, enable, enabled, openSettings, settings, sock, status };
+       }, [disable, enable, enabled, openSettings, settings, sock, status]);
 
        return <context.Provider value={value}>
                {children}
+               <SettingsDialog
+                       deviceList={status.deviceList}
+                       onHide={closeSettings}
+                       onSubmit={saveSettings}
+                       settings={settings}
+                       show={showSettingsDialog}
+               />
        </context.Provider>;
 };
 
index 0f20e4e8f350e9f7523c64243d6aa38f9f58b415..b8442dcd3d63f4eaf2d1ef3561a28f538582c075 100644 (file)
@@ -447,6 +447,13 @@ export default {
                search: {
                        noResults: 'Keine Treffer',
                },
+               snes: {
+                       device: 'Bevorzugtes Gerät',
+                       host: 'Host',
+                       port: 'Port',
+                       proto: 'Protokoll',
+                       settings: 'SNES Einstellungen',
+               },
                techniques: {
                        description: 'Tutorials für The Legend of Zelda: A Link to the Past Randomizer',
                        heading: 'Techniken',
index 4852f1458578623f553681c82ad974c4f0343a49..c5efdf45b5ba552712291a5a8eecf4decc465cc7 100644 (file)
@@ -447,6 +447,13 @@ export default {
                search: {
                        noResults: 'No results',
                },
+               snes: {
+                       device: 'Preferred device',
+                       host: 'Host',
+                       port: 'Port',
+                       proto: 'Protocol',
+                       settings: 'SNES Settings',
+               },
                techniques: {
                        description: 'Tutorials for The Legend of Zelda: A Link to the Past Randomizer',
                        heading: 'Techniques',