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 @@ - [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](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==