diff --git a/client/package.json b/client/package.json index e771174ae..7f6b0a7c8 100644 --- a/client/package.json +++ b/client/package.json @@ -3,9 +3,9 @@ "version": "0.1.0", "private": true, "dependencies": { - "@chakra-ui/react": "^1.4.1", - "@emotion/react": "^11", - "@emotion/styled": "^11", + "@chakra-ui/react": "^1.6.10", + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", "@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", diff --git a/client/src/app/api/ontimeApi.js b/client/src/app/api/ontimeApi.js index e23ab1e9d..d5fd4e568 100644 --- a/client/src/app/api/ontimeApi.js +++ b/client/src/app/api/ontimeApi.js @@ -3,6 +3,11 @@ import { ontimeURL } from './apiConstants'; export const ontimePlaceholderInfo = { networkInterfaces: [], + version: '', + serverPort: 4001, + oscInPort: '', + oscOutPort: '', + oscOutIP: '', }; export const getInfo = async () => { @@ -10,6 +15,11 @@ export const getInfo = async () => { return res.data; }; +export const postInfo = async (data) => { + const res = await axios.post(ontimeURL + '/info', data); + return res; +}; + export const downloadEvents = async () => { await axios({ url: ontimeURL + '/db', diff --git a/client/src/features/info/Info.jsx b/client/src/features/info/Info.jsx index 4f0f51e18..fc04fdcbe 100644 --- a/client/src/features/info/Info.jsx +++ b/client/src/features/info/Info.jsx @@ -1,10 +1,9 @@ -import { useEffect } from 'react'; +import { useState, useEffect } from 'react'; import { useSocket } from 'app/context/socketContext'; import style from './Info.module.css'; import InfoTitle from './InfoTitle'; import InfoLogger from './InfoLogger'; import InfoNif from './InfoNif'; -import { useState } from 'react'; export default function Info() { const socket = useSocket(); diff --git a/client/src/features/info/InfoNif.jsx b/client/src/features/info/InfoNif.jsx index 4935733ea..d86f9c138 100644 --- a/client/src/features/info/InfoNif.jsx +++ b/client/src/features/info/InfoNif.jsx @@ -11,8 +11,7 @@ export default function InfoNif() { placeholderData: ontimePlaceholderInfo, }); const [collapsed, setCollapsed] = useState(false); - - const baseURL = `http://__IP__:${4001}`; + const baseURL = 'http://__IP__:4001'; const handleLink = (url) => { if (window.process?.type === 'renderer') { diff --git a/client/src/features/modals/AliasesModal.jsx b/client/src/features/modals/AliasesModal.jsx new file mode 100644 index 000000000..3962b8768 --- /dev/null +++ b/client/src/features/modals/AliasesModal.jsx @@ -0,0 +1,152 @@ +import { IconButton } from '@chakra-ui/button'; +import { FiPlus, FiMinus } from 'react-icons/fi'; +import { ModalBody } from '@chakra-ui/modal'; +import { Input } from '@chakra-ui/react'; +import { fetchEvent } from 'app/api/eventApi'; +import { useState } from 'react'; +import { useFetch } from 'app/hooks/useFetch'; +import { EVENT_TABLE } from 'app/api/apiConstants'; +import style from './Modals.module.css'; + +export default function AliasesModal() { + const { data, status, isError } = useFetch(EVENT_TABLE, fetchEvent); + const [submitting, setSubmitting] = useState(false); + + const submitHandler = async (event) => { + event.preventDefault(); + // NOTHING HERE YET + }; + + // Hardcoded links for now + // it will need dynamic PORT assignment + const speakerLink = 'http://localhost:4001/speaker'; + const smLink = 'http://localhost:4001/sm'; + const publicLink = 'http://localhost:4001/public'; + const pipLink = 'http://localhost:4001/pip'; + + return ( + <> +
+ +

+ Configure easy to use URL Aliases +
+ !!! Feature is not yet implemented !!! +

+ + Default URLs + +
+

+ Presenter Screen
+ + {speakerLink} + +

+

+ Backstage / Stage Manager Screen
+ + {smLink} + +

+

+ Public / Foyer Screen
+ + {publicLink} + +

+

+ Picture in Picture Screen
+ + {pipLink} + +

+
+ + Manage custom aliases +
+ { + // Nothing here yet + }} + isDisabled={true} + /> + { + // Nothing here yet + }} + isDisabled={true} + /> + } + colorScheme='red' + disabled + /> +
+
+
+ { + // Nothing here yet + }} + isDisabled={true} + /> + { + // Nothing here yet + }} + isDisabled={true} + /> + } + colorScheme='blue' + disabled + /> +
+ + + + ); +} diff --git a/client/src/features/modals/AppSettingsModal.jsx b/client/src/features/modals/AppSettingsModal.jsx new file mode 100644 index 000000000..bdc8d1c35 --- /dev/null +++ b/client/src/features/modals/AppSettingsModal.jsx @@ -0,0 +1,185 @@ +import { ModalBody } from '@chakra-ui/modal'; +import { FormLabel, FormControl, Input, Button } from '@chakra-ui/react'; +import { getInfo, ontimePlaceholderInfo, postInfo } from 'app/api/ontimeApi'; +import { useEffect, useState } from 'react'; +import { useFetch } from 'app/hooks/useFetch'; +import { APP_TABLE } from 'app/api/apiConstants'; +import { showErrorToast } from 'common/helpers/toastManager'; +import style from './Modals.module.css'; + +export default function AppSettingsModal() { + const { data, status } = useFetch(APP_TABLE, getInfo); + const [formData, setFormData] = useState(ontimePlaceholderInfo); + const [changed, setChanged] = useState(false); + const [submitting, setSubmitting] = useState(false); + + useEffect(() => { + if (data == null) return; + + setFormData({ + oscInPort: data.oscInPort, + oscOutPort: data.oscOutPort, + oscOutIP: data.oscOutIP, + }); + }, [data]); + + const submitHandler = async (event) => { + event.preventDefault(); + + const f = formData; + let e = { status: false, message: '' }; + + // Validate fields + if (f.oscInPort < 1024 || f.oscInPort > 65535) { + // Port in incorrect range + e.status = true; + e.message += 'OSC IN Port in incorrect range (1024 - 65535)'; + } else if (f.oscOutPort < 1024 || f.oscOutPort > 65535) { + // Port in incorrect range + e.status = true; + e.message += 'OSC OUT Port in incorrect range (1024 - 65535)'; + } else if (f.oscInPort === f.oscOutPort) { + // Cant use the same port + e.status = true; + e.message += 'OSC IN and OUT Ports cant be the same'; + } + + // set fields with error + if (e.status) { + showErrorToast('Invalid Input', e.message); + return; + } + + // Post here + postInfo(formData); + + setChanged(false); + setSubmitting(false); + }; + + return ( + <> +
+ + {status === 'success' && ( + <> +

+ Options related to the application +
+ !!! Changes take effect after app restart !!! +

+ + + + Viewer Port + Port to access viewers + + + (Read Only Value) + + + + OSC In Port + +
+ App Control - Default 8888 +
+
+ { + setChanged(true); + setFormData({ + ...formData, + oscInPort: parseInt(event.target.value), + }); + }} + isDisabled={submitting} + style={{ width: '6em', textAlign: 'center' }} + /> +
+
+ + + OSC Out Target IP + +
+ App Feedback - Default 127.0.0.1 +
+
+ { + setChanged(true); + setFormData({ + ...formData, + oscOutIP: event.target.value, + }); + }} + isDisabled={submitting} + style={{ width: '12em', textAlign: 'right' }} + /> +
+ + + OSC Out Port + +
+ Default 9999 +
+
+ { + setChanged(true); + setFormData({ + ...formData, + oscOutPort: parseInt(event.target.value), + }); + }} + isDisabled={submitting} + style={{ width: '6em', textAlign: 'left' }} + /> +
+
+ + )} + +
+
+ + ); +} diff --git a/client/src/features/modals/SettingsModal.jsx b/client/src/features/modals/EventSettingsModal.jsx similarity index 57% rename from client/src/features/modals/SettingsModal.jsx rename to client/src/features/modals/EventSettingsModal.jsx index 7188ab4c3..58238f042 100644 --- a/client/src/features/modals/SettingsModal.jsx +++ b/client/src/features/modals/EventSettingsModal.jsx @@ -10,15 +10,18 @@ import { fetchEvent, postEvent } from 'app/api/eventApi'; import { useEffect, useState } from 'react'; import { useFetch } from 'app/hooks/useFetch'; import { EVENT_TABLE } from 'app/api/apiConstants'; +import style from './Modals.module.css'; export default function SettingsModal() { - const { data, status, isError } = useFetch(EVENT_TABLE, fetchEvent); + const { data, status } = useFetch(EVENT_TABLE, fetchEvent); const [formData, setFormData] = useState({ title: '', url: '', publicInfo: '', backstageInfo: '', + endMessage: '', }); + const [changed, setChanged] = useState(false); const [submitting, setSubmitting] = useState(false); useEffect(() => { @@ -29,6 +32,7 @@ export default function SettingsModal() { url: data.url, publicInfo: data.publicInfo, backstageInfo: data.backstageInfo, + endMessage: data.endMessage, }); }, [data]); @@ -36,41 +40,47 @@ export default function SettingsModal() { event.preventDefault(); setSubmitting(true); - await postEvent(formData).then(setSubmitting(false)); + await postEvent(formData); + + setChanged(false); + setSubmitting(false); }; return ( <>
- + {status === 'success' && ( <> +

+ Options related to the running event +
+ Affect rendered views +

+ - - Event Title - + Event Title - setFormData({ ...formData, title: event.target.value }) - } + onChange={(event) => { + setChanged(true); + setFormData({ ...formData, title: event.target.value }); + }} isDisabled={submitting} /> - + Event URL + + (shown as a QR code in some views) + - setFormData({ ...formData, url: event.target.value }) - } + onChange={(event) => { + setChanged(true); + setFormData({ ...formData, url: event.target.value }); + }} isDisabled={submitting} /> - - Public Info - + Public Info