Merge branch 'master' into feat/info

This commit is contained in:
Carlos Valente
2021-06-13 14:34:20 +02:00
committed by GitHub
71 changed files with 6100 additions and 754 deletions
+98
View File
@@ -0,0 +1,98 @@
# This is a basic workflow to help you get started with Actions
name: CI
# Controls when the action will run.
on:
push:
# Pattern matched against refs/tags
tags:
# Push events to every tag not containing /
- '*'
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# build a file for windows
buildwin:
# The type of runner that the job will run on
runs-on: windows-latest
env:
CI: ''
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v1
with:
node-version: '14.x'
# install and build react
- name: Install React dependencies
run: yarn install
working-directory: ./client
- name: Build React App
run: yarn build
working-directory: ./client
# install and build electron
- name: Install Electron + nodejs dependencies
run: yarn install
working-directory: ./server
- name: Build Electron App
run: yarn dist-win
working-directory: ./server
# release
- name: Release
uses: softprops/action-gh-release@v1
with:
files: ./server/dist/ontime-win64.exe
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# build a file for mac
buildmac:
# The type of runner that the job will run on
runs-on: macOS-latest
env:
CI: ''
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v1
with:
node-version: '14.x'
# install and build react
- name: Install React dependencies
run: yarn install
working-directory: ./client
- name: Build React App
run: yarn build
working-directory: ./client
# install and build electron
- name: Install Electron + nodejs dependencies
run: yarn install
working-directory: ./server
- name: Build Electron App
run: yarn dist-win
working-directory: ./server
# release
- name: Release
uses: softprops/action-gh-release@v1
with:
files: ./server/dist/ontime-macOS.dmg
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+1 -4
View File
@@ -28,10 +28,7 @@ TODO.md
# working stuff # working stuff
_SS/ _SS/
.vscode/launch.json .vscode/launch.json
server/db.json db.json
.eslintrc.json .eslintrc.json
yarn.lock
package.json
package-lock.json
db.json db.json
db backup.json db backup.json
+9
View File
@@ -0,0 +1,9 @@
The MIT License (MIT)
Copyright © 2021 <Carlos Valente>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+1 -69
View File
@@ -1,70 +1,2 @@
# Getting Started with Create React App # Initial page
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `yarn start`
Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.\
You will also see any lint errors in the console.
### `yarn test`
Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `yarn build`
Builds the app for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `yarn eject`
**Note: this is a one-way operation. Once you `eject`, you cant go back!**
If you arent satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
### Code Splitting
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
### Analyzing the Bundle Size
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
### Making a Progressive Web App
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
### Advanced Configuration
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
### Deployment
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
### `yarn build` fails to minify
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
+4
View File
@@ -0,0 +1,4 @@
# Table of contents
* [Initial page](README.md)
+4
View File
@@ -7,6 +7,10 @@ body,
html, html,
.App { .App {
margin: 0px auto; margin: 0px auto;
overflow: hidden;
overflow: clip; overflow: clip;
height: 100vh; height: 100vh;
-webkit-user-select: none;
user-select: none;
-webkit-app-region: drag;
} }
+4 -1
View File
@@ -37,7 +37,6 @@ export const downloadEvents = async () => {
}; };
export const uploadEvents = async (file) => { export const uploadEvents = async (file) => {
console.log('uploading', file);
const formData = new FormData(); const formData = new FormData();
formData.append('jsondb', file); // appending file formData.append('jsondb', file); // appending file
await axios await axios
@@ -49,3 +48,7 @@ export const uploadEvents = async (file) => {
.then((res) => console.log(res.data)) .then((res) => console.log(res.data))
.catch((err) => console.error(err)); .catch((err) => console.error(err));
}; };
export const uploadEventsWithPath = async (filepath) => {
await axios.post(ontimeURL + '/dbpath', { path: filepath });
};
@@ -1,16 +1,19 @@
import { IconButton } from '@chakra-ui/button'; import { IconButton } from '@chakra-ui/button';
import { Tooltip } from '@chakra-ui/tooltip';
import { FiChevronsDown } from 'react-icons/fi'; import { FiChevronsDown } from 'react-icons/fi';
export default function ApplyIconBtn(props) { export default function ApplyIconBtn(props) {
const { clickhandler, ...rest } = props; const { clickhandler, ...rest } = props;
return ( return (
<IconButton <Tooltip label='Apply delays'>
size={props.size || 'xs'} <IconButton
icon={<FiChevronsDown />} size={props.size || 'xs'}
colorScheme='orange' icon={<FiChevronsDown />}
onClick={clickhandler} colorScheme='orange'
_focus={{ boxShadow: 'none' }} onClick={clickhandler}
{...rest} _focus={{ boxShadow: 'none' }}
/> {...rest}
/>
</Tooltip>
); );
} }
@@ -6,13 +6,13 @@ export default function LockIconBtn(props) {
const { clickhandler, active, ref } = props; const { clickhandler, active, ref } = props;
return ( return (
<Tooltip label='Lock cursor to current'> <Tooltip label='Lock cursor to current'>
<IconButton <IconButton
ref={ref} ref={ref}
size={props.size || 'xs'} size={props.size || 'xs'}
icon={<FiTarget />} icon={<FiTarget />}
color={active ? 'pink.100' : 'pink.300'} color={active ? 'pink.100' : 'pink.300'}
borderColor={active ? undefined : 'pink.300'} borderColor={active ? undefined : 'pink.300'}
backgroundColor={active ? 'pink.300' : undefined} backgroundColor={active ? 'pink.400' : undefined}
variant={active ? 'solid' : 'outline'} variant={active ? 'solid' : 'outline'}
onClick={clickhandler} onClick={clickhandler}
_focus={{ boxShadow: 'none' }} _focus={{ boxShadow: 'none' }}
@@ -0,0 +1,22 @@
import { IconButton } from '@chakra-ui/button';
import { Tooltip } from '@chakra-ui/tooltip';
import { FiUsers } from 'react-icons/fi';
export default function PublicIconBtn(props) {
const { actionHandler, active, ...rest } = props;
return (
<Tooltip label={active ? 'Make event private' : 'Make event public'}>
<IconButton
size={props.size || 'xs'}
icon={<FiUsers />}
colorScheme='blue'
variant={active ? 'solid' : 'outline'}
onClick={() =>
actionHandler('update', { field: 'isPublic', value: !active })
}
_focus={{ boxShadow: 'none' }}
{...rest}
/>
</Tooltip>
);
}
@@ -1,11 +0,0 @@
import { Flex, Text } from '@chakra-ui/layout';
import styles from './NumberedText.module.css';
export default function NumberedText({ number = 1, text = '' }) {
return (
<Flex>
<div className={styles.stylednumber}>{number}</div>
<Text>{text}</Text>
</Flex>
);
}
@@ -1,13 +0,0 @@
.stylednumber {
display: inline;
text-align: center;
color: white;
font-weight: 600;
background-color: #ff5b7e;
border: 1px solid rgba(255,255,255,0.17);
width: 1.5em;
border-radius: 1em;
margin-right: 0.5em;
}
@@ -8,9 +8,12 @@
.block { .block {
display: 'block'; display: 'block';
overflow-x: hidden; overflow: hidden;
} }
.inline { .inline {
display: inline; display: inline;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
} }
+7 -23
View File
@@ -1,3 +1,4 @@
import { lazy, useEffect } from 'react'; import { lazy, useEffect } from 'react';
import { Heading } from '@chakra-ui/layout'; import { Heading } from '@chakra-ui/layout';
import { Box } from '@chakra-ui/layout'; import { Box } from '@chakra-ui/layout';
@@ -28,50 +29,33 @@ export default function Editor() {
<div className={styles.mainContainer}> <div className={styles.mainContainer}>
<Box id='settings' className={styles.settings}> <Box id='settings' className={styles.settings}>
<MenuBar onOpen={onOpen} onClose={onClose} /> <MenuBar onOpen={onOpen} />
</Box> </Box>
<Box className={styles.editor}> <Box className={styles.editor}>
<Heading size='lg' paddingBottom={'0.25em'}> <h1>Event List</h1>
Event List
</Heading>
<NumberedText number={1} text={'Manage events'} />
<div className={styles.content}> <div className={styles.content}>
<EventListWrapper /> <EventListWrapper />
</div> </div>
</Box> </Box>
<Box className={styles.messages}> <Box className={styles.messages}>
<Heading size='lg' paddingBottom={'0.25em'}> <h1>Display Messages</h1>
Display Messages
</Heading>
<NumberedText
number={3}
text={'Show realtime messages on different screens'}
/>
<div className={styles.content}> <div className={styles.content}>
<MessageControl /> <MessageControl />
</div> </div>
</Box> </Box>
<Box className={styles.playback}> <Box className={styles.playback}>
<Heading size='lg' paddingBottom={'0.25em'}> <h1>Timer Control</h1>
Time Control
</Heading>
<NumberedText number={2} text={'Control timers'} />
<div className={styles.content}> <div className={styles.content}>
<PlaybackControl /> <PlaybackControl />
</div> </div>
</Box> </Box>
<Box className={styles.info} borderRadius='0.5em' overflowX='auto'> <Box className={styles.info} borderRadius='0.5em' overflowX='auto'>
<Heading size='lg' paddingBottom={'0.25em'}> <h1>Info</h1>
Info <div className={styles.content}></div>
</Heading>
<NumberedText number={4} text={'Running Info'} />
<div className={styles.content}>
<Info />
</div>
</Box> </Box>
</div> </div>
</> </>
@@ -69,6 +69,12 @@
} }
} }
h1 {
font-size: 2.5vh;
color: rgba(255, 255, 255, 0.63);
padding-bottom: 0.25em;
}
.mainContainer > div { .mainContainer > div {
border-radius: 0.5em; border-radius: 0.5em;
height: 100%; height: 100%;
@@ -1,6 +1,6 @@
import { Menu, MenuButton, MenuItem, MenuList } from '@chakra-ui/menu'; import { Menu, MenuButton, MenuItem, MenuList } from '@chakra-ui/menu';
import { IconButton } from '@chakra-ui/button'; import { IconButton } from '@chakra-ui/button';
import { FiZap, FiPlus, FiMinusCircle, FiClock } from 'react-icons/fi'; import { FiPlus, FiMinusCircle, FiClock } from 'react-icons/fi';
export default function ActionButtons(props) { export default function ActionButtons(props) {
const { showAdd, showDelay, showBlock, actionHandler } = props; const { showAdd, showDelay, showBlock, actionHandler } = props;
@@ -16,7 +16,7 @@ export default function ActionButtons(props) {
as={IconButton} as={IconButton}
aria-label='Options' aria-label='Options'
size='xs' size='xs'
icon={<FiZap />} icon={<FiPlus />}
_expanded={{ bg: 'orange.300', color: 'white' }} _expanded={{ bg: 'orange.300', color: 'white' }}
_focus={{ boxShadow: 'none' }} _focus={{ boxShadow: 'none' }}
backgroundColor={'orange.200'} backgroundColor={'orange.200'}
@@ -6,7 +6,7 @@ import EventTimes from 'common/components/eventTimes/EventTimes';
import EventTimesVertical from 'common/components/eventTimes/EventTimesVertical'; import EventTimesVertical from 'common/components/eventTimes/EventTimesVertical';
import EditableText from 'common/input/EditableText'; import EditableText from 'common/input/EditableText';
import ActionButtons from './ActionButtons'; import ActionButtons from './ActionButtons';
import VisibleIconBtn from 'common/components/buttons/VisibleIconBtn'; import PublicIconBtn from 'common/components/buttons/PublicIconBtn';
import DeleteIconBtn from 'common/components/buttons/DeleteIconBtn'; import DeleteIconBtn from 'common/components/buttons/DeleteIconBtn';
import { millisToMinutes } from 'common/dateConfig'; import { millisToMinutes } from 'common/dateConfig';
import style from './EventBlock.module.css'; import style from './EventBlock.module.css';
@@ -94,7 +94,7 @@ const ExpandedBlock = (props) => {
onClick={() => props.setCollapsed(true)} onClick={() => props.setCollapsed(true)}
/> />
<div className={style.actionOverlay}> <div className={style.actionOverlay}>
<VisibleIconBtn actionHandler={actionHandler} active={data.isPublic} /> <PublicIconBtn actionHandler={actionHandler} active={data.isPublic} />
<ActionButtons <ActionButtons
showAdd showAdd
showDelay showDelay
@@ -147,7 +147,7 @@ const CollapsedBlock = (props) => {
onClick={() => props.setCollapsed(false)} onClick={() => props.setCollapsed(false)}
/> />
<div className={style.actionOverlay}> <div className={style.actionOverlay}>
<VisibleIconBtn actionHandler={actionHandler} active={data.isPublic} /> <PublicIconBtn actionHandler={actionHandler} active={data.isPublic} />
<ActionButtons <ActionButtons
showAdd showAdd
showDelay showDelay
@@ -152,6 +152,8 @@
font-size: 0.8em; font-size: 0.8em;
float: right; float: right;
padding-right: 1em; padding-right: 1em;
-webkit-user-select: text;
user-select: text;
} }
/* ================ MORE ================ */ /* ================ MORE ================ */
@@ -1,12 +1,6 @@
import { Menu, MenuButton, MenuItem, MenuList } from '@chakra-ui/menu'; import { Menu, MenuButton, MenuItem, MenuList } from '@chakra-ui/menu';
import { IconButton } from '@chakra-ui/button'; import { IconButton } from '@chakra-ui/button';
import { import { FiTrash2, FiPlus, FiClock, FiMinusCircle } from 'react-icons/fi';
FiTrash2,
FiZap,
FiPlus,
FiClock,
FiMinusCircle,
} from 'react-icons/fi';
import { Divider } from '@chakra-ui/layout'; import { Divider } from '@chakra-ui/layout';
export default function MenuActionButtons(props) { export default function MenuActionButtons(props) {
@@ -20,9 +14,9 @@ export default function MenuActionButtons(props) {
<Menu isLazy lazyBehavior='unmount'> <Menu isLazy lazyBehavior='unmount'>
<MenuButton <MenuButton
as={IconButton} as={IconButton}
aria-label='Options' aria-label='Create Menu'
size={props.size || 'xs'} size={props.size || 'xs'}
icon={<FiZap />} icon={<FiPlus />}
_expanded={{ bg: 'orange.300', color: 'white' }} _expanded={{ bg: 'orange.300', color: 'white' }}
_focus={{ boxShadow: 'none' }} _focus={{ boxShadow: 'none' }}
backgroundColor={'orange.200'} backgroundColor={'orange.200'}
+92 -9
View File
@@ -1,4 +1,6 @@
import { downloadEvents } from 'app/api/ontimeApi'; import { useMutation, useQueryClient } from 'react-query';
import { downloadEvents, uploadEvents } from 'app/api/ontimeApi';
import { EVENTS_TABLE } from 'app/api/apiConstants';
import DownloadIconBtn from './buttons/DownloadIconBtn'; import DownloadIconBtn from './buttons/DownloadIconBtn';
import SettingsIconBtn from './buttons/SettingsIconBtn'; import SettingsIconBtn from './buttons/SettingsIconBtn';
import InfoIconBtn from './buttons/InfoIconBtn'; import InfoIconBtn from './buttons/InfoIconBtn';
@@ -7,24 +9,105 @@ import MinIconBtn from './buttons/MinIconBtn';
import QuitIconBtn from './buttons/QuitIconBtn'; import QuitIconBtn from './buttons/QuitIconBtn';
import style from './MenuBar.module.css'; import style from './MenuBar.module.css';
import HelpIconBtn from './buttons/HelpIconBtn'; import HelpIconBtn from './buttons/HelpIconBtn';
import UploadIconBtn from './buttons/UploadIconBtn';
import { useRef } from 'react';
const { ipcRenderer } = window.require('electron');
export default function MenuBar(props) { export default function MenuBar(props) {
const { onOpen, onClose } = props; const { onOpen } = props;
const hiddenFileInput = useRef(null);
const queryClient = useQueryClient();
const uploaddb = useMutation(uploadEvents, {
onSettled: () => {
queryClient.invalidateQueries(EVENTS_TABLE);
},
});
const handleDownload = () => { const handleDownload = () => {
downloadEvents(); downloadEvents();
}; };
const handleClick = () => {
if (hiddenFileInput && hiddenFileInput.current) {
hiddenFileInput.current.click();
}
};
const handleUpload = (event) => {
const fileUploaded = event.target.files[0];
if (fileUploaded == null) return;
try {
uploaddb.mutate(fileUploaded);
} catch (error) {
console.log(error);
}
};
const handleIPC = (action) => {
switch (action) {
case 'min':
ipcRenderer.send('set-window', 'to-tray');
break;
case 'max':
ipcRenderer.send('set-window', 'to-max');
break;
case 'shutdown':
ipcRenderer.send('shutdown', 'now');
break;
case 'help':
ipcRenderer.send('send-to-link', 'help');
break;
default:
break;
}
};
return ( return (
<> <>
<QuitIconBtn size='md' /> <QuitIconBtn size='lg' clickhandler={() => handleIPC('shutdown')} />
<MaxIconBtn size='md' /> <MaxIconBtn
<MinIconBtn size='md' /> style={{ fontSize: '1.5em' }}
size='lg'
clickhandler={() => handleIPC('max')}
/>
<MinIconBtn
style={{ fontSize: '1.5em' }}
size='lg'
clickhandler={() => handleIPC('min')}
/>
<div className={style.gap} /> <div className={style.gap} />
<HelpIconBtn size='md' disabled /> <HelpIconBtn
<SettingsIconBtn size='md' disabled /> style={{ fontSize: '1.5em' }}
<InfoIconBtn size='md' clickhandler={onOpen} /> size='lg'
<DownloadIconBtn size='md' clickhandler={handleDownload} /> clickhandler={() => handleIPC('help')}
/>
<SettingsIconBtn style={{ fontSize: '1.5em' }} size='lg' disabled />
<div className={style.gap} />
<InfoIconBtn
style={{ fontSize: '1.5em' }}
size='lg'
clickhandler={onOpen}
/>
<input
type='file'
style={{ display: 'none' }}
ref={hiddenFileInput}
onChange={handleUpload}
accept='.json'
/>
<UploadIconBtn
style={{ fontSize: '1.5em' }}
size='lg'
clickhandler={handleClick}
/>
<DownloadIconBtn
style={{ fontSize: '1.5em' }}
size='lg'
clickhandler={handleDownload}
/>
</> </>
); );
} }
@@ -10,9 +10,6 @@ export default function DownloadIconBtn(props) {
size={props.size || 'xs'} size={props.size || 'xs'}
icon={<FiDownload />} icon={<FiDownload />}
colorScheme='white' colorScheme='white'
variant='outline'
isRound
borderColor='#fff1'
onClick={clickhandler} onClick={clickhandler}
_focus={{ boxShadow: 'none' }} _focus={{ boxShadow: 'none' }}
{...rest} {...rest}
@@ -10,9 +10,6 @@ export default function HelpIconBtn(props) {
size={props.size || 'xs'} size={props.size || 'xs'}
icon={<FiHelpCircle />} icon={<FiHelpCircle />}
colorScheme='white' colorScheme='white'
variant='outline'
isRound
borderColor='#fff1'
onClick={clickhandler} onClick={clickhandler}
_focus={{ boxShadow: 'none' }} _focus={{ boxShadow: 'none' }}
{...rest} {...rest}
@@ -10,9 +10,6 @@ export default function InfoIconBtn(props) {
size={props.size || 'xs'} size={props.size || 'xs'}
icon={<FiHome />} icon={<FiHome />}
colorScheme='white' colorScheme='white'
variant='outline'
isRound
borderColor='#fff1'
onClick={clickhandler} onClick={clickhandler}
_focus={{ boxShadow: 'none' }} _focus={{ boxShadow: 'none' }}
{...rest} {...rest}
@@ -10,9 +10,6 @@ export default function MaxIconBtn(props) {
size={props.size || 'xs'} size={props.size || 'xs'}
icon={<FiMaximize />} icon={<FiMaximize />}
colorScheme='white' colorScheme='white'
variant='outline'
isRound
borderColor='#fff1'
onClick={clickhandler} onClick={clickhandler}
_focus={{ boxShadow: 'none' }} _focus={{ boxShadow: 'none' }}
{...rest} {...rest}
@@ -10,9 +10,6 @@ export default function MinIconBtn(props) {
size={props.size || 'xs'} size={props.size || 'xs'}
icon={<FiMinimize />} icon={<FiMinimize />}
colorScheme='white' colorScheme='white'
variant='outline'
isRound
borderColor='#fff1'
onClick={clickhandler} onClick={clickhandler}
_focus={{ boxShadow: 'none' }} _focus={{ boxShadow: 'none' }}
{...rest} {...rest}
@@ -1,21 +1,68 @@
import { IconButton } from '@chakra-ui/button'; import { Button, IconButton } from '@chakra-ui/button';
import {
AlertDialog,
AlertDialogBody,
AlertDialogContent,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogOverlay,
} from '@chakra-ui/modal';
import { Tooltip } from '@chakra-ui/tooltip'; import { Tooltip } from '@chakra-ui/tooltip';
import { useRef, useState } from 'react';
import { FiPower } from 'react-icons/fi'; import { FiPower } from 'react-icons/fi';
export default function QuitIconBtn(props) { export default function QuitIconBtn(props) {
const { clickhandler, ...rest } = props; const { clickhandler, ...rest } = props;
const [isOpen, setIsOpen] = useState(false);
const onClose = () => setIsOpen(false);
const cancelRef = useRef();
const handleShutdown = () => {
onClose();
clickhandler();
};
return ( return (
<Tooltip label='Quit Application'> <>
<IconButton <Tooltip label='Quit Application'>
size={props.size || 'xs'} <IconButton
icon={<FiPower />} size={props.size || 'xs'}
colorScheme='red' icon={<FiPower />}
variant='outline' colorScheme='red'
isRound variant='outline'
onClick={clickhandler} isRound
_focus={{ boxShadow: 'none' }} onClick={() => setIsOpen(true)}
{...rest} _focus={{ boxShadow: 'none' }}
/> {...rest}
</Tooltip> />
</Tooltip>
<AlertDialog
isOpen={isOpen}
leastDestructiveRef={cancelRef}
onClose={onClose}
>
<AlertDialogOverlay>
<AlertDialogContent>
<AlertDialogHeader fontSize='lg' fontWeight='bold'>
Server Shutdown
</AlertDialogHeader>
<AlertDialogBody>
This will shutdown the program and all running servers. Are you
sure?
</AlertDialogBody>
<AlertDialogFooter>
<Button ref={cancelRef} onClick={onClose}>
Cancel
</Button>
<Button colorScheme='red' onClick={handleShutdown} ml={3}>
Shutdown
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogOverlay>
</AlertDialog>
</>
); );
} }
@@ -10,9 +10,6 @@ export default function SettingsIconBtn(props) {
size={props.size || 'xs'} size={props.size || 'xs'}
icon={<FiSettings />} icon={<FiSettings />}
colorScheme='white' colorScheme='white'
variant='outline'
isRound
borderColor='#fff1'
onClick={clickhandler} onClick={clickhandler}
_focus={{ boxShadow: 'none' }} _focus={{ boxShadow: 'none' }}
{...rest} {...rest}
@@ -0,0 +1,19 @@
import { IconButton } from '@chakra-ui/button';
import { Tooltip } from '@chakra-ui/tooltip';
import { FiUpload } from 'react-icons/fi';
export default function UploadIconBtn(props) {
const { clickhandler, ...rest } = props;
return (
<Tooltip label='Upload File'>
<IconButton
size={props.size || 'xs'}
icon={<FiUpload />}
colorScheme='white'
onClick={clickhandler}
_focus={{ boxShadow: 'none' }}
{...rest}
/>
</Tooltip>
);
}
+4
View File
@@ -0,0 +1,4 @@
{
"dependencies": {},
"devDependencies": {}
}
+70
View File
@@ -0,0 +1,70 @@
# Getting Started with Create React App
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `yarn start`
Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.\
You will also see any lint errors in the console.
### `yarn test`
Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `yarn build`
Builds the app for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `yarn eject`
**Note: this is a one-way operation. Once you `eject`, you cant go back!**
If you arent satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
### Code Splitting
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
### Analyzing the Bundle Size
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
### Making a Progressive Web App
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
### Advanced Configuration
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
### Deployment
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
### `yarn build` fails to minify
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

+21
View File
@@ -0,0 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg" width="275" height="275" fill="none" viewBox="0 0 275 275">
<path fill="#4D4D4D" d="M49.83 133.919c0-18.343 3.531-34.692 10.595-49.048 7.064-14.355 17.204-25.464 30.42-33.325 13.33-7.861 28.768-11.792 46.313-11.792 24.952 0 45.288 7.633 61.011 22.9 15.837 15.267 24.666 36.003 26.489 62.207l.342 12.647c0 28.369-7.918 51.155-23.755 68.359-15.836 17.09-37.085 25.635-63.745 25.635-26.66 0-47.965-8.545-63.916-25.635-15.836-17.09-23.755-40.332-23.755-69.726v-2.222zm49.389 3.589c0 17.545 3.304 30.989 9.912 40.332 6.608 9.228 16.064 13.843 28.369 13.843 11.963 0 21.305-4.558 28.028-13.672 6.722-9.229 10.083-23.926 10.083-44.092 0-17.204-3.361-30.534-10.083-39.99-6.723-9.457-16.179-14.185-28.37-14.185-12.076 0-21.419 4.728-28.027 14.185-6.608 9.342-9.912 23.869-9.912 43.579z"/>
<mask id="a" width="177" height="193" x="49" y="39" maskUnits="userSpaceOnUse">
<path fill="#fff" d="M49.83 133.919c0-18.343 3.531-34.692 10.595-49.048 7.064-14.355 17.204-25.464 30.42-33.325 13.33-7.861 28.768-11.792 46.313-11.792 24.952 0 45.288 7.633 61.011 22.9 15.837 15.267 24.666 36.003 26.489 62.207l.342 12.647c0 28.369-7.918 51.155-23.755 68.359-15.836 17.09-37.085 25.635-63.745 25.635-26.66 0-47.965-8.545-63.916-25.635-15.836-17.09-23.755-40.332-23.755-69.726v-2.222zm49.389 3.589c0 17.545 3.304 30.989 9.912 40.332 6.608 9.228 16.064 13.843 28.369 13.843 11.963 0 21.305-4.558 28.028-13.672 6.722-9.229 10.083-23.926 10.083-44.092 0-17.204-3.361-30.534-10.083-39.99-6.723-9.457-16.179-14.185-28.37-14.185-12.076 0-21.419 4.728-28.027 14.185-6.608 9.342-9.912 23.869-9.912 43.579z"/>
</mask>
<g mask="url(#a)">
<path fill="url(#paint0_linear)" d="M19.07 95.347c15.556-6.524 14.288-34.989 22.08-30.11 9.74 6.099 0-23.084 13.549-16.56 21.714 10.455-10.977 18.645 12.296 32.007 23.274 13.362 117.508 24.346 150.898 90.045 12.812 25.21 19.341 32.653 21.836 34.543 1.508-.13 1.78 1.349 0 0-.38.033-.838.168-1.362.476-5.516 3.245-106.876 85.867-148.54 34.124C48.162 188.129 2.25 140.221 14.31 127.463c9.648-10.205 1.393-25.103 4.76-32.116z"/>
<path fill="url(#paint1_linear)" d="M42.153 69.754c-7.791-4.88-.412 13.939-24.087 22.582-3.366 7.013 4.63 19.904-5.018 30.109-12.06 12.757 37.623 69.197 79.288 120.94 41.664 51.743 123.453-24.356 128.969-27.6 5.515-3.245 14.092 10.618-10.114-37.154-33.382-65.882-126.481-75.197-149.59-98.582-20.018-20.26 33.92-42.547 10.662-37.896-30.11 6.022-20.37 33.7-30.11 27.6z"/>
</g>
<defs>
<linearGradient id="paint0_linear" x1="93.96" x2="67.62" y1="-33.993" y2="351.251" gradientUnits="userSpaceOnUse">
<stop/>
<stop offset="1" stop-color="#414141" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint1_linear" x1="88.433" x2="62.092" y1="-37.321" y2="347.924" gradientUnits="userSpaceOnUse">
<stop stop-color="#FF005C" stop-opacity=".74"/>
<stop offset="0"/>
<stop offset="1" stop-color="#242424" stop-opacity="0"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

+1
View File
@@ -0,0 +1 @@
window.require = require;
+104
View File
@@ -0,0 +1,104 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; style-src 'unsafe-inline'; script-src 'self'"
/>
<title>ontime</title>
<style>
body {
-webkit-user-select: none;
user-select: none;
-webkit-app-region: drag;
animation: fadein 0s;
background-color: #0005;
}
h1 {
font-family: Roboto, sans-serif;
font-weight: 100;
color: #fffa;
font-size: 24px;
}
.container {
margin-top: 5vh;
display: grid;
place-items: center;
}
.lds-ellipsis {
display: inline-block;
position: relative;
width: 71px;
}
.lds-ellipsis div {
position: absolute;
width: 8px;
height: 8px;
border-radius: 50%;
background: #ff7597aa;
animation-timing-function: cubic-bezier(0, 1, 1, 0);
}
.lds-ellipsis div:nth-child(1) {
left: 8px;
animation: lds-ellipsis1 0.6s infinite;
}
.lds-ellipsis div:nth-child(2) {
left: 8px;
animation: lds-ellipsis2 0.6s infinite;
}
.lds-ellipsis div:nth-child(3) {
left: 32px;
animation: lds-ellipsis2 0.6s infinite;
}
.lds-ellipsis div:nth-child(4) {
left: 56px;
animation: lds-ellipsis3 0.6s infinite;
}
@keyframes fadein {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes lds-ellipsis1 {
0% {
transform: scale(0);
}
100% {
transform: scale(1);
}
}
@keyframes lds-ellipsis3 {
0% {
transform: scale(1);
}
100% {
transform: scale(0);
}
}
@keyframes lds-ellipsis2 {
0% {
transform: translate(0, 0);
}
100% {
transform: translate(24px, 0);
}
}
</style>
</head>
<body>
<div class="container">
<img src="../../assets/logo.png" />
<h1>ontime · event timers</h1>
<div class="lds-ellipsis">
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>
</body>
</html>
+260
View File
@@ -0,0 +1,260 @@
const {
app,
BrowserWindow,
Menu,
globalShortcut,
Tray,
dialog,
ipcMain,
shell,
Notification,
} = require('electron');
const path = require('path');
const env = process.env.NODE_ENV || 'prod';
let loaded = 'Nothing loaded';
let isQuitting = false;
const nodePath =
env != 'prod'
? path.join('file://', __dirname, 'src/app.js')
: path.join('file://', __dirname, '../', 'extraResources', 'src/app.js');
(async () => {
try {
const { startServer, startOSCServer, startOSCClient } = await import(
nodePath
);
// Start express server
loaded = startServer();
// Start OSC Server (API)
startOSCServer();
// Start OSC Client (Feedback)
startOSCClient();
} catch (error) {
loaded = error;
}
})();
// Load Icons
// TODO: Icons appear pixelated
const trayIcon = path.join(__dirname, './assets/images/logos/LOGO-512.png');
const appIcon = path.join(__dirname, './assets/images/logos/LOGO-512.png');
function showNotification(text) {
new Notification({
title: 'ontime',
body: text,
silent: true,
}).show();
}
let win;
let splash;
let tray = null;
// Ensure there isn't another instance of the app running already
const lock = app.requestSingleInstanceLock();
if (!lock) {
dialog.showErrorBox(
'Multiple instances',
'An instance if the App is already running.'
);
app.quit();
return;
} else {
app.on('second-instance', (event, commandLine, workingDirectory) => {
// Someone tried to run a second instance, we should focus our window.
if (win) {
if (win.isMinimized()) win.restore();
win.show();
}
});
}
function createWindow() {
// create a new `splash`-Window
splash = new BrowserWindow({
width: 333,
height: 333,
transparent: true,
icon: appIcon,
resizable: false,
frame: false,
alwaysOnTop: true,
});
splash.loadURL(`file://${__dirname}/electron/splash/splash.html`);
win = new BrowserWindow({
width: 1920,
height: 1000,
minWidth: 500,
minHeight: 530,
maxWidth: 1920,
maxHeight: 1440,
backgroundColor: '#202020',
icon: appIcon,
show: false,
textAreasAreResizable: false,
enableWebSQL: false,
webPreferences: {
preload: path.join(__dirname, './electron/preload.js'),
// TODO: what are recommended alternatives to node integration?
nodeIntegration: true,
contextIsolation: false,
enableRemoteModule: true,
},
});
win.setMenu(null);
// Load page served by node
const reactApp =
env == 'prod'
? 'http://localhost:4001/editor'
: 'http://localhost:3000/editor';
win.loadURL(reactApp).then(() => {
win.webContents.setBackgroundThrottling(false);
});
}
app.whenReady().then(() => {
createWindow();
/* ======================================
* CONTEXT MENU CREATION ON REACT SIDE
// Create context menu
// const contextMenu = new Menu();
// contextMenu.append(
// new MenuItem({
// label: 'Build context menu here',
// })
// );
// open context menu when clicked
// win.webContents.on('context-menu', (e, params) => {
// contextMenu.popup(win, params.x, params.y);
// });
* ======================================
*/
// register global shortcuts
// (available regardless of wheter app is in focus)
// bring focus to window
globalShortcut.register('Alt+1', () => {
win.show();
});
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) {
createWindow();
}
});
win.once('ready-to-show', () => {
setTimeout(() => {
// window stuff
win.show();
splash.destroy();
showNotification(loaded.toString());
// tray stuff
// TODO: get IP Address
tray.setToolTip(loaded);
}, 2000);
});
// Hide on close
win.on('close', function (event) {
event.preventDefault();
if (!isQuitting) {
showNotification('App running in background');
win.hide();
return false;
}
return true;
});
// create tray
// TODO: Design better icon
tray = new Tray(trayIcon);
// TODO: Move to separate file
// Define context menu
const trayMenuTemplate = [
{
label: 'Show App',
click: () => win.show(),
},
{
label: 'Close',
click: () => {
win.destroy();
app.quit();
},
},
];
const trayContextMenu = Menu.buildFromTemplate(trayMenuTemplate);
tray.setContextMenu(trayContextMenu);
});
// unregister shortcuts before quitting
app.once('will-quit', () => {
globalShortcut.unregisterAll();
});
// Get messages from react
// Test message
ipcMain.on('test-message', (event, arg) => {
showNotification('testing 1-2', arg);
});
// Terminate
ipcMain.on('shutdown', (event, arg) => {
console.log('Got IPC shutdown');
// terminate node service
(async () => {
const { shutdown } = await import(nodePath);
// Shutdown service
await shutdown();
})();
isQuitting = true;
tray.destroy();
win.destroy();
app.quit();
});
// Window manipulation
ipcMain.on('set-window', (event, arg) => {
console.log('Got IPC set-window', arg);
if (arg === 'to-max') {
// window full
win.setContentSize(1920, 1000);
win.setPosition(0, 0);
} else if (arg === 'to-tray') {
// window to tray
win.hide();
}
});
// Open links external
ipcMain.on('send-to-link', (event, arg) => {
console.log('Got IPC send-to-link', arg);
// send to help URL
if (arg === 'help') {
shell.openExternal('https://cpvalente.gitbook.io/ontime/');
}
});
+76 -17
View File
@@ -1,21 +1,80 @@
{ {
"name": "passport-example", "name": "ontime",
"version": "0.0.1", "version": "0.1.0",
"description": "Example with Passport (http://www.passportjs.org/)", "author": "Carlos Valente",
"type": "module", "description": "Time keeping for live events",
"dependencies": { "repository": "https://github.com/cpvalente/ontime",
"body-parser": "~1.19.0", "keywords": [ "lighdev", "ontime", "timer" ],
"express": "~4.17.1", "license": "MIT",
"express-session": "~1.17.1", "main": "main.js",
"lowdb": "2.1.0", "devDependencies": {
"multer": "^1.4.2", "concurrently": "^6.1.0",
"nanoid": "^3.1.22", "electron": "^13.1.2",
"node-osc": "6.0.2", "electron-builder": "^22.11.3",
"passport": "~0.4.1", "eslint": "^7.26.0",
"passport-local": "~1.0.0", "eslint-config-airbnb": "^18.2.1",
"socket.io": "^4.0.0" "eslint-plugin-import": "^2.22.1",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-react": "^7.23.2",
"eslint-plugin-react-hooks": "^4.2.0",
"eslint-plugin-simple-import-sort": "^7.0.0"
}, },
"scripts": { "scripts": {
"start": "node src/app.js" "nodestart": "NODE_ENV=development node app.js",
} "start": "NODE_ENV=development electron .",
"pack": "electron-builder --dir",
"dist": "electron-builder",
"dist-win": "electron-builder --publish=never --x64 --win",
"dist-mac": "electron-builder --publish=never --x64 --mac",
"dist-all": "electron-builder -mw"
},
"build": {
"productName": "ontime",
"appId": "no.lightdev.ontime",
"asar": true,
"dmg": {
"artifactName": "ontime-macOS.dmg",
"icon": "icon.icns"
},
"mac": {
"target": "dmg",
"category": "public.app-category.productivity",
"icon": "icon.icns"
},
"win": {
"target": "nsis"
},
"nsis": {
"artifactName": "ontime-win64.exe",
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"deleteAppDataOnUninstall": true,
"installerIcon": "icon.ico"
},
"files": [
"**/*",
"assets/"
],
"directories": {
"buildResources": "./assets/"
},
"extraResources": [
{
"from": "../client/build",
"to": "extraResources/client/build",
"filter": [
"**/*"
]
},
{
"from": "src",
"to": "extraResources/src",
"filter": [
"**/*"
]
}
]
},
"postinstall": "electron-builder install-app-deps"
} }
+64 -19
View File
@@ -3,9 +3,15 @@ import { config } from './config/config.js';
// init database // init database
import { Low, JSONFile } from 'lowdb'; import { Low, JSONFile } from 'lowdb';
import { join } from 'path';
const file = join('data/', config.database.filename); import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const env = process.env.NODE_ENV || 'prod';
const file = path.join(__dirname, 'data/', config.database.filename);
const adapter = new JSONFile(file); const adapter = new JSONFile(file);
export const db = new Low(adapter); export const db = new Low(adapter);
@@ -34,9 +40,6 @@ import { router as eventsRouter } from './routes/eventsRouter.js';
import { router as eventRouter } from './routes/eventRouter.js'; import { router as eventRouter } from './routes/eventRouter.js';
import { router as ontimeRouter } from './routes/ontimeRouter.js'; import { router as ontimeRouter } from './routes/ontimeRouter.js';
// Setup default port
const port = process.env.PORT || config.server.port;
// Global Objects // Global Objects
import { EventTimer } from './classes/EventTimer.js'; import { EventTimer } from './classes/EventTimer.js';
@@ -60,30 +63,72 @@ app.use('/events', eventsRouter);
app.use('/event', eventRouter); app.use('/event', eventRouter);
app.use('/ontime', ontimeRouter); app.use('/ontime', ontimeRouter);
// implement general router // serve react
app.get('/', (req, res) => { app.use(
res.send('ontime API'); express.static(
path.join(__dirname, env == 'prod' ? '../' : '../../', 'client/build')
)
);
app.get('*', (req, res) => {
res.sendFile(
path.resolve(
__dirname,
env == 'prod' ? '../' : '../../',
'client',
'build',
'index.html'
)
);
}); });
// Implement route for errors // Implement route for errors
app.use((err, req, res, next) => { app.use((err, req, res, next) => {
console.error(err.stack); res.status(500).send(err.stack);
res.status(500).send('Something broke!');
}); });
// create HTTP server // create HTTP server
const server = http.createServer(app); const server = http.createServer(app);
// init timer export const startServer = (overrideConfig = null) => {
global.timer = new EventTimer(server, config); // Setup default port
global.timer.setupWithEventList(data.events); const serverPort = overrideConfig?.port || config.server.port;
// Start server // Start server
server.listen(port, () => const returnMessage = `HTTP Server is listening on port ${serverPort}`;
console.log(`HTTP Server is listening on port ${port}`) server.listen(serverPort, '0.0.0.0', () => console.log(returnMessage));
);
// init timer
global.timer = new EventTimer(server, config);
global.timer.setupWithEventList(data.events);
return returnMessage;
};
// Start OSC server // Start OSC server
import { initiateOSC } from './controllers/OscController.js'; import { initiateOSC, shutdownOSCServer } from './controllers/OscController.js';
initiateOSC(config.osc); 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');
// shutdown express server
server.close();
// shutdown OSC Server
shutdownOSCServer();
// shutdown OSC Client
// shutdown timer
global.timer.shutdown();
};
+8
View File
@@ -83,6 +83,14 @@ export class EventTimer extends Timer {
this._listenToConnections(); this._listenToConnections();
} }
/**
* @description Shutdown process
*/
shutdown() {
console.log('Closing socket server');
this.io.close();
}
// send current timer // send current timer
broadcastTimer() { broadcastTimer() {
this.io.emit('timer', this.getObject()); this.io.emit('timer', this.getObject());
+1
View File
@@ -11,5 +11,6 @@ export const config = {
}, },
osc: { osc: {
port: 8888, port: 8888,
portOut: 8889,
}, },
}; };
+7 -1
View File
@@ -1,7 +1,13 @@
import { Server } from 'node-osc'; import { Server } from 'node-osc';
let oscServer = null;
export const shutdownOSCServer = () => {
if (oscServer != null) oscServer.close();
};
export const initiateOSC = (config) => { export const initiateOSC = (config) => {
const oscServer = new Server(config.port, '0.0.0.0', () => { oscServer = new Server(config.port, '0.0.0.0', () => {
console.log(`OSC Server is listening on port ${config.port}`); console.log(`OSC Server is listening on port ${config.port}`);
}); });
+40 -13
View File
@@ -1,6 +1,9 @@
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
// get database // get database
import { db, data } from '../app.js'; import { db, data } from '../app.js';
import fs from 'fs';
import { import {
event as eventDef, event as eventDef,
delay as delayDef, delay as delayDef,
@@ -9,6 +12,9 @@ import {
import { dbModel } from '../data/dataModel.js'; import { dbModel } from '../data/dataModel.js';
import { networkInterfaces } from 'os'; import { networkInterfaces } from 'os';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
function getEventTitle() { function getEventTitle() {
return data.event.title; return data.event.title;
} }
@@ -24,6 +30,7 @@ async function deleteFile(file) {
// parses version 1 of the data system // parses version 1 of the data system
async function parsev1(jsonData) { async function parsev1(jsonData) {
let numEntries = 0;
if ('events' in jsonData) { if ('events' in jsonData) {
let events = []; let events = [];
let ids = []; let ids = [];
@@ -46,15 +53,19 @@ async function parsev1(jsonData) {
isPublic: e.isPublic, isPublic: e.isPublic,
id: e.id, id: e.id,
}); });
numEntries++;
} else if (e.type === 'delay') { } else if (e.type === 'delay') {
events.push({ ...delayDef, duration: e.duration }); events.push({ ...delayDef, duration: e.duration });
numEntries++;
} else if (e.type === 'block') { } else if (e.type === 'block') {
events.push({ ...blockDef }); events.push({ ...blockDef });
numEntries++;
} }
} }
// write to db // write to db
db.data.events = events; db.data.events = events;
db.write(); db.write();
console.log(`Uploaded file with ${numEntries} entries`);
} }
if ('event' in jsonData) { if ('event' in jsonData) {
@@ -83,7 +94,9 @@ async function parsev1(jsonData) {
// Returns - // Returns -
export const dbDownload = async (req, res) => { export const dbDownload = async (req, res) => {
const fileTitle = getEventTitle() || 'ontime events'; const fileTitle = getEventTitle() || 'ontime events';
res.download('db.json', `${fileTitle}.json`, (err) => { const dbFile = path.resolve(__dirname, '../', 'data/db.json');
res.download(dbFile, `${fileTitle}.json`, (err) => {
if (err) { if (err) {
res.status(500).send({ res.status(500).send({
message: 'Could not download the file. ' + err, message: 'Could not download the file. ' + err,
@@ -92,15 +105,7 @@ export const dbDownload = async (req, res) => {
}); });
}; };
// Create controller for POST request to '/ontime/db' const upload = async (file, req, res) => {
// Returns -
export const dbUpload = async (req, res) => {
if (!req.file) {
res.status(400).send({ message: 'File not found' });
return;
}
const file = req.file.path;
if (!fs.existsSync(file)) { if (!fs.existsSync(file)) {
res.status(500).send({ message: 'Upload failed' }); res.status(500).send({ message: 'Upload failed' });
return; return;
@@ -108,8 +113,8 @@ export const dbUpload = async (req, res) => {
try { try {
// get file // get file
let rawdata = fs.readFileSync(file); const rawdata = fs.readFileSync(file);
let uploadedJson = JSON.parse(rawdata); const uploadedJson = JSON.parse(rawdata);
// delete file // delete file
deleteFile(file); deleteFile(file);
@@ -158,4 +163,26 @@ export const getInfo = async (req, res) => {
res.status(200).send({ res.status(200).send({
networkInterfaces: ni, networkInterfaces: ni,
}); });
// Create controller for POST request to '/ontime/db'
// Returns -
export const dbUpload = async (req, res) => {
if (!req.file) {
res.status(400).send({ message: 'File not found' });
return;
}
const file = req.file.path;
upload(file, req, res);
};
// Create controller for POST request to '/ontime/dbpath'
// Returns -
export const dbPathToUpload = async (req, res) => {
if (!req.body.path) {
res.status(400).send({ message: 'Path to file not found' });
return;
}
upload(req.body.path, req, res);
}; };
+28
View File
@@ -0,0 +1,28 @@
{
"type": "module",
"dependencies": {
"body-parser": "~1.19.0",
"express": "~4.17.1",
"express-session": "~1.17.1",
"lowdb": "2.1.0",
"multer": "^1.4.2",
"nanoid": "^3.1.22",
"node-osc": "6.0.2",
"passport": "~0.4.1",
"passport-local": "~1.0.0",
"socket.io": "^4.0.0"
},
"devDependencies": {
"eslint": "^7.26.0",
"eslint-config-airbnb": "^18.2.1",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-react": "^7.23.2",
"eslint-plugin-react-hooks": "^4.2.0",
"eslint-plugin-simple-import-sort": "^7.0.0"
},
"scripts": {
"nodestart": "nodemon app.js",
"start": "node app.js"
}
}
+4
View File
@@ -6,6 +6,7 @@ import {
dbDownload, dbDownload,
dbUpload, dbUpload,
getInfo, getInfo,
dbPathToUpload,
} from '../controllers/ontimeController.js'; } from '../controllers/ontimeController.js';
// create route between controller and '/ontime/db' endpoint // create route between controller and '/ontime/db' endpoint
@@ -16,3 +17,6 @@ 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/dbpath' endpoint
router.post('/dbpath', dbPathToUpload);
+2189
View File
File diff suppressed because it is too large Load Diff
+2865 -518
View File
File diff suppressed because it is too large Load Diff
+4
View File
@@ -0,0 +1,4 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1