diff --git a/.github/linux-download.png b/.github/linux-download.png new file mode 100644 index 000000000..8f48a8a15 --- /dev/null +++ b/.github/linux-download.png @@ -0,0 +1,2674 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ontime/linux-download.png at feat/macos · cpvalente/ontime + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Skip to content + + + + + + + + + + + + + + +
+ +
+ + + + + + + +
+ + + +
+ + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + cpvalente  /   + ontime  /   + +
+
+ + + +
+ + +
+ + +
+ + + +
+
+ Tip: + Type # to search pull requests +
+
+ Type ? for help and tips +
+
+
+ +
+
+ Tip: + Type # to search issues +
+
+ Type ? for help and tips +
+
+
+ +
+
+ Tip: + Type # to search discussions +
+
+ Type ? for help and tips +
+
+
+ +
+
+ Tip: + Type ! to search projects +
+
+ Type ? for help and tips +
+
+
+ +
+
+ Tip: + Type @ to search teams +
+
+ Type ? for help and tips +
+
+
+ +
+
+ Tip: + Type @ to search people and organizations +
+
+ Type ? for help and tips +
+
+
+ +
+
+ Tip: + Type > to activate command mode +
+
+ Type ? for help and tips +
+
+
+ +
+
+ Tip: + Go to your accessibility settings to change your keyboard shortcuts +
+
+ Type ? for help and tips +
+
+
+ +
+
+ Tip: + Type author:@me to search your content +
+
+ Type ? for help and tips +
+
+
+ +
+
+ Tip: + Type is:pr to filter to pull requests +
+
+ Type ? for help and tips +
+
+
+ +
+
+ Tip: + Type is:issue to filter to issues +
+
+ Type ? for help and tips +
+
+
+ +
+
+ Tip: + Type is:project to filter to projects +
+
+ Type ? for help and tips +
+
+
+ +
+
+ Tip: + Type is:open to filter to open content +
+
+ Type ? for help and tips +
+
+
+ +
+ +
+
+ We’ve encountered an error and some results aren't available at this time. Type a new search or try again later. +
+
+ + No results matched your search + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + Search for issues and pull requests + + # + + + + Search for issues, pull requests, discussions, and projects + + # + + + + Search for organizations, repositories, and users + + @ + + + + Search for projects + + ! + + + + Search for files + + / + + + + Activate command mode + + > + + + + Search your issues, pull requests, and discussions + + # author:@me + + + + Search your issues, pull requests, and discussions + + # author:@me + + + + Filter to pull requests + + # is:pr + + + + Filter to issues + + # is:issue + + + + Filter to discussions + + # is:discussion + + + + Filter to projects + + # is:project + + + + Filter to open issues, pull requests, and discussions + + # is:open + + + + + + + + + + + + + + + + +
+
+
+ +
+ + + + + + + + + + +
+ + + + +
+
+
+ + + + + + + + + +
+ +
+ +
+

+ + + / + + ontime + + + Public +

+ +
+ +
    + +
  • +
    +
    +
    +
  • + + +
  • + +
    + + + + + Unwatch + + + + + 3 + + + + +
    +
    +

    Notifications

    + +
    + +
    +
    + + + + + + + + +
    + + +
    +
    +
    + + + + +
    +
    +
    + + + +
  • + +
  • +
    +
    + Fork + 2 + + + +
    + +

    Fork ontime

    +
    + +
    + + + + + +

    If this dialog fails to load, you can visit the fork page directly.

    +
    +
    + +
    +
    +
    + +
  • + +
  • + + +
    +
    + + +
    +
    + +
    +
    + + + + +
    + +
    +
    + + + + + + + +
    + +
    +
    +
    +
    +
    +
  • + + + +
+ +
+ +
+
+ + + + +
+ + + +
+ Open in github.dev + Open in a new github.dev tab + + + + + + +
+ + +
+ + + +
+ + +
+
We found potential security vulnerabilities in your dependencies.
+ +

+ Only the owner of this repository can see this message. +

+
+ + + + +
+ + + +Permalink + +
+ +
+
+ + + feat/macos + + + + +
+
+
+ Switch branches/tags + +
+ + + +
+ +
+ +
+ + +
+ +
+ + + + + + + + + + + + + + + + +
+ + +
+
+
+
+ +
+ +
+ + + Go to file + + +
+ + + + + +
+
+
+ + + + + + + + + +
+ +
+
+
 
+
+ +
+
 
+ Cannot retrieve contributors at this time +
+
+ + + + + + + + + + + + +
+ +
+ + +
+ + 2.27 KB +
+ +
+ + + + +
+ + + + +
+ +
+
+ +
+ +
+
+ + + +
+ +
+ linux-download.png +
+
+ +
+ + + + +
+ + +
+ + +
+
+ + +
+ +
+ + +
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + diff --git a/.github/mac-download.png b/.github/mac-download.png new file mode 100644 index 000000000..578b5441f --- /dev/null +++ b/.github/mac-download.png @@ -0,0 +1,2674 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ontime/mac-download.png at feat/macos · cpvalente/ontime + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Skip to content + + + + + + + + + + + + + + +
+ +
+ + + + + + + +
+ + + +
+ + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + cpvalente  /   + ontime  /   + +
+
+ + + +
+ + +
+ + +
+ + + +
+
+ Tip: + Type # to search pull requests +
+
+ Type ? for help and tips +
+
+
+ +
+
+ Tip: + Type # to search issues +
+
+ Type ? for help and tips +
+
+
+ +
+
+ Tip: + Type # to search discussions +
+
+ Type ? for help and tips +
+
+
+ +
+
+ Tip: + Type ! to search projects +
+
+ Type ? for help and tips +
+
+
+ +
+
+ Tip: + Type @ to search teams +
+
+ Type ? for help and tips +
+
+
+ +
+
+ Tip: + Type @ to search people and organizations +
+
+ Type ? for help and tips +
+
+
+ +
+
+ Tip: + Type > to activate command mode +
+
+ Type ? for help and tips +
+
+
+ +
+
+ Tip: + Go to your accessibility settings to change your keyboard shortcuts +
+
+ Type ? for help and tips +
+
+
+ +
+
+ Tip: + Type author:@me to search your content +
+
+ Type ? for help and tips +
+
+
+ +
+
+ Tip: + Type is:pr to filter to pull requests +
+
+ Type ? for help and tips +
+
+
+ +
+
+ Tip: + Type is:issue to filter to issues +
+
+ Type ? for help and tips +
+
+
+ +
+
+ Tip: + Type is:project to filter to projects +
+
+ Type ? for help and tips +
+
+
+ +
+
+ Tip: + Type is:open to filter to open content +
+
+ Type ? for help and tips +
+
+
+ +
+ +
+
+ We’ve encountered an error and some results aren't available at this time. Type a new search or try again later. +
+
+ + No results matched your search + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + Search for issues and pull requests + + # + + + + Search for issues, pull requests, discussions, and projects + + # + + + + Search for organizations, repositories, and users + + @ + + + + Search for projects + + ! + + + + Search for files + + / + + + + Activate command mode + + > + + + + Search your issues, pull requests, and discussions + + # author:@me + + + + Search your issues, pull requests, and discussions + + # author:@me + + + + Filter to pull requests + + # is:pr + + + + Filter to issues + + # is:issue + + + + Filter to discussions + + # is:discussion + + + + Filter to projects + + # is:project + + + + Filter to open issues, pull requests, and discussions + + # is:open + + + + + + + + + + + + + + + + +
+
+
+ +
+ + + + + + + + + + +
+ + + + +
+
+
+ + + + + + + + + +
+ +
+ +
+

+ + + / + + ontime + + + Public +

+ +
+ +
    + +
  • +
    +
    +
    +
  • + + +
  • + +
    + + + + + Unwatch + + + + + 3 + + + + +
    +
    +

    Notifications

    + +
    + +
    +
    + + + + + + + + +
    + + +
    +
    +
    + + + + +
    +
    +
    + + + +
  • + +
  • +
    +
    + Fork + 2 + + + +
    + +

    Fork ontime

    +
    + +
    + + + + + +

    If this dialog fails to load, you can visit the fork page directly.

    +
    +
    + +
    +
    +
    + +
  • + +
  • + + +
    +
    + + +
    +
    + +
    +
    + + + + +
    + +
    +
    + + + + + + + +
    + +
    +
    +
    +
    +
    +
  • + + + +
+ +
+ +
+
+ + + + +
+ + + +
+ Open in github.dev + Open in a new github.dev tab + + + + + + +
+ + +
+ + + +
+ + +
+
We found potential security vulnerabilities in your dependencies.
+ +

+ Only the owner of this repository can see this message. +

+
+ + + + +
+ + + +Permalink + +
+ +
+
+ + + feat/macos + + + + +
+
+
+ Switch branches/tags + +
+ + + +
+ +
+ +
+ + +
+ +
+ + + + + + + + + + + + + + + + +
+ + +
+
+
+
+ +
+ +
+ + + Go to file + + +
+ + + + + +
+
+
+ + + + + + + + + +
+ +
+
+
 
+
+ +
+
 
+ Cannot retrieve contributors at this time +
+
+ + + + + + + + + + + + +
+ +
+ + +
+ + 3.83 KB +
+ +
+ + + + +
+ + + + +
+ +
+
+ +
+ +
+
+ + + +
+ +
+ mac-download.png +
+
+ +
+ + + + +
+ + +
+ + +
+
+ + +
+ +
+ + +
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + diff --git a/.github/win-download.png b/.github/win-download.png new file mode 100644 index 000000000..1e1a5531f --- /dev/null +++ b/.github/win-download.png @@ -0,0 +1,2674 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ontime/win-download.png at feat/macos · cpvalente/ontime + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Skip to content + + + + + + + + + + + + + + +
+ +
+ + + + + + + +
+ + + +
+ + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + cpvalente  /   + ontime  /   + +
+
+ + + +
+ + +
+ + +
+ + + +
+
+ Tip: + Type # to search pull requests +
+
+ Type ? for help and tips +
+
+
+ +
+
+ Tip: + Type # to search issues +
+
+ Type ? for help and tips +
+
+
+ +
+
+ Tip: + Type # to search discussions +
+
+ Type ? for help and tips +
+
+
+ +
+
+ Tip: + Type ! to search projects +
+
+ Type ? for help and tips +
+
+
+ +
+
+ Tip: + Type @ to search teams +
+
+ Type ? for help and tips +
+
+
+ +
+
+ Tip: + Type @ to search people and organizations +
+
+ Type ? for help and tips +
+
+
+ +
+
+ Tip: + Type > to activate command mode +
+
+ Type ? for help and tips +
+
+
+ +
+
+ Tip: + Go to your accessibility settings to change your keyboard shortcuts +
+
+ Type ? for help and tips +
+
+
+ +
+
+ Tip: + Type author:@me to search your content +
+
+ Type ? for help and tips +
+
+
+ +
+
+ Tip: + Type is:pr to filter to pull requests +
+
+ Type ? for help and tips +
+
+
+ +
+
+ Tip: + Type is:issue to filter to issues +
+
+ Type ? for help and tips +
+
+
+ +
+
+ Tip: + Type is:project to filter to projects +
+
+ Type ? for help and tips +
+
+
+ +
+
+ Tip: + Type is:open to filter to open content +
+
+ Type ? for help and tips +
+
+
+ +
+ +
+
+ We’ve encountered an error and some results aren't available at this time. Type a new search or try again later. +
+
+ + No results matched your search + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + Search for issues and pull requests + + # + + + + Search for issues, pull requests, discussions, and projects + + # + + + + Search for organizations, repositories, and users + + @ + + + + Search for projects + + ! + + + + Search for files + + / + + + + Activate command mode + + > + + + + Search your issues, pull requests, and discussions + + # author:@me + + + + Search your issues, pull requests, and discussions + + # author:@me + + + + Filter to pull requests + + # is:pr + + + + Filter to issues + + # is:issue + + + + Filter to discussions + + # is:discussion + + + + Filter to projects + + # is:project + + + + Filter to open issues, pull requests, and discussions + + # is:open + + + + + + + + + + + + + + + + +
+
+
+ +
+ + + + + + + + + + +
+ + + + +
+
+
+ + + + + + + + + +
+ +
+ +
+

+ + + / + + ontime + + + Public +

+ +
+ +
    + +
  • +
    +
    +
    +
  • + + +
  • + +
    + + + + + Unwatch + + + + + 3 + + + + +
    +
    +

    Notifications

    + +
    + +
    +
    + + + + + + + + +
    + + +
    +
    +
    + + + + +
    +
    +
    + + + +
  • + +
  • +
    +
    + Fork + 2 + + + +
    + +

    Fork ontime

    +
    + +
    + + + + + +

    If this dialog fails to load, you can visit the fork page directly.

    +
    +
    + +
    +
    +
    + +
  • + +
  • + + +
    +
    + + +
    +
    + +
    +
    + + + + +
    + +
    +
    + + + + + + + +
    + +
    +
    +
    +
    +
    +
  • + + + +
+ +
+ +
+
+ + + + +
+ + + +
+ Open in github.dev + Open in a new github.dev tab + + + + + + +
+ + +
+ + + +
+ + +
+
We found potential security vulnerabilities in your dependencies.
+ +

+ Only the owner of this repository can see this message. +

+
+ + + + +
+ + + +Permalink + +
+ +
+
+ + + feat/macos + + + + +
+
+
+ Switch branches/tags + +
+ + + +
+ +
+ +
+ + +
+ +
+ + + + + + + + + + + + + + + + +
+ + +
+
+
+
+ +
+ +
+ + + Go to file + + +
+ + + + + +
+
+
+ + + + + + + + + +
+ +
+
+
 
+
+ +
+
 
+ Cannot retrieve contributors at this time +
+
+ + + + + + + + + + + + +
+ +
+ + +
+ + 2.5 KB +
+ +
+ + + + +
+ + + + +
+ +
+
+ +
+ +
+
+ + + +
+ +
+ win-download.png +
+
+ +
+ + + + +
+ + +
+ + +
+
+ + +
+ +
+ + +
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..a44711750 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,166 @@ +name: Ontime build + +on: + push: + # run when a tag is created + tags: + - '*' + workflow_dispatch: + +jobs: + build_mac: + runs-on: macOS-latest + env: + CI: '' + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js + uses: actions/setup-node@v2 + with: + node-version: '14.x' + + # React + - name: React - Install dependencies + run: yarn install + working-directory: ./client + + - name: React - Build project + run: yarn build + working-directory: ./client + + # Node server + - name: Server - Install dependencies + run: yarn install + working-directory: ./server/src + + # App + - name: Electron - Install dependencies + shell: bash + run: yarn install && yarn setdb + working-directory: ./server + - name: Electron - Inject utils + run: yarn cheat + working-directory: ./server + + - name: Electron - Build app + run: yarn dist-mac + 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 }} + + build_win: + runs-on: windows-latest + env: + CI: '' + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js + uses: actions/setup-node@v1 + with: + node-version: '14.x' + + # React + - name: React - Install dependencies + run: yarn install + working-directory: ./client + + - name: React - Build project + run: yarn build + working-directory: ./client + + # Node server + - name: Server - Install dependencies + run: yarn install + working-directory: ./server/src + + # App + - name: Electron - Install dependencies + shell: bash + run: yarn install && yarn setdb + working-directory: ./server + - name: Electron - Inject utils + run: yarn cheat + working-directory: ./server + + - name: Electron - Build app + run: yarn dist-mac + 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_linux: +# runs-on: ubuntu-latest +# env: +# CI: '' +# +# steps: +# - uses: actions/checkout@v2 +# - name: Use Node.js +# uses: actions/setup-node@v1 +# with: +# node-version: '14.x' +# +# Utils server +# - name: Utils - Install dependencies +# run: yarn install +# working-directory: ./server/utils +# +# - name: Utils - run link command +# run: yarn link +# working-directory: ./server/utils +# +# React +# - name: React - Link to utils +# run: yarn link ontime-utils +# working-directory: ./client +# +# - name: React - Install dependencies +# run: yarn install +# working-directory: ./client +# +# - name: React - Build project +# run: yarn build +# working-directory: ./client +# +# Node server +# - name: React - Link to utils +# run: yarn link ontime-utils +# working-directory: ./server/src +# +# - name: Server - Install dependencies +# run: yarn install +# working-directory: ./server/src +# +# App +# - name: Electron - Install dependencies +# run: yarn install && yarn make && yarn setdb +# working-directory: ./server +# - name: Electron - Inject utils +# run: yarn cheat +# working-directory: ./server +# +# - name: Electron - Build app +# run: yarn dist-linux +# 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 }} diff --git a/.github/workflows/ontime_cy.yml b/.github/workflows/ontime_cy.yml index ba151a10e..5bdcff8e9 100644 --- a/.github/workflows/ontime_cy.yml +++ b/.github/workflows/ontime_cy.yml @@ -19,20 +19,7 @@ jobs: with: node-version: '14.x' - # Utils server - - name: Utils - Install dependencies - run: yarn install - working-directory: ./server/utils - - - name: Utils - run link command - run: yarn link - working-directory: ./server/utils - # React - - name: React - Link to utils - run: yarn link ontime-utils - working-directory: ./client - - name: React - Install dependencies run: yarn install working-directory: ./client @@ -46,17 +33,13 @@ jobs: working-directory: ./client # Node server - - name: React - Link to utils - run: yarn link ontime-utils - working-directory: ./server/src - - name: Server - Install dependencies run: yarn install working-directory: ./server/src # App - name: Electron - Install dependencies - run: yarn install && yarn make && yarn setdb + run: yarn install && yarn setdb working-directory: ./server - name: Electron - Run tests diff --git a/README.md b/README.md index 435cd06e0..343c9274e 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,11 @@ Download the latest releases here -- [Windows](https://github.com/cpvalente/ontime/releases/latest/download/ontime-win64.exe) +
+ Download MacOS + Download Windows + Download Linux +
# Ontime Ontime is an application for managing event rundowns and running stage timers. @@ -27,7 +31,7 @@ and extend with using the URL aliases feature For the presentation views... ------------------------------------------------------------- IP.ADDRESS:4001 > Web server default to presenter timer view -IP.ADDRESS:4001/presenter > Presenter / Stage timer view +IP.ADDRESS:4001/timer > Presenter / Stage timer view IP.ADDRESS:4001/sm > Stage Manager / Backstage view IP.ADDRESS:4001/public > Public / Foyer view IP.ADDRESS:4001/pip > Picture in Picture view @@ -85,25 +89,44 @@ Taking advantage of the integrations in Ontime, we currently use Ontime with: ### Continued development There are several features planned in the roadmap. These will be implemented in a development friendly order unless there is user demand to bump any of them. +- [ ] HTTP Server (vMix integration) +- [ ] Improvement with event component design +- [ ] New playback mode for [cumulative time keeping](https://github.com/cpvalente/ontime/issues/100) - [ ] Linux version - [ ] Headless version (run server only anywhere, configure from a browser locally) - [ ] Companion module - [ ] Lower Third Manager - [ ] Note only event - [ ] Reach Schedule: way to speedup timer to meet a deadline -- [ ] vMix integration ### For version 1 Almost reaching a feature set that we can call v1. Before that: - [ ] Mac OS version ### Issues -The app is still in pre-release and there are a few issues, mainly concerning style. -This will be receiving attention as we near v1 release +The app is still in pre-release and there are a few issues, mainly concerning responsiveness in +different screens. If you run into problems, please open an issue with a screenshot and your device +resolution -#### Style -- [ ] App needs improvement on handling zoomed interfaces: Please ensure that windows settings have no display zoom (it is 125% by default) -- [ ] Very long titles might cause interface to shift +#### Unsigned App + +When installing the app you would see warning screens from the Operating System like: + +```Microsoft Defender SmartScreen prevented an unrecognised app from starting. Running this app might put your PC at risk.``` +or +```Ontime can't be opened because it is from an unidentified developer``` +Long story short: Ontime app is unsigned.
Purchasing the certificates for both Mac and Windows +incurs an early cost and is not a priority. This is unlikely to change in future. +If you have tips on how to improve this, or would like to sponsor the code signing, +please [open an issue so we can discuss it](https://github.com/cpvalente/ontime/issues/new) + +#### Safari + +There are some issues with Safari versions lower than 13: +- Spacing and text styles do not render correctly +- Table view does not work + +There is no plan for any further work on this since the breaking code belongs to third party libraries. # Help Help is underway! ... and can be viewed [here](https://cpvalente.gitbook.io/ontime/) diff --git a/client/package.json b/client/package.json index f6f8fa21a..5535fe695 100644 --- a/client/package.json +++ b/client/package.json @@ -14,7 +14,6 @@ "axios": "^0.25.0", "color": "^4.2.1", "framer-motion": "4.1.17", - "ontime-utils": "link: ../server/utils/", "react": "17.0.2", "react-beautiful-dnd": "^13.1.0", "react-dom": "^17.0.1", diff --git a/client/src/app/api/eventApi.js b/client/src/app/api/eventApi.js index 65982241e..734f22185 100644 --- a/client/src/app/api/eventApi.js +++ b/client/src/app/api/eventApi.js @@ -6,7 +6,4 @@ export const fetchEvent = async () => { return res.data; }; -export const postEvent = async (data) => { - const res = await axios.post(eventURL, data); - return res; -}; +export const postEvent = async (data) => axios.post(eventURL, data); diff --git a/client/src/app/api/eventsApi.js b/client/src/app/api/eventsApi.js index eeec7f4a1..a6234aa73 100644 --- a/client/src/app/api/eventsApi.js +++ b/client/src/app/api/eventsApi.js @@ -6,31 +6,16 @@ export const fetchAllEvents = async () => { return res.data; }; -export const requestPost = async (data) => { - await axios.post(eventsURL, data); -}; +export const requestPost = async (data) => axios.post(eventsURL, data); -export const requestPut = async (data) => { - await axios.put(eventsURL, data); -}; +export const requestPut = async (data) => axios.put(eventsURL, data); -export const requestPatch = async (data) => { - await axios.patch(eventsURL, data); -}; +export const requestPatch = async (data) => axios.patch(eventsURL, data); -export const requestReorder = async (data) => { - await axios.patch(`${eventsURL}/reorder`, data); -}; +export const requestReorder = async (data) => axios.patch(`${eventsURL}/reorder`, data); -export const requestApplyDelay = async (eventId) => { - const action = 'applydelay'; - return await axios.patch(`${eventsURL}/${action}/${eventId}`); -}; +export const requestApplyDelay = async (eventId) => axios.patch(`${eventsURL}/applydelay/${eventId}`); -export const requestDelete = async (eventId) => { - await axios.delete(`${eventsURL}/${eventId}`); -}; +export const requestDelete = async (eventId) => axios.delete(`${eventsURL}/${eventId}`); -export const requestDeleteAll = async () => { - await axios.delete(`${eventsURL}/all`); -}; +export const requestDeleteAll = async () => axios.delete(`${eventsURL}/all`); diff --git a/client/src/app/api/ontimeApi.js b/client/src/app/api/ontimeApi.js index e642109c0..7dab87669 100644 --- a/client/src/app/api/ontimeApi.js +++ b/client/src/app/api/ontimeApi.js @@ -104,46 +104,35 @@ export const getSettings = async () => { return res.data; }; -export const postSettings = async (data) => { - await axios.post(`${ontimeURL}/settings`, data); -}; +export const postSettings = async (data) => axios.post(`${ontimeURL}/settings`, data); export const getInfo = async () => { const res = await axios.get(`${ontimeURL}/info`); return res.data; }; -export const postInfo = async (data) => { - await axios.post(`${ontimeURL}/info`, data); -}; +export const postInfo = async (data) => axios.post(`${ontimeURL}/info`, data); export const getAliases = async () => { const res = await axios.get(`${ontimeURL}/aliases`); return res.data; }; -export const postAliases = async (data) => { - await axios.post(`${ontimeURL}/aliases`, data); -}; - +export const postAliases = async (data) => axios.post(`${ontimeURL}/aliases`, data); export const getUserFields = async () => { const res = await axios.get(`${ontimeURL}/userfields`); return res.data; }; -export const postUserFields = async (data) => { - await axios.post(`${ontimeURL}/userfields`, data); -}; +export const postUserFields = async (data) => axios.post(`${ontimeURL}/userfields`, data); export const getOSC = async () => { const res = await axios.get(`${ontimeURL}/osc`); return res.data; }; -export const postOSC = async (data) => { - await axios.post(`${ontimeURL}/osc`, data); -}; +export const postOSC = async (data) => axios.post(`${ontimeURL}/osc`, data); export const downloadEvents = async () => { await axios({ @@ -151,13 +140,13 @@ export const downloadEvents = async () => { method: 'GET', responseType: 'blob', // important }).then((response) => { - let headerLine = response.headers['Content-Disposition']; + const headerLine = response.headers['Content-Disposition']; let filename = 'events.json'; // try and get the filename from the response if (headerLine != null) { - let startFileNameIndex = headerLine.indexOf('"') + 1; - let endFileNameIndex = headerLine.lastIndexOf('"'); + const startFileNameIndex = headerLine.indexOf('"') + 1; + const endFileNameIndex = headerLine.lastIndexOf('"'); filename = headerLine.substring(startFileNameIndex, endFileNameIndex); } @@ -173,14 +162,11 @@ export const downloadEvents = async () => { export const uploadEvents = async (file) => { const formData = new FormData(); formData.append('userFile', file); // appending file - await axios - .post(`${ontimeURL}/db`, formData, { - headers: { - 'Content-Type': 'multipart/form-data', - }, - }); + await axios.post(`${ontimeURL}/db`, formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }); }; -export const uploadEventsWithPath = async (filepath) => { - await axios.post(`${ontimeURL}/dbpath`, { path: filepath }); -}; +export const uploadEventsWithPath = async (filepath) => axios.post(`${ontimeURL}/dbpath`, { path: filepath }); diff --git a/client/src/app/api/playbackApi.js b/client/src/app/api/playbackApi.js index 0145da7f5..5ce795273 100644 --- a/client/src/app/api/playbackApi.js +++ b/client/src/app/api/playbackApi.js @@ -1,37 +1,16 @@ import axios from 'axios'; -import { playbackURL } from '../api/apiConstants'; +import { playbackURL } from './apiConstants'; -export const getStart = async () => { - const res = await axios.get(playbackURL + '/start'); - return res; -}; +export const getStart = async () => axios.get(`${playbackURL}/start`); -export const getPause = async () => { - const res = axios.get(playbackURL + '/pause'); - return res; -}; +export const getPause = async () => axios.get(`${playbackURL}/pause`); -export const getRoll = async () => { - const res = axios.get(playbackURL + '/roll'); - return res; -}; +export const getRoll = async () => axios.get(`${playbackURL}/roll`); -export const getPrevious = async () => { - const res = axios.get(playbackURL + '/previous'); - return res; -}; +export const getPrevious = async () => axios.get(`${playbackURL}/previous`); -export const getNext = async () => { - const res = axios.get(playbackURL + '/next'); - return res; -}; +export const getNext = async () => axios.get(`${playbackURL}/next`); -export const getUnload = async () => { - const res = axios.get(playbackURL + '/unload'); - return res; -}; +export const getUnload = async () => axios.get(`${playbackURL}/unload`); -export const getReload = async () => { - const res = axios.get(playbackURL + '/reload'); - return res; -}; +export const getReload = async () => axios.get(`${playbackURL}/reload`); diff --git a/client/src/app/context/CursorContext.js b/client/src/app/context/CursorContext.js index 20b5811c6..e0942489c 100644 --- a/client/src/app/context/CursorContext.js +++ b/client/src/app/context/CursorContext.js @@ -31,7 +31,7 @@ export const CursorProvider = ({ children }) => { */ const toggleCursorLocked = useCallback( (newValue = undefined) => { - if (newValue === undefined) { + if (typeof newValue === 'undefined') { if (isCursorLocked) { cursorLockedOff(); } else { diff --git a/client/src/app/context/LoggingContext.js b/client/src/app/context/LoggingContext.js index 0d00ed7c2..566533cb7 100644 --- a/client/src/app/context/LoggingContext.js +++ b/client/src/app/context/LoggingContext.js @@ -1,7 +1,7 @@ import React, { createContext, useCallback, useEffect, useState } from 'react'; import { useSocket } from './socketContext'; -import { generateId } from 'ontime-utils/generate_id'; -import { nowInMillis, stringFromMillis } from 'ontime-utils/time'; +import { generateId } from '../../common/utils/generate_id'; +import { nowInMillis, stringFromMillis } from '../../common/utils/time'; export const LoggingContext = createContext({ logData: [], diff --git a/client/src/app/entryValidator.js b/client/src/app/entryValidator.js index 34afcc96c..3b4c1059c 100644 --- a/client/src/app/entryValidator.js +++ b/client/src/app/entryValidator.js @@ -5,7 +5,7 @@ * @returns {{catch: string, value: boolean}} */ export const validateTimes = (timeStart, timeEnd) => { - let validate = { value: true, catch: '' }; + const validate = { value: true, catch: '' }; if (timeStart > timeEnd) { validate.catch = 'Start time later than end time'; } diff --git a/client/src/common/components/buttons/CollapseBtn.jsx b/client/src/common/components/buttons/CollapseBtn.jsx index bd366654a..8137f0da2 100644 --- a/client/src/common/components/buttons/CollapseBtn.jsx +++ b/client/src/common/components/buttons/CollapseBtn.jsx @@ -12,7 +12,6 @@ export default function CollapseBtn(props) { icon={} colorScheme='white' variant='outline' - background='#fff1' onClick={clickhandler} _focus={{ boxShadow: 'none' }} /> diff --git a/client/src/common/components/buttons/CursorLockedBtn.jsx b/client/src/common/components/buttons/CursorLockedBtn.jsx index 89f2d1468..f299720a8 100644 --- a/client/src/common/components/buttons/CursorLockedBtn.jsx +++ b/client/src/common/components/buttons/CursorLockedBtn.jsx @@ -4,7 +4,7 @@ import { Tooltip } from '@chakra-ui/tooltip'; import { FiTarget } from '@react-icons/all-files/fi/FiTarget'; export default function CursorLockedBtn(props) { - const { clickhandler, active, ref, size } = props; + const { clickhandler, active, ref, size, ...rest } = props; return ( ); diff --git a/client/src/common/components/buttons/DeleteIconBtn.jsx b/client/src/common/components/buttons/DeleteIconBtn.jsx index 836523bb0..762bdd177 100644 --- a/client/src/common/components/buttons/DeleteIconBtn.jsx +++ b/client/src/common/components/buttons/DeleteIconBtn.jsx @@ -1,6 +1,5 @@ -import React from 'react'; +import React, { useState } from 'react'; import { IconButton } from '@chakra-ui/button'; -import { useState } from 'react'; import { IoRemove } from '@react-icons/all-files/io5/IoRemove'; import { Tooltip } from '@chakra-ui/tooltip'; diff --git a/client/src/common/components/buttons/ExpandBtn.jsx b/client/src/common/components/buttons/ExpandBtn.jsx index e493af23c..bba0b7718 100644 --- a/client/src/common/components/buttons/ExpandBtn.jsx +++ b/client/src/common/components/buttons/ExpandBtn.jsx @@ -12,7 +12,6 @@ export default function ExpandBtn(props) { icon={} colorScheme='white' variant='outline' - background='#fff1' onClick={clickhandler} _focus={{ boxShadow: 'none' }} /> diff --git a/client/src/common/components/collapseBar/CollapseBar.jsx b/client/src/common/components/collapseBar/CollapseBar.jsx index 5c86e918f..af8120da5 100644 --- a/client/src/common/components/collapseBar/CollapseBar.jsx +++ b/client/src/common/components/collapseBar/CollapseBar.jsx @@ -1,14 +1,14 @@ import React from 'react'; -import PropTypes from "prop-types"; -import style from "../../../features/info/Info.module.scss"; -import {Icon} from "@chakra-ui/react"; -import {FiChevronUp} from "@react-icons/all-files/fi/FiChevronUp"; +import PropTypes from 'prop-types'; +import { Icon } from '@chakra-ui/react'; +import { FiChevronUp } from '@react-icons/all-files/fi/FiChevronUp'; +import style from './CollapseBar.module.scss'; export default function CollapseBar(props) { - const {title = 'Collapse bar', isCollapsed = false, onClick}= props; + const { title = 'Collapse bar', isCollapsed, onClick, roll } = props; - return( -
+ return ( +
{title}
- ) + ); } CollapseBar.propTypes = { title: PropTypes.string, isCollapsed: PropTypes.bool, onClick: PropTypes.func, -} \ No newline at end of file + roll: PropTypes.bool, +}; diff --git a/client/src/common/components/collapseBar/CollapseBar.module.scss b/client/src/common/components/collapseBar/CollapseBar.module.scss index 43fbb7a87..dd10a1d72 100644 --- a/client/src/common/components/collapseBar/CollapseBar.module.scss +++ b/client/src/common/components/collapseBar/CollapseBar.module.scss @@ -1,17 +1,34 @@ +@use '../../../styles/main' as *; + .header, -.header__roll { +.headerRoll { padding: 0; margin: 0; font-size: 0.9em; - color: #ccc; display: flex; justify-content: space-between; } .header { - color: #ccc; + color: $header-gray; } -.header__roll { - color: #2b6cb0; -} \ No newline at end of file +.headerRoll { + color: $ontime-roll; +} + +.moreExpanded, +.moreCollapsed { + cursor: pointer; + color: $text-white; +} + +.moreExpanded { + transform: scaleY(-1); + transition: transform 0.3s; +} + +.moreCollapsed { + transform: scaleY(1); + transition: transform 0.3s; +} diff --git a/client/src/common/components/countdown/Countdown.jsx b/client/src/common/components/countdown/Countdown.jsx index 9d597c64d..6222dd149 100644 --- a/client/src/common/components/countdown/Countdown.jsx +++ b/client/src/common/components/countdown/Countdown.jsx @@ -1,22 +1,18 @@ import React, { memo } from 'react'; import { formatDisplay } from 'common/utils/dateConfig'; import PropTypes from 'prop-types'; -import styles from './Countdown.module.css'; +import styles from './Countdown.module.scss'; const Countdown = ({ time, small, isNegative, hideZeroHours }) => { // prepare display string const display = - time != null && !isNaN(time) - ? formatDisplay(time, hideZeroHours) - : '-- : -- : --'; + time != null && !isNaN(time) ? formatDisplay(time, hideZeroHours) : '-- : -- : --'; - const colour = isNegative ? '#ff7597' : '#fffffa'; + const classes = `${small ? styles.countdownClockSmall : styles.countdownClock} + ${isNegative ? styles.negative : ''}`; return ( -
+
{display}
); diff --git a/client/src/common/components/countdown/Countdown.module.css b/client/src/common/components/countdown/Countdown.module.scss similarity index 74% rename from client/src/common/components/countdown/Countdown.module.css rename to client/src/common/components/countdown/Countdown.module.scss index 3b360444f..20c4ad66f 100644 --- a/client/src/common/components/countdown/Countdown.module.css +++ b/client/src/common/components/countdown/Countdown.module.scss @@ -1,10 +1,11 @@ -/* @import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;600;800&display=swap'); */ +@use '../../../styles/main' as *; /* Common */ .countdownClock, .countdownClockSmall { font-family: 'Open Sans', sans-serif; line-height: 0.9; + color: $text-white; } /* Viewers */ @@ -21,5 +22,8 @@ text-align: center; letter-spacing: 0.1em; font-weight: 600; - color: #fffffa; +} + +.negative { + color: $ontime-pink; } diff --git a/client/src/common/components/errorBoundary/ErrorBoundary.jsx b/client/src/common/components/errorBoundary/ErrorBoundary.jsx index 780bfb806..a206e7184 100644 --- a/client/src/common/components/errorBoundary/ErrorBoundary.jsx +++ b/client/src/common/components/errorBoundary/ErrorBoundary.jsx @@ -1,9 +1,11 @@ /* eslint-disable react/destructuring-assignment */ import React from 'react'; import { LoggingContext } from '../../../app/context/LoggingContext'; +import style from './ErrorBoundary.module.scss'; class ErrorBoundary extends React.Component { static contextType = LoggingContext; + reportContent = ''; constructor(props) { super(props); @@ -21,11 +23,25 @@ class ErrorBoundary extends React.Component { errorInfo: info, }); this.context.emitError(error.toString()); + this.reportContent = `${error} ${info.componentStack}`; } render() { if (this.state.errorMessage) { - return

:/

; + return ( +
+
+

:/

+

Something went wrong

+

navigator.clipboard.writeText(this.reportContent)} + > + Copy error +

+
+
+ ); } return this.props.children; } diff --git a/client/src/common/components/errorBoundary/ErrorBoundary.module.scss b/client/src/common/components/errorBoundary/ErrorBoundary.module.scss new file mode 100644 index 000000000..453e11f60 --- /dev/null +++ b/client/src/common/components/errorBoundary/ErrorBoundary.module.scss @@ -0,0 +1,28 @@ +@use '../../../styles/main' as *; + +.errorContainer { + width: 100%; + height: 100%; + display: grid; + place-content: center; + background-color: #121212; + color: white; + + .error { + color: $ontime-pink; + font-weight: 600; + } + + .report { + text-decoration: underline $ontime-pink; + cursor: pointer; + } + + .report:hover { + color: $ontime-pink; + } + + .report:active { + color: white; + } +} diff --git a/client/src/common/components/eventTimes/EventTimesVertical.jsx b/client/src/common/components/eventTimes/EventTimesVertical.jsx index 574bd5e68..30a7244ea 100644 --- a/client/src/common/components/eventTimes/EventTimesVertical.jsx +++ b/client/src/common/components/eventTimes/EventTimesVertical.jsx @@ -1,112 +1,9 @@ import React, { useContext } from 'react'; -import EditableTimer from 'common/input/EditableTimer'; -import { stringFromMillis } from 'ontime-utils/time'; import { LoggingContext } from '../../../app/context/LoggingContext'; import { validateTimes } from '../../../app/entryValidator'; import PropTypes from 'prop-types'; - -const label = { - fontSize: '0.75em', - color: '#aaa', -}; - -const TimesDelayed = (props) => { - const { handleValidate, actionHandler, delay, timeStart, timeEnd, duration, previousEnd } = props; - - const scheduledStart = stringFromMillis(timeStart, false); - const scheduledEnd = stringFromMillis(timeEnd, false); - - return ( - <> - - Start {scheduledStart} - - - - End {scheduledEnd} - - - Duration - - - ); -}; - -TimesDelayed.propTypes = { - handleValidate: PropTypes.func.isRequired, - actionHandler: PropTypes.func.isRequired, - delay: PropTypes.number, - timeStart: PropTypes.number, - timeEnd: PropTypes.number, - duration: PropTypes.number, - previousEnd: PropTypes.number, -}; - -const Times = (props) => { - const { handleValidate, actionHandler, timeStart, timeEnd, duration, previousEnd } = props; - - return ( - <> - Start - - End - - Duration - - - ); -}; - -Times.propTypes = { - handleValidate: PropTypes.func.isRequired, - actionHandler: PropTypes.func.isRequired, - timeStart: PropTypes.number, - timeEnd: PropTypes.number, - duration: PropTypes.number, - previousEnd: PropTypes.number, -}; +import TimesDelayed from './TimesDelayed'; +import Times from './Times'; export default function EventTimesVertical(props) { const { delay, timeStart, timeEnd, duration, previousEnd, actionHandler } = props; @@ -129,11 +26,7 @@ export default function EventTimesVertical(props) { start = val; } else if (entry === 'timeEnd') { end = val; - } else if (entry === 'durationOverride') { - return true; - } else { - return false; - } + } else return entry === 'durationOverride'; const valid = validateTimes(start, end); // give warning but not enforce validation diff --git a/client/src/common/components/eventTimes/Times.jsx b/client/src/common/components/eventTimes/Times.jsx new file mode 100644 index 000000000..6b11c1899 --- /dev/null +++ b/client/src/common/components/eventTimes/Times.jsx @@ -0,0 +1,49 @@ +import React from 'react'; +import EditableTimer from '../../input/EditableTimer'; +import PropTypes from 'prop-types'; +import style from './Times.module.scss' + +export default function Times(props) { + const { handleValidate, actionHandler, timeStart, timeEnd, duration, previousEnd } = props; + + return ( + <> + Start + + End + + Duration + + + ); +}; + +Times.propTypes = { + handleValidate: PropTypes.func.isRequired, + actionHandler: PropTypes.func.isRequired, + timeStart: PropTypes.number, + timeEnd: PropTypes.number, + duration: PropTypes.number, + previousEnd: PropTypes.number, +}; diff --git a/client/src/common/components/eventTimes/Times.module.scss b/client/src/common/components/eventTimes/Times.module.scss new file mode 100644 index 000000000..99594805d --- /dev/null +++ b/client/src/common/components/eventTimes/Times.module.scss @@ -0,0 +1,6 @@ +@use '../../../styles/main' as *; + +.label { + font-size: 0.75em; + color: $label-gray; +} diff --git a/client/src/common/components/eventTimes/TimesDelayed.jsx b/client/src/common/components/eventTimes/TimesDelayed.jsx new file mode 100644 index 000000000..94a8ddfb2 --- /dev/null +++ b/client/src/common/components/eventTimes/TimesDelayed.jsx @@ -0,0 +1,58 @@ +import React from 'react'; +import EditableTimer from '../../input/EditableTimer'; +import PropTypes from 'prop-types'; +import { stringFromMillis } from '../../utils/time'; +import style from './Times.module.scss' + +export default function TimesDelayed(props) { + const { handleValidate, actionHandler, delay, timeStart, timeEnd, duration, previousEnd } = props; + + const scheduledStart = stringFromMillis(timeStart, false); + const scheduledEnd = stringFromMillis(timeEnd, false); + + return ( + <> + + Start {scheduledStart} + + + + End {scheduledEnd} + + + Duration + + + ); +}; + +TimesDelayed.propTypes = { + handleValidate: PropTypes.func.isRequired, + actionHandler: PropTypes.func.isRequired, + delay: PropTypes.number, + timeStart: PropTypes.number, + timeEnd: PropTypes.number, + duration: PropTypes.number, + previousEnd: PropTypes.number, +}; diff --git a/client/src/common/components/myProgressBar/MyProgressBar.jsx b/client/src/common/components/myProgressBar/MyProgressBar.jsx index 81cfcfc3b..9ba1b5f89 100644 --- a/client/src/common/components/myProgressBar/MyProgressBar.jsx +++ b/client/src/common/components/myProgressBar/MyProgressBar.jsx @@ -1,6 +1,6 @@ import React from 'react'; import { clamp } from 'app/utils/math'; -import styles from './MyProgressBar.module.css'; +import styles from './MyProgressBar.module.scss'; export default function MyProgressBar(props) { const { now, complete, showElapsed } = props; diff --git a/client/src/common/components/myProgressBar/MyProgressBar.module.css b/client/src/common/components/myProgressBar/MyProgressBar.module.scss similarity index 59% rename from client/src/common/components/myProgressBar/MyProgressBar.module.css rename to client/src/common/components/myProgressBar/MyProgressBar.module.scss index 1a24b7fd4..6f183f155 100644 --- a/client/src/common/components/myProgressBar/MyProgressBar.module.css +++ b/client/src/common/components/myProgressBar/MyProgressBar.module.scss @@ -1,22 +1,23 @@ +@use '../../../styles/main' as *; + .progress, .progressCountdown { height: 2vh; - background-color: rgba(255, 255, 255, 0.13); border-radius: 4px; } .progress { - background-color: rgba(255, 255, 255, 0.13); + background-color: $bg-gray-900; } .progressCountdown { - background-color: #ff7597; + background-color: $ontime-pink; } .progressBar { width: 100%; height: 2vh; - background-color: rgb(255, 255, 255); + background-color: $title-white; border-radius: 4px; transition: 1s linear; transition-property: width; diff --git a/client/src/common/components/nav/NavLogo.jsx b/client/src/common/components/nav/NavLogo.jsx index a41b9d1db..092a9d39a 100644 --- a/client/src/common/components/nav/NavLogo.jsx +++ b/client/src/common/components/nav/NavLogo.jsx @@ -1,5 +1,5 @@ -import React, { useState, useEffect, useCallback } from 'react'; -import PropTypes from "prop-types"; +import React, { useCallback, useEffect, useState } from 'react'; +import PropTypes from 'prop-types'; import { Link } from 'react-router-dom'; import { Image } from '@chakra-ui/react'; import { AnimatePresence, motion } from 'framer-motion'; @@ -7,7 +7,7 @@ import navlogo from 'assets/images/logos/LOGO-72.png'; import style from './NavLogo.module.scss'; export default function NavLogo(props) { - const {isHidden} = props; + const { isHidden } = props; const [showNav, setShowNav] = useState(false); const handleClick = () => { @@ -34,74 +34,43 @@ export default function NavLogo(props) { }; }, [handleKeyPress]); - const baseOpacity = (isHidden) ? 0 : 0.5 + const baseOpacity = isHidden ? 0 : 0.5; + return ( - + {showNav && ( - + Timer - + Minimal Timer - + Backstage - + Public - + Lower Thirds - + PIP - + Studio Clock @@ -113,4 +82,4 @@ export default function NavLogo(props) { NavLogo.propTypes = { isHidden: PropTypes.bool, -} +}; diff --git a/client/src/common/components/nav/NavLogo.module.scss b/client/src/common/components/nav/NavLogo.module.scss index 3f594bfe9..4eb069dec 100644 --- a/client/src/common/components/nav/NavLogo.module.scss +++ b/client/src/common/components/nav/NavLogo.module.scss @@ -1,4 +1,4 @@ -$nav-color: #fff; +@use '../../../styles/main' as *; .navContainer { position: absolute; @@ -9,22 +9,24 @@ $nav-color: #fff; align-items: flex-end; font-size: max(1vw, 16px); z-index: 10; -} -.logo { - width: max(3vw, 32px); - opacity: 0.5; -} + .logo { + width: max(3vw, 32px); + opacity: 0.5; + cursor: pointer; + } -.nav { - gap: 2vh; - display: flex; - flex-direction: column; -} + .nav, + .showNav { + gap: 2vh; + display: flex; + flex-direction: column; -.navItem { - background-color: rgba(0, 0, 0, 0.7); - padding: 0.5vh 1vw; - border-radius: 4px; - color: $nav-color; + .navItem { + background-color: $bg-overlay; + padding: 0.5vh 1vw; + border-radius: 4px; + color: $text-white; + } + } } diff --git a/client/src/common/components/protectRoute/ProtectRoute.jsx b/client/src/common/components/protectRoute/ProtectRoute.jsx index e466acbab..8f8b6dd9e 100644 --- a/client/src/common/components/protectRoute/ProtectRoute.jsx +++ b/client/src/common/components/protectRoute/ProtectRoute.jsx @@ -1,7 +1,7 @@ -import React, { useContext, useEffect, useState } from 'react'; +import React, { useCallback, useContext, useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import style from './ProtectRoute.module.scss'; -import { PinInput, PinInputField } from '@chakra-ui/react'; +import { HStack, PinInput, PinInputField } from '@chakra-ui/react'; import { IconButton } from '@chakra-ui/button'; import { FiCheck } from '@react-icons/all-files/fi/FiCheck'; import { AppContext } from '../../../app/context/AppContext'; @@ -13,53 +13,77 @@ export default function ProtectRoute({ children }) { const [failed, setFailed] = useState(false); const { auth, validate } = useContext(AppContext); + const handleValidation = useCallback(() => { + const r = validate(pin); + if (!r) { + setFailed(true); + setPin(''); + } + }, [pin, validate]); + // Set window title useEffect(() => { document.title = 'ontime'; }, []); - const handleValidation = () => { - const r = validate(pin); - if (!r) { - setFailed(true); - } - }; - - return ( - <> - {!isLocal && !auth ? ( -
- ontime -
- { - setFailed(false); - setPin(value); - }} - > - - - - - - } - style={{ fontSize: '1.5em' }} - onClick={() => handleValidation()} - /> -
-
- ) : ( - children - )} - + // Handle keyboard shortcuts + const handleKeyPress = useCallback( + (e) => { + // handle held key + if (e.repeat) return; + // Space bar + if (e.keyCode === 13) { + handleValidation(); + } + }, + [handleValidation], ); + + useEffect(() => { + // attach the event listener + document.addEventListener('keydown', handleKeyPress); + + // remove the event listener + return () => { + document.removeEventListener('keydown', handleKeyPress); + }; + }, [handleKeyPress]); + + if (isLocal || auth) { + return children; + } else { + return ( +
+ ontime + + { + setFailed(false); + setPin(value); + }} + > + + + + + + } + style={{ fontSize: '1.5em' }} + onClick={() => handleValidation()} + /> + +
+ ); + } } ProtectRoute.propTypes = { diff --git a/client/src/common/components/protectRoute/ProtectRoute.module.scss b/client/src/common/components/protectRoute/ProtectRoute.module.scss index 0b9118d31..729a20bb2 100644 --- a/client/src/common/components/protectRoute/ProtectRoute.module.scss +++ b/client/src/common/components/protectRoute/ProtectRoute.module.scss @@ -2,7 +2,7 @@ @use '../../../styles/mixins' as *; .container { - background: #222; + background: $bg-black; display: grid; place-content: center; @@ -18,8 +18,6 @@ .pin, .pin__failed { - display: flex; - gap: 10px; padding: 20px; input { diff --git a/client/src/common/components/views/Paginator.jsx b/client/src/common/components/views/Paginator.jsx index d6366f130..d31986b4f 100644 --- a/client/src/common/components/views/Paginator.jsx +++ b/client/src/common/components/views/Paginator.jsx @@ -1,33 +1,53 @@ import React, { useEffect, useState } from 'react'; import TodayItem from './TodayItem'; -import style from './Paginator.module.css'; import { useInterval } from 'app/hooks/useInterval'; import PropTypes from 'prop-types'; +import style from './Paginator.module.scss'; +import Empty from '../../state/Empty'; export default function Paginator(props) { - const { events, selectedId, limit = 7, time = 10, isBackstage } = props; + const { + events, + selectedId, + limit = 8, + time = 10, + isBackstage, + setPageNumber, + setCurrentPage, + } = props; const LIMIT_PER_PAGE = limit; - const SCROLL_TIME = time * 1000 || 10000; + const SCROLL_TIME = time * 1000; const [numEvents, setNumEvents] = useState(0); const [page, setPage] = useState([]); const [pages, setPages] = useState(0); const [selPage, setSelPage] = useState(0); + // keep parent up to date + useEffect(() => { + if (setPageNumber) { + setPageNumber(pages); + } + }, [setPageNumber, pages]); + + useEffect(() => { + if (setCurrentPage) { + setCurrentPage(selPage); + } + }, [setCurrentPage, selPage]); + useEffect(() => { if (events == null) return; // how many events in list - let n = events.length; + const n = events.length; setNumEvents(n); // how many paginated views - let p = Math.ceil(n / LIMIT_PER_PAGE); - setPages(p); + setPages(Math.ceil(n / LIMIT_PER_PAGE)); // divide events in parts of LIMIT_PER_PAGE const eventStart = LIMIT_PER_PAGE * selPage; const eventEnd = LIMIT_PER_PAGE * (selPage + 1); - let e = events.slice(eventStart, eventEnd); - setPage(e); + setPage(events.slice(eventStart, eventEnd)); // if array is completely in past, show depending on SCROLL_PAST }, [events, selPage, LIMIT_PER_PAGE]); @@ -42,14 +62,10 @@ export default function Paginator(props) { let selectedState = 0; - return ( - <> -
- {pages > 1 && - [...Array(pages)].map((p, i) => ( -
- ))} -
+ if (events?.length < 1) { + return ; + } else { + return (
{page.map((e) => { if (e.id === selectedId) selectedState = 1; @@ -67,8 +83,8 @@ export default function Paginator(props) { ); })}
- - ); + ); + } } Paginator.propTypes = { @@ -77,4 +93,6 @@ Paginator.propTypes = { limit: PropTypes.number, time: PropTypes.number, isBackstage: PropTypes.bool, + setPageNumber: PropTypes.func, + setCurrentPage: PropTypes.func, }; diff --git a/client/src/common/components/views/Paginator.module.css b/client/src/common/components/views/Paginator.module.css deleted file mode 100644 index 165f568ed..000000000 --- a/client/src/common/components/views/Paginator.module.css +++ /dev/null @@ -1,87 +0,0 @@ -.entries { - display: flex; - flex-direction: column; -} - -.navItem, -.navItemSelected { - background-color: rgba(255, 255, 255, 0.39); - width: 0.7vw; - height: 0.7vw; - border-radius: 0.35vw; -} - -.navItemSelected { - background-color: rgba(255, 255, 255, 0.79); -} - -.nav { - align-self: center; - justify-content: flex-end; - display: flex; - margin-bottom: 2.5vh; -} - -.nav > div { - margin-left: 0.5vw; -} - -.entryTimes { - font-family: 'Open Sans', sans-serif; - font-size: 1.4vw; - - min-width: 10vw; - opacity: 0.9; - letter-spacing: 0.035em; -} - -.entryTitle { - align-self: center; - - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.entryPast, -.entryNow, -.entryFuture { - display: flex; - font-size: 1.3vw; - padding: 0.2vh 1vw; - margin-bottom: 1.5vh; - border-radius: 3px; -} - -.entryPast { - 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.07); - border-bottom: 0.5vh solid #ff7597aa; - 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/Paginator.module.scss b/client/src/common/components/views/Paginator.module.scss new file mode 100644 index 000000000..5e155ced8 --- /dev/null +++ b/client/src/common/components/views/Paginator.module.scss @@ -0,0 +1,64 @@ +@use '../../../styles/main' as *; + +.entries { + width: 100%; +} + +.entryTimes { + font-family: 'Open Sans', sans-serif; + font-size: 1.4vw; + + min-width: 10vw; + opacity: 0.9; + letter-spacing: 0.035em; +} + +.entryTitle { + align-self: center; + @include ellipsis; +} + +.entryPast, +.entryNow, +.entryFuture { + display: flex; + font-size: 1.3vw; + padding: 0.2vh 1vw; + margin-bottom: 1.5vh; + border-radius: 3px; +} + +.entryPast { + font-size: 1.2vw; +} + +.entryPast > .entryTitle { + color: $text-gray-disabled; +} + +.entryNow, +.entryFuture { + color: $text-white; +} + +.entryFuture { + background-color: $bg-gray-950; +} + +.entryNow { + line-height: 5vh; + background-color: $bg-gray-900; + border-bottom: 0.5vh solid $ontime-pink; + margin-left: -0.5em; + padding-left: 0.5em; +} + +.backstageInd { + color: $ontime-pink; + margin-left: auto; + margin-right: -0.5vw; +} + +.backstageInd::before { + content: '*'; +} diff --git a/client/src/common/components/views/TitleCard.jsx b/client/src/common/components/views/TitleCard.jsx index a206f503e..530db772a 100644 --- a/client/src/common/components/views/TitleCard.jsx +++ b/client/src/common/components/views/TitleCard.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import style from './TitleCard.module.css'; +import style from './TitleCard.module.scss'; export default function TitleCard(props) { const { label, title, subtitle, presenter } = props; diff --git a/client/src/common/components/views/TitleCard.module.css b/client/src/common/components/views/TitleCard.module.scss similarity index 65% rename from client/src/common/components/views/TitleCard.module.css rename to client/src/common/components/views/TitleCard.module.scss index 9df583803..c596eef1a 100644 --- a/client/src/common/components/views/TitleCard.module.css +++ b/client/src/common/components/views/TitleCard.module.scss @@ -1,27 +1,24 @@ +@use '../../../styles/main' as *; + .label { - font-size: 1.3vw; - color: #ff7597; + @include card-label; } .title, .subtitle, .presenter { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + @include ellipsis; } .title { - color: #ddd; + @include card-title; font-size: 2.5vw; flex: 1; - font-weight: 600; - } .subtitle, .presenter { - color: #aaa; + color: $subtitle-gray; } .subtitle { diff --git a/client/src/common/components/views/TitleSide.jsx b/client/src/common/components/views/TitleSide.jsx index 590d07259..c57aa3432 100644 --- a/client/src/common/components/views/TitleSide.jsx +++ b/client/src/common/components/views/TitleSide.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import style from './TitleSide.module.css'; +import style from './TitleSide.module.scss'; export default function TitleSide(props) { const { type, label, title, subtitle, presenter } = props; diff --git a/client/src/common/components/views/TitleSide.module.css b/client/src/common/components/views/TitleSide.module.scss similarity index 83% rename from client/src/common/components/views/TitleSide.module.css rename to client/src/common/components/views/TitleSide.module.scss index 56049fdc9..08a253b7f 100644 --- a/client/src/common/components/views/TitleSide.module.css +++ b/client/src/common/components/views/TitleSide.module.scss @@ -1,19 +1,19 @@ +@use '../../../styles/main' as *; + .label { - font-size: 1.3vw; - color: #ff7597; + @include card-label; } .nowTitle, .nextTitle { - color: #ddd; - font-weight: 600; + @include card-title; } .nowSubtitle, .nowPresenter, .nextSubtitle, .nextPresenter { - color: #aaa; + color: $subtitle-gray; } .nowTitle { diff --git a/client/src/common/components/views/TodayItem.jsx b/client/src/common/components/views/TodayItem.jsx index 09e260894..eba700660 100644 --- a/client/src/common/components/views/TodayItem.jsx +++ b/client/src/common/components/views/TodayItem.jsx @@ -1,7 +1,7 @@ import React from 'react'; -import { stringFromMillis } from 'ontime-utils/time'; -import style from './Paginator.module.css'; import PropTypes from 'prop-types'; +import { stringFromMillis } from '../../utils/time'; +import style from './Paginator.module.scss'; export default function TodayItem(props) { const { selected, timeStart, timeEnd, title, backstageEvent, colour } = props; @@ -19,7 +19,7 @@ export default function TodayItem(props) { else if (selected === 2) selectStyle = style.entryFuture; return (
-
+
{`${start} · ${end}`}
{title}
@@ -29,7 +29,7 @@ export default function TodayItem(props) { } TodayItem.propTypes = { - selected: PropTypes.bool, + selected: PropTypes.number, timeStart: PropTypes.number, timeEnd: PropTypes.number, title: PropTypes.string, diff --git a/client/src/common/input/EditableText.module.scss b/client/src/common/input/EditableText.module.scss index 122ee22b5..69e9e5775 100644 --- a/client/src/common/input/EditableText.module.scss +++ b/client/src/common/input/EditableText.module.scss @@ -15,7 +15,7 @@ } .preview { - color: #666; + color: $bg-gray-500; } .inline { diff --git a/client/src/common/input/EditableTimer.jsx b/client/src/common/input/EditableTimer.jsx index 88db85aa1..4b627b10f 100644 --- a/client/src/common/input/EditableTimer.jsx +++ b/client/src/common/input/EditableTimer.jsx @@ -1,35 +1,16 @@ import React, { useContext, useEffect, useState } from 'react'; import { Editable, EditableInput, EditablePreview } from '@chakra-ui/editable'; import { forgivingStringToMillis } from '../utils/dateConfig'; -import { stringFromMillis } from 'ontime-utils/time'; -import style from './EditableTimer.module.css'; import { LoggingContext } from '../../app/context/LoggingContext'; import PropTypes from 'prop-types'; +import { stringFromMillis } from '../utils/time'; +import style from './EditableTimer.module.scss'; export default function EditableTimer(props) { const { name, actionHandler, time, delay, validate, previousEnd } = props; const { emitError } = useContext(LoggingContext); const [value, setValue] = useState(''); - // prepare time fields - const validateValue = (value) => { - const success = handleSubmit(value); - if (success) { - const ms = forgivingStringToMillis(value); - setValue(stringFromMillis(ms + delay)); - } else { - setValue(stringFromMillis(time + delay)); - } - }; - - useEffect(() => { - if (time == null) return; - try { - setValue(stringFromMillis(time + delay)); - } catch (error) { - emitError(`Unable to parse date: ${error.text}`); - } - }, [time, delay, emitError]); const handleSubmit = (value) => { // Check if there is anything there @@ -67,6 +48,26 @@ export default function EditableTimer(props) { return true; }; + // prepare time fields + const validateValue = (value) => { + const success = handleSubmit(value); + if (success) { + const ms = forgivingStringToMillis(value); + setValue(stringFromMillis(ms + delay)); + } else { + setValue(stringFromMillis(time + delay)); + } + }; + + useEffect(() => { + if (time == null) return; + try { + setValue(stringFromMillis(time + delay)); + } catch (error) { + emitError(`Unable to parse date: ${error.text}`); + } + }, [time, delay, emitError]); + const isDelayed = delay != null && delay !== 0; return ( diff --git a/client/src/common/input/EditableTimer.module.css b/client/src/common/input/EditableTimer.module.scss similarity index 56% rename from client/src/common/input/EditableTimer.module.css rename to client/src/common/input/EditableTimer.module.scss index 1f35dcad8..8307bc17d 100644 --- a/client/src/common/input/EditableTimer.module.css +++ b/client/src/common/input/EditableTimer.module.scss @@ -1,7 +1,9 @@ +@use '../../styles/main'as *; + .editable, .delayedEditable { - background-color: rgba(255, 255, 255, 0.03); - border: 1px solid rgba(255, 255, 255, 0.05); + background-color: $input-bg; + border: $input-border; width: 6.5em; letter-spacing: 1px; height: fit-content; @@ -11,5 +13,5 @@ } .delayedEditable { - border: 1px solid #d69e2e55; + border: $input-delayed-border; } diff --git a/client/src/common/state/Empty.jsx b/client/src/common/state/Empty.jsx index f0cee053b..28eb00b61 100644 --- a/client/src/common/state/Empty.jsx +++ b/client/src/common/state/Empty.jsx @@ -1,13 +1,18 @@ import React from 'react'; import { ReactComponent as Emptyimage } from 'assets/images/empty.svg'; -import style from './Empty.module.css'; +import PropTypes from 'prop-types'; +import style from './Empty.module.scss'; export default function Empty(props) { - const { text } = props; + const { text, ...rest } = props; return ( -
+
{text}
); } + +Empty.propTypes = { + text: PropTypes.string, +} \ No newline at end of file diff --git a/client/src/common/state/Empty.module.css b/client/src/common/state/Empty.module.scss similarity index 68% rename from client/src/common/state/Empty.module.css rename to client/src/common/state/Empty.module.scss index 6a1ceafed..d4b7b0100 100644 --- a/client/src/common/state/Empty.module.css +++ b/client/src/common/state/Empty.module.scss @@ -1,3 +1,5 @@ +@use '../../styles/main' as *; + .emptyContainer { width: 100%; text-align: center; @@ -5,13 +7,11 @@ .empty { width: 100%; - margin: auto 0; - margin-top: 10vh; opacity: 0.3; } .text { - color: rgba(0, 0, 0, 0.17); + color: $bg-black-300; font-weight: 600; font-size: 2em; } diff --git a/client/src/common/utils/__tests__/dateConfig.test.js b/client/src/common/utils/__tests__/dateConfig.test.js index 934d52b8f..691d8261c 100644 --- a/client/src/common/utils/__tests__/dateConfig.test.js +++ b/client/src/common/utils/__tests__/dateConfig.test.js @@ -6,7 +6,7 @@ import { millisToSeconds, timeStringToMillis, } from '../dateConfig'; -import { stringFromMillis } from 'ontime-utils/time'; +import { stringFromMillis } from '../time'; describe('test string from formatDisplay function', () => { it('test with null values', () => { diff --git a/server/utils/__tests__/generate_id.test.js b/client/src/common/utils/__tests__/generate_id.test.js similarity index 100% rename from server/utils/__tests__/generate_id.test.js rename to client/src/common/utils/__tests__/generate_id.test.js diff --git a/client/src/common/utils/dateConfig.js b/client/src/common/utils/dateConfig.js index 47e29b7db..12d26542b 100644 --- a/client/src/common/utils/dateConfig.js +++ b/client/src/common/utils/dateConfig.js @@ -21,7 +21,7 @@ export function formatDisplay(seconds, hideZero = false) { const minutes = Math.floor((s % 3600) / 60); if (hideZero && hours < 1) return [minutes, s % 60].map(format).join(':'); - else return [hours, minutes, s % 60].map(format).join(':'); + return [hours, minutes, s % 60].map(format).join(':'); } /** @@ -53,7 +53,7 @@ export const timeStringToMillis = (string) => { if (time.length === 1) return Math.abs(time[0] * mts); if (time.length === 2) return Math.abs(time[0]) * mtm + time[1] * mts; if (time.length === 3) return Math.abs(time[0]) * mth + time[1] * mtm + time[2] * mts; - else return 0; + return 0; }; /** diff --git a/client/src/common/utils/eventsManager.js b/client/src/common/utils/eventsManager.js index ee38ab066..2c7a70138 100644 --- a/client/src/common/utils/eventsManager.js +++ b/client/src/common/utils/eventsManager.js @@ -1,10 +1,11 @@ -import { stringFromMillis } from 'ontime-utils/time'; +import { stringFromMillis } from './time'; /** * @description From a list of events, returns only events of type event with calculated delays * @param {Object[]} events - given events * @returns {Object[]} Filtered events with calculated delays */ + export const getEventsWithDelay = (events) => { if (events == null) return []; @@ -66,7 +67,7 @@ export const formatEventList = (events, selectedId, nextId, showEnd = false) => const givenEvents = [...events]; // format list - let formattedEvents = []; + const formattedEvents = []; for (const g of givenEvents) { const start = stringFromMillis(g.timeStart, false); const end = stringFromMillis(g.timeEnd, false); diff --git a/server/utils/generate_id.js b/client/src/common/utils/generate_id.js similarity index 100% rename from server/utils/generate_id.js rename to client/src/common/utils/generate_id.js diff --git a/server/utils/time.js b/client/src/common/utils/time.js similarity index 100% rename from server/utils/time.js rename to client/src/common/utils/time.js diff --git a/client/src/features/control/PlaybackButtons.jsx b/client/src/features/control/PlaybackButtons.jsx deleted file mode 100644 index aa3c1962c..000000000 --- a/client/src/features/control/PlaybackButtons.jsx +++ /dev/null @@ -1,105 +0,0 @@ -import React, { memo } from 'react'; -import PropTypes from 'prop-types'; -import style from './PlaybackControl.module.scss'; -import StartIconBtn from 'common/components/buttons/StartIconBtn'; -import PauseIconBtn from 'common/components/buttons/PauseIconBtn'; -import PrevIconBtn from 'common/components/buttons/PrevIconBtn'; -import NextIconBtn from 'common/components/buttons/NextIconBtn'; -import RollIconBtn from 'common/components/buttons/RollIconBtn'; -import UnloadIconBtn from 'common/components/buttons/UnloadIconBtn'; -import ReloadIconButton from 'common/components/buttons/ReloadIconBtn'; - -const areEqual = (prevProps, nextProps) => { - return ( - prevProps.playback === nextProps.playback - && prevProps.selectedId === nextProps.selectedId - && prevProps.noEvents === nextProps.noEvents - ); -}; - -const Playback = (props) => { - const { playback, selectedId, playbackControl, noEvents } = props; - const isRolling = playback === 'roll'; - - return ( -
- playbackControl('start')} - disabled={!selectedId || isRolling || noEvents} - /> - playbackControl('pause')} - disabled={!selectedId || isRolling || noEvents || playback !== 'start'} - /> - playbackControl('roll')} - /> -
- ); -}; - -const Transport = (props) => { - const { playback, selectedId, playbackControl, noEvents } = props; - const isRolling = playback === 'roll'; - - return ( -
- playbackControl('previous')} - disabled={isRolling || noEvents} - /> - playbackControl('next')} - disabled={isRolling || noEvents} - /> - playbackControl('reload')} - disabled={selectedId == null || isRolling || noEvents} - /> - playbackControl('unload')} - disabled={(selectedId == null && !isRolling) || noEvents} - /> -
- ); -}; - -const PlaybackButtons = (props) => { - const { playback, selectedId, noEvents, playbackControl } = props; - return ( - <> - - - - ); -}; - -export default memo(PlaybackButtons, areEqual); - -PlaybackButtons.propTypes = { - playback: PropTypes.string, - selectedId: PropTypes.string, - playbackControl: PropTypes.func.isRequired, - noEvents: PropTypes.bool.isRequired, -}; - -Transport.propTypes = { - playback: PropTypes.string, - selectedId: PropTypes.string, - playbackControl: PropTypes.func.isRequired, - noEvents: PropTypes.bool.isRequired, -}; diff --git a/client/src/features/control/PlaybackTimer.jsx b/client/src/features/control/PlaybackTimer.jsx deleted file mode 100644 index 0b5790b88..000000000 --- a/client/src/features/control/PlaybackTimer.jsx +++ /dev/null @@ -1,140 +0,0 @@ -import React from 'react'; -import style from './PlaybackControl.module.scss'; -import Countdown from 'common/components/countdown/Countdown'; -import { stringFromMillis } from 'ontime-utils/time'; -import { Tooltip } from '@chakra-ui/react'; -import { Button } from '@chakra-ui/button'; -import { memo } from 'react'; -import PropTypes from 'prop-types'; - -const areEqual = (prevProps, nextProps) => { - return ( - prevProps.timer.running === nextProps.timer.running && - prevProps.timer.isNegative === nextProps.timer.isNegative && - prevProps.timer.expectedFinish === nextProps.timer.expectedFinish && - prevProps.timer.startedAt === nextProps.timer.startedAt && - prevProps.playback === nextProps.playback && - prevProps.timer.secondary === nextProps.timer.secondary && - prevProps.selectedId === nextProps.selectedId - ); -}; - -const incrementProps = { - size: 'sm', - width: '2.9em', - colorScheme: 'whiteAlpha', - variant: 'outline', - _focus: { boxShadow: 'none' }, -}; - -const PlaybackTimer = (props) => { - const { timer, playback, handleIncrement, selectedId } = props; - const started = stringFromMillis(timer.startedAt, true); - const finish = stringFromMillis(timer.expectedFinish, true); - const isRolling = playback === 'roll'; - const isWaiting = timer.secondary > 0 && timer.running == null; - const disableButtons = selectedId == null || isRolling; - - return ( - <> -
-
- -
- -
-
-
-
- -
- {isWaiting ? ( -
- Roll: Countdown to start - FIX -
- ) : ( - <> -
- Started at - {started} -
-
- Finish at - {finish} -
- - )} -
- - - - - - - - - - - - -
-
- - ); -}; - -export default memo(PlaybackTimer, areEqual); - -PlaybackTimer.propTypes = { - timer: PropTypes.object.isRequired, - playback: PropTypes.string, - handleIncrement: PropTypes.func.isRequired, - selectedId: PropTypes.string, -}; diff --git a/client/src/features/control/__tests__/MessageControl.test.js b/client/src/features/control/__tests__/MessageControl.test.js index 2eb24ca70..10ea53059 100644 --- a/client/src/features/control/__tests__/MessageControl.test.js +++ b/client/src/features/control/__tests__/MessageControl.test.js @@ -1,6 +1,7 @@ +import React from 'react'; import { render, screen } from '@testing-library/react'; import SocketProvider from 'app/context/socketContext'; -import MessageControl from '../MessageControl'; +import MessageControl from '../message/MessageControl'; // need to inject the socket provider to make component // render without failing diff --git a/client/src/features/control/__tests__/PlaybackControl.test.js b/client/src/features/control/__tests__/PlaybackControl.test.js index b4a41149c..19a80003a 100644 --- a/client/src/features/control/__tests__/PlaybackControl.test.js +++ b/client/src/features/control/__tests__/PlaybackControl.test.js @@ -1,6 +1,7 @@ +import React from 'react'; import { render, screen } from '@testing-library/react'; import SocketProvider from 'app/context/socketContext'; -import PlaybackControl from '../PlaybackControl'; +import PlaybackControl from '../playback/PlaybackControl'; test('check that playback control renders', async () => { // need to inject the socket provider to make component diff --git a/client/src/features/control/message/InputRow.jsx b/client/src/features/control/message/InputRow.jsx new file mode 100644 index 000000000..abd6f3dbb --- /dev/null +++ b/client/src/features/control/message/InputRow.jsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { Editable, EditableInput, EditablePreview } from '@chakra-ui/editable'; +import VisibleIconBtn from '../../../common/components/buttons/VisibleIconBtn'; +import style from './MessageControl.module.scss'; + +const inputProps = { + size: 'sm', +}; + +export default function InputRow(props) { + const { label, placeholder, text, visible, actionHandler, changeHandler } = props; + + return ( + <> + {label} +
+ changeHandler(event)} + value={text} + placeholder={placeholder} + className={style.inline} + color={text === '' ? '#666' : 'inherit'} + > + + + + +
+ + ); +} diff --git a/client/src/features/control/MessageControl.jsx b/client/src/features/control/message/MessageControl.jsx similarity index 77% rename from client/src/features/control/MessageControl.jsx rename to client/src/features/control/message/MessageControl.jsx index 1e6a05e94..4f2e2d4a4 100644 --- a/client/src/features/control/MessageControl.jsx +++ b/client/src/features/control/message/MessageControl.jsx @@ -1,41 +1,9 @@ import React, { useEffect, useState } from 'react'; -import { Editable, EditableInput, EditablePreview } from '@chakra-ui/editable'; import { useSocket } from 'app/context/socketContext'; -import VisibleIconBtn from 'common/components/buttons/VisibleIconBtn'; -import OnAirIconBtn from '../../common/components/buttons/OnAirIconBtn'; +import InputRow from './InputRow'; +import OnAirIconBtn from '../../../common/components/buttons/OnAirIconBtn'; import style from './MessageControl.module.scss'; -const inputProps = { - size: 'sm', -}; - -const InputRow = (props) => { - const { label, placeholder, text, visible, actionHandler, changeHandler } = props; - - return ( - <> - {label} -
- changeHandler(event)} - value={text} - placeholder={placeholder} - className={style.inline} - color={text === '' ? '#666' : 'inherit'} - > - - - - -
- - ); -}; - export default function MessageControl() { const socket = useSocket(); const [pres, setPres] = useState({ diff --git a/client/src/features/control/MessageControl.module.scss b/client/src/features/control/message/MessageControl.module.scss similarity index 81% rename from client/src/features/control/MessageControl.module.scss rename to client/src/features/control/message/MessageControl.module.scss index 1ff50b75e..e726c1b9e 100644 --- a/client/src/features/control/MessageControl.module.scss +++ b/client/src/features/control/message/MessageControl.module.scss @@ -1,5 +1,5 @@ -@use '../../styles/main' as *; -@use '../../styles/mixins' as *; +@use '../../../styles/main' as *; +@use '../../../styles/mixins' as *; .messageContainer, .onAirToggle { @@ -22,13 +22,13 @@ padding: 0; margin: 0; font-size: 0.9em; - color: #ccc; + color: $label-gray; } .inline { border-radius: 4px; - background-color: rgba(255, 255, 255, 0.05); - border: 1px solid rgba(255, 255, 255, 0.05); + background-color: $input-bg; + border: $input-border; } .padleft { diff --git a/client/src/features/control/playback/Playback.jsx b/client/src/features/control/playback/Playback.jsx new file mode 100644 index 000000000..d5a8f782f --- /dev/null +++ b/client/src/features/control/playback/Playback.jsx @@ -0,0 +1,38 @@ +import React from 'react'; +import StartIconBtn from '../../../common/components/buttons/StartIconBtn'; +import PauseIconBtn from '../../../common/components/buttons/PauseIconBtn'; +import RollIconBtn from '../../../common/components/buttons/RollIconBtn'; +import PropTypes from 'prop-types'; +import style from './PlaybackControl.module.scss'; + +export default function Playback(props) { + const { playback, selectedId, playbackControl, noEvents } = props; + const isRolling = playback === 'roll'; + + return ( +
+ playbackControl('start')} + disabled={!selectedId || isRolling || noEvents} + /> + playbackControl('pause')} + disabled={!selectedId || isRolling || noEvents || playback !== 'start'} + /> + playbackControl('roll')} + /> +
+ ); +} + +Playback.propTypes = { + playback: PropTypes.string, + selectedId: PropTypes.string, + playbackControl: PropTypes.func.isRequired, + noEvents: PropTypes.bool.isRequired, +}; diff --git a/client/src/features/control/playback/PlaybackButtons.jsx b/client/src/features/control/playback/PlaybackButtons.jsx new file mode 100644 index 000000000..5225ff3a6 --- /dev/null +++ b/client/src/features/control/playback/PlaybackButtons.jsx @@ -0,0 +1,41 @@ +import React, { memo } from 'react'; +import PropTypes from 'prop-types'; +import Transport from './Transport'; +import Playback from './Playback'; + +const areEqual = (prevProps, nextProps) => { + return ( + prevProps.playback === nextProps.playback && + prevProps.selectedId === nextProps.selectedId && + prevProps.noEvents === nextProps.noEvents + ); +}; + +const PlaybackButtons = (props) => { + const { playback, selectedId, noEvents, playbackControl } = props; + return ( + <> + + + + ); +}; + +export default memo(PlaybackButtons, areEqual); + +PlaybackButtons.propTypes = { + playback: PropTypes.string, + selectedId: PropTypes.string, + playbackControl: PropTypes.func.isRequired, + noEvents: PropTypes.bool.isRequired, +}; diff --git a/client/src/features/control/PlaybackControl.jsx b/client/src/features/control/playback/PlaybackControl.jsx similarity index 98% rename from client/src/features/control/PlaybackControl.jsx rename to client/src/features/control/playback/PlaybackControl.jsx index 0bc5a72b8..453ff9cc1 100644 --- a/client/src/features/control/PlaybackControl.jsx +++ b/client/src/features/control/playback/PlaybackControl.jsx @@ -63,7 +63,7 @@ export default function PlaybackControl() { }; }, [socket]); - const playbackControl = async (action, payload) => { + const playbackControl = async (action) => { switch (action) { case 'start': socket.emit('set-playstate', 'start'); diff --git a/client/src/features/control/PlaybackControl.module.scss b/client/src/features/control/playback/PlaybackControl.module.scss similarity index 81% rename from client/src/features/control/PlaybackControl.module.scss rename to client/src/features/control/playback/PlaybackControl.module.scss index 1010bb00f..0a1a532af 100644 --- a/client/src/features/control/PlaybackControl.module.scss +++ b/client/src/features/control/playback/PlaybackControl.module.scss @@ -1,16 +1,16 @@ +@use '../../../styles/main' as *; +@use '../../../styles/mixins' as *; + .mainContainer { width: 100%; - display: flex; + display: grid; margin: 0 auto; - flex-direction: column; gap: 5px; } .timeContainer, .playbackContainer { - background-color: rgba(0, 0, 0, 0.05); - border: 1px solid rgba(0, 0, 0, 0.05); - border-radius: 4px; + @include second-container; padding: 0.5em; } @@ -43,7 +43,7 @@ .indRoll, .indDelay, .indNegative { - background-color: rgba(0, 0, 0, 0.05); + background-color: $bg-black-300; } .indRoll, @@ -56,7 +56,7 @@ } .indRollActive { - background-color: #2b6cb0; + background-color: $ontime-roll; } .indNegative, @@ -67,11 +67,11 @@ } .indNegativeActive { - background-color: #ff7597; + background-color: $ontime-pink; } .indDelayActive { - background-color: #dd6b20; + background-color: $ontime-delay; } .btn { @@ -102,17 +102,17 @@ } .time { - color: #ccc; + color: $header-gray; font-size: 1.1em; } .tag { - color: #aaa; + color: $label-gray; font-size: 0.9em; } .rolltag { - color: #2b6cb0; + color: $ontime-roll; font-size: 0.9em; } diff --git a/client/src/features/control/playback/PlaybackTimer.jsx b/client/src/features/control/playback/PlaybackTimer.jsx new file mode 100644 index 000000000..a38e173c2 --- /dev/null +++ b/client/src/features/control/playback/PlaybackTimer.jsx @@ -0,0 +1,103 @@ +import React, { memo } from 'react'; +import Countdown from 'common/components/countdown/Countdown'; +import { Tooltip } from '@chakra-ui/react'; +import { Button } from '@chakra-ui/button'; +import PropTypes from 'prop-types'; +import { stringFromMillis } from '../../../common/utils/time'; +import style from './PlaybackControl.module.scss'; + +const areEqual = (prevProps, nextProps) => { + return ( + prevProps.timer.running === nextProps.timer.running && + prevProps.timer.isNegative === nextProps.timer.isNegative && + prevProps.timer.expectedFinish === nextProps.timer.expectedFinish && + prevProps.timer.startedAt === nextProps.timer.startedAt && + prevProps.playback === nextProps.playback && + prevProps.timer.secondary === nextProps.timer.secondary && + prevProps.selectedId === nextProps.selectedId + ); +}; + +const incrementProps = { + size: 'sm', + width: '2.9em', + colorScheme: 'whiteAlpha', + variant: 'outline', + _focus: { boxShadow: 'none' }, +}; + +const PlaybackTimer = (props) => { + const { timer, playback, handleIncrement, selectedId } = props; + const started = stringFromMillis(timer.startedAt, true); + const finish = stringFromMillis(timer.expectedFinish, true); + const isRolling = playback === 'roll'; + const isWaiting = timer.secondary > 0 && timer.running == null; + const disableButtons = selectedId == null || isRolling; + + return ( +
+
+ +
+ +
+
+
+
+ +
+ {isWaiting ? ( +
+ Roll: Countdown to start + FIX +
+ ) : ( + <> +
+ Started at + {started} +
+
+ Finish at + {finish} +
+ + )} +
+ + + + + + + + + + + + +
+
+ ); +}; + +export default memo(PlaybackTimer, areEqual); + +PlaybackTimer.propTypes = { + timer: PropTypes.object.isRequired, + playback: PropTypes.string, + handleIncrement: PropTypes.func.isRequired, + selectedId: PropTypes.string, +}; diff --git a/client/src/features/control/playback/Transport.jsx b/client/src/features/control/playback/Transport.jsx new file mode 100644 index 000000000..0369b1565 --- /dev/null +++ b/client/src/features/control/playback/Transport.jsx @@ -0,0 +1,40 @@ +import React from 'react'; +import PrevIconBtn from '../../../common/components/buttons/PrevIconBtn'; +import NextIconBtn from '../../../common/components/buttons/NextIconBtn'; +import ReloadIconButton from '../../../common/components/buttons/ReloadIconBtn'; +import UnloadIconBtn from '../../../common/components/buttons/UnloadIconBtn'; +import PropTypes from 'prop-types'; +import style from './PlaybackControl.module.scss'; + +export default function Transport(props) { + const { playback, selectedId, playbackControl, noEvents } = props; + const isRolling = playback === 'roll'; + + return ( +
+ playbackControl('previous')} + disabled={isRolling || noEvents} + /> + playbackControl('next')} + disabled={isRolling || noEvents} + /> + playbackControl('reload')} + disabled={selectedId == null || isRolling || noEvents} + /> + playbackControl('unload')} + disabled={(selectedId == null && !isRolling) || noEvents} + /> +
+ ); +}; + +Transport.propTypes = { + playback: PropTypes.string, + selectedId: PropTypes.string, + playbackControl: PropTypes.func.isRequired, + noEvents: PropTypes.bool.isRequired, +}; diff --git a/client/src/features/editors/BlockBlock/BlockBlock.jsx b/client/src/features/editors/BlockBlock/BlockBlock.jsx index ed5cd4fa0..d846aeda5 100644 --- a/client/src/features/editors/BlockBlock/BlockBlock.jsx +++ b/client/src/features/editors/BlockBlock/BlockBlock.jsx @@ -1,4 +1,5 @@ import React from 'react'; +import { HStack } from '@chakra-ui/react'; import { Draggable } from 'react-beautiful-dnd'; import { FiMoreVertical } from '@react-icons/all-files/fi/FiMoreVertical'; import DeleteIconBtn from 'common/components/buttons/DeleteIconBtn'; @@ -20,10 +21,10 @@ export default function BlockBlock(props) { -
+ -
+
)} diff --git a/client/src/features/editors/BlockBlock/BlockBlock.module.scss b/client/src/features/editors/BlockBlock/BlockBlock.module.scss index c3e2d4011..7caab45db 100644 --- a/client/src/features/editors/BlockBlock/BlockBlock.module.scss +++ b/client/src/features/editors/BlockBlock/BlockBlock.module.scss @@ -41,7 +41,6 @@ align-content: center; align-self: flex-start; padding-top: 0.2em; - gap: 0.5em; opacity: 0.8; transition: linear 0.1s; justify-self: end; diff --git a/client/src/features/editors/DelayBlock/DelayBlock.jsx b/client/src/features/editors/DelayBlock/DelayBlock.jsx index 1cb3a71a3..478fb78c5 100644 --- a/client/src/features/editors/DelayBlock/DelayBlock.jsx +++ b/client/src/features/editors/DelayBlock/DelayBlock.jsx @@ -1,4 +1,5 @@ import React from 'react'; +import { HStack } from '@chakra-ui/react'; import { Draggable } from 'react-beautiful-dnd'; import { FiMoreVertical } from '@react-icons/all-files/fi/FiMoreVertical'; import { millisToMinutes } from 'common/utils/dateConfig'; @@ -16,7 +17,7 @@ export default function DelayBlock(props) { eventsHandler('applyDelay', { id: data.id, duration: data.duration }); }; - let delayValue = data.duration != null ? millisToMinutes(data.duration) : undefined; + const delayValue = data.duration != null ? millisToMinutes(data.duration) : undefined; return ( {(provided) => ( @@ -25,11 +26,11 @@ export default function DelayBlock(props) { -
+ -
+
)} diff --git a/client/src/features/editors/DelayBlock/DelayBlock.module.scss b/client/src/features/editors/DelayBlock/DelayBlock.module.scss index 3b3e47b28..1b5dfc65c 100644 --- a/client/src/features/editors/DelayBlock/DelayBlock.module.scss +++ b/client/src/features/editors/DelayBlock/DelayBlock.module.scss @@ -52,7 +52,6 @@ align-content: center; align-self: flex-start; padding-top: 0.2em; - gap: 0.5em; opacity: 0.8; transition: linear 0.1s; } diff --git a/client/src/features/editors/Editor.jsx b/client/src/features/editors/Editor.jsx index 99a4d6471..8fd309469 100644 --- a/client/src/features/editors/Editor.jsx +++ b/client/src/features/editors/Editor.jsx @@ -11,8 +11,8 @@ import { CollapseProvider } from '../../app/context/CollapseContext'; import styles from './Editor.module.scss'; const EventListWrapper = lazy(() => import('features/editors/list/EventListWrapper')); -const PlaybackControl = lazy(() => import('features/control/PlaybackControl')); -const MessageControl = lazy(() => import('features/control/MessageControl')); +const PlaybackControl = lazy(() => import('features/control/playback/PlaybackControl')); +const MessageControl = lazy(() => import('features/control/message/MessageControl')); const Info = lazy(() => import('features/info/Info')); export default function Editor() { @@ -35,7 +35,7 @@ export default function Editor() { - + diff --git a/client/src/features/editors/Editor.module.scss b/client/src/features/editors/Editor.module.scss index 51c39b1ed..a378e8783 100644 --- a/client/src/features/editors/Editor.module.scss +++ b/client/src/features/editors/Editor.module.scss @@ -1,9 +1,11 @@ +@use '../../styles/main' as *; + .mainContainer { - background: linear-gradient(90deg, #202020 0%, #121212 100%); + background: $bg-black; width: 100%; height: 100%; margin: auto; - color: #fffd; + color: $title-white; padding: max(16px, 2vh); display: grid; @@ -94,14 +96,14 @@ h1 { font-size: max(1.5em, 16px); - color: rgba(255, 255, 255, 0.63); + color: $bg-gray-100; padding-bottom: 0.25em; } .mainContainer > div { border-radius: 0.5em; height: 100%; - background-color: rgba(255, 255, 255, 0.13); + background-color: $bg-gray-1000; padding: 0.8em 1.5em; display: flex; diff --git a/client/src/features/editors/EventBlock/CollapsedBlock.jsx b/client/src/features/editors/EventBlock/CollapsedBlock.jsx new file mode 100644 index 000000000..8f181ee80 --- /dev/null +++ b/client/src/features/editors/EventBlock/CollapsedBlock.jsx @@ -0,0 +1,56 @@ +import React from 'react'; +import { HStack } from '@chakra-ui/react'; +import { FiMoreVertical } from '@react-icons/all-files/fi/FiMoreVertical'; +import EventTimes from '../../../common/components/eventTimes/EventTimes'; +import EditableText from '../../../common/input/EditableText'; +import PublicIconBtn from '../../../common/components/buttons/PublicIconBtn'; +import ActionButtons from '../list/ActionButtons'; +import PropTypes from 'prop-types'; +import style from './EventBlock.module.scss'; + +export default function CollapsedBlock (props) { + const { provided, data, next, delay, delayValue, previousEnd, actionHandler } = props; + + return ( + <> + + + + +
+ Next + {delayValue != null && {delayValue}} +
+ +
+ actionHandler('update', { field: 'title', value: v })} + /> +
+ + + + + + ); +}; + +CollapsedBlock.propTypes = { + provided: PropTypes.any.isRequired, + data: PropTypes.object.isRequired, + next: PropTypes.bool.isRequired, + delay: PropTypes.any, + delayValue: PropTypes.string, + previousEnd: PropTypes.number.isRequired, + actionHandler: PropTypes.func.isRequired, +}; diff --git a/client/src/features/editors/EventBlock/EventBlock.jsx b/client/src/features/editors/EventBlock/EventBlock.jsx index addd7e9a2..4d3a2afd5 100644 --- a/client/src/features/editors/EventBlock/EventBlock.jsx +++ b/client/src/features/editors/EventBlock/EventBlock.jsx @@ -1,143 +1,13 @@ import React, { useContext, useMemo } from 'react'; import Icon from '@chakra-ui/icon'; import { FiChevronUp } from '@react-icons/all-files/fi/FiChevronUp'; -import { FiMoreVertical } from '@react-icons/all-files/fi/FiMoreVertical'; import { Draggable } from 'react-beautiful-dnd'; -import EventTimes from 'common/components/eventTimes/EventTimes'; -import EventTimesVertical from 'common/components/eventTimes/EventTimesVertical'; -import EditableText from 'common/input/EditableText'; -import ActionButtons from '../list/ActionButtons'; -import PublicIconBtn from 'common/components/buttons/PublicIconBtn'; -import DeleteIconBtn from 'common/components/buttons/DeleteIconBtn'; import { millisToMinutes } from 'common/utils/dateConfig'; import PropTypes from 'prop-types'; import { CollapseContext } from '../../../app/context/CollapseContext'; -import style from './EventBlock.module.css'; - -const ExpandedBlock = (props) => { - const { provided, data, eventIndex, next, delay, delayValue, previousEnd, actionHandler } = props; - - const oscid = data?.id || '...'; - - return ( - <> - - - - -
- Next - {delayValue != null && {delayValue}} -
-
- -
- -
- actionHandler('update', { field: 'title', value: v })} - /> - actionHandler('update', { field: 'presenter', value: v })} - /> - actionHandler('update', { field: 'subtitle', value: v })} - /> - actionHandler('update', { field: 'note', value: v })} - /> - - {`/ontime/goto ${eventIndex + 1} << OSC >> /ontime/gotoid ${oscid}`} - -
-
- - - -
- - ); -}; - -ExpandedBlock.propTypes = { - provided: PropTypes.any.isRequired, - data: PropTypes.object.isRequired, - eventIndex: PropTypes.number.isRequired, - next: PropTypes.bool.isRequired, - delay: PropTypes.number, - delayValue: PropTypes.string, - previousEnd: PropTypes.number, - actionHandler: PropTypes.func.isRequired, -}; - -const CollapsedBlock = (props) => { - const { provided, data, next, delay, delayValue, previousEnd, actionHandler } = props; - - return ( - <> - - - - -
- Next - {delayValue != null && {delayValue}} -
- -
- actionHandler('update', { field: 'title', value: v })} - /> -
-
- - -
- - ); -}; - -CollapsedBlock.propTypes = { - provided: PropTypes.any.isRequired, - data: PropTypes.object.isRequired, - next: PropTypes.bool.isRequired, - delay: PropTypes.any, - delayValue: PropTypes.string, - previousEnd: PropTypes.number.isRequired, - actionHandler: PropTypes.func.isRequired, -}; +import CollapsedBlock from './CollapsedBlock'; +import ExpandedBlock from './ExpandedBlock'; +import style from './EventBlock.module.scss'; export default function EventBlock(props) { const { data, selected, delay, index, eventIndex, previousEnd, actionHandler, next } = props; diff --git a/client/src/features/editors/EventBlock/EventBlock.module.css b/client/src/features/editors/EventBlock/EventBlock.module.scss similarity index 97% rename from client/src/features/editors/EventBlock/EventBlock.module.css rename to client/src/features/editors/EventBlock/EventBlock.module.scss index c9cd728fe..65c381c01 100644 --- a/client/src/features/editors/EventBlock/EventBlock.module.css +++ b/client/src/features/editors/EventBlock/EventBlock.module.scss @@ -1,3 +1,5 @@ +@use '../../../styles/main' as *; + /* ============= COMMON ============= */ .event { margin: 0.2em 0; @@ -8,11 +10,11 @@ border-radius: 8px; font-size: 15px; border: 1px solid rgba(255, 255, 255, 0.05); + background-color: $bg-gray-950; display: grid; gap: 0.5em; - background-color: rgba(255, 255, 255, 0.02); } .active { @@ -69,7 +71,7 @@ .nextDisabled { width: max-content; padding: 0 0.2em; - color: #4bffab; + color: $ontime-accent; transition: 0.3s; } @@ -199,7 +201,6 @@ align-content: center; align-self: flex-start; padding-top: 0.2em; - gap: 0.5em; opacity: 0.8; transition: linear 0.1s; } diff --git a/client/src/features/editors/EventBlock/ExpandedBlock.jsx b/client/src/features/editors/EventBlock/ExpandedBlock.jsx new file mode 100644 index 000000000..c1161a863 --- /dev/null +++ b/client/src/features/editors/EventBlock/ExpandedBlock.jsx @@ -0,0 +1,88 @@ +import React from 'react'; +import { VStack } from '@chakra-ui/react'; +import { FiMoreVertical } from '@react-icons/all-files/fi/FiMoreVertical'; +import EventTimesVertical from '../../../common/components/eventTimes/EventTimesVertical'; +import EditableText from '../../../common/input/EditableText'; +import PublicIconBtn from '../../../common/components/buttons/PublicIconBtn'; +import ActionButtons from '../list/ActionButtons'; +import DeleteIconBtn from '../../../common/components/buttons/DeleteIconBtn'; +import PropTypes from 'prop-types'; +import style from './EventBlock.module.scss'; + +export default function ExpandedBlock(props) { + const { provided, data, eventIndex, next, delay, delayValue, previousEnd, actionHandler } = props; + + const oscid = data?.id || '...'; + + return ( + <> + + + + +
+ Next + {delayValue != null && {delayValue}} +
+
+ +
+ +
+ actionHandler('update', { field: 'title', value: v })} + /> + actionHandler('update', { field: 'presenter', value: v })} + /> + actionHandler('update', { field: 'subtitle', value: v })} + /> + actionHandler('update', { field: 'note', value: v })} + /> + + {`/ontime/goto ${eventIndex + 1} << OSC >> /ontime/gotoid ${oscid}`} + +
+ + + + + + + ); +}; + +ExpandedBlock.propTypes = { + provided: PropTypes.any.isRequired, + data: PropTypes.object.isRequired, + eventIndex: PropTypes.number.isRequired, + next: PropTypes.bool.isRequired, + delay: PropTypes.number, + delayValue: PropTypes.string, + previousEnd: PropTypes.number, + actionHandler: PropTypes.func.isRequired, +}; diff --git a/client/src/features/editors/list/EventList.jsx b/client/src/features/editors/list/EventList.jsx index 63e4b0e72..223ff3457 100644 --- a/client/src/features/editors/list/EventList.jsx +++ b/client/src/features/editors/list/EventList.jsx @@ -127,7 +127,7 @@ export default function EventList(props) { }, [selectedId, isCursorLocked]); if (events.length < 1) { - return ; + return ; } // DND @@ -178,7 +178,7 @@ export default function EventList(props) { )}
{ eventsHandler, delay, previousEnd, - ...rest } = props; const { emitError } = useContext(LoggingContext); const { starTimeIsLastEnd, defaultPublic } = useContext(LocalEventSettingsContext); @@ -42,6 +41,7 @@ const EventListItem = (props) => { (start, end) => (start > end ? end + 86400000 - start : end - start), [] ); + // Create / delete new events const actionHandler = useCallback( (action, payload) => { @@ -101,7 +101,7 @@ const EventListItem = (props) => { break; } }, - [data, defaultPublic, emitError, eventsHandler, index, starTimeIsLastEnd] + [calculateDuration, data, defaultPublic, emitError, eventsHandler, index, starTimeIsLastEnd] ); switch (type) { diff --git a/client/src/features/editors/list/EventListWrapper.jsx b/client/src/features/editors/list/EventListWrapper.jsx index 7804f24fd..f77be8de7 100644 --- a/client/src/features/editors/list/EventListWrapper.jsx +++ b/client/src/features/editors/list/EventListWrapper.jsx @@ -39,7 +39,7 @@ export default function EventListWrapper() { } // optimistically update object, temp ID until refetch - let optimistic = [...previousEvents]; + const optimistic = [...previousEvents]; optimistic.splice(newEvent.order, 0, { ...newEvent, id: new Date().toISOString(), @@ -128,8 +128,7 @@ export default function EventListWrapper() { // Snapshot the previous value const previousEvents = queryClient.getQueryData(EVENTS_TABLE); - let filtered = [...previousEvents]; - filtered.filter((e) => e.id === 'eventId'); + const filtered = [...previousEvents].filter((e) => e.id === 'eventId') // optimistically update object queryClient.setQueryData(EVENTS_TABLE, filtered); @@ -158,7 +157,7 @@ export default function EventListWrapper() { // Snapshot the previous value const previousEvents = queryClient.getQueryData(EVENTS_TABLE); - let clear = []; + const clear = []; // optimistically update object queryClient.setQueryData(EVENTS_TABLE, clear); @@ -225,13 +224,13 @@ export default function EventListWrapper() { // Events API const eventsHandler = useCallback( - async (action, payload, options = undefined) => { + async (action, payload, options) => { switch (action) { case 'add': try { - let newEvent = { ...payload }; + const newEvent = { ...payload }; // there is an option to pass an index of an array to use as start time - if (options?.startIsLastEnd !== undefined) { + if (typeof options?.startIsLastEnd !== 'undefined') { newEvent.timeStart = data[options.startIsLastEnd].timeEnd || 0; } // hard coding duration value to be as expected for now diff --git a/client/src/features/editors/list/List.module.scss b/client/src/features/editors/list/List.module.scss index e0655f421..852c2ae7e 100644 --- a/client/src/features/editors/list/List.module.scss +++ b/client/src/features/editors/list/List.module.scss @@ -1,10 +1,11 @@ +@use '../../../styles/main' as *; +@use '../../../styles/mixins' as *; + .eventContainer { + @include second-container; margin-top: 1em; display: flex; flex-direction: column; - background-color: rgba(0, 0, 0, 0.05); - border: 1px solid rgba(0, 0, 0, 0.05); - border-radius: 4px; padding: 8px; overflow-y: scroll; height: 100%; @@ -21,14 +22,6 @@ } .cursor { - width: 100%; - background: linear-gradient( - 180deg, - #ff7597 2%, - #0001 3%, - #0001 97%, - #ff7597 98% - ); - + box-shadow: 2px 2px 0 $ontime-pink; border-radius: 14px; } diff --git a/client/src/features/info/Info.module.scss b/client/src/features/info/Info.module.scss index 337c053f9..6999c20dc 100644 --- a/client/src/features/info/Info.module.scss +++ b/client/src/features/info/Info.module.scss @@ -3,11 +3,7 @@ @mixin container { margin-top: 1em; - display: flex; - flex-direction: column; - background-color: rgba(0, 0, 0, 0.05); - border: 1px solid rgba(0, 0, 0, 0.05); - border-radius: 4px; + @include second-container; padding: 8px; } @@ -73,7 +69,7 @@ .emptyLabel { font-size: 0.8em; - color: #555; + color: $bg-gray-700; } .notes { @@ -85,26 +81,12 @@ .if { font-size: 0.8em; color: $ontime-accent; - @include container-bg; + background-color: $bg-gray-900; + padding: 0 0.5em; + margin: 0 0.5em; } ul > li { font-size: 0.9em; - color: #fff; -} - -.moreExpanded, -.moreCollapsed { - cursor: pointer; - color: #fff; -} - -.moreExpanded { - transform: scaleY(-1); - transition: transform 0.3s; -} - -.moreCollapsed { - transform: scaleY(1); - transition: transform 0.3s; + color: $text-white; } diff --git a/client/src/features/info/InfoLogger.jsx b/client/src/features/info/InfoLogger.jsx index 2c37119d4..3a80b0006 100644 --- a/client/src/features/info/InfoLogger.jsx +++ b/client/src/features/info/InfoLogger.jsx @@ -1,7 +1,8 @@ import React, { useContext, useEffect, useState } from 'react'; -import style from './InfoLogger.module.scss'; -import CollapseBar from "../../common/components/collapseBar/CollapseBar"; +import { HStack } from '@chakra-ui/react'; +import CollapseBar from '../../common/components/collapseBar/CollapseBar'; import { LoggingContext } from '../../app/context/LoggingContext'; +import style from './InfoLogger.module.scss'; export default function InfoLogger() { const { logData, clearLog } = useContext(LoggingContext); @@ -16,6 +17,10 @@ export default function InfoLogger() { const [showUser, setShowUser] = useState(true); useEffect(() => { + if (!logData) { + return; + } + const matchers = []; if (showUser) { matchers.push('USER'); @@ -36,12 +41,10 @@ export default function InfoLogger() { matchers.push('PLAYBACK'); } - const d = logData.filter((d) => ( - matchers.some((m) => d.origin === m) - )) + const d = logData.filter((d) => matchers.some((m) => d.origin === m)); setData(d); - },[logData, showUser, showClient, showServer, showPlayback, showRx, showTx]) + }, [logData, showUser, showClient, showServer, showPlayback, showRx, showTx]); const disableOthers = (toEnable) => { toEnable === 'USER' ? setShowUser(true) : setShowUser(false); @@ -50,62 +53,75 @@ export default function InfoLogger() { toEnable === 'RX' ? setShowRx(true) : setShowRx(false); toEnable === 'TX' ? setShowTx(true) : setShowTx(false); toEnable === 'PLAYBACK' ? setShowPlayback(true) : setShowPlayback(false); - } + }; return (
setCollapsed((c) => !c)} /> {!collapsed && ( <> -
+
setShowUser((s) => !s)} onAuxClick={() => disableOthers('USER')} - className={(showUser) ? style.active : null}> + className={showUser ? style.active : null} + > USER
setShowClient((s) => !s)} onAuxClick={() => disableOthers('CLIENT')} - className={(showClient) ? style.active : null}> + className={showClient ? style.active : null} + > CLIENT
setShowServer((s) => !s)} onAuxClick={() => disableOthers('SERVER')} - className={(showServer) ? style.active : null}> + className={showServer ? style.active : null} + > SERVER
setShowPlayback((s) => !s)} onAuxClick={() => disableOthers('PLAYBACK')} - className={(showPlayback) ? style.active : null}> + className={showPlayback ? style.active : null} + > Playback
setShowRx((s) => !s)} onAuxClick={() => disableOthers('RX')} - className={(showRx) ? style.active : null}> + className={showRx ? style.active : null} + > RX
setShowTx((s) => !s)} onAuxClick={() => disableOthers('TX')} - className={(showTx) ? style.active : null}> + className={showTx ? style.active : null} + > TX
-
+
Clear
-
+