Merge branch 'master' into feat/info
@@ -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 }}
|
||||
@@ -28,10 +28,7 @@ TODO.md
|
||||
# working stuff
|
||||
_SS/
|
||||
.vscode/launch.json
|
||||
server/db.json
|
||||
db.json
|
||||
.eslintrc.json
|
||||
yarn.lock
|
||||
package.json
|
||||
package-lock.json
|
||||
db.json
|
||||
db backup.json
|
||||
|
||||
@@ -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,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 can’t go back!**
|
||||
|
||||
If you aren’t 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 you’re on your own.
|
||||
|
||||
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t 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)
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# Table of contents
|
||||
|
||||
* [Initial page](README.md)
|
||||
|
||||
@@ -7,6 +7,10 @@ body,
|
||||
html,
|
||||
.App {
|
||||
margin: 0px auto;
|
||||
overflow: hidden;
|
||||
overflow: clip;
|
||||
height: 100vh;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@ export const downloadEvents = async () => {
|
||||
};
|
||||
|
||||
export const uploadEvents = async (file) => {
|
||||
console.log('uploading', file);
|
||||
const formData = new FormData();
|
||||
formData.append('jsondb', file); // appending file
|
||||
await axios
|
||||
@@ -49,3 +48,7 @@ export const uploadEvents = async (file) => {
|
||||
.then((res) => console.log(res.data))
|
||||
.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 { Tooltip } from '@chakra-ui/tooltip';
|
||||
import { FiChevronsDown } from 'react-icons/fi';
|
||||
|
||||
export default function ApplyIconBtn(props) {
|
||||
const { clickhandler, ...rest } = props;
|
||||
return (
|
||||
<IconButton
|
||||
size={props.size || 'xs'}
|
||||
icon={<FiChevronsDown />}
|
||||
colorScheme='orange'
|
||||
onClick={clickhandler}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
{...rest}
|
||||
/>
|
||||
<Tooltip label='Apply delays'>
|
||||
<IconButton
|
||||
size={props.size || 'xs'}
|
||||
icon={<FiChevronsDown />}
|
||||
colorScheme='orange'
|
||||
onClick={clickhandler}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
{...rest}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,13 +6,13 @@ export default function LockIconBtn(props) {
|
||||
const { clickhandler, active, ref } = props;
|
||||
return (
|
||||
<Tooltip label='Lock cursor to current'>
|
||||
<IconButton
|
||||
<IconButton
|
||||
ref={ref}
|
||||
size={props.size || 'xs'}
|
||||
icon={<FiTarget />}
|
||||
color={active ? 'pink.100' : 'pink.300'}
|
||||
borderColor={active ? undefined : 'pink.300'}
|
||||
backgroundColor={active ? 'pink.300' : undefined}
|
||||
backgroundColor={active ? 'pink.400' : undefined}
|
||||
variant={active ? 'solid' : 'outline'}
|
||||
onClick={clickhandler}
|
||||
_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 {
|
||||
display: 'block';
|
||||
overflow-x: hidden;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.inline {
|
||||
display: inline;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
import { lazy, useEffect } from 'react';
|
||||
import { Heading } from '@chakra-ui/layout';
|
||||
import { Box } from '@chakra-ui/layout';
|
||||
@@ -28,50 +29,33 @@ export default function Editor() {
|
||||
|
||||
<div className={styles.mainContainer}>
|
||||
<Box id='settings' className={styles.settings}>
|
||||
<MenuBar onOpen={onOpen} onClose={onClose} />
|
||||
<MenuBar onOpen={onOpen} />
|
||||
</Box>
|
||||
|
||||
<Box className={styles.editor}>
|
||||
<Heading size='lg' paddingBottom={'0.25em'}>
|
||||
Event List
|
||||
</Heading>
|
||||
<NumberedText number={1} text={'Manage events'} />
|
||||
<h1>Event List</h1>
|
||||
<div className={styles.content}>
|
||||
<EventListWrapper />
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<Box className={styles.messages}>
|
||||
<Heading size='lg' paddingBottom={'0.25em'}>
|
||||
Display Messages
|
||||
</Heading>
|
||||
<NumberedText
|
||||
number={3}
|
||||
text={'Show realtime messages on different screens'}
|
||||
/>
|
||||
<h1>Display Messages</h1>
|
||||
<div className={styles.content}>
|
||||
<MessageControl />
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<Box className={styles.playback}>
|
||||
<Heading size='lg' paddingBottom={'0.25em'}>
|
||||
Time Control
|
||||
</Heading>
|
||||
<NumberedText number={2} text={'Control timers'} />
|
||||
<h1>Timer Control</h1>
|
||||
<div className={styles.content}>
|
||||
<PlaybackControl />
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<Box className={styles.info} borderRadius='0.5em' overflowX='auto'>
|
||||
<Heading size='lg' paddingBottom={'0.25em'}>
|
||||
Info
|
||||
</Heading>
|
||||
<NumberedText number={4} text={'Running Info'} />
|
||||
<div className={styles.content}>
|
||||
<Info />
|
||||
</div>
|
||||
<h1>Info</h1>
|
||||
<div className={styles.content}></div>
|
||||
</Box>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -69,6 +69,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.5vh;
|
||||
color: rgba(255, 255, 255, 0.63);
|
||||
padding-bottom: 0.25em;
|
||||
}
|
||||
|
||||
.mainContainer > div {
|
||||
border-radius: 0.5em;
|
||||
height: 100%;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Menu, MenuButton, MenuItem, MenuList } from '@chakra-ui/menu';
|
||||
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) {
|
||||
const { showAdd, showDelay, showBlock, actionHandler } = props;
|
||||
@@ -16,7 +16,7 @@ export default function ActionButtons(props) {
|
||||
as={IconButton}
|
||||
aria-label='Options'
|
||||
size='xs'
|
||||
icon={<FiZap />}
|
||||
icon={<FiPlus />}
|
||||
_expanded={{ bg: 'orange.300', color: 'white' }}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
backgroundColor={'orange.200'}
|
||||
|
||||
@@ -6,7 +6,7 @@ import EventTimes from 'common/components/eventTimes/EventTimes';
|
||||
import EventTimesVertical from 'common/components/eventTimes/EventTimesVertical';
|
||||
import EditableText from 'common/input/EditableText';
|
||||
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 { millisToMinutes } from 'common/dateConfig';
|
||||
import style from './EventBlock.module.css';
|
||||
@@ -94,7 +94,7 @@ const ExpandedBlock = (props) => {
|
||||
onClick={() => props.setCollapsed(true)}
|
||||
/>
|
||||
<div className={style.actionOverlay}>
|
||||
<VisibleIconBtn actionHandler={actionHandler} active={data.isPublic} />
|
||||
<PublicIconBtn actionHandler={actionHandler} active={data.isPublic} />
|
||||
<ActionButtons
|
||||
showAdd
|
||||
showDelay
|
||||
@@ -147,7 +147,7 @@ const CollapsedBlock = (props) => {
|
||||
onClick={() => props.setCollapsed(false)}
|
||||
/>
|
||||
<div className={style.actionOverlay}>
|
||||
<VisibleIconBtn actionHandler={actionHandler} active={data.isPublic} />
|
||||
<PublicIconBtn actionHandler={actionHandler} active={data.isPublic} />
|
||||
<ActionButtons
|
||||
showAdd
|
||||
showDelay
|
||||
|
||||
@@ -152,6 +152,8 @@
|
||||
font-size: 0.8em;
|
||||
float: right;
|
||||
padding-right: 1em;
|
||||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
/* ================ MORE ================ */
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
import { Menu, MenuButton, MenuItem, MenuList } from '@chakra-ui/menu';
|
||||
import { IconButton } from '@chakra-ui/button';
|
||||
import {
|
||||
FiTrash2,
|
||||
FiZap,
|
||||
FiPlus,
|
||||
FiClock,
|
||||
FiMinusCircle,
|
||||
} from 'react-icons/fi';
|
||||
import { FiTrash2, FiPlus, FiClock, FiMinusCircle } from 'react-icons/fi';
|
||||
import { Divider } from '@chakra-ui/layout';
|
||||
|
||||
export default function MenuActionButtons(props) {
|
||||
@@ -20,9 +14,9 @@ export default function MenuActionButtons(props) {
|
||||
<Menu isLazy lazyBehavior='unmount'>
|
||||
<MenuButton
|
||||
as={IconButton}
|
||||
aria-label='Options'
|
||||
aria-label='Create Menu'
|
||||
size={props.size || 'xs'}
|
||||
icon={<FiZap />}
|
||||
icon={<FiPlus />}
|
||||
_expanded={{ bg: 'orange.300', color: 'white' }}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
backgroundColor={'orange.200'}
|
||||
|
||||
@@ -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 SettingsIconBtn from './buttons/SettingsIconBtn';
|
||||
import InfoIconBtn from './buttons/InfoIconBtn';
|
||||
@@ -7,24 +9,105 @@ import MinIconBtn from './buttons/MinIconBtn';
|
||||
import QuitIconBtn from './buttons/QuitIconBtn';
|
||||
import style from './MenuBar.module.css';
|
||||
import HelpIconBtn from './buttons/HelpIconBtn';
|
||||
import UploadIconBtn from './buttons/UploadIconBtn';
|
||||
import { useRef } from 'react';
|
||||
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
|
||||
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 = () => {
|
||||
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 (
|
||||
<>
|
||||
<QuitIconBtn size='md' />
|
||||
<MaxIconBtn size='md' />
|
||||
<MinIconBtn size='md' />
|
||||
<QuitIconBtn size='lg' clickhandler={() => handleIPC('shutdown')} />
|
||||
<MaxIconBtn
|
||||
style={{ fontSize: '1.5em' }}
|
||||
size='lg'
|
||||
clickhandler={() => handleIPC('max')}
|
||||
/>
|
||||
<MinIconBtn
|
||||
style={{ fontSize: '1.5em' }}
|
||||
size='lg'
|
||||
clickhandler={() => handleIPC('min')}
|
||||
/>
|
||||
<div className={style.gap} />
|
||||
<HelpIconBtn size='md' disabled />
|
||||
<SettingsIconBtn size='md' disabled />
|
||||
<InfoIconBtn size='md' clickhandler={onOpen} />
|
||||
<DownloadIconBtn size='md' clickhandler={handleDownload} />
|
||||
<HelpIconBtn
|
||||
style={{ fontSize: '1.5em' }}
|
||||
size='lg'
|
||||
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'}
|
||||
icon={<FiDownload />}
|
||||
colorScheme='white'
|
||||
variant='outline'
|
||||
isRound
|
||||
borderColor='#fff1'
|
||||
onClick={clickhandler}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
{...rest}
|
||||
|
||||
@@ -10,9 +10,6 @@ export default function HelpIconBtn(props) {
|
||||
size={props.size || 'xs'}
|
||||
icon={<FiHelpCircle />}
|
||||
colorScheme='white'
|
||||
variant='outline'
|
||||
isRound
|
||||
borderColor='#fff1'
|
||||
onClick={clickhandler}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
{...rest}
|
||||
|
||||
@@ -10,9 +10,6 @@ export default function InfoIconBtn(props) {
|
||||
size={props.size || 'xs'}
|
||||
icon={<FiHome />}
|
||||
colorScheme='white'
|
||||
variant='outline'
|
||||
isRound
|
||||
borderColor='#fff1'
|
||||
onClick={clickhandler}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
{...rest}
|
||||
|
||||
@@ -10,9 +10,6 @@ export default function MaxIconBtn(props) {
|
||||
size={props.size || 'xs'}
|
||||
icon={<FiMaximize />}
|
||||
colorScheme='white'
|
||||
variant='outline'
|
||||
isRound
|
||||
borderColor='#fff1'
|
||||
onClick={clickhandler}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
{...rest}
|
||||
|
||||
@@ -10,9 +10,6 @@ export default function MinIconBtn(props) {
|
||||
size={props.size || 'xs'}
|
||||
icon={<FiMinimize />}
|
||||
colorScheme='white'
|
||||
variant='outline'
|
||||
isRound
|
||||
borderColor='#fff1'
|
||||
onClick={clickhandler}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
{...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 { useRef, useState } from 'react';
|
||||
import { FiPower } from 'react-icons/fi';
|
||||
|
||||
export default function QuitIconBtn(props) {
|
||||
const { clickhandler, ...rest } = props;
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const onClose = () => setIsOpen(false);
|
||||
const cancelRef = useRef();
|
||||
|
||||
const handleShutdown = () => {
|
||||
onClose();
|
||||
clickhandler();
|
||||
};
|
||||
|
||||
return (
|
||||
<Tooltip label='Quit Application'>
|
||||
<IconButton
|
||||
size={props.size || 'xs'}
|
||||
icon={<FiPower />}
|
||||
colorScheme='red'
|
||||
variant='outline'
|
||||
isRound
|
||||
onClick={clickhandler}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
{...rest}
|
||||
/>
|
||||
</Tooltip>
|
||||
<>
|
||||
<Tooltip label='Quit Application'>
|
||||
<IconButton
|
||||
size={props.size || 'xs'}
|
||||
icon={<FiPower />}
|
||||
colorScheme='red'
|
||||
variant='outline'
|
||||
isRound
|
||||
onClick={() => setIsOpen(true)}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
{...rest}
|
||||
/>
|
||||
</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'}
|
||||
icon={<FiSettings />}
|
||||
colorScheme='white'
|
||||
variant='outline'
|
||||
isRound
|
||||
borderColor='#fff1'
|
||||
onClick={clickhandler}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
{...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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"dependencies": {},
|
||||
"devDependencies": {}
|
||||
}
|
||||
@@ -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 can’t go back!**
|
||||
|
||||
If you aren’t 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 you’re on your own.
|
||||
|
||||
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t 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)
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 179 KiB |
|
After Width: | Height: | Size: 47 KiB |
@@ -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 |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 8.1 KiB |
@@ -0,0 +1 @@
|
||||
window.require = require;
|
||||
@@ -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>
|
||||
@@ -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/');
|
||||
}
|
||||
});
|
||||
@@ -1,21 +1,80 @@
|
||||
{
|
||||
"name": "passport-example",
|
||||
"version": "0.0.1",
|
||||
"description": "Example with Passport (http://www.passportjs.org/)",
|
||||
"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"
|
||||
"name": "ontime",
|
||||
"version": "0.1.0",
|
||||
"author": "Carlos Valente",
|
||||
"description": "Time keeping for live events",
|
||||
"repository": "https://github.com/cpvalente/ontime",
|
||||
"keywords": [ "lighdev", "ontime", "timer" ],
|
||||
"license": "MIT",
|
||||
"main": "main.js",
|
||||
"devDependencies": {
|
||||
"concurrently": "^6.1.0",
|
||||
"electron": "^13.1.2",
|
||||
"electron-builder": "^22.11.3",
|
||||
"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": {
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -3,9 +3,15 @@ import { config } from './config/config.js';
|
||||
|
||||
// init database
|
||||
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);
|
||||
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 ontimeRouter } from './routes/ontimeRouter.js';
|
||||
|
||||
// Setup default port
|
||||
const port = process.env.PORT || config.server.port;
|
||||
|
||||
// Global Objects
|
||||
import { EventTimer } from './classes/EventTimer.js';
|
||||
|
||||
@@ -60,30 +63,72 @@ app.use('/events', eventsRouter);
|
||||
app.use('/event', eventRouter);
|
||||
app.use('/ontime', ontimeRouter);
|
||||
|
||||
// implement general router
|
||||
app.get('/', (req, res) => {
|
||||
res.send('ontime API');
|
||||
// serve react
|
||||
app.use(
|
||||
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
|
||||
app.use((err, req, res, next) => {
|
||||
console.error(err.stack);
|
||||
res.status(500).send('Something broke!');
|
||||
res.status(500).send(err.stack);
|
||||
});
|
||||
|
||||
// create HTTP server
|
||||
const server = http.createServer(app);
|
||||
|
||||
// init timer
|
||||
global.timer = new EventTimer(server, config);
|
||||
global.timer.setupWithEventList(data.events);
|
||||
export const startServer = (overrideConfig = null) => {
|
||||
// Setup default port
|
||||
const serverPort = overrideConfig?.port || config.server.port;
|
||||
|
||||
// Start server
|
||||
server.listen(port, () =>
|
||||
console.log(`HTTP Server is listening on port ${port}`)
|
||||
);
|
||||
// Start server
|
||||
const returnMessage = `HTTP Server is listening on port ${serverPort}`;
|
||||
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
|
||||
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();
|
||||
};
|
||||
|
||||
@@ -83,6 +83,14 @@ export class EventTimer extends Timer {
|
||||
this._listenToConnections();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Shutdown process
|
||||
*/
|
||||
shutdown() {
|
||||
console.log('Closing socket server');
|
||||
this.io.close();
|
||||
}
|
||||
|
||||
// send current timer
|
||||
broadcastTimer() {
|
||||
this.io.emit('timer', this.getObject());
|
||||
|
||||
@@ -11,5 +11,6 @@ export const config = {
|
||||
},
|
||||
osc: {
|
||||
port: 8888,
|
||||
portOut: 8889,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import { Server } from 'node-osc';
|
||||
|
||||
let oscServer = null;
|
||||
|
||||
export const shutdownOSCServer = () => {
|
||||
if (oscServer != null) oscServer.close();
|
||||
};
|
||||
|
||||
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}`);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
// get database
|
||||
import { db, data } from '../app.js';
|
||||
import fs from 'fs';
|
||||
import {
|
||||
event as eventDef,
|
||||
delay as delayDef,
|
||||
@@ -9,6 +12,9 @@ import {
|
||||
import { dbModel } from '../data/dataModel.js';
|
||||
import { networkInterfaces } from 'os';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
function getEventTitle() {
|
||||
return data.event.title;
|
||||
}
|
||||
@@ -24,6 +30,7 @@ async function deleteFile(file) {
|
||||
|
||||
// parses version 1 of the data system
|
||||
async function parsev1(jsonData) {
|
||||
let numEntries = 0;
|
||||
if ('events' in jsonData) {
|
||||
let events = [];
|
||||
let ids = [];
|
||||
@@ -46,15 +53,19 @@ async function parsev1(jsonData) {
|
||||
isPublic: e.isPublic,
|
||||
id: e.id,
|
||||
});
|
||||
numEntries++;
|
||||
} else if (e.type === 'delay') {
|
||||
events.push({ ...delayDef, duration: e.duration });
|
||||
numEntries++;
|
||||
} else if (e.type === 'block') {
|
||||
events.push({ ...blockDef });
|
||||
numEntries++;
|
||||
}
|
||||
}
|
||||
// write to db
|
||||
db.data.events = events;
|
||||
db.write();
|
||||
console.log(`Uploaded file with ${numEntries} entries`);
|
||||
}
|
||||
|
||||
if ('event' in jsonData) {
|
||||
@@ -83,7 +94,9 @@ async function parsev1(jsonData) {
|
||||
// Returns -
|
||||
export const dbDownload = async (req, res) => {
|
||||
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) {
|
||||
res.status(500).send({
|
||||
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'
|
||||
// Returns -
|
||||
export const dbUpload = async (req, res) => {
|
||||
if (!req.file) {
|
||||
res.status(400).send({ message: 'File not found' });
|
||||
return;
|
||||
}
|
||||
|
||||
const file = req.file.path;
|
||||
const upload = async (file, req, res) => {
|
||||
if (!fs.existsSync(file)) {
|
||||
res.status(500).send({ message: 'Upload failed' });
|
||||
return;
|
||||
@@ -108,8 +113,8 @@ export const dbUpload = async (req, res) => {
|
||||
|
||||
try {
|
||||
// get file
|
||||
let rawdata = fs.readFileSync(file);
|
||||
let uploadedJson = JSON.parse(rawdata);
|
||||
const rawdata = fs.readFileSync(file);
|
||||
const uploadedJson = JSON.parse(rawdata);
|
||||
|
||||
// delete file
|
||||
deleteFile(file);
|
||||
@@ -158,4 +163,26 @@ export const getInfo = async (req, res) => {
|
||||
res.status(200).send({
|
||||
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);
|
||||
|
||||
};
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
dbDownload,
|
||||
dbUpload,
|
||||
getInfo,
|
||||
dbPathToUpload,
|
||||
} from '../controllers/ontimeController.js';
|
||||
|
||||
// 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
|
||||
router.get('/info', uploadJson, getInfo);
|
||||
|
||||
// create route between controller and '/ontime/dbpath' endpoint
|
||||
router.post('/dbpath', dbPathToUpload);
|
||||
|
||||