diff --git a/.github/workflows/autobuild.yml b/.github/workflows/autobuild.yml
new file mode 100644
index 000000000..e8953185b
--- /dev/null
+++ b/.github/workflows/autobuild.yml
@@ -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 }}
diff --git a/README.md b/README.md
index 35ec677ab..8b694edcf 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,2 @@
- [](https://opensource.org/licenses/MIT)
+# Initial page
-# License
-This project is licensed under the terms of the MIT license
\ No newline at end of file
diff --git a/SUMMARY.md b/SUMMARY.md
new file mode 100644
index 000000000..5da1733df
--- /dev/null
+++ b/SUMMARY.md
@@ -0,0 +1,4 @@
+# Table of contents
+
+* [Initial page](README.md)
+
diff --git a/client/package.json b/client/package.json
index 886e0b989..661b9195b 100644
--- a/client/package.json
+++ b/client/package.json
@@ -16,6 +16,7 @@
"react": "^17.0.1",
"react-beautiful-dnd": "^13.1.0",
"react-dom": "^17.0.1",
+ "react-fast-compare": "^3.2.0",
"react-icons": "^4.2.0",
"react-qr-code": "^1.1.1",
"react-query": "^3.13.5",
diff --git a/client/src/common/components/myProgressBar/MyProgressBar.jsx b/client/src/common/components/myProgressBar/MyProgressBar.jsx
index dda5e48c6..e7006cdaa 100644
--- a/client/src/common/components/myProgressBar/MyProgressBar.jsx
+++ b/client/src/common/components/myProgressBar/MyProgressBar.jsx
@@ -3,16 +3,21 @@ import styles from './MyProgressBar.module.css';
export default function MyProgressBar(props) {
const { now, complete, showElapsed } = props;
- const percentComplete = showElapsed
- ? clamp((100 - (now * 100) / complete), 0, 100)
- : clamp((now * 100) / complete, 0, 100)
+
+ let percentComplete = showElapsed ? 0 : 100;
+
+ if (now != null && complete != null) {
+ percentComplete = showElapsed
+ ? clamp(100 - (now * 100) / complete, 0, 100)
+ : clamp((now * 100) / complete, 0, 100);
+ }
return (
-
+
);
}
diff --git a/client/src/common/components/myProgressBar/MyProgressBar.module.css b/client/src/common/components/myProgressBar/MyProgressBar.module.css
index 6cfcb0c25..1a24b7fd4 100644
--- a/client/src/common/components/myProgressBar/MyProgressBar.module.css
+++ b/client/src/common/components/myProgressBar/MyProgressBar.module.css
@@ -1,13 +1,22 @@
-.progress {
+.progress,
+.progressCountdown {
height: 2vh;
background-color: rgba(255, 255, 255, 0.13);
border-radius: 4px;
}
+.progress {
+ background-color: rgba(255, 255, 255, 0.13);
+}
+
+.progressCountdown {
+ background-color: #ff7597;
+}
+
.progressBar {
width: 100%;
height: 2vh;
- background-color: rgba(255, 255, 255, 0.79);
+ background-color: rgb(255, 255, 255);
border-radius: 4px;
transition: 1s linear;
transition-property: width;
diff --git a/client/src/common/components/views/Paginator.jsx b/client/src/common/components/views/Paginator.jsx
index 28ed3e543..2412b49bd 100644
--- a/client/src/common/components/views/Paginator.jsx
+++ b/client/src/common/components/views/Paginator.jsx
@@ -12,7 +12,6 @@ export default function Paginator(props) {
const [page, setPage] = useState([]);
const [pages, setPages] = useState(0);
const [selPage, setSelPage] = useState(0);
- const [selected, setSelected] = useState(-1);
useEffect(() => {
if (events == null) return;
@@ -33,18 +32,6 @@ export default function Paginator(props) {
// if array is completely in past, show depending on SCROLL_PAST
}, [events, selPage, LIMIT_PER_PAGE]);
- useEffect(() => {
- // maybe nothing is selected
- if (events == null || selectedId == null) return;
-
- // which one is selected
- const s = events.filter((e) => e.id === selectedId);
-
- if (s.length < 1) return;
-
- setSelected(s[0].id);
- }, [events, selectedId]);
-
// every SCROLL_TIME go to the next array
useInterval(() => {
if (numEvents > LIMIT_PER_PAGE) {
@@ -68,9 +55,8 @@ export default function Paginator(props) {
{page.map((e) => {
- if (selectedState === 1) selectedState = 2;
- if (e.id === selected) selectedState = 1;
- else if (e.id > selected) selectedState = 2;
+ if (e.id === selectedId) selectedState = 1;
+ else if (selectedState === 1) selectedState = 2;
return (
);
})}
diff --git a/client/src/common/components/views/Paginator.module.css b/client/src/common/components/views/Paginator.module.css
index e52e2dcfc..5e58e99ad 100644
--- a/client/src/common/components/views/Paginator.module.css
+++ b/client/src/common/components/views/Paginator.module.css
@@ -26,11 +26,13 @@
margin-left: 0.5vw;
}
-.entryStart,
-.entryEnd {
+.entryTimes {
font-family: 'Open Sans', sans-serif;
- min-width: 5vw;
+ font-size: 1.4vw;
+
+ min-width: 10vw;
opacity: 0.9;
+ letter-spacing: 0.035em;
}
.entryTitle {
@@ -40,26 +42,42 @@
.entryPast,
.entryNow,
.entryFuture {
- background-color: rgba(255, 255, 255, 0.03);
display: flex;
font-size: 1.3vw;
padding: 0.2vh 1vw;
margin-bottom: 1.5vh;
+ border-radius: 3px;
}
.entryPast {
- color: #aaa;
+ background-color: rgba(255, 255, 255, 0.01);
font-size: 1.2vw;
}
+.entryPast > .entryTitle {
+ color: #999;
+}
+
.entryFuture {
+ background-color: rgba(255, 255, 255, 0.03);
color: #ddd;
}
.entryNow {
color: #fff;
line-height: 5vh;
- background-color: rgba(255, 255, 255, 0.09);
+ background-color: rgba(255, 255, 255, 0.07);
border-bottom: 0.5vh solid #ff7597aa;
- margin-left: -0.5vw;
+ margin-left: -0.5em;
+ padding-left: 0.5em;
+}
+
+.backstageInd {
+ color: #ff7597aa;
+ margin-left: auto;
+ margin-right: -0.5vw;
+}
+
+.backstageInd::before {
+ content: '*';
}
diff --git a/client/src/common/components/views/TodayItem.jsx b/client/src/common/components/views/TodayItem.jsx
index 04848cb9d..3238e9e2c 100644
--- a/client/src/common/components/views/TodayItem.jsx
+++ b/client/src/common/components/views/TodayItem.jsx
@@ -1,7 +1,7 @@
import { stringFromMillis } from 'common/dateConfig';
import style from './Paginator.module.css';
export default function TodayItem(props) {
- const { selected, timeStart, timeEnd, title } = props;
+ const { selected, timeStart, timeEnd, title, backstageEvent } = props;
// Format timers
const start = stringFromMillis(timeStart, false) || '';
@@ -13,9 +13,13 @@ export default function TodayItem(props) {
else if (selected === 2) selectStyle = style.entryFuture;
return (
-
{start}
-
{end}
+
{`${start} ยท ${end}`}
{title}
+ {backstageEvent &&
}
);
}
diff --git a/client/src/features/editors/Editor.jsx b/client/src/features/editors/Editor.jsx
index d1be42873..d0894a730 100644
--- a/client/src/features/editors/Editor.jsx
+++ b/client/src/features/editors/Editor.jsx
@@ -1,13 +1,19 @@
+
+import { lazy, useEffect } from 'react';
+import { Heading } from '@chakra-ui/layout';
import { Box } from '@chakra-ui/layout';
-import PlaybackControl from '../control/PlaybackControl';
-import MessageControl from '../control/MessageControl';
+import NumberedText from 'common/components/text/NumberedText';
import styles from './Editor.module.css';
-import EventListWrapper from './list/EventListWrapper';
import { useDisclosure } from '@chakra-ui/hooks';
import SettingsModal from '../modals/SettingsModal';
-import { useEffect } from 'react';
import MenuBar from 'features/menu/MenuBar';
+const EventListWrapper = lazy(() =>
+ import('features/editors/list/EventListWrapper')
+);
+const PlaybackControl = lazy(() => import('features/control/PlaybackControl'));
+const MessageControl = lazy(() => import('features/control/MessageControl'));
+
export default function Editor() {
const { isOpen, onOpen, onClose } = useDisclosure();
diff --git a/client/src/features/viewers/presenter/PresenterView.jsx b/client/src/features/viewers/presenter/PresenterView.jsx
index 53a74faab..edb8bb92b 100644
--- a/client/src/features/viewers/presenter/PresenterView.jsx
+++ b/client/src/features/viewers/presenter/PresenterView.jsx
@@ -71,11 +71,7 @@ export default function PresenterView(props) {
isPlaying ? style.progressContainer : style.progressContainerPaused
}
>
-
+
)}
diff --git a/client/src/features/viewers/production/lower/LowerClean.jsx b/client/src/features/viewers/production/lower/LowerClean.jsx
index 780a3e1ef..fb9fc8c40 100644
--- a/client/src/features/viewers/production/lower/LowerClean.jsx
+++ b/client/src/features/viewers/production/lower/LowerClean.jsx
@@ -33,6 +33,7 @@ export default function LowerClean(props) {
// calculate transition times
useEffect(() => {});
+
// Format messages
const showLowerMessage = lower.text !== '' && lower.visible;
diff --git a/client/src/features/viewers/production/lower/LowerLines.jsx b/client/src/features/viewers/production/lower/LowerLines.jsx
index 7c2c28d66..f96963c1d 100644
--- a/client/src/features/viewers/production/lower/LowerLines.jsx
+++ b/client/src/features/viewers/production/lower/LowerLines.jsx
@@ -30,6 +30,10 @@ export default function LowerLines(props) {
return () => clearTimeout(timeout);
}, [options.fadeOut, options.transitionIn, defaults.transitionIn]);
+ useEffect(() => {
+ setShowLower(title.showNow);
+ }, [title.showNow]);
+
// Format messages
const showLowerMessage = lower.text !== '' && lower.visible;
diff --git a/client/src/features/viewers/production/lower/LowerWrapper.jsx b/client/src/features/viewers/production/lower/LowerWrapper.jsx
index 0ee5cae91..bc1f9cc90 100644
--- a/client/src/features/viewers/production/lower/LowerWrapper.jsx
+++ b/client/src/features/viewers/production/lower/LowerWrapper.jsx
@@ -1,8 +1,27 @@
-import { useEffect, useState } from 'react';
+import { memo, useEffect, useState } from 'react';
import LowerClean from './LowerClean';
import LowerLines from './LowerLines';
+const isEqual = require('react-fast-compare');
-export default function Lower(props) {
+const areEqual = (prevProps, nextProps) => {
+ return (
+ isEqual(prevProps.title, nextProps.title) &&
+ isEqual(prevProps.lower && nextProps.lower)
+ );
+};
+
+const Lower = (props) => {
+ const { title } = props;
+ const [titles, setTitles] = useState({
+ titleNow: '',
+ titleNext: '',
+ subtitleNow: '',
+ subtitleNext: '',
+ presenterNow: '',
+ presenterNext: '',
+ showNow: false,
+ showNext: false,
+ });
const [preset, setPreset] = useState(1);
const [lowerOptions, setLowerOptions] = useState({});
@@ -11,6 +30,32 @@ export default function Lower(props) {
document.title = 'ontime - Lower Thirds';
}, []);
+ // reload if data changes
+ useEffect(() => {
+ // clear titles if necessary
+ // will trigger an animation out in the component
+ let timeout = null;
+ if (
+ title?.titleNow !== titles?.titleNow ||
+ title?.subtitleNow !== titles?.subtitleNow ||
+ title?.presenterNow !== titles?.presenterNow
+ ) {
+ setTitles((t) => ({ ...t, showNow: false }));
+
+ const transitionTime = 2000;
+
+ timeout = setTimeout(() => {
+ setTitles(title);
+ }, transitionTime);
+ }
+
+ return () => {
+ if (timeout != null) {
+ clearTimeout(timeout);
+ }
+ };
+ }, [title.titleNow, title.subtitleNow, title.presenterNow]);
+
// TODO: sanitize data
// getting config from URL: preset, size, transition, bg, text, key
// eg. http://localhost:3000/lower?bg=ff2&text=f00&size=0.6&transition=5
@@ -77,10 +122,18 @@ export default function Lower(props) {
switch (preset) {
case 0:
- return ;
+ return (
+
+ );
case 1:
- return ;
+ return (
+
+ );
default:
- return ;
+ return (
+
+ );
}
-}
+};
+
+export default memo(Lower, areEqual);
diff --git a/client/yarn.lock b/client/yarn.lock
index 1062924a6..84ee4f1c6 100644
--- a/client/yarn.lock
+++ b/client/yarn.lock
@@ -10078,7 +10078,7 @@ react-error-overlay@^6.0.9:
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a"
integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==
-react-fast-compare@3.2.0:
+react-fast-compare@3.2.0, react-fast-compare@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb"
integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==