mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-21 15:09:10 +00:00
+2
-2
@@ -29,7 +29,7 @@ TODO.md
|
|||||||
# working stuff
|
# working stuff
|
||||||
_SS/
|
_SS/
|
||||||
.vscode/launch.json
|
.vscode/launch.json
|
||||||
db.json
|
|
||||||
.eslintrc.json
|
.eslintrc.json
|
||||||
db.json
|
|
||||||
db backup.json
|
db backup.json
|
||||||
|
server/src/data/db.json
|
||||||
|
server/src/models/db.json
|
||||||
|
|||||||
+6
-7
@@ -3,27 +3,26 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chakra-ui/react": "^1.4.1",
|
"@chakra-ui/react": "^1.6.10",
|
||||||
"@emotion/react": "^11",
|
"@emotion/react": "^11.5.0",
|
||||||
"@emotion/styled": "^11",
|
"@emotion/styled": "^11.3.0",
|
||||||
"@testing-library/jest-dom": "^5.11.4",
|
"@testing-library/jest-dom": "^5.11.4",
|
||||||
"@testing-library/react": "^11.1.0",
|
"@testing-library/react": "^11.1.0",
|
||||||
"@testing-library/user-event": "^12.1.10",
|
"@testing-library/user-event": "^12.1.10",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"date-fns": "^2.20.1",
|
|
||||||
"framer-motion": "^4.1.6",
|
"framer-motion": "^4.1.6",
|
||||||
"jotai": "^0.16.5",
|
"jotai": "^0.16.5",
|
||||||
"react": "^17.0.1",
|
"react": "^17.0.1",
|
||||||
"react-beautiful-dnd": "^13.1.0",
|
"react-beautiful-dnd": "^13.1.0",
|
||||||
"react-dom": "^17.0.1",
|
"react-dom": "^17.0.1",
|
||||||
"react-fast-compare": "^3.2.0",
|
"react-fast-compare": "^3.2.0",
|
||||||
"react-icons": "^4.2.0",
|
"react-icons": "^4.3.1",
|
||||||
"react-qr-code": "^1.1.1",
|
"react-qr-code": "^2.0.2",
|
||||||
"react-query": "^3.13.5",
|
"react-query": "^3.13.5",
|
||||||
"react-router": "^5.2.0",
|
"react-router": "^5.2.0",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
"react-scripts": "4.0.3",
|
"react-scripts": "4.0.3",
|
||||||
"socket.io-client": "^4.0.1",
|
"socket.io-client": "^4.3.2",
|
||||||
"tinykeys": "^1.1.2",
|
"tinykeys": "^1.1.2",
|
||||||
"typeface-open-sans": "^1.1.13",
|
"typeface-open-sans": "^1.1.13",
|
||||||
"web-vitals": "^1.0.1"
|
"web-vitals": "^1.0.1"
|
||||||
|
|||||||
@@ -5,14 +5,21 @@
|
|||||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#121212" />
|
<meta name="theme-color" content="#121212" />
|
||||||
<meta
|
<meta name="onsite" content="onsite - event timer manager" />
|
||||||
name="onsite"
|
|
||||||
content="onsite - event timer manager"
|
|
||||||
/>
|
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="%PUBLIC_URL%/favicon-32x32.png" />
|
<link
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="%PUBLIC_URL%/favicon-16x16.png" />
|
rel="icon"
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/site.webmanifest">
|
type="image/png"
|
||||||
|
sizes="32x32"
|
||||||
|
href="%PUBLIC_URL%/favicon-32x32.png"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
sizes="16x16"
|
||||||
|
href="%PUBLIC_URL%/favicon-16x16.png"
|
||||||
|
/>
|
||||||
|
<link rel="manifest" href="%PUBLIC_URL%/site.webmanifest" />
|
||||||
<!--
|
<!--
|
||||||
manifest.json provides metadata used when your web app is installed on a
|
manifest.json provides metadata used when your web app is installed on a
|
||||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||||
|
|||||||
@@ -14,3 +14,7 @@ html,
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
-webkit-app-region: drag;
|
-webkit-app-region: drag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
option {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|||||||
+19
-1
@@ -1,9 +1,10 @@
|
|||||||
import { lazy, Suspense } from 'react';
|
import { lazy, Suspense, useEffect } from 'react';
|
||||||
import { Route, Switch } from 'react-router-dom';
|
import { Route, Switch } from 'react-router-dom';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||||
import SocketProvider from 'app/context/socketContext';
|
import SocketProvider from 'app/context/socketContext';
|
||||||
import withSocket from 'features/viewers/ViewWrapper';
|
import withSocket from 'features/viewers/ViewWrapper';
|
||||||
|
import tinykeys from 'tinykeys';
|
||||||
|
|
||||||
const Editor = lazy(() => import('features/editors/Editor'));
|
const Editor = lazy(() => import('features/editors/Editor'));
|
||||||
const PresenterView = lazy(() =>
|
const PresenterView = lazy(() =>
|
||||||
@@ -36,6 +37,22 @@ const SLowerThird = withSocket(Lower);
|
|||||||
const SPip = withSocket(Pip);
|
const SPip = withSocket(Pip);
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
useEffect(() => {
|
||||||
|
let unsubscribe = tinykeys(window, {
|
||||||
|
'Alt+t': () => {
|
||||||
|
// if we are in electron
|
||||||
|
if (window.process?.type === undefined) return;
|
||||||
|
if (window.process.type === 'renderer') {
|
||||||
|
// ask to see debug
|
||||||
|
window.ipcRenderer.send('set-window', 'show-dev');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return () => {
|
||||||
|
unsubscribe();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SocketProvider>
|
<SocketProvider>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
@@ -45,6 +62,7 @@ function App() {
|
|||||||
<Route exact path='/' component={SSpeaker} />
|
<Route exact path='/' component={SSpeaker} />
|
||||||
<Route exact path='/sm' component={SStageManager} />
|
<Route exact path='/sm' component={SStageManager} />
|
||||||
<Route exact path='/speaker' component={SSpeaker} />
|
<Route exact path='/speaker' component={SSpeaker} />
|
||||||
|
<Route exact path='/stage' component={SSpeaker} />
|
||||||
<Route exact path='/speakersimple' component={SSpeakerSimple} />
|
<Route exact path='/speakersimple' component={SSpeakerSimple} />
|
||||||
<Route exact path='/editor' component={Editor} />
|
<Route exact path='/editor' component={Editor} />
|
||||||
<Route exact path='/public' component={SPublic} />
|
<Route exact path='/public' component={SPublic} />
|
||||||
|
|||||||
@@ -3,6 +3,11 @@ import { ontimeURL } from './apiConstants';
|
|||||||
|
|
||||||
export const ontimePlaceholderInfo = {
|
export const ontimePlaceholderInfo = {
|
||||||
networkInterfaces: [],
|
networkInterfaces: [],
|
||||||
|
version: '',
|
||||||
|
serverPort: 4001,
|
||||||
|
oscInPort: '',
|
||||||
|
oscOutPort: '',
|
||||||
|
oscOutIP: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getInfo = async () => {
|
export const getInfo = async () => {
|
||||||
@@ -10,6 +15,11 @@ export const getInfo = async () => {
|
|||||||
return res.data;
|
return res.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const postInfo = async (data) => {
|
||||||
|
const res = await axios.post(ontimeURL + '/info', data);
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
export const downloadEvents = async () => {
|
export const downloadEvents = async () => {
|
||||||
await axios({
|
await axios({
|
||||||
url: ontimeURL + '/db',
|
url: ontimeURL + '/db',
|
||||||
@@ -17,7 +27,6 @@ export const downloadEvents = async () => {
|
|||||||
responseType: 'blob', // important
|
responseType: 'blob', // important
|
||||||
}).then((response) => {
|
}).then((response) => {
|
||||||
let headerLine = response.headers['Content-Disposition'];
|
let headerLine = response.headers['Content-Disposition'];
|
||||||
console.log(response);
|
|
||||||
let filename = 'events.json';
|
let filename = 'events.json';
|
||||||
|
|
||||||
// try and get the filename from the response
|
// try and get the filename from the response
|
||||||
|
|||||||
@@ -7,9 +7,11 @@ export default function AddIconBtn(props) {
|
|||||||
<IconButton
|
<IconButton
|
||||||
size={props.size || 'xs'}
|
size={props.size || 'xs'}
|
||||||
icon={<FiPlus />}
|
icon={<FiPlus />}
|
||||||
colorScheme='blue'
|
_expanded={{ bg: 'orange.300', color: 'white' }}
|
||||||
onClick={clickhandler}
|
|
||||||
_focus={{ boxShadow: 'none' }}
|
_focus={{ boxShadow: 'none' }}
|
||||||
|
backgroundColor={'orange.200'}
|
||||||
|
color={'orange.500'}
|
||||||
|
onClick={clickhandler}
|
||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { IconButton } from '@chakra-ui/button';
|
import { IconButton } from '@chakra-ui/button';
|
||||||
import { FiCornerLeftUp } from 'react-icons/fi';
|
import { FiXOctagon } from 'react-icons/fi';
|
||||||
|
|
||||||
export default function UnloadIconBtn(props) {
|
export default function UnloadIconBtn(props) {
|
||||||
const { clickhandler, ...rest } = props;
|
const { clickhandler, ...rest } = props;
|
||||||
return (
|
return (
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={<FiCornerLeftUp />}
|
icon={<FiXOctagon />}
|
||||||
colorScheme='whiteAlpha'
|
colorScheme='whiteAlpha'
|
||||||
backgroundColor='#ffffff05'
|
backgroundColor='#ffffff05'
|
||||||
variant='outline'
|
variant='outline'
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ export default function Paginator(props) {
|
|||||||
const { events, selectedId } = props;
|
const { events, selectedId } = props;
|
||||||
const LIMIT_PER_PAGE = props.limit || 8;
|
const LIMIT_PER_PAGE = props.limit || 8;
|
||||||
const SCROLL_TIME = props.time * 1000 || 10000;
|
const SCROLL_TIME = props.time * 1000 || 10000;
|
||||||
const SCROLL_PAST = false;
|
|
||||||
const [numEvents, setNumEvents] = useState(0);
|
const [numEvents, setNumEvents] = useState(0);
|
||||||
const [page, setPage] = useState([]);
|
const [page, setPage] = useState([]);
|
||||||
const [pages, setPages] = useState(0);
|
const [pages, setPages] = useState(0);
|
||||||
|
|||||||
@@ -37,6 +37,10 @@
|
|||||||
|
|
||||||
.entryTitle {
|
.entryTitle {
|
||||||
align-self: center;
|
align-self: center;
|
||||||
|
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.entryPast,
|
.entryPast,
|
||||||
|
|||||||
@@ -30,22 +30,29 @@ const Playback = ({ playback, selectedId, playbackControl }) => {
|
|||||||
/>
|
/>
|
||||||
<RollIconBtn
|
<RollIconBtn
|
||||||
active={playback === 'roll'}
|
active={playback === 'roll'}
|
||||||
|
disabled={playback === 'roll'}
|
||||||
clickhandler={() => playbackControl('roll')}
|
clickhandler={() => playbackControl('roll')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Transport = ({ selectedId, playbackControl }) => {
|
const Transport = ({ playback, selectedId, playbackControl }) => {
|
||||||
return (
|
return (
|
||||||
<div className={style.playbackContainer}>
|
<div className={style.playbackContainer}>
|
||||||
<PrevIconBtn clickhandler={() => playbackControl('previous')} />
|
<PrevIconBtn
|
||||||
<NextIconBtn clickhandler={() => playbackControl('next')} />
|
clickhandler={() => playbackControl('previous')}
|
||||||
<UnloadIconBtn clickhandler={() => playbackControl('unload')} />
|
disabled={playback === 'roll'}
|
||||||
|
/>
|
||||||
|
<NextIconBtn
|
||||||
|
clickhandler={() => playbackControl('next')}
|
||||||
|
disabled={playback === 'roll'}
|
||||||
|
/>
|
||||||
<ReloadIconButton
|
<ReloadIconButton
|
||||||
clickhandler={() => playbackControl('reload')}
|
clickhandler={() => playbackControl('reload')}
|
||||||
disabled={!selectedId}
|
disabled={!selectedId}
|
||||||
/>
|
/>
|
||||||
|
<UnloadIconBtn clickhandler={() => playbackControl('unload')} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -60,6 +67,7 @@ const PlaybackButtons = (props) => {
|
|||||||
playbackControl={props.playbackControl}
|
playbackControl={props.playbackControl}
|
||||||
/>
|
/>
|
||||||
<Transport
|
<Transport
|
||||||
|
playback={playback}
|
||||||
selectedId={selectedId}
|
selectedId={selectedId}
|
||||||
playbackControl={props.playbackControl}
|
playbackControl={props.playbackControl}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export default function PlaybackControl() {
|
|||||||
running: null,
|
running: null,
|
||||||
startedAt: null,
|
startedAt: null,
|
||||||
expectedFinish: null,
|
expectedFinish: null,
|
||||||
|
secondary: null,
|
||||||
});
|
});
|
||||||
const [selectedId, setSelectedId] = useState(null);
|
const [selectedId, setSelectedId] = useState(null);
|
||||||
|
|
||||||
@@ -20,6 +21,7 @@ export default function PlaybackControl() {
|
|||||||
running: null,
|
running: null,
|
||||||
startedAt: null,
|
startedAt: null,
|
||||||
expectedFinish: null,
|
expectedFinish: null,
|
||||||
|
secondary: null,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -105,6 +105,11 @@
|
|||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rolltag {
|
||||||
|
color: #2b6cb0;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
.playbackContainer {
|
.playbackContainer {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-evenly;
|
justify-content: space-evenly;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import style from './PlaybackControl.module.css';
|
import style from './PlaybackControl.module.css';
|
||||||
import Countdown from 'common/components/countdown/Countdown';
|
import Countdown from 'common/components/countdown/Countdown';
|
||||||
import { stringFromMillis } from 'common/dateConfig';
|
import { stringFromMillis } from 'common/dateConfig';
|
||||||
|
import { Tooltip } from '@chakra-ui/react';
|
||||||
import { Button } from '@chakra-ui/button';
|
import { Button } from '@chakra-ui/button';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
|
||||||
@@ -9,7 +10,8 @@ const areEqual = (prevProps, nextProps) => {
|
|||||||
prevProps.timer.running === nextProps.timer.running &&
|
prevProps.timer.running === nextProps.timer.running &&
|
||||||
prevProps.timer.expectedFinish === nextProps.timer.expectedFinish &&
|
prevProps.timer.expectedFinish === nextProps.timer.expectedFinish &&
|
||||||
prevProps.timer.startedAt === nextProps.timer.startedAt &&
|
prevProps.timer.startedAt === nextProps.timer.startedAt &&
|
||||||
prevProps.playback === nextProps.playback
|
prevProps.playback === nextProps.playback &&
|
||||||
|
prevProps.timer.secondary === nextProps.timer.secondary
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -18,8 +20,8 @@ const PlaybackTimer = (props) => {
|
|||||||
const started = stringFromMillis(timer.startedAt, true);
|
const started = stringFromMillis(timer.startedAt, true);
|
||||||
const finish = stringFromMillis(timer.expectedFinish, true);
|
const finish = stringFromMillis(timer.expectedFinish, true);
|
||||||
const isNegative = timer.running < 0;
|
const isNegative = timer.running < 0;
|
||||||
const isDelayed = false;
|
|
||||||
const isRolling = playback === 'roll';
|
const isRolling = playback === 'roll';
|
||||||
|
const isWaiting = timer.secondary > 0 && timer.running == null;
|
||||||
|
|
||||||
const incrementProps = {
|
const incrementProps = {
|
||||||
size: 'sm',
|
size: 'sm',
|
||||||
@@ -33,23 +35,38 @@ const PlaybackTimer = (props) => {
|
|||||||
<>
|
<>
|
||||||
<div className={style.timeContainer}>
|
<div className={style.timeContainer}>
|
||||||
<div className={style.indicators}>
|
<div className={style.indicators}>
|
||||||
<div className={isRolling ? style.indRollActive : style.indRoll} />
|
<Tooltip label='Roll mode active'>
|
||||||
|
<div className={isRolling ? style.indRollActive : style.indRoll} />
|
||||||
|
</Tooltip>
|
||||||
<div
|
<div
|
||||||
className={isNegative ? style.indNegativeActive : style.indNegative}
|
className={isNegative ? style.indNegativeActive : style.indNegative}
|
||||||
/>
|
/>
|
||||||
<div className={style.indDelay} />
|
<div className={style.indDelay} />
|
||||||
</div>
|
</div>
|
||||||
<div className={style.timer}>
|
<div className={style.timer}>
|
||||||
<Countdown time={timer?.running} small negative={isNegative} />
|
<Countdown
|
||||||
</div>
|
time={isWaiting ? timer.secondary : timer.running}
|
||||||
<div className={style.start}>
|
small
|
||||||
<span className={style.tag}>Started at </span>
|
negative={isNegative}
|
||||||
<span className={style.time}>{started}</span>
|
/>
|
||||||
</div>
|
|
||||||
<div className={style.finish}>
|
|
||||||
<span className={style.tag}>Finish at </span>
|
|
||||||
<span className={style.time}>{finish}</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
{isWaiting ? (
|
||||||
|
<div className={style.start}>
|
||||||
|
<span className={style.rolltag}>Roll: Countdown to start</span>
|
||||||
|
<span className={style.time}>{''}</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div className={style.start}>
|
||||||
|
<span className={style.tag}>Started at </span>
|
||||||
|
<span className={style.time}>{started}</span>
|
||||||
|
</div>
|
||||||
|
<div className={style.finish}>
|
||||||
|
<span className={style.tag}>Finish at </span>
|
||||||
|
<span className={style.time}>{finish}</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<div className={style.btn}>
|
<div className={style.btn}>
|
||||||
<Button
|
<Button
|
||||||
{...incrementProps}
|
{...incrementProps}
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ h1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
padding-top: 1.5em;
|
padding-top: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cornerButtonContainer {
|
.cornerButtonContainer {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export default function ActionButtons(props) {
|
|||||||
onClick={() => actionHandler('event')}
|
onClick={() => actionHandler('event')}
|
||||||
isDisabled={!showAdd}
|
isDisabled={!showAdd}
|
||||||
>
|
>
|
||||||
Event next
|
Add Event after
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|
||||||
<MenuItem
|
<MenuItem
|
||||||
@@ -36,14 +36,14 @@ export default function ActionButtons(props) {
|
|||||||
onClick={() => actionHandler('delay')}
|
onClick={() => actionHandler('delay')}
|
||||||
isDisabled={!showDelay}
|
isDisabled={!showDelay}
|
||||||
>
|
>
|
||||||
Delay next
|
Add Delay after
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={<FiMinusCircle />}
|
icon={<FiMinusCircle />}
|
||||||
onClick={() => actionHandler('block')}
|
onClick={() => actionHandler('block')}
|
||||||
isDisabled={!showBlock}
|
isDisabled={!showBlock}
|
||||||
>
|
>
|
||||||
Block next
|
Add Block after
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</MenuList>
|
</MenuList>
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import Icon from '@chakra-ui/icon';
|
import Icon from '@chakra-ui/icon';
|
||||||
import { FiChevronDown, FiChevronUp, FiMoreVertical } from 'react-icons/fi';
|
import { FiChevronUp, FiMoreVertical } from 'react-icons/fi';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { Draggable } from 'react-beautiful-dnd';
|
import { Draggable } from 'react-beautiful-dnd';
|
||||||
import EventTimes from 'common/components/eventTimes/EventTimes';
|
import EventTimes from 'common/components/eventTimes/EventTimes';
|
||||||
@@ -86,13 +86,6 @@ const ExpandedBlock = (props) => {
|
|||||||
{`/ontime/goto ${eventIndex + 1} << OSC >> /ontime/gotoid ${oscid}`}
|
{`/ontime/goto ${eventIndex + 1} << OSC >> /ontime/gotoid ${oscid}`}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<Icon
|
|
||||||
className={style.more}
|
|
||||||
as={FiChevronUp}
|
|
||||||
marginTop='0.2em'
|
|
||||||
gridArea='more'
|
|
||||||
onClick={() => props.setCollapsed(true)}
|
|
||||||
/>
|
|
||||||
<div className={style.actionOverlay}>
|
<div className={style.actionOverlay}>
|
||||||
<PublicIconBtn actionHandler={actionHandler} active={data.isPublic} />
|
<PublicIconBtn actionHandler={actionHandler} active={data.isPublic} />
|
||||||
<ActionButtons
|
<ActionButtons
|
||||||
@@ -139,13 +132,6 @@ const CollapsedBlock = (props) => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Icon
|
|
||||||
className={style.more}
|
|
||||||
as={FiChevronDown}
|
|
||||||
marginTop='0.2em'
|
|
||||||
gridArea='more'
|
|
||||||
onClick={() => props.setCollapsed(false)}
|
|
||||||
/>
|
|
||||||
<div className={style.actionOverlay}>
|
<div className={style.actionOverlay}>
|
||||||
<PublicIconBtn actionHandler={actionHandler} active={data.isPublic} />
|
<PublicIconBtn actionHandler={actionHandler} active={data.isPublic} />
|
||||||
<ActionButtons
|
<ActionButtons
|
||||||
@@ -176,6 +162,7 @@ export default function EventBlock(props) {
|
|||||||
const delayValue = delay > 0 ? millisToMinutes(delay) : null;
|
const delayValue = delay > 0 ? millisToMinutes(delay) : null;
|
||||||
|
|
||||||
const handleCollapse = (isCollapsed) => {
|
const handleCollapse = (isCollapsed) => {
|
||||||
|
console.log('setting collapsed', isCollapsed);
|
||||||
setCollapsed({ [data.id]: isCollapsed });
|
setCollapsed({ [data.id]: isCollapsed });
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -187,6 +174,11 @@ export default function EventBlock(props) {
|
|||||||
{...provided.draggableProps}
|
{...provided.draggableProps}
|
||||||
ref={provided.innerRef}
|
ref={provided.innerRef}
|
||||||
>
|
>
|
||||||
|
<Icon
|
||||||
|
className={collapsed ? style.moreCollapsed : style.moreExpanded}
|
||||||
|
as={FiChevronUp}
|
||||||
|
onClick={() => handleCollapse(!collapsed)}
|
||||||
|
/>
|
||||||
{collapsed ? (
|
{collapsed ? (
|
||||||
<CollapsedBlock
|
<CollapsedBlock
|
||||||
provided={provided}
|
provided={provided}
|
||||||
@@ -195,7 +187,6 @@ export default function EventBlock(props) {
|
|||||||
delay={delay}
|
delay={delay}
|
||||||
delayValue={delayValue}
|
delayValue={delayValue}
|
||||||
actionHandler={actionHandler}
|
actionHandler={actionHandler}
|
||||||
setCollapsed={handleCollapse}
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<ExpandedBlock
|
<ExpandedBlock
|
||||||
@@ -206,7 +197,6 @@ export default function EventBlock(props) {
|
|||||||
delay={delay}
|
delay={delay}
|
||||||
delayValue={delayValue}
|
delayValue={delayValue}
|
||||||
actionHandler={actionHandler}
|
actionHandler={actionHandler}
|
||||||
setCollapsed={handleCollapse}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -144,6 +144,9 @@
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: block;
|
display: block;
|
||||||
|
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.oscLabel {
|
.oscLabel {
|
||||||
@@ -157,9 +160,24 @@
|
|||||||
|
|
||||||
/* ================ MORE ================ */
|
/* ================ MORE ================ */
|
||||||
|
|
||||||
.more {
|
.moreExpanded,
|
||||||
|
.moreCollapsed {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
color: #fff;
|
||||||
|
grid-area: more;
|
||||||
|
margin-top: 0.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.moreExpanded {
|
||||||
|
transform: scaleY(-1);
|
||||||
|
transition: transform 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.moreCollapsed {
|
||||||
|
transform: scaleY(1);
|
||||||
|
transition: transform 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
/* ============== ACTION ================ */
|
/* ============== ACTION ================ */
|
||||||
|
|
||||||
.actionOverlay {
|
.actionOverlay {
|
||||||
|
|||||||
@@ -127,7 +127,6 @@ export default function EventList(props) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('EventList: events in event list', events);
|
|
||||||
let cumulativeDelay = 0;
|
let cumulativeDelay = 0;
|
||||||
let eventIndex = -1;
|
let eventIndex = -1;
|
||||||
|
|
||||||
|
|||||||
@@ -35,12 +35,10 @@ export default function EventListWrapper() {
|
|||||||
|
|
||||||
// Snapshot the previous value
|
// Snapshot the previous value
|
||||||
let previousEvents = queryClient.getQueryData(EVENTS_TABLE);
|
let previousEvents = queryClient.getQueryData(EVENTS_TABLE);
|
||||||
console.log('debug', previousEvents);
|
|
||||||
if (previousEvents == null) {
|
if (previousEvents == null) {
|
||||||
refetch();
|
refetch();
|
||||||
previousEvents = queryClient.getQueryData(EVENTS_TABLE);
|
previousEvents = queryClient.getQueryData(EVENTS_TABLE);
|
||||||
}
|
}
|
||||||
console.log('debug 2', previousEvents);
|
|
||||||
|
|
||||||
// optimistically update object, temp ID until refetch
|
// optimistically update object, temp ID until refetch
|
||||||
let optimistic = [...previousEvents];
|
let optimistic = [...previousEvents];
|
||||||
@@ -241,52 +239,40 @@ export default function EventListWrapper() {
|
|||||||
switch (action) {
|
switch (action) {
|
||||||
case 'add':
|
case 'add':
|
||||||
try {
|
try {
|
||||||
let t = Date.now();
|
|
||||||
await addEvent.mutateAsync(payload);
|
await addEvent.mutateAsync(payload);
|
||||||
console.log('debug m add', Date.now() - t);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast('Error creating event', error.message);
|
showErrorToast('Error creating event', error.message);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'update':
|
case 'update':
|
||||||
try {
|
try {
|
||||||
let t = Date.now();
|
|
||||||
await updateEvent.mutateAsync(payload);
|
await updateEvent.mutateAsync(payload);
|
||||||
console.log('debug m update', Date.now() - t);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast('Error updating event', error.message);
|
showErrorToast('Error updating event', error.message);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'patch':
|
case 'patch':
|
||||||
try {
|
try {
|
||||||
let t = Date.now();
|
|
||||||
await patchEvent.mutateAsync(payload);
|
await patchEvent.mutateAsync(payload);
|
||||||
console.log('debug m patch', Date.now() - t);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast('Error updating event', error.message);
|
showErrorToast('Error updating event', error.message);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'delete':
|
case 'delete':
|
||||||
try {
|
try {
|
||||||
let t = Date.now();
|
|
||||||
await deleteEvent.mutateAsync(payload);
|
await deleteEvent.mutateAsync(payload);
|
||||||
console.log('debug m delete', Date.now() - t);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast('Error deleting event', error.message);
|
showErrorToast('Error deleting event', error.message);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'reorder':
|
case 'reorder':
|
||||||
try {
|
try {
|
||||||
let t = Date.now();
|
|
||||||
await reorderEvent.mutateAsync(payload);
|
await reorderEvent.mutateAsync(payload);
|
||||||
console.log('debug m reorder', Date.now() - t);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast('Error reordering event', error.message);
|
showErrorToast('Error reordering event', error.message);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'applyDelay':
|
case 'applyDelay':
|
||||||
let t = Date.now();
|
|
||||||
|
|
||||||
// if delay <= 0 delete delay and next block
|
// if delay <= 0 delete delay and next block
|
||||||
if (payload.duration <= 0) {
|
if (payload.duration <= 0) {
|
||||||
try {
|
try {
|
||||||
@@ -309,14 +295,12 @@ export default function EventListWrapper() {
|
|||||||
showErrorToast('Error applying delay', error.message);
|
showErrorToast('Error applying delay', error.message);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('debug applydelay', payload.id);
|
|
||||||
try {
|
try {
|
||||||
await applyDelay.mutateAsync(payload.id);
|
await applyDelay.mutateAsync(payload.id);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast('Error applying delay', error.message);
|
showErrorToast('Error applying delay', error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log('debug m apply', Date.now() - t);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'collapseall':
|
case 'collapseall':
|
||||||
@@ -330,9 +314,7 @@ export default function EventListWrapper() {
|
|||||||
|
|
||||||
case 'deleteall':
|
case 'deleteall':
|
||||||
try {
|
try {
|
||||||
let t = Date.now();
|
|
||||||
await deleteAllEvents.mutateAsync();
|
await deleteAllEvents.mutateAsync();
|
||||||
console.log('debug m deleteall', Date.now() - t);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast('Error deleting events', error.message);
|
showErrorToast('Error deleting events', error.message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useSocket } from 'app/context/socketContext';
|
import { useSocket } from 'app/context/socketContext';
|
||||||
import style from './Info.module.css';
|
import style from './Info.module.css';
|
||||||
import InfoTitle from './InfoTitle';
|
import InfoTitle from './InfoTitle';
|
||||||
import InfoLogger from './InfoLogger';
|
import InfoLogger from './InfoLogger';
|
||||||
import InfoNif from './InfoNif';
|
import InfoNif from './InfoNif';
|
||||||
import { useState } from 'react';
|
|
||||||
|
|
||||||
export default function Info() {
|
export default function Info() {
|
||||||
const socket = useSocket();
|
const socket = useSocket();
|
||||||
@@ -21,6 +20,8 @@ export default function Info() {
|
|||||||
const [selected, setSelected] = useState('No events');
|
const [selected, setSelected] = useState('No events');
|
||||||
const logData = [];
|
const logData = [];
|
||||||
|
|
||||||
|
console.log(`titles`, titles);
|
||||||
|
|
||||||
// handle incoming messages
|
// handle incoming messages
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (socket == null) return;
|
if (socket == null) return;
|
||||||
|
|||||||
@@ -24,6 +24,12 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.labelContainer {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
.label,
|
.label,
|
||||||
.emptyLabel {
|
.emptyLabel {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -47,6 +53,8 @@
|
|||||||
|
|
||||||
.notes {
|
.notes {
|
||||||
color: #d69e2e;
|
color: #d69e2e;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.if {
|
.if {
|
||||||
|
|||||||
@@ -11,12 +11,13 @@ export default function InfoNif() {
|
|||||||
placeholderData: ontimePlaceholderInfo,
|
placeholderData: ontimePlaceholderInfo,
|
||||||
});
|
});
|
||||||
const [collapsed, setCollapsed] = useState(false);
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
|
const baseURL = 'http://__IP__:4001';
|
||||||
const baseURL = `http://__IP__:${4001}`;
|
|
||||||
|
|
||||||
const handleLink = (url) => {
|
const handleLink = (url) => {
|
||||||
if (window.process.type === 'renderer') {
|
if (window.process?.type === 'renderer') {
|
||||||
window.ipcRenderer.send('send-to-link', url);
|
window.ipcRenderer.send('send-to-link', url);
|
||||||
|
} else {
|
||||||
|
window.open(url);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -26,19 +26,19 @@ export default function InfoTitle(props) {
|
|||||||
|
|
||||||
{!collapsed && (
|
{!collapsed && (
|
||||||
<>
|
<>
|
||||||
<div>
|
<div className={style.labelContainer}>
|
||||||
<span className={noTitl ? style.emptyLabel : style.label}>
|
<span className={noTitl ? style.emptyLabel : style.label}>
|
||||||
Title
|
Title
|
||||||
</span>
|
</span>
|
||||||
{data.title}
|
{data.title}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className={style.labelContainer}>
|
||||||
<span className={noPres ? style.emptyLabel : style.label}>
|
<span className={noPres ? style.emptyLabel : style.label}>
|
||||||
Presenter
|
Presenter
|
||||||
</span>
|
</span>
|
||||||
{data.presenter}
|
{data.presenter}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className={style.labelContainer}>
|
||||||
<span className={noSubt ? style.emptyLabel : style.label}>
|
<span className={noSubt ? style.emptyLabel : style.label}>
|
||||||
Subtitle
|
Subtitle
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -24,16 +24,16 @@ export default function MenuActionButtons(props) {
|
|||||||
/>
|
/>
|
||||||
<MenuList style={menuStyle}>
|
<MenuList style={menuStyle}>
|
||||||
<MenuItem icon={<FiPlus />} onClick={() => actionHandler('event')}>
|
<MenuItem icon={<FiPlus />} onClick={() => actionHandler('event')}>
|
||||||
Event first
|
Add Event first
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem icon={<FiClock />} onClick={() => actionHandler('delay')}>
|
<MenuItem icon={<FiClock />} onClick={() => actionHandler('delay')}>
|
||||||
Delay first
|
Add Delay first
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={<FiMinusCircle />}
|
icon={<FiMinusCircle />}
|
||||||
onClick={() => actionHandler('block')}
|
onClick={() => actionHandler('block')}
|
||||||
>
|
>
|
||||||
Block first
|
Add Block first
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<Divider />
|
<Divider />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
|
|||||||
@@ -45,7 +45,12 @@ export default function MenuBar(props) {
|
|||||||
|
|
||||||
const handleIPC = (action) => {
|
const handleIPC = (action) => {
|
||||||
// Stop crashes when testing locally
|
// Stop crashes when testing locally
|
||||||
if (window.process?.type === undefined) return;
|
if (window.process?.type === undefined) {
|
||||||
|
if (action === 'help') {
|
||||||
|
window.open('https://cpvalente.gitbook.io/ontime/');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (window.process.type === 'renderer') {
|
if (window.process.type === 'renderer') {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
<>
|
||||||
|
<form onSubmit={submitHandler}>
|
||||||
|
<ModalBody className={style.modalBody}>
|
||||||
|
<p className={style.notes}>
|
||||||
|
Configure easy to use URL Aliases
|
||||||
|
<br />
|
||||||
|
!!! Feature is not yet implemented !!!
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<span> Default URLs </span>
|
||||||
|
|
||||||
|
<div className={style.highNotes}>
|
||||||
|
<p className={style.flexNote}>
|
||||||
|
Presenter Screen <br />
|
||||||
|
<a
|
||||||
|
href={speakerLink}
|
||||||
|
target='_blank'
|
||||||
|
rel='noreferrer'
|
||||||
|
className={style.label}
|
||||||
|
>
|
||||||
|
{speakerLink}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p className={style.flexNote}>
|
||||||
|
Backstage / Stage Manager Screen <br />
|
||||||
|
<a
|
||||||
|
href={smLink}
|
||||||
|
target='_blank'
|
||||||
|
rel='noreferrer'
|
||||||
|
className={style.label}
|
||||||
|
>
|
||||||
|
{smLink}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p className={style.flexNote}>
|
||||||
|
Public / Foyer Screen <br />
|
||||||
|
<a
|
||||||
|
href={publicLink}
|
||||||
|
target='_blank'
|
||||||
|
rel='noreferrer'
|
||||||
|
className={style.label}
|
||||||
|
>
|
||||||
|
{publicLink}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p className={style.flexNote}>
|
||||||
|
Picture in Picture Screen <br />
|
||||||
|
<a
|
||||||
|
href={pipLink}
|
||||||
|
target='_blank'
|
||||||
|
rel='noreferrer'
|
||||||
|
className={style.label}
|
||||||
|
>
|
||||||
|
{pipLink}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span> Manage custom aliases</span>
|
||||||
|
<div className={style.modalInline}>
|
||||||
|
<Input
|
||||||
|
size='sm'
|
||||||
|
name='URL'
|
||||||
|
placeholder='A long URL'
|
||||||
|
autoComplete='off'
|
||||||
|
value={'A long URL'}
|
||||||
|
onChange={(event) => {
|
||||||
|
// Nothing here yet
|
||||||
|
}}
|
||||||
|
isDisabled={true}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
size='sm'
|
||||||
|
name='Alias'
|
||||||
|
placeholder='A nice alias'
|
||||||
|
autoComplete='off'
|
||||||
|
value={'A nice alias'}
|
||||||
|
onChange={(event) => {
|
||||||
|
// Nothing here yet
|
||||||
|
}}
|
||||||
|
isDisabled={true}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
size='sm'
|
||||||
|
icon={<FiMinus />}
|
||||||
|
colorScheme='red'
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={style.separator} />
|
||||||
|
<div className={style.modalInline}>
|
||||||
|
<Input
|
||||||
|
size='sm'
|
||||||
|
name='URL'
|
||||||
|
placeholder='URL'
|
||||||
|
autoComplete='off'
|
||||||
|
value={'URL'}
|
||||||
|
onChange={(event) => {
|
||||||
|
// Nothing here yet
|
||||||
|
}}
|
||||||
|
isDisabled={true}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
size='sm'
|
||||||
|
name='Alias'
|
||||||
|
placeholder='Alias'
|
||||||
|
autoComplete='off'
|
||||||
|
value={'Alias'}
|
||||||
|
onChange={(event) => {
|
||||||
|
// Nothing here yet
|
||||||
|
}}
|
||||||
|
isDisabled={true}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
size='sm'
|
||||||
|
icon={<FiPlus />}
|
||||||
|
colorScheme='blue'
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</ModalBody>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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 (
|
||||||
|
<>
|
||||||
|
<form onSubmit={submitHandler}>
|
||||||
|
<ModalBody className={style.modalBody}>
|
||||||
|
{status === 'success' && (
|
||||||
|
<>
|
||||||
|
<p className={style.notes}>
|
||||||
|
Options related to the application
|
||||||
|
<br />
|
||||||
|
!!! Changes take effect after app restart !!!
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<FormControl id='serverPort'>
|
||||||
|
<FormLabel htmlFor='serverPort'>
|
||||||
|
Viewer Port
|
||||||
|
<span className={style.notes}>Port to access viewers</span>
|
||||||
|
</FormLabel>
|
||||||
|
<Input
|
||||||
|
size='sm'
|
||||||
|
name='title'
|
||||||
|
placeholder='4001'
|
||||||
|
autoComplete='off'
|
||||||
|
value={4001}
|
||||||
|
readOnly
|
||||||
|
style={{ width: '6em', textAlign: 'center' }}
|
||||||
|
/>
|
||||||
|
<span className={style.notes}>(Read Only Value)</span>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl id='oscInPort'>
|
||||||
|
<FormLabel htmlFor='oscInPort'>
|
||||||
|
OSC In Port
|
||||||
|
<span className={style.notes}>
|
||||||
|
<br />
|
||||||
|
App Control - Default 8888
|
||||||
|
</span>
|
||||||
|
</FormLabel>
|
||||||
|
<Input
|
||||||
|
size='sm'
|
||||||
|
name='oscInPort'
|
||||||
|
placeholder='8888'
|
||||||
|
autoComplete='off'
|
||||||
|
type='number'
|
||||||
|
value={formData.oscInPort}
|
||||||
|
min='1024'
|
||||||
|
max='65535'
|
||||||
|
onChange={(event) => {
|
||||||
|
setChanged(true);
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
oscInPort: parseInt(event.target.value),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
isDisabled={submitting}
|
||||||
|
style={{ width: '6em', textAlign: 'center' }}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<div className={style.modalInline}>
|
||||||
|
<FormControl id='oscOutIP' width='auto'>
|
||||||
|
<FormLabel htmlFor='oscOutIP'>
|
||||||
|
OSC Out Target IP
|
||||||
|
<span className={style.notes}>
|
||||||
|
<br />
|
||||||
|
App Feedback - Default 127.0.0.1
|
||||||
|
</span>
|
||||||
|
</FormLabel>
|
||||||
|
<Input
|
||||||
|
size='sm'
|
||||||
|
name='oscOutIP'
|
||||||
|
placeholder='127.0.0.1'
|
||||||
|
autoComplete='off'
|
||||||
|
value={formData.oscOutIP}
|
||||||
|
onChange={(event) => {
|
||||||
|
setChanged(true);
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
oscOutIP: event.target.value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
isDisabled={submitting}
|
||||||
|
style={{ width: '12em', textAlign: 'right' }}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl id='oscOutPort' width='auto'>
|
||||||
|
<FormLabel htmlFor='oscOutPort'>
|
||||||
|
OSC Out Port
|
||||||
|
<span className={style.notes}>
|
||||||
|
<br />
|
||||||
|
Default 9999
|
||||||
|
</span>
|
||||||
|
</FormLabel>
|
||||||
|
<Input
|
||||||
|
size='sm'
|
||||||
|
name='oscOutPort'
|
||||||
|
placeholder='9999'
|
||||||
|
autoComplete='off'
|
||||||
|
type='number'
|
||||||
|
value={formData.oscOutPort}
|
||||||
|
min='1024'
|
||||||
|
max='65535'
|
||||||
|
onChange={(event) => {
|
||||||
|
setChanged(true);
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
oscOutPort: parseInt(event.target.value),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
isDisabled={submitting}
|
||||||
|
style={{ width: '6em', textAlign: 'left' }}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
colorScheme='blue'
|
||||||
|
type='submit'
|
||||||
|
isLoading={submitting}
|
||||||
|
disabled={!changed}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</ModalBody>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
+67
-39
@@ -10,15 +10,18 @@ import { fetchEvent, postEvent } from 'app/api/eventApi';
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useFetch } from 'app/hooks/useFetch';
|
import { useFetch } from 'app/hooks/useFetch';
|
||||||
import { EVENT_TABLE } from 'app/api/apiConstants';
|
import { EVENT_TABLE } from 'app/api/apiConstants';
|
||||||
|
import style from './Modals.module.css';
|
||||||
|
|
||||||
export default function SettingsModal() {
|
export default function SettingsModal() {
|
||||||
const { data, status, isError } = useFetch(EVENT_TABLE, fetchEvent);
|
const { data, status } = useFetch(EVENT_TABLE, fetchEvent);
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
title: '',
|
title: '',
|
||||||
url: '',
|
url: '',
|
||||||
publicInfo: '',
|
publicInfo: '',
|
||||||
backstageInfo: '',
|
backstageInfo: '',
|
||||||
|
endMessage: '',
|
||||||
});
|
});
|
||||||
|
const [changed, setChanged] = useState(false);
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -29,6 +32,7 @@ export default function SettingsModal() {
|
|||||||
url: data.url,
|
url: data.url,
|
||||||
publicInfo: data.publicInfo,
|
publicInfo: data.publicInfo,
|
||||||
backstageInfo: data.backstageInfo,
|
backstageInfo: data.backstageInfo,
|
||||||
|
endMessage: data.endMessage,
|
||||||
});
|
});
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
@@ -36,41 +40,47 @@ export default function SettingsModal() {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
|
|
||||||
await postEvent(formData).then(setSubmitting(false));
|
await postEvent(formData);
|
||||||
|
|
||||||
|
setChanged(false);
|
||||||
|
setSubmitting(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<form onSubmit={submitHandler}>
|
<form onSubmit={submitHandler}>
|
||||||
<ModalBody>
|
<ModalBody className={style.modalBody}>
|
||||||
{status === 'success' && (
|
{status === 'success' && (
|
||||||
<>
|
<>
|
||||||
|
<p className={style.notes}>
|
||||||
|
Options related to the running event
|
||||||
|
<br />
|
||||||
|
Affect rendered views
|
||||||
|
</p>
|
||||||
|
|
||||||
<FormControl id='title'>
|
<FormControl id='title'>
|
||||||
<FormLabel
|
<FormLabel htmlFor='title'>Event Title</FormLabel>
|
||||||
style={{ fontWeight: 400, paddingTop: '1em' }}
|
|
||||||
htmlFor='title'
|
|
||||||
>
|
|
||||||
Event Title
|
|
||||||
</FormLabel>
|
|
||||||
<Input
|
<Input
|
||||||
size='sm'
|
size='sm'
|
||||||
|
maxLength={35}
|
||||||
name='title'
|
name='title'
|
||||||
placeholder='Event Title'
|
placeholder='Event Title'
|
||||||
autoComplete='off'
|
autoComplete='off'
|
||||||
value={formData.title}
|
value={formData.title}
|
||||||
onChange={(event) =>
|
onChange={(event) => {
|
||||||
setFormData({ ...formData, title: event.target.value })
|
setChanged(true);
|
||||||
}
|
setFormData({ ...formData, title: event.target.value });
|
||||||
|
}}
|
||||||
isDisabled={submitting}
|
isDisabled={submitting}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormControl id='url'>
|
<FormControl id='url'>
|
||||||
<FormLabel
|
<FormLabel htmlFor='url'>
|
||||||
style={{ fontWeight: 400, paddingTop: '1em' }}
|
|
||||||
htmlFor='url'
|
|
||||||
>
|
|
||||||
Event URL
|
Event URL
|
||||||
|
<span className={style.notes}>
|
||||||
|
(shown as a QR code in some views)
|
||||||
|
</span>
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<Input
|
<Input
|
||||||
size='sm'
|
size='sm'
|
||||||
@@ -78,55 +88,73 @@ export default function SettingsModal() {
|
|||||||
placeholder='www.onsite.no'
|
placeholder='www.onsite.no'
|
||||||
autoComplete='off'
|
autoComplete='off'
|
||||||
value={formData.url}
|
value={formData.url}
|
||||||
onChange={(event) =>
|
onChange={(event) => {
|
||||||
setFormData({ ...formData, url: event.target.value })
|
setChanged(true);
|
||||||
}
|
setFormData({ ...formData, url: event.target.value });
|
||||||
|
}}
|
||||||
isDisabled={submitting}
|
isDisabled={submitting}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormControl id='pubInfo'>
|
<FormControl id='pubInfo'>
|
||||||
<FormLabel
|
<FormLabel htmlFor='pubInfo'>Public Info</FormLabel>
|
||||||
style={{ fontWeight: 400, paddingTop: '1em' }}
|
|
||||||
htmlFor='pubInfo'
|
|
||||||
>
|
|
||||||
Public Info
|
|
||||||
</FormLabel>
|
|
||||||
<Textarea
|
<Textarea
|
||||||
size='sm'
|
size='sm'
|
||||||
name='pubInfo'
|
name='pubInfo'
|
||||||
placeholder='Information to be shown on public screens'
|
placeholder='Information to be shown on public screens'
|
||||||
autoComplete='off'
|
autoComplete='off'
|
||||||
value={formData.publicInfo}
|
value={formData.publicInfo}
|
||||||
onChange={(event) =>
|
onChange={(event) => {
|
||||||
|
setChanged(true);
|
||||||
setFormData({
|
setFormData({
|
||||||
...formData,
|
...formData,
|
||||||
publicInfo: event.target.value,
|
publicInfo: event.target.value,
|
||||||
})
|
});
|
||||||
}
|
}}
|
||||||
isDisabled={submitting}
|
isDisabled={submitting}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormControl id='backstageInfo'>
|
<FormControl id='backstageInfo'>
|
||||||
<FormLabel
|
<FormLabel htmlFor='backstageInfo'>Backstage Info</FormLabel>
|
||||||
style={{ fontWeight: 400, paddingTop: '1em' }}
|
|
||||||
htmlFor='backstageInfo'
|
|
||||||
>
|
|
||||||
Backstage Info
|
|
||||||
</FormLabel>
|
|
||||||
<Textarea
|
<Textarea
|
||||||
size='sm'
|
size='sm'
|
||||||
name='backstageInfo'
|
name='backstageInfo'
|
||||||
placeholder='Information to be shown on backstage screens'
|
placeholder='Information to be shown on backstage screens'
|
||||||
autoComplete='off'
|
autoComplete='off'
|
||||||
value={formData.backstageInfo}
|
value={formData.backstageInfo}
|
||||||
onChange={(event) =>
|
onChange={(event) => {
|
||||||
|
setChanged(true);
|
||||||
setFormData({
|
setFormData({
|
||||||
...formData,
|
...formData,
|
||||||
backstageInfo: event.target.value,
|
backstageInfo: event.target.value,
|
||||||
})
|
});
|
||||||
}
|
}}
|
||||||
|
isDisabled={submitting}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormControl id='endMessage'>
|
||||||
|
<FormLabel htmlFor='endMessage'>
|
||||||
|
End Message
|
||||||
|
<span className={style.notes}>
|
||||||
|
Shown on presenter view when time is finished
|
||||||
|
</span>
|
||||||
|
</FormLabel>
|
||||||
|
<Input
|
||||||
|
size='sm'
|
||||||
|
maxLength={30}
|
||||||
|
name='endMessage'
|
||||||
|
placeholder='Empty message shows elapsed time'
|
||||||
|
autoComplete='off'
|
||||||
|
value={formData.endMessage}
|
||||||
|
onChange={(event) => {
|
||||||
|
setChanged(true);
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
endMessage: event.target.value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
isDisabled={submitting}
|
isDisabled={submitting}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@@ -134,9 +162,9 @@ export default function SettingsModal() {
|
|||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
colorScheme='blue'
|
colorScheme='blue'
|
||||||
mr={3}
|
|
||||||
isLoading={submitting}
|
|
||||||
type='submit'
|
type='submit'
|
||||||
|
isLoading={submitting}
|
||||||
|
disabled={!changed}
|
||||||
>
|
>
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
@@ -7,7 +7,9 @@ import {
|
|||||||
ModalOverlay,
|
ModalOverlay,
|
||||||
} from '@chakra-ui/modal';
|
} from '@chakra-ui/modal';
|
||||||
import { Tab, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/tabs';
|
import { Tab, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/tabs';
|
||||||
import SettingsModal from './SettingsModal';
|
import EventSettingsModal from './EventSettingsModal';
|
||||||
|
import AppSettingsModal from './AppSettingsModal';
|
||||||
|
import AliasesModal from './AliasesModal';
|
||||||
|
|
||||||
export default function ModalManager(props) {
|
export default function ModalManager(props) {
|
||||||
const { isOpen, onClose } = props;
|
const { isOpen, onClose } = props;
|
||||||
@@ -31,19 +33,13 @@ export default function ModalManager(props) {
|
|||||||
</TabList>
|
</TabList>
|
||||||
<TabPanels>
|
<TabPanels>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
<SettingsModal onClose={onClose} />
|
<EventSettingsModal />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
<p>Application settings will be here!</p>
|
<AppSettingsModal />
|
||||||
<p>Set port</p>
|
|
||||||
<p>Set end message</p>
|
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
<p>You will be able to define URL aliases here!</p>
|
<AliasesModal />
|
||||||
<p>List existing aliases</p>
|
|
||||||
<p>List existing aliases</p>
|
|
||||||
<p>List existing aliases</p>
|
|
||||||
<p>Add new alias input + button</p>
|
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabPanels>
|
</TabPanels>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
@@ -54,5 +50,5 @@ export default function ModalManager(props) {
|
|||||||
|
|
||||||
ModalManager.propTypes = {
|
ModalManager.propTypes = {
|
||||||
isOpen: PropTypes.bool.isRequired,
|
isOpen: PropTypes.bool.isRequired,
|
||||||
onClose: PropTypes.bool.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
.modalBody {
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modalBody > * {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modalBody > button {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notes {
|
||||||
|
font-weight: 400;
|
||||||
|
color: #2b6cb0;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.notes {
|
||||||
|
text-align: center;
|
||||||
|
border-color: #2b6cb055;
|
||||||
|
border-width: 0 2px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.notes {
|
||||||
|
font-size: 0.8em;
|
||||||
|
padding-left: 0.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modalInline {
|
||||||
|
display: flex;
|
||||||
|
gap: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highNotes {
|
||||||
|
background-color: #2b6cb022;
|
||||||
|
margin: 1em 0;
|
||||||
|
padding: 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flexNote {
|
||||||
|
font-size: 0.9em;
|
||||||
|
padding-bottom: 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a::after {
|
||||||
|
content: ' \2197';
|
||||||
|
color: #ff7597;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #ff7597;
|
||||||
|
}
|
||||||
|
|
||||||
|
.separator {
|
||||||
|
border: 1px solid #2b6cb055;
|
||||||
|
width: 50%;
|
||||||
|
margin: 0.5em auto;
|
||||||
|
}
|
||||||
@@ -64,6 +64,7 @@ const withSocket = (Component) => {
|
|||||||
url: '',
|
url: '',
|
||||||
publicInfo: '',
|
publicInfo: '',
|
||||||
backstageInfo: '',
|
backstageInfo: '',
|
||||||
|
endMessage: '',
|
||||||
});
|
});
|
||||||
const [playback, setPlayback] = useState(null);
|
const [playback, setPlayback] = useState(null);
|
||||||
|
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ export default function StageManager(props) {
|
|||||||
<div className={style.qr}>
|
<div className={style.qr}>
|
||||||
{general.url != null && general.url !== '' && (
|
{general.url != null && general.url !== '' && (
|
||||||
<QRCode
|
<QRCode
|
||||||
value='www.carlosvalente.com'
|
value={general.url}
|
||||||
size={window.innerWidth / 12}
|
size={window.innerWidth / 12}
|
||||||
level='L'
|
level='L'
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr 1fr 3vw 2fr;
|
grid-template-columns: 1fr 1fr 1fr 3vw 2fr;
|
||||||
grid-template-rows: 2fr 1fr 1fr 0 1fr;
|
grid-template-rows: 15vh 1fr 1fr 0 13vh 13vh;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
' titl titl titl . schd'
|
' titl titl titl . schd'
|
||||||
' now now now . schd'
|
' now now now . schd'
|
||||||
@@ -55,6 +55,8 @@
|
|||||||
.todayContainer {
|
.todayContainer {
|
||||||
background-color: rgba(255, 255, 255, 0.05);
|
background-color: rgba(255, 255, 255, 0.05);
|
||||||
padding: 1vh 2vw;
|
padding: 1vh 2vw;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clockContainer,
|
.clockContainer,
|
||||||
@@ -163,7 +165,7 @@
|
|||||||
|
|
||||||
.countdownContainer {
|
.countdownContainer {
|
||||||
grid-area: clck;
|
grid-area: clck;
|
||||||
border-radius: 1vw 1vw 0 0;
|
border-radius: 1vw 1vw 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clock {
|
.clock {
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ export default function Public(props) {
|
|||||||
<div className={style.qr}>
|
<div className={style.qr}>
|
||||||
{general.url != null && general.url !== '' && (
|
{general.url != null && general.url !== '' && (
|
||||||
<QRCode
|
<QRCode
|
||||||
value='www.carlosvalente.com'
|
value={general.url}
|
||||||
size={window.innerWidth / 12}
|
size={window.innerWidth / 12}
|
||||||
level='L'
|
level='L'
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr 1fr 3vw 2fr;
|
grid-template-columns: 1fr 1fr 1fr 3vw 2fr;
|
||||||
grid-template-rows: 2fr 1fr 1fr 0 1fr;
|
grid-template-rows: 15vh 1fr 1fr 0 13vh 13vh;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
' titl titl titl . schd'
|
' titl titl titl . schd'
|
||||||
' now now now . schd'
|
' now now now . schd'
|
||||||
@@ -55,6 +55,8 @@
|
|||||||
.todayContainer {
|
.todayContainer {
|
||||||
background-color: rgba(255, 255, 255, 0.05);
|
background-color: rgba(255, 255, 255, 0.05);
|
||||||
padding: 1vh 2vw;
|
padding: 1vh 2vw;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clockContainer,
|
.clockContainer,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import TitleCard from 'common/components/views/TitleCard';
|
|||||||
import style from './PresenterView.module.css';
|
import style from './PresenterView.module.css';
|
||||||
|
|
||||||
export default function PresenterView(props) {
|
export default function PresenterView(props) {
|
||||||
const { pres, title, time } = props;
|
const { general, pres, title, time } = props;
|
||||||
|
|
||||||
// Set window title
|
// Set window title
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -18,6 +18,14 @@ export default function PresenterView(props) {
|
|||||||
const isPlaying = time.playstate !== 'pause';
|
const isPlaying = time.playstate !== 'pause';
|
||||||
const normalisedTime = Math.max(time.running, 0);
|
const normalisedTime = Math.max(time.running, 0);
|
||||||
|
|
||||||
|
// show timer if end message is empty
|
||||||
|
const endMessage =
|
||||||
|
general.endMessage == null || general.endMessage === '' ? (
|
||||||
|
<Countdown time={time.running} hideZeroHours negative />
|
||||||
|
) : (
|
||||||
|
general.endMessage
|
||||||
|
);
|
||||||
|
|
||||||
// motion
|
// motion
|
||||||
const titleVariants = {
|
const titleVariants = {
|
||||||
hidden: {
|
hidden: {
|
||||||
@@ -57,7 +65,7 @@ export default function PresenterView(props) {
|
|||||||
|
|
||||||
<div className={style.timerContainer}>
|
<div className={style.timerContainer}>
|
||||||
{time.finished ? (
|
{time.finished ? (
|
||||||
<div className={style.finished}>TIME UP</div>
|
<div className={style.finished}>{endMessage}</div>
|
||||||
) : (
|
) : (
|
||||||
<div className={isPlaying ? style.countdown : style.countdownPaused}>
|
<div className={isPlaying ? style.countdown : style.countdownPaused}>
|
||||||
<Countdown time={normalisedTime} hideZeroHours />
|
<Countdown time={normalisedTime} hideZeroHours />
|
||||||
|
|||||||
@@ -66,7 +66,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.finished {
|
.finished {
|
||||||
font-size: 18vw;
|
text-align: center;
|
||||||
|
font-size: 12vw;
|
||||||
line-height: 18vw;
|
line-height: 18vw;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #ff6969;
|
color: #ff6969;
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ export default function Pip(props) {
|
|||||||
<div className={style.qr}>
|
<div className={style.qr}>
|
||||||
{general.url != null && general.url !== '' && (
|
{general.url != null && general.url !== '' && (
|
||||||
<QRCode
|
<QRCode
|
||||||
value='www.carlosvalente.com'
|
value={general.url}
|
||||||
size={window.innerWidth / 12}
|
size={window.innerWidth / 12}
|
||||||
level='L'
|
level='L'
|
||||||
/>
|
/>
|
||||||
|
|||||||
+521
-488
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 151 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 151 KiB |
@@ -0,0 +1,127 @@
|
|||||||
|
{
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"title": "Welcome to Ontime",
|
||||||
|
"subtitle": "Subtitles are useful",
|
||||||
|
"presenter": "cpvalente",
|
||||||
|
"note": "Maybe a running note for the operator?",
|
||||||
|
"timeStart": 28800000,
|
||||||
|
"timeEnd": 30600000,
|
||||||
|
"isPublic": false,
|
||||||
|
"type": "event",
|
||||||
|
"revision": 7,
|
||||||
|
"id": "5946"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "This is your event list",
|
||||||
|
"subtitle": "",
|
||||||
|
"presenter": "cpvalente",
|
||||||
|
"note": "",
|
||||||
|
"timeStart": 30600000,
|
||||||
|
"timeEnd": 34200000,
|
||||||
|
"isPublic": false,
|
||||||
|
"type": "event",
|
||||||
|
"revision": 4,
|
||||||
|
"id": "c2e7"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Events run in the list order",
|
||||||
|
"subtitle": "",
|
||||||
|
"presenter": "",
|
||||||
|
"note": "",
|
||||||
|
"timeStart": 34200000,
|
||||||
|
"timeEnd": 34800000,
|
||||||
|
"isPublic": false,
|
||||||
|
"type": "event",
|
||||||
|
"revision": 4,
|
||||||
|
"id": "cc0f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Unless recalled by the OSC address",
|
||||||
|
"subtitle": "",
|
||||||
|
"presenter": "",
|
||||||
|
"note": "In green, below",
|
||||||
|
"timeStart": 34800000,
|
||||||
|
"timeEnd": 35400000,
|
||||||
|
"isPublic": false,
|
||||||
|
"type": "event",
|
||||||
|
"revision": 7,
|
||||||
|
"id": "8ee5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Use simpler times to create a timer",
|
||||||
|
"subtitle": "",
|
||||||
|
"presenter": "",
|
||||||
|
"note": "",
|
||||||
|
"timeStart": 0,
|
||||||
|
"timeEnd": 600000,
|
||||||
|
"isPublic": false,
|
||||||
|
"type": "event",
|
||||||
|
"revision": 2,
|
||||||
|
"id": "8222"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"duration": 900000,
|
||||||
|
"type": "delay",
|
||||||
|
"revision": 1,
|
||||||
|
"id": "a386"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Add delay blocks to affect all events",
|
||||||
|
"subtitle": "",
|
||||||
|
"presenter": "",
|
||||||
|
"note": "* Until a block is found",
|
||||||
|
"timeStart": 35400000,
|
||||||
|
"timeEnd": 36600000,
|
||||||
|
"isPublic": false,
|
||||||
|
"type": "event",
|
||||||
|
"revision": 6,
|
||||||
|
"id": "6dce"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Add and remove events with [+] and [-]",
|
||||||
|
"subtitle": "",
|
||||||
|
"presenter": "",
|
||||||
|
"note": "Orange and red buttons on the right",
|
||||||
|
"timeStart": 36600000,
|
||||||
|
"timeEnd": 43200000,
|
||||||
|
"isPublic": false,
|
||||||
|
"type": "event",
|
||||||
|
"revision": 9,
|
||||||
|
"id": "2651"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "block",
|
||||||
|
"id": "e6a1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "And control whether they are public",
|
||||||
|
"subtitle": "",
|
||||||
|
"presenter": "",
|
||||||
|
"note": "Blue button on the right",
|
||||||
|
"timeStart": 46800000,
|
||||||
|
"timeEnd": 57600000,
|
||||||
|
"isPublic": false,
|
||||||
|
"type": "event",
|
||||||
|
"revision": 6,
|
||||||
|
"id": "1358"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"event": {
|
||||||
|
"title": "All about Carlos demo event",
|
||||||
|
"url": "www.carlosvalente.com",
|
||||||
|
"publicInfo": "WiFi: demoproject \nPassword: ontimeproject",
|
||||||
|
"backstageInfo": "WiFi: demobackstage\nPassword: ontimeproject"
|
||||||
|
"endMessage": ""
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"app": "ontime",
|
||||||
|
"version": 1,
|
||||||
|
"serverPort": 4001,
|
||||||
|
"oscInPort": 8888,
|
||||||
|
"oscOutPort": 9999,
|
||||||
|
"oscOutIP": "127.0.0.1",
|
||||||
|
"oscEnabled": true,
|
||||||
|
"lock": false
|
||||||
|
}
|
||||||
|
}
|
||||||
+21
-18
@@ -26,12 +26,14 @@ const nodePath =
|
|||||||
const { startServer, startOSCServer, startOSCClient } = await import(
|
const { startServer, startOSCServer, startOSCClient } = await import(
|
||||||
nodePath
|
nodePath
|
||||||
);
|
);
|
||||||
// Start express server
|
|
||||||
loaded = startServer();
|
|
||||||
// Start OSC Server (API)
|
|
||||||
startOSCServer();
|
|
||||||
// Start OSC Client (Feedback)
|
// Start OSC Client (Feedback)
|
||||||
startOSCClient();
|
await startOSCClient();
|
||||||
|
|
||||||
|
// Start express server
|
||||||
|
loaded = await startServer();
|
||||||
|
|
||||||
|
// Start OSC Server (API)
|
||||||
|
await startOSCServer();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
loaded = error;
|
loaded = error;
|
||||||
@@ -42,9 +44,9 @@ const nodePath =
|
|||||||
const trayIcon = path.join(__dirname, './assets/background.png');
|
const trayIcon = path.join(__dirname, './assets/background.png');
|
||||||
const appIcon = path.join(__dirname, './assets/logo.png');
|
const appIcon = path.join(__dirname, './assets/logo.png');
|
||||||
|
|
||||||
function showNotification(text) {
|
function showNotification(title, text) {
|
||||||
new Notification({
|
new Notification({
|
||||||
title: 'ontime',
|
title: title,
|
||||||
body: text,
|
body: text,
|
||||||
silent: true,
|
silent: true,
|
||||||
}).show();
|
}).show();
|
||||||
@@ -99,7 +101,6 @@ function createWindow() {
|
|||||||
show: false,
|
show: false,
|
||||||
textAreasAreResizable: false,
|
textAreasAreResizable: false,
|
||||||
enableWebSQL: false,
|
enableWebSQL: false,
|
||||||
skipTaskbar: true,
|
|
||||||
darkTheme: true,
|
darkTheme: true,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
preload: path.join(__dirname, './electron/preload.js'),
|
preload: path.join(__dirname, './electron/preload.js'),
|
||||||
@@ -113,6 +114,11 @@ function createWindow() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
app.whenReady().then(() => {
|
app.whenReady().then(() => {
|
||||||
|
// Set app title in windows
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
app.setAppUserModelId(app.name);
|
||||||
|
}
|
||||||
|
|
||||||
createWindow();
|
createWindow();
|
||||||
|
|
||||||
// register global shortcuts
|
// register global shortcuts
|
||||||
@@ -123,11 +129,6 @@ app.whenReady().then(() => {
|
|||||||
win.focus();
|
win.focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
globalShortcut.register('Alt+t', () => {
|
|
||||||
// Show dev tools
|
|
||||||
win.webContents.openDevTools({ mode: 'detach' });
|
|
||||||
});
|
|
||||||
|
|
||||||
// recreate window if no others open
|
// recreate window if no others open
|
||||||
app.on('activate', () => {
|
app.on('activate', () => {
|
||||||
if (BrowserWindow.getAllWindows().length === 0) {
|
if (BrowserWindow.getAllWindows().length === 0) {
|
||||||
@@ -151,7 +152,6 @@ app.whenReady().then(() => {
|
|||||||
win.focus();
|
win.focus();
|
||||||
|
|
||||||
splash.destroy();
|
splash.destroy();
|
||||||
showNotification(loaded.toString());
|
|
||||||
|
|
||||||
// tray stuff
|
// tray stuff
|
||||||
// TODO: get IP Address
|
// TODO: get IP Address
|
||||||
@@ -163,7 +163,7 @@ app.whenReady().then(() => {
|
|||||||
win.on('close', function (event) {
|
win.on('close', function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (!isQuitting) {
|
if (!isQuitting) {
|
||||||
showNotification('App running in background');
|
showNotification('Window Closed', 'App running in background');
|
||||||
win.hide();
|
win.hide();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -177,14 +177,14 @@ app.whenReady().then(() => {
|
|||||||
// Define context menu
|
// Define context menu
|
||||||
const trayMenuTemplate = [
|
const trayMenuTemplate = [
|
||||||
{
|
{
|
||||||
label: 'Show App',
|
label: 'Show App (Alt + 1)',
|
||||||
click: () => {
|
click: () => {
|
||||||
win.show();
|
win.show();
|
||||||
win.focus();
|
win.focus();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Close',
|
label: 'Shutdown',
|
||||||
click: () => {
|
click: () => {
|
||||||
win.destroy();
|
win.destroy();
|
||||||
app.quit();
|
app.quit();
|
||||||
@@ -209,7 +209,7 @@ app.once('before-quit', () => {
|
|||||||
// Get messages from react
|
// Get messages from react
|
||||||
// Test message
|
// Test message
|
||||||
ipcMain.on('test-message', (event, arg) => {
|
ipcMain.on('test-message', (event, arg) => {
|
||||||
showNotification('testing 1-2', arg);
|
showNotification('Test Message', 'test from react', arg);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Terminate
|
// Terminate
|
||||||
@@ -240,6 +240,9 @@ ipcMain.on('set-window', (event, arg) => {
|
|||||||
} else if (arg === 'to-tray') {
|
} else if (arg === 'to-tray') {
|
||||||
// window to tray
|
// window to tray
|
||||||
win.hide();
|
win.hide();
|
||||||
|
} else if (arg === 'show-dev') {
|
||||||
|
// Show dev tools
|
||||||
|
win.webContents.openDevTools({ mode: 'detach' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+7
-3
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ontime",
|
"name": "ontime",
|
||||||
"version": "0.1.0",
|
"version": "0.1.1",
|
||||||
"author": "Carlos Valente",
|
"author": "Carlos Valente",
|
||||||
"description": "Time keeping for live events",
|
"description": "Time keeping for live events",
|
||||||
"repository": "https://github.com/cpvalente/ontime",
|
"repository": "https://github.com/cpvalente/ontime",
|
||||||
@@ -8,7 +8,6 @@
|
|||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"concurrently": "^6.1.0",
|
|
||||||
"electron": "^13.1.2",
|
"electron": "^13.1.2",
|
||||||
"electron-builder": "^22.11.3",
|
"electron-builder": "^22.11.3",
|
||||||
"eslint": "^7.26.0",
|
"eslint": "^7.26.0",
|
||||||
@@ -48,11 +47,16 @@
|
|||||||
},
|
},
|
||||||
"nsis": {
|
"nsis": {
|
||||||
"artifactName": "ontime-win64.exe",
|
"artifactName": "ontime-win64.exe",
|
||||||
|
"oneClick": false,
|
||||||
|
"allowToChangeInstallationDirectory": true,
|
||||||
|
"allowElevation": false,
|
||||||
"createDesktopShortcut": true,
|
"createDesktopShortcut": true,
|
||||||
"createStartMenuShortcut": true,
|
"createStartMenuShortcut": true,
|
||||||
"deleteAppDataOnUninstall": true,
|
"deleteAppDataOnUninstall": true,
|
||||||
"runAfterFinish": false,
|
"runAfterFinish": false,
|
||||||
"installerIcon": "icon.ico"
|
"installerIcon": "icon.ico",
|
||||||
|
"installerSidebar": "ontime-install.bmp",
|
||||||
|
"uninstallerSidebar": "ontime-uninstall.bmp"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"**/*",
|
"**/*",
|
||||||
|
|||||||
+58
-29
@@ -4,6 +4,7 @@ import { sessionId, user } from './utils/analytics.js';
|
|||||||
user.screenview('Node service', 'ontime').send();
|
user.screenview('Node service', 'ontime').send();
|
||||||
user.event('NODE', 'started', 'starting node service').send();
|
user.event('NODE', 'started', 'starting node service').send();
|
||||||
|
|
||||||
|
// import config
|
||||||
import { config } from './config/config.js';
|
import { config } from './config/config.js';
|
||||||
|
|
||||||
// init database
|
// init database
|
||||||
@@ -25,7 +26,7 @@ import { Client } from 'node-osc';
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import http from 'http';
|
import http from 'http';
|
||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
import { dbModel } from './data/dataModel.js';
|
import { dbModel } from './models/dataModel.js';
|
||||||
import ua from 'universal-analytics';
|
import ua from 'universal-analytics';
|
||||||
|
|
||||||
// Read data from JSON file, this will set db.data content
|
// Read data from JSON file, this will set db.data content
|
||||||
@@ -95,20 +96,66 @@ app.use((err, req, res, next) => {
|
|||||||
res.status(500).send(err.stack);
|
res.status(500).send(err.stack);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*************** START SERVICES ***************/
|
||||||
|
/* Override config
|
||||||
|
* ----------------
|
||||||
|
*
|
||||||
|
* Configuration of services comes from app general config
|
||||||
|
* It can be overriden here by the settings in the db
|
||||||
|
* It can also be overriden on call
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
const s = data.settings;
|
||||||
|
|
||||||
|
const oscIP = s.oscOutIP || config.osc.ipOut;
|
||||||
|
const oscOutPort = s.oscOutPort || config.osc.portOut;
|
||||||
|
const oscInPort = s.oscInPort || config.osc.port;
|
||||||
|
|
||||||
|
const serverPort = s.serverPort || config.server.port;
|
||||||
|
|
||||||
|
// Start OSC server
|
||||||
|
import { initiateOSC, shutdownOSCServer } from './controllers/OscController.js';
|
||||||
|
|
||||||
|
export const startOSCServer = async (overrideConfig = null) => {
|
||||||
|
// Setup default port
|
||||||
|
const oscSettings = {
|
||||||
|
port: overrideConfig?.port || oscInPort,
|
||||||
|
ipOut: oscIP,
|
||||||
|
portOut: oscOutPort,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start OSC Server
|
||||||
|
initiateOSC(oscSettings);
|
||||||
|
};
|
||||||
|
|
||||||
// Start OSC Client
|
// Start OSC Client
|
||||||
// TODO: Move this to function
|
let oscClient = null;
|
||||||
const oscClient = new Client('127.0.0.1', 9999);
|
|
||||||
|
export const startOSCClient = async (overrideConfig = null) => {
|
||||||
|
// Setup default port
|
||||||
|
const port = overrideConfig?.port || oscOutPort;
|
||||||
|
console.log('initialise OSC Client on port: ', port);
|
||||||
|
|
||||||
|
oscClient = new Client(oscIP, oscOutPort);
|
||||||
|
};
|
||||||
|
|
||||||
// create HTTP server
|
// create HTTP server
|
||||||
const server = http.createServer(app);
|
const server = http.createServer(app);
|
||||||
|
|
||||||
export const startServer = (overrideConfig = null) => {
|
export const startServer = async (overrideConfig = null) => {
|
||||||
// Setup default port
|
// Setup default port
|
||||||
const serverPort = overrideConfig?.port || config.server.port;
|
// const port = overrideConfig?.port || serverPort;
|
||||||
|
|
||||||
|
/* Note: Port is hardcoded
|
||||||
|
* need to find a good solution for sharing
|
||||||
|
* the dynamic info with the frontend
|
||||||
|
*/
|
||||||
|
const port = 4001;
|
||||||
|
|
||||||
// Start server
|
// Start server
|
||||||
const returnMessage = `HTTP Server is listening on port ${serverPort}`;
|
const returnMessage = `HTTP Server is listening on port ${port}`;
|
||||||
server.listen(serverPort, '0.0.0.0', () => console.log(returnMessage));
|
server.listen(port, '0.0.0.0', () => console.log(returnMessage));
|
||||||
|
|
||||||
// init timer
|
// init timer
|
||||||
global.timer = new EventTimer(server, oscClient, config);
|
global.timer = new EventTimer(server, oscClient, config);
|
||||||
@@ -117,38 +164,20 @@ export const startServer = (overrideConfig = null) => {
|
|||||||
return returnMessage;
|
return returnMessage;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Start OSC server
|
export const shutdown = async () => {
|
||||||
import { initiateOSC, shutdownOSCServer } from './controllers/OscController.js';
|
|
||||||
|
|
||||||
export const startOSCServer = (overrideConfig = null) => {
|
|
||||||
// Setup default port
|
|
||||||
const oscInPort = overrideConfig?.port || config.osc.port;
|
|
||||||
initiateOSC(config.osc);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const startOSCClient = (overrideConfig = null) => {
|
|
||||||
// Setup default port
|
|
||||||
const oscOutPort = overrideConfig?.port || config.osc.portOut;
|
|
||||||
console.log('initialise OSC Client at port: ', oscOutPort);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const shutdown = () => {
|
|
||||||
console.log('Node service shutdown');
|
console.log('Node service shutdown');
|
||||||
|
|
||||||
user.event('NODE', 'shutdown', 'requesting node shutfown').send();
|
user.event('NODE', 'shutdown', 'requesting node shutfown').send();
|
||||||
|
|
||||||
// shutdown express server
|
// shutdown express server
|
||||||
server.close();
|
server.close();
|
||||||
|
|
||||||
// shutdown OSC Server
|
// shutdown OSC Server
|
||||||
shutdownOSCServer();
|
shutdownOSCServer();
|
||||||
|
|
||||||
// shutdown OSC Client
|
// shutdown OSC Client
|
||||||
|
oscClient.close();
|
||||||
|
|
||||||
// shutdown timer
|
// shutdown timer
|
||||||
global.timer.shutdown();
|
global.timer.shutdown();
|
||||||
};
|
};
|
||||||
|
|
||||||
// if (env == 'development') {
|
|
||||||
// startServer();
|
|
||||||
// startOSCServer();
|
|
||||||
// startOSCClient();
|
|
||||||
// }
|
|
||||||
|
|||||||
@@ -242,10 +242,6 @@ export class EventTimer extends Timer {
|
|||||||
} else {
|
} else {
|
||||||
// look for event if none is loaded
|
// look for event if none is loaded
|
||||||
if (this.current <= 0 || this.secondaryTimer <= 0) this.rollLoad();
|
if (this.current <= 0 || this.secondaryTimer <= 0) this.rollLoad();
|
||||||
|
|
||||||
// count to next event
|
|
||||||
// TODO: replace with proper counter
|
|
||||||
if (this.secondaryTimer != null) this.secondaryTimer -= 1000;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -946,8 +942,12 @@ export class EventTimer extends Timer {
|
|||||||
|
|
||||||
rollLoad() {
|
rollLoad() {
|
||||||
const now = this._getCurrentTime();
|
const now = this._getCurrentTime();
|
||||||
this._resetTimers(true);
|
|
||||||
this._resetSelection();
|
// maybe roll has already been loaded
|
||||||
|
if (this.secondaryTimer == null) {
|
||||||
|
this._resetTimers(true);
|
||||||
|
this._resetSelection();
|
||||||
|
}
|
||||||
|
|
||||||
let foundNow = null;
|
let foundNow = null;
|
||||||
let nextIndex = null;
|
let nextIndex = null;
|
||||||
@@ -988,16 +988,20 @@ export class EventTimer extends Timer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// nothing to play next, unload
|
// nothing to play next, unload
|
||||||
if (!foundNow && !nextIndex) {
|
if (foundNow == null && nextIndex == null) {
|
||||||
this.unload();
|
this.unload();
|
||||||
|
console.log('Roll: no events found');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextIndex) {
|
if (nextIndex != null) {
|
||||||
// load titles
|
// load titles
|
||||||
this._loadThisTitles(nextIndex, 'next');
|
const e = this._eventlist[nextIndex];
|
||||||
|
this._loadThisTitles(e, 'next');
|
||||||
|
|
||||||
if (!foundNow) {
|
if (foundNow == null) {
|
||||||
|
if (this.secondaryTimer == null)
|
||||||
|
console.log('Roll: waiting for event start');
|
||||||
// timer counts to nextStart
|
// timer counts to nextStart
|
||||||
this.secondaryTimer = nextStart;
|
this.secondaryTimer = nextStart;
|
||||||
}
|
}
|
||||||
@@ -1066,8 +1070,8 @@ export class EventTimer extends Timer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unload() {
|
unload() {
|
||||||
// reset duration
|
// reset timer
|
||||||
this.duration = null;
|
this._resetTimers(true);
|
||||||
|
|
||||||
// reset selected
|
// reset selected
|
||||||
this._resetSelection();
|
this._resetSelection();
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ export class Timer {
|
|||||||
_resetTimers(total = false) {
|
_resetTimers(total = false) {
|
||||||
if (total) this.duration = null;
|
if (total) this.duration = null;
|
||||||
this.current = this.duration;
|
this.current = this.duration;
|
||||||
|
this.running = null;
|
||||||
this.secondaryTimer = null;
|
this.secondaryTimer = null;
|
||||||
this._finishAt = null;
|
this._finishAt = null;
|
||||||
this._finishedAt = null;
|
this._finishedAt = null;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
event as eventDef,
|
event as eventDef,
|
||||||
delay as delayDef,
|
delay as delayDef,
|
||||||
block as blockDef,
|
block as blockDef,
|
||||||
} from '../data/eventsDefinition.js';
|
} from '../models/eventsDefinition.js';
|
||||||
|
|
||||||
function _getEventsCount() {
|
function _getEventsCount() {
|
||||||
return Array.from(data.events).length;
|
return Array.from(data.events).length;
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import {
|
|||||||
event as eventDef,
|
event as eventDef,
|
||||||
delay as delayDef,
|
delay as delayDef,
|
||||||
block as blockDef,
|
block as blockDef,
|
||||||
} from '../data/eventsDefinition.js';
|
} from '../models/eventsDefinition.js';
|
||||||
import { dbModel } from '../data/dataModel.js';
|
import { dbModel } from '../models/dataModel.js';
|
||||||
import { networkInterfaces } from 'os';
|
import { networkInterfaces } from 'os';
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
@@ -32,6 +32,7 @@ async function deleteFile(file) {
|
|||||||
async function parsev1(jsonData) {
|
async function parsev1(jsonData) {
|
||||||
let numEntries = 0;
|
let numEntries = 0;
|
||||||
if ('events' in jsonData) {
|
if ('events' in jsonData) {
|
||||||
|
console.log('Found events definition, importing...');
|
||||||
let events = [];
|
let events = [];
|
||||||
let ids = [];
|
let ids = [];
|
||||||
for (const e of jsonData.events) {
|
for (const e of jsonData.events) {
|
||||||
@@ -69,6 +70,7 @@ async function parsev1(jsonData) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ('event' in jsonData) {
|
if ('event' in jsonData) {
|
||||||
|
console.log('Found event data, importing...');
|
||||||
const e = jsonData.event;
|
const e = jsonData.event;
|
||||||
// filter known properties
|
// filter known properties
|
||||||
const event = {
|
const event = {
|
||||||
@@ -77,6 +79,7 @@ async function parsev1(jsonData) {
|
|||||||
url: e.url,
|
url: e.url,
|
||||||
publicInfo: e.publicInfo,
|
publicInfo: e.publicInfo,
|
||||||
backstageInfo: e.backstageInfo,
|
backstageInfo: e.backstageInfo,
|
||||||
|
endMessage: e.endMessage,
|
||||||
};
|
};
|
||||||
|
|
||||||
// write to db
|
// write to db
|
||||||
@@ -84,10 +87,23 @@ async function parsev1(jsonData) {
|
|||||||
db.write();
|
db.write();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not handling settings yet
|
// Settings handled partially
|
||||||
// let settings = {};
|
if ('settings' in jsonData) {
|
||||||
// if ('settings' in jsonData) {
|
console.log('Found settings definition, importing...');
|
||||||
// }
|
const s = jsonData.settings;
|
||||||
|
let settings = {};
|
||||||
|
|
||||||
|
if (s.oscInPort) settings.oscInPort = s.oscInPort;
|
||||||
|
if (s.oscOutPort) settings.oscOutPort = s.oscOutPort;
|
||||||
|
if (s.oscOutIP) settings.oscOutIP = s.oscOutIP;
|
||||||
|
|
||||||
|
// write to db
|
||||||
|
db.data.settings = {
|
||||||
|
...dbModel.settings,
|
||||||
|
...settings,
|
||||||
|
};
|
||||||
|
db.write();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create controller for GET request to '/ontime/db'
|
// Create controller for GET request to '/ontime/db'
|
||||||
@@ -158,19 +174,52 @@ const getNetworkInterfaces = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create controller for POST request to '/ontime/info'
|
// Create controller for POST request to '/ontime/info'
|
||||||
// Returns -
|
// Returns -
|
||||||
// TODO: Add version
|
|
||||||
export const getInfo = async (req, res) => {
|
export const getInfo = async (req, res) => {
|
||||||
|
const version = data.settings.version;
|
||||||
|
const serverPort = data.settings.serverPort;
|
||||||
|
const oscInPort = data.settings.oscInPort;
|
||||||
|
const oscOutPort = data.settings.oscOutPort;
|
||||||
|
const oscOutIP = data.settings.oscOutIP;
|
||||||
|
|
||||||
|
// get nif and inject localhost
|
||||||
const ni = getNetworkInterfaces();
|
const ni = getNetworkInterfaces();
|
||||||
|
ni.unshift({ name: 'localhost', address: '127.0.0.1' });
|
||||||
|
|
||||||
|
// send object with network information
|
||||||
res.status(200).send({
|
res.status(200).send({
|
||||||
networkInterfaces: ni,
|
networkInterfaces: ni,
|
||||||
|
version,
|
||||||
|
serverPort,
|
||||||
|
oscInPort,
|
||||||
|
oscOutPort,
|
||||||
|
oscOutIP,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Create controller for POST request to '/ontime/info'
|
||||||
|
// Returns ACK message
|
||||||
|
export const postInfo = async (req, res) => {
|
||||||
|
if (!req.body) {
|
||||||
|
res.status(400).send('No object found in request');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// TODO: validate data
|
||||||
|
try {
|
||||||
|
data.settings = { ...data.settings, ...req.body };
|
||||||
|
await db.write();
|
||||||
|
res.sendStatus(200);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).send(error);
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Create controller for POST request to '/ontime/db'
|
// Create controller for POST request to '/ontime/db'
|
||||||
// Returns -
|
// Returns -
|
||||||
export const dbUpload = async (req, res) => {
|
export const dbUpload = async (req, res) => {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export const pbGet = async (req, res) => {
|
|||||||
// Create controller for GET request to '/playback/start'
|
// Create controller for GET request to '/playback/start'
|
||||||
// Starts timer object
|
// Starts timer object
|
||||||
export const pbStart = async (req, res) => {
|
export const pbStart = async (req, res) => {
|
||||||
|
console.log('Calling start');
|
||||||
global.timer.start();
|
global.timer.start();
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
};
|
};
|
||||||
@@ -14,6 +15,7 @@ export const pbStart = async (req, res) => {
|
|||||||
// Create controller for GET request to '/playback/pause'
|
// Create controller for GET request to '/playback/pause'
|
||||||
// Pauses timer object
|
// Pauses timer object
|
||||||
export const pbPause = async (req, res) => {
|
export const pbPause = async (req, res) => {
|
||||||
|
console.log('Calling pause');
|
||||||
global.timer.pause();
|
global.timer.pause();
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
};
|
};
|
||||||
@@ -21,6 +23,7 @@ export const pbPause = async (req, res) => {
|
|||||||
// Create controller for GET request to '/playback/stop'
|
// Create controller for GET request to '/playback/stop'
|
||||||
// Stops timer object
|
// Stops timer object
|
||||||
export const pbStop = async (req, res) => {
|
export const pbStop = async (req, res) => {
|
||||||
|
console.log('Calling stop');
|
||||||
global.timer.stop();
|
global.timer.stop();
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
};
|
};
|
||||||
@@ -28,6 +31,7 @@ export const pbStop = async (req, res) => {
|
|||||||
// Create controller for GET request to '/playback/roll'
|
// Create controller for GET request to '/playback/roll'
|
||||||
// Sets timer object to roll mode
|
// Sets timer object to roll mode
|
||||||
export const pbRoll = async (req, res) => {
|
export const pbRoll = async (req, res) => {
|
||||||
|
console.log('Calling roll');
|
||||||
global.timer.roll();
|
global.timer.roll();
|
||||||
res.sendStatus(501);
|
res.sendStatus(501);
|
||||||
};
|
};
|
||||||
@@ -35,6 +39,7 @@ export const pbRoll = async (req, res) => {
|
|||||||
// Create controller for GET request to '/playback/previous'
|
// Create controller for GET request to '/playback/previous'
|
||||||
// Sets timer object to roll mode
|
// Sets timer object to roll mode
|
||||||
export const pbPrevious = async (req, res) => {
|
export const pbPrevious = async (req, res) => {
|
||||||
|
console.log('Calling previous');
|
||||||
global.timer.previous();
|
global.timer.previous();
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
};
|
};
|
||||||
@@ -42,6 +47,7 @@ export const pbPrevious = async (req, res) => {
|
|||||||
// Create controller for GET request to '/playback/next'
|
// Create controller for GET request to '/playback/next'
|
||||||
// Sets timer object to roll mode
|
// Sets timer object to roll mode
|
||||||
export const pbNext = async (req, res) => {
|
export const pbNext = async (req, res) => {
|
||||||
|
console.log('Calling next');
|
||||||
global.timer.next();
|
global.timer.next();
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
};
|
};
|
||||||
@@ -49,8 +55,8 @@ export const pbNext = async (req, res) => {
|
|||||||
// Create controller for GET request to '/playback/unload'
|
// Create controller for GET request to '/playback/unload'
|
||||||
// Unloads any events
|
// Unloads any events
|
||||||
export const pbUnload = async (req, res) => {
|
export const pbUnload = async (req, res) => {
|
||||||
|
console.log('Calling unload');
|
||||||
global.timer.unload();
|
global.timer.unload();
|
||||||
console.log('debug: unload called');
|
|
||||||
|
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
};
|
};
|
||||||
@@ -58,7 +64,7 @@ export const pbUnload = async (req, res) => {
|
|||||||
// Create controller for GET request to '/playback/reload'
|
// Create controller for GET request to '/playback/reload'
|
||||||
// Reloads current event
|
// Reloads current event
|
||||||
export const pbReload = async (req, res) => {
|
export const pbReload = async (req, res) => {
|
||||||
|
console.log('Calling reload');
|
||||||
global.timer.reload();
|
global.timer.reload();
|
||||||
console.log('debug: reload called');
|
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,12 +5,16 @@ export const dbModel = {
|
|||||||
url: '',
|
url: '',
|
||||||
publicInfo: '',
|
publicInfo: '',
|
||||||
backstageInfo: '',
|
backstageInfo: '',
|
||||||
|
endMessage: '',
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
app: 'ontime',
|
app: 'ontime',
|
||||||
version: 1,
|
version: 1,
|
||||||
osc_enabled: false,
|
serverPort: 4001,
|
||||||
osc_port: 8888,
|
oscInPort: 8888,
|
||||||
|
oscOutPort: 9999,
|
||||||
|
oscOutIP: '127.0.0.1',
|
||||||
|
oscEnabled: false,
|
||||||
lock: false,
|
lock: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -2,16 +2,16 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"body-parser": "~1.19.0",
|
"body-parser": "~1.19.0",
|
||||||
|
"dotenv": "^10.0.0",
|
||||||
"express": "~4.17.1",
|
"express": "~4.17.1",
|
||||||
"express-session": "~1.17.1",
|
"express-session": "~1.17.1",
|
||||||
"lowdb": "2.1.0",
|
"lowdb": "3.0.0",
|
||||||
"multer": "^1.4.2",
|
"multer": "^1.4.2",
|
||||||
"nanoid": "^3.1.22",
|
"nanoid": "^3.1.22",
|
||||||
"node-osc": "6.0.2",
|
"node-osc": "6.1.10",
|
||||||
"passport": "~0.4.1",
|
"passport": "~0.4.1",
|
||||||
"passport-local": "~1.0.0",
|
"passport-local": "~1.0.0",
|
||||||
"socket.io": "^4.0.0",
|
"socket.io": "^4.3.1",
|
||||||
"dotenv": "^10.0.0",
|
|
||||||
"universal-analytics": "^0.4.23"
|
"universal-analytics": "^0.4.23"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
dbDownload,
|
dbDownload,
|
||||||
dbUpload,
|
dbUpload,
|
||||||
getInfo,
|
getInfo,
|
||||||
|
postInfo,
|
||||||
dbPathToUpload,
|
dbPathToUpload,
|
||||||
} from '../controllers/ontimeController.js';
|
} from '../controllers/ontimeController.js';
|
||||||
|
|
||||||
@@ -18,5 +19,8 @@ router.post('/db', uploadJson, dbUpload);
|
|||||||
// create route between controller and '/ontime/info' endpoint
|
// create route between controller and '/ontime/info' endpoint
|
||||||
router.get('/info', uploadJson, getInfo);
|
router.get('/info', uploadJson, getInfo);
|
||||||
|
|
||||||
|
// create route between controller and '/ontime/info' endpoint
|
||||||
|
router.post('/info', postInfo);
|
||||||
|
|
||||||
// create route between controller and '/ontime/dbpath' endpoint
|
// create route between controller and '/ontime/dbpath' endpoint
|
||||||
router.post('/dbpath', dbPathToUpload);
|
router.post('/dbpath', dbPathToUpload);
|
||||||
|
|||||||
@@ -1,8 +1,23 @@
|
|||||||
import multer from 'multer';
|
import multer from 'multer';
|
||||||
|
import { statSync, mkdirSync } from 'fs';
|
||||||
|
|
||||||
const storage = multer.diskStorage({
|
const storage = multer.diskStorage({
|
||||||
destination: function (req, file, cb) {
|
destination: function (req, file, cb) {
|
||||||
cb(null, 'uploads/');
|
let newDestination = 'uploads/';
|
||||||
|
let stat = null;
|
||||||
|
try {
|
||||||
|
stat = statSync(newDestination);
|
||||||
|
} catch (err) {
|
||||||
|
mkdirSync(newDestination);
|
||||||
|
}
|
||||||
|
if (stat && !stat.isDirectory()) {
|
||||||
|
throw new Error(
|
||||||
|
'Directory cannot be created because an inode of a different type exists at "' +
|
||||||
|
dest +
|
||||||
|
'"'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
cb(null, newDestination);
|
||||||
},
|
},
|
||||||
filename: function (req, file, cb) {
|
filename: function (req, file, cb) {
|
||||||
cb(null, Date.now() + '--' + file.originalname);
|
cb(null, Date.now() + '--' + file.originalname);
|
||||||
|
|||||||
+352
-410
File diff suppressed because it is too large
Load Diff
+5
-87
@@ -14,7 +14,7 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@babel/highlight" "^7.10.4"
|
"@babel/highlight" "^7.10.4"
|
||||||
|
|
||||||
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.14.5":
|
"@babel/code-frame@^7.12.13", "@babel/code-frame@^7.14.5":
|
||||||
version "7.14.5"
|
version "7.14.5"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.14.5.tgz#23b08d740e83f49c5e59945fbf1b43e80bbf4edb"
|
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.14.5.tgz#23b08d740e83f49c5e59945fbf1b43e80bbf4edb"
|
||||||
integrity sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==
|
integrity sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==
|
||||||
@@ -711,11 +711,6 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.3.tgz#6d327abaa4be34a74e421ed6409a0ae2f47f4c3d"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.3.tgz#6d327abaa4be34a74e421ed6409a0ae2f47f4c3d"
|
||||||
integrity sha512-e6ZowgGJmTuXa3GyaPbTGxX17tnThl2aSSizrFthQ7m9uLGZBXiGhgE55cjRZTF5kjZvYn9EOPOMljdjwbflxw==
|
integrity sha512-e6ZowgGJmTuXa3GyaPbTGxX17tnThl2aSSizrFthQ7m9uLGZBXiGhgE55cjRZTF5kjZvYn9EOPOMljdjwbflxw==
|
||||||
|
|
||||||
"@types/normalize-package-data@^2.4.0":
|
|
||||||
version "2.4.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
|
|
||||||
integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==
|
|
||||||
|
|
||||||
"@types/plist@^3.0.1":
|
"@types/plist@^3.0.1":
|
||||||
version "3.0.2"
|
version "3.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@types/plist/-/plist-3.0.2.tgz#61b3727bba0f5c462fe333542534a0c3e19ccb01"
|
resolved "https://registry.yarnpkg.com/@types/plist/-/plist-3.0.2.tgz#61b3727bba0f5c462fe333542534a0c3e19ccb01"
|
||||||
@@ -1407,21 +1402,6 @@ concat-stream@^1.6.2:
|
|||||||
readable-stream "^2.2.2"
|
readable-stream "^2.2.2"
|
||||||
typedarray "^0.0.6"
|
typedarray "^0.0.6"
|
||||||
|
|
||||||
concurrently@^6.1.0:
|
|
||||||
version "6.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-6.2.0.tgz#587e2cb8afca7234172d8ea55176088632c4c56d"
|
|
||||||
integrity sha512-v9I4Y3wFoXCSY2L73yYgwA9ESrQMpRn80jMcqMgHx720Hecz2GZAvTI6bREVST6lkddNypDKRN22qhK0X8Y00g==
|
|
||||||
dependencies:
|
|
||||||
chalk "^4.1.0"
|
|
||||||
date-fns "^2.16.1"
|
|
||||||
lodash "^4.17.21"
|
|
||||||
read-pkg "^5.2.0"
|
|
||||||
rxjs "^6.6.3"
|
|
||||||
spawn-command "^0.0.2-1"
|
|
||||||
supports-color "^8.1.0"
|
|
||||||
tree-kill "^1.2.2"
|
|
||||||
yargs "^16.2.0"
|
|
||||||
|
|
||||||
config-chain@^1.1.11:
|
config-chain@^1.1.11:
|
||||||
version "1.1.13"
|
version "1.1.13"
|
||||||
resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4"
|
resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4"
|
||||||
@@ -1521,11 +1501,6 @@ data-urls@^2.0.0:
|
|||||||
whatwg-mimetype "^2.3.0"
|
whatwg-mimetype "^2.3.0"
|
||||||
whatwg-url "^8.0.0"
|
whatwg-url "^8.0.0"
|
||||||
|
|
||||||
date-fns@^2.16.1:
|
|
||||||
version "2.22.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.22.1.tgz#1e5af959831ebb1d82992bf67b765052d8f0efc4"
|
|
||||||
integrity sha512-yUFPQjrxEmIsMqlHhAhmxkuH769baF21Kk+nZwZGyrMoyLA+LugaQtC0+Tqf9CBUUULWwUJt6Q5ySI3LJDDCGg==
|
|
||||||
|
|
||||||
debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1:
|
debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1:
|
||||||
version "4.3.1"
|
version "4.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
|
||||||
@@ -3278,11 +3253,6 @@ json-parse-better-errors@^1.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
|
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
|
||||||
integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==
|
integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==
|
||||||
|
|
||||||
json-parse-even-better-errors@^2.3.0:
|
|
||||||
version "2.3.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
|
|
||||||
integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
|
|
||||||
|
|
||||||
json-schema-traverse@^0.4.1:
|
json-schema-traverse@^0.4.1:
|
||||||
version "0.4.1"
|
version "0.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
|
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
|
||||||
@@ -3398,11 +3368,6 @@ levn@~0.3.0:
|
|||||||
prelude-ls "~1.1.2"
|
prelude-ls "~1.1.2"
|
||||||
type-check "~0.3.2"
|
type-check "~0.3.2"
|
||||||
|
|
||||||
lines-and-columns@^1.1.6:
|
|
||||||
version "1.1.6"
|
|
||||||
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
|
|
||||||
integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=
|
|
||||||
|
|
||||||
load-json-file@^4.0.0:
|
load-json-file@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b"
|
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b"
|
||||||
@@ -3443,7 +3408,7 @@ lodash.truncate@^4.4.2:
|
|||||||
resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
|
resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
|
||||||
integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=
|
integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=
|
||||||
|
|
||||||
lodash@^4.17.10, lodash@^4.17.15, lodash@^4.17.21, lodash@^4.7.0:
|
lodash@^4.17.10, lodash@^4.17.15, lodash@^4.7.0:
|
||||||
version "4.17.21"
|
version "4.17.21"
|
||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||||
@@ -3592,7 +3557,7 @@ node-releases@^1.1.71:
|
|||||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.73.tgz#dd4e81ddd5277ff846b80b52bb40c49edf7a7b20"
|
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.73.tgz#dd4e81ddd5277ff846b80b52bb40c49edf7a7b20"
|
||||||
integrity sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==
|
integrity sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==
|
||||||
|
|
||||||
normalize-package-data@^2.3.2, normalize-package-data@^2.5.0:
|
normalize-package-data@^2.3.2:
|
||||||
version "2.5.0"
|
version "2.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
|
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
|
||||||
integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==
|
integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==
|
||||||
@@ -3796,16 +3761,6 @@ parse-json@^4.0.0:
|
|||||||
error-ex "^1.3.1"
|
error-ex "^1.3.1"
|
||||||
json-parse-better-errors "^1.0.1"
|
json-parse-better-errors "^1.0.1"
|
||||||
|
|
||||||
parse-json@^5.0.0:
|
|
||||||
version "5.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
|
|
||||||
integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==
|
|
||||||
dependencies:
|
|
||||||
"@babel/code-frame" "^7.0.0"
|
|
||||||
error-ex "^1.3.1"
|
|
||||||
json-parse-even-better-errors "^2.3.0"
|
|
||||||
lines-and-columns "^1.1.6"
|
|
||||||
|
|
||||||
parse5@6.0.1:
|
parse5@6.0.1:
|
||||||
version "6.0.1"
|
version "6.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
|
resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
|
||||||
@@ -4025,16 +3980,6 @@ read-pkg@^3.0.0:
|
|||||||
normalize-package-data "^2.3.2"
|
normalize-package-data "^2.3.2"
|
||||||
path-type "^3.0.0"
|
path-type "^3.0.0"
|
||||||
|
|
||||||
read-pkg@^5.2.0:
|
|
||||||
version "5.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc"
|
|
||||||
integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==
|
|
||||||
dependencies:
|
|
||||||
"@types/normalize-package-data" "^2.4.0"
|
|
||||||
normalize-package-data "^2.5.0"
|
|
||||||
parse-json "^5.0.0"
|
|
||||||
type-fest "^0.6.0"
|
|
||||||
|
|
||||||
readable-stream@^2.2.2:
|
readable-stream@^2.2.2:
|
||||||
version "2.3.7"
|
version "2.3.7"
|
||||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
|
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
|
||||||
@@ -4149,13 +4094,6 @@ roarr@^2.15.3:
|
|||||||
semver-compare "^1.0.0"
|
semver-compare "^1.0.0"
|
||||||
sprintf-js "^1.1.2"
|
sprintf-js "^1.1.2"
|
||||||
|
|
||||||
rxjs@^6.6.3:
|
|
||||||
version "6.6.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9"
|
|
||||||
integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==
|
|
||||||
dependencies:
|
|
||||||
tslib "^1.9.0"
|
|
||||||
|
|
||||||
safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||||
version "5.1.2"
|
version "5.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||||
@@ -4301,11 +4239,6 @@ source-map@^0.7.3:
|
|||||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
|
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
|
||||||
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
|
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
|
||||||
|
|
||||||
spawn-command@^0.0.2-1:
|
|
||||||
version "0.0.2-1"
|
|
||||||
resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2-1.tgz#62f5e9466981c1b796dc5929937e11c9c6921bd0"
|
|
||||||
integrity sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=
|
|
||||||
|
|
||||||
spdx-correct@^3.0.0:
|
spdx-correct@^3.0.0:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9"
|
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9"
|
||||||
@@ -4492,7 +4425,7 @@ supports-color@^7.0.0, supports-color@^7.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
has-flag "^4.0.0"
|
has-flag "^4.0.0"
|
||||||
|
|
||||||
supports-color@^8.0.0, supports-color@^8.1.0:
|
supports-color@^8.0.0:
|
||||||
version "8.1.1"
|
version "8.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c"
|
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c"
|
||||||
integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
|
integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
|
||||||
@@ -4611,11 +4544,6 @@ tr46@^2.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
punycode "^2.1.1"
|
punycode "^2.1.1"
|
||||||
|
|
||||||
tree-kill@^1.2.2:
|
|
||||||
version "1.2.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
|
|
||||||
integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==
|
|
||||||
|
|
||||||
truncate-utf8-bytes@^1.0.0:
|
truncate-utf8-bytes@^1.0.0:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz#405923909592d56f78a5818434b0b78489ca5f2b"
|
resolved "https://registry.yarnpkg.com/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz#405923909592d56f78a5818434b0b78489ca5f2b"
|
||||||
@@ -4633,11 +4561,6 @@ tsconfig-paths@^3.9.0:
|
|||||||
minimist "^1.2.0"
|
minimist "^1.2.0"
|
||||||
strip-bom "^3.0.0"
|
strip-bom "^3.0.0"
|
||||||
|
|
||||||
tslib@^1.9.0:
|
|
||||||
version "1.14.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
|
||||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
|
||||||
|
|
||||||
tunnel@^0.0.6:
|
tunnel@^0.0.6:
|
||||||
version "0.0.6"
|
version "0.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c"
|
resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c"
|
||||||
@@ -4677,11 +4600,6 @@ type-fest@^0.21.3:
|
|||||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37"
|
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37"
|
||||||
integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==
|
integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==
|
||||||
|
|
||||||
type-fest@^0.6.0:
|
|
||||||
version "0.6.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b"
|
|
||||||
integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==
|
|
||||||
|
|
||||||
typedarray-to-buffer@^3.1.5:
|
typedarray-to-buffer@^3.1.5:
|
||||||
version "3.1.5"
|
version "3.1.5"
|
||||||
resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080"
|
resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080"
|
||||||
@@ -4952,7 +4870,7 @@ yargs-parser@^20.2.2:
|
|||||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.7.tgz#61df85c113edfb5a7a4e36eb8aa60ef423cbc90a"
|
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.7.tgz#61df85c113edfb5a7a4e36eb8aa60ef423cbc90a"
|
||||||
integrity sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==
|
integrity sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==
|
||||||
|
|
||||||
yargs@^16.0.3, yargs@^16.2.0:
|
yargs@^16.0.3:
|
||||||
version "16.2.0"
|
version "16.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
|
resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
|
||||||
integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==
|
integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==
|
||||||
|
|||||||
Reference in New Issue
Block a user