V2 monorepo (#285)
* refactor(project structure): UI * refactor(project structure): extract utilities * refactor(project structure): remove unused * refactor(project structure): electron * refactor(project structure): server refactor: migrate to vitest refactor: monorepo config * refactor: extract application menu * refactor: exit process * refactor: extract tray menu * chore: electron build * Added Seconds in studio clock #282 --------- Co-authored-by: Fabian Posenau <fabian@fphome.de> --------- Co-authored-by: Fabian Posenau <fabian.p99@gmx.de> Co-authored-by: Fabian Posenau <fabian@fphome.de>
@@ -3,7 +3,6 @@ version = 1
|
|||||||
test_patterns = [
|
test_patterns = [
|
||||||
"__mocks__/**",
|
"__mocks__/**",
|
||||||
"__tests__/**",
|
"__tests__/**",
|
||||||
"cypress/**",
|
|
||||||
"*.test.*",
|
"*.test.*",
|
||||||
"*.mock.*",
|
"*.mock.*",
|
||||||
"*.spec.*"
|
"*.spec.*"
|
||||||
|
|||||||
@@ -9,26 +9,11 @@
|
|||||||
"extends": [
|
"extends": [
|
||||||
"eslint:recommended"
|
"eslint:recommended"
|
||||||
],
|
],
|
||||||
"rules": {
|
"overrides": [
|
||||||
// disallow certain object properties
|
{
|
||||||
// https://eslint.org/docs/rules/no-restricted-properties
|
"files": ["e2e/**/**.spec.ts", "e2e/**/**.test.ts"],
|
||||||
"no-restricted-properties": [
|
"extends": ["plugin:playwright/playwright-test"]
|
||||||
"error",
|
}
|
||||||
{
|
],
|
||||||
"object": "global",
|
"rules": {}
|
||||||
"property": "isNaN",
|
|
||||||
"message": "Please use Number.isNaN instead"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"object": "self",
|
|
||||||
"property": "isNaN",
|
|
||||||
"message": "Please use Number.isNaN instead"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"object": "window",
|
|
||||||
"property": "isNaN",
|
|
||||||
"message": "Please use Number.isNaN instead"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 285 KiB After Width: | Height: | Size: 285 KiB |
|
Before Width: | Height: | Size: 241 KiB After Width: | Height: | Size: 241 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 250 KiB After Width: | Height: | Size: 250 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
@@ -21,34 +21,19 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: 16
|
node-version: 16
|
||||||
|
|
||||||
# React
|
- name: Install dependencies
|
||||||
- name: React - Install dependencies
|
run: pnpm i --frozen-lockfile
|
||||||
run: yarn install --frozen-lockfile --network-timeout 300000
|
|
||||||
working-directory: ./client
|
|
||||||
|
|
||||||
- name: React - Build project
|
- name: Build project packages
|
||||||
run: yarn build
|
run: turbo build
|
||||||
working-directory: ./client
|
|
||||||
|
|
||||||
# Node server
|
|
||||||
- name: Server - Install dependencies
|
|
||||||
run: yarn install --frozen-lockfile --production --network-timeout 300000
|
|
||||||
working-directory: ./server/src
|
|
||||||
|
|
||||||
# App
|
|
||||||
- name: Electron - Install dependencies
|
|
||||||
shell: bash
|
|
||||||
run: yarn install --frozen-lockfile --network-timeout 300000 && yarn setdb
|
|
||||||
working-directory: ./server
|
|
||||||
- name: Electron - Build app
|
- name: Electron - Build app
|
||||||
run: yarn dist-mac
|
run: turbo dist-mac
|
||||||
working-directory: ./server
|
|
||||||
|
|
||||||
# Release
|
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
files: ./server/dist/ontime-macOS.dmg
|
files: ./electron/dist/ontime-macOS.dmg
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
@@ -65,34 +50,20 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: 16
|
node-version: 16
|
||||||
|
|
||||||
# React
|
- name: Install dependencies
|
||||||
- name: React - Install dependencies
|
run: pnpm i --frozen-lockfile
|
||||||
run: yarn install --frozen-lockfile --network-timeout 300000
|
|
||||||
working-directory: ./client
|
|
||||||
|
|
||||||
- name: React - Build project
|
- name: Build project packages
|
||||||
run: yarn build
|
run: turbo build
|
||||||
working-directory: ./client
|
|
||||||
|
|
||||||
# Node server
|
|
||||||
- name: Server - Install dependencies
|
|
||||||
run: yarn install --frozen-lockfile --production --network-timeout 300000
|
|
||||||
working-directory: ./server/src
|
|
||||||
|
|
||||||
# App
|
|
||||||
- name: Electron - Install dependencies
|
|
||||||
shell: bash
|
|
||||||
run: yarn install --frozen-lockfile --network-timeout 300000 && yarn setdb
|
|
||||||
working-directory: ./server
|
|
||||||
- name: Electron - Build app
|
- name: Electron - Build app
|
||||||
run: yarn dist-win
|
run: turbo dist-win
|
||||||
working-directory: ./server
|
|
||||||
|
|
||||||
# Release
|
# Release
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
files: ./server/dist/ontime-win64.exe
|
files: ./electron/dist/ontime-win64.exe
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
@@ -109,34 +80,20 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: 16
|
node-version: 16
|
||||||
|
|
||||||
# React
|
- name: Install dependencies
|
||||||
- name: React - Install dependencies
|
run: pnpm i --frozen-lockfile
|
||||||
run: yarn install --frozen-lockfile --network-timeout 300000
|
|
||||||
working-directory: ./client
|
|
||||||
|
|
||||||
- name: React - Build project
|
- name: Build project packages
|
||||||
run: yarn build
|
run: turbo build
|
||||||
working-directory: ./client
|
|
||||||
|
|
||||||
# Node server
|
|
||||||
- name: Server - Install dependencies
|
|
||||||
run: yarn install --frozen-lockfile --production --network-timeout 300000
|
|
||||||
working-directory: ./server/src
|
|
||||||
|
|
||||||
# App
|
|
||||||
- name: Electron - Install dependencies
|
|
||||||
shell: bash
|
|
||||||
run: yarn install --frozen-lockfile --network-timeout 300000 && yarn setdb
|
|
||||||
working-directory: ./server
|
|
||||||
- name: Electron - Build app
|
- name: Electron - Build app
|
||||||
run: yarn dist-linux
|
run: turbo dist-linux
|
||||||
working-directory: ./server
|
|
||||||
|
|
||||||
# Release
|
# Release
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
files: ./server/dist/ontime-linux.AppImage
|
files: ./electron/dist/ontime-linux.AppImage
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
@@ -145,6 +102,7 @@ jobs:
|
|||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
env:
|
env:
|
||||||
CI: ''
|
CI: ''
|
||||||
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
@@ -155,19 +113,14 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: 16
|
node-version: 16
|
||||||
|
|
||||||
# React
|
- name: Install dependencies
|
||||||
- name: React - Install dependencies
|
run: pnpm i --frozen-lockfile
|
||||||
run: yarn install --network-timeout 300000
|
|
||||||
working-directory: ./client
|
|
||||||
|
|
||||||
- name: React - Build project
|
- name: Build project packages
|
||||||
run: yarn build
|
run: turbo build
|
||||||
working-directory: ./client
|
|
||||||
|
|
||||||
# Node server
|
- name: Electron - Build app
|
||||||
- name: Server - Install dependencies
|
run: turbo dist-mac
|
||||||
run: yarn install --frozen-lockfile --production --network-timeout 300000
|
|
||||||
working-directory: ./server/src
|
|
||||||
|
|
||||||
# Login to docker
|
# Login to docker
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
# For most projects, this workflow file will not need changing; you simply need
|
|
||||||
# to commit it to your repository.
|
|
||||||
#
|
|
||||||
# You may wish to alter this file to override the set of languages analyzed,
|
|
||||||
# or to provide custom queries or build logic.
|
|
||||||
#
|
|
||||||
# ******** NOTE ********
|
|
||||||
# We have attempted to detect the languages in your repository. Please check
|
|
||||||
# the `language` matrix defined below to confirm you have the correct set of
|
|
||||||
# supported CodeQL languages.
|
|
||||||
#
|
|
||||||
name: "CodeQL"
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ master ]
|
|
||||||
pull_request:
|
|
||||||
# The branches below must be a subset of the branches above
|
|
||||||
branches: [ master ]
|
|
||||||
schedule:
|
|
||||||
- cron: '33 02 * * 5'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
analyze:
|
|
||||||
name: Analyze
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
actions: read
|
|
||||||
contents: read
|
|
||||||
security-events: write
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
language: [ 'javascript' ]
|
|
||||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
|
||||||
# Learn more about CodeQL language support at https://git.io/codeql-language-support
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
|
||||||
- name: Initialize CodeQL
|
|
||||||
uses: github/codeql-action/init@v1
|
|
||||||
with:
|
|
||||||
languages: ${{ matrix.language }}
|
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
|
||||||
# By default, queries listed here will override any specified in a config file.
|
|
||||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
|
||||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
|
||||||
|
|
||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
|
||||||
- name: Autobuild
|
|
||||||
uses: github/codeql-action/autobuild@v1
|
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
|
||||||
# 📚 https://git.io/JvXDl
|
|
||||||
|
|
||||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
|
||||||
# and modify them (or add more) to build your code if your project
|
|
||||||
# uses a compiled language
|
|
||||||
|
|
||||||
#- run: |
|
|
||||||
# make bootstrap
|
|
||||||
# make release
|
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
|
||||||
uses: github/codeql-action/analyze@v1
|
|
||||||
@@ -5,38 +5,42 @@ node_modules/
|
|||||||
/.pnp
|
/.pnp
|
||||||
.pnp.js
|
.pnp.js
|
||||||
|
|
||||||
# testing
|
|
||||||
coverage/
|
|
||||||
*.mp4
|
|
||||||
|
|
||||||
# production
|
|
||||||
build/
|
|
||||||
dist/
|
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.env
|
*.local
|
||||||
.env.local
|
|
||||||
.env.development.local
|
|
||||||
.env.test.local
|
|
||||||
.env.production.local
|
|
||||||
|
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
|
||||||
# working stuff
|
|
||||||
_SS/
|
|
||||||
db backup.json
|
|
||||||
server/src/preloaded-db/db.json
|
|
||||||
server/src/models/db.json
|
|
||||||
TODO.md
|
|
||||||
ontime-db/
|
|
||||||
ontime-external/
|
|
||||||
|
|
||||||
# vscode stuff
|
# vscode stuff
|
||||||
.vscode/*
|
.vscode/*
|
||||||
ontime.code-workspace
|
ontime.code-workspace
|
||||||
|
|
||||||
# webstorm stuff
|
# webstorm stuff
|
||||||
.idea/*
|
.idea/*
|
||||||
|
|
||||||
|
# turborepo stuff
|
||||||
|
.turbo
|
||||||
|
|
||||||
|
# testing
|
||||||
|
test-results
|
||||||
|
playwright-report
|
||||||
|
/playwright/.cache/
|
||||||
|
|
||||||
|
# production
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# working stuff
|
||||||
|
**/TODO.md
|
||||||
|
|
||||||
|
# docker utils
|
||||||
|
ontime-db
|
||||||
|
ontime-external/
|
||||||
|
|
||||||
|
# working database
|
||||||
|
apps/server/src/preloaded-db/db.json
|
||||||
|
|
||||||
|
# versioning file
|
||||||
|
**/ONTIME_VERSION.js
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"trailingComma": "es5",
|
"trailingComma": "all",
|
||||||
"tabWidth": 2,
|
"tabWidth": 2,
|
||||||
"semi": true,
|
"semi": true,
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"jsxSingleQuote": true,
|
"jsxSingleQuote": true,
|
||||||
"printWidth": 100
|
"printWidth": 120
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# GETTING STARTED
|
||||||
|
|
||||||
|
Ontime consists of 3 distinct parts
|
||||||
|
- __client__: A React app for Ontime's UI and web clients
|
||||||
|
- __electron__: An electron app which facilitates the cross-platform distribution of Ontime
|
||||||
|
- __server__: A node application which handles the domains services and integrations
|
||||||
|
|
||||||
|
The steps below will assume you have locally installed the necessary dependencies.
|
||||||
|
Other dependencies will be installed as part of the setup
|
||||||
|
- __node__ (>=16.16)
|
||||||
|
- __pnpm__ (>=7)
|
||||||
|
- __docker__ (only necessary to run and build docker images)
|
||||||
|
|
||||||
|
## LOCAL DEVELOPMENT
|
||||||
|
|
||||||
|
The electron app is only necessary to distribute an installable version of the app and is not required for local development.
|
||||||
|
Locally, we would need to run both the React client and the node.js server in development mode
|
||||||
|
|
||||||
|
From the project root, run the following commands
|
||||||
|
- __Install the project dependencies__ by running `pnpm i`
|
||||||
|
- __Run dev mode__ by running `turbo dev`
|
||||||
|
|
||||||
|
## CREATE AN INSTALLABLE FILE (Windows | MacOS | Linux)
|
||||||
|
|
||||||
|
Ontime uses Electron to distribute the application.
|
||||||
|
You can generate a distribution for your OS by running the following steps.
|
||||||
|
|
||||||
|
From the project root, run the following commands
|
||||||
|
- __Install the project dependencies__ by running `pnpm i`
|
||||||
|
- __Build the UI and server__ by running `turbo build`
|
||||||
|
- __Create the package__ by running `turbo dist-win`, `turbo dist-mac` or `turbo dist-linux`
|
||||||
|
|
||||||
|
The build distribution assets will be at `.apps/electron/dist`
|
||||||
|
|
||||||
|
## DOCKER
|
||||||
|
|
||||||
|
Ontime provides a docker-compose file to aid with building and running docker images.
|
||||||
|
While it should allow for a generic setup, it might need to be modified to fit your infrastructure.
|
||||||
|
|
||||||
|
From the project root, run the following commands
|
||||||
|
- __Install the project dependencies__ by running `pnpm i`
|
||||||
|
- __Build docker image from__ by running `docker build -t getontime/ontime`
|
||||||
|
- __Run docker image from compose__ by running `docker-compose up -d`
|
||||||
|
|
||||||
|
Other useful commands
|
||||||
|
- __List running processes__ by running `docker ps`
|
||||||
|
- __Kill running process__ by running `docker kill <process-id>`
|
||||||
@@ -1,20 +1,21 @@
|
|||||||
FROM node:14-alpine
|
FROM node:16-alpine
|
||||||
|
|
||||||
WORKDIR /app/server
|
WORKDIR /apps/server
|
||||||
|
|
||||||
# Prepare UI
|
# Prepare UI
|
||||||
COPY /client/build ../client/build
|
COPY /apps/client/build ../client/build
|
||||||
|
|
||||||
# Prepare Backend
|
# Prepare Backend
|
||||||
COPY /server/src ./
|
COPY /apps/server ./
|
||||||
|
|
||||||
# Export default ports Main - OSC IN
|
# Export default ports Main - OSC IN
|
||||||
EXPOSE 4001/tcp 8888/udp
|
EXPOSE 4001/tcp 8888/udp
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
ENV ONTIME_DATA=/server/
|
ENV ONTIME_DATA=/server/
|
||||||
|
|
||||||
CMD ["yarn", "start:headless"]
|
CMD ["npm", "start:headless"]
|
||||||
|
|
||||||
# Build an run commandsN
|
# Build and run commands
|
||||||
|
# !!! Note that this command needs pre-build versions of the UI and server apps
|
||||||
# docker build -t getontime/ontime .
|
# docker build -t getontime/ontime .
|
||||||
# docker run -p 4001:4001 -p 10.0.0.12:8888:8888/udp --mount type=bind,source="$(pwd)/ontime-db",target=/server/preloaded-db getontime/ontime
|
# docker run -p 4001:4001 -p 10.0.0.12:8888:8888/udp --mount type=bind,source="$(pwd)/ontime-db",target=/server/preloaded-db getontime/ontime
|
||||||
|
|||||||
@@ -14,120 +14,114 @@
|
|||||||
|
|
||||||
Ontime is an application for managing event rundowns and running stage timers.
|
Ontime is an application for managing event rundowns and running stage timers.
|
||||||
|
|
||||||
It allows a center application to be able to distribute event information in the local network. This
|
A single, locally hosted central application distributes your event information over the local network.
|
||||||
minimises needs for using Media Server outputs or expensive video distribution while allowing easy
|
This enables the distribution of the data to a series of viewers and allows integration into video and control workflows, including OBS and d3.
|
||||||
integration in workflows including OBS and d3.
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Using Ontime
|
## Using Ontime
|
||||||
|
|
||||||
Once installed and running, ontime starts a background server that is the heart of all processes.
|
Once installed and running, Ontime starts a background server that is the heart of all processes.
|
||||||
The app, is used to add / edit your running order in the event list, and running the timers using
|
From the app, you can add / edit your running order and control the timer playback.
|
||||||
the Playback Control function.
|
|
||||||
|
|
||||||
From here, any device in the same network with a browser is able to render the views as described.
|
Any device with a browser in the same network can choose one of the supported views to render the available data.
|
||||||
This is done by reaching the ontime server at the _default port 4001_ eg: `localhost:4001`
|
This is done by reaching the ontime server at the _default port 4001_ eg: `localhost:4001`
|
||||||
or `192.168.1.3:4001`
|
or `192.168.1.3:4001`
|
||||||
You can then use the ontime logo in the top right corner to select the desired view (event in the
|
<br />
|
||||||
lower thirds view, where it is hidden).
|
You can then use the Ontime logo in the top left corner to select the desired view.
|
||||||
|
The logo will be initially hidden until there is mouse interaction.
|
||||||
|
|
||||||
In case of unattended machines or automations, it is possible to use different URL to recall
|
In the case of unattended machines or automation, it is possible to use different URL to recall
|
||||||
individual views and extend with using the URL aliases feature
|
individual views and extend view settings using the URL aliases feature
|
||||||
|
|
||||||
```
|
```
|
||||||
For the presentation views...
|
For the presentation views
|
||||||
-------------------------------------------------------------
|
-------------------------------------------------------------
|
||||||
IP.ADDRESS:4001 > Web server default to presenter timer view
|
IP.ADDRESS:4001 > Web server default to presenter timer view
|
||||||
IP.ADDRESS:4001/timer > Presenter / Stage timer view
|
IP.ADDRESS:4001/timer > Presenter / Stage timer view
|
||||||
|
IP.ADDRESS:4001/minimal > Simple timer view
|
||||||
|
IP.ADDRESS:4001/clock > Simple clock view
|
||||||
IP.ADDRESS:4001/sm > Stage Manager / Backstage view
|
IP.ADDRESS:4001/sm > Stage Manager / Backstage view
|
||||||
IP.ADDRESS:4001/public > Public / Foyer view
|
IP.ADDRESS:4001/public > Public / Foyer view
|
||||||
IP.ADDRESS:4001/pip > Picture in Picture view
|
|
||||||
IP.ADDRESS:4001/lower > Lower Thirds
|
IP.ADDRESS:4001/lower > Lower Thirds
|
||||||
IP.ADDRESS:4001/studio > Studio Clock
|
IP.ADDRESS:4001/studio > Studio Clock
|
||||||
IP.ADDRESS:4001/cuesheet > Cue Sheet
|
```
|
||||||
|
```
|
||||||
...and for the editor (the control interface, same as the app)
|
For management views
|
||||||
-------------------------------------------------------------
|
-------------------------------------------------------------
|
||||||
IP.ADDRESS:4001/editor
|
IP.ADDRESS:4001/editor > the control interface, same as the app
|
||||||
|
IP.ADDRESS:4001/cuesheet > realtime cuesheets for collaboration
|
||||||
```
|
```
|
||||||
|
|
||||||
More documentation available [here](https://cpvalente.gitbook.io/ontime/)
|
More documentation is available [in our docs](https://cpvalente.gitbook.io/ontime/)
|
||||||
|
|
||||||
## Feature List (in no specific order)
|
## Feature List (in no specific order)
|
||||||
|
|
||||||
- [x] Distribute Data over network and render in the browser
|
- [x] Distribute data over network and render it in the browser
|
||||||
- [x] Different screen types
|
- [x] Different screen types
|
||||||
- Stage Timer
|
- Stage Timer
|
||||||
- Backstage Info
|
- Backstage Info
|
||||||
- Public Info
|
- Public Info
|
||||||
- Picture in Picture
|
|
||||||
- Studio Clock
|
- Studio Clock
|
||||||
- [Make your own?](#make-your-own-viewer)
|
- [Make your own?](#make-your-own-viewer)
|
||||||
- [x] Configurable realtime Lower Thirds
|
- [x] Configurable Lower Thirds
|
||||||
- [x] Cuesheets with additional custom fields
|
- [x] Cuesheets with user definable fields
|
||||||
- [x] Send live messages to different screen types
|
- [x] Send live messages to different screen types
|
||||||
- [x] Ability to differentiate between backstage and public data
|
- [x] Differentiate between backstage and public data
|
||||||
- [x] Manage delays workflow
|
- [x] Workflow for managing delays
|
||||||
- [x] Open Sound Control (OSC) Control and Feedback
|
- [x] Protocol integrations for Control and Feedback
|
||||||
- [x] Integrate with hardware using Companion or one of the APIs
|
- OSC (Open Sound Control)
|
||||||
|
- HTTP
|
||||||
|
- WebSockets
|
||||||
- [x] Roll mode: run independently using the system clock
|
- [x] Roll mode: run independently using the system clock
|
||||||
- [x] Import event list from Excel
|
- [x] [Headless run](#headless-run): run server in a separate machine, configure from a browser locally
|
||||||
- [x] URL Aliases (define configurable aliases to ease onsite setup)
|
- [x] [Countdown to anything!](https://cpvalente.gitbook.io/ontime/views/countdown): have
|
||||||
- [x] Logging view
|
|
||||||
- [x] Edit anywhere: run ontime in your local network and use any machine to reach the editor page (
|
|
||||||
same as app)
|
|
||||||
- [x] Multi platform (available on Windows, MacOS and Linux)
|
|
||||||
- [x] [Headless run](#headless-run) (run server only, configure from a browser locally)
|
|
||||||
- [x] [Countdown to anything!](https://cpvalente.gitbook.io/ontime/views/countdown): ability to have
|
|
||||||
a countdown to any scheduled event
|
a countdown to any scheduled event
|
||||||
|
- [x] Multi-platform (available on Windows, MacOS and Linux)
|
||||||
|
- [x] [Companion integration](https://bitfocus.io/connections/getontime-ontime)
|
||||||
|
|
||||||
## Unopinionated
|
## Unopinionated
|
||||||
|
|
||||||
We are not interested in forcing workflows and have made ontime, so it is flexible to whichever way
|
We are not interested in forcing workflows and have made Ontime, so it is flexible to whichever way
|
||||||
you would like to work.
|
you would like to work.
|
||||||
|
|
||||||
- [x] You do not need an order list to use the timer. Create an empty event and the OSC API works
|
- [x] If you want just the info screens, there is no need to use the timer!
|
||||||
just the same
|
|
||||||
- [x] If you want just the info screens, no need to use the timer!
|
|
||||||
- [x] Don't have or care for a schedule?
|
- [x] Don't have or care for a schedule?
|
||||||
- [x] a single event with no data is enough to use the OSC API and get going
|
- [x] a single event with no data is enough to use one of the APIs and use a dynamic timer
|
||||||
- [x] use the order list to create a set of quick timers by setting the beginning and start
|
- [x] use the order list to create a set of quick timers by setting the beginning and start
|
||||||
times to 00:00 and 00:10 (**BAM**! 10 minute timer). You can quickly recall this with OSC as
|
times to 00:00 and 00:10 (**BAM**! 10 minute timer). You can quickly recall this with OSC or any of the other available integrations
|
||||||
always
|
|
||||||
|
|
||||||
## Rich APIs for workflow integrations
|
## Rich APIs for workflow integrations
|
||||||
|
|
||||||
The app is being currently developed to a wide user base, from broadcast to entertainment and
|
The app is currently being developed for a broad user base, from broadcast to entertainment and
|
||||||
conference halls.
|
conference halls.
|
||||||
|
|
||||||
Taking advantage of the integrations in Ontime, we currently use Ontime with:
|
Taking advantage of the integrations, we currently use Ontime with:
|
||||||
|
|
||||||
- `disguise`: trigger ontime from d3's timeline using the **OSC API**, **render views** using d3's
|
- `disguise`: trigger Ontime from d3's timeline using the **OSC API**, and **render views** using d3's
|
||||||
webmodule
|
webmodule
|
||||||
- `OBS`: **render views** using the Browser Module
|
- `OBS`: **render views** using the Browser Module
|
||||||
- `QLab`: trigger ontime using **OSC API**
|
- `QLab`: trigger Ontime using **OSC API**
|
||||||
- `Companion`: Ontime has a **companion module**. Issue report and feature requests should be done
|
- `Companion`: Ontime has a **companion module**. Issue report and feature requests should be done
|
||||||
in
|
in the [repository getontime/ontime](https://github.com/bitfocus/companion-module-getontime-ontime)
|
||||||
the [repository getontime/ontime](https://github.com/bitfocus/companion-module-getontime-ontime)
|
|
||||||
|
|
||||||
### Make your own viewer
|
### Make your own viewer
|
||||||
|
|
||||||
Ontime broadcasts its data over websockets. This allows you to build your own viewers by leveranging
|
Ontime broadcasts its data over WebSockets. This allows you to consume its data outside of the application.
|
||||||
basic knowledge of HTML + CSS + Javascript (or any other language that can run in the browser).
|
|
||||||
|
|
||||||
|
Writing a new view for the browser can be done with basic knowledge of HTML + CSS + Javascript (or any other language that can run in the browser).
|
||||||
|
<br />
|
||||||
See [this repository](https://github.com/cpvalente/ontime-viewer-template) with a small template on
|
See [this repository](https://github.com/cpvalente/ontime-viewer-template) with a small template on
|
||||||
how to get you started and read the docs about
|
how to get you started and read the docs about
|
||||||
the [Websocket API](https://app.gitbook.com/s/-Mc0giSOToAhq0ROd0CR/control-and-feedback/websocket-api)
|
the [Websocket API](https://app.gitbook.com/s/-Mc0giSOToAhq0ROd0CR/control-and-feedback/websocket-api)
|
||||||
|
|
||||||
### Headless run️
|
### Headless run️
|
||||||
|
|
||||||
You can self host and run ontime in a docker image, the run command should:
|
You can self-host and run Ontime in a docker image. The run command will:
|
||||||
|
|
||||||
- expose the necessary ports (listen in Dockerfile)
|
- expose the necessary ports (listed in the Dockerfile)
|
||||||
- mount a local file to persist your data (in the example: ````$(pwd)/local-data````)
|
- mount a local file to persist your data (in the example: ````$(pwd)/local-data````)
|
||||||
- the image name __getontime/ontime__
|
- the image name __getontime/ontime__
|
||||||
|
|
||||||
@@ -154,19 +148,14 @@ docker-compose up
|
|||||||
|
|
||||||
### Continued development
|
### Continued development
|
||||||
|
|
||||||
There are several features planned in the roadmap. These will be implemented in a development
|
Several features are planned in the roadmap, and we continuously adjust this to match how users interact with the app.
|
||||||
friendly order unless there is user demand to bump any of them.
|
<br />
|
||||||
|
Have an idea? Reach out via [email](mail@getontime.no) or [open an issue](https://github.com/cpvalente/ontime/issues/new)
|
||||||
- [ ] HTTP Server (vMix integration)
|
|
||||||
- [ ] Improvements in event interface
|
|
||||||
- [ ] Moderator view
|
|
||||||
- [ ] New playback mode for [cumulative time keeping](https://github.com/cpvalente/ontime/issues/100)
|
|
||||||
- [ ] Lower Third Manager
|
|
||||||
- [ ] Reach Schedule: way to speedup timer to meet a deadline
|
|
||||||
|
|
||||||
### Issues
|
### Issues
|
||||||
|
|
||||||
If you come across any bugs, [please open an issue]((https://github.com/cpvalente/ontime/issues/new)). Usually bugs get fixed pretty quickly when reported
|
We use Github's issue tracking for bug reporting and feature requests. <br />
|
||||||
|
Found a bug? [Open an issue](https://github.com/cpvalente/ontime/issues/new).
|
||||||
|
|
||||||
#### Unsigned App
|
#### Unsigned App
|
||||||
|
|
||||||
@@ -192,22 +181,28 @@ You can circumvent this by allowing the execution of the app manually.
|
|||||||
|
|
||||||
Long story short: Ontime app is unsigned. </br>Purchasing the certificates for both Mac and Windows
|
Long story short: Ontime app is unsigned. </br>Purchasing the certificates for both Mac and Windows
|
||||||
would mean a recurrent expense and is not a priority. This is unlikely to change in future. If you
|
would mean a recurrent expense 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,
|
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)
|
please [open an issue](https://github.com/cpvalente/ontime/issues/new)
|
||||||
|
|
||||||
#### Safari
|
#### Safari
|
||||||
|
|
||||||
There are some issues with Safari versions lower than 13:
|
There are known issues with Safari versions lower than 13:
|
||||||
|
|
||||||
- Spacing and text styles might have small inconsistencies
|
- Spacing and text styles might have small inconsistencies
|
||||||
- Table view does not work
|
- Table view does not work
|
||||||
|
|
||||||
There is no plan for any further work on this since the breaking code belongs to third party
|
There is no plan for any further work on this.
|
||||||
libraries.
|
|
||||||
|
# Contributing
|
||||||
|
|
||||||
|
Looking to contribute? All types of help are appreciated, from coding to testing and feature specification.
|
||||||
|
<br /><br />
|
||||||
|
If you are a developer and would like to contribute with some code, please open an issue to discuss before opening a Pull Request.
|
||||||
|
<br />
|
||||||
|
Information about the project setup can be found in the [development documentation](./DEVELOPMENT.md)
|
||||||
|
|
||||||
# Help
|
# Help
|
||||||
|
|
||||||
Help is underway! ... and can be viewed [here](https://cpvalente.gitbook.io/ontime/)
|
Help is underway! ... and can be found [here](https://cpvalente.gitbook.io/ontime/)
|
||||||
|
|
||||||
# License
|
# License
|
||||||
|
|
||||||
|
|||||||
@@ -8,21 +8,31 @@
|
|||||||
"browser": true,
|
"browser": true,
|
||||||
"node": true
|
"node": true
|
||||||
},
|
},
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
"extends": [
|
"extends": [
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
"plugin:react/recommended",
|
"plugin:react/recommended",
|
||||||
"plugin:react-hooks/recommended",
|
"plugin:react-hooks/recommended",
|
||||||
"plugin:@typescript-eslint/recommended",
|
"plugin:@typescript-eslint/recommended",
|
||||||
"eslint-config-prettier",
|
"eslint-config-prettier",
|
||||||
"plugin:@tanstack/eslint-plugin-query/recommended"
|
"plugin:@tanstack/eslint-plugin-query/recommended",
|
||||||
|
"prettier"
|
||||||
],
|
],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"react",
|
"react",
|
||||||
"testing-library",
|
"testing-library",
|
||||||
"simple-import-sort",
|
"simple-import-sort",
|
||||||
"@tanstack/query"
|
"@tanstack/query",
|
||||||
|
"@typescript-eslint",
|
||||||
|
"prettier"
|
||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
|
"prettier/prettier": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"endOfLine": "auto"
|
||||||
|
}
|
||||||
|
],
|
||||||
"no-useless-concat": "warn",
|
"no-useless-concat": "warn",
|
||||||
"prefer-template": "warn",
|
"prefer-template": "warn",
|
||||||
"react/jsx-no-bind": [
|
"react/jsx-no-bind": [
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"trailingComma": "all",
|
||||||
|
"tabWidth": 2,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"jsxSingleQuote": true,
|
||||||
|
"printWidth": 120
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ontime-ui",
|
"name": "ontime-ui",
|
||||||
"version": "1.9.7",
|
"version": "2.0.0-alpha",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chakra-ui/react": "^2.4.5",
|
"@chakra-ui/react": "^2.4.5",
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
"framer-motion": "^8.0.2",
|
"framer-motion": "^8.0.2",
|
||||||
"jotai": "^1.10.0",
|
"jotai": "^1.10.0",
|
||||||
"luxon": "^3.1.0",
|
"luxon": "^3.1.0",
|
||||||
|
"ontime-utils": "workspace:*",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-beautiful-dnd": "^13.1.1",
|
"react-beautiful-dnd": "^13.1.1",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
@@ -30,15 +31,18 @@
|
|||||||
"react-table": "^7.7.0",
|
"react-table": "^7.7.0",
|
||||||
"socket.io-client": "^4.5.4",
|
"socket.io-client": "^4.5.4",
|
||||||
"typeface-open-sans": "^1.1.13",
|
"typeface-open-sans": "^1.1.13",
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^3.1.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "set BROWSER=none&&vite",
|
"addversion": "node -p \"'export const ONTIME_VERSION = ' + JSON.stringify(require('../../package.json').version) + ';'\" > src/ONTIME_VERSION.js",
|
||||||
|
"postinstall": "pnpm addversion",
|
||||||
|
"dev": "set BROWSER=none&&vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"stylelint": "npx stylelint \"**/*.scss\"\n",
|
"stylelint": "npx stylelint \"**/*.scss\"\n",
|
||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
"test:pipeline": "vitest run"
|
"test:pipeline": "vitest run",
|
||||||
|
"cleanup": "rm -rf .turbo && rm -rf node_modules && rm -rf build"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
@@ -55,38 +59,36 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sentry/vite-plugin": "^0.3.0",
|
"@sentry/vite-plugin": "^0.3.0",
|
||||||
"@tanstack/eslint-plugin-query": "^4.15.1",
|
"@tanstack/eslint-plugin-query": "^4.15.1",
|
||||||
"@testing-library/jest-dom": "^5.16.4",
|
"@testing-library/jest-dom": "^5.16.5",
|
||||||
"@testing-library/react": "^13.1.1",
|
"@testing-library/react": "^13.1.1",
|
||||||
"@testing-library/user-event": "^14.1.1",
|
"@testing-library/user-event": "^14.1.1",
|
||||||
"@types/color": "^3.0.3",
|
"@types/color": "^3.0.3",
|
||||||
"@types/node": "^18.11.18",
|
"@types/prop-types": "^15.7.5",
|
||||||
"@types/react": "^18.0.26",
|
"@types/react": "^18.0.26",
|
||||||
"@types/react-beautiful-dnd": "^13.1.3",
|
"@types/react-beautiful-dnd": "^13.1.3",
|
||||||
"@types/react-dom": "^18.0.10",
|
"@types/react-dom": "^18.0.10",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.47.1",
|
"@types/testing-library__jest-dom": "^5.14.5",
|
||||||
"@typescript-eslint/parser": "^5.47.1",
|
"@typescript-eslint/eslint-plugin": "^5.48.1",
|
||||||
"@vitejs/plugin-react": "^3.0.0",
|
"@typescript-eslint/parser": "^5.48.1",
|
||||||
"eslint": "^8.25.0",
|
"@vitejs/plugin-react": "^3.0.1",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint": "^8.31.0",
|
||||||
|
"eslint-config-prettier": "^8.6.0",
|
||||||
"eslint-plugin-jest": "^27.1.7",
|
"eslint-plugin-jest": "^27.1.7",
|
||||||
"eslint-plugin-react": "^7.31.11",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
|
"eslint-plugin-react": "^7.32.0",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"eslint-plugin-simple-import-sort": "^8.0.0",
|
"eslint-plugin-simple-import-sort": "^8.0.0",
|
||||||
"eslint-plugin-testing-library": "^5.9.1",
|
"eslint-plugin-testing-library": "^5.9.1",
|
||||||
"jsdom": "^20.0.3",
|
"jsdom": "^21.1.0",
|
||||||
"prettier": "^2.8.1",
|
"prettier": "^2.8.3",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"sass": "^1.57.1",
|
"sass": "^1.57.1",
|
||||||
"stylelint": "^14.16.0",
|
"stylelint": "^14.16.1",
|
||||||
"stylelint-config-prettier": "^9.0.4",
|
"stylelint-config-prettier": "^9.0.4",
|
||||||
"stylelint-config-standard-scss": "^6.1.0",
|
"stylelint-config-standard-scss": "^6.1.0",
|
||||||
"typescript": "^4.8.3",
|
"typescript": "^4.9.4",
|
||||||
"vite": "^4.0.3",
|
"vite": "^4.0.4",
|
||||||
"vite-plugin-svgr": "^2.4.0",
|
"vite-plugin-svgr": "^2.4.0",
|
||||||
"vite-tsconfig-paths": "^4.0.3",
|
"vite-tsconfig-paths": "^4.0.3"
|
||||||
"vitest": "^0.26.2"
|
|
||||||
},
|
|
||||||
"resolutions": {
|
|
||||||
"**/@types/react": "18.0.21"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 600 B After Width: | Height: | Size: 600 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
@@ -3,10 +3,10 @@ import { BrowserRouter } from 'react-router-dom';
|
|||||||
import { ChakraProvider } from '@chakra-ui/react';
|
import { ChakraProvider } from '@chakra-ui/react';
|
||||||
import { QueryClientProvider } from '@tanstack/react-query';
|
import { QueryClientProvider } from '@tanstack/react-query';
|
||||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||||||
import ErrorBoundary from 'common/components/errorBoundary/ErrorBoundary';
|
|
||||||
import { AppContextProvider } from 'common/context/AppContext';
|
|
||||||
import { LoggingProvider } from 'common/context/LoggingContext';
|
|
||||||
|
|
||||||
|
import ErrorBoundary from './common/components/error-boundary/ErrorBoundary';
|
||||||
|
import { AppContextProvider } from './common/context/AppContext';
|
||||||
|
import { LoggingProvider } from './common/context/LoggingContext';
|
||||||
import useElectronEvent from './common/hooks/useElectronEvent';
|
import useElectronEvent from './common/hooks/useElectronEvent';
|
||||||
import { ontimeQueryClient } from './common/queryClient';
|
import { ontimeQueryClient } from './common/queryClient';
|
||||||
import theme from './theme/theme';
|
import theme from './theme/theme';
|
||||||
@@ -4,18 +4,18 @@ import { Navigate, Route, Routes, useLocation, useNavigate } from 'react-router-
|
|||||||
import useAliases from './common/hooks-query/useAliases';
|
import useAliases from './common/hooks-query/useAliases';
|
||||||
import withSocket from './features/viewers/ViewWrapper';
|
import withSocket from './features/viewers/ViewWrapper';
|
||||||
|
|
||||||
const Editor = lazy(() => import('features/editors/ProtectedEditor'));
|
const Editor = lazy(() => import('./features/editors/ProtectedEditor'));
|
||||||
const Table = lazy(() => import('features/table/ProtectedTable'));
|
const Table = lazy(() => import('./features/table/ProtectedTable'));
|
||||||
|
|
||||||
const TimerView = lazy(() => import('features/viewers/timer/Timer'));
|
const TimerView = lazy(() => import('./features/viewers/timer/Timer'));
|
||||||
const MinimalTimerView = lazy(() => import('features/viewers/minimal-timer/MinimalTimer'));
|
const MinimalTimerView = lazy(() => import('./features/viewers/minimal-timer/MinimalTimer'));
|
||||||
const ClockView = lazy(() => import('features/viewers/clock/Clock'));
|
const ClockView = lazy(() => import('./features/viewers/clock/Clock'));
|
||||||
const Countdown = lazy(() => import('features/viewers/countdown/Countdown'));
|
const Countdown = lazy(() => import('./features/viewers/countdown/Countdown'));
|
||||||
|
|
||||||
const Backstage = lazy(() => import('features/viewers/backstage/Backstage'));
|
const Backstage = lazy(() => import('./features/viewers/backstage/Backstage'));
|
||||||
const Public = lazy(() => import('features/viewers/public/Public'));
|
const Public = lazy(() => import('./features/viewers/public/Public'));
|
||||||
const Lower = lazy(() => import('features/viewers/lower-thirds/LowerWrapper'));
|
const Lower = lazy(() => import('./features/viewers/lower-thirds/LowerWrapper'));
|
||||||
const StudioClock = lazy(() => import('features/viewers/studio/StudioClock'));
|
const StudioClock = lazy(() => import('./features/viewers/studio/StudioClock'));
|
||||||
|
|
||||||
const STimer = withSocket(TimerView);
|
const STimer = withSocket(TimerView);
|
||||||
const SMinimalTimer = withSocket(MinimalTimerView);
|
const SMinimalTimer = withSocket(MinimalTimerView);
|
||||||
@@ -26,11 +26,11 @@ const SPublic = withSocket(Public);
|
|||||||
const SLowerThird = withSocket(Lower);
|
const SLowerThird = withSocket(Lower);
|
||||||
const SStudio = withSocket(StudioClock);
|
const SStudio = withSocket(StudioClock);
|
||||||
|
|
||||||
const FeatureWrapper = lazy(() => import('features/FeatureWrapper'));
|
const FeatureWrapper = lazy(() => import('./features/FeatureWrapper'));
|
||||||
const RundownPanel = lazy(() => import('features/rundown/RundownExport'));
|
const RundownPanel = lazy(() => import('./features/rundown/RundownExport'));
|
||||||
const TimerControl = lazy(() => import('features/control/playback/TimerControlExport'));
|
const TimerControl = lazy(() => import('./features/control/playback/TimerControlExport'));
|
||||||
const MessageControl = lazy(() => import('features/control/message/MessageControlExport'));
|
const MessageControl = lazy(() => import('./features/control/message/MessageControlExport'));
|
||||||
const Info = lazy(() => import('features/info/InfoExport'));
|
const Info = lazy(() => import('./features/info/InfoExport'));
|
||||||
|
|
||||||
export default function AppRouter() {
|
export default function AppRouter() {
|
||||||
const { data } = useAliases();
|
const { data } = useAliases();
|
||||||
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
@@ -1,6 +1,7 @@
|
|||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { Input } from '@chakra-ui/react';
|
import { Input } from '@chakra-ui/react';
|
||||||
import { clamp } from 'common/utils/math';
|
|
||||||
|
import { clamp } from '../../../utils/math';
|
||||||
|
|
||||||
import style from './DelayInput.module.scss';
|
import style from './DelayInput.module.scss';
|
||||||
|
|
||||||
@@ -1,13 +1,17 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { vi } from 'vitest';
|
|
||||||
|
|
||||||
import TextInput from '../TextInput';
|
import TextInput from '../TextInput';
|
||||||
|
|
||||||
describe('TextInput component', () => {
|
describe('TextInput component', () => {
|
||||||
describe('when given props', () => {
|
describe('when given props', () => {
|
||||||
|
// small hack to reset DOM between tests
|
||||||
|
beforeEach(() => {
|
||||||
|
document.getElementsByTagName('html')[0].innerHTML = '';
|
||||||
|
});
|
||||||
|
|
||||||
it('renders correctly', () => {
|
it('renders correctly', () => {
|
||||||
const testField = 'test';
|
const testField = 'title';
|
||||||
const testText = 'Test 123';
|
const testText = 'Test 123';
|
||||||
const submitHandler = vi.fn();
|
const submitHandler = vi.fn();
|
||||||
render(<TextInput field={testField} initialText={testText} submitHandler={submitHandler} />);
|
render(<TextInput field={testField} initialText={testText} submitHandler={submitHandler} />);
|
||||||
@@ -18,11 +22,10 @@ describe('TextInput component', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Handles renders as textarea', () => {
|
it('Handles renders as textarea', () => {
|
||||||
const testField = 'test';
|
const testField = 'title';
|
||||||
const testText = 'Test 123';
|
const testText = 'Test 123';
|
||||||
const submitHandler = vi.fn();
|
const submitHandler = vi.fn();
|
||||||
render(<TextInput field={testField} initialText={testText} isTextArea
|
render(<TextInput field={testField} initialText={testText} isTextArea submitHandler={submitHandler} />);
|
||||||
submitHandler={submitHandler} />);
|
|
||||||
|
|
||||||
const input = screen.getByTestId('input-textarea');
|
const input = screen.getByTestId('input-textarea');
|
||||||
expect(input).toBeInTheDocument();
|
expect(input).toBeInTheDocument();
|
||||||
@@ -31,8 +34,12 @@ describe('TextInput component', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('on status change', () => {
|
describe('on status change', () => {
|
||||||
|
// small hack to reset DOM between tests
|
||||||
|
afterEach(() => {
|
||||||
|
document.getElementsByTagName('html')[0].innerHTML = '';
|
||||||
|
});
|
||||||
it('calls submitHandler on new value', async () => {
|
it('calls submitHandler on new value', async () => {
|
||||||
const testField = 'test';
|
const testField = 'title';
|
||||||
const testText = 'Test 123';
|
const testText = 'Test 123';
|
||||||
const myTypedString = '456';
|
const myTypedString = '456';
|
||||||
const expectedString = testText + myTypedString;
|
const expectedString = testText + myTypedString;
|
||||||
@@ -53,7 +60,7 @@ describe('TextInput component', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('cleans value before submitting', async () => {
|
it('cleans value before submitting', async () => {
|
||||||
const testField = 'test';
|
const testField = 'title';
|
||||||
const myTypedString = ' 456 ';
|
const myTypedString = ' 456 ';
|
||||||
const expectedString = '456';
|
const expectedString = '456';
|
||||||
const submitHandler = vi.fn();
|
const submitHandler = vi.fn();
|
||||||
@@ -70,25 +77,8 @@ describe('TextInput component', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('handles edge cases', () => {
|
describe('handles edge cases', () => {
|
||||||
it('handles number values', () => {
|
|
||||||
const testField = 'test';
|
|
||||||
const testText = 123;
|
|
||||||
render(<TextInput field={testField} initialText={testText} />);
|
|
||||||
const input = screen.getByTestId('input-textfield');
|
|
||||||
expect(input).toBeInTheDocument();
|
|
||||||
expect(input).toHaveValue(`${testText}`);
|
|
||||||
});
|
|
||||||
it('handles null value', () => {
|
|
||||||
const testField = 'test';
|
|
||||||
const testText = null;
|
|
||||||
const expected = '';
|
|
||||||
render(<TextInput field={testField} initialText={testText} />);
|
|
||||||
const input = screen.getByTestId('input-textfield');
|
|
||||||
expect(input).toBeInTheDocument();
|
|
||||||
expect(input).toHaveValue(expected);
|
|
||||||
});
|
|
||||||
it('handles undefined value', () => {
|
it('handles undefined value', () => {
|
||||||
const testField = 'test';
|
const testField = 'title';
|
||||||
const expected = '';
|
const expected = '';
|
||||||
render(<TextInput field={testField} submitHandler={vi.fn()} />);
|
render(<TextInput field={testField} submitHandler={vi.fn()} />);
|
||||||
const input = screen.getByTestId('input-textfield');
|
const input = screen.getByTestId('input-textfield');
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import { KeyboardEvent, useCallback, useContext, useEffect, useRef, useState } from 'react';
|
import { KeyboardEvent, useCallback, useContext, useEffect, useRef, useState } from 'react';
|
||||||
import { Button, Input, InputGroup, InputLeftElement, Tooltip } from '@chakra-ui/react';
|
import { Button, Input, InputGroup, InputLeftElement, Tooltip } from '@chakra-ui/react';
|
||||||
import { LoggingContext } from 'common/context/LoggingContext';
|
|
||||||
import { forgivingStringToMillis } from 'common/utils/dateConfig';
|
|
||||||
import { stringFromMillis } from 'common/utils/time';
|
|
||||||
|
|
||||||
import { EventEditorSubmitActions } from '../../../../features/event-editor/EventEditor';
|
import { EventEditorSubmitActions } from '../../../../features/event-editor/EventEditor';
|
||||||
import { tooltipDelayFast } from '../../../../ontimeConfig';
|
import { tooltipDelayFast } from '../../../../ontimeConfig';
|
||||||
|
import { LoggingContext } from '../../../context/LoggingContext';
|
||||||
|
import { forgivingStringToMillis } from '../../../utils/dateConfig';
|
||||||
|
import { stringFromMillis } from '../../../utils/time';
|
||||||
import { TimeEntryField } from '../../../utils/timesManager';
|
import { TimeEntryField } from '../../../utils/timesManager';
|
||||||
|
|
||||||
import style from './TimeInput.module.scss';
|
import style from './TimeInput.module.scss';
|
||||||
@@ -144,7 +144,7 @@ export default function TimeInput(props: TimeInputProps) {
|
|||||||
return (
|
return (
|
||||||
<InputGroup size='sm' className={`${style.timeInput} ${isDelayed ? style.delayed : ''}`}>
|
<InputGroup size='sm' className={`${style.timeInput} ${isDelayed ? style.delayed : ''}`}>
|
||||||
<InputLeftElement width='fit-content'>
|
<InputLeftElement width='fit-content'>
|
||||||
<Tooltip label={<ButtonTooltip />} openDelay={tooltipDelayFast} variant='ontime-ondark'>
|
<Tooltip label={ButtonTooltip()} openDelay={tooltipDelayFast} variant='ontime-ondark'>
|
||||||
<Button
|
<Button
|
||||||
size='sm'
|
size='sm'
|
||||||
variant='ontime-subtle-white'
|
variant='ontime-subtle-white'
|
||||||
@@ -154,7 +154,7 @@ export default function TimeInput(props: TimeInputProps) {
|
|||||||
borderRight='1px solid transparent'
|
borderRight='1px solid transparent'
|
||||||
borderRadius='2px 0 0 2px'
|
borderRadius='2px 0 0 2px'
|
||||||
>
|
>
|
||||||
<ButtonInitial />
|
{ButtonInitial()}
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</InputLeftElement>
|
</InputLeftElement>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { clamp } from 'common/utils/math';
|
import { clamp } from '../../utils/math';
|
||||||
|
|
||||||
import './ProgressBar.scss';
|
import './ProgressBar.scss';
|
||||||
|
|
||||||