--- /dev/null
+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);
import PropTypes from 'prop-types';
import React from 'react';
+import SettingsDialog from '../components/snes/SettingsDialog';
import SNESSocket from '../helpers/SNESSocket';
const context = React.createContext({});
export const SNESProvider = ({ children }) => {
const [enabled, setEnabled] = React.useState(false);
+ const [showSettingsDialog, setShowSettingsDialog] = React.useState(false);
const sock = React.useRef(null);
});
}, []);
+ 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) {
}, []);
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>;
};