mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-06 16:03:52 +00:00
Compare commits
217 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2a2554d32a | |||
| a7a78702b1 | |||
| 26d887c1a6 | |||
| ceb490c60a | |||
| 1849b4d39f | |||
| 9516ac21bb | |||
| 0e173137e8 | |||
| 1202ef3e80 | |||
| 2ef0b81743 | |||
| 5a5450d7ef | |||
| 6f1f10ccff | |||
| 5ff7861968 | |||
| f1a1c358bc | |||
| efd19353e9 | |||
| a29a493678 | |||
| 27b179276d | |||
| 155cf48a17 | |||
| 358ad79ae4 | |||
| c1fcdf7065 | |||
| 1fe58e21be | |||
| 3c0e7ba4d5 | |||
| e8f159d894 | |||
| 168d7103b1 | |||
| 63d7250a70 | |||
| 88f21e2186 | |||
| d9a18b0c42 | |||
| df588b8845 | |||
| 5be504ce8f | |||
| 6268e327b9 | |||
| 1c81b3a23c | |||
| d28895ce9c | |||
| 1a23287863 | |||
| 0b3ba65df3 | |||
| c3a7cd6a39 | |||
| 39e6c15adf | |||
| aec6673b07 | |||
| 9e8cb1c755 | |||
| cdf02f7c3f | |||
| 77779a4648 | |||
| e16ec3f388 | |||
| 63cfc9ec19 | |||
| 4827117163 | |||
| 5777ef3532 | |||
| 80a3b04493 | |||
| 7c5fdd3b19 | |||
| 31fd8e5200 | |||
| b6a01a52e2 | |||
| f9563fca5f | |||
| af1da58718 | |||
| 0f83ceaf01 | |||
| aecf9575b1 | |||
| 773229ce4c | |||
| da02ecd113 | |||
| 967e92e2c8 | |||
| d7af73d9ed | |||
| c5045ad82e | |||
| 8c22968bc9 | |||
| 8aad271ffe | |||
| b28573455a | |||
| e25725ea8b | |||
| 1f8065143a | |||
| 8e32314667 | |||
| 0de55e74a8 | |||
| e5c5ec18d3 | |||
| f9f6cb74ad | |||
| 026e24bf84 | |||
| ec1890c275 | |||
| 046f1176eb | |||
| 04c5b67475 | |||
| 7b5f797c95 | |||
| 14f9472250 | |||
| efede83271 | |||
| 904db95f59 | |||
| 525999440a | |||
| 6e5805977a | |||
| eb897a2a23 | |||
| 05d997a52d | |||
| f8cde5d627 | |||
| 1b55254834 | |||
| 6436e3fda8 | |||
| 680273770c | |||
| 75e77de466 | |||
| 75cbcd8e85 | |||
| 701f24cece | |||
| 166160dda9 | |||
| 9c5116f197 | |||
| deb1f043c7 | |||
| cbba19a9b1 | |||
| a5daa76616 | |||
| 08b8e73393 | |||
| 2f3cf9825a | |||
| b0f02e726d | |||
| ac40292813 | |||
| 2ac00ee8cc | |||
| 972f9e35fd | |||
| 1d7ee163a0 | |||
| 1054ec0a5e | |||
| fc2e76db0a | |||
| ef3d541eb3 | |||
| 8daf3313f2 | |||
| 93c913ec5a | |||
| 71b2fa8e21 | |||
| f2b78e0f32 | |||
| b70ef80320 | |||
| 426d42ab63 | |||
| 242cefd35c | |||
| 6beda1575a | |||
| ba75d52d29 | |||
| 35a4361ae9 | |||
| 77225383a4 | |||
| f0e81c0cf0 | |||
| 9355d7bf27 | |||
| cd39227af7 | |||
| 14e21214af | |||
| 03184b0321 | |||
| cd5af769ab | |||
| d116670318 | |||
| 9ba5ebd4bd | |||
| 3f1f06f7c5 | |||
| a78586d2fa | |||
| 8e93b0af25 | |||
| 9a5d50d933 | |||
| 1189a94bff | |||
| f128f7acd2 | |||
| 80ec2d1186 | |||
| 18e6f0d7c4 | |||
| 48f2511764 | |||
| c20ea88021 | |||
| ce2b2b9606 | |||
| f223766445 | |||
| 3dfb49e63f | |||
| a13d3dad93 | |||
| fa9695873a | |||
| 70ce1d5553 | |||
| 9974ce24cd | |||
| 93e9c19e0d | |||
| 1694cca92c | |||
| c1d9276dd5 | |||
| f5753a0f5a | |||
| 843f5a968d | |||
| e09b6bbc16 | |||
| 19d6b40bdb | |||
| 1f2706a063 | |||
| aaeadd13cf | |||
| 72e3eb11d7 | |||
| 6f55d8a6c9 | |||
| 70846aefbe | |||
| 47ba90f191 | |||
| cee3c9c070 | |||
| 15f3b9625c | |||
| d34a1f0fd8 | |||
| 0e3b3bcf9a | |||
| 50c91f976b | |||
| 50599eccd2 | |||
| 9f2db10548 | |||
| 5dce1a40bc | |||
| b75b69c3b1 | |||
| e6070e6efd | |||
| 3918664945 | |||
| b65b3fa6d2 | |||
| 162693e19e | |||
| becd734ea8 | |||
| 9dfc8083f0 | |||
| 7bd98c23a4 | |||
| 6f19d78e53 | |||
| 163baa752f | |||
| 6f708df6f6 | |||
| eb5c62cca1 | |||
| 99d2debca8 | |||
| 74e59dccdb | |||
| 812b17a221 | |||
| 32bf0f53f6 | |||
| 17f80baf48 | |||
| e0377a7244 | |||
| b7e5723fb2 | |||
| 897210d5e3 | |||
| ee3d38fce8 | |||
| 37abd3fe47 | |||
| b509ee5b1f | |||
| dd2b59efe5 | |||
| 5788785b3e | |||
| e488ad19b3 | |||
| cbc03c898c | |||
| 155bfbdc42 | |||
| 5d3714a4fc | |||
| a8a1d210d8 | |||
| 49de8b63a7 | |||
| 03c9f51b47 | |||
| 8d00fd3966 | |||
| 80fd21354e | |||
| a87b15825a | |||
| 6a8f98c469 | |||
| 8685298430 | |||
| d43af2db57 | |||
| ef718c48e4 | |||
| 45e7373279 | |||
| 6af2d6e021 | |||
| 9b36f36f73 | |||
| 3a0dbf057e | |||
| dae84ab48a | |||
| aa927597f3 | |||
| ef7f0fb54a | |||
| 324320fdc8 | |||
| 4b27d080b6 | |||
| 7918063753 | |||
| e2202c2417 | |||
| 9817c8f8c0 | |||
| 1d8c04d0b4 | |||
| 10762f10bf | |||
| da7c0d4ff2 | |||
| 4a27973de1 | |||
| bdeb087e28 | |||
| 3662cd58f5 | |||
| 62c52bca07 | |||
| 1fc4a06133 | |||
| 147862e647 | |||
| 12bc323e57 |
+2
-3
@@ -1,5 +1,5 @@
|
|||||||
# Ignore config files
|
# Ignore formatter config files
|
||||||
.prettier*
|
.oxfmt*
|
||||||
|
|
||||||
# Ignore install stuff
|
# Ignore install stuff
|
||||||
**/yarn.lock
|
**/yarn.lock
|
||||||
@@ -17,7 +17,6 @@
|
|||||||
# Ignore build folders
|
# Ignore build folders
|
||||||
node_modules
|
node_modules
|
||||||
**/node_modules
|
**/node_modules
|
||||||
**/dist
|
|
||||||
|
|
||||||
# Ignore default volumes created by running docker compose up
|
# Ignore default volumes created by running docker compose up
|
||||||
ontime-db
|
ontime-db
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
"ONTIME_VERSION.js"
|
|
||||||
dist/
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
{
|
|
||||||
"root": true,
|
|
||||||
"parserOptions": {
|
|
||||||
"ecmaVersion": 2020
|
|
||||||
},
|
|
||||||
"env": {
|
|
||||||
"es6": true,
|
|
||||||
"jest": true
|
|
||||||
},
|
|
||||||
"parser": "@typescript-eslint/parser",
|
|
||||||
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier", "eslint-config-prettier"],
|
|
||||||
"plugins": ["@typescript-eslint", "prettier"],
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"files": ["e2e/**/**.spec.ts", "e2e/**/**.test.ts"],
|
|
||||||
"extends": ["plugin:playwright/playwright-test"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"no-useless-concat": "warn",
|
|
||||||
"prefer-template": "warn",
|
|
||||||
"no-throw-literal": "error",
|
|
||||||
"no-console": [
|
|
||||||
"warn",
|
|
||||||
{
|
|
||||||
"allow": ["warn", "error"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"no-restricted-imports": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"patterns": ["ontime-types/src/*", "ontime-utils/src/*"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"@typescript-eslint/no-non-null-assertion": "warn",
|
|
||||||
"@typescript-eslint/no-explicit-any": "warn",
|
|
||||||
"@typescript-eslint/no-unused-vars": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"argsIgnorePattern": "^_",
|
|
||||||
"caughtErrorsIgnorePattern": "^_",
|
|
||||||
"destructuredArrayIgnorePattern": "^_",
|
|
||||||
"varsIgnorePattern": "^_"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"prettier/prettier": [
|
|
||||||
"warn",
|
|
||||||
{
|
|
||||||
"endOfLine": "auto"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
github: cpvalente
|
github: cpvalente
|
||||||
buy_me_a_coffee: cpvalente
|
buy_me_a_coffee: cpvalente
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 183 KiB After Width: | Height: | Size: 259 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 336 KiB After Width: | Height: | Size: 149 KiB |
@@ -13,13 +13,18 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# This step is only needed to setup the permissions to update npm as pnpm will setup the correct node version
|
||||||
|
- uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version-file: '.nvmrc'
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
|
|
||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
run: echo "PNPM_STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
run: echo "PNPM_STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Setup pnpm cache
|
- name: Setup pnpm cache
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
@@ -38,16 +43,19 @@ jobs:
|
|||||||
|
|
||||||
- name: Copy server
|
- name: Copy server
|
||||||
run: mkdir -p apps/cli/server && cp apps/server/dist/index.cjs apps/cli/server/index.cjs
|
run: mkdir -p apps/cli/server && cp apps/server/dist/index.cjs apps/cli/server/index.cjs
|
||||||
|
|
||||||
- name: Copy client
|
- name: Copy client
|
||||||
run: cp -R apps/client/build apps/cli/client
|
run: cp -R apps/client/build apps/cli/client
|
||||||
|
|
||||||
- name: Copy external
|
- name: Copy external
|
||||||
run: cp -R apps/server/src/external apps/cli/external
|
run: cp -R apps/server/src/external apps/cli/external
|
||||||
|
|
||||||
|
# This will be included in v24 of NodeJS so when the project upgrades to that this can be removed
|
||||||
|
- name: Install newer version of npm
|
||||||
|
run: npm install -g npm@11
|
||||||
|
|
||||||
- name: Publish to NPM
|
- name: Publish to NPM
|
||||||
run: pnpm publish --access public --no-git-checks
|
run: pnpm publish --access public --no-git-checks
|
||||||
env:
|
env:
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
||||||
NPM_CONFIG_PROVENANCE: true
|
NPM_CONFIG_PROVENANCE: true
|
||||||
working-directory: ./apps/cli
|
working-directory: ./apps/cli
|
||||||
|
|||||||
@@ -6,43 +6,62 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
publish_docker:
|
publish_docker:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
CI: ''
|
CI: ''
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Docker Login
|
- name: Setup pnpm
|
||||||
uses: docker/login-action@v2.1.0
|
uses: pnpm/action-setup@v4
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Docker Setup Buildx
|
- name: Get pnpm store directory
|
||||||
uses: docker/setup-buildx-action@v2.5.0
|
run: echo "PNPM_STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Build and push stable release
|
- name: Setup pnpm cache
|
||||||
if: github.event.release.prerelease == false
|
uses: actions/cache@v4
|
||||||
uses: docker/build-push-action@v4.0.0
|
with:
|
||||||
with:
|
path: ${{ env.PNPM_STORE_PATH }}
|
||||||
context: .
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('pnpm-lock.yaml') }}
|
||||||
file: ./Dockerfile
|
restore-keys: |
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
${{ runner.os }}-pnpm-store-
|
||||||
# Push is a shorthand for --output=type=registry
|
|
||||||
push: true
|
|
||||||
tags: ${{ secrets.DOCKERHUB_USERNAME }}/ontime:${{ github.event.release.tag_name }} , ${{ secrets.DOCKERHUB_USERNAME }}/ontime:latest
|
|
||||||
|
|
||||||
- name: Build and push pre-release
|
- name: Install dependencies
|
||||||
if: github.event.release.prerelease == true
|
run: pnpm install --frozen-lockfile
|
||||||
uses: docker/build-push-action@v4.0.0
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: ./Dockerfile
|
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
|
||||||
# Push is a shorthand for --output=type=registry
|
|
||||||
push: true
|
|
||||||
tags: ${{ secrets.DOCKERHUB_USERNAME }}/ontime:${{ github.event.release.tag_name }} , ${{ secrets.DOCKERHUB_USERNAME }}/ontime:nightly
|
|
||||||
|
|
||||||
|
- name: Build project packages
|
||||||
|
env:
|
||||||
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
|
run: pnpm build:docker
|
||||||
|
|
||||||
|
- name: Docker Login
|
||||||
|
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Docker Setup Buildx
|
||||||
|
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||||
|
|
||||||
|
- name: Setup Docker metadata
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
|
||||||
|
with:
|
||||||
|
images: ${{ secrets.DOCKERHUB_USERNAME }}/ontime
|
||||||
|
tags: |
|
||||||
|
type=semver,pattern=v{{version}}
|
||||||
|
type=semver,pattern=v{{major}}
|
||||||
|
type=raw,value=nightly,enable=${{ github.event.release.prerelease == true }}
|
||||||
|
|
||||||
|
- name: Build and push stable release
|
||||||
|
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./Dockerfile
|
||||||
|
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||||
|
# Push is a shorthand for --output=type=registry
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ name: Ontime build
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags: [ "*" ]
|
tags: ['*']
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -11,13 +11,13 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
|
|
||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
run: echo "PNPM_STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
run: echo "PNPM_STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Setup pnpm cache
|
- name: Setup pnpm cache
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
run: pnpm build
|
run: pnpm build
|
||||||
|
|
||||||
- name: Electron - Build app
|
- name: Electron - Build app
|
||||||
env:
|
env:
|
||||||
APPLE_ID: ${{ secrets.APPLEID }}
|
APPLE_ID: ${{ secrets.APPLEID }}
|
||||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLEIDPASS }}
|
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLEIDPASS }}
|
||||||
APPLE_TEAM_ID: ${{ secrets.TEAMID }}
|
APPLE_TEAM_ID: ${{ secrets.TEAMID }}
|
||||||
@@ -103,6 +103,9 @@ jobs:
|
|||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
files: './apps/electron/dist/ontime-linux.AppImage'
|
files: |
|
||||||
|
./apps/electron/dist/ontime-linux-x86_64.AppImage
|
||||||
|
./apps/electron/dist/ontime-linux-arm64.AppImage
|
||||||
|
./apps/electron/dist/ontime-linux-armv7l.AppImage
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
name: Ontime Resolver build
|
name: Ontime Resolver build
|
||||||
|
|
||||||
on:
|
on:
|
||||||
release:
|
|
||||||
types: [published]
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -13,13 +11,18 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# This step is only needed to setup the permissions to update npm as pnpm will setup the correct node version
|
||||||
|
- uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version-file: '.nvmrc'
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
|
|
||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
run: echo "PNPM_STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
run: echo "PNPM_STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Setup pnpm cache
|
- name: Setup pnpm cache
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
@@ -34,9 +37,12 @@ jobs:
|
|||||||
- name: Build project packages
|
- name: Build project packages
|
||||||
run: pnpm build:resolver
|
run: pnpm build:resolver
|
||||||
|
|
||||||
|
# This will be included in v24 of NodeJS so when the project upgrades to that this can be removed
|
||||||
|
- name: Install newer version of npm
|
||||||
|
run: npm install -g npm@11
|
||||||
|
|
||||||
- name: Publish to NPM
|
- name: Publish to NPM
|
||||||
run: pnpm publish --access public --no-git-checks
|
run: pnpm publish --access public --no-git-checks
|
||||||
env:
|
env:
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
||||||
NPM_CONFIG_PROVENANCE: true
|
NPM_CONFIG_PROVENANCE: true
|
||||||
working-directory: ./apps/resolver
|
working-directory: ./apps/resolver
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
run: echo "PNPM_STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
run: echo "PNPM_STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Setup pnpm cache
|
- name: Setup pnpm cache
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
@@ -32,10 +32,10 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
# Run code quality
|
# Run code quality
|
||||||
- name: Run linter
|
- name: Run linter
|
||||||
run: pnpm lint
|
run: pnpm lint
|
||||||
|
|
||||||
- name: Run TypeScript checks
|
- name: Run TypeScript checks
|
||||||
run: pnpm typecheck
|
run: pnpm typecheck
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
run: echo "PNPM_STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
run: echo "PNPM_STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Setup pnpm cache
|
- name: Setup pnpm cache
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
@@ -74,7 +74,7 @@ jobs:
|
|||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.cache/ms-playwright
|
~/.cache/ms-playwright
|
||||||
key: ${{ runner.os }}-playwright-${{ env.PLAYWRIGHT_VERSION }}
|
key: ${{ runner.os }}-playwright-${{ env.PLAYWRIGHT_VERSION }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-playwright-
|
${{ runner.os }}-playwright-
|
||||||
|
|||||||
+3
-1
@@ -33,6 +33,7 @@ e2e/tests/fixtures/tmp/*
|
|||||||
# production
|
# production
|
||||||
build/
|
build/
|
||||||
dist/
|
dist/
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
# bundled assets
|
# bundled assets
|
||||||
translations.json
|
translations.json
|
||||||
@@ -40,6 +41,7 @@ override.css
|
|||||||
|
|
||||||
# working stuff
|
# working stuff
|
||||||
**/TODO.md
|
**/TODO.md
|
||||||
|
**.local.**
|
||||||
|
|
||||||
# docker utils
|
# docker utils
|
||||||
ontime-db
|
ontime-db
|
||||||
@@ -50,4 +52,4 @@ ontime-data/
|
|||||||
**/ONTIME_VERSION.js
|
**/ONTIME_VERSION.js
|
||||||
|
|
||||||
# temporary write files
|
# temporary write files
|
||||||
**.tmp
|
**.tmp
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/oxfmt/configuration_schema.json",
|
||||||
|
"endOfLine": "lf",
|
||||||
|
"trailingComma": "all",
|
||||||
|
"tabWidth": 2,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"jsxSingleQuote": true,
|
||||||
|
"printWidth": 120,
|
||||||
|
"sortPackageJson": false,
|
||||||
|
"sortImports": {},
|
||||||
|
"ignorePatterns": [
|
||||||
|
"build",
|
||||||
|
"coverage",
|
||||||
|
"dist",
|
||||||
|
"node_modules",
|
||||||
|
"playwright-report",
|
||||||
|
"pnpm-lock.yaml",
|
||||||
|
"**/*.toml",
|
||||||
|
"**/*.json",
|
||||||
|
"!tsconfig.common.json",
|
||||||
|
"!turbo.json",
|
||||||
|
"!package.json"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||||
|
"categories": {
|
||||||
|
"correctness": "warn",
|
||||||
|
"suspicious": "off",
|
||||||
|
"pedantic": "off",
|
||||||
|
"perf": "warn",
|
||||||
|
"style": "off",
|
||||||
|
"restriction": "off",
|
||||||
|
"nursery": "off"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"no-unused-vars": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"argsIgnorePattern": "^_",
|
||||||
|
"varsIgnorePattern": "^_",
|
||||||
|
"caughtErrorsIgnorePattern": "^_",
|
||||||
|
"destructuredArrayIgnorePattern": "^_",
|
||||||
|
"caughtErrors": "none"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"no-restricted-imports": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"patterns": ["ontime-types/src/*", "ontime-utils/src/*"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ignorePatterns": [
|
||||||
|
"**/node_modules/**",
|
||||||
|
"**/dist/**",
|
||||||
|
"apps/client/build/**",
|
||||||
|
"apps/electron/assets/**",
|
||||||
|
"**/ONTIME_VERSION.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
build
|
|
||||||
coverage
|
|
||||||
dist
|
|
||||||
node_modules
|
|
||||||
|
|
||||||
playwright-report
|
|
||||||
|
|
||||||
**/*.toml
|
|
||||||
**/*.yml
|
|
||||||
**/*.json
|
|
||||||
!tsconfig.common.json
|
|
||||||
!turbo.json
|
|
||||||
!package.json
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"endOfLine": "lf",
|
|
||||||
"trailingComma": "all",
|
|
||||||
"tabWidth": 2,
|
|
||||||
"semi": true,
|
|
||||||
"singleQuote": true,
|
|
||||||
"jsxSingleQuote": true,
|
|
||||||
"printWidth": 120
|
|
||||||
}
|
|
||||||
+24
-24
@@ -2,16 +2,16 @@
|
|||||||
|
|
||||||
Ontime consists of 3 distinct parts
|
Ontime consists of 3 distinct parts
|
||||||
|
|
||||||
- __client__: A React app for Ontime's UI and web clients
|
- **client**: A React app for Ontime's UI and web clients
|
||||||
- __electron__: An electron app which facilitates the cross-platform distribution of Ontime
|
- **electron**: An electron app which facilitates the cross-platform distribution of Ontime
|
||||||
- __server__: A node application which handles the domains services and integrations
|
- **server**: A node application which handles the domains services and integrations
|
||||||
|
|
||||||
The steps below will assume you have locally installed the necessary dependencies.
|
The steps below will assume you have locally installed the necessary dependencies.
|
||||||
Other dependencies will be installed as part of the setup
|
Other dependencies will be installed as part of the setup
|
||||||
|
|
||||||
- __node__ (~22)
|
- **node** (~22)
|
||||||
- __pnpm__ (~10)
|
- **pnpm** (~10)
|
||||||
- __docker__ (only necessary to run and build docker images)
|
- **docker** (only necessary to run and build docker images)
|
||||||
|
|
||||||
## LOCAL DEVELOPMENT
|
## LOCAL DEVELOPMENT
|
||||||
|
|
||||||
@@ -20,9 +20,10 @@ development.
|
|||||||
Locally, we would need to run both the React client and the node.js server in development mode
|
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
|
From the project root, run the following commands
|
||||||
- __Install the project dependencies__ by running `pnpm i`
|
|
||||||
- __Run dev mode__ by running `pnpm dev` or `pnpm dev:electron` to get the electron window
|
|
||||||
|
|
||||||
|
- **Install the project dependencies** by running `pnpm i`
|
||||||
|
- **Create a local build** by running `pnpm build`, this will populate local dependencies
|
||||||
|
- **Run dev mode** by running `pnpm dev` or `pnpm dev:electron` to get the electron window
|
||||||
|
|
||||||
### Debugging backend
|
### Debugging backend
|
||||||
|
|
||||||
@@ -30,8 +31,9 @@ The previous command will start the development servers for both the client, ser
|
|||||||
Typically in dev mode we prefer to start these in separate terminals to help with error tracking and debugging.
|
Typically in dev mode we prefer to start these in separate terminals to help with error tracking and debugging.
|
||||||
|
|
||||||
We do that by creating two terminals an running
|
We do that by creating two terminals an running
|
||||||
- __Run the React UI__ by running `pnpm dev --filter=ontime-ui`
|
|
||||||
- __Run the nodejs server__ by running `pnpm dev --filter=ontime-server`
|
- **Run the React UI** by running `pnpm dev --filter=ontime-ui`
|
||||||
|
- **Run the nodejs server** by running `pnpm dev --filter=ontime-server`
|
||||||
|
|
||||||
- If you need to set breakpoints and inspect the code execution, enable Node.js inspect mode by running `pnpm dev:inspect --filter=ontime-server`.
|
- If you need to set breakpoints and inspect the code execution, enable Node.js inspect mode by running `pnpm dev:inspect --filter=ontime-server`.
|
||||||
|
|
||||||
@@ -76,15 +78,12 @@ You can generate a distribution for your OS by running the following steps.
|
|||||||
|
|
||||||
From the project root, run the following commands
|
From the project root, run the following commands
|
||||||
|
|
||||||
- __Install the project dependencies__ by running `pnpm i`
|
- **Install the project dependencies** by running `pnpm i`
|
||||||
- __Build the UI and server__ by running `pnpm build`
|
- **Build the UI and server** by running `pnpm build`
|
||||||
- __Create the package__ by running `pnpm dist-win`, `pnpm dist-mac` or `pnpm dist-linux`
|
- **Create the package** by running `pnpm dist-win`, `pnpm dist-mac` or `pnpm dist-linux`
|
||||||
|
|
||||||
The build distribution assets will be at `.apps/electron/dist`
|
The build distribution assets will be at `.apps/electron/dist`
|
||||||
|
|
||||||
Note: The MacOS build will only work in CI, locally it will fail due to notarisation issues.
|
|
||||||
Use the `pnpm dist-mac:local` command to build a MacOS distribution locally and skip the notary process.
|
|
||||||
|
|
||||||
## DOCKER
|
## DOCKER
|
||||||
|
|
||||||
Ontime provides a docker-compose file to aid with building and running docker images.
|
Ontime provides a docker-compose file to aid with building and running docker images.
|
||||||
@@ -92,23 +91,24 @@ While it should allow for a generic setup, it might need to be modified to fit y
|
|||||||
|
|
||||||
From the project root, run the following commands
|
From the project root, run the following commands
|
||||||
|
|
||||||
- __Build docker image from__ by running `docker build -t getontime/ontime .`
|
- **Build docker image from** by running `docker build -t getontime/ontime .`
|
||||||
- __Run docker image from compose__ by running `docker-compose up -d`
|
- **Run docker image from compose** by running `docker-compose up -d`
|
||||||
|
|
||||||
Other useful commands
|
Other useful commands
|
||||||
|
|
||||||
- __List running processes__ by running `docker ps`
|
- **List running processes** by running `docker ps`
|
||||||
- __Kill running process__ by running `docker kill <process-id>`
|
- **Kill running process** by running `docker kill <process-id>`
|
||||||
|
|
||||||
## CONTRIBUTION GUIDELINES
|
## CONTRIBUTION GUIDELINES
|
||||||
|
|
||||||
If you want to propose changes to the codebase, please reach out before opening a Pull Request.
|
If you want to propose changes to the codebase, please reach out before opening a Pull Request.
|
||||||
|
|
||||||
For new PRs, please follow the following checklist:
|
For new PRs, please follow the following checklist:
|
||||||
* [ ] You have updated and ran unit locally and they are passing. Unit tests are generally created for all utility functions and business logic
|
|
||||||
* [ ] You have ran code formatting and linting in all your changes
|
- [ ] You have updated and ran unit locally and they are passing. Unit tests are generally created for all utility functions and business logic
|
||||||
* [ ] The branch is clean and the commits are meaningfully separated and contain descriptive messages
|
- [ ] You have ran code formatting and linting in all your changes
|
||||||
* [ ] The PR body contains description and motivation for the changes
|
- [ ] The branch is clean and the commits are meaningfully separated and contain descriptive messages
|
||||||
|
- [ ] The PR body contains description and motivation for the changes
|
||||||
|
|
||||||
After this checklist is complete, you can request a review from one of the maintainers to get feedback and approval on the changes. \
|
After this checklist is complete, you can request a review from one of the maintainers to get feedback and approval on the changes. \
|
||||||
We will review as soon as possible
|
We will review as soon as possible
|
||||||
|
|||||||
+9
-15
@@ -1,13 +1,5 @@
|
|||||||
FROM node:22-bullseye AS builder
|
ARG NODE_VERSION=22.15.1
|
||||||
ENV PNPM_HOME="/pnpm"
|
FROM node:${NODE_VERSION}-alpine
|
||||||
ENV PATH="$PNPM_HOME:$PATH"
|
|
||||||
RUN npm install -g pnpm@10.11.0
|
|
||||||
COPY . /app
|
|
||||||
WORKDIR /app
|
|
||||||
RUN pnpm --filter=ontime-ui --filter=ontime-server --filter=ontime-utils --filter=ontime-types install --config.dedupe-peer-dependents=false --frozen-lockfile
|
|
||||||
RUN pnpm run build:docker
|
|
||||||
|
|
||||||
FROM node:22-alpine
|
|
||||||
|
|
||||||
# Set environment variables
|
# Set environment variables
|
||||||
# Environment Variable to signal that we are running production
|
# Environment Variable to signal that we are running production
|
||||||
@@ -15,16 +7,17 @@ ENV NODE_ENV=docker
|
|||||||
# Ontime Data path
|
# Ontime Data path
|
||||||
ENV ONTIME_DATA=/data/
|
ENV ONTIME_DATA=/data/
|
||||||
|
|
||||||
|
RUN mkdir /app
|
||||||
WORKDIR /app/
|
WORKDIR /app/
|
||||||
|
|
||||||
# Prepare UI
|
# Prepare UI
|
||||||
COPY --from=builder /app/apps/client/build ./client/
|
COPY apps/client/build/ ./client/
|
||||||
|
|
||||||
# Prepare Backend
|
# Prepare Backend
|
||||||
COPY --from=builder /app/apps/server/dist/ ./server/
|
COPY apps/server/dist/ ./server/
|
||||||
COPY --from=builder /app/apps/server/src/external/ ./external/
|
COPY apps/server/src/external/ ./external/
|
||||||
COPY --from=builder /app/apps/server/src/user/ ./user/
|
COPY apps/server/src/user/ ./user/
|
||||||
COPY --from=builder /app/apps/server/src/html/ ./html/
|
COPY apps/server/src/html/ ./html/
|
||||||
|
|
||||||
# Export default ports
|
# Export default ports
|
||||||
EXPOSE 4001/tcp 8888/udp 9999/udp
|
EXPOSE 4001/tcp 8888/udp 9999/udp
|
||||||
@@ -32,5 +25,6 @@ EXPOSE 4001/tcp 8888/udp 9999/udp
|
|||||||
CMD ["node", "server/docker.cjs"]
|
CMD ["node", "server/docker.cjs"]
|
||||||
|
|
||||||
# Build and run commands
|
# Build and run commands
|
||||||
|
# pnpm build:docker
|
||||||
# docker buildx build . -t getontime/ontime
|
# docker buildx build . -t getontime/ontime
|
||||||
# docker run -p 4001:4001 -p 8888:8888/udp -p 9999:9999/udp -v ./ontime-db:/data/ getontime/ontime
|
# docker run -p 4001:4001 -p 8888:8888/udp -p 9999:9999/udp -v ./ontime-db:/data/ getontime/ontime
|
||||||
|
|||||||
@@ -7,27 +7,31 @@
|
|||||||
[](https://www.buymeacoffee.com/cpvalente)
|
[](https://www.buymeacoffee.com/cpvalente)
|
||||||
|
|
||||||
## Download the latest release
|
## Download the latest release
|
||||||
|
|
||||||
<a href="https://www.buymeacoffee.com/cpvalente" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" height="32"></a>
|
<a href="https://www.buymeacoffee.com/cpvalente" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" height="32"></a>
|
||||||
|
|
||||||
- Download for <a href="https://github.com/cpvalente/ontime/releases/latest/download/ontime-win64.exe">Windows</a>
|
- Download for <a href="https://github.com/cpvalente/ontime/releases/latest/download/ontime-win64.exe">Windows</a>
|
||||||
- Download for <a href="https://github.com/cpvalente/ontime/releases/latest/download/ontime-macOS-arm64.dmg">MacOS Arm</a>
|
- Download for <a href="https://github.com/cpvalente/ontime/releases/latest/download/ontime-macOS-arm64.dmg">macOS (Apple Silicon)</a>
|
||||||
- Download for <a href="https://github.com/cpvalente/ontime/releases/latest/download/ontime-macOS-x64.dmg">MacOS Intel</a>
|
- Download for <a href="https://github.com/cpvalente/ontime/releases/latest/download/ontime-macOS-x64.dmg">macOS (Intel)</a>
|
||||||
- Download AppImage for <a href="https://github.com/cpvalente/ontime/releases/latest/download/ontime-linux.AppImage">Linux</a>
|
- Download AppImage for <a href="https://github.com/cpvalente/ontime/releases/latest/download/ontime-linux-x86_64.AppImage">Linux (Intel / AMD 64-bit)</a>
|
||||||
|
- Download AppImage for <a href="https://github.com/cpvalente/ontime/releases/latest/download/ontime-linux-arm64.AppImage">Linux (ARM 64-bit, Raspberry Pi 4+)</a>
|
||||||
|
- Download AppImage for <a href="https://github.com/cpvalente/ontime/releases/latest/download/ontime-linux-armv7l.AppImage">Linux (ARM 32-bit, older Raspberry Pi)</a>
|
||||||
|
|
||||||
... or
|
... or
|
||||||
|
|
||||||
- Get from <a href="https://hub.docker.com/r/getontime/ontime">Docker hub</a>
|
- Get from <a href="https://hub.docker.com/r/getontime/ontime">Docker hub</a>
|
||||||
- Install from <a href="https://www.npmjs.com/package/ontime">NPM</a>
|
- Install from <a href="https://www.npmjs.com/package/@getontime/cli">NPM</a>
|
||||||
- Install from <a href="https://formulae.brew.sh/cask/ontime">Homebrew</a>
|
- Install from <a href="https://formulae.brew.sh/cask/ontime">Homebrew</a>
|
||||||
|
|
||||||
## Need help?
|
## Need help?
|
||||||
|
|
||||||
We do our best to have most topics covered by the documentation. However, if your question is not covered, you are welcome to [fill in a bug report in an issue](https://github.com/cpvalente/ontime/issues), [ask a question in GitHub discussions](https://github.com/cpvalente/ontime/discussions) or hop in the [discord server](https://discord.com/invite/eje3CSUEXm) for a chat.
|
We do our best to have most topics covered by the documentation. However, if your question is not covered, you are welcome to [fill in a bug report in an issue](https://github.com/cpvalente/ontime/issues), [ask a question in GitHub discussions](https://github.com/cpvalente/ontime/discussions) or hop in the [discord server](https://discord.com/invite/eje3CSUEXm) for a chat.
|
||||||
|
|
||||||
## Using Ontime?
|
## Using Ontime?
|
||||||
|
|
||||||
Let us know!
|
Let us know!
|
||||||
Ontime improves from the collaboration with its users. We would like to understand how you use Ontime and appreciate your feedback.
|
Ontime improves from the collaboration with its users. We would like to understand how you use Ontime and appreciate your feedback.
|
||||||
|
|
||||||
We would also like to include a testimonials section in our ✨new website✨. It would be great to showcase the diversity of users running Ontime.
|
|
||||||
|
|
||||||
# Ontime
|
# Ontime
|
||||||
|
|
||||||
Ontime is a browser-based application that manages event rundowns, scheduling, and cueing.
|
Ontime is a browser-based application that manages event rundowns, scheduling, and cueing.
|
||||||
@@ -43,6 +47,7 @@ Ontime is made by entertainment and broadcast engineers and used by
|
|||||||
- Houses of worship
|
- Houses of worship
|
||||||
|
|
||||||
## Main features
|
## Main features
|
||||||
|
|
||||||
- **Multiplatform**: Available as a Cloud service and for Windows, macOS, Linux, or self-hosted via Docker.
|
- **Multiplatform**: Available as a Cloud service and for Windows, macOS, Linux, or self-hosted via Docker.
|
||||||
- **In any device**: Ontime is available to any device with a browser, eg: tablets, mobile phones, laptops, signage, media servers...
|
- **In any device**: Ontime is available to any device with a browser, eg: tablets, mobile phones, laptops, signage, media servers...
|
||||||
- **Team Collaboration**: Dedicated views for directors, operators, backstage, and signage.
|
- **Team Collaboration**: Dedicated views for directors, operators, backstage, and signage.
|
||||||
@@ -111,7 +116,8 @@ If you are a developer and would like to contribute with code, please open an is
|
|||||||
Information about the project setup can be found in the [development documentation](./DEVELOPMENT.md)
|
Information about the project setup can be found in the [development documentation](./DEVELOPMENT.md)
|
||||||
|
|
||||||
## Links
|
## Links
|
||||||
- [Ontime website](https://getontime.no)
|
|
||||||
|
- [Ontime website](https://getontime.no)
|
||||||
- [Documentation](https://docs.getontime.no)
|
- [Documentation](https://docs.getontime.no)
|
||||||
- [Video tutorials](https://www.youtube.com/@ontimeapp)
|
- [Video tutorials](https://www.youtube.com/@ontimeapp)
|
||||||
- [Ontime discord server](https://discord.com/invite/eje3CSUEXm)
|
- [Ontime discord server](https://discord.com/invite/eje3CSUEXm)
|
||||||
|
|||||||
@@ -1,19 +1,24 @@
|
|||||||
# Terms of sponsorship
|
# Terms of sponsorship
|
||||||
|
|
||||||
Ontime does not exist as a fiscal entity. The sponsorship recipient is the project owner. In this light, contributors are funding the project owner's capacity to work on the project, not the project itself. As a contribution recipient, the code owner reserves the right to redistribute.
|
Ontime does not exist as a fiscal entity. The sponsorship recipient is the project owner. In this light, contributors are funding the project owner's capacity to work on the project, not the project itself. As a contribution recipient, the code owner reserves the right to redistribute.
|
||||||
|
|
||||||
## Why sponsor
|
## Why sponsor
|
||||||
|
|
||||||
Ontime is and will remain free, regardless of your decision to contribute.
|
Ontime is and will remain free, regardless of your decision to contribute.
|
||||||
Regardless, you may choose to provide a monetary contribution:
|
Regardless, you may choose to provide a monetary contribution:
|
||||||
|
|
||||||
### Improve user experience
|
### Improve user experience
|
||||||
|
|
||||||
Certain parts of the application user experience are tied to our capacity to pay for services. App signing and Google Sheets integration are good examples of this.
|
Certain parts of the application user experience are tied to our capacity to pay for services. App signing and Google Sheets integration are good examples of this.
|
||||||
|
|
||||||
### Cool stuff
|
### Cool stuff
|
||||||
|
|
||||||
We may choose not to implement some features. This could be related to the complexity required, to the skill set, or to the fact that we just feel it doesn't improve the app for a significant percentage of users. In those cases, a feature may be considered "not worth it," regardless of its pertinence.
|
We may choose not to implement some features. This could be related to the complexity required, to the skill set, or to the fact that we just feel it doesn't improve the app for a significant percentage of users. In those cases, a feature may be considered "not worth it," regardless of its pertinence.
|
||||||
|
|
||||||
As a user, you can choose to sponsor a particular feature or donate to the app so that we can create bounties for features.
|
As a user, you can choose to sponsor a particular feature or donate to the app so that we can create bounties for features.
|
||||||
|
|
||||||
### Priorities
|
### Priorities
|
||||||
|
|
||||||
Monetary contributions are most immediately needed to offset the costs of presenting and distributing Ontime, i.e., hosting, domains, and certificates.
|
Monetary contributions are most immediately needed to offset the costs of presenting and distributing Ontime, i.e., hosting, domains, and certificates.
|
||||||
|
|
||||||
Additional funding will be considered according to the needs at the time.
|
Additional funding will be considered according to the needs at the time.
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
{
|
|
||||||
"parserOptions": {
|
|
||||||
"sourceType": "module"
|
|
||||||
},
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"node": true
|
|
||||||
},
|
|
||||||
"extends": [
|
|
||||||
"eslint:recommended"
|
|
||||||
],
|
|
||||||
"plugins": [],
|
|
||||||
"rules": {
|
|
||||||
"@typescript-eslint/no-var-requires": "off"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../../node_modules/oxlint/configuration_schema.json",
|
||||||
|
"extends": ["../../.oxlintrc.json"],
|
||||||
|
"plugins": ["unicorn", "typescript", "oxc", "vitest"]
|
||||||
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"endOfLine": "lf",
|
|
||||||
"trailingComma": "all",
|
|
||||||
"tabWidth": 2,
|
|
||||||
"semi": true,
|
|
||||||
"singleQuote": true,
|
|
||||||
"jsxSingleQuote": true,
|
|
||||||
"printWidth": 120
|
|
||||||
}
|
|
||||||
+9
-1
@@ -1,6 +1,7 @@
|
|||||||
# Ontime CLI
|
# Ontime CLI
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
> Ontime is an application for managing rundown and event timers.
|
> Ontime is an application for managing rundown and event timers.
|
||||||
|
|
||||||
Congratulations! You got this far into Ontime's rabbit hole and want to manage your installation.
|
Congratulations! You got this far into Ontime's rabbit hole and want to manage your installation.
|
||||||
@@ -10,33 +11,40 @@ The CLI tool is a fast and lightweight way of installing Ontime and is perfect f
|
|||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
You will need to have installed the correct version of node.js
|
You will need to have installed the correct version of node.js
|
||||||
Please check [the app engines declaration](https://github.com/cpvalente/ontime/blob/master/package.json) for the correct version.
|
Please check [the app engines declaration](https://github.com/cpvalente/ontime/blob/master/package.json) for the correct version.
|
||||||
|
|
||||||
### Running Ontime
|
### Running Ontime
|
||||||
|
|
||||||
To get Ontime running, all you need to do is install it in your system using your package manager of choice:
|
To get Ontime running, all you need to do is install it in your system using your package manager of choice:
|
||||||
|
|
||||||
Install globally in your system
|
Install globally in your system
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install -g @getontime/cli
|
npm install -g @getontime/cli
|
||||||
```
|
```
|
||||||
|
|
||||||
... and run using the installed script
|
... and run using the installed script
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ontime
|
ontime
|
||||||
```
|
```
|
||||||
|
|
||||||
Or install and run ontime (the installation here is temporary for the duration of the session)
|
Or install and run ontime (the installation here is temporary for the duration of the session)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx install @getontime/cli
|
npx install @getontime/cli
|
||||||
```
|
```
|
||||||
|
|
||||||
## Links
|
## Links
|
||||||
|
|
||||||
- [Ontime's repository](https://github.com/cpvalente/ontime)
|
- [Ontime's repository](https://github.com/cpvalente/ontime)
|
||||||
- [Ontime's documentation](https://docs.getontime.no/)
|
- [Ontime's documentation](https://docs.getontime.no/)
|
||||||
- [Ontime's website](https://getontime.no/)
|
- [Ontime's website](https://getontime.no/)
|
||||||
|
|
||||||
## Sponsoring
|
## Sponsoring
|
||||||
|
|
||||||
You can help the development of this project or say thank you with a one time donation. \
|
You can help the development of this project or say thank you with a one time donation. \
|
||||||
See the [terms of donations](https://github.com/cpvalente/ontime/blob/master/SPONSOR.md)
|
See the [terms of donations](https://github.com/cpvalente/ontime/blob/master/SPONSOR.md)
|
||||||
|
|
||||||
|
|||||||
+21
-26
@@ -1,27 +1,22 @@
|
|||||||
{
|
{
|
||||||
"name": "@getontime/cli",
|
"name": "@getontime/cli",
|
||||||
"version": "4.0.0-beta.5",
|
"version": "4.5.0",
|
||||||
"author": "Carlos Valente",
|
"author": "Carlos Valente",
|
||||||
"description": "Time keeping for live events",
|
"description": "Time keeping for live events",
|
||||||
"repository": "https://github.com/cpvalente/ontime",
|
"repository": "https://github.com/cpvalente/ontime",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"lighdev",
|
"lighdev",
|
||||||
"ontime",
|
"ontime",
|
||||||
"timer"
|
"timer"
|
||||||
],
|
],
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
"ontime": "main.js"
|
"ontime": "main.js"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"client",
|
"client",
|
||||||
"external",
|
"external",
|
||||||
"server"
|
"server"
|
||||||
],
|
]
|
||||||
"devDependencies": {
|
}
|
||||||
"eslint": "catalog:",
|
|
||||||
"eslint-config-prettier": "catalog:",
|
|
||||||
"prettier": "catalog:"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
node_modules
|
|
||||||
build
|
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
{
|
|
||||||
"settings": {
|
|
||||||
"react": {
|
|
||||||
"version": "detect"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"node": true
|
|
||||||
},
|
|
||||||
"extends": [
|
|
||||||
"plugin:react/recommended",
|
|
||||||
"plugin:react-hooks/recommended",
|
|
||||||
"plugin:@tanstack/eslint-plugin-query/recommended"
|
|
||||||
],
|
|
||||||
"plugins": [
|
|
||||||
"react",
|
|
||||||
"react-compiler",
|
|
||||||
"simple-import-sort",
|
|
||||||
"@tanstack/query"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"react-compiler/react-compiler": "warn",
|
|
||||||
"react/jsx-no-bind": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"ignoreRefs": true,
|
|
||||||
"allowArrowFunctions": true,
|
|
||||||
"allowFunctions": false,
|
|
||||||
"allowBind": false,
|
|
||||||
"ignoreDOMComponents": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"react/jsx-boolean-value": [
|
|
||||||
"error",
|
|
||||||
"never",
|
|
||||||
{
|
|
||||||
"always": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"react/jsx-handler-names": [
|
|
||||||
"off",
|
|
||||||
{
|
|
||||||
"eventHandlerPrefix": "handle",
|
|
||||||
"eventHandlerPropPrefix": "on"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"react/no-danger": "warn",
|
|
||||||
"react/no-deprecated": [
|
|
||||||
"error"
|
|
||||||
],
|
|
||||||
"react/jsx-uses-react": "off",
|
|
||||||
"react/react-in-jsx-scope": "off",
|
|
||||||
"react/jsx-no-undef": "error",
|
|
||||||
"react/jsx-closing-tag-location": "warn",
|
|
||||||
"react/jsx-no-useless-fragment": "warn",
|
|
||||||
"react/no-unescaped-entities": "error",
|
|
||||||
"react/jsx-tag-spacing": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"closingSlash": "never",
|
|
||||||
"beforeSelfClosing": "always",
|
|
||||||
"afterOpening": "never",
|
|
||||||
"beforeClosing": "never"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"react/jsx-space-before-closing": [
|
|
||||||
"off",
|
|
||||||
"always"
|
|
||||||
],
|
|
||||||
"react/jsx-curly-brace-presence": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"props": "never",
|
|
||||||
"children": "never"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"react/destructuring-assignment": [
|
|
||||||
"error",
|
|
||||||
"always"
|
|
||||||
],
|
|
||||||
"react/no-unsafe": "off",
|
|
||||||
"react/prop-types": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"ignore": [],
|
|
||||||
"customValidators": [],
|
|
||||||
"skipUndeclared": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"simple-import-sort/imports": "warn",
|
|
||||||
"simple-import-sort/exports": "warn"
|
|
||||||
},
|
|
||||||
"overrides": [
|
|
||||||
// override "simple-import-sort" config
|
|
||||||
{
|
|
||||||
"files": [
|
|
||||||
"*.js",
|
|
||||||
"*.jsx",
|
|
||||||
"*.ts",
|
|
||||||
"*.tsx"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"simple-import-sort/imports": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"groups": [
|
|
||||||
// Packages `react` related packages come first.
|
|
||||||
[
|
|
||||||
"^react",
|
|
||||||
"^@?\\w"
|
|
||||||
],
|
|
||||||
// Internal packages.
|
|
||||||
[
|
|
||||||
"^(@|components)(/.*|$)"
|
|
||||||
],
|
|
||||||
// Side effect imports.
|
|
||||||
[
|
|
||||||
"^\\u0000"
|
|
||||||
],
|
|
||||||
// Parent imports. Put `..` last.
|
|
||||||
[
|
|
||||||
"^\\.\\.(?!/?$)",
|
|
||||||
"^\\.\\./?$"
|
|
||||||
],
|
|
||||||
// Other relative imports. Put same-folder imports and `.` last.
|
|
||||||
[
|
|
||||||
"^\\./(?=.*/)(?!/?$)",
|
|
||||||
"^\\.(?!/?$)",
|
|
||||||
"^\\./?$"
|
|
||||||
],
|
|
||||||
// Style imports.
|
|
||||||
[
|
|
||||||
"^.+\\.?(css)$"
|
|
||||||
]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../../node_modules/oxlint/configuration_schema.json",
|
||||||
|
"extends": ["../../.oxlintrc.json"],
|
||||||
|
"plugins": ["unicorn", "typescript", "oxc", "react", "vitest"]
|
||||||
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"endOfLine": "lf",
|
|
||||||
"trailingComma": "all",
|
|
||||||
"tabWidth": 2,
|
|
||||||
"semi": true,
|
|
||||||
"singleQuote": true,
|
|
||||||
"jsxSingleQuote": true,
|
|
||||||
"printWidth": 120
|
|
||||||
}
|
|
||||||
+11
-23
@@ -1,15 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "ontime-ui",
|
"name": "ontime-ui",
|
||||||
"version": "4.0.0-beta.5",
|
"version": "4.5.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@base-ui-components/react": "1.0.0-beta.4",
|
"@base-ui/react": "1.0.0",
|
||||||
"@dnd-kit/core": "^6.3.1",
|
"@dnd-kit/core": "^6.3.1",
|
||||||
"@dnd-kit/sortable": "^10.0.0",
|
"@dnd-kit/sortable": "^10.0.0",
|
||||||
"@dnd-kit/utilities": "^3.2.2",
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
"@fontsource/open-sans": "^5.2.6",
|
"@fontsource/open-sans": "^5.2.6",
|
||||||
"@mantine/hooks": "^8.2.8",
|
"@mantine/hooks": "^8.3.7",
|
||||||
"@sentry/react": "^10.2.0",
|
"@sentry/react": "^10.2.0",
|
||||||
"@table-nav/react": "^0.0.7",
|
"@table-nav/react": "^0.0.7",
|
||||||
"@tanstack/react-query": "^5.85.9",
|
"@tanstack/react-query": "^5.85.9",
|
||||||
@@ -17,29 +17,28 @@
|
|||||||
"@tanstack/react-table": "^8.21.3",
|
"@tanstack/react-table": "^8.21.3",
|
||||||
"autosize": "^6.0.1",
|
"autosize": "^6.0.1",
|
||||||
"axios": "^1.12.2",
|
"axios": "^1.12.2",
|
||||||
"babel-plugin-react-compiler": "19.1.0-rc.3",
|
|
||||||
"csv-stringify": "^6.6.0",
|
"csv-stringify": "^6.6.0",
|
||||||
"prismjs": "^1.30.0",
|
"prismjs": "^1.30.0",
|
||||||
"react": "^19.1.1",
|
"react": "^19.2.3",
|
||||||
"react-colorful": "^5.6.1",
|
"react-colorful": "^5.6.1",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.2.3",
|
||||||
"react-fast-compare": "^3.2.2",
|
"react-fast-compare": "^3.2.2",
|
||||||
"react-hook-form": "^7.62.0",
|
"react-hook-form": "^7.62.0",
|
||||||
"react-icons": "5.5.0",
|
"react-icons": "5.5.0",
|
||||||
"react-qr-code": "^2.0.18",
|
"react-qr-code": "^2.0.18",
|
||||||
"react-router": "^7.8.2",
|
"react-router": "^7.11.0",
|
||||||
"react-simple-code-editor": "^0.14.1",
|
"react-simple-code-editor": "^0.14.1",
|
||||||
"react-virtuoso": "^4.14.0",
|
"react-virtuoso": "^4.17.0",
|
||||||
"web-vitals": "^5.1.0",
|
"web-vitals": "^5.1.0",
|
||||||
"zustand": "^5.0.8"
|
"zustand": "^5.0.9"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"addversion": "node -p \"'export const ONTIME_VERSION = ' + JSON.stringify(require('../../package.json').version) + ';'\" > src/ONTIME_VERSION.js",
|
"addversion": "node -p \"'export const ONTIME_VERSION = ' + JSON.stringify(require('../../package.json').version) + ';'\" > src/ONTIME_VERSION.js",
|
||||||
"postinstall": "pnpm addversion",
|
"postinstall": "pnpm addversion",
|
||||||
"dev": "cross-env BROWSER=none vite",
|
"dev": "cross-env BROWSER=none vite",
|
||||||
"dev:electron": "pnpm dev",
|
"dev:electron": "pnpm dev",
|
||||||
"lint": "eslint . --quiet",
|
"lint": "oxlint --quiet --type-aware",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc -p tsconfig.app.json --noEmit",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
"test:pipeline": "vitest run",
|
"test:pipeline": "vitest run",
|
||||||
@@ -59,24 +58,13 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sentry/vite-plugin": "^2.16.1",
|
"@sentry/vite-plugin": "^2.16.1",
|
||||||
"@tanstack/eslint-plugin-query": "^5.8.4",
|
|
||||||
"@types/prismjs": "^1.26.5",
|
"@types/prismjs": "^1.26.5",
|
||||||
"@types/react": "^19.1.12",
|
"@types/react": "^19.1.12",
|
||||||
"@types/react-dom": "^19.1.9",
|
"@types/react-dom": "^19.1.9",
|
||||||
"@typescript-eslint/eslint-plugin": "catalog:",
|
|
||||||
"@typescript-eslint/parser": "catalog:",
|
|
||||||
"@vitejs/plugin-react": "4.5.1",
|
"@vitejs/plugin-react": "4.5.1",
|
||||||
"eslint": "catalog:",
|
"babel-plugin-react-compiler": "1.0.0",
|
||||||
"eslint-config-prettier": "catalog:",
|
|
||||||
"eslint-plugin-jest": "^28.6.0",
|
|
||||||
"eslint-plugin-prettier": "catalog:",
|
|
||||||
"eslint-plugin-react": "^7.32.0",
|
|
||||||
"eslint-plugin-react-compiler": "19.1.0-rc.2",
|
|
||||||
"eslint-plugin-react-hooks": "^5.2.0",
|
|
||||||
"eslint-plugin-simple-import-sort": "^8.0.0",
|
|
||||||
"ontime-types": "workspace:*",
|
"ontime-types": "workspace:*",
|
||||||
"ontime-utils": "workspace:*",
|
"ontime-utils": "workspace:*",
|
||||||
"prettier": "catalog:",
|
|
||||||
"sass": "^1.57.1",
|
"sass": "^1.57.1",
|
||||||
"typescript": "catalog:",
|
"typescript": "catalog:",
|
||||||
"vite": "6.3.1",
|
"vite": "6.3.1",
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
"short_name": "",
|
"short_name": "",
|
||||||
"icons": [
|
"icons": [{ "src": "/ontime-logo.png", "sizes": "295x295", "type": "image/png" }],
|
||||||
{ "src": "/ontime-logo.png", "sizes": "295x295", "type": "image/png" }
|
|
||||||
],
|
|
||||||
"theme_color": "#2B5ABC",
|
"theme_color": "#2B5ABC",
|
||||||
"background_color": "#101010",
|
"background_color": "#101010",
|
||||||
"display": "standalone"
|
"display": "standalone"
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
import { BrowserRouter } from 'react-router';
|
import { Tooltip } from '@base-ui/react/tooltip';
|
||||||
import { Tooltip } from '@base-ui-components/react/tooltip';
|
|
||||||
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 { BrowserRouter } from 'react-router';
|
||||||
|
|
||||||
|
// apply global axios config defaults
|
||||||
|
import './common/api/axios.config';
|
||||||
|
import AppRouter from './AppRouter';
|
||||||
import ErrorBoundary from './common/components/error-boundary/ErrorBoundary';
|
import ErrorBoundary from './common/components/error-boundary/ErrorBoundary';
|
||||||
import IdentifyOverlay from './common/components/identify-overlay/IdentifyOverlay';
|
import IdentifyOverlay from './common/components/identify-overlay/IdentifyOverlay';
|
||||||
import { AppContextProvider } from './common/context/AppContext';
|
import { AppContextProvider } from './common/context/AppContext';
|
||||||
import { ontimeQueryClient } from './common/queryClient';
|
import { ontimeQueryClient } from './common/queryClient';
|
||||||
import { connectSocket } from './common/utils/socket';
|
import { connectSocket } from './common/utils/socket';
|
||||||
|
import { baseURI } from './externals';
|
||||||
import KeepAwake from './features/keep-awake/KeepAwake';
|
import KeepAwake from './features/keep-awake/KeepAwake';
|
||||||
import { TranslationProvider } from './translation/TranslationProvider';
|
import { TranslationProvider } from './translation/TranslationProvider';
|
||||||
import AppRouter from './AppRouter';
|
|
||||||
import { baseURI } from './externals';
|
|
||||||
|
|
||||||
connectSocket();
|
connectSocket();
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import { ComponentType, lazy, Suspense, useEffect, useMemo } from 'react';
|
|
||||||
import { Navigate, Route, useLocation, useNavigate, useParams } from 'react-router';
|
|
||||||
import { OntimeView, OntimeViewPresettable, URLPreset } from 'ontime-types';
|
import { OntimeView, OntimeViewPresettable, URLPreset } from 'ontime-types';
|
||||||
|
import { ComponentType, Suspense, lazy, useEffect, useMemo } from 'react';
|
||||||
|
import { Navigate, Route, useLocation, useNavigate, useParams } from 'react-router';
|
||||||
|
|
||||||
import ViewNavigationMenu from './common/components/navigation-menu/ViewNavigationMenu';
|
import ViewNavigationMenu from './common/components/navigation-menu/ViewNavigationMenu';
|
||||||
import { PresetContext } from './common/context/PresetContext';
|
import { PresetContext } from './common/context/PresetContext';
|
||||||
import { useClientPath } from './common/hooks/useClientPath';
|
|
||||||
import useUrlPresets from './common/hooks-query/useUrlPresets';
|
import useUrlPresets from './common/hooks-query/useUrlPresets';
|
||||||
|
import { useClientPath } from './common/hooks/useClientPath';
|
||||||
import { getRouteFromPreset } from './common/utils/urlPresets';
|
import { getRouteFromPreset } from './common/utils/urlPresets';
|
||||||
|
import { getIsNavigationLocked, sessionScope } from './externals';
|
||||||
import Log from './features/log/Log';
|
import Log from './features/log/Log';
|
||||||
|
import { initializeSentry } from './sentry.config';
|
||||||
import Loader from './views/common/loader/Loader';
|
import Loader from './views/common/loader/Loader';
|
||||||
import NotFound from './views/common/not-found/NotFound';
|
import NotFound from './views/common/not-found/NotFound';
|
||||||
import ViewLoader from './views/ViewLoader';
|
import ViewLoader from './views/ViewLoader';
|
||||||
import { getIsNavigationLocked, sessionScope } from './externals';
|
|
||||||
import { initializeSentry } from './sentry.config';
|
|
||||||
|
|
||||||
const Timer = lazy(() => import('./views/timer/Timer'));
|
const Timer = lazy(() => import('./views/timer/Timer'));
|
||||||
const Countdown = lazy(() => import('./views/countdown/Countdown'));
|
const Countdown = lazy(() => import('./views/countdown/Countdown'));
|
||||||
@@ -102,6 +102,7 @@ export default function AppRouter() {
|
|||||||
path='op'
|
path='op'
|
||||||
element={
|
element={
|
||||||
<ViewLoader>
|
<ViewLoader>
|
||||||
|
<ViewNavigationMenu isNavigationLocked={getIsNavigationLocked()} />
|
||||||
<Operator />
|
<Operator />
|
||||||
</ViewLoader>
|
</ViewLoader>
|
||||||
}
|
}
|
||||||
@@ -207,13 +208,13 @@ function PresetView() {
|
|||||||
/**
|
/**
|
||||||
* Locked presets do not allow configuration changes
|
* Locked presets do not allow configuration changes
|
||||||
* Whether the user can navigate is determined by the locked param
|
* Whether the user can navigate is determined by the locked param
|
||||||
|
*
|
||||||
|
* We inject the preset to the context value for the view to consume
|
||||||
*/
|
*/
|
||||||
const Component = PresetViewMap[preset.target as OntimeViewPresettable];
|
const Component = PresetViewMap[preset.target as OntimeViewPresettable];
|
||||||
return (
|
return (
|
||||||
<PresetContext value={preset}>
|
<PresetContext value={preset}>
|
||||||
{preset.target !== OntimeView.Cuesheet && (
|
<ViewNavigationMenu isNavigationLocked={getIsNavigationLocked()} suppressSettings />
|
||||||
<ViewNavigationMenu isNavigationLocked={getIsNavigationLocked()} suppressSettings />
|
|
||||||
)}
|
|
||||||
{Component ? <Component /> : <NotFound />}
|
{Component ? <Component /> : <NotFound />}
|
||||||
</PresetContext>
|
</PresetContext>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,16 +2,16 @@ import axios from 'axios';
|
|||||||
import { TranslationObject } from 'ontime-types';
|
import { TranslationObject } from 'ontime-types';
|
||||||
|
|
||||||
import { ontimeQueryClient } from '../../common/queryClient';
|
import { ontimeQueryClient } from '../../common/queryClient';
|
||||||
|
import { TRANSLATION, apiEntryUrl, customTranslationsURL } from './constants';
|
||||||
import { apiEntryUrl, customTranslationsURL, TRANSLATION } from './constants';
|
import type { RequestOptions } from './requestOptions';
|
||||||
|
|
||||||
const assetsPath = `${apiEntryUrl}/assets`;
|
const assetsPath = `${apiEntryUrl}/assets`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP request to get css contents
|
* HTTP request to get css contents
|
||||||
*/
|
*/
|
||||||
export async function getCSSContents(): Promise<string> {
|
export async function getCSSContents(options?: RequestOptions): Promise<string> {
|
||||||
const res = await axios.get(`${assetsPath}/css`);
|
const res = await axios.get(`${assetsPath}/css`, { signal: options?.signal });
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,8 +35,8 @@ export async function restoreCSSContents(): Promise<string> {
|
|||||||
/**
|
/**
|
||||||
* HTTP request to get user translation
|
* HTTP request to get user translation
|
||||||
*/
|
*/
|
||||||
export async function getUserTranslation(): Promise<TranslationObject> {
|
export async function getUserTranslation(options?: RequestOptions): Promise<TranslationObject> {
|
||||||
const res = await axios.get(customTranslationsURL);
|
const res = await axios.get(customTranslationsURL, { signal: options?.signal });
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,14 +9,15 @@ import type {
|
|||||||
} from 'ontime-types';
|
} from 'ontime-types';
|
||||||
|
|
||||||
import { apiEntryUrl } from './constants';
|
import { apiEntryUrl } from './constants';
|
||||||
|
import type { RequestOptions } from './requestOptions';
|
||||||
|
|
||||||
const automationsPath = `${apiEntryUrl}/automations`;
|
const automationsPath = `${apiEntryUrl}/automations`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP request to get the automations settings
|
* HTTP request to get the automations settings
|
||||||
*/
|
*/
|
||||||
export async function getAutomationSettings(): Promise<AutomationSettings> {
|
export async function getAutomationSettings(options?: RequestOptions): Promise<AutomationSettings> {
|
||||||
const res = await axios.get(automationsPath);
|
const res = await axios.get(automationsPath, { signal: options?.signal });
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
|
import { axiosConfig } from './requestTimeouts';
|
||||||
|
|
||||||
axios.defaults.validateStatus = (status) => {
|
axios.defaults.validateStatus = (status) => {
|
||||||
return (status >= 200 && status < 300) || status === 304;
|
return status >= 200 && status < 300;
|
||||||
};
|
};
|
||||||
|
axios.defaults.timeout = axiosConfig.shortTimeout;
|
||||||
|
|||||||
@@ -2,14 +2,15 @@ import axios from 'axios';
|
|||||||
import { CustomField, CustomFieldKey, CustomFields } from 'ontime-types';
|
import { CustomField, CustomFieldKey, CustomFields } from 'ontime-types';
|
||||||
|
|
||||||
import { apiEntryUrl } from './constants';
|
import { apiEntryUrl } from './constants';
|
||||||
|
import type { RequestOptions } from './requestOptions';
|
||||||
|
|
||||||
const customFieldsPath = `${apiEntryUrl}/custom-fields`;
|
const customFieldsPath = `${apiEntryUrl}/custom-fields`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests list of known custom fields
|
* Requests list of known custom fields
|
||||||
*/
|
*/
|
||||||
export async function getCustomFields(): Promise<CustomFields> {
|
export async function getCustomFields(options?: RequestOptions): Promise<CustomFields> {
|
||||||
const res = await axios.get(customFieldsPath);
|
const res = await axios.get(customFieldsPath, { signal: options?.signal });
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import axios, { AxiosResponse } from 'axios';
|
|||||||
import { DatabaseModel, MessageResponse, ProjectData, ProjectFileListResponse, QuickStartData } from 'ontime-types';
|
import { DatabaseModel, MessageResponse, ProjectData, ProjectFileListResponse, QuickStartData } from 'ontime-types';
|
||||||
|
|
||||||
import { apiEntryUrl } from './constants';
|
import { apiEntryUrl } from './constants';
|
||||||
|
import type { RequestOptions } from './requestOptions';
|
||||||
|
import { axiosConfig } from './requestTimeouts';
|
||||||
import { createBlob, downloadBlob } from './utils';
|
import { createBlob, downloadBlob } from './utils';
|
||||||
|
|
||||||
const dbPath = `${apiEntryUrl}/db`;
|
const dbPath = `${apiEntryUrl}/db`;
|
||||||
@@ -10,7 +12,7 @@ const dbPath = `${apiEntryUrl}/db`;
|
|||||||
* HTTP request to the current DB
|
* HTTP request to the current DB
|
||||||
*/
|
*/
|
||||||
export function getDb(filename: string): Promise<AxiosResponse<DatabaseModel>> {
|
export function getDb(filename: string): Promise<AxiosResponse<DatabaseModel>> {
|
||||||
return axios.post(`${dbPath}/download`, { filename });
|
return axios.post(`${dbPath}/download`, { filename }, { timeout: axiosConfig.longTimeout });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -37,6 +39,7 @@ export async function uploadProjectFile(file: File): Promise<MessageResponse> {
|
|||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('project', file);
|
formData.append('project', file);
|
||||||
const response = await axios.post(`${dbPath}/upload`, formData, {
|
const response = await axios.post(`${dbPath}/upload`, formData, {
|
||||||
|
timeout: axiosConfig.longTimeout,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'multipart/form-data',
|
'Content-Type': 'multipart/form-data',
|
||||||
},
|
},
|
||||||
@@ -76,8 +79,11 @@ export async function quickProject(data: QuickStartData): Promise<MessageRespons
|
|||||||
/**
|
/**
|
||||||
* HTTP request to get the list of available project files
|
* HTTP request to get the list of available project files
|
||||||
*/
|
*/
|
||||||
export async function getProjects(): Promise<ProjectFileListResponse> {
|
export async function getProjects(options?: RequestOptions): Promise<ProjectFileListResponse> {
|
||||||
const res = await axios.get(`${dbPath}/all`);
|
const res = await axios.get(`${dbPath}/all`, {
|
||||||
|
signal: options?.signal,
|
||||||
|
timeout: options?.timeout,
|
||||||
|
});
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import axios, { AxiosResponse } from 'axios';
|
import axios, { AxiosResponse } from 'axios';
|
||||||
import { CustomFields, Rundown } from 'ontime-types';
|
import { CustomFields, Rundown, RundownSummary } from 'ontime-types';
|
||||||
import { ImportMap } from 'ontime-utils';
|
import { ImportMap } from 'ontime-utils';
|
||||||
|
|
||||||
import { apiEntryUrl } from './constants';
|
import { apiEntryUrl } from './constants';
|
||||||
|
import type { RequestOptions } from './requestOptions';
|
||||||
|
import { axiosConfig } from './requestTimeouts';
|
||||||
|
import { downloadBlob } from './utils';
|
||||||
|
|
||||||
const excelPath = `${apiEntryUrl}/excel`;
|
const excelPath = `${apiEntryUrl}/excel`;
|
||||||
|
|
||||||
@@ -10,32 +13,54 @@ const excelPath = `${apiEntryUrl}/excel`;
|
|||||||
* upload Excel file to server
|
* upload Excel file to server
|
||||||
* @return string - file ID op the uploaded file
|
* @return string - file ID op the uploaded file
|
||||||
*/
|
*/
|
||||||
export async function upload(file: File) {
|
export async function upload(file: File, requestOptions?: RequestOptions): Promise<string[]> {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('excel', file);
|
formData.append('excel', file);
|
||||||
await axios.post(`${excelPath}/upload`, formData, {
|
const response = await axios.post(`${excelPath}/upload`, formData, {
|
||||||
|
signal: requestOptions?.signal,
|
||||||
|
timeout: requestOptions?.timeout ?? axiosConfig.longTimeout,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'multipart/form-data',
|
'Content-Type': 'multipart/form-data',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get Worksheet names
|
|
||||||
* @return string[] - array of available worksheets
|
|
||||||
*/
|
|
||||||
export async function getWorksheetNames(): Promise<string[]> {
|
|
||||||
const response: AxiosResponse<string[]> = await axios.get(`${excelPath}/worksheets`);
|
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
type PreviewSpreadsheetResponse = {
|
type PreviewSpreadsheetResponse = {
|
||||||
rundown: Rundown;
|
rundown: Rundown;
|
||||||
customFields: CustomFields;
|
customFields: CustomFields;
|
||||||
|
summary: RundownSummary;
|
||||||
};
|
};
|
||||||
export async function importRundownPreview(options: ImportMap): Promise<PreviewSpreadsheetResponse> {
|
export async function importRundownPreview(
|
||||||
const response: AxiosResponse<PreviewSpreadsheetResponse> = await axios.post(`${excelPath}/preview`, {
|
options: ImportMap,
|
||||||
options,
|
requestOptions?: RequestOptions,
|
||||||
});
|
): Promise<PreviewSpreadsheetResponse> {
|
||||||
|
const response: AxiosResponse<PreviewSpreadsheetResponse> = await axios.post(
|
||||||
|
`${excelPath}/preview`,
|
||||||
|
{
|
||||||
|
options,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
signal: requestOptions?.signal,
|
||||||
|
timeout: requestOptions?.timeout ?? axiosConfig.longTimeout,
|
||||||
|
},
|
||||||
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Downloads a xlsx representation of the rundown from the server
|
||||||
|
*/
|
||||||
|
export async function downloadAsExcel(rundownId: string, fileName?: string, requestOptions?: RequestOptions) {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`${excelPath}/${rundownId}/export`, {
|
||||||
|
signal: requestOptions?.signal,
|
||||||
|
timeout: requestOptions?.timeout ?? axiosConfig.longTimeout,
|
||||||
|
responseType: 'blob',
|
||||||
|
});
|
||||||
|
|
||||||
|
downloadBlob(response.data, `${fileName ?? 'Ontime_rundown'}.xlsx`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error downloading file:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
import { apiRepoLatest } from '../../externals';
|
import { apiRepoLatest } from '../../externals';
|
||||||
|
import type { RequestOptions } from './requestOptions';
|
||||||
|
|
||||||
export type HasUpdate = {
|
export type HasUpdate = {
|
||||||
url: string;
|
url: string;
|
||||||
@@ -10,8 +11,8 @@ export type HasUpdate = {
|
|||||||
/**
|
/**
|
||||||
* HTTP request to get the latest version and url from github
|
* HTTP request to get the latest version and url from github
|
||||||
*/
|
*/
|
||||||
export async function getLatestVersion(): Promise<HasUpdate> {
|
export async function getLatestVersion(options?: RequestOptions): Promise<HasUpdate> {
|
||||||
const res = await axios.get(apiRepoLatest);
|
const res = await axios.get(apiRepoLatest, { signal: options?.signal });
|
||||||
return {
|
return {
|
||||||
url: res.data.html_url as string,
|
url: res.data.html_url as string,
|
||||||
version: res.data.tag_name as string,
|
version: res.data.tag_name as string,
|
||||||
|
|||||||
@@ -2,14 +2,19 @@ import axios, { AxiosResponse } from 'axios';
|
|||||||
import { ProjectData, ProjectLogoResponse } from 'ontime-types';
|
import { ProjectData, ProjectLogoResponse } from 'ontime-types';
|
||||||
|
|
||||||
import { apiEntryUrl } from './constants';
|
import { apiEntryUrl } from './constants';
|
||||||
|
import type { RequestOptions } from './requestOptions';
|
||||||
|
import { axiosConfig } from './requestTimeouts';
|
||||||
|
|
||||||
const projectPath = `${apiEntryUrl}/project`;
|
const projectPath = `${apiEntryUrl}/project`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP request to fetch project data
|
* HTTP request to fetch project data
|
||||||
*/
|
*/
|
||||||
export async function getProjectData(): Promise<ProjectData> {
|
export async function getProjectData(options?: RequestOptions): Promise<ProjectData> {
|
||||||
const res = await axios.get(projectPath);
|
const res = await axios.get(projectPath, {
|
||||||
|
signal: options?.signal,
|
||||||
|
timeout: options?.timeout,
|
||||||
|
});
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,6 +32,7 @@ export async function uploadProjectLogo(file: File): Promise<AxiosResponse<Proje
|
|||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('image', file);
|
formData.append('image', file);
|
||||||
const response = await axios.post(`${projectPath}/upload`, formData, {
|
const response = await axios.post(`${projectPath}/upload`, formData, {
|
||||||
|
timeout: axiosConfig.longTimeout,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'multipart/form-data',
|
'Content-Type': 'multipart/form-data',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,16 +2,16 @@ import axios from 'axios';
|
|||||||
import { OntimeReport } from 'ontime-types';
|
import { OntimeReport } from 'ontime-types';
|
||||||
|
|
||||||
import { ontimeQueryClient } from '../../common/queryClient';
|
import { ontimeQueryClient } from '../../common/queryClient';
|
||||||
|
import { REPORT, apiEntryUrl } from './constants';
|
||||||
import { apiEntryUrl, REPORT } from './constants';
|
import type { RequestOptions } from './requestOptions';
|
||||||
|
|
||||||
export const reportUrl = `${apiEntryUrl}/report`;
|
export const reportUrl = `${apiEntryUrl}/report`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP request to fetch all reports
|
* HTTP request to fetch all reports
|
||||||
*/
|
*/
|
||||||
export async function fetchReport(): Promise<OntimeReport> {
|
export async function fetchReport(options?: RequestOptions): Promise<OntimeReport> {
|
||||||
const res = await axios.get(reportUrl);
|
const res = await axios.get(reportUrl, { signal: options?.signal });
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export type RequestOptions = {
|
||||||
|
signal?: AbortSignal;
|
||||||
|
timeout?: number;
|
||||||
|
};
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* Keep a short global timeout for regular API requests, but allow
|
||||||
|
* longer windows for file transfer and heavy import/export operations.
|
||||||
|
*/
|
||||||
|
export const axiosConfig = {
|
||||||
|
shortTimeout: 20 * 1000, // 20 seconds
|
||||||
|
longTimeout: 3 * 60 * 1000, // 3 minutes
|
||||||
|
} as const;
|
||||||
@@ -2,7 +2,9 @@ import axios, { AxiosResponse } from 'axios';
|
|||||||
import { EntryId, OntimeEntry, OntimeEvent, ProjectRundownsList, Rundown, TransientEventPayload } from 'ontime-types';
|
import { EntryId, OntimeEntry, OntimeEvent, ProjectRundownsList, Rundown, TransientEventPayload } from 'ontime-types';
|
||||||
|
|
||||||
import { apiEntryUrl } from './constants';
|
import { apiEntryUrl } from './constants';
|
||||||
|
import type { RequestOptions } from './requestOptions';
|
||||||
|
|
||||||
|
type RundownId = string;
|
||||||
const rundownPath = `${apiEntryUrl}/rundowns`;
|
const rundownPath = `${apiEntryUrl}/rundowns`;
|
||||||
|
|
||||||
// #region operations on project rundowns =========================
|
// #region operations on project rundowns =========================
|
||||||
@@ -10,24 +12,40 @@ const rundownPath = `${apiEntryUrl}/rundowns`;
|
|||||||
/**
|
/**
|
||||||
* HTTP request to fetch a list of existing rundowns
|
* HTTP request to fetch a list of existing rundowns
|
||||||
*/
|
*/
|
||||||
export async function fetchProjectRundownList(): Promise<ProjectRundownsList> {
|
export async function fetchProjectRundownList(options?: RequestOptions): Promise<ProjectRundownsList> {
|
||||||
const res = await axios.get(rundownPath);
|
const res = await axios.get(rundownPath, { signal: options?.signal });
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP request to fetch all entries in the currently loaded rundown
|
* HTTP request to fetch all entries in the currently loaded rundown
|
||||||
*/
|
*/
|
||||||
export async function fetchCurrentRundown(): Promise<Rundown> {
|
export async function fetchCurrentRundown(options?: RequestOptions): Promise<Rundown> {
|
||||||
const res = await axios.get(`${rundownPath}/current`);
|
const res = await axios.get(`${rundownPath}/current`, { signal: options?.signal });
|
||||||
|
if (!isValidRundown(res.data)) {
|
||||||
|
throw new Error('Invalid rundown payload');
|
||||||
|
}
|
||||||
return res.data;
|
return res.data;
|
||||||
|
|
||||||
|
function isValidRundown(x: any): x is Rundown {
|
||||||
|
return (
|
||||||
|
x &&
|
||||||
|
typeof x === 'object' &&
|
||||||
|
typeof x.id === 'string' &&
|
||||||
|
Array.isArray(x.order) &&
|
||||||
|
Array.isArray(x.flatOrder) &&
|
||||||
|
x.entries &&
|
||||||
|
typeof x.entries === 'object' &&
|
||||||
|
typeof x.revision === 'number'
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP request to switch the currently loaded rundown
|
* HTTP request to switch the currently loaded rundown
|
||||||
*/
|
*/
|
||||||
export async function loadRundown(id: string): Promise<AxiosResponse<ProjectRundownsList>> {
|
export async function loadRundown(rundownId: RundownId): Promise<AxiosResponse<ProjectRundownsList>> {
|
||||||
return axios.post(`${rundownPath}/${id}/load`);
|
return axios.post(`${rundownPath}/${rundownId}/load`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -37,11 +55,25 @@ export async function createRundown(title: string): Promise<AxiosResponse<Projec
|
|||||||
return axios.post(rundownPath, { title });
|
return axios.post(rundownPath, { title });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP request to duplicate an existing rundown
|
||||||
|
*/
|
||||||
|
export async function duplicateRundown(rundownId: RundownId): Promise<AxiosResponse<ProjectRundownsList>> {
|
||||||
|
return axios.post(`${rundownPath}/${rundownId}/duplicate`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP request to rename an existing rundown
|
||||||
|
*/
|
||||||
|
export async function renameRundown(rundownId: RundownId, title: string): Promise<AxiosResponse<ProjectRundownsList>> {
|
||||||
|
return axios.patch(`${rundownPath}/${rundownId}`, { title });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP request to delete a rundown
|
* HTTP request to delete a rundown
|
||||||
*/
|
*/
|
||||||
export async function deleteRundown(id: string): Promise<AxiosResponse<ProjectRundownsList>> {
|
export async function deleteRundown(rundownId: RundownId): Promise<AxiosResponse<ProjectRundownsList>> {
|
||||||
return axios.delete(`${rundownPath}/${id}`);
|
return axios.delete(`${rundownPath}/${rundownId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// #endregion operations on project rundowns ======================
|
// #endregion operations on project rundowns ======================
|
||||||
@@ -51,7 +83,7 @@ export async function deleteRundown(id: string): Promise<AxiosResponse<ProjectRu
|
|||||||
* HTTP request to post new entry
|
* HTTP request to post new entry
|
||||||
*/
|
*/
|
||||||
export async function postAddEntry(
|
export async function postAddEntry(
|
||||||
rundownId: string,
|
rundownId: RundownId,
|
||||||
data: TransientEventPayload,
|
data: TransientEventPayload,
|
||||||
): Promise<AxiosResponse<OntimeEntry>> {
|
): Promise<AxiosResponse<OntimeEntry>> {
|
||||||
return axios.post(`${rundownPath}/${rundownId}/entry`, data);
|
return axios.post(`${rundownPath}/${rundownId}/entry`, data);
|
||||||
@@ -60,7 +92,10 @@ export async function postAddEntry(
|
|||||||
/**
|
/**
|
||||||
* HTTP request to edit an entry
|
* HTTP request to edit an entry
|
||||||
*/
|
*/
|
||||||
export async function putEditEntry(rundownId: string, data: Partial<OntimeEntry>): Promise<AxiosResponse<OntimeEntry>> {
|
export async function putEditEntry(
|
||||||
|
rundownId: RundownId,
|
||||||
|
data: Partial<OntimeEntry>,
|
||||||
|
): Promise<AxiosResponse<OntimeEntry>> {
|
||||||
return axios.put(`${rundownPath}/${rundownId}/entry`, data);
|
return axios.put(`${rundownPath}/${rundownId}/entry`, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,7 +107,7 @@ export type BatchEditEntry = {
|
|||||||
/**
|
/**
|
||||||
* HTTP request to edit multiple events
|
* HTTP request to edit multiple events
|
||||||
*/
|
*/
|
||||||
export async function putBatchEditEvents(rundownId: string, data: BatchEditEntry): Promise<AxiosResponse<Rundown>> {
|
export async function putBatchEditEvents(rundownId: RundownId, data: BatchEditEntry): Promise<AxiosResponse<Rundown>> {
|
||||||
return axios.put(`${rundownPath}/${rundownId}/batch`, data);
|
return axios.put(`${rundownPath}/${rundownId}/batch`, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,56 +120,64 @@ export type ReorderEntry = {
|
|||||||
/**
|
/**
|
||||||
* HTTP request to reorder an entry
|
* HTTP request to reorder an entry
|
||||||
*/
|
*/
|
||||||
export async function patchReorderEntry(rundownId: string, data: ReorderEntry): Promise<AxiosResponse<Rundown>> {
|
export async function patchReorderEntry(rundownId: RundownId, data: ReorderEntry): Promise<AxiosResponse<Rundown>> {
|
||||||
return axios.patch(`${rundownPath}/${rundownId}/reorder`, data);
|
return axios.patch(`${rundownPath}/${rundownId}/reorder`, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP request to swap two events
|
* HTTP request to swap two events
|
||||||
*/
|
*/
|
||||||
export async function requestEventSwap(rundownId: string, from: EntryId, to: EntryId): Promise<AxiosResponse<Rundown>> {
|
export async function requestEventSwap(
|
||||||
|
rundownId: RundownId,
|
||||||
|
from: EntryId,
|
||||||
|
to: EntryId,
|
||||||
|
): Promise<AxiosResponse<Rundown>> {
|
||||||
return axios.patch(`${rundownPath}/${rundownId}/swap`, { from, to });
|
return axios.patch(`${rundownPath}/${rundownId}/swap`, { from, to });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP request to request application of delay
|
* HTTP request to request application of delay
|
||||||
*/
|
*/
|
||||||
export async function requestApplyDelay(rundownId: string, delayId: EntryId): Promise<AxiosResponse<Rundown>> {
|
export async function requestApplyDelay(rundownId: RundownId, delayId: EntryId): Promise<AxiosResponse<Rundown>> {
|
||||||
return axios.patch(`${rundownPath}/${rundownId}/applydelay/${delayId}`);
|
return axios.patch(`${rundownPath}/${rundownId}/applydelay/${delayId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP request for cloning an entry
|
* HTTP request for cloning an entry
|
||||||
*/
|
*/
|
||||||
export async function postCloneEntry(rundownId: string, entryId: EntryId): Promise<AxiosResponse<Rundown>> {
|
export async function postCloneEntry(
|
||||||
return axios.post(`${rundownPath}/${rundownId}/clone/${entryId}`);
|
rundownId: RundownId,
|
||||||
|
entryId: EntryId,
|
||||||
|
options?: { before?: EntryId; after?: EntryId },
|
||||||
|
): Promise<AxiosResponse<Rundown>> {
|
||||||
|
return axios.post(`${rundownPath}/${rundownId}/clone/${entryId}`, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP request for grouping a list of entries into a group
|
* HTTP request for grouping a list of entries into a group
|
||||||
*/
|
*/
|
||||||
export async function requestGroupEntries(rundownId: string, entryIds: EntryId[]): Promise<AxiosResponse<Rundown>> {
|
export async function requestGroupEntries(rundownId: RundownId, entryIds: EntryId[]): Promise<AxiosResponse<Rundown>> {
|
||||||
return axios.post(`${rundownPath}/${rundownId}/group`, { ids: entryIds });
|
return axios.post(`${rundownPath}/${rundownId}/group`, { ids: entryIds });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP request for dissolving of a group
|
* HTTP request for dissolving of a group
|
||||||
*/
|
*/
|
||||||
export async function requestUngroup(rundownId: string, groupId: EntryId): Promise<AxiosResponse<Rundown>> {
|
export async function requestUngroup(rundownId: RundownId, groupId: EntryId): Promise<AxiosResponse<Rundown>> {
|
||||||
return axios.post(`${rundownPath}/${rundownId}/ungroup/${groupId}`);
|
return axios.post(`${rundownPath}/${rundownId}/ungroup/${groupId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP request to delete entries of a given rundown
|
* HTTP request to delete entries of a given rundown
|
||||||
*/
|
*/
|
||||||
export async function deleteEntries(rundownId: string, entryIds: EntryId[]): Promise<AxiosResponse<Rundown>> {
|
export async function deleteEntries(rundownId: RundownId, entryIds: EntryId[]): Promise<AxiosResponse<Rundown>> {
|
||||||
return axios.delete(`${rundownPath}/${rundownId}/entries`, { data: { ids: entryIds } });
|
return axios.delete(`${rundownPath}/${rundownId}/entries`, { data: { ids: entryIds } });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP request to delete all entries of a given rundown
|
* HTTP request to delete all entries of a given rundown
|
||||||
*/
|
*/
|
||||||
export async function requestDeleteAll(rundownId: string): Promise<AxiosResponse<Rundown>> {
|
export async function requestDeleteAll(rundownId: RundownId): Promise<AxiosResponse<Rundown>> {
|
||||||
return axios.delete(`${rundownPath}/${rundownId}/all`);
|
return axios.delete(`${rundownPath}/${rundownId}/all`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,15 @@ import axios from 'axios';
|
|||||||
import { GetInfo, LinkOptions } from 'ontime-types';
|
import { GetInfo, LinkOptions } from 'ontime-types';
|
||||||
|
|
||||||
import { apiEntryUrl } from './constants';
|
import { apiEntryUrl } from './constants';
|
||||||
|
import type { RequestOptions } from './requestOptions';
|
||||||
|
|
||||||
const sessionPath = `${apiEntryUrl}/session`;
|
const sessionPath = `${apiEntryUrl}/session`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP request to retrieve application info
|
* HTTP request to retrieve application info
|
||||||
*/
|
*/
|
||||||
export async function getInfo(): Promise<GetInfo> {
|
export async function getInfo(options?: RequestOptions): Promise<GetInfo> {
|
||||||
const res = await axios.get(`${sessionPath}/info`);
|
const res = await axios.get(`${sessionPath}/info`, { signal: options?.signal });
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import axios, { AxiosResponse } from 'axios';
|
import axios, { AxiosResponse } from 'axios';
|
||||||
import { Settings } from 'ontime-types';
|
import { PortInfo, Settings } from 'ontime-types';
|
||||||
|
|
||||||
import { apiEntryUrl } from './constants';
|
import { apiEntryUrl } from './constants';
|
||||||
|
import type { RequestOptions } from './requestOptions';
|
||||||
|
|
||||||
const settingsPath = `${apiEntryUrl}/settings`;
|
const settingsPath = `${apiEntryUrl}/settings`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP request to retrieve application settings
|
* HTTP request to retrieve application settings
|
||||||
*/
|
*/
|
||||||
export async function getSettings(): Promise<Settings> {
|
export async function getSettings(options?: RequestOptions): Promise<Settings> {
|
||||||
const res = await axios.get(settingsPath);
|
const res = await axios.get(settingsPath, { signal: options?.signal });
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,3 +27,18 @@ export async function postSettings(data: Settings): Promise<AxiosResponse<Settin
|
|||||||
export async function postShowWelcomeDialog(show: boolean) {
|
export async function postShowWelcomeDialog(show: boolean) {
|
||||||
axios.post(`${settingsPath}/welcomedialog`, { show });
|
axios.post(`${settingsPath}/welcomedialog`, { show });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP request to retrieve server port
|
||||||
|
*/
|
||||||
|
export async function getServerPort(): Promise<PortInfo> {
|
||||||
|
const res = await axios.get(`${settingsPath}/serverport`);
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP request to set server port
|
||||||
|
*/
|
||||||
|
export async function postServerPort(serverPort: number): Promise<AxiosResponse<PortInfo>> {
|
||||||
|
return axios.post(`${settingsPath}/serverport`, { serverPort });
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import axios, { AxiosResponse } from 'axios';
|
import axios, { AxiosResponse } from 'axios';
|
||||||
import { AuthenticationStatus, CustomFields, Rundown } from 'ontime-types';
|
import { AuthenticationStatus, CustomFields, Rundown, RundownSummary } from 'ontime-types';
|
||||||
import { ImportMap } from 'ontime-utils';
|
import { ImportMap } from 'ontime-utils';
|
||||||
|
|
||||||
import { apiEntryUrl } from './constants';
|
import { apiEntryUrl } from './constants';
|
||||||
|
import type { RequestOptions } from './requestOptions';
|
||||||
|
import { axiosConfig } from './requestTimeouts';
|
||||||
|
|
||||||
const sheetsPath = `${apiEntryUrl}/sheets`;
|
const sheetsPath = `${apiEntryUrl}/sheets`;
|
||||||
|
|
||||||
@@ -23,6 +25,7 @@ export const verifyAuthenticationStatus = async (): Promise<{
|
|||||||
export const requestConnection = async (
|
export const requestConnection = async (
|
||||||
file: File,
|
file: File,
|
||||||
sheetId: string,
|
sheetId: string,
|
||||||
|
requestOptions?: RequestOptions,
|
||||||
): Promise<{
|
): Promise<{
|
||||||
verification_url: string;
|
verification_url: string;
|
||||||
user_code: string;
|
user_code: string;
|
||||||
@@ -31,6 +34,8 @@ export const requestConnection = async (
|
|||||||
formData.append('client_secret', file);
|
formData.append('client_secret', file);
|
||||||
|
|
||||||
const response = await axios.post(`${sheetsPath}/${sheetId}/connect`, formData, {
|
const response = await axios.post(`${sheetsPath}/${sheetId}/connect`, formData, {
|
||||||
|
signal: requestOptions?.signal,
|
||||||
|
timeout: requestOptions?.timeout ?? axiosConfig.longTimeout,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'multipart/form-data',
|
'Content-Type': 'multipart/form-data',
|
||||||
},
|
},
|
||||||
@@ -53,23 +58,46 @@ export const revokeAuthentication = async (): Promise<{ authenticated: Authentic
|
|||||||
export const previewRundown = async (
|
export const previewRundown = async (
|
||||||
sheetId: string,
|
sheetId: string,
|
||||||
options: ImportMap,
|
options: ImportMap,
|
||||||
|
requestOptions?: RequestOptions,
|
||||||
): Promise<{
|
): Promise<{
|
||||||
rundown: Rundown;
|
rundown: Rundown;
|
||||||
customFields: CustomFields;
|
customFields: CustomFields;
|
||||||
|
summary: RundownSummary;
|
||||||
}> => {
|
}> => {
|
||||||
const response = await axios.post(`${sheetsPath}/${sheetId}/read`, { options });
|
const response = await axios.post(
|
||||||
|
`${sheetsPath}/${sheetId}/read`,
|
||||||
|
{ options },
|
||||||
|
{
|
||||||
|
signal: requestOptions?.signal,
|
||||||
|
timeout: requestOptions?.timeout ?? axiosConfig.longTimeout,
|
||||||
|
},
|
||||||
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getWorksheetNames = async (sheetId: string): Promise<string[]> => {
|
export const getWorksheetNames = async (sheetId: string, requestOptions?: RequestOptions): Promise<string[]> => {
|
||||||
const response: AxiosResponse<string[]> = await axios.post(`${sheetsPath}/${sheetId}/worksheets`);
|
const response: AxiosResponse<string[]> = await axios.post(`${sheetsPath}/${sheetId}/worksheets`, undefined, {
|
||||||
|
signal: requestOptions?.signal,
|
||||||
|
timeout: requestOptions?.timeout ?? axiosConfig.longTimeout,
|
||||||
|
});
|
||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP request to upload the rundown to a google sheet
|
* HTTP request to upload the rundown to a google sheet
|
||||||
*/
|
*/
|
||||||
export const uploadRundown = async (sheetId: string, options: ImportMap): Promise<void> => {
|
export const uploadRundown = async (
|
||||||
const response = await axios.post(`${sheetsPath}/${sheetId}/write`, { options });
|
sheetId: string,
|
||||||
|
options: ImportMap,
|
||||||
|
requestOptions?: RequestOptions,
|
||||||
|
): Promise<void> => {
|
||||||
|
const response = await axios.post(
|
||||||
|
`${sheetsPath}/${sheetId}/write`,
|
||||||
|
{ options },
|
||||||
|
{
|
||||||
|
signal: requestOptions?.signal,
|
||||||
|
timeout: requestOptions?.timeout ?? axiosConfig.longTimeout,
|
||||||
|
},
|
||||||
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,14 +2,15 @@ import axios from 'axios';
|
|||||||
import { URLPreset } from 'ontime-types';
|
import { URLPreset } from 'ontime-types';
|
||||||
|
|
||||||
import { apiEntryUrl } from './constants';
|
import { apiEntryUrl } from './constants';
|
||||||
|
import type { RequestOptions } from './requestOptions';
|
||||||
|
|
||||||
const urlPresetsPath = `${apiEntryUrl}/url-presets`;
|
const urlPresetsPath = `${apiEntryUrl}/url-presets`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP request to retrieve all presets
|
* HTTP request to retrieve all presets
|
||||||
*/
|
*/
|
||||||
export async function getUrlPresets(): Promise<URLPreset[]> {
|
export async function getUrlPresets(options?: RequestOptions): Promise<URLPreset[]> {
|
||||||
const res = await axios.get(urlPresetsPath);
|
const res = await axios.get(urlPresetsPath, { signal: options?.signal });
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import axios from 'axios';
|
|||||||
import type { ViewSettings } from 'ontime-types';
|
import type { ViewSettings } from 'ontime-types';
|
||||||
|
|
||||||
import { apiEntryUrl } from './constants';
|
import { apiEntryUrl } from './constants';
|
||||||
|
import type { RequestOptions } from './requestOptions';
|
||||||
|
|
||||||
const viewSettingsPath = `${apiEntryUrl}/view-settings`;
|
const viewSettingsPath = `${apiEntryUrl}/view-settings`;
|
||||||
|
|
||||||
@@ -9,8 +10,8 @@ const viewSettingsPath = `${apiEntryUrl}/view-settings`;
|
|||||||
* HTTP request to fetch view settings
|
* HTTP request to fetch view settings
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export async function getViewSettings(): Promise<ViewSettings> {
|
export async function getViewSettings(options?: RequestOptions): Promise<ViewSettings> {
|
||||||
const res = await axios.get(viewSettingsPath);
|
const res = await axios.get(viewSettingsPath, { signal: options?.signal });
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,140 +0,0 @@
|
|||||||
.subtle {
|
|
||||||
background: $gray-1050;
|
|
||||||
color: $blue-400;
|
|
||||||
line-height: 1em;
|
|
||||||
|
|
||||||
&:hover:not(:disabled):not(:active) {
|
|
||||||
background: $gray-1000;
|
|
||||||
color: $blue-500;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active:not(:disabled) {
|
|
||||||
background: $gray-1100;
|
|
||||||
border-color: $gray-1250;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.subtle-white {
|
|
||||||
background: $gray-1050;
|
|
||||||
color: $ui-white;
|
|
||||||
|
|
||||||
&:hover:not(:disabled):not(:active) {
|
|
||||||
background: $gray-1000;
|
|
||||||
color: $ui-white;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active:not(:disabled) {
|
|
||||||
background: $gray-1100;
|
|
||||||
border-color: $gray-1250;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
background: $gray-1050;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.primary {
|
|
||||||
background: $blue-700;
|
|
||||||
color: $ui-white;
|
|
||||||
|
|
||||||
&:hover:not(:disabled):not(:active) {
|
|
||||||
background: $blue-600;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active:not(:disabled) {
|
|
||||||
background: $blue-800;
|
|
||||||
border-color: $blue-900;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
opacity: $viewer-opacity-disabled;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.destructive {
|
|
||||||
background: $red-700;
|
|
||||||
color: $ui-white;
|
|
||||||
|
|
||||||
&:hover:not(:disabled):not(:active) {
|
|
||||||
background: $red-600;
|
|
||||||
color: $ui-white;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active:not(:disabled) {
|
|
||||||
background: $red-800;
|
|
||||||
border-color: $red-900;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.subtle-destructive {
|
|
||||||
background: $gray-1050;
|
|
||||||
color: $red-400;
|
|
||||||
|
|
||||||
&:hover:not(:disabled):not(:active) {
|
|
||||||
background: $gray-1000;
|
|
||||||
color: $red-500;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active:not(:disabled) {
|
|
||||||
background: $gray-1100;
|
|
||||||
border-color: $gray-1250;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ghosted {
|
|
||||||
background: transparent;
|
|
||||||
color: $blue-500;
|
|
||||||
|
|
||||||
&:hover:not(:disabled):not(:active) {
|
|
||||||
background: $gray-1000;
|
|
||||||
color: $blue-500;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active:not(:disabled) {
|
|
||||||
background: $gray-1100;
|
|
||||||
border-color: $gray-1250;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
opacity: $opacity-disabled;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ghosted-white {
|
|
||||||
background: transparent;
|
|
||||||
color: $ui-white;
|
|
||||||
|
|
||||||
&:hover:not(:disabled):not(:active) {
|
|
||||||
background: $gray-1000;
|
|
||||||
color: $ui-white;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active:not(:disabled) {
|
|
||||||
background: $gray-1100;
|
|
||||||
border-color: $gray-1250;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
opacity: $opacity-disabled;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.ghosted-destructive {
|
|
||||||
background: transparent;
|
|
||||||
color: $red-500;
|
|
||||||
|
|
||||||
&:hover:not(:disabled):not(:active) {
|
|
||||||
background: $gray-1000;
|
|
||||||
color: $red-500;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active:not(:disabled) {
|
|
||||||
background: $gray-1100;
|
|
||||||
border-color: $gray-1250;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
opacity: $opacity-disabled;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
@use '@/theme/viewerDefs' as *;
|
@use './buttonVariants' as base;
|
||||||
@import './BaseButtonStyles.module.scss';
|
|
||||||
|
@include base.button-variants;
|
||||||
|
|
||||||
.baseButton {
|
.baseButton {
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -81,4 +82,3 @@
|
|||||||
.fluid {
|
.fluid {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
@use '@/theme/viewerDefs' as *;
|
@use './buttonVariants' as base;
|
||||||
@import './BaseButtonStyles.module.scss';
|
|
||||||
|
@include base.button-variants;
|
||||||
|
|
||||||
.baseIconButton {
|
.baseIconButton {
|
||||||
aspect-ratio: 1;
|
aspect-ratio: 1;
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
@use '@/theme/themeTokens' as *;
|
||||||
|
@use '@/theme/viewerDefs' as *;
|
||||||
|
|
||||||
|
@mixin button-variants {
|
||||||
|
.subtle {
|
||||||
|
background: $gray-1050;
|
||||||
|
color: $blue-400;
|
||||||
|
line-height: 1em;
|
||||||
|
|
||||||
|
&:hover:not(:disabled):not(:active) {
|
||||||
|
background: $gray-1000;
|
||||||
|
color: $blue-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active:not(:disabled) {
|
||||||
|
background: $gray-1100;
|
||||||
|
border-color: $gray-1250;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtle-white {
|
||||||
|
background: $gray-1050;
|
||||||
|
color: $ui-white;
|
||||||
|
|
||||||
|
&:hover:not(:disabled):not(:active) {
|
||||||
|
background: $gray-1000;
|
||||||
|
color: $ui-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active:not(:disabled) {
|
||||||
|
background: $gray-1100;
|
||||||
|
border-color: $gray-1250;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
background: $gray-1050;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary {
|
||||||
|
background: $blue-700;
|
||||||
|
color: $ui-white;
|
||||||
|
|
||||||
|
&:hover:not(:disabled):not(:active) {
|
||||||
|
background: $blue-600;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active:not(:disabled) {
|
||||||
|
background: $blue-800;
|
||||||
|
border-color: $blue-900;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: $viewer-opacity-disabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.destructive {
|
||||||
|
background: $red-700;
|
||||||
|
color: $ui-white;
|
||||||
|
|
||||||
|
&:hover:not(:disabled):not(:active) {
|
||||||
|
background: $red-600;
|
||||||
|
color: $ui-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active:not(:disabled) {
|
||||||
|
background: $red-800;
|
||||||
|
border-color: $red-900;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtle-destructive {
|
||||||
|
background: $gray-1050;
|
||||||
|
color: $red-400;
|
||||||
|
|
||||||
|
&:hover:not(:disabled):not(:active) {
|
||||||
|
background: $gray-1000;
|
||||||
|
color: $red-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active:not(:disabled) {
|
||||||
|
background: $gray-1100;
|
||||||
|
border-color: $gray-1250;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ghosted {
|
||||||
|
background: transparent;
|
||||||
|
color: $blue-500;
|
||||||
|
|
||||||
|
&:hover:not(:disabled):not(:active) {
|
||||||
|
background: $gray-1000;
|
||||||
|
color: $blue-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active:not(:disabled) {
|
||||||
|
background: $gray-1100;
|
||||||
|
border-color: $gray-1250;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: $opacity-disabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ghosted-white {
|
||||||
|
background: transparent;
|
||||||
|
color: $ui-white;
|
||||||
|
|
||||||
|
&:hover:not(:disabled):not(:active) {
|
||||||
|
background: $gray-1000;
|
||||||
|
color: $ui-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active:not(:disabled) {
|
||||||
|
background: $gray-1100;
|
||||||
|
border-color: $gray-1250;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: $opacity-disabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ghosted-destructive {
|
||||||
|
background: transparent;
|
||||||
|
color: $red-500;
|
||||||
|
|
||||||
|
&:hover:not(:disabled):not(:active) {
|
||||||
|
background: $gray-1000;
|
||||||
|
color: $red-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active:not(:disabled) {
|
||||||
|
background: $gray-1100;
|
||||||
|
border-color: $gray-1250;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: $opacity-disabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import { Checkbox as BaseCheckbox } from '@base-ui/react/checkbox';
|
||||||
import { IoCheckmark } from 'react-icons/io5';
|
import { IoCheckmark } from 'react-icons/io5';
|
||||||
import { Checkbox as BaseCheckbox } from '@base-ui-components/react/checkbox';
|
|
||||||
|
|
||||||
import style from './Checkbox.module.scss';
|
import style from './Checkbox.module.scss';
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import { useState } from 'react';
|
|||||||
import { IoArrowForward } from 'react-icons/io5';
|
import { IoArrowForward } from 'react-icons/io5';
|
||||||
|
|
||||||
import { navigatorConstants } from '../../../viewerConfig';
|
import { navigatorConstants } from '../../../viewerConfig';
|
||||||
import { setClientRemote } from '../../hooks/useSocket';
|
|
||||||
import useUrlPresets from '../../hooks-query/useUrlPresets';
|
import useUrlPresets from '../../hooks-query/useUrlPresets';
|
||||||
|
import { setClientRemote } from '../../hooks/useSocket';
|
||||||
import Button from '../buttons/Button';
|
import Button from '../buttons/Button';
|
||||||
|
import Dialog from '../dialog/Dialog';
|
||||||
import Info from '../info/Info';
|
import Info from '../info/Info';
|
||||||
import Input from '../input/input/Input';
|
import Input from '../input/input/Input';
|
||||||
import AppLink from '../link/app-link/AppLink';
|
import AppLink from '../link/app-link/AppLink';
|
||||||
import Modal from '../modal/Modal';
|
|
||||||
import Select from '../select/Select';
|
import Select from '../select/Select';
|
||||||
|
|
||||||
import style from './RedirectClientModal.module.scss';
|
import style from './RedirectClientModal.module.scss';
|
||||||
@@ -57,7 +57,7 @@ export function RedirectClientModal({ id, isOpen, name, currentPath, origin, onC
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Dialog
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
showCloseButton
|
showCloseButton
|
||||||
@@ -98,7 +98,10 @@ export function RedirectClientModal({ id, isOpen, name, currentPath, origin, onC
|
|||||||
fluid
|
fluid
|
||||||
options={viewOptions}
|
options={viewOptions}
|
||||||
defaultValue={viewOptions[0].value}
|
defaultValue={viewOptions[0].value}
|
||||||
onValueChange={(value) => setSelected(value)}
|
onValueChange={(value) => {
|
||||||
|
if (value === null) return;
|
||||||
|
setSelected(value);
|
||||||
|
}}
|
||||||
disabled={enabledPresets.length === 0}
|
disabled={enabledPresets.length === 0}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { PropsWithChildren, useRef, useState } from 'react';
|
import { PropsWithChildren, useRef, useState } from 'react';
|
||||||
import { IoCheckmark } from 'react-icons/io5';
|
import { IoCheckmark, IoCopy } from 'react-icons/io5';
|
||||||
import { IoCopy } from 'react-icons/io5';
|
|
||||||
|
|
||||||
import copyToClipboard from '../../utils/copyToClipboard';
|
import { copyToClipboard } from '../../utils/copyToClipboard';
|
||||||
import { cx } from '../../utils/styleUtils';
|
import { cx } from '../../utils/styleUtils';
|
||||||
import Button from '../buttons/Button';
|
import Button from '../buttons/Button';
|
||||||
import IconButton from '../buttons/IconButton';
|
import IconButton from '../buttons/IconButton';
|
||||||
@@ -26,15 +25,19 @@ export default function CopyTag({
|
|||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = async () => {
|
||||||
copyToClipboard(copyValue);
|
try {
|
||||||
setCopied(true);
|
await copyToClipboard(copyValue);
|
||||||
|
setCopied(true);
|
||||||
|
|
||||||
// reset copied state
|
// reset copied state
|
||||||
if (timeoutRef.current) {
|
if (timeoutRef.current) {
|
||||||
clearTimeout(timeoutRef.current);
|
clearTimeout(timeoutRef.current);
|
||||||
|
}
|
||||||
|
timeoutRef.current = setTimeout(() => setCopied(false), 2000);
|
||||||
|
} catch {
|
||||||
|
// ignore errors
|
||||||
}
|
}
|
||||||
timeoutRef.current = setTimeout(() => setCopied(false), 2000);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
|
|
||||||
padding-inline: 1rem;
|
padding-inline: 1rem;
|
||||||
min-width: min(420px, 90vw);
|
min-width: min(600px, 90vw);
|
||||||
|
|
||||||
background-color: $gray-1250;
|
background-color: $gray-1250;
|
||||||
color: $ui-white;
|
color: $ui-white;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import { Dialog as BaseDialog } from '@base-ui/react/dialog';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { IoClose } from 'react-icons/io5';
|
import { IoClose } from 'react-icons/io5';
|
||||||
import { Dialog as BaseDialog } from '@base-ui-components/react/dialog';
|
|
||||||
|
|
||||||
import IconButton from '../buttons/IconButton';
|
import IconButton from '../buttons/IconButton';
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ interface DialogProps {
|
|||||||
showCloseButton?: boolean;
|
showCloseButton?: boolean;
|
||||||
showBackdrop?: boolean;
|
showBackdrop?: boolean;
|
||||||
bodyElements: ReactNode;
|
bodyElements: ReactNode;
|
||||||
footerElements: ReactNode;
|
footerElements?: ReactNode;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,16 +27,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
outline: 0;
|
outline: 0;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
padding-block: 0.5rem;
|
padding-block: 0.5rem;
|
||||||
padding-inline: 1rem 2rem;
|
padding-inline: 1rem;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.5rem;
|
justify-content: space-between;
|
||||||
line-height: 1em;
|
align-items: flex-start;
|
||||||
|
gap: 1rem;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
color: $gray-500;
|
color: $gray-500;
|
||||||
@@ -61,6 +61,42 @@
|
|||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
background-color: $gray-1000;
|
background-color: $gray-1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&[data-type='destructive'] {
|
||||||
|
color: $red-500;
|
||||||
|
svg {
|
||||||
|
color: $red-500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item .iconStrong {
|
||||||
|
color: $ui-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
color: $gray-400;
|
||||||
|
font-size: calc(1rem - 2px);
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shortcut {
|
||||||
|
color: $gray-500;
|
||||||
|
font-size: calc(1rem - 4px);
|
||||||
|
letter-spacing: 0.02em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.separator {
|
.separator {
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
|
import { Menu as BaseMenu } from '@base-ui/react/menu';
|
||||||
import { PropsWithChildren } from 'react';
|
import { PropsWithChildren } from 'react';
|
||||||
import { IconType } from 'react-icons';
|
import { IconType } from 'react-icons';
|
||||||
import { Menu as BaseMenu } from '@base-ui-components/react/menu';
|
|
||||||
|
|
||||||
import style from './DropdownMenu.module.scss';
|
import style from './DropdownMenu.module.scss';
|
||||||
|
|
||||||
type DropdownMenuItemDivider = { type: 'divider' };
|
type DropdownMenuItemDivider = { type: 'divider' };
|
||||||
type DropdownMenuItem = {
|
type DropdownMenuItem = {
|
||||||
type: 'item';
|
type: 'item' | 'destructive';
|
||||||
label: string;
|
label: string;
|
||||||
|
description?: string;
|
||||||
icon?: IconType;
|
icon?: IconType;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
|
shortcut?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DropdownMenuOption = DropdownMenuItemDivider | DropdownMenuItem;
|
export type DropdownMenuOption = DropdownMenuItemDivider | DropdownMenuItem;
|
||||||
@@ -31,9 +33,21 @@ export function DropdownMenu({ items, children, ...triggerProps }: PropsWithChil
|
|||||||
return <BaseMenu.Separator key={index} className={style.separator} />;
|
return <BaseMenu.Separator key={index} className={style.separator} />;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<BaseMenu.Item key={index} className={style.item} onClick={item.onClick} disabled={item.disabled}>
|
<BaseMenu.Item
|
||||||
{item.icon && <item.icon />}
|
key={index}
|
||||||
{item.label}
|
className={style.item}
|
||||||
|
onClick={item.onClick}
|
||||||
|
disabled={item.disabled}
|
||||||
|
data-type={item.type}
|
||||||
|
>
|
||||||
|
<span className={style.content}>
|
||||||
|
<span className={style.label}>
|
||||||
|
{item.icon && <item.icon />}
|
||||||
|
{item.label}
|
||||||
|
</span>
|
||||||
|
{item.description && <span className={style.description}>{item.description}</span>}
|
||||||
|
</span>
|
||||||
|
{item.shortcut && <span className={style.shortcut}>{item.shortcut}</span>}
|
||||||
</BaseMenu.Item>
|
</BaseMenu.Item>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -69,8 +83,14 @@ export function PositionedDropdownMenu({ items, isOpen, position, onClose }: Pos
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<BaseMenu.Item key={index} className={style.item} onClick={item.onClick} disabled={item.disabled}>
|
<BaseMenu.Item key={index} className={style.item} onClick={item.onClick} disabled={item.disabled}>
|
||||||
{item.icon && <item.icon />}
|
<span className={style.content}>
|
||||||
{item.label}
|
<span className={style.label}>
|
||||||
|
{item.icon && <item.icon />}
|
||||||
|
{item.label}
|
||||||
|
</span>
|
||||||
|
{item.description && <span className={style.description}>{item.description}</span>}
|
||||||
|
</span>
|
||||||
|
{item.shortcut && <span className={style.shortcut}>{item.shortcut}</span>}
|
||||||
</BaseMenu.Item>
|
</BaseMenu.Item>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
.corner {
|
.arrow {
|
||||||
transform: rotate(45deg);
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
.corner {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0.5rem;
|
top: 0.5rem;
|
||||||
right: 0.5rem;
|
right: 0.5rem;
|
||||||
@@ -21,6 +22,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.offsetCorner {
|
||||||
|
right: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
}
|
}
|
||||||
@@ -51,6 +56,13 @@
|
|||||||
|
|
||||||
&.vertical {
|
&.vertical {
|
||||||
width: 1px;
|
width: 1px;
|
||||||
height: 0.75em;
|
height: 0.75em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
position: relative;
|
||||||
|
border-radius: $panel-border-radius;
|
||||||
|
background-color: $bg-container-l2;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +1,33 @@
|
|||||||
import type { HTMLAttributes, LabelHTMLAttributes } from 'react';
|
import type { HTMLAttributes, JSX, LabelHTMLAttributes, MouseEventHandler } from 'react';
|
||||||
import { IconBaseProps } from 'react-icons';
|
import { IconBaseProps } from 'react-icons';
|
||||||
import { IoArrowUp } from 'react-icons/io5';
|
import { IoArrowUp } from 'react-icons/io5';
|
||||||
|
import { TbPictureInPictureOff } from 'react-icons/tb';
|
||||||
|
|
||||||
import { cx } from '../../utils/styleUtils';
|
import { cx } from '../../utils/styleUtils';
|
||||||
|
|
||||||
import style from './EditorUtils.module.scss';
|
import style from './EditorUtils.module.scss';
|
||||||
|
|
||||||
export function Corner({ className, ...elementProps }: IconBaseProps) {
|
export function CornerExtract({ className, ...elementProps }: IconBaseProps) {
|
||||||
return <IoArrowUp className={cx([style.corner, className])} {...elementProps} />;
|
return <IoArrowUp className={cx([style.corner, style.arrow, className])} {...elementProps} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CornerPipButton({ className, ...elementProps }: IconBaseProps) {
|
||||||
|
return <TbPictureInPictureOff className={cx([style.corner, style.offsetCorner, className])} {...elementProps} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExtractAndPip extends IconBaseProps {
|
||||||
|
onExtractClick: MouseEventHandler<SVGElement>;
|
||||||
|
pipElement: JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CornerWithPip({ className, pipElement, onExtractClick }: ExtractAndPip) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<IoArrowUp className={cx([style.corner, style.arrow, className])} onClick={onExtractClick} />
|
||||||
|
{/* the pip element returns the icon button */}
|
||||||
|
{pipElement}
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Title({ children, className, ...elementProps }: HTMLAttributes<HTMLHeadingElement>) {
|
export function Title({ children, className, ...elementProps }: HTMLAttributes<HTMLHeadingElement>) {
|
||||||
@@ -35,3 +55,7 @@ interface SeparatorProps extends HTMLAttributes<HTMLDivElement> {
|
|||||||
export function Separator({ className, orientation = 'vertical', ...elementProps }: SeparatorProps) {
|
export function Separator({ className, orientation = 'vertical', ...elementProps }: SeparatorProps) {
|
||||||
return <div className={cx([style.separator, style[orientation], className])} role='separator' {...elementProps} />;
|
return <div className={cx([style.separator, style[orientation], className])} role='separator' {...elementProps} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function Panel({ className, ...props }: HTMLAttributes<HTMLDivElement>) {
|
||||||
|
return <div className={cx([style.panel, className])} {...props} />;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable react/destructuring-assignment */
|
|
||||||
import React from 'react';
|
|
||||||
// skipcq: JS-C1003 - sentry does not expose itself as an ES Module.
|
// skipcq: JS-C1003 - sentry does not expose itself as an ES Module.
|
||||||
import * as Sentry from '@sentry/react';
|
import * as Sentry from '@sentry/react';
|
||||||
|
/* eslint-disable react/destructuring-assignment */
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
import { hasConnected, reconnectAttempts } from '../../../common/utils/socket';
|
import { hasConnected, reconnectAttempts } from '../../../common/utils/socket';
|
||||||
import { runtimeStore } from '../../stores/runtime';
|
import { runtimeStore } from '../../stores/runtime';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import { MILLIS_PER_MINUTE } from 'ontime-utils';
|
||||||
import { useCallback, useEffect, useRef } from 'react';
|
import { useCallback, useEffect, useRef } from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { MILLIS_PER_MINUTE } from 'ontime-utils';
|
|
||||||
|
|
||||||
import { setClientRemote } from '../../hooks/useSocket';
|
import { setClientRemote } from '../../hooks/useSocket';
|
||||||
import { useClientStore } from '../../stores/clientStore';
|
import { useClientStore } from '../../stores/clientStore';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { RefObject, useEffect } from 'react';
|
|
||||||
// @ts-expect-error no types from library
|
// @ts-expect-error no types from library
|
||||||
import autosize from 'autosize/dist/autosize';
|
import autosize from 'autosize/dist/autosize';
|
||||||
|
import { RefObject, useEffect } from 'react';
|
||||||
|
|
||||||
import Textarea, { type TextareaProps } from '../textarea/Textarea';
|
import Textarea, { type TextareaProps } from '../textarea/Textarea';
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useController, UseControllerProps } from 'react-hook-form';
|
|
||||||
import { IoEyedrop } from 'react-icons/io5';
|
|
||||||
import { useDebouncedCallback } from '@mantine/hooks';
|
import { useDebouncedCallback } from '@mantine/hooks';
|
||||||
import { ViewSettings } from 'ontime-types';
|
import { ViewSettings } from 'ontime-types';
|
||||||
|
import { UseControllerProps, useController } from 'react-hook-form';
|
||||||
|
import { IoEyedrop } from 'react-icons/io5';
|
||||||
|
|
||||||
import { cx, getAccessibleColour } from '../../../utils/styleUtils';
|
import { cx, getAccessibleColour } from '../../../utils/styleUtils';
|
||||||
import PopoverPicker from '../popover-picker/PopoverPicker';
|
import PopoverPicker from '../popover-picker/PopoverPicker';
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
outline: none;
|
outline: none;
|
||||||
|
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
color: $gray-500;
|
color: $gray-600;
|
||||||
letter-spacing: 0;
|
letter-spacing: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { forwardRef, InputHTMLAttributes } from 'react';
|
import { InputHTMLAttributes, forwardRef } from 'react';
|
||||||
|
|
||||||
import { cx } from '../../../utils/styleUtils';
|
import { cx } from '../../../utils/styleUtils';
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import { Popover } from '@base-ui/react/popover';
|
||||||
import { PropsWithChildren } from 'react';
|
import { PropsWithChildren } from 'react';
|
||||||
import { HexAlphaColorPicker, HexColorInput } from 'react-colorful';
|
import { HexAlphaColorPicker, HexColorInput } from 'react-colorful';
|
||||||
import { Popover } from '@base-ui-components/react/popover';
|
|
||||||
|
|
||||||
import PopoverContents from '../../popover/Popover';
|
import PopoverContents from '../../popover/Popover';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import { HotkeyItem, getHotkeyHandler } from '@mantine/hooks';
|
||||||
import { ChangeEvent, KeyboardEvent, RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { ChangeEvent, KeyboardEvent, RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { getHotkeyHandler, HotkeyItem } from '@mantine/hooks';
|
|
||||||
|
|
||||||
interface UseReactiveTextInputReturn {
|
interface UseReactiveTextInputReturn {
|
||||||
value: string;
|
value: string;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { forwardRef, TextareaHTMLAttributes } from 'react';
|
import { TextareaHTMLAttributes, forwardRef } from 'react';
|
||||||
|
|
||||||
import { cx } from '../../../utils/styleUtils';
|
import { cx } from '../../../utils/styleUtils';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { FocusEvent, KeyboardEvent, useCallback, useEffect, useRef, useState } from 'react';
|
|
||||||
import { millisToString, parseUserTime } from 'ontime-utils';
|
import { millisToString, parseUserTime } from 'ontime-utils';
|
||||||
|
import { FocusEvent, KeyboardEvent, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
import { cx } from '../../../utils/styleUtils';
|
import { cx } from '../../../utils/styleUtils';
|
||||||
import Input from '../input/Input';
|
import Input from '../input/Input';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { FocusEvent, KeyboardEvent, useCallback, useEffect, useRef, useState } from 'react';
|
|
||||||
import { millisToString, parseUserTime } from 'ontime-utils';
|
import { millisToString, parseUserTime } from 'ontime-utils';
|
||||||
|
import { FocusEvent, KeyboardEvent, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
import { cx } from '../../../utils/styleUtils';
|
import { cx } from '../../../utils/styleUtils';
|
||||||
import Input from '../input/Input';
|
import Input from '../input/Input';
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
box-shadow: 0 1px 0 0 currentColor;
|
box-shadow: 0 1px 0 0 currentColor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,11 @@
|
|||||||
top: 10vh;
|
top: 10vh;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
|
|
||||||
padding-inline: 1rem;
|
padding-inline: 1rem;
|
||||||
min-width: min(680px, 90vw);
|
min-width: min(880px, 90vw);
|
||||||
min-height: min(200px, 10vh);
|
min-height: min(200px, 10vh);
|
||||||
max-width: min(900px, 90vw);
|
max-width: min(1200px, 90vw);
|
||||||
|
|
||||||
background-color: $gray-1250;
|
background-color: $gray-1250;
|
||||||
color: $ui-white;
|
color: $ui-white;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import { Dialog as BaseDialog } from '@base-ui/react/dialog';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { IoClose } from 'react-icons/io5';
|
import { IoClose } from 'react-icons/io5';
|
||||||
import { Dialog as BaseDialog } from '@base-ui-components/react/dialog';
|
|
||||||
|
|
||||||
import IconButton from '../buttons/IconButton';
|
import IconButton from '../buttons/IconButton';
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ export default function Modal({
|
|||||||
onOpenChange={(isOpen) => {
|
onOpenChange={(isOpen) => {
|
||||||
if (!isOpen) onClose();
|
if (!isOpen) onClose();
|
||||||
}}
|
}}
|
||||||
dismissible={false}
|
disablePointerDismissal
|
||||||
>
|
>
|
||||||
<BaseDialog.Portal>
|
<BaseDialog.Portal>
|
||||||
{showBackdrop && <BaseDialog.Backdrop className={style.backdrop} />}
|
{showBackdrop && <BaseDialog.Backdrop className={style.backdrop} />}
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
|
import { Dialog } from '@base-ui/react/dialog';
|
||||||
|
import { useDisclosure, useFullscreen } from '@mantine/hooks';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { IoClose, IoContract, IoExpand, IoLockClosedOutline, IoSwapVertical } from 'react-icons/io5';
|
import { IoClose, IoContract, IoExpand, IoLockClosedOutline, IoSwapVertical } from 'react-icons/io5';
|
||||||
import { LuCoffee } from 'react-icons/lu';
|
import { LuCoffee } from 'react-icons/lu';
|
||||||
import { useLocation } from 'react-router';
|
import { useLocation } from 'react-router';
|
||||||
import { Dialog } from '@base-ui-components/react/dialog';
|
|
||||||
import { useDisclosure, useFullscreen } from '@mantine/hooks';
|
|
||||||
|
|
||||||
import { isLocalhost } from '../../../externals';
|
import { isLocalhost, supportsFullscreen } from '../../../externals';
|
||||||
import { useKeepAwakeOptions } from '../../../features/keep-awake/KeepAwake';
|
import { canUseWakeLock, useKeepAwakeOptions } from '../../../features/keep-awake/useWakeLock';
|
||||||
import { navigatorConstants } from '../../../viewerConfig';
|
import { navigatorConstants } from '../../../viewerConfig';
|
||||||
|
import { useIsSmallScreen } from '../../hooks/useIsSmallScreen';
|
||||||
import { useClientStore } from '../../stores/clientStore';
|
import { useClientStore } from '../../stores/clientStore';
|
||||||
import { useViewOptionsStore } from '../../stores/viewOptions';
|
import { useViewOptionsStore } from '../../stores/viewOptions';
|
||||||
import IconButton from '../buttons/IconButton';
|
import IconButton from '../buttons/IconButton';
|
||||||
import { RenameClientModal } from '../client-modal/RenameClientModal';
|
import { RenameClientModal } from '../client-modal/RenameClientModal';
|
||||||
|
|
||||||
import ClientLink from './client-link/ClientLink';
|
import ClientLink from './client-link/ClientLink';
|
||||||
import EditorNavigation from './editor-navigation/EditorNavigation';
|
import EditorNavigation from './editor-navigation/EditorNavigation';
|
||||||
import NavigationMenuItem from './navigation-menu-item/NavigationMenuItem';
|
import NavigationMenuItem from './navigation-menu-item/NavigationMenuItem';
|
||||||
@@ -29,6 +29,7 @@ export default memo(NavigationMenu);
|
|||||||
function NavigationMenu({ isOpen, onClose }: NavigationMenuProps) {
|
function NavigationMenu({ isOpen, onClose }: NavigationMenuProps) {
|
||||||
const id = useClientStore((store) => store.id);
|
const id = useClientStore((store) => store.id);
|
||||||
const name = useClientStore((store) => store.name);
|
const name = useClientStore((store) => store.name);
|
||||||
|
const isSmallScreen = useIsSmallScreen();
|
||||||
|
|
||||||
const [isRenameOpen, handlers] = useDisclosure(false);
|
const [isRenameOpen, handlers] = useDisclosure(false);
|
||||||
const { fullscreen, toggle } = useFullscreen();
|
const { fullscreen, toggle } = useFullscreen();
|
||||||
@@ -56,16 +57,18 @@ function NavigationMenu({ isOpen, onClose }: NavigationMenuProps) {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
<div className={style.body}>
|
<div className={style.body}>
|
||||||
<NavigationMenuItem active={fullscreen} onClick={toggle}>
|
{supportsFullscreen && (
|
||||||
Toggle Fullscreen
|
<NavigationMenuItem active={fullscreen} onClick={toggle}>
|
||||||
{fullscreen ? <IoContract /> : <IoExpand />}
|
Toggle Fullscreen
|
||||||
</NavigationMenuItem>
|
{fullscreen ? <IoContract /> : <IoExpand />}
|
||||||
|
</NavigationMenuItem>
|
||||||
|
)}
|
||||||
<NavigationMenuItem active={mirror} onClick={() => toggleMirror()}>
|
<NavigationMenuItem active={mirror} onClick={() => toggleMirror()}>
|
||||||
Flip Screen
|
Flip Screen
|
||||||
<IoSwapVertical />
|
<IoSwapVertical />
|
||||||
{mirror && <span className={style.note}>Active</span>}
|
{mirror && <span className={style.note}>Active</span>}
|
||||||
</NavigationMenuItem>
|
</NavigationMenuItem>
|
||||||
{window.isSecureContext && (
|
{canUseWakeLock && (
|
||||||
<NavigationMenuItem active={keepAwake} onClick={toggleKeepAwake}>
|
<NavigationMenuItem active={keepAwake} onClick={toggleKeepAwake}>
|
||||||
Keep Awake
|
Keep Awake
|
||||||
<LuCoffee />
|
<LuCoffee />
|
||||||
@@ -77,11 +80,15 @@ function NavigationMenu({ isOpen, onClose }: NavigationMenuProps) {
|
|||||||
<hr className={style.separator} />
|
<hr className={style.separator} />
|
||||||
|
|
||||||
<EditorNavigation />
|
<EditorNavigation />
|
||||||
<ClientLink to='cuesheet' current={location.pathname === '/cuesheet'}>
|
<ClientLink
|
||||||
|
to='cuesheet'
|
||||||
|
current={location.pathname === '/cuesheet'}
|
||||||
|
postAction={isSmallScreen ? onClose : undefined}
|
||||||
|
>
|
||||||
<IoLockClosedOutline />
|
<IoLockClosedOutline />
|
||||||
Cuesheet
|
Cuesheet
|
||||||
</ClientLink>
|
</ClientLink>
|
||||||
<ClientLink to='op' current={location.pathname === '/op'}>
|
<ClientLink to='op' current={location.pathname === '/op'} postAction={isSmallScreen ? onClose : undefined}>
|
||||||
<IoLockClosedOutline />
|
<IoLockClosedOutline />
|
||||||
Operator
|
Operator
|
||||||
</ClientLink>
|
</ClientLink>
|
||||||
@@ -89,7 +96,12 @@ function NavigationMenu({ isOpen, onClose }: NavigationMenuProps) {
|
|||||||
<hr className={style.separator} />
|
<hr className={style.separator} />
|
||||||
|
|
||||||
{navigatorConstants.map((route) => (
|
{navigatorConstants.map((route) => (
|
||||||
<ClientLink key={route.url} to={route.url} current={location.pathname === `/${route.url}`}>
|
<ClientLink
|
||||||
|
key={route.url}
|
||||||
|
to={route.url}
|
||||||
|
current={location.pathname === `/${route.url}`}
|
||||||
|
postAction={isSmallScreen ? onClose : undefined}
|
||||||
|
>
|
||||||
{route.label}
|
{route.label}
|
||||||
</ClientLink>
|
</ClientLink>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { memo } from 'react';
|
|
||||||
import { useDisclosure, useHotkeys } from '@mantine/hooks';
|
import { useDisclosure, useHotkeys } from '@mantine/hooks';
|
||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
import { useViewParamsEditorStore } from '../view-params-editor/viewParamsEditor.store';
|
import { useViewParamsEditorStore } from '../view-params-editor/viewParamsEditor.store';
|
||||||
|
|
||||||
import FloatingNavigation from './floating-navigation/FloatingNavigation';
|
import FloatingNavigation from './floating-navigation/FloatingNavigation';
|
||||||
import ViewLockedIcon from './view-locked-icon/ViewLockedIcon';
|
|
||||||
import NavigationMenu from './NavigationMenu';
|
import NavigationMenu from './NavigationMenu';
|
||||||
|
import ViewLockedIcon from './view-locked-icon/ViewLockedIcon';
|
||||||
|
|
||||||
interface ViewNavigationMenuProps {
|
interface ViewNavigationMenuProps {
|
||||||
/** prevent navigation */
|
/** prevent navigation */
|
||||||
|
|||||||
@@ -11,15 +11,22 @@ import style from './ClientLink.module.scss';
|
|||||||
interface ClientLinkProps {
|
interface ClientLinkProps {
|
||||||
current: boolean;
|
current: boolean;
|
||||||
to: string;
|
to: string;
|
||||||
|
postAction?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ClientLink({ current, to, children }: PropsWithChildren<ClientLinkProps>) {
|
export default function ClientLink({ current, to, postAction, children }: PropsWithChildren<ClientLinkProps>) {
|
||||||
const { isElectron } = useElectronEvent();
|
const { isElectron } = useElectronEvent();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
if (isElectron) {
|
if (isElectron) {
|
||||||
return (
|
return (
|
||||||
<NavigationMenuItem active={current} onClick={() => handleLinks(to)}>
|
<NavigationMenuItem
|
||||||
|
active={current}
|
||||||
|
onClick={() => {
|
||||||
|
handleLinks(to);
|
||||||
|
postAction?.();
|
||||||
|
}}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
<IoArrowUp className={style.linkIcon} />
|
<IoArrowUp className={style.linkIcon} />
|
||||||
</NavigationMenuItem>
|
</NavigationMenuItem>
|
||||||
@@ -27,7 +34,13 @@ export default function ClientLink({ current, to, children }: PropsWithChildren<
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavigationMenuItem active={current} onClick={() => navigate(`/${to}`)}>
|
<NavigationMenuItem
|
||||||
|
active={current}
|
||||||
|
onClick={() => {
|
||||||
|
navigate(`/${to}`);
|
||||||
|
postAction?.();
|
||||||
|
}}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</NavigationMenuItem>
|
</NavigationMenuItem>
|
||||||
);
|
);
|
||||||
|
|||||||
+1
-2
@@ -1,5 +1,4 @@
|
|||||||
import { IoApps } from 'react-icons/io5';
|
import { IoApps, IoSettingsOutline } from 'react-icons/io5';
|
||||||
import { IoSettingsOutline } from 'react-icons/io5';
|
|
||||||
|
|
||||||
import { useFadeOutOnInactivity } from '../../../hooks/useFadeOutOnInactivity';
|
import { useFadeOutOnInactivity } from '../../../hooks/useFadeOutOnInactivity';
|
||||||
import { cx } from '../../../utils/styleUtils';
|
import { cx } from '../../../utils/styleUtils';
|
||||||
|
|||||||
+3
-1
@@ -24,7 +24,9 @@ export default function NavigationMenuItem({
|
|||||||
role='button'
|
role='button'
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
isKeyEnter(event) && onClick();
|
if (isKeyEnter(event)) {
|
||||||
|
onClick();
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
color: $ui-white;
|
color: $ui-white;
|
||||||
background-color: $gray-1250;
|
background-color: $gray-1250;
|
||||||
|
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
box-shadow: $box-shadow-l1;
|
box-shadow: $box-shadow-l1;
|
||||||
border: 1px solid $gray-1000;
|
border: 1px solid $gray-1000;
|
||||||
|
|
||||||
@@ -27,4 +27,3 @@
|
|||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import { Popover } from '@base-ui/react/popover';
|
||||||
import { PropsWithChildren } from 'react';
|
import { PropsWithChildren } from 'react';
|
||||||
import { Popover } from '@base-ui-components/react/popover';
|
|
||||||
|
|
||||||
import style from './Popover.module.scss';
|
import style from './Popover.module.scss';
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { PropsWithChildren, useCallback, useContext } from 'react';
|
|||||||
|
|
||||||
import { isLocalhost } from '../../../externals';
|
import { isLocalhost } from '../../../externals';
|
||||||
import { AppContext } from '../../context/AppContext';
|
import { AppContext } from '../../context/AppContext';
|
||||||
|
|
||||||
import PinPage from './PinPage';
|
import PinPage from './PinPage';
|
||||||
|
|
||||||
interface ProtectRouteProps {
|
interface ProtectRouteProps {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Radio } from '@base-ui-components/react/radio';
|
import { Radio } from '@base-ui/react/radio';
|
||||||
import { RadioGroup as BaseRadioGroup } from '@base-ui-components/react/radio-group';
|
import { RadioGroup as BaseRadioGroup } from '@base-ui/react/radio-group';
|
||||||
|
|
||||||
import { cx } from '../../utils/styleUtils';
|
import { cx } from '../../utils/styleUtils';
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
@use '../../../theme/ontimeColours' as *;
|
||||||
|
@use '../../../theme/ontimeStyles' as *;
|
||||||
|
|
||||||
|
// copied from index.scss
|
||||||
|
$track-color: $white-1;
|
||||||
|
$thumb-color: $white-20;
|
||||||
|
$thumb-color-hover: $white-60;
|
||||||
|
|
||||||
|
.root {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewport {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
overscroll-behavior: contain;
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
outline: 2px solid $blue-500;
|
||||||
|
outline-offset: -2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollbar {
|
||||||
|
background: transparent;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 150ms;
|
||||||
|
|
||||||
|
&[data-orientation='vertical'] {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-orientation='horizontal'] {
|
||||||
|
height: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-orientation='horizontal'][data-has-overflow-x] {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-scrolling] {
|
||||||
|
transition-duration: 0ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-hovering],
|
||||||
|
&[data-scrolling] {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumb {
|
||||||
|
background: $thumb-color;
|
||||||
|
border-radius: 2px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $thumb-color-hover;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { ScrollArea } from '@base-ui/react/scroll-area';
|
||||||
|
import { CSSProperties, PropsWithChildren, Ref } from 'react';
|
||||||
|
|
||||||
|
import { cx } from '../../utils/styleUtils';
|
||||||
|
|
||||||
|
import style from './ScrollArea.module.scss';
|
||||||
|
|
||||||
|
interface ScrollAreaProps {
|
||||||
|
className?: string;
|
||||||
|
viewportClassName?: string;
|
||||||
|
contentClassName?: string;
|
||||||
|
contentStyle?: CSSProperties;
|
||||||
|
ref?: Ref<HTMLDivElement>;
|
||||||
|
orientation?: 'vertical' | 'horizontal';
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function StyledScrollArea({
|
||||||
|
className,
|
||||||
|
viewportClassName,
|
||||||
|
contentClassName,
|
||||||
|
contentStyle,
|
||||||
|
children,
|
||||||
|
ref,
|
||||||
|
orientation = 'vertical',
|
||||||
|
}: PropsWithChildren<ScrollAreaProps>) {
|
||||||
|
return (
|
||||||
|
<ScrollArea.Root className={cx([style.root, className])}>
|
||||||
|
<ScrollArea.Viewport ref={ref} className={cx([style.viewport, viewportClassName])}>
|
||||||
|
<ScrollArea.Content className={contentClassName} style={contentStyle}>
|
||||||
|
{children}
|
||||||
|
</ScrollArea.Content>
|
||||||
|
</ScrollArea.Viewport>
|
||||||
|
<ScrollArea.Scrollbar className={style.scrollbar} orientation={orientation}>
|
||||||
|
<ScrollArea.Thumb className={style.thumb} />
|
||||||
|
</ScrollArea.Scrollbar>
|
||||||
|
</ScrollArea.Root>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import { Select as BaseSelect } from '@base-ui/react/select';
|
||||||
import { IoCheckmark } from 'react-icons/io5';
|
import { IoCheckmark } from 'react-icons/io5';
|
||||||
import { LuChevronsUpDown } from 'react-icons/lu';
|
import { LuChevronsUpDown } from 'react-icons/lu';
|
||||||
import { Select as BaseSelect } from '@base-ui-components/react/select';
|
|
||||||
|
|
||||||
import { cx } from '../../utils/styleUtils';
|
import { cx } from '../../utils/styleUtils';
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
.text {
|
.text {
|
||||||
display: block;
|
display: block;
|
||||||
margin-inline: auto;;
|
margin-inline: auto;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
/* Reset and base styles */
|
/* Reset and base styles */
|
||||||
appearance: none;
|
appearance: none;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
border-radius: 99px;
|
border-radius: 99px;
|
||||||
background-color: $gray-1100;
|
background-color: $gray-1100;
|
||||||
|
|
||||||
/* Transitions */
|
/* Transitions */
|
||||||
transition: background-color 125ms cubic-bezier(0.26, 0.75, 0.38, 0.45);
|
transition: background-color 125ms cubic-bezier(0.26, 0.75, 0.38, 0.45);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Switch as BaseSwitch } from '@base-ui-components/react/switch';
|
import { Switch as BaseSwitch } from '@base-ui/react/switch';
|
||||||
|
|
||||||
import { cx } from '../../utils/styleUtils';
|
import { cx } from '../../utils/styleUtils';
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import { ReactNode } from 'react';
|
import { PropsWithChildren } from 'react';
|
||||||
|
|
||||||
import style from './Tag.module.scss';
|
import style from './Tag.module.scss';
|
||||||
|
|
||||||
export default function Tag({ children }: { children: ReactNode }) {
|
interface TagProps {
|
||||||
return <span className={style.tag}>{children}</span>;
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Tag({ className, children }: PropsWithChildren<TagProps>) {
|
||||||
|
return <span className={`${style.tag} ${className || ''}`}>{children}</span>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
.title-card__title {
|
.title-card__title {
|
||||||
color: var(--color-override, $viewer-color);
|
color: var(--color-override, $viewer-color);
|
||||||
padding-right: 1em;
|
padding-right: 1em;
|
||||||
|
min-height: 1.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-card__placeholder {
|
.title-card__placeholder {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
.tooltip {
|
.tooltip {
|
||||||
font-size: calc(1rem - 3px);
|
font-size: calc(1rem - 3px);
|
||||||
background-color: $ui-white;
|
background-color: $ui-white;
|
||||||
color: $ui-black;
|
color: $ui-black;
|
||||||
padding: 0.125rem 0.5rem;
|
padding: 0.125rem 0.5rem;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
line-height: 1.25em;
|
line-height: 1.25em;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import { Tooltip as BaseTooltip } from '@base-ui/react/tooltip';
|
||||||
import { PropsWithChildren } from 'react';
|
import { PropsWithChildren } from 'react';
|
||||||
import { Tooltip as BaseTooltip } from '@base-ui-components/react/tooltip';
|
|
||||||
|
|
||||||
import style from './Tooltip.module.scss';
|
import style from './Tooltip.module.scss';
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { useRef } from 'react';
|
||||||
|
|
||||||
import { projectLogoPath } from '../../api/constants';
|
import { projectLogoPath } from '../../api/constants';
|
||||||
|
|
||||||
import './ViewLogo.scss';
|
import './ViewLogo.scss';
|
||||||
@@ -7,13 +9,19 @@ interface ViewLogoProps {
|
|||||||
className: string;
|
className: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ViewLogo(props: ViewLogoProps) {
|
export default function ViewLogo({ name, className }: ViewLogoProps) {
|
||||||
const { name, className } = props;
|
const imageRef = useRef<HTMLImageElement>(null);
|
||||||
|
|
||||||
|
const hideImage = () => {
|
||||||
|
if (!imageRef.current) return;
|
||||||
|
|
||||||
|
imageRef.current.style.display = 'none';
|
||||||
|
};
|
||||||
|
|
||||||
// we wrap the image in a div to help maintain the aspect ratio
|
// we wrap the image in a div to help maintain the aspect ratio
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
<img alt='' src={`${projectLogoPath}/${name}`} className='viewLogo' />
|
<img ref={imageRef} alt='' src={`${projectLogoPath}/${name}`} className='viewLogo' onError={hideImage} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,11 +32,11 @@
|
|||||||
margin-left: 0.25rem;
|
margin-left: 0.25rem;
|
||||||
width: 0.75em;
|
width: 0.75em;
|
||||||
height: 0.75em;
|
height: 0.75em;
|
||||||
background: var(--user-bg);
|
background: var(--user-bg, $gray-900);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty {
|
.empty {
|
||||||
color: $ui-white;
|
color: $ui-white;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import { ComponentProps, useEffect, useState } from 'react';
|
import { ComponentProps, useEffect, useState } from 'react';
|
||||||
import { useSearchParams } from 'react-router';
|
import { useSearchParams } from 'react-router';
|
||||||
|
|
||||||
import { isStringBoolean } from '../../../features/viewers/common/viewUtils';
|
import { isStringBoolean } from '../../../views/common/viewUtils';
|
||||||
import Checkbox from '../checkbox/Checkbox';
|
import Checkbox from '../checkbox/Checkbox';
|
||||||
import Input from '../input/input/Input';
|
import Input from '../input/input/Input';
|
||||||
import Select, { SelectOption } from '../select/Select';
|
import Select, { SelectOption } from '../select/Select';
|
||||||
import Switch from '../switch/Switch';
|
import Switch from '../switch/Switch';
|
||||||
|
|
||||||
import InlineColourPicker from './InlineColourPicker';
|
import InlineColourPicker from './InlineColourPicker';
|
||||||
import { ParamField } from './viewParams.types';
|
import { ParamField } from './viewParams.types';
|
||||||
|
|
||||||
@@ -44,7 +43,7 @@ export default function ParamInput({ paramField }: ParamInputProps) {
|
|||||||
return (
|
return (
|
||||||
<MultiOption
|
<MultiOption
|
||||||
paramField={paramField}
|
paramField={paramField}
|
||||||
options={optionFromParams.length ? optionFromParams : paramField.defaultValue ?? ['']}
|
options={optionFromParams.length ? optionFromParams : (paramField.defaultValue ?? [''])}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -160,7 +159,16 @@ function ControlledSelect({ id, initialValue, options }: ControlledSelectProps)
|
|||||||
}, [initialValue]);
|
}, [initialValue]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select size='large' name={id} options={options} value={selected} onValueChange={(value) => setSelected(value)} />
|
<Select
|
||||||
|
size='large'
|
||||||
|
name={id}
|
||||||
|
options={options}
|
||||||
|
value={selected}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
if (value === null) return;
|
||||||
|
setSelected(value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
width: 40rem;
|
width: 40rem;
|
||||||
max-width: 100vw;
|
max-width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
height: 100dvh;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user