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 = [
|
||||
"__mocks__/**",
|
||||
"__tests__/**",
|
||||
"cypress/**",
|
||||
"*.test.*",
|
||||
"*.mock.*",
|
||||
"*.spec.*"
|
||||
|
||||
@@ -9,26 +9,11 @@
|
||||
"extends": [
|
||||
"eslint:recommended"
|
||||
],
|
||||
"rules": {
|
||||
// disallow certain object properties
|
||||
// https://eslint.org/docs/rules/no-restricted-properties
|
||||
"no-restricted-properties": [
|
||||
"error",
|
||||
{
|
||||
"object": "global",
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["e2e/**/**.spec.ts", "e2e/**/**.test.ts"],
|
||||
"extends": ["plugin:playwright/playwright-test"]
|
||||
}
|
||||
],
|
||||
"rules": {}
|
||||
}
|
||||
|
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:
|
||||
node-version: 16
|
||||
|
||||
# React
|
||||
- name: React - Install dependencies
|
||||
run: yarn install --frozen-lockfile --network-timeout 300000
|
||||
working-directory: ./client
|
||||
- name: Install dependencies
|
||||
run: pnpm i --frozen-lockfile
|
||||
|
||||
- name: React - Build project
|
||||
run: yarn build
|
||||
working-directory: ./client
|
||||
- name: Build project packages
|
||||
run: turbo build
|
||||
|
||||
# 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
|
||||
run: yarn dist-mac
|
||||
working-directory: ./server
|
||||
run: turbo dist-mac
|
||||
|
||||
# Release
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: ./server/dist/ontime-macOS.dmg
|
||||
files: ./electron/dist/ontime-macOS.dmg
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -65,34 +50,20 @@ jobs:
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
# React
|
||||
- name: React - Install dependencies
|
||||
run: yarn install --frozen-lockfile --network-timeout 300000
|
||||
working-directory: ./client
|
||||
- name: Install dependencies
|
||||
run: pnpm i --frozen-lockfile
|
||||
|
||||
- name: React - Build project
|
||||
run: yarn build
|
||||
working-directory: ./client
|
||||
- name: Build project packages
|
||||
run: turbo build
|
||||
|
||||
# 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
|
||||
run: yarn dist-win
|
||||
working-directory: ./server
|
||||
run: turbo dist-win
|
||||
|
||||
# Release
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: ./server/dist/ontime-win64.exe
|
||||
files: ./electron/dist/ontime-win64.exe
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -109,34 +80,20 @@ jobs:
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
# React
|
||||
- name: React - Install dependencies
|
||||
run: yarn install --frozen-lockfile --network-timeout 300000
|
||||
working-directory: ./client
|
||||
- name: Install dependencies
|
||||
run: pnpm i --frozen-lockfile
|
||||
|
||||
- name: React - Build project
|
||||
run: yarn build
|
||||
working-directory: ./client
|
||||
- name: Build project packages
|
||||
run: turbo build
|
||||
|
||||
# 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
|
||||
run: yarn dist-linux
|
||||
working-directory: ./server
|
||||
run: turbo dist-linux
|
||||
|
||||
# Release
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: ./server/dist/ontime-linux.AppImage
|
||||
files: ./electron/dist/ontime-linux.AppImage
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -145,6 +102,7 @@ jobs:
|
||||
timeout-minutes: 20
|
||||
env:
|
||||
CI: ''
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -155,19 +113,14 @@ jobs:
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
# React
|
||||
- name: React - Install dependencies
|
||||
run: yarn install --network-timeout 300000
|
||||
working-directory: ./client
|
||||
- name: Install dependencies
|
||||
run: pnpm i --frozen-lockfile
|
||||
|
||||
- name: React - Build project
|
||||
run: yarn build
|
||||
working-directory: ./client
|
||||
- name: Build project packages
|
||||
run: turbo build
|
||||
|
||||
# Node server
|
||||
- name: Server - Install dependencies
|
||||
run: yarn install --frozen-lockfile --production --network-timeout 300000
|
||||
working-directory: ./server/src
|
||||
- name: Electron - Build app
|
||||
run: turbo dist-mac
|
||||
|
||||
# Login to docker
|
||||
- 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.js
|
||||
|
||||
# testing
|
||||
coverage/
|
||||
*.mp4
|
||||
|
||||
# production
|
||||
build/
|
||||
dist/
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
*.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.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/*
|
||||
ontime.code-workspace
|
||||
|
||||
# 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,
|
||||
"semi": true,
|
||||
"singleQuote": 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
|
||||
COPY /client/build ../client/build
|
||||
COPY /apps/client/build ../client/build
|
||||
|
||||
# Prepare Backend
|
||||
COPY /server/src ./
|
||||
COPY /apps/server ./
|
||||
|
||||
# Export default ports Main - OSC IN
|
||||
EXPOSE 4001/tcp 8888/udp
|
||||
ENV NODE_ENV=production
|
||||
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 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.
|
||||
|
||||
It allows a center application to be able to distribute event information in the local network. This
|
||||
minimises needs for using Media Server outputs or expensive video distribution while allowing easy
|
||||
integration in workflows including OBS and d3.
|
||||
A single, locally hosted central application distributes your event information over the local network.
|
||||
This enables the distribution of the data to a series of viewers and allows integration into video and control workflows, including OBS and d3.
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
## Using Ontime
|
||||
|
||||
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
|
||||
the Playback Control function.
|
||||
Once installed and running, Ontime starts a background server that is the heart of all processes.
|
||||
From the app, you can add / edit your running order and control the timer playback.
|
||||
|
||||
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`
|
||||
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
|
||||
lower thirds view, where it is hidden).
|
||||
<br />
|
||||
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
|
||||
individual views and extend with using the URL aliases feature
|
||||
In the case of unattended machines or automation, it is possible to use different URL to recall
|
||||
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/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/public > Public / Foyer view
|
||||
IP.ADDRESS:4001/pip > Picture in Picture view
|
||||
IP.ADDRESS:4001/lower > Lower Thirds
|
||||
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)
|
||||
|
||||
- [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
|
||||
- Stage Timer
|
||||
- Backstage Info
|
||||
- Public Info
|
||||
- Picture in Picture
|
||||
- Studio Clock
|
||||
- [Make your own?](#make-your-own-viewer)
|
||||
- [x] Configurable realtime Lower Thirds
|
||||
- [x] Cuesheets with additional custom fields
|
||||
- [x] Configurable Lower Thirds
|
||||
- [x] Cuesheets with user definable fields
|
||||
- [x] Send live messages to different screen types
|
||||
- [x] Ability to differentiate between backstage and public data
|
||||
- [x] Manage delays workflow
|
||||
- [x] Open Sound Control (OSC) Control and Feedback
|
||||
- [x] Integrate with hardware using Companion or one of the APIs
|
||||
- [x] Differentiate between backstage and public data
|
||||
- [x] Workflow for managing delays
|
||||
- [x] Protocol integrations for Control and Feedback
|
||||
- OSC (Open Sound Control)
|
||||
- HTTP
|
||||
- WebSockets
|
||||
- [x] Roll mode: run independently using the system clock
|
||||
- [x] Import event list from Excel
|
||||
- [x] URL Aliases (define configurable aliases to ease onsite setup)
|
||||
- [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
|
||||
- [x] [Headless run](#headless-run): run server in a separate machine, configure from a browser locally
|
||||
- [x] [Countdown to anything!](https://cpvalente.gitbook.io/ontime/views/countdown): have
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
- [x] You do not need an order list to use the timer. Create an empty event and the OSC API works
|
||||
just the same
|
||||
- [x] If you want just the info screens, no need to use the timer!
|
||||
- [x] If you want just the info screens, there is no need to use the timer!
|
||||
- [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
|
||||
times to 00:00 and 00:10 (**BAM**! 10 minute timer). You can quickly recall this with OSC as
|
||||
always
|
||||
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
|
||||
|
||||
## 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.
|
||||
|
||||
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
|
||||
- `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
|
||||
in
|
||||
the [repository getontime/ontime](https://github.com/bitfocus/companion-module-getontime-ontime)
|
||||
in the [repository getontime/ontime](https://github.com/bitfocus/companion-module-getontime-ontime)
|
||||
|
||||
### Make your own viewer
|
||||
|
||||
Ontime broadcasts its data over websockets. This allows you to build your own viewers by leveranging
|
||||
basic knowledge of HTML + CSS + Javascript (or any other language that can run in the browser).
|
||||
Ontime broadcasts its data over WebSockets. This allows you to consume its data outside of the application.
|
||||
|
||||
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
|
||||
how to get you started and read the docs about
|
||||
the [Websocket API](https://app.gitbook.com/s/-Mc0giSOToAhq0ROd0CR/control-and-feedback/websocket-api)
|
||||
|
||||
### 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````)
|
||||
- the image name __getontime/ontime__
|
||||
|
||||
@@ -154,19 +148,14 @@ docker-compose up
|
||||
|
||||
### 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)
|
||||
- [ ] 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
|
||||
Several features are planned in the roadmap, and we continuously adjust this to match how users interact with the app.
|
||||
<br />
|
||||
Have an idea? Reach out via [email](mail@getontime.no) or [open an issue](https://github.com/cpvalente/ontime/issues/new)
|
||||
|
||||
### 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
|
||||
|
||||
@@ -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
|
||||
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,
|
||||
please [open an issue, so we can discuss it](https://github.com/cpvalente/ontime/issues/new)
|
||||
have tips on how to improve this or would like to sponsor the code signing,
|
||||
please [open an issue](https://github.com/cpvalente/ontime/issues/new)
|
||||
|
||||
#### 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
|
||||
- Table view does not work
|
||||
|
||||
There is no plan for any further work on this since the breaking code belongs to third party
|
||||
libraries.
|
||||
There is no plan for any further work on this.
|
||||
|
||||
# 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 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
|
||||
|
||||
|
||||
@@ -8,21 +8,31 @@
|
||||
"browser": true,
|
||||
"node": true
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:react-hooks/recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"eslint-config-prettier",
|
||||
"plugin:@tanstack/eslint-plugin-query/recommended"
|
||||
"plugin:@tanstack/eslint-plugin-query/recommended",
|
||||
"prettier"
|
||||
],
|
||||
"plugins": [
|
||||
"react",
|
||||
"testing-library",
|
||||
"simple-import-sort",
|
||||
"@tanstack/query"
|
||||
"@tanstack/query",
|
||||
"@typescript-eslint",
|
||||
"prettier"
|
||||
],
|
||||
"rules": {
|
||||
"prettier/prettier": [
|
||||
"error",
|
||||
{
|
||||
"endOfLine": "auto"
|
||||
}
|
||||
],
|
||||
"no-useless-concat": "warn",
|
||||
"prefer-template": "warn",
|
||||
"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",
|
||||
"version": "1.9.7",
|
||||
"version": "2.0.0-alpha",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@chakra-ui/react": "^2.4.5",
|
||||
@@ -21,6 +21,7 @@
|
||||
"framer-motion": "^8.0.2",
|
||||
"jotai": "^1.10.0",
|
||||
"luxon": "^3.1.0",
|
||||
"ontime-utils": "workspace:*",
|
||||
"react": "^18.2.0",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-dom": "^18.2.0",
|
||||
@@ -30,15 +31,18 @@
|
||||
"react-table": "^7.7.0",
|
||||
"socket.io-client": "^4.5.4",
|
||||
"typeface-open-sans": "^1.1.13",
|
||||
"web-vitals": "^2.1.4"
|
||||
"web-vitals": "^3.1.1"
|
||||
},
|
||||
"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",
|
||||
"lint": "eslint .",
|
||||
"stylelint": "npx stylelint \"**/*.scss\"\n",
|
||||
"test": "vitest",
|
||||
"test:pipeline": "vitest run"
|
||||
"test:pipeline": "vitest run",
|
||||
"cleanup": "rm -rf .turbo && rm -rf node_modules && rm -rf build"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
@@ -55,38 +59,36 @@
|
||||
"devDependencies": {
|
||||
"@sentry/vite-plugin": "^0.3.0",
|
||||
"@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/user-event": "^14.1.1",
|
||||
"@types/color": "^3.0.3",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/prop-types": "^15.7.5",
|
||||
"@types/react": "^18.0.26",
|
||||
"@types/react-beautiful-dnd": "^13.1.3",
|
||||
"@types/react-dom": "^18.0.10",
|
||||
"@typescript-eslint/eslint-plugin": "^5.47.1",
|
||||
"@typescript-eslint/parser": "^5.47.1",
|
||||
"@vitejs/plugin-react": "^3.0.0",
|
||||
"eslint": "^8.25.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"@types/testing-library__jest-dom": "^5.14.5",
|
||||
"@typescript-eslint/eslint-plugin": "^5.48.1",
|
||||
"@typescript-eslint/parser": "^5.48.1",
|
||||
"@vitejs/plugin-react": "^3.0.1",
|
||||
"eslint": "^8.31.0",
|
||||
"eslint-config-prettier": "^8.6.0",
|
||||
"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-simple-import-sort": "^8.0.0",
|
||||
"eslint-plugin-testing-library": "^5.9.1",
|
||||
"jsdom": "^20.0.3",
|
||||
"prettier": "^2.8.1",
|
||||
"jsdom": "^21.1.0",
|
||||
"prettier": "^2.8.3",
|
||||
"prop-types": "^15.8.1",
|
||||
"sass": "^1.57.1",
|
||||
"stylelint": "^14.16.0",
|
||||
"stylelint": "^14.16.1",
|
||||
"stylelint-config-prettier": "^9.0.4",
|
||||
"stylelint-config-standard-scss": "^6.1.0",
|
||||
"typescript": "^4.8.3",
|
||||
"vite": "^4.0.3",
|
||||
"typescript": "^4.9.4",
|
||||
"vite": "^4.0.4",
|
||||
"vite-plugin-svgr": "^2.4.0",
|
||||
"vite-tsconfig-paths": "^4.0.3",
|
||||
"vitest": "^0.26.2"
|
||||
},
|
||||
"resolutions": {
|
||||
"**/@types/react": "18.0.21"
|
||||
"vite-tsconfig-paths": "^4.0.3"
|
||||
}
|
||||
}
|
||||
|
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 { QueryClientProvider } from '@tanstack/react-query';
|
||||
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 { ontimeQueryClient } from './common/queryClient';
|
||||
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 withSocket from './features/viewers/ViewWrapper';
|
||||
|
||||
const Editor = lazy(() => import('features/editors/ProtectedEditor'));
|
||||
const Table = lazy(() => import('features/table/ProtectedTable'));
|
||||
const Editor = lazy(() => import('./features/editors/ProtectedEditor'));
|
||||
const Table = lazy(() => import('./features/table/ProtectedTable'));
|
||||
|
||||
const TimerView = lazy(() => import('features/viewers/timer/Timer'));
|
||||
const MinimalTimerView = lazy(() => import('features/viewers/minimal-timer/MinimalTimer'));
|
||||
const ClockView = lazy(() => import('features/viewers/clock/Clock'));
|
||||
const Countdown = lazy(() => import('features/viewers/countdown/Countdown'));
|
||||
const TimerView = lazy(() => import('./features/viewers/timer/Timer'));
|
||||
const MinimalTimerView = lazy(() => import('./features/viewers/minimal-timer/MinimalTimer'));
|
||||
const ClockView = lazy(() => import('./features/viewers/clock/Clock'));
|
||||
const Countdown = lazy(() => import('./features/viewers/countdown/Countdown'));
|
||||
|
||||
const Backstage = lazy(() => import('features/viewers/backstage/Backstage'));
|
||||
const Public = lazy(() => import('features/viewers/public/Public'));
|
||||
const Lower = lazy(() => import('features/viewers/lower-thirds/LowerWrapper'));
|
||||
const StudioClock = lazy(() => import('features/viewers/studio/StudioClock'));
|
||||
const Backstage = lazy(() => import('./features/viewers/backstage/Backstage'));
|
||||
const Public = lazy(() => import('./features/viewers/public/Public'));
|
||||
const Lower = lazy(() => import('./features/viewers/lower-thirds/LowerWrapper'));
|
||||
const StudioClock = lazy(() => import('./features/viewers/studio/StudioClock'));
|
||||
|
||||
const STimer = withSocket(TimerView);
|
||||
const SMinimalTimer = withSocket(MinimalTimerView);
|
||||
@@ -26,11 +26,11 @@ const SPublic = withSocket(Public);
|
||||
const SLowerThird = withSocket(Lower);
|
||||
const SStudio = withSocket(StudioClock);
|
||||
|
||||
const FeatureWrapper = lazy(() => import('features/FeatureWrapper'));
|
||||
const RundownPanel = lazy(() => import('features/rundown/RundownExport'));
|
||||
const TimerControl = lazy(() => import('features/control/playback/TimerControlExport'));
|
||||
const MessageControl = lazy(() => import('features/control/message/MessageControlExport'));
|
||||
const Info = lazy(() => import('features/info/InfoExport'));
|
||||
const FeatureWrapper = lazy(() => import('./features/FeatureWrapper'));
|
||||
const RundownPanel = lazy(() => import('./features/rundown/RundownExport'));
|
||||
const TimerControl = lazy(() => import('./features/control/playback/TimerControlExport'));
|
||||
const MessageControl = lazy(() => import('./features/control/message/MessageControlExport'));
|
||||
const Info = lazy(() => import('./features/info/InfoExport'));
|
||||
|
||||
export default function AppRouter() {
|
||||
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 { Input } from '@chakra-ui/react';
|
||||
import { clamp } from 'common/utils/math';
|
||||
|
||||
import { clamp } from '../../../utils/math';
|
||||
|
||||
import style from './DelayInput.module.scss';
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { vi } from 'vitest';
|
||||
|
||||
import TextInput from '../TextInput';
|
||||
|
||||
describe('TextInput component', () => {
|
||||
describe('when given props', () => {
|
||||
// small hack to reset DOM between tests
|
||||
beforeEach(() => {
|
||||
document.getElementsByTagName('html')[0].innerHTML = '';
|
||||
});
|
||||
|
||||
it('renders correctly', () => {
|
||||
const testField = 'test';
|
||||
const testField = 'title';
|
||||
const testText = 'Test 123';
|
||||
const submitHandler = vi.fn();
|
||||
render(<TextInput field={testField} initialText={testText} submitHandler={submitHandler} />);
|
||||
@@ -18,11 +22,10 @@ describe('TextInput component', () => {
|
||||
});
|
||||
|
||||
it('Handles renders as textarea', () => {
|
||||
const testField = 'test';
|
||||
const testField = 'title';
|
||||
const testText = 'Test 123';
|
||||
const submitHandler = vi.fn();
|
||||
render(<TextInput field={testField} initialText={testText} isTextArea
|
||||
submitHandler={submitHandler} />);
|
||||
render(<TextInput field={testField} initialText={testText} isTextArea submitHandler={submitHandler} />);
|
||||
|
||||
const input = screen.getByTestId('input-textarea');
|
||||
expect(input).toBeInTheDocument();
|
||||
@@ -31,8 +34,12 @@ describe('TextInput component', () => {
|
||||
});
|
||||
|
||||
describe('on status change', () => {
|
||||
// small hack to reset DOM between tests
|
||||
afterEach(() => {
|
||||
document.getElementsByTagName('html')[0].innerHTML = '';
|
||||
});
|
||||
it('calls submitHandler on new value', async () => {
|
||||
const testField = 'test';
|
||||
const testField = 'title';
|
||||
const testText = 'Test 123';
|
||||
const myTypedString = '456';
|
||||
const expectedString = testText + myTypedString;
|
||||
@@ -53,7 +60,7 @@ describe('TextInput component', () => {
|
||||
});
|
||||
|
||||
it('cleans value before submitting', async () => {
|
||||
const testField = 'test';
|
||||
const testField = 'title';
|
||||
const myTypedString = ' 456 ';
|
||||
const expectedString = '456';
|
||||
const submitHandler = vi.fn();
|
||||
@@ -70,25 +77,8 @@ describe('TextInput component', () => {
|
||||
});
|
||||
|
||||
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', () => {
|
||||
const testField = 'test';
|
||||
const testField = 'title';
|
||||
const expected = '';
|
||||
render(<TextInput field={testField} submitHandler={vi.fn()} />);
|
||||
const input = screen.getByTestId('input-textfield');
|
||||
@@ -1,11 +1,11 @@
|
||||
import { KeyboardEvent, useCallback, useContext, useEffect, useRef, useState } from '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 { tooltipDelayFast } from '../../../../ontimeConfig';
|
||||
import { LoggingContext } from '../../../context/LoggingContext';
|
||||
import { forgivingStringToMillis } from '../../../utils/dateConfig';
|
||||
import { stringFromMillis } from '../../../utils/time';
|
||||
import { TimeEntryField } from '../../../utils/timesManager';
|
||||
|
||||
import style from './TimeInput.module.scss';
|
||||
@@ -144,7 +144,7 @@ export default function TimeInput(props: TimeInputProps) {
|
||||
return (
|
||||
<InputGroup size='sm' className={`${style.timeInput} ${isDelayed ? style.delayed : ''}`}>
|
||||
<InputLeftElement width='fit-content'>
|
||||
<Tooltip label={<ButtonTooltip />} openDelay={tooltipDelayFast} variant='ontime-ondark'>
|
||||
<Tooltip label={ButtonTooltip()} openDelay={tooltipDelayFast} variant='ontime-ondark'>
|
||||
<Button
|
||||
size='sm'
|
||||
variant='ontime-subtle-white'
|
||||
@@ -154,7 +154,7 @@ export default function TimeInput(props: TimeInputProps) {
|
||||
borderRight='1px solid transparent'
|
||||
borderRadius='2px 0 0 2px'
|
||||
>
|
||||
<ButtonInitial />
|
||||
{ButtonInitial()}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</InputLeftElement>
|
||||
@@ -1,4 +1,4 @@
|
||||
import { clamp } from 'common/utils/math';
|
||||
import { clamp } from '../../utils/math';
|
||||
|
||||
import './ProgressBar.scss';
|
||||
|
||||