mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-07 16:33:51 +00:00
+2
-2
@@ -29,7 +29,7 @@ TODO.md
|
||||
# working stuff
|
||||
_SS/
|
||||
.vscode/launch.json
|
||||
db.json
|
||||
.eslintrc.json
|
||||
db.json
|
||||
db backup.json
|
||||
server/src/data/db.json
|
||||
server/src/models/db.json
|
||||
|
||||
+6
-7
@@ -3,27 +3,26 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@chakra-ui/react": "^1.4.1",
|
||||
"@emotion/react": "^11",
|
||||
"@emotion/styled": "^11",
|
||||
"@chakra-ui/react": "^1.6.10",
|
||||
"@emotion/react": "^11.5.0",
|
||||
"@emotion/styled": "^11.3.0",
|
||||
"@testing-library/jest-dom": "^5.11.4",
|
||||
"@testing-library/react": "^11.1.0",
|
||||
"@testing-library/user-event": "^12.1.10",
|
||||
"axios": "^0.21.1",
|
||||
"date-fns": "^2.20.1",
|
||||
"framer-motion": "^4.1.6",
|
||||
"jotai": "^0.16.5",
|
||||
"react": "^17.0.1",
|
||||
"react-beautiful-dnd": "^13.1.0",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-fast-compare": "^3.2.0",
|
||||
"react-icons": "^4.2.0",
|
||||
"react-qr-code": "^1.1.1",
|
||||
"react-icons": "^4.3.1",
|
||||
"react-qr-code": "^2.0.2",
|
||||
"react-query": "^3.13.5",
|
||||
"react-router": "^5.2.0",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-scripts": "4.0.3",
|
||||
"socket.io-client": "^4.0.1",
|
||||
"socket.io-client": "^4.3.2",
|
||||
"tinykeys": "^1.1.2",
|
||||
"typeface-open-sans": "^1.1.13",
|
||||
"web-vitals": "^1.0.1"
|
||||
|
||||
@@ -5,14 +5,21 @@
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#121212" />
|
||||
<meta
|
||||
name="onsite"
|
||||
content="onsite - event timer manager"
|
||||
/>
|
||||
<meta name="onsite" content="onsite - event timer manager" />
|
||||
<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 rel="icon" type="image/png" sizes="16x16" href="%PUBLIC_URL%/favicon-16x16.png" />
|
||||
<link rel="manifest" href="%PUBLIC_URL%/site.webmanifest">
|
||||
<link
|
||||
rel="icon"
|
||||
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
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
|
||||
@@ -14,3 +14,7 @@ html,
|
||||
user-select: none;
|
||||
-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 './App.css';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import SocketProvider from 'app/context/socketContext';
|
||||
import withSocket from 'features/viewers/ViewWrapper';
|
||||
import tinykeys from 'tinykeys';
|
||||
|
||||
const Editor = lazy(() => import('features/editors/Editor'));
|
||||
const PresenterView = lazy(() =>
|
||||
@@ -36,6 +37,22 @@ const SLowerThird = withSocket(Lower);
|
||||
const SPip = withSocket(Pip);
|
||||
|
||||
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 (
|
||||
<SocketProvider>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
@@ -45,6 +62,7 @@ function App() {
|
||||
<Route exact path='/' component={SSpeaker} />
|
||||
<Route exact path='/sm' component={SStageManager} />
|
||||
<Route exact path='/speaker' component={SSpeaker} />
|
||||
<Route exact path='/stage' component={SSpeaker} />
|
||||
<Route exact path='/speakersimple' component={SSpeakerSimple} />
|
||||
<Route exact path='/editor' component={Editor} />
|
||||
<Route exact path='/public' component={SPublic} />
|
||||
|
||||
@@ -3,6 +3,11 @@ import { ontimeURL } from './apiConstants';
|
||||
|
||||
export const ontimePlaceholderInfo = {
|
||||
networkInterfaces: [],
|
||||
version: '',
|
||||
serverPort: 4001,
|
||||
oscInPort: '',
|
||||
oscOutPort: '',
|
||||
oscOutIP: '',
|
||||
};
|
||||
|
||||
export const getInfo = async () => {
|
||||
@@ -10,6 +15,11 @@ export const getInfo = async () => {
|
||||
return res.data;
|
||||
};
|
||||
|
||||
export const postInfo = async (data) => {
|
||||
const res = await axios.post(ontimeURL + '/info', data);
|
||||
return res;
|
||||
};
|
||||
|
||||
export const downloadEvents = async () => {
|
||||
await axios({
|
||||
url: ontimeURL + '/db',
|
||||
@@ -17,7 +27,6 @@ export const downloadEvents = async () => {
|
||||
responseType: 'blob', // important
|
||||
}).then((response) => {
|
||||
let headerLine = response.headers['Content-Disposition'];
|
||||
console.log(response);
|
||||
let filename = 'events.json';
|
||||
|
||||
// try and get the filename from the response
|
||||
|
||||
@@ -7,9 +7,11 @@ export default function AddIconBtn(props) {
|
||||
<IconButton
|
||||
size={props.size || 'xs'}
|
||||
icon={<FiPlus />}
|
||||
colorScheme='blue'
|
||||
onClick={clickhandler}
|
||||
_expanded={{ bg: 'orange.300', color: 'white' }}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
backgroundColor={'orange.200'}
|
||||
color={'orange.500'}
|
||||
onClick={clickhandler}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { IconButton } from '@chakra-ui/button';
|
||||
import { FiCornerLeftUp } from 'react-icons/fi';
|
||||
import { FiXOctagon } from 'react-icons/fi';
|
||||
|
||||
export default function UnloadIconBtn(props) {
|
||||
const { clickhandler, ...rest } = props;
|
||||
return (
|
||||
<IconButton
|
||||
icon={<FiCornerLeftUp />}
|
||||
icon={<FiXOctagon />}
|
||||
colorScheme='whiteAlpha'
|
||||
backgroundColor='#ffffff05'
|
||||
variant='outline'
|
||||
|
||||
@@ -7,7 +7,6 @@ export default function Paginator(props) {
|
||||
const { events, selectedId } = props;
|
||||
const LIMIT_PER_PAGE = props.limit || 8;
|
||||
const SCROLL_TIME = props.time * 1000 || 10000;
|
||||
const SCROLL_PAST = false;
|
||||
const [numEvents, setNumEvents] = useState(0);
|
||||
const [page, setPage] = useState([]);
|
||||
const [pages, setPages] = useState(0);
|
||||
|
||||
@@ -37,6 +37,10 @@
|
||||
|
||||
.entryTitle {
|
||||
align-self: center;
|
||||
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.entryPast,
|
||||
|
||||
@@ -30,22 +30,29 @@ const Playback = ({ playback, selectedId, playbackControl }) => {
|
||||
/>
|
||||
<RollIconBtn
|
||||
active={playback === 'roll'}
|
||||
disabled={playback === 'roll'}
|
||||
clickhandler={() => playbackControl('roll')}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Transport = ({ selectedId, playbackControl }) => {
|
||||
const Transport = ({ playback, selectedId, playbackControl }) => {
|
||||
return (
|
||||
<div className={style.playbackContainer}>
|
||||
<PrevIconBtn clickhandler={() => playbackControl('previous')} />
|
||||
<NextIconBtn clickhandler={() => playbackControl('next')} />
|
||||
<UnloadIconBtn clickhandler={() => playbackControl('unload')} />
|
||||
<PrevIconBtn
|
||||
clickhandler={() => playbackControl('previous')}
|
||||
disabled={playback === 'roll'}
|
||||
/>
|
||||
<NextIconBtn
|
||||
clickhandler={() => playbackControl('next')}
|
||||
disabled={playback === 'roll'}
|
||||
/>
|
||||
<ReloadIconButton
|
||||
clickhandler={() => playbackControl('reload')}
|
||||
disabled={!selectedId}
|
||||
/>
|
||||
<UnloadIconBtn clickhandler={() => playbackControl('unload')} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -60,6 +67,7 @@ const PlaybackButtons = (props) => {
|
||||
playbackControl={props.playbackControl}
|
||||
/>
|
||||
<Transport
|
||||
playback={playback}
|
||||
selectedId={selectedId}
|
||||
playbackControl={props.playbackControl}
|
||||
/>
|
||||
|
||||
@@ -12,6 +12,7 @@ export default function PlaybackControl() {
|
||||
running: null,
|
||||
startedAt: null,
|
||||
expectedFinish: null,
|
||||
secondary: null,
|
||||
});
|
||||
const [selectedId, setSelectedId] = useState(null);
|
||||
|
||||
@@ -20,6 +21,7 @@ export default function PlaybackControl() {
|
||||
running: null,
|
||||
startedAt: null,
|
||||
expectedFinish: null,
|
||||
secondary: null,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -105,6 +105,11 @@
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.rolltag {
|
||||
color: #2b6cb0;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.playbackContainer {
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import style from './PlaybackControl.module.css';
|
||||
import Countdown from 'common/components/countdown/Countdown';
|
||||
import { stringFromMillis } from 'common/dateConfig';
|
||||
import { Tooltip } from '@chakra-ui/react';
|
||||
import { Button } from '@chakra-ui/button';
|
||||
import { memo } from 'react';
|
||||
|
||||
@@ -9,7 +10,8 @@ const areEqual = (prevProps, nextProps) => {
|
||||
prevProps.timer.running === nextProps.timer.running &&
|
||||
prevProps.timer.expectedFinish === nextProps.timer.expectedFinish &&
|
||||
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 finish = stringFromMillis(timer.expectedFinish, true);
|
||||
const isNegative = timer.running < 0;
|
||||
const isDelayed = false;
|
||||
const isRolling = playback === 'roll';
|
||||
const isWaiting = timer.secondary > 0 && timer.running == null;
|
||||
|
||||
const incrementProps = {
|
||||
size: 'sm',
|
||||
@@ -33,23 +35,38 @@ const PlaybackTimer = (props) => {
|
||||
<>
|
||||
<div className={style.timeContainer}>
|
||||
<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
|
||||
className={isNegative ? style.indNegativeActive : style.indNegative}
|
||||
/>
|
||||
<div className={style.indDelay} />
|
||||
</div>
|
||||
<div className={style.timer}>
|
||||
<Countdown time={timer?.running} small negative={isNegative} />
|
||||
</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>
|
||||
<Countdown
|
||||
time={isWaiting ? timer.secondary : timer.running}
|
||||
small
|
||||
negative={isNegative}
|
||||
/>
|
||||
</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}>
|
||||
<Button
|
||||
{...incrementProps}
|
||||
|
||||
@@ -114,7 +114,7 @@ h1 {
|
||||
}
|
||||
|
||||
.content {
|
||||
padding-top: 1.5em;
|
||||
padding-top: 0.5em;
|
||||
}
|
||||
|
||||
.cornerButtonContainer {
|
||||
|
||||
@@ -28,7 +28,7 @@ export default function ActionButtons(props) {
|
||||
onClick={() => actionHandler('event')}
|
||||
isDisabled={!showAdd}
|
||||
>
|
||||
Event next
|
||||
Add Event after
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem
|
||||
@@ -36,14 +36,14 @@ export default function ActionButtons(props) {
|
||||
onClick={() => actionHandler('delay')}
|
||||
isDisabled={!showDelay}
|
||||
>
|
||||
Delay next
|
||||
Add Delay after
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={<FiMinusCircle />}
|
||||
onClick={() => actionHandler('block')}
|
||||
isDisabled={!showBlock}
|
||||
>
|
||||
Block next
|
||||
Add Block after
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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 { Draggable } from 'react-beautiful-dnd';
|
||||
import EventTimes from 'common/components/eventTimes/EventTimes';
|
||||
@@ -86,13 +86,6 @@ const ExpandedBlock = (props) => {
|
||||
{`/ontime/goto ${eventIndex + 1} << OSC >> /ontime/gotoid ${oscid}`}
|
||||
</span>
|
||||
</div>
|
||||
<Icon
|
||||
className={style.more}
|
||||
as={FiChevronUp}
|
||||
marginTop='0.2em'
|
||||
gridArea='more'
|
||||
onClick={() => props.setCollapsed(true)}
|
||||
/>
|
||||
<div className={style.actionOverlay}>
|
||||
<PublicIconBtn actionHandler={actionHandler} active={data.isPublic} />
|
||||
<ActionButtons
|
||||
@@ -139,13 +132,6 @@ const CollapsedBlock = (props) => {
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<Icon
|
||||
className={style.more}
|
||||
as={FiChevronDown}
|
||||
marginTop='0.2em'
|
||||
gridArea='more'
|
||||
onClick={() => props.setCollapsed(false)}
|
||||
/>
|
||||
<div className={style.actionOverlay}>
|
||||
<PublicIconBtn actionHandler={actionHandler} active={data.isPublic} />
|
||||
<ActionButtons
|
||||
@@ -176,6 +162,7 @@ export default function EventBlock(props) {
|
||||
const delayValue = delay > 0 ? millisToMinutes(delay) : null;
|
||||
|
||||
const handleCollapse = (isCollapsed) => {
|
||||
console.log('setting collapsed', isCollapsed);
|
||||
setCollapsed({ [data.id]: isCollapsed });
|
||||
};
|
||||
|
||||
@@ -187,6 +174,11 @@ export default function EventBlock(props) {
|
||||
{...provided.draggableProps}
|
||||
ref={provided.innerRef}
|
||||
>
|
||||
<Icon
|
||||
className={collapsed ? style.moreCollapsed : style.moreExpanded}
|
||||
as={FiChevronUp}
|
||||
onClick={() => handleCollapse(!collapsed)}
|
||||
/>
|
||||
{collapsed ? (
|
||||
<CollapsedBlock
|
||||
provided={provided}
|
||||
@@ -195,7 +187,6 @@ export default function EventBlock(props) {
|
||||
delay={delay}
|
||||
delayValue={delayValue}
|
||||
actionHandler={actionHandler}
|
||||
setCollapsed={handleCollapse}
|
||||
/>
|
||||
) : (
|
||||
<ExpandedBlock
|
||||
@@ -206,7 +197,6 @@ export default function EventBlock(props) {
|
||||
delay={delay}
|
||||
delayValue={delayValue}
|
||||
actionHandler={actionHandler}
|
||||
setCollapsed={handleCollapse}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -144,6 +144,9 @@
|
||||
border-radius: 4px;
|
||||
width: 100%;
|
||||
display: block;
|
||||
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.oscLabel {
|
||||
@@ -157,9 +160,24 @@
|
||||
|
||||
/* ================ MORE ================ */
|
||||
|
||||
.more {
|
||||
.moreExpanded,
|
||||
.moreCollapsed {
|
||||
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 ================ */
|
||||
|
||||
.actionOverlay {
|
||||
|
||||
@@ -127,7 +127,6 @@ export default function EventList(props) {
|
||||
});
|
||||
};
|
||||
|
||||
console.log('EventList: events in event list', events);
|
||||
let cumulativeDelay = 0;
|
||||
let eventIndex = -1;
|
||||
|
||||
|
||||
@@ -35,12 +35,10 @@ export default function EventListWrapper() {
|
||||
|
||||
// Snapshot the previous value
|
||||
let previousEvents = queryClient.getQueryData(EVENTS_TABLE);
|
||||
console.log('debug', previousEvents);
|
||||
if (previousEvents == null) {
|
||||
refetch();
|
||||
previousEvents = queryClient.getQueryData(EVENTS_TABLE);
|
||||
}
|
||||
console.log('debug 2', previousEvents);
|
||||
|
||||
// optimistically update object, temp ID until refetch
|
||||
let optimistic = [...previousEvents];
|
||||
@@ -241,52 +239,40 @@ export default function EventListWrapper() {
|
||||
switch (action) {
|
||||
case 'add':
|
||||
try {
|
||||
let t = Date.now();
|
||||
await addEvent.mutateAsync(payload);
|
||||
console.log('debug m add', Date.now() - t);
|
||||
} catch (error) {
|
||||
showErrorToast('Error creating event', error.message);
|
||||
}
|
||||
break;
|
||||
case 'update':
|
||||
try {
|
||||
let t = Date.now();
|
||||
await updateEvent.mutateAsync(payload);
|
||||
console.log('debug m update', Date.now() - t);
|
||||
} catch (error) {
|
||||
showErrorToast('Error updating event', error.message);
|
||||
}
|
||||
break;
|
||||
case 'patch':
|
||||
try {
|
||||
let t = Date.now();
|
||||
await patchEvent.mutateAsync(payload);
|
||||
console.log('debug m patch', Date.now() - t);
|
||||
} catch (error) {
|
||||
showErrorToast('Error updating event', error.message);
|
||||
}
|
||||
break;
|
||||
case 'delete':
|
||||
try {
|
||||
let t = Date.now();
|
||||
await deleteEvent.mutateAsync(payload);
|
||||
console.log('debug m delete', Date.now() - t);
|
||||
} catch (error) {
|
||||
showErrorToast('Error deleting event', error.message);
|
||||
}
|
||||
break;
|
||||
case 'reorder':
|
||||
try {
|
||||
let t = Date.now();
|
||||
await reorderEvent.mutateAsync(payload);
|
||||
console.log('debug m reorder', Date.now() - t);
|
||||
} catch (error) {
|
||||
showErrorToast('Error reordering event', error.message);
|
||||
}
|
||||
break;
|
||||
case 'applyDelay':
|
||||
let t = Date.now();
|
||||
|
||||
// if delay <= 0 delete delay and next block
|
||||
if (payload.duration <= 0) {
|
||||
try {
|
||||
@@ -309,14 +295,12 @@ export default function EventListWrapper() {
|
||||
showErrorToast('Error applying delay', error.message);
|
||||
}
|
||||
} else {
|
||||
console.log('debug applydelay', payload.id);
|
||||
try {
|
||||
await applyDelay.mutateAsync(payload.id);
|
||||
} catch (error) {
|
||||
showErrorToast('Error applying delay', error.message);
|
||||
}
|
||||
}
|
||||
console.log('debug m apply', Date.now() - t);
|
||||
break;
|
||||
|
||||
case 'collapseall':
|
||||
@@ -330,9 +314,7 @@ export default function EventListWrapper() {
|
||||
|
||||
case 'deleteall':
|
||||
try {
|
||||
let t = Date.now();
|
||||
await deleteAllEvents.mutateAsync();
|
||||
console.log('debug m deleteall', Date.now() - t);
|
||||
} catch (error) {
|
||||
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 style from './Info.module.css';
|
||||
import InfoTitle from './InfoTitle';
|
||||
import InfoLogger from './InfoLogger';
|
||||
import InfoNif from './InfoNif';
|
||||
import { useState } from 'react';
|
||||
|
||||
export default function Info() {
|
||||
const socket = useSocket();
|
||||
@@ -21,6 +20,8 @@ export default function Info() {
|
||||
const [selected, setSelected] = useState('No events');
|
||||
const logData = [];
|
||||
|
||||
console.log(`titles`, titles);
|
||||
|
||||
// handle incoming messages
|
||||
useEffect(() => {
|
||||
if (socket == null) return;
|
||||
|
||||
@@ -24,6 +24,12 @@
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.labelContainer {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.label,
|
||||
.emptyLabel {
|
||||
padding: 0;
|
||||
@@ -47,6 +53,8 @@
|
||||
|
||||
.notes {
|
||||
color: #d69e2e;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.if {
|
||||
|
||||
@@ -11,12 +11,13 @@ export default function InfoNif() {
|
||||
placeholderData: ontimePlaceholderInfo,
|
||||
});
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
|
||||
const baseURL = `http://__IP__:${4001}`;
|
||||
const baseURL = 'http://__IP__:4001';
|
||||
|
||||
const handleLink = (url) => {
|
||||
if (window.process.type === 'renderer') {
|
||||
if (window.process?.type === 'renderer') {
|
||||
window.ipcRenderer.send('send-to-link', url);
|
||||
} else {
|
||||
window.open(url);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -26,19 +26,19 @@ export default function InfoTitle(props) {
|
||||
|
||||
{!collapsed && (
|
||||
<>
|
||||
<div>
|
||||
<div className={style.labelContainer}>
|
||||
<span className={noTitl ? style.emptyLabel : style.label}>
|
||||
Title
|
||||
</span>
|
||||
{data.title}
|
||||
</div>
|
||||
<div>
|
||||
<div className={style.labelContainer}>
|
||||
<span className={noPres ? style.emptyLabel : style.label}>
|
||||
Presenter
|
||||
</span>
|
||||
{data.presenter}
|
||||
</div>
|
||||
<div>
|
||||
<div className={style.labelContainer}>
|
||||
<span className={noSubt ? style.emptyLabel : style.label}>
|
||||
Subtitle
|
||||
</span>
|
||||
|
||||
@@ -24,16 +24,16 @@ export default function MenuActionButtons(props) {
|
||||
/>
|
||||
<MenuList style={menuStyle}>
|
||||
<MenuItem icon={<FiPlus />} onClick={() => actionHandler('event')}>
|
||||
Event first
|
||||
Add Event first
|
||||
</MenuItem>
|
||||
<MenuItem icon={<FiClock />} onClick={() => actionHandler('delay')}>
|
||||
Delay first
|
||||
Add Delay first
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={<FiMinusCircle />}
|
||||
onClick={() => actionHandler('block')}
|
||||
>
|
||||
Block first
|
||||
Add Block first
|
||||
</MenuItem>
|
||||
<Divider />
|
||||
<MenuItem
|
||||
|
||||
@@ -45,7 +45,12 @@ export default function MenuBar(props) {
|
||||
|
||||
const handleIPC = (action) => {
|
||||
// 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') {
|
||||
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 { useFetch } from 'app/hooks/useFetch';
|
||||
import { EVENT_TABLE } from 'app/api/apiConstants';
|
||||
import style from './Modals.module.css';
|
||||
|
||||
export default function SettingsModal() {
|
||||
const { data, status, isError } = useFetch(EVENT_TABLE, fetchEvent);
|
||||
const { data, status } = useFetch(EVENT_TABLE, fetchEvent);
|
||||
const [formData, setFormData] = useState({
|
||||
title: '',
|
||||
url: '',
|
||||
publicInfo: '',
|
||||
backstageInfo: '',
|
||||
endMessage: '',
|
||||
});
|
||||
const [changed, setChanged] = useState(false);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -29,6 +32,7 @@ export default function SettingsModal() {
|
||||
url: data.url,
|
||||
publicInfo: data.publicInfo,
|
||||
backstageInfo: data.backstageInfo,
|
||||
endMessage: data.endMessage,
|
||||
});
|
||||
}, [data]);
|
||||
|
||||
@@ -36,41 +40,47 @@ export default function SettingsModal() {
|
||||
event.preventDefault();
|
||||
setSubmitting(true);
|
||||
|
||||
await postEvent(formData).then(setSubmitting(false));
|
||||
await postEvent(formData);
|
||||
|
||||
setChanged(false);
|
||||
setSubmitting(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<form onSubmit={submitHandler}>
|
||||
<ModalBody>
|
||||
<ModalBody className={style.modalBody}>
|
||||
{status === 'success' && (
|
||||
<>
|
||||
<p className={style.notes}>
|
||||
Options related to the running event
|
||||
<br />
|
||||
Affect rendered views
|
||||
</p>
|
||||
|
||||
<FormControl id='title'>
|
||||
<FormLabel
|
||||
style={{ fontWeight: 400, paddingTop: '1em' }}
|
||||
htmlFor='title'
|
||||
>
|
||||
Event Title
|
||||
</FormLabel>
|
||||
<FormLabel htmlFor='title'>Event Title</FormLabel>
|
||||
<Input
|
||||
size='sm'
|
||||
maxLength={35}
|
||||
name='title'
|
||||
placeholder='Event Title'
|
||||
autoComplete='off'
|
||||
value={formData.title}
|
||||
onChange={(event) =>
|
||||
setFormData({ ...formData, title: event.target.value })
|
||||
}
|
||||
onChange={(event) => {
|
||||
setChanged(true);
|
||||
setFormData({ ...formData, title: event.target.value });
|
||||
}}
|
||||
isDisabled={submitting}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormControl id='url'>
|
||||
<FormLabel
|
||||
style={{ fontWeight: 400, paddingTop: '1em' }}
|
||||
htmlFor='url'
|
||||
>
|
||||
<FormLabel htmlFor='url'>
|
||||
Event URL
|
||||
<span className={style.notes}>
|
||||
(shown as a QR code in some views)
|
||||
</span>
|
||||
</FormLabel>
|
||||
<Input
|
||||
size='sm'
|
||||
@@ -78,55 +88,73 @@ export default function SettingsModal() {
|
||||
placeholder='www.onsite.no'
|
||||
autoComplete='off'
|
||||
value={formData.url}
|
||||
onChange={(event) =>
|
||||
setFormData({ ...formData, url: event.target.value })
|
||||
}
|
||||
onChange={(event) => {
|
||||
setChanged(true);
|
||||
setFormData({ ...formData, url: event.target.value });
|
||||
}}
|
||||
isDisabled={submitting}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormControl id='pubInfo'>
|
||||
<FormLabel
|
||||
style={{ fontWeight: 400, paddingTop: '1em' }}
|
||||
htmlFor='pubInfo'
|
||||
>
|
||||
Public Info
|
||||
</FormLabel>
|
||||
<FormLabel htmlFor='pubInfo'>Public Info</FormLabel>
|
||||
<Textarea
|
||||
size='sm'
|
||||
name='pubInfo'
|
||||
placeholder='Information to be shown on public screens'
|
||||
autoComplete='off'
|
||||
value={formData.publicInfo}
|
||||
onChange={(event) =>
|
||||
onChange={(event) => {
|
||||
setChanged(true);
|
||||
setFormData({
|
||||
...formData,
|
||||
publicInfo: event.target.value,
|
||||
})
|
||||
}
|
||||
});
|
||||
}}
|
||||
isDisabled={submitting}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormControl id='backstageInfo'>
|
||||
<FormLabel
|
||||
style={{ fontWeight: 400, paddingTop: '1em' }}
|
||||
htmlFor='backstageInfo'
|
||||
>
|
||||
Backstage Info
|
||||
</FormLabel>
|
||||
<FormLabel htmlFor='backstageInfo'>Backstage Info</FormLabel>
|
||||
<Textarea
|
||||
size='sm'
|
||||
name='backstageInfo'
|
||||
placeholder='Information to be shown on backstage screens'
|
||||
autoComplete='off'
|
||||
value={formData.backstageInfo}
|
||||
onChange={(event) =>
|
||||
onChange={(event) => {
|
||||
setChanged(true);
|
||||
setFormData({
|
||||
...formData,
|
||||
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}
|
||||
/>
|
||||
</FormControl>
|
||||
@@ -134,9 +162,9 @@ export default function SettingsModal() {
|
||||
)}
|
||||
<Button
|
||||
colorScheme='blue'
|
||||
mr={3}
|
||||
isLoading={submitting}
|
||||
type='submit'
|
||||
isLoading={submitting}
|
||||
disabled={!changed}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
@@ -7,7 +7,9 @@ import {
|
||||
ModalOverlay,
|
||||
} from '@chakra-ui/modal';
|
||||
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) {
|
||||
const { isOpen, onClose } = props;
|
||||
@@ -31,19 +33,13 @@ export default function ModalManager(props) {
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
<TabPanel>
|
||||
<SettingsModal onClose={onClose} />
|
||||
<EventSettingsModal />
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<p>Application settings will be here!</p>
|
||||
<p>Set port</p>
|
||||
<p>Set end message</p>
|
||||
<AppSettingsModal />
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<p>You will be able to define URL aliases here!</p>
|
||||
<p>List existing aliases</p>
|
||||
<p>List existing aliases</p>
|
||||
<p>List existing aliases</p>
|
||||
<p>Add new alias input + button</p>
|
||||
<AliasesModal />
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
@@ -54,5 +50,5 @@ export default function ModalManager(props) {
|
||||
|
||||
ModalManager.propTypes = {
|
||||
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: '',
|
||||
publicInfo: '',
|
||||
backstageInfo: '',
|
||||
endMessage: '',
|
||||
});
|
||||
const [playback, setPlayback] = useState(null);
|
||||
|
||||
|
||||
@@ -142,7 +142,7 @@ export default function StageManager(props) {
|
||||
<div className={style.qr}>
|
||||
{general.url != null && general.url !== '' && (
|
||||
<QRCode
|
||||
value='www.carlosvalente.com'
|
||||
value={general.url}
|
||||
size={window.innerWidth / 12}
|
||||
level='L'
|
||||
/>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
font-weight: 300;
|
||||
display: grid;
|
||||
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:
|
||||
' titl titl titl . schd'
|
||||
' now now now . schd'
|
||||
@@ -55,6 +55,8 @@
|
||||
.todayContainer {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
padding: 1vh 2vw;
|
||||
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.clockContainer,
|
||||
@@ -163,7 +165,7 @@
|
||||
|
||||
.countdownContainer {
|
||||
grid-area: clck;
|
||||
border-radius: 1vw 1vw 0 0;
|
||||
border-radius: 1vw 1vw 0 0;
|
||||
}
|
||||
|
||||
.clock {
|
||||
|
||||
@@ -109,7 +109,7 @@ export default function Public(props) {
|
||||
<div className={style.qr}>
|
||||
{general.url != null && general.url !== '' && (
|
||||
<QRCode
|
||||
value='www.carlosvalente.com'
|
||||
value={general.url}
|
||||
size={window.innerWidth / 12}
|
||||
level='L'
|
||||
/>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
font-weight: 300;
|
||||
display: grid;
|
||||
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:
|
||||
' titl titl titl . schd'
|
||||
' now now now . schd'
|
||||
@@ -55,6 +55,8 @@
|
||||
.todayContainer {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
padding: 1vh 2vw;
|
||||
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.clockContainer,
|
||||
|
||||
@@ -7,7 +7,7 @@ import TitleCard from 'common/components/views/TitleCard';
|
||||
import style from './PresenterView.module.css';
|
||||
|
||||
export default function PresenterView(props) {
|
||||
const { pres, title, time } = props;
|
||||
const { general, pres, title, time } = props;
|
||||
|
||||
// Set window title
|
||||
useEffect(() => {
|
||||
@@ -18,6 +18,14 @@ export default function PresenterView(props) {
|
||||
const isPlaying = time.playstate !== 'pause';
|
||||
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
|
||||
const titleVariants = {
|
||||
hidden: {
|
||||
@@ -57,7 +65,7 @@ export default function PresenterView(props) {
|
||||
|
||||
<div className={style.timerContainer}>
|
||||
{time.finished ? (
|
||||
<div className={style.finished}>TIME UP</div>
|
||||
<div className={style.finished}>{endMessage}</div>
|
||||
) : (
|
||||
<div className={isPlaying ? style.countdown : style.countdownPaused}>
|
||||
<Countdown time={normalisedTime} hideZeroHours />
|
||||
|
||||
@@ -66,7 +66,8 @@
|
||||
}
|
||||
|
||||
.finished {
|
||||
font-size: 18vw;
|
||||
text-align: center;
|
||||
font-size: 12vw;
|
||||
line-height: 18vw;
|
||||
font-weight: 600;
|
||||
color: #ff6969;
|
||||
|
||||
@@ -87,7 +87,7 @@ export default function Pip(props) {
|
||||
<div className={style.qr}>
|
||||
{general.url != null && general.url !== '' && (
|
||||
<QRCode
|
||||
value='www.carlosvalente.com'
|
||||
value={general.url}
|
||||
size={window.innerWidth / 12}
|
||||
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(
|
||||
nodePath
|
||||
);
|
||||
// Start express server
|
||||
loaded = startServer();
|
||||
// Start OSC Server (API)
|
||||
startOSCServer();
|
||||
// Start OSC Client (Feedback)
|
||||
startOSCClient();
|
||||
await startOSCClient();
|
||||
|
||||
// Start express server
|
||||
loaded = await startServer();
|
||||
|
||||
// Start OSC Server (API)
|
||||
await startOSCServer();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
loaded = error;
|
||||
@@ -42,9 +44,9 @@ const nodePath =
|
||||
const trayIcon = path.join(__dirname, './assets/background.png');
|
||||
const appIcon = path.join(__dirname, './assets/logo.png');
|
||||
|
||||
function showNotification(text) {
|
||||
function showNotification(title, text) {
|
||||
new Notification({
|
||||
title: 'ontime',
|
||||
title: title,
|
||||
body: text,
|
||||
silent: true,
|
||||
}).show();
|
||||
@@ -99,7 +101,6 @@ function createWindow() {
|
||||
show: false,
|
||||
textAreasAreResizable: false,
|
||||
enableWebSQL: false,
|
||||
skipTaskbar: true,
|
||||
darkTheme: true,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, './electron/preload.js'),
|
||||
@@ -113,6 +114,11 @@ function createWindow() {
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
// Set app title in windows
|
||||
if (process.platform === 'win32') {
|
||||
app.setAppUserModelId(app.name);
|
||||
}
|
||||
|
||||
createWindow();
|
||||
|
||||
// register global shortcuts
|
||||
@@ -123,11 +129,6 @@ app.whenReady().then(() => {
|
||||
win.focus();
|
||||
});
|
||||
|
||||
globalShortcut.register('Alt+t', () => {
|
||||
// Show dev tools
|
||||
win.webContents.openDevTools({ mode: 'detach' });
|
||||
});
|
||||
|
||||
// recreate window if no others open
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
@@ -151,7 +152,6 @@ app.whenReady().then(() => {
|
||||
win.focus();
|
||||
|
||||
splash.destroy();
|
||||
showNotification(loaded.toString());
|
||||
|
||||
// tray stuff
|
||||
// TODO: get IP Address
|
||||
@@ -163,7 +163,7 @@ app.whenReady().then(() => {
|
||||
win.on('close', function (event) {
|
||||
event.preventDefault();
|
||||
if (!isQuitting) {
|
||||
showNotification('App running in background');
|
||||
showNotification('Window Closed', 'App running in background');
|
||||
win.hide();
|
||||
return false;
|
||||
}
|
||||
@@ -177,14 +177,14 @@ app.whenReady().then(() => {
|
||||
// Define context menu
|
||||
const trayMenuTemplate = [
|
||||
{
|
||||
label: 'Show App',
|
||||
label: 'Show App (Alt + 1)',
|
||||
click: () => {
|
||||
win.show();
|
||||
win.focus();
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Close',
|
||||
label: 'Shutdown',
|
||||
click: () => {
|
||||
win.destroy();
|
||||
app.quit();
|
||||
@@ -209,7 +209,7 @@ app.once('before-quit', () => {
|
||||
// Get messages from react
|
||||
// Test message
|
||||
ipcMain.on('test-message', (event, arg) => {
|
||||
showNotification('testing 1-2', arg);
|
||||
showNotification('Test Message', 'test from react', arg);
|
||||
});
|
||||
|
||||
// Terminate
|
||||
@@ -240,6 +240,9 @@ ipcMain.on('set-window', (event, arg) => {
|
||||
} else if (arg === 'to-tray') {
|
||||
// window to tray
|
||||
win.hide();
|
||||
} else if (arg === 'show-dev') {
|
||||
// Show dev tools
|
||||
win.webContents.openDevTools({ mode: 'detach' });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+7
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ontime",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"author": "Carlos Valente",
|
||||
"description": "Time keeping for live events",
|
||||
"repository": "https://github.com/cpvalente/ontime",
|
||||
@@ -8,7 +8,6 @@
|
||||
"license": "AGPL-3.0-only",
|
||||
"main": "main.js",
|
||||
"devDependencies": {
|
||||
"concurrently": "^6.1.0",
|
||||
"electron": "^13.1.2",
|
||||
"electron-builder": "^22.11.3",
|
||||
"eslint": "^7.26.0",
|
||||
@@ -48,11 +47,16 @@
|
||||
},
|
||||
"nsis": {
|
||||
"artifactName": "ontime-win64.exe",
|
||||
"oneClick": false,
|
||||
"allowToChangeInstallationDirectory": true,
|
||||
"allowElevation": false,
|
||||
"createDesktopShortcut": true,
|
||||
"createStartMenuShortcut": true,
|
||||
"deleteAppDataOnUninstall": true,
|
||||
"runAfterFinish": false,
|
||||
"installerIcon": "icon.ico"
|
||||
"installerIcon": "icon.ico",
|
||||
"installerSidebar": "ontime-install.bmp",
|
||||
"uninstallerSidebar": "ontime-uninstall.bmp"
|
||||
},
|
||||
"files": [
|
||||
"**/*",
|
||||
|
||||
+58
-29
@@ -4,6 +4,7 @@ import { sessionId, user } from './utils/analytics.js';
|
||||
user.screenview('Node service', 'ontime').send();
|
||||
user.event('NODE', 'started', 'starting node service').send();
|
||||
|
||||
// import config
|
||||
import { config } from './config/config.js';
|
||||
|
||||
// init database
|
||||
@@ -25,7 +26,7 @@ import { Client } from 'node-osc';
|
||||
import express from 'express';
|
||||
import http from 'http';
|
||||
import cors from 'cors';
|
||||
import { dbModel } from './data/dataModel.js';
|
||||
import { dbModel } from './models/dataModel.js';
|
||||
import ua from 'universal-analytics';
|
||||
|
||||
// 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);
|
||||
});
|
||||
|
||||
/*************** 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
|
||||
// TODO: Move this to function
|
||||
const oscClient = new Client('127.0.0.1', 9999);
|
||||
let oscClient = null;
|
||||
|
||||
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
|
||||
const server = http.createServer(app);
|
||||
|
||||
export const startServer = (overrideConfig = null) => {
|
||||
export const startServer = async (overrideConfig = null) => {
|
||||
// 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
|
||||
const returnMessage = `HTTP Server is listening on port ${serverPort}`;
|
||||
server.listen(serverPort, '0.0.0.0', () => console.log(returnMessage));
|
||||
const returnMessage = `HTTP Server is listening on port ${port}`;
|
||||
server.listen(port, '0.0.0.0', () => console.log(returnMessage));
|
||||
|
||||
// init timer
|
||||
global.timer = new EventTimer(server, oscClient, config);
|
||||
@@ -117,38 +164,20 @@ export const startServer = (overrideConfig = null) => {
|
||||
return returnMessage;
|
||||
};
|
||||
|
||||
// Start OSC server
|
||||
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 = () => {
|
||||
export const shutdown = async () => {
|
||||
console.log('Node service shutdown');
|
||||
|
||||
user.event('NODE', 'shutdown', 'requesting node shutfown').send();
|
||||
|
||||
// shutdown express server
|
||||
server.close();
|
||||
|
||||
// shutdown OSC Server
|
||||
shutdownOSCServer();
|
||||
|
||||
// shutdown OSC Client
|
||||
oscClient.close();
|
||||
|
||||
// shutdown timer
|
||||
global.timer.shutdown();
|
||||
};
|
||||
|
||||
// if (env == 'development') {
|
||||
// startServer();
|
||||
// startOSCServer();
|
||||
// startOSCClient();
|
||||
// }
|
||||
|
||||
@@ -242,10 +242,6 @@ export class EventTimer extends Timer {
|
||||
} else {
|
||||
// look for event if none is loaded
|
||||
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() {
|
||||
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 nextIndex = null;
|
||||
@@ -988,16 +988,20 @@ export class EventTimer extends Timer {
|
||||
}
|
||||
|
||||
// nothing to play next, unload
|
||||
if (!foundNow && !nextIndex) {
|
||||
if (foundNow == null && nextIndex == null) {
|
||||
this.unload();
|
||||
console.log('Roll: no events found');
|
||||
return;
|
||||
}
|
||||
|
||||
if (nextIndex) {
|
||||
if (nextIndex != null) {
|
||||
// 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
|
||||
this.secondaryTimer = nextStart;
|
||||
}
|
||||
@@ -1066,8 +1070,8 @@ export class EventTimer extends Timer {
|
||||
}
|
||||
|
||||
unload() {
|
||||
// reset duration
|
||||
this.duration = null;
|
||||
// reset timer
|
||||
this._resetTimers(true);
|
||||
|
||||
// reset selected
|
||||
this._resetSelection();
|
||||
|
||||
@@ -120,6 +120,7 @@ export class Timer {
|
||||
_resetTimers(total = false) {
|
||||
if (total) this.duration = null;
|
||||
this.current = this.duration;
|
||||
this.running = null;
|
||||
this.secondaryTimer = null;
|
||||
this._finishAt = null;
|
||||
this._finishedAt = null;
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
event as eventDef,
|
||||
delay as delayDef,
|
||||
block as blockDef,
|
||||
} from '../data/eventsDefinition.js';
|
||||
} from '../models/eventsDefinition.js';
|
||||
|
||||
function _getEventsCount() {
|
||||
return Array.from(data.events).length;
|
||||
|
||||
@@ -8,8 +8,8 @@ import {
|
||||
event as eventDef,
|
||||
delay as delayDef,
|
||||
block as blockDef,
|
||||
} from '../data/eventsDefinition.js';
|
||||
import { dbModel } from '../data/dataModel.js';
|
||||
} from '../models/eventsDefinition.js';
|
||||
import { dbModel } from '../models/dataModel.js';
|
||||
import { networkInterfaces } from 'os';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
@@ -32,6 +32,7 @@ async function deleteFile(file) {
|
||||
async function parsev1(jsonData) {
|
||||
let numEntries = 0;
|
||||
if ('events' in jsonData) {
|
||||
console.log('Found events definition, importing...');
|
||||
let events = [];
|
||||
let ids = [];
|
||||
for (const e of jsonData.events) {
|
||||
@@ -69,6 +70,7 @@ async function parsev1(jsonData) {
|
||||
}
|
||||
|
||||
if ('event' in jsonData) {
|
||||
console.log('Found event data, importing...');
|
||||
const e = jsonData.event;
|
||||
// filter known properties
|
||||
const event = {
|
||||
@@ -77,6 +79,7 @@ async function parsev1(jsonData) {
|
||||
url: e.url,
|
||||
publicInfo: e.publicInfo,
|
||||
backstageInfo: e.backstageInfo,
|
||||
endMessage: e.endMessage,
|
||||
};
|
||||
|
||||
// write to db
|
||||
@@ -84,10 +87,23 @@ async function parsev1(jsonData) {
|
||||
db.write();
|
||||
}
|
||||
|
||||
// Not handling settings yet
|
||||
// let settings = {};
|
||||
// if ('settings' in jsonData) {
|
||||
// }
|
||||
// Settings handled partially
|
||||
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'
|
||||
@@ -158,19 +174,52 @@ const getNetworkInterfaces = () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
};
|
||||
|
||||
// Create controller for POST request to '/ontime/info'
|
||||
// Returns -
|
||||
// TODO: Add version
|
||||
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();
|
||||
ni.unshift({ name: 'localhost', address: '127.0.0.1' });
|
||||
|
||||
// send object with network information
|
||||
res.status(200).send({
|
||||
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'
|
||||
// Returns -
|
||||
export const dbUpload = async (req, res) => {
|
||||
|
||||
@@ -7,6 +7,7 @@ export const pbGet = async (req, res) => {
|
||||
// Create controller for GET request to '/playback/start'
|
||||
// Starts timer object
|
||||
export const pbStart = async (req, res) => {
|
||||
console.log('Calling start');
|
||||
global.timer.start();
|
||||
res.sendStatus(200);
|
||||
};
|
||||
@@ -14,6 +15,7 @@ export const pbStart = async (req, res) => {
|
||||
// Create controller for GET request to '/playback/pause'
|
||||
// Pauses timer object
|
||||
export const pbPause = async (req, res) => {
|
||||
console.log('Calling pause');
|
||||
global.timer.pause();
|
||||
res.sendStatus(200);
|
||||
};
|
||||
@@ -21,6 +23,7 @@ export const pbPause = async (req, res) => {
|
||||
// Create controller for GET request to '/playback/stop'
|
||||
// Stops timer object
|
||||
export const pbStop = async (req, res) => {
|
||||
console.log('Calling stop');
|
||||
global.timer.stop();
|
||||
res.sendStatus(200);
|
||||
};
|
||||
@@ -28,6 +31,7 @@ export const pbStop = async (req, res) => {
|
||||
// Create controller for GET request to '/playback/roll'
|
||||
// Sets timer object to roll mode
|
||||
export const pbRoll = async (req, res) => {
|
||||
console.log('Calling roll');
|
||||
global.timer.roll();
|
||||
res.sendStatus(501);
|
||||
};
|
||||
@@ -35,6 +39,7 @@ export const pbRoll = async (req, res) => {
|
||||
// Create controller for GET request to '/playback/previous'
|
||||
// Sets timer object to roll mode
|
||||
export const pbPrevious = async (req, res) => {
|
||||
console.log('Calling previous');
|
||||
global.timer.previous();
|
||||
res.sendStatus(200);
|
||||
};
|
||||
@@ -42,6 +47,7 @@ export const pbPrevious = async (req, res) => {
|
||||
// Create controller for GET request to '/playback/next'
|
||||
// Sets timer object to roll mode
|
||||
export const pbNext = async (req, res) => {
|
||||
console.log('Calling next');
|
||||
global.timer.next();
|
||||
res.sendStatus(200);
|
||||
};
|
||||
@@ -49,8 +55,8 @@ export const pbNext = async (req, res) => {
|
||||
// Create controller for GET request to '/playback/unload'
|
||||
// Unloads any events
|
||||
export const pbUnload = async (req, res) => {
|
||||
console.log('Calling unload');
|
||||
global.timer.unload();
|
||||
console.log('debug: unload called');
|
||||
|
||||
res.sendStatus(200);
|
||||
};
|
||||
@@ -58,7 +64,7 @@ export const pbUnload = async (req, res) => {
|
||||
// Create controller for GET request to '/playback/reload'
|
||||
// Reloads current event
|
||||
export const pbReload = async (req, res) => {
|
||||
console.log('Calling reload');
|
||||
global.timer.reload();
|
||||
console.log('debug: reload called');
|
||||
res.sendStatus(200);
|
||||
};
|
||||
|
||||
@@ -5,12 +5,16 @@ export const dbModel = {
|
||||
url: '',
|
||||
publicInfo: '',
|
||||
backstageInfo: '',
|
||||
endMessage: '',
|
||||
},
|
||||
settings: {
|
||||
app: 'ontime',
|
||||
version: 1,
|
||||
osc_enabled: false,
|
||||
osc_port: 8888,
|
||||
serverPort: 4001,
|
||||
oscInPort: 8888,
|
||||
oscOutPort: 9999,
|
||||
oscOutIP: '127.0.0.1',
|
||||
oscEnabled: false,
|
||||
lock: false,
|
||||
},
|
||||
};
|
||||
@@ -2,16 +2,16 @@
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"body-parser": "~1.19.0",
|
||||
"dotenv": "^10.0.0",
|
||||
"express": "~4.17.1",
|
||||
"express-session": "~1.17.1",
|
||||
"lowdb": "2.1.0",
|
||||
"lowdb": "3.0.0",
|
||||
"multer": "^1.4.2",
|
||||
"nanoid": "^3.1.22",
|
||||
"node-osc": "6.0.2",
|
||||
"node-osc": "6.1.10",
|
||||
"passport": "~0.4.1",
|
||||
"passport-local": "~1.0.0",
|
||||
"socket.io": "^4.0.0",
|
||||
"dotenv": "^10.0.0",
|
||||
"socket.io": "^4.3.1",
|
||||
"universal-analytics": "^0.4.23"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
dbDownload,
|
||||
dbUpload,
|
||||
getInfo,
|
||||
postInfo,
|
||||
dbPathToUpload,
|
||||
} from '../controllers/ontimeController.js';
|
||||
|
||||
@@ -18,5 +19,8 @@ router.post('/db', uploadJson, dbUpload);
|
||||
// create route between controller and '/ontime/info' endpoint
|
||||
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
|
||||
router.post('/dbpath', dbPathToUpload);
|
||||
|
||||
@@ -1,8 +1,23 @@
|
||||
import multer from 'multer';
|
||||
import { statSync, mkdirSync } from 'fs';
|
||||
|
||||
const storage = multer.diskStorage({
|
||||
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) {
|
||||
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:
|
||||
"@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"
|
||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.14.5.tgz#23b08d740e83f49c5e59945fbf1b43e80bbf4edb"
|
||||
integrity sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==
|
||||
@@ -711,11 +711,6 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.3.tgz#6d327abaa4be34a74e421ed6409a0ae2f47f4c3d"
|
||||
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":
|
||||
version "3.0.2"
|
||||
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"
|
||||
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:
|
||||
version "1.1.13"
|
||||
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-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:
|
||||
version "4.3.1"
|
||||
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"
|
||||
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:
|
||||
version "0.4.1"
|
||||
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"
|
||||
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:
|
||||
version "4.0.0"
|
||||
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"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
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"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
|
||||
integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==
|
||||
@@ -3796,16 +3761,6 @@ parse-json@^4.0.0:
|
||||
error-ex "^1.3.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:
|
||||
version "6.0.1"
|
||||
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"
|
||||
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:
|
||||
version "2.3.7"
|
||||
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"
|
||||
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:
|
||||
version "5.1.2"
|
||||
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"
|
||||
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:
|
||||
version "3.1.1"
|
||||
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:
|
||||
has-flag "^4.0.0"
|
||||
|
||||
supports-color@^8.0.0, supports-color@^8.1.0:
|
||||
supports-color@^8.0.0:
|
||||
version "8.1.1"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c"
|
||||
integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
|
||||
@@ -4611,11 +4544,6 @@ tr46@^2.1.0:
|
||||
dependencies:
|
||||
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:
|
||||
version "1.0.2"
|
||||
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"
|
||||
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:
|
||||
version "0.0.6"
|
||||
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"
|
||||
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:
|
||||
version "3.1.5"
|
||||
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"
|
||||
integrity sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==
|
||||
|
||||
yargs@^16.0.3, yargs@^16.2.0:
|
||||
yargs@^16.0.3:
|
||||
version "16.2.0"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
|
||||
integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==
|
||||
|
||||
Reference in New Issue
Block a user