mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-03 06:28:01 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a40fa0dbf8 | |||
| e92f59ac55 | |||
| 077c67e912 | |||
| 9644cbde92 | |||
| 8b0e03e980 | |||
| 0966659596 | |||
| 1123e29499 | |||
| 3d2248cceb | |||
| 043ba38916 | |||
| 5b6d4c5b28 | |||
| 07e8bedff4 | |||
| 6e61a7ecf9 | |||
| accdf2c396 | |||
| 5e39c9b51f | |||
| b52fb0f9ee |
@@ -1,9 +1,8 @@
|
||||
name: Ontime build v3
|
||||
name: Ontime build
|
||||
|
||||
on:
|
||||
# Only trigger manually for now for testing
|
||||
# push:
|
||||
# tags: [ "*" ]
|
||||
push:
|
||||
tags: [ "*" ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
@@ -31,7 +30,14 @@ jobs:
|
||||
run: pnpm build
|
||||
|
||||
- name: Electron - Build app
|
||||
env:
|
||||
APPLE_ID: ${{ secrets.APPLEID }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLEIDPASS }}
|
||||
APPLE_TEAM_ID: ${{ secrets.TEAMID }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||
run: pnpm dist-mac
|
||||
timeout-minutes: 60
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Docker Image CI Ontime V2
|
||||
name: Docker Image CI Ontime
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -13,14 +13,14 @@ jobs:
|
||||
CI: ''
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup env
|
||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup Node.js environment
|
||||
uses: actions/setup-node@v3.6.0
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
version: 16.16.0
|
||||
node-version: 18.18.2
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
@@ -1,108 +0,0 @@
|
||||
name: Ontime build v2
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: [ "*" ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build_macos:
|
||||
runs-on: macOS-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.18.2
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build project packages
|
||||
env:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
run: pnpm build
|
||||
|
||||
- name: Electron - Build app
|
||||
run: pnpm dist-mac
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: |
|
||||
./apps/electron/dist/ontime-macOS-x64.dmg
|
||||
./apps/electron/dist/ontime-macOS-arm64.dmg
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
build_windows:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.18.2
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build project packages
|
||||
env:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
run: pnpm build
|
||||
|
||||
- name: Electron - Build app
|
||||
run: pnpm dist-win
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: './apps/electron/dist/ontime-win64.exe'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
build_ubuntu:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.18.2
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build project packages
|
||||
env:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
run: pnpm build
|
||||
|
||||
- name: Electron - Build app
|
||||
run: pnpm dist-linux
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: './apps/electron/dist/ontime-linux.AppImage'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -1,9 +1,8 @@
|
||||
name: Ontime test v3
|
||||
name: Ontime test
|
||||
|
||||
on:
|
||||
# Only trigger manually for now for testing
|
||||
# pull_request:
|
||||
# branches: '*'
|
||||
pull_request:
|
||||
branches: '*'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
@@ -1,106 +0,0 @@
|
||||
name: Ontime test v2
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: '*'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
unit-test:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
env:
|
||||
CI: ''
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.18.2
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
# Run code quality per package
|
||||
- name: React - Run linter + TypeScript checks
|
||||
if: always()
|
||||
run: pnpm lint && tsc --noEmit
|
||||
working-directory: ./apps/client
|
||||
|
||||
- name: Server - Run linter + TypeScript checks
|
||||
if: always()
|
||||
run: pnpm lint && tsc --noEmit
|
||||
working-directory: ./apps/server
|
||||
|
||||
- name: Utils - Run linter + TypeScript checks
|
||||
if: always()
|
||||
run: pnpm lint && tsc --noEmit
|
||||
working-directory: ./packages/utils
|
||||
|
||||
- name: Types - Run linter
|
||||
if: always()
|
||||
run: pnpm lint
|
||||
working-directory: ./packages/types
|
||||
|
||||
# We choose to run tests separately
|
||||
- name: React - Run unit tests
|
||||
if: always()
|
||||
run: pnpm test:pipeline
|
||||
working-directory: ./apps/client
|
||||
|
||||
- name: Server - Run unit tests
|
||||
if: always()
|
||||
run: pnpm test:pipeline
|
||||
working-directory: ./apps/server
|
||||
|
||||
- name: Utils - Run unit tests
|
||||
if: always()
|
||||
run: pnpm test:pipeline
|
||||
working-directory: ./packages/utils
|
||||
|
||||
e2e-test:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.18.2
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build client
|
||||
run: pnpm build:local
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
run: npx playwright install --with-deps
|
||||
|
||||
- name: Run Playwright tests
|
||||
run: pnpm e2e
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: playwright-report/
|
||||
retention-days: 30
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: automated-screenshots
|
||||
path: automated-screenshots/
|
||||
retention-days: 14
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ontime-ui",
|
||||
"version": "3.0.0-beta.2",
|
||||
"version": "3.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
@@ -10,6 +10,7 @@
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@emotion/react": "^11.10.6",
|
||||
"@emotion/styled": "^11.10.6",
|
||||
"@fontsource/open-sans": "^5.0.28",
|
||||
"@mantine/hooks": "^7.6.2",
|
||||
"@react-icons/all-files": "^4.1.0",
|
||||
"@sentry/react": "^7.92.0",
|
||||
@@ -28,7 +29,6 @@
|
||||
"react-hook-form": "^7.49.2",
|
||||
"react-qr-code": "^2.0.12",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"typeface-open-sans": "^1.1.13",
|
||||
"web-vitals": "^3.1.1",
|
||||
"zustand": "^4.5.0"
|
||||
},
|
||||
|
||||
@@ -13,10 +13,6 @@ import theme from './theme/theme';
|
||||
import { TranslationProvider } from './translation/TranslationProvider';
|
||||
import AppRouter from './AppRouter';
|
||||
|
||||
// Load Open Sans typeface
|
||||
// @ts-expect-error no types from font import
|
||||
import('typeface-open-sans');
|
||||
|
||||
const preferredClientName = socketClientName.getState().name;
|
||||
connectSocket(preferredClientName);
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ export default function GSheetSetup(props: GSheetSetupProps) {
|
||||
const patchStepData = useSheetStore((state) => state.patchStepData);
|
||||
const authenticationStatus = useSheetStore((state) => state.authenticationStatus);
|
||||
const setAuthenticationStatus = useSheetStore((state) => state.setAuthenticationStatus);
|
||||
const authenticationError = useSheetStore((state) => state.stepData.authenticate.error);
|
||||
|
||||
/** Check if we are authenticated */
|
||||
const getAuthStatus = async () => {
|
||||
@@ -44,6 +45,7 @@ export default function GSheetSetup(props: GSheetSetupProps) {
|
||||
|
||||
/** check if the current session has been authenticated */
|
||||
useEffect(() => {
|
||||
patchStepData({ authenticate: { available: false, error: '' } });
|
||||
untilAuthenticated();
|
||||
}, []);
|
||||
|
||||
@@ -150,7 +152,7 @@ export default function GSheetSetup(props: GSheetSetupProps) {
|
||||
</Panel.Title>
|
||||
<Panel.ListGroup>
|
||||
<Panel.Description>Upload Client Secret provided by Google</Panel.Description>
|
||||
<Panel.Error>{undefined}</Panel.Error>
|
||||
<Panel.Error>{authenticationError}</Panel.Error>
|
||||
<Input
|
||||
type='file'
|
||||
onChange={handleClientSecret}
|
||||
|
||||
@@ -25,9 +25,9 @@ export default function useGoogleSheet() {
|
||||
/** whether the current session has been authenticated */
|
||||
const verifyAuth = async (): Promise<{ authenticated: AuthenticationStatus; sheetId: string } | void> => {
|
||||
try {
|
||||
return verifyAuthenticationStatus();
|
||||
} catch (_error) {
|
||||
/** we do not handle errors here */
|
||||
return await verifyAuthenticationStatus();
|
||||
} catch (error) {
|
||||
patchStepData({ authenticate: { available: false, error: maybeAxiosError(error) } });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -37,18 +37,18 @@ export default function useGoogleSheet() {
|
||||
sheetId: string,
|
||||
): Promise<{ verification_url: string; user_code: string } | void> => {
|
||||
try {
|
||||
return requestConnection(file, sheetId);
|
||||
} catch (_error) {
|
||||
/** we do not handle errors here */
|
||||
return await requestConnection(file, sheetId);
|
||||
} catch (error) {
|
||||
patchStepData({ authenticate: { available: false, error: maybeAxiosError(error) } });
|
||||
}
|
||||
};
|
||||
|
||||
/** requests the revoking of an existing authenticated session */
|
||||
const revoke = async (): Promise<{ authenticated: AuthenticationStatus } | void> => {
|
||||
try {
|
||||
return revokeAuthentication();
|
||||
} catch (_error) {
|
||||
/** we do not handle errors here */
|
||||
return await revokeAuthentication();
|
||||
} catch (error) {
|
||||
patchStepData({ authenticate: { available: false, error: maybeAxiosError(error) } });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { lazy, useCallback, useEffect } from 'react';
|
||||
import { IconButton, useDisclosure } from '@chakra-ui/react';
|
||||
import { IoApps } from '@react-icons/all-files/io5/IoApps';
|
||||
import { IoClose } from '@react-icons/all-files/io5/IoClose';
|
||||
import { IoSettingsOutline } from '@react-icons/all-files/io5/IoSettingsOutline';
|
||||
|
||||
import ProductionNavigationMenu from '../../common/components/navigation-menu/ProductionNavigationMenu';
|
||||
@@ -84,9 +85,9 @@ export default function Editor() {
|
||||
/>
|
||||
<IconButton
|
||||
aria-label='Toggle settings'
|
||||
variant='ontime-subtle-white'
|
||||
variant={isSettingsOpen ? 'ontime-subtle' : 'ontime-subtle-white'}
|
||||
size='lg'
|
||||
icon={<IoSettingsOutline />}
|
||||
icon={isSettingsOpen ? <IoClose /> : <IoSettingsOutline />}
|
||||
onClick={toggleSettings}
|
||||
/>
|
||||
</EditorOverview>
|
||||
|
||||
@@ -137,7 +137,6 @@
|
||||
}
|
||||
|
||||
.qr {
|
||||
margin-left: clamp(16px, 5vw, 64px);
|
||||
padding: 4px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,36 @@
|
||||
@use './theme/ontimeStyles' as *;
|
||||
|
||||
/* open-sans-latin-300-normal */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 300;
|
||||
src: url(@fontsource/open-sans/files/open-sans-latin-300-normal.woff2) format('woff2'), url(@fontsource/open-sans/files/open-sans-latin-300-normal.woff) format('woff');
|
||||
unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
|
||||
}
|
||||
|
||||
/* open-sans-latin-400-normal */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 400;
|
||||
src: url(@fontsource/open-sans/files/open-sans-latin-400-normal.woff2) format('woff2'), url(@fontsource/open-sans/files/open-sans-latin-400-normal.woff) format('woff');
|
||||
unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
|
||||
}
|
||||
|
||||
/* open-sans-latin-600-normal */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 600;
|
||||
src: url(@fontsource/open-sans/files/open-sans-latin-600-normal.woff2) format('woff2'), url(@fontsource/open-sans/files/open-sans-latin-600-normal.woff) format('woff');
|
||||
unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
|
||||
}
|
||||
|
||||
|
||||
$track-color: $white-1;
|
||||
$thumb-color: $gray-1100;
|
||||
$thumb-color-hover: $gray-900;
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ontime-prerelease",
|
||||
"version": "3.0.0-beta.2",
|
||||
"name": "ontime",
|
||||
"version": "3.0.0",
|
||||
"author": "Carlos Valente",
|
||||
"description": "Time keeping for live events",
|
||||
"repository": "https://github.com/cpvalente/ontime",
|
||||
@@ -13,7 +13,7 @@
|
||||
"main": "main.js",
|
||||
"devDependencies": {
|
||||
"electron": "^28.0.0",
|
||||
"electron-builder": "^24.9.1",
|
||||
"electron-builder": "^24.13.3",
|
||||
"eslint": "^8.53.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"prettier": "^3.0.3"
|
||||
@@ -23,20 +23,27 @@
|
||||
"lint": "eslint . --quiet",
|
||||
"lint-staged": "eslint",
|
||||
"dev:electron": "cross-env NODE_ENV=development electron .",
|
||||
"dist-win": "electron-builder --publish=never --x64 --win",
|
||||
"dist-mac": "electron-builder --publish=never --mac",
|
||||
"dist-linux": "electron-builder --publish=never --x64 --linux",
|
||||
"dist-win": "electron-builder --publish=never --x64 --win",
|
||||
"dist-mac": "electron-builder --publish=never --mac",
|
||||
"dist-linux": "electron-builder --publish=never --x64 --linux",
|
||||
"cleanup": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
|
||||
},
|
||||
"build": {
|
||||
"productName": "ontime-prerelease",
|
||||
"appId": "no.lightdev.ontime.prerelease",
|
||||
"productName": "ontime",
|
||||
"appId": "no.lightdev.ontime",
|
||||
"asar": true,
|
||||
"dmg": {
|
||||
"artifactName": "ontime-macOS-${arch}.dmg",
|
||||
"icon": "icon.icns"
|
||||
},
|
||||
"mac": {
|
||||
"notarize": {
|
||||
"teamId": "MDAU6QK6R4"
|
||||
},
|
||||
"hardenedRuntime": true,
|
||||
"gatekeeperAssess": false,
|
||||
"entitlements": "./entitlements.plist",
|
||||
"entitlementsInherit": "./entitlements.plist",
|
||||
"target": {
|
||||
"target": "dmg",
|
||||
"arch": [
|
||||
|
||||
@@ -18,7 +18,7 @@ function getApplicationMenu(isMac, askToQuit, urlBase) {
|
||||
{ role: 'unhide' },
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'quit',
|
||||
label: 'Quit',
|
||||
click: () => askToQuit(),
|
||||
accelerator: 'Cmd+Q',
|
||||
},
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "ontime-server",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"version": "3.0.0-beta.2",
|
||||
"version": "3.0.0",
|
||||
"exports": "./src/index.js",
|
||||
"dependencies": {
|
||||
"@googleapis/sheets": "^5.0.5",
|
||||
|
||||
Vendored
+3
-3
@@ -66,9 +66,9 @@ const connectSocket = () => {
|
||||
switch (type) {
|
||||
case 'ontime': {
|
||||
// destructure known data from ontime
|
||||
// see https://docs.getontime.no/api/osc-and-ws/
|
||||
const { timer, playback } = payload;
|
||||
updateTimerElement(playback, timer);
|
||||
// see https://docs.getontime.no/api/data/runtime-data/
|
||||
const { current, playback } = payload.timer;
|
||||
updateTimerElement(playback, current);
|
||||
break;
|
||||
}
|
||||
case 'ontime-timer': {
|
||||
|
||||
@@ -1,27 +1,15 @@
|
||||
import { OntimeEvent, Playback, RuntimeStore } from 'ontime-types';
|
||||
import { OntimeEvent } from 'ontime-types';
|
||||
|
||||
import { deepEqual } from 'fast-equals';
|
||||
|
||||
import { eventStore } from '../stores/EventStore.js';
|
||||
import * as runtimeState from '../stores/runtimeState.js';
|
||||
import type { RuntimeState, UpdateResult } from '../stores/runtimeState.js';
|
||||
|
||||
import { restoreService } from './RestoreService.js';
|
||||
import type { UpdateResult } from '../stores/runtimeState.js';
|
||||
|
||||
/**
|
||||
* Service manages Ontime's main timer
|
||||
* It is responsible for streaming the data to the event store
|
||||
*/
|
||||
export class TimerService {
|
||||
private readonly _interval: NodeJS.Timer;
|
||||
/** how often we update the socket */
|
||||
static _updateInterval: number;
|
||||
/** how often we recalculate */
|
||||
static _refreshInterval: number;
|
||||
/** last time we updated the socket */
|
||||
static previousUpdate: number;
|
||||
/** last known state */
|
||||
static previousState: RuntimeState;
|
||||
|
||||
/** when timer will be finished */
|
||||
private endCallback: NodeJS.Timer;
|
||||
@@ -39,10 +27,6 @@ export class TimerService {
|
||||
updateInterval: number;
|
||||
onUpdateCallback: (updateResult: UpdateResult) => void;
|
||||
}) {
|
||||
TimerService.previousUpdate = -1;
|
||||
TimerService.previousState = {} as RuntimeState;
|
||||
|
||||
TimerService._updateInterval = timerConfig.updateInterval;
|
||||
TimerService._refreshInterval = timerConfig.refresh;
|
||||
|
||||
this.onUpdateCallback = timerConfig.onUpdateCallback;
|
||||
@@ -51,7 +35,6 @@ export class TimerService {
|
||||
}, TimerService._refreshInterval);
|
||||
}
|
||||
|
||||
@broadcastResult
|
||||
start() {
|
||||
if (!runtimeState.start()) {
|
||||
return false;
|
||||
@@ -63,7 +46,6 @@ export class TimerService {
|
||||
return true;
|
||||
}
|
||||
|
||||
@broadcastResult
|
||||
pause() {
|
||||
if (!runtimeState.pause()) {
|
||||
return false;
|
||||
@@ -74,7 +56,6 @@ export class TimerService {
|
||||
return true;
|
||||
}
|
||||
|
||||
@broadcastResult
|
||||
stop() {
|
||||
if (!runtimeState.stop()) {
|
||||
return false;
|
||||
@@ -89,7 +70,6 @@ export class TimerService {
|
||||
* Adds time to running timer by given amount
|
||||
* @param {number} amount
|
||||
*/
|
||||
@broadcastResult
|
||||
addTime(amount: number): boolean {
|
||||
if (!runtimeState.addTime(amount)) {
|
||||
return false;
|
||||
@@ -105,7 +85,6 @@ export class TimerService {
|
||||
/**
|
||||
* Update the app at regular intervals
|
||||
*/
|
||||
@broadcastResult
|
||||
update() {
|
||||
const updateResult = runtimeState.update();
|
||||
// pass the result to the parent
|
||||
@@ -116,7 +95,6 @@ export class TimerService {
|
||||
* Loads roll information into timer service
|
||||
* @param {OntimeEvent[]} rundown -- list of events to run
|
||||
*/
|
||||
@broadcastResult
|
||||
roll(rundown: OntimeEvent[]) {
|
||||
runtimeState.roll(rundown);
|
||||
}
|
||||
@@ -126,99 +104,3 @@ export class TimerService {
|
||||
clearTimeout(this.endCallback);
|
||||
}
|
||||
}
|
||||
|
||||
function broadcastResult(_target: any, _propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
const originalMethod = descriptor.value;
|
||||
|
||||
descriptor.value = function (...args: any[]) {
|
||||
// call the original method and get the state
|
||||
const result = originalMethod.apply(this, args);
|
||||
const state = runtimeState.getState();
|
||||
|
||||
// we do the comparison by explicitly for each property
|
||||
// to apply custom logic for different datasets
|
||||
|
||||
// some of the data, we only update at intervals
|
||||
const isTimeToUpdate =
|
||||
state.clock < TimerService.previousUpdate ||
|
||||
state.clock - TimerService.previousUpdate >= TimerService._updateInterval;
|
||||
|
||||
// some changes need an immediate update
|
||||
const hasNewLoaded = state.eventNow?.id !== TimerService.previousState?.eventNow?.id;
|
||||
|
||||
const hasSkippedBack = state.clock < TimerService.previousUpdate;
|
||||
const justStarted = !TimerService.previousState?.timer;
|
||||
const hasChangedPlayback = TimerService.previousState.timer?.playback !== state.timer.playback;
|
||||
const hasImmediateChanges = hasNewLoaded || hasSkippedBack || justStarted || hasChangedPlayback;
|
||||
|
||||
if (hasChangedPlayback) {
|
||||
eventStore.set('onAir', state.timer.playback !== Playback.Stop);
|
||||
}
|
||||
|
||||
if (hasImmediateChanges || (isTimeToUpdate && !deepEqual(TimerService.previousState?.timer, state.timer))) {
|
||||
eventStore.set('timer', state.timer);
|
||||
TimerService.previousState.timer = { ...state.timer };
|
||||
}
|
||||
|
||||
if (hasChangedPlayback || (isTimeToUpdate && !deepEqual(TimerService.previousState?.runtime, state.runtime))) {
|
||||
eventStore.set('runtime', state.runtime);
|
||||
TimerService.previousState.runtime = { ...state.runtime };
|
||||
}
|
||||
|
||||
// Update the events if they have changed
|
||||
updateEventIfChanged('eventNow', state);
|
||||
updateEventIfChanged('publicEventNow', state);
|
||||
updateEventIfChanged('eventNext', state);
|
||||
updateEventIfChanged('publicEventNext', state);
|
||||
|
||||
if (isTimeToUpdate) {
|
||||
TimerService.previousUpdate = state.clock;
|
||||
eventStore.set('clock', state.clock);
|
||||
saveRestoreState(state);
|
||||
}
|
||||
|
||||
// Helper function to update an event if it has changed
|
||||
function updateEventIfChanged(eventKey: keyof RuntimeStore, state: RuntimeState) {
|
||||
const previous = TimerService.previousState?.[eventKey];
|
||||
const now = state[eventKey];
|
||||
|
||||
// if there was nothing, and there is nothing, noop
|
||||
if (!previous?.id && !now?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if load status changed, save new
|
||||
if (previous?.id !== now?.id) {
|
||||
storeKey(eventKey);
|
||||
return;
|
||||
}
|
||||
|
||||
// maybe the event itself has changed
|
||||
if (!deepEqual(TimerService.previousState?.[eventKey], state[eventKey])) {
|
||||
storeKey(eventKey);
|
||||
return;
|
||||
}
|
||||
|
||||
function storeKey(eventKey: keyof RuntimeStore) {
|
||||
eventStore.set(eventKey, state[eventKey]);
|
||||
TimerService.previousState[eventKey] = { ...state[eventKey] };
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to save the restore state
|
||||
function saveRestoreState(state: RuntimeState) {
|
||||
restoreService.save({
|
||||
playback: state.timer.playback,
|
||||
selectedEventId: state.eventNow?.id ?? null,
|
||||
startedAt: state.timer.startedAt,
|
||||
addedTime: state.timer.addedTime,
|
||||
pausedAt: state._timer.pausedAt,
|
||||
firstStart: state.runtime.actualStart,
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { LogOrigin } from 'ontime-types';
|
||||
|
||||
import IIntegration, { TimerLifeCycleKey } from './IIntegration.js';
|
||||
import { getState } from '../../stores/runtimeState.js';
|
||||
import { eventStore } from '../../stores/EventStore.js';
|
||||
import { logger } from '../../classes/Logger.js';
|
||||
|
||||
class IntegrationService {
|
||||
@@ -20,14 +20,7 @@ class IntegrationService {
|
||||
}
|
||||
|
||||
dispatch(action: TimerLifeCycleKey) {
|
||||
/**
|
||||
* We currently get the state from the runtimeState store
|
||||
* This solves an issue where the state is not updated until after the integrations have ran
|
||||
* The workaround solves the issue with the tradeoff of
|
||||
* - we do not have access to data outside runtimeState (eg: messages or auxtimers)
|
||||
* - we couple the integrationService to runtimeState
|
||||
*/
|
||||
const state = getState();
|
||||
const state = eventStore.poll();
|
||||
this.integrations.forEach((integration) => {
|
||||
integration.dispatch(action, state);
|
||||
});
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import { EndAction, LogOrigin, OntimeEvent, Playback, TimerLifeCycle } from 'ontime-types';
|
||||
import { EndAction, LogOrigin, OntimeEvent, Playback, RuntimeStore, TimerLifeCycle } from 'ontime-types';
|
||||
import { millisToString, validatePlayback } from 'ontime-utils';
|
||||
|
||||
import { TimerService } from '../TimerService.js';
|
||||
import { deepEqual } from 'fast-equals';
|
||||
|
||||
import { logger } from '../../classes/Logger.js';
|
||||
import { RestorePoint } from '../RestoreService.js';
|
||||
|
||||
import * as runtimeState from '../../stores/runtimeState.js';
|
||||
import type { RuntimeState } from '../../stores/runtimeState.js';
|
||||
import { timerConfig } from '../../config/config.js';
|
||||
import { eventStore } from '../../stores/EventStore.js';
|
||||
|
||||
import { TimerService } from '../TimerService.js';
|
||||
import { RestorePoint, restoreService } from '../RestoreService.js';
|
||||
import {
|
||||
findNext,
|
||||
findPrevious,
|
||||
@@ -16,7 +20,6 @@ import {
|
||||
getPlayableEvents,
|
||||
} from '../rundown-service/rundownUtils.js';
|
||||
import { integrationService } from '../integration-service/IntegrationService.js';
|
||||
import { timerConfig } from '../../config/config.js';
|
||||
|
||||
/**
|
||||
* Service manages runtime status of app
|
||||
@@ -26,11 +29,24 @@ class RuntimeService {
|
||||
private eventTimer: TimerService | null = null;
|
||||
private lastOnUpdate = -1;
|
||||
|
||||
/** last time we updated the socket */
|
||||
static previousUpdate: number;
|
||||
/** last known state */
|
||||
static previousState: RuntimeState;
|
||||
|
||||
constructor() {
|
||||
RuntimeService.previousUpdate = -1;
|
||||
RuntimeService.previousState = {} as RuntimeState;
|
||||
}
|
||||
|
||||
/** Checks result of an update and notifies integrations as needed */
|
||||
@broadcastResult
|
||||
checkTimerUpdate({ shouldCallRoll, hasTimerFinished }: runtimeState.UpdateResult) {
|
||||
const newState = runtimeState.getState();
|
||||
if (hasTimerFinished) {
|
||||
integrationService.dispatch(TimerLifeCycle.onFinish);
|
||||
process.nextTick(() => {
|
||||
integrationService.dispatch(TimerLifeCycle.onFinish);
|
||||
});
|
||||
|
||||
// handle end action if there was a timer playing
|
||||
// actions are added to the queue stack to ensure that the order of operations is maintained
|
||||
@@ -49,10 +65,14 @@ class RuntimeService {
|
||||
if (newState.clock - this.lastOnUpdate >= timerConfig.notificationRate) {
|
||||
const hasRunningTimer = Boolean(newState.eventNow) && newState.timer.playback === Playback.Play;
|
||||
if (hasRunningTimer) {
|
||||
integrationService.dispatch(TimerLifeCycle.onUpdate);
|
||||
process.nextTick(() => {
|
||||
integrationService.dispatch(TimerLifeCycle.onUpdate);
|
||||
});
|
||||
}
|
||||
process.nextTick(() => {
|
||||
integrationService.dispatch(TimerLifeCycle.onClock);
|
||||
});
|
||||
|
||||
integrationService.dispatch(TimerLifeCycle.onClock);
|
||||
this.lastOnUpdate = newState.clock;
|
||||
}
|
||||
|
||||
@@ -186,6 +206,7 @@ class RuntimeService {
|
||||
* @param {OntimeEvent} event
|
||||
* @return {boolean} success - whether an event was loaded
|
||||
*/
|
||||
@broadcastResult
|
||||
loadEvent(event: OntimeEvent): boolean {
|
||||
if (event.skip) {
|
||||
logger.warning(LogOrigin.Playback, `Refused skipped event with ID ${event.id}`);
|
||||
@@ -196,10 +217,10 @@ class RuntimeService {
|
||||
const success = runtimeState.load(event, timedEvents);
|
||||
|
||||
if (success) {
|
||||
// TODO: dispatch should happen after store update
|
||||
// currently store update is handled in TimerService only
|
||||
integrationService.dispatch(TimerLifeCycle.onLoad);
|
||||
logger.info(LogOrigin.Playback, `Loaded event with ID ${event.id}`);
|
||||
process.nextTick(() => {
|
||||
integrationService.dispatch(TimerLifeCycle.onLoad);
|
||||
});
|
||||
}
|
||||
return success;
|
||||
}
|
||||
@@ -325,6 +346,7 @@ class RuntimeService {
|
||||
/**
|
||||
* Starts playback on selected event
|
||||
*/
|
||||
@broadcastResult
|
||||
start(): boolean {
|
||||
const state = runtimeState.getState();
|
||||
const canStart = validatePlayback(state.timer.playback).start;
|
||||
@@ -335,7 +357,9 @@ class RuntimeService {
|
||||
const didStart = this.eventTimer?.start() ?? false;
|
||||
logger.info(LogOrigin.Playback, `Play Mode ${state.timer.playback.toUpperCase()}`);
|
||||
if (didStart) {
|
||||
integrationService.dispatch(TimerLifeCycle.onStart);
|
||||
process.nextTick(() => {
|
||||
integrationService.dispatch(TimerLifeCycle.onStart);
|
||||
});
|
||||
}
|
||||
return didStart;
|
||||
}
|
||||
@@ -367,6 +391,7 @@ class RuntimeService {
|
||||
/**
|
||||
* Pauses playback on selected event
|
||||
*/
|
||||
@broadcastResult
|
||||
pause() {
|
||||
const state = runtimeState.getState();
|
||||
const canPause = validatePlayback(state.timer.playback).pause;
|
||||
@@ -376,12 +401,15 @@ class RuntimeService {
|
||||
this.eventTimer?.pause();
|
||||
const newState = state.timer.playback;
|
||||
logger.info(LogOrigin.Playback, `Play Mode ${newState.toUpperCase()}`);
|
||||
integrationService.dispatch(TimerLifeCycle.onPause);
|
||||
process.nextTick(() => {
|
||||
integrationService.dispatch(TimerLifeCycle.onPause);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops timer and unloads any events
|
||||
*/
|
||||
@broadcastResult
|
||||
stop(): boolean {
|
||||
const state = runtimeState.getState();
|
||||
const canStop = validatePlayback(state.timer.playback).stop;
|
||||
@@ -392,7 +420,10 @@ class RuntimeService {
|
||||
if (didStop) {
|
||||
const newState = state.timer.playback;
|
||||
logger.info(LogOrigin.Playback, `Play Mode ${newState.toUpperCase()}`);
|
||||
integrationService.dispatch(TimerLifeCycle.onStop);
|
||||
process.nextTick(() => {
|
||||
integrationService.dispatch(TimerLifeCycle.onStop);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -401,6 +432,7 @@ class RuntimeService {
|
||||
/**
|
||||
* Reloads current event
|
||||
*/
|
||||
@broadcastResult
|
||||
reload() {
|
||||
const state = runtimeState.getState();
|
||||
if (state.eventNow) {
|
||||
@@ -411,6 +443,7 @@ class RuntimeService {
|
||||
/**
|
||||
* Sets playback to roll
|
||||
*/
|
||||
@broadcastResult
|
||||
roll() {
|
||||
const beforeState = runtimeState.getState();
|
||||
const canRoll = validatePlayback(beforeState.timer.playback).roll;
|
||||
@@ -435,6 +468,7 @@ class RuntimeService {
|
||||
* @description resume playback state given a restore point
|
||||
* @param restorePoint
|
||||
*/
|
||||
@broadcastResult
|
||||
resume(restorePoint: RestorePoint) {
|
||||
const { selectedEventId, playback } = restorePoint;
|
||||
if (playback === Playback.Roll) {
|
||||
@@ -470,3 +504,99 @@ class RuntimeService {
|
||||
}
|
||||
|
||||
export const runtimeService = new RuntimeService();
|
||||
|
||||
function broadcastResult(_target: any, _propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
const originalMethod = descriptor.value;
|
||||
|
||||
descriptor.value = function (...args: any[]) {
|
||||
// call the original method and get the state
|
||||
const result = originalMethod.apply(this, args);
|
||||
const state = runtimeState.getState();
|
||||
|
||||
// we do the comparison by explicitly for each property
|
||||
// to apply custom logic for different datasets
|
||||
|
||||
// some of the data, we only update at intervals
|
||||
const isTimeToUpdate =
|
||||
state.clock < RuntimeService.previousUpdate ||
|
||||
state.clock - RuntimeService.previousUpdate >= timerConfig.notificationRate;
|
||||
|
||||
// some changes need an immediate update
|
||||
const hasNewLoaded = state.eventNow?.id !== RuntimeService.previousState?.eventNow?.id;
|
||||
|
||||
const hasSkippedBack = state.clock < RuntimeService.previousUpdate;
|
||||
const justStarted = !RuntimeService.previousState?.timer;
|
||||
const hasChangedPlayback = RuntimeService.previousState.timer?.playback !== state.timer.playback;
|
||||
const hasImmediateChanges = hasNewLoaded || hasSkippedBack || justStarted || hasChangedPlayback;
|
||||
|
||||
if (hasChangedPlayback) {
|
||||
eventStore.set('onAir', state.timer.playback !== Playback.Stop);
|
||||
}
|
||||
|
||||
if (hasImmediateChanges || (isTimeToUpdate && !deepEqual(RuntimeService.previousState?.timer, state.timer))) {
|
||||
eventStore.set('timer', state.timer);
|
||||
RuntimeService.previousState.timer = { ...state.timer };
|
||||
}
|
||||
|
||||
if (hasChangedPlayback || (isTimeToUpdate && !deepEqual(RuntimeService.previousState?.runtime, state.runtime))) {
|
||||
eventStore.set('runtime', state.runtime);
|
||||
RuntimeService.previousState.runtime = { ...state.runtime };
|
||||
}
|
||||
|
||||
// Update the events if they have changed
|
||||
updateEventIfChanged('eventNow', state);
|
||||
updateEventIfChanged('publicEventNow', state);
|
||||
updateEventIfChanged('eventNext', state);
|
||||
updateEventIfChanged('publicEventNext', state);
|
||||
|
||||
if (isTimeToUpdate) {
|
||||
RuntimeService.previousUpdate = state.clock;
|
||||
eventStore.set('clock', state.clock);
|
||||
saveRestoreState(state);
|
||||
}
|
||||
|
||||
// Helper function to update an event if it has changed
|
||||
function updateEventIfChanged(eventKey: keyof RuntimeStore, state: runtimeState.RuntimeState) {
|
||||
const previous = RuntimeService.previousState?.[eventKey];
|
||||
const now = state[eventKey];
|
||||
|
||||
// if there was nothing, and there is nothing, noop
|
||||
if (!previous?.id && !now?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if load status changed, save new
|
||||
if (previous?.id !== now?.id) {
|
||||
storeKey(eventKey);
|
||||
return;
|
||||
}
|
||||
|
||||
// maybe the event itself has changed
|
||||
if (!deepEqual(RuntimeService.previousState?.[eventKey], state[eventKey])) {
|
||||
storeKey(eventKey);
|
||||
return;
|
||||
}
|
||||
|
||||
function storeKey(eventKey: keyof RuntimeStore) {
|
||||
eventStore.set(eventKey, state[eventKey]);
|
||||
RuntimeService.previousState[eventKey] = { ...state[eventKey] };
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to save the restore state
|
||||
function saveRestoreState(state: runtimeState.RuntimeState) {
|
||||
restoreService.save({
|
||||
playback: state.timer.playback,
|
||||
selectedEventId: state.eventNow?.id ?? null,
|
||||
startedAt: state.timer.startedAt,
|
||||
addedTime: state.timer.addedTime,
|
||||
pausedAt: state._timer.pausedAt,
|
||||
firstStart: state.runtime.actualStart,
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
@@ -121,6 +121,10 @@ function getCellData(key: string, event: OntimeRundownEntry) {
|
||||
if (key === 'colour') {
|
||||
return { userEnteredValue: { stringValue: event[key] } };
|
||||
}
|
||||
if (key.startsWith('custom')) {
|
||||
const customKey = key.split(':')[1];
|
||||
return { userEnteredValue: { stringValue: event.custom[customKey] } };
|
||||
}
|
||||
|
||||
const dataType = typeof event[key];
|
||||
if (dataType === 'number') {
|
||||
|
||||
@@ -1488,24 +1488,31 @@ describe('parseExcel()', () => {
|
||||
};
|
||||
|
||||
const result = parseExcel(testData, importMap);
|
||||
const rundown = parseRundown(result);
|
||||
const initialRundown = parseRundown(result);
|
||||
|
||||
cache.init(rundown, {});
|
||||
const cachedRundown = cache.get().rundown;
|
||||
cache.init(initialRundown, {});
|
||||
const { rundown, order } = cache.get();
|
||||
|
||||
const events = Object.values(cachedRundown).filter((e) => e.type === SupportedEvent.Event) as OntimeEvent[];
|
||||
const firstId = order.at(0); // A
|
||||
const secondId = order.at(1); // C
|
||||
const thirdId = order.at(2); // D
|
||||
const fourthId = order.at(3); // E
|
||||
const fifhtId = order.at(4); // Block
|
||||
const sixthId = order.at(5); // G
|
||||
|
||||
expect(events.at(0).timeStart).toEqual(16200000);
|
||||
expect((rundown[firstId] as OntimeEvent).timeStart).toEqual(16200000);
|
||||
|
||||
expect(events.at(1).timeStart).toEqual(events.at(0).timeEnd);
|
||||
expect(events.at(1).linkStart).toEqual(events.at(0).id);
|
||||
expect((rundown[secondId] as OntimeEvent).timeStart).toEqual((rundown[firstId] as OntimeEvent).timeEnd);
|
||||
expect((rundown[secondId] as OntimeEvent).linkStart).toEqual((rundown[firstId] as OntimeEvent).id);
|
||||
|
||||
expect(events.at(2).timeStart).toEqual(events.at(1).timeEnd);
|
||||
expect(events.at(2).linkStart).toEqual(events.at(1).id);
|
||||
expect((rundown[thirdId] as OntimeEvent).timeStart).toEqual((rundown[secondId] as OntimeEvent).timeEnd);
|
||||
expect((rundown[thirdId] as OntimeEvent).linkStart).toEqual((rundown[secondId] as OntimeEvent).id);
|
||||
|
||||
expect(events.at(3).timeStart).toEqual(78300000);
|
||||
expect((rundown[fourthId] as OntimeEvent).timeStart).toEqual(78300000);
|
||||
|
||||
expect(events.at(4).timeStart).toEqual(events.at(3).timeEnd);
|
||||
expect(events.at(4).linkStart).toEqual(events.at(3).id);
|
||||
expect((rundown[fifhtId] as OntimeEvent).type).toEqual(SupportedEvent.Block);
|
||||
|
||||
expect((rundown[sixthId] as OntimeEvent).timeStart).toEqual((rundown[fourthId] as OntimeEvent).timeEnd);
|
||||
expect((rundown[sixthId] as OntimeEvent).linkStart).toEqual((rundown[fourthId] as OntimeEvent).id);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { HttpSubscription, OscSubscription } from 'ontime-types';
|
||||
import { sanitiseHttpSubscriptions, sanitiseOscSubscriptions } from '../parserFunctions.js';
|
||||
import { CustomFields, HttpSubscription, OscSubscription } from 'ontime-types';
|
||||
import { sanitiseCustomFields, sanitiseHttpSubscriptions, sanitiseOscSubscriptions } from '../parserFunctions.js';
|
||||
|
||||
describe('sanitiseOscSubscriptions()', () => {
|
||||
it('returns an empty array if not an array', () => {
|
||||
@@ -71,3 +71,87 @@ describe('sanitiseHttpSubscriptions()', () => {
|
||||
expect(sanitationResult.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sanitiseCustomFields()', () => {
|
||||
it('returns an empty array if not an array', () => {
|
||||
expect(sanitiseCustomFields({})).toEqual({});
|
||||
});
|
||||
|
||||
it('returns an object of valid entries', () => {
|
||||
const customFields: CustomFields = {
|
||||
test: { label: 'test', type: 'string', colour: 'red' },
|
||||
test2: { label: 'test2', type: 'string', colour: 'green' },
|
||||
test3: { label: 'Test3', type: 'string', colour: '' },
|
||||
};
|
||||
const sanitationResult = sanitiseCustomFields(customFields);
|
||||
expect(sanitationResult).toStrictEqual(customFields);
|
||||
});
|
||||
|
||||
it('type is forced to be string', () => {
|
||||
const customFields: CustomFields = {
|
||||
// @ts-expect-error intentional bad data
|
||||
test: { label: 'test', type: 'another', colour: 'red' },
|
||||
};
|
||||
const expectedCustomFields: CustomFields = {
|
||||
test: { label: 'test', type: 'string', colour: 'red' },
|
||||
};
|
||||
const sanitationResult = sanitiseCustomFields(customFields);
|
||||
expect(sanitationResult).toStrictEqual(expectedCustomFields);
|
||||
});
|
||||
|
||||
it('colour must be a string', () => {
|
||||
const customFields: CustomFields = {
|
||||
// @ts-expect-error intentional bad data
|
||||
test: { label: 'test', type: 'string', colour: 5 },
|
||||
};
|
||||
const sanitationResult = sanitiseCustomFields(customFields);
|
||||
expect(sanitationResult).toStrictEqual({});
|
||||
});
|
||||
|
||||
it('label can not be empty', () => {
|
||||
const customFields: CustomFields = {
|
||||
['']: { label: '', type: 'string', colour: 'red' },
|
||||
};
|
||||
const sanitationResult = sanitiseCustomFields(customFields);
|
||||
expect(sanitationResult).toStrictEqual({});
|
||||
});
|
||||
|
||||
it('remove extra stuff', () => {
|
||||
const customFields: CustomFields = {
|
||||
// @ts-expect-error intentional bad data
|
||||
test: { label: 'test', type: 'string', colour: 'red', extra: 'should be removed' },
|
||||
};
|
||||
const expectedCustomFields: CustomFields = {
|
||||
test: { label: 'test', type: 'string', colour: 'red' },
|
||||
};
|
||||
const sanitationResult = sanitiseCustomFields(customFields);
|
||||
expect(sanitationResult).toStrictEqual(expectedCustomFields);
|
||||
});
|
||||
|
||||
it('enforece name cohesion', () => {
|
||||
const customFields: CustomFields = {
|
||||
test: { label: 'New Name', type: 'string', colour: 'red' },
|
||||
};
|
||||
const expectedCustomFields: CustomFields = {
|
||||
['new name']: { label: 'New Name', type: 'string', colour: 'red' },
|
||||
};
|
||||
const sanitationResult = sanitiseCustomFields(customFields);
|
||||
expect(sanitationResult).toStrictEqual(expectedCustomFields);
|
||||
});
|
||||
|
||||
it('filters invalid entries', () => {
|
||||
const customFields: CustomFields = {
|
||||
test: { label: 'test', type: 'string', colour: 'red' },
|
||||
test2: { label: 'test2', type: 'string', colour: 'green' },
|
||||
bad: { label: '', type: 'string', colour: '' },
|
||||
test3: { label: 'Test3', type: 'string', colour: '' },
|
||||
};
|
||||
const expectedCustomFields: CustomFields = {
|
||||
test: { label: 'test', type: 'string', colour: 'red' },
|
||||
test2: { label: 'test2', type: 'string', colour: 'green' },
|
||||
test3: { label: 'Test3', type: 'string', colour: '' },
|
||||
};
|
||||
const sanitationResult = sanitiseCustomFields(customFields);
|
||||
expect(sanitationResult).toStrictEqual(expectedCustomFields);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -180,7 +180,7 @@ export const parseExcel = (excelData: unknown[][], options?: Partial<ImportMap>)
|
||||
},
|
||||
custom: (row: number, col: number, columnText: string) => {
|
||||
customFieldIndexes[col] = columnText;
|
||||
rundownMetadata[`custom-${columnText}`] = { row, col };
|
||||
rundownMetadata[`custom:${columnText}`] = { row, col };
|
||||
},
|
||||
} as const;
|
||||
|
||||
|
||||
@@ -248,20 +248,24 @@ export const parseCustomFields = (data: Partial<DatabaseModel>): CustomFields =>
|
||||
if (typeof data.customFields !== 'object') {
|
||||
return { ...dbModel.customFields };
|
||||
}
|
||||
|
||||
console.log('Found Custom Fields, importing...');
|
||||
|
||||
return sanitiseCustomFields(data.customFields);
|
||||
};
|
||||
|
||||
export const sanitiseCustomFields = (data: object): CustomFields => {
|
||||
const newCustomFields: CustomFields = {};
|
||||
|
||||
for (const fieldLabel in data.customFields) {
|
||||
const field = data.customFields[fieldLabel];
|
||||
if (!field.label || !field.type || !field.colour) {
|
||||
for (const fieldLabel in data) {
|
||||
const field = data[fieldLabel];
|
||||
if (!('label' in field) || field.label === '' || !('colour' in field) || typeof field.colour != 'string') {
|
||||
console.log('ERROR: missing required field, skipping');
|
||||
continue;
|
||||
}
|
||||
|
||||
const key = field.label.toLowerCase();
|
||||
newCustomFields[key] = {
|
||||
type: field.type,
|
||||
type: 'string',
|
||||
colour: field.colour,
|
||||
label: field.label,
|
||||
};
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ontime",
|
||||
"version": "3.0.0-beta.2",
|
||||
"version": "3.0.0",
|
||||
"description": "Time keeping for live events",
|
||||
"keywords": [
|
||||
"lightdev",
|
||||
|
||||
@@ -13,11 +13,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"deepmerge-ts": "^5.1.0",
|
||||
"luxon": "^3.4.4",
|
||||
"nanoid": "^5.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/luxon": "^3.4.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.10.0",
|
||||
"@typescript-eslint/parser": "^6.10.0",
|
||||
"eslint": "^8.53.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { dayInMs } from '../timeConstants';
|
||||
import { MILLIS_PER_HOUR } from './conversionUtils';
|
||||
import { millisToString, removeLeadingZero } from './timeFormatting';
|
||||
import { formatFromMillis, millisToString, removeLeadingZero } from './timeFormatting';
|
||||
|
||||
describe('millisToString()', () => {
|
||||
it('returns fallback if millis is null', () => {
|
||||
@@ -61,3 +61,159 @@ describe('removeLeadingZero()', () => {
|
||||
expect(removeLeadingZero('-00:08:47')).toBe('-8:47');
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatFromMillis()', () => {
|
||||
it('milliseconds', () => {
|
||||
const millis = 76211123; // Jan 1, 1970 21:10:11.123 UTC
|
||||
const format = 'S';
|
||||
const expectedResult = '123';
|
||||
expect(formatFromMillis(millis, format)).toBe(expectedResult);
|
||||
});
|
||||
|
||||
it('seconds (no padding)', () => {
|
||||
const millis = 76211123;
|
||||
const format = 's';
|
||||
const expectedResult = '11';
|
||||
expect(formatFromMillis(millis, format)).toBe(expectedResult);
|
||||
});
|
||||
|
||||
it('seconds (padding)', () => {
|
||||
const millis = 76211123;
|
||||
const format = 'ss';
|
||||
const expectedResult = '11';
|
||||
expect(formatFromMillis(millis, format)).toBe(expectedResult);
|
||||
});
|
||||
|
||||
it('minute (no padding)', () => {
|
||||
const millis = 76211123;
|
||||
const format = 'm';
|
||||
const expectedResult = '10';
|
||||
expect(formatFromMillis(millis, format)).toBe(expectedResult);
|
||||
});
|
||||
|
||||
it('minute (padding)', () => {
|
||||
const millis = 76211123;
|
||||
const format = 'mm';
|
||||
const expectedResult = '10';
|
||||
expect(formatFromMillis(millis, format)).toBe(expectedResult);
|
||||
});
|
||||
|
||||
it('hour - 12 (no padding)', () => {
|
||||
const millis = 76211123;
|
||||
const format = 'h';
|
||||
const expectedResult = '9';
|
||||
expect(formatFromMillis(millis, format)).toBe(expectedResult);
|
||||
});
|
||||
|
||||
it('hour - 12 (padding)', () => {
|
||||
const millis = 76211123;
|
||||
const format = 'hh';
|
||||
const expectedResult = '09';
|
||||
expect(formatFromMillis(millis, format)).toBe(expectedResult);
|
||||
});
|
||||
|
||||
it('hour - 24 (no padding)', () => {
|
||||
const millis = 76211123;
|
||||
const format = 'H';
|
||||
const expectedResult = '21';
|
||||
expect(formatFromMillis(millis, format)).toBe(expectedResult);
|
||||
});
|
||||
|
||||
it('hour - 24 (padding)', () => {
|
||||
const millis = 76211123;
|
||||
const format = 'HH';
|
||||
const expectedResult = '21';
|
||||
expect(formatFromMillis(millis, format)).toBe(expectedResult);
|
||||
});
|
||||
|
||||
it('formatted time HH:mm:ss', () => {
|
||||
const millis = 76211123;
|
||||
const format = 'HH:mm:ss';
|
||||
const expectedResult = '21:10:11';
|
||||
expect(formatFromMillis(millis, format)).toBe(expectedResult);
|
||||
});
|
||||
|
||||
it('formatted time hh:mm a', () => {
|
||||
const millis = 76211123;
|
||||
const format = 'hh:mm a';
|
||||
const expectedResult = '09:10 PM';
|
||||
expect(formatFromMillis(millis, format)).toBe(expectedResult);
|
||||
});
|
||||
|
||||
it('milliseconds', () => {
|
||||
const millis = 29167345; // 08:06:08.345
|
||||
const format = 'S';
|
||||
const expectedResult = '345';
|
||||
expect(formatFromMillis(millis, format)).toBe(expectedResult);
|
||||
});
|
||||
|
||||
it('seconds (no padding)', () => {
|
||||
const millis = 29167345;
|
||||
const format = 's';
|
||||
const expectedResult = '7';
|
||||
expect(formatFromMillis(millis, format)).toBe(expectedResult);
|
||||
});
|
||||
|
||||
it('seconds (padding)', () => {
|
||||
const millis = 29167345;
|
||||
const format = 'ss';
|
||||
const expectedResult = '07';
|
||||
expect(formatFromMillis(millis, format)).toBe(expectedResult);
|
||||
});
|
||||
|
||||
it('minute (no padding)', () => {
|
||||
const millis = 29167345;
|
||||
const format = 'm';
|
||||
const expectedResult = '6';
|
||||
expect(formatFromMillis(millis, format)).toBe(expectedResult);
|
||||
});
|
||||
|
||||
it('minute (padding)', () => {
|
||||
const millis = 29167345;
|
||||
const format = 'mm';
|
||||
const expectedResult = '06';
|
||||
expect(formatFromMillis(millis, format)).toBe(expectedResult);
|
||||
});
|
||||
|
||||
it('hour - 12 (no padding)', () => {
|
||||
const millis = 29167345;
|
||||
const format = 'h';
|
||||
const expectedResult = '8';
|
||||
expect(formatFromMillis(millis, format)).toBe(expectedResult);
|
||||
});
|
||||
|
||||
it('hour - 12 (padding)', () => {
|
||||
const millis = 29167345;
|
||||
const format = 'hh';
|
||||
const expectedResult = '08';
|
||||
expect(formatFromMillis(millis, format)).toBe(expectedResult);
|
||||
});
|
||||
|
||||
it('hour - 24 (no padding)', () => {
|
||||
const millis = 29167345;
|
||||
const format = 'H';
|
||||
const expectedResult = '8';
|
||||
expect(formatFromMillis(millis, format)).toBe(expectedResult);
|
||||
});
|
||||
|
||||
it('hour - 24 (padding)', () => {
|
||||
const millis = 29167345;
|
||||
const format = 'HH';
|
||||
const expectedResult = '08';
|
||||
expect(formatFromMillis(millis, format)).toBe(expectedResult);
|
||||
});
|
||||
|
||||
it('formatted time HH:mm:ss', () => {
|
||||
const millis = 29167345;
|
||||
const format = 'HH:mm:ss';
|
||||
const expectedResult = '08:06:07';
|
||||
expect(formatFromMillis(millis, format)).toBe(expectedResult);
|
||||
});
|
||||
|
||||
it('formatted time hh:mm a', () => {
|
||||
const millis = 29167345;
|
||||
const format = 'hh:mm a';
|
||||
const expectedResult = '08:06 AM';
|
||||
expect(formatFromMillis(millis, format)).toBe(expectedResult);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { DateTime } from 'luxon';
|
||||
import type { MaybeNumber } from 'ontime-types';
|
||||
|
||||
import { millisToHours, millisToMinutes, millisToSeconds } from './conversionUtils.js';
|
||||
@@ -71,11 +70,60 @@ export function removeSeconds(timer: string): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* @description utility function to format a date in milliseconds using luxon
|
||||
* @param {number} millis
|
||||
* @param {string} format
|
||||
* @return {string}
|
||||
* Formats a given date into a custom string format based on UTC time.
|
||||
*
|
||||
* @param millis - The number of milliseconds.
|
||||
* @param format - A string specifying the desired output format.
|
||||
* For example, 'ss' will format the millis as '07' seconds.
|
||||
*
|
||||
* @returns The formatted date as a string according to the provided `format` string.
|
||||
* If input `millis` is smaller than zero, it returns undefined.
|
||||
*
|
||||
*/
|
||||
export function formatFromMillis(millis: number, format: string): string {
|
||||
return DateTime.fromMillis(millis).toUTC().toFormat(format);
|
||||
export function formatFromMillis(millis: number, format: string): string | undefined {
|
||||
if (millis < 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const date: Date = new Date(millis);
|
||||
|
||||
const hour24Padded = date.getUTCHours().toString().padStart(2, '0');
|
||||
const hour24 = date.getUTCHours().toString();
|
||||
const minutePadded = date.getUTCMinutes().toString().padStart(2, '0');
|
||||
const minute = date.getUTCMinutes().toString();
|
||||
const secondPadded = date.getUTCSeconds().toString().padStart(2, '0');
|
||||
const second = date.getUTCSeconds().toString();
|
||||
const milliseconds = date.getUTCMilliseconds().toString().padStart(3, '0');
|
||||
const hour12 = ((date.getUTCHours() % 12) || 12).toString();
|
||||
const hour12Padded = hour12.padStart(2, '0');
|
||||
const amPm = date.getUTCHours() >= 12 ? 'PM' : 'AM';
|
||||
|
||||
const replacements: Record<string, string> = {
|
||||
'HH': hour24Padded,
|
||||
'H': hour24,
|
||||
'hh': hour12Padded,
|
||||
'h': hour12,
|
||||
'mm': minutePadded,
|
||||
'm': minute,
|
||||
'ss': secondPadded,
|
||||
's': second,
|
||||
'S': milliseconds,
|
||||
'a': amPm
|
||||
};
|
||||
|
||||
return applyReplacements(format, replacements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies replacements to a template string based on a dictionary of tokens and their corresponding values.
|
||||
*
|
||||
* @param {string} template - The format template string containing tokens to be replaced.
|
||||
* @param {Record<string, string>} replacements - A record of tokens and their corresponding values.
|
||||
* @returns {string} The formatted string with all tokens replaced by their values.
|
||||
*/
|
||||
function applyReplacements(template: string, replacements: Record<string, string>): string {
|
||||
return Object.keys(replacements).reduce((result, token) => {
|
||||
const regex = new RegExp(`\\b${token}\\b`, 'g');
|
||||
return result.replace(regex, replacements[token]);
|
||||
}, template);
|
||||
}
|
||||
|
||||
Generated
+174
-58
@@ -68,6 +68,9 @@ importers:
|
||||
'@emotion/styled':
|
||||
specifier: ^11.10.6
|
||||
version: 11.10.6(@emotion/react@11.10.6)(@types/react@18.0.26)(react@18.2.0)
|
||||
'@fontsource/open-sans':
|
||||
specifier: ^5.0.28
|
||||
version: 5.0.28
|
||||
'@mantine/hooks':
|
||||
specifier: ^7.6.2
|
||||
version: 7.6.2(react@18.2.0)
|
||||
@@ -122,9 +125,6 @@ importers:
|
||||
react-router-dom:
|
||||
specifier: ^6.3.0
|
||||
version: 6.6.2(react-dom@18.2.0)(react@18.2.0)
|
||||
typeface-open-sans:
|
||||
specifier: ^1.1.13
|
||||
version: 1.1.13
|
||||
web-vitals:
|
||||
specifier: ^3.1.1
|
||||
version: 3.1.1
|
||||
@@ -232,8 +232,8 @@ importers:
|
||||
specifier: ^28.0.0
|
||||
version: 28.0.0
|
||||
electron-builder:
|
||||
specifier: ^24.9.1
|
||||
version: 24.9.1
|
||||
specifier: ^24.13.3
|
||||
version: 24.13.3(electron-builder-squirrel-windows@24.13.3)
|
||||
eslint:
|
||||
specifier: ^8.53.0
|
||||
version: 8.53.0
|
||||
@@ -391,16 +391,10 @@ importers:
|
||||
deepmerge-ts:
|
||||
specifier: ^5.1.0
|
||||
version: 5.1.0
|
||||
luxon:
|
||||
specifier: ^3.4.4
|
||||
version: 3.4.4
|
||||
nanoid:
|
||||
specifier: ^5.0.4
|
||||
version: 5.0.4
|
||||
devDependencies:
|
||||
'@types/luxon':
|
||||
specifier: ^3.4.0
|
||||
version: 3.4.0
|
||||
'@typescript-eslint/eslint-plugin':
|
||||
specifier: ^6.10.0
|
||||
version: 6.10.0(@typescript-eslint/parser@6.10.0)(eslint@8.53.0)(typescript@5.4.3)
|
||||
@@ -1906,8 +1900,8 @@ packages:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@electron/notarize@2.1.0:
|
||||
resolution: {integrity: sha512-Q02xem1D0sg4v437xHgmBLxI2iz/fc0D4K7fiVWHa/AnW8o7D751xyKNXgziA6HrTOme9ul1JfWN5ark8WH1xA==}
|
||||
/@electron/notarize@2.2.1:
|
||||
resolution: {integrity: sha512-aL+bFMIkpR0cmmj5Zgy0LMKEpgy43/hw5zadEArgmAMWWlKc5buwFvFT9G/o/YJkvXAJm5q3iuTuLaiaXW39sg==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
dependencies:
|
||||
debug: 4.3.4
|
||||
@@ -1932,8 +1926,8 @@ packages:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@electron/universal@1.4.1:
|
||||
resolution: {integrity: sha512-lE/U3UNw1YHuowNbTmKNs9UlS3En3cPgwM5MI+agIgr/B1hSze9NdOP0qn7boZaI9Lph8IDv3/24g9IxnJP7aQ==}
|
||||
/@electron/universal@1.5.1:
|
||||
resolution: {integrity: sha512-kbgXxyEauPJiQQUNG2VgUeyfQNFk6hBF11ISN2PNI6agUgPl55pv4eQmaqHzTAzchBvqZ2tQuRVaPStGf0mxGw==}
|
||||
engines: {node: '>=8.6'}
|
||||
dependencies:
|
||||
'@electron/asar': 3.2.8
|
||||
@@ -2321,6 +2315,10 @@ packages:
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
dev: true
|
||||
|
||||
/@fontsource/open-sans@5.0.28:
|
||||
resolution: {integrity: sha512-hBvJHY76pJT/JynGUB5EXWhnzjYfLdcMn655J5p1v9lTT9HdQSy+keq2KPVXO2Htlg998BBa3p6u/jlrZ6w0kg==}
|
||||
dev: false
|
||||
|
||||
/@googleapis/sheets@5.0.5:
|
||||
resolution: {integrity: sha512-XMoONmgAJm2jYeTYHX4054VcEkElxlgqmnHvt0wAurzEHoGJLdUHhTAJXGPLgSs4WVMPtgU8HLrmk7/U+Qlw7A==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
@@ -3308,10 +3306,6 @@ packages:
|
||||
resolution: {integrity: sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==}
|
||||
dev: false
|
||||
|
||||
/@types/luxon@3.4.0:
|
||||
resolution: {integrity: sha512-PEVoA4MOfSsFNaPrZjIUGUZujBDxnO/tj2A2N9KfzlR+pNgpBdDuk0TmRvSMAVUP5q4q8IkMEZ8UOp3MIr+QgA==}
|
||||
dev: true
|
||||
|
||||
/@types/mime@3.0.1:
|
||||
resolution: {integrity: sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==}
|
||||
dev: true
|
||||
@@ -3924,25 +3918,29 @@ packages:
|
||||
resolution: {integrity: sha512-xwdG0FJPQMe0M0UA4Tz0zEB8rBJTRA5a476ZawAqiBkMv16GRK5xpXThOjMaEOFnZ6zabejjG4J3da0SXG63KA==}
|
||||
dev: true
|
||||
|
||||
/app-builder-lib@24.9.1:
|
||||
resolution: {integrity: sha512-Q1nYxZcio4r+W72cnIRVYofEAyjBd3mG47o+zms8HlD51zWtA/YxJb01Jei5F+jkWhge/PTQK+uldsPh6d0/4g==}
|
||||
/app-builder-lib@24.13.3(dmg-builder@24.13.3)(electron-builder-squirrel-windows@24.13.3):
|
||||
resolution: {integrity: sha512-FAzX6IBit2POXYGnTCT8YHFO/lr5AapAII6zzhQO3Rw4cEDOgK+t1xhLc5tNcKlicTHlo9zxIwnYCX9X2DLkig==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
dmg-builder: 24.13.3
|
||||
electron-builder-squirrel-windows: 24.13.3
|
||||
dependencies:
|
||||
7zip-bin: 5.2.0
|
||||
'@develar/schema-utils': 2.6.5
|
||||
'@electron/notarize': 2.1.0
|
||||
'@electron/notarize': 2.2.1
|
||||
'@electron/osx-sign': 1.0.5
|
||||
'@electron/universal': 1.4.1
|
||||
'@electron/universal': 1.5.1
|
||||
'@malept/flatpak-bundler': 0.4.0
|
||||
'@types/fs-extra': 9.0.13
|
||||
async-exit-hook: 2.0.1
|
||||
bluebird-lst: 1.0.9
|
||||
builder-util: 24.8.1
|
||||
builder-util-runtime: 9.2.3
|
||||
builder-util: 24.13.1
|
||||
builder-util-runtime: 9.2.4
|
||||
chromium-pickle-js: 0.2.0
|
||||
debug: 4.3.4
|
||||
dmg-builder: 24.13.3(electron-builder-squirrel-windows@24.13.3)
|
||||
ejs: 3.1.9
|
||||
electron-publish: 24.8.1
|
||||
electron-builder-squirrel-windows: 24.13.3(dmg-builder@24.13.3)
|
||||
electron-publish: 24.13.1
|
||||
form-data: 4.0.0
|
||||
fs-extra: 10.1.0
|
||||
hosted-git-info: 4.1.0
|
||||
@@ -3964,6 +3962,38 @@ packages:
|
||||
resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==}
|
||||
dev: false
|
||||
|
||||
/archiver-utils@2.1.0:
|
||||
resolution: {integrity: sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==}
|
||||
engines: {node: '>= 6'}
|
||||
dependencies:
|
||||
glob: 7.2.3
|
||||
graceful-fs: 4.2.11
|
||||
lazystream: 1.0.1
|
||||
lodash.defaults: 4.2.0
|
||||
lodash.difference: 4.5.0
|
||||
lodash.flatten: 4.4.0
|
||||
lodash.isplainobject: 4.0.6
|
||||
lodash.union: 4.6.0
|
||||
normalize-path: 3.0.0
|
||||
readable-stream: 2.3.7
|
||||
dev: true
|
||||
|
||||
/archiver-utils@3.0.4:
|
||||
resolution: {integrity: sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==}
|
||||
engines: {node: '>= 10'}
|
||||
dependencies:
|
||||
glob: 7.2.3
|
||||
graceful-fs: 4.2.11
|
||||
lazystream: 1.0.1
|
||||
lodash.defaults: 4.2.0
|
||||
lodash.difference: 4.5.0
|
||||
lodash.flatten: 4.4.0
|
||||
lodash.isplainobject: 4.0.6
|
||||
lodash.union: 4.6.0
|
||||
normalize-path: 3.0.0
|
||||
readable-stream: 3.6.2
|
||||
dev: true
|
||||
|
||||
/archiver-utils@4.0.1:
|
||||
resolution: {integrity: sha512-Q4Q99idbvzmgCTEAAhi32BkOyq8iVI5EwdO0PmBDSGIzzjYNdcFn7Q7k3OzbLy4kLUPXfJtG6fO2RjftXbobBg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
@@ -3976,6 +4006,19 @@ packages:
|
||||
readable-stream: 3.6.2
|
||||
dev: true
|
||||
|
||||
/archiver@5.3.2:
|
||||
resolution: {integrity: sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==}
|
||||
engines: {node: '>= 10'}
|
||||
dependencies:
|
||||
archiver-utils: 2.1.0
|
||||
async: 3.2.5
|
||||
buffer-crc32: 0.2.13
|
||||
readable-stream: 3.6.2
|
||||
readdir-glob: 1.1.3
|
||||
tar-stream: 2.2.0
|
||||
zip-stream: 4.1.1
|
||||
dev: true
|
||||
|
||||
/archiver@6.0.1:
|
||||
resolution: {integrity: sha512-CXGy4poOLBKptiZH//VlWdFuUC1RESbdZjGjILwBuZ73P7WkAUN0htfSfBq/7k6FRFlpu7bg4JOkj1vU9G6jcQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
@@ -4143,6 +4186,14 @@ packages:
|
||||
resolution: {integrity: sha512-KcSrsGiIKgklTWweVb9XnZPWO1/rGSsK3fwR7VnbDPbLKPlkvSKd/ZrJ1W712r6HzH5u0fa/AZCftATO09x8Aw==}
|
||||
dev: false
|
||||
|
||||
/bl@4.1.0:
|
||||
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
|
||||
dependencies:
|
||||
buffer: 5.7.1
|
||||
inherits: 2.0.4
|
||||
readable-stream: 3.6.2
|
||||
dev: true
|
||||
|
||||
/bluebird-lst@1.0.9:
|
||||
resolution: {integrity: sha512-7B1Rtx82hjnSD4PGLAjVWeYH3tHAcVUmChh85a3lltKQm6FresXh9ErQo6oAv6CqxttczC3/kEg8SY5NluPuUw==}
|
||||
dependencies:
|
||||
@@ -4240,10 +4291,9 @@ packages:
|
||||
base64-js: 1.5.1
|
||||
ieee754: 1.2.1
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/builder-util-runtime@9.2.3:
|
||||
resolution: {integrity: sha512-FGhkqXdFFZ5dNC4C+yuQB9ak311rpGAw+/ASz8ZdxwODCv1GGMWgLDeofRkdi0F3VCHQEWy/aXcJQozx2nOPiw==}
|
||||
/builder-util-runtime@9.2.4:
|
||||
resolution: {integrity: sha512-upp+biKpN/XZMLim7aguUyW8s0FUpDvOtK6sbanMFDAMBzpHDqdhgVYm6zc9HJ6nWo7u2Lxk60i2M6Jd3aiNrA==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
dependencies:
|
||||
debug: 4.3.4
|
||||
@@ -4252,14 +4302,14 @@ packages:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/builder-util@24.8.1:
|
||||
resolution: {integrity: sha512-ibmQ4BnnqCnJTNrdmdNlnhF48kfqhNzSeqFMXHLIl+o9/yhn6QfOaVrloZ9YUu3m0k3rexvlT5wcki6LWpjTZw==}
|
||||
/builder-util@24.13.1:
|
||||
resolution: {integrity: sha512-NhbCSIntruNDTOVI9fdXz0dihaqX2YuE1D6zZMrwiErzH4ELZHE6mdiB40wEgZNprDia+FghRFgKoAqMZRRjSA==}
|
||||
dependencies:
|
||||
7zip-bin: 5.2.0
|
||||
'@types/debug': 4.1.12
|
||||
app-builder-bin: 4.0.0
|
||||
bluebird-lst: 1.0.9
|
||||
builder-util-runtime: 9.2.3
|
||||
builder-util-runtime: 9.2.4
|
||||
chalk: 4.1.2
|
||||
cross-spawn: 7.0.3
|
||||
debug: 4.3.4
|
||||
@@ -4537,6 +4587,16 @@ packages:
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/compress-commons@4.1.2:
|
||||
resolution: {integrity: sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==}
|
||||
engines: {node: '>= 10'}
|
||||
dependencies:
|
||||
buffer-crc32: 0.2.13
|
||||
crc32-stream: 4.0.3
|
||||
normalize-path: 3.0.0
|
||||
readable-stream: 3.6.2
|
||||
dev: true
|
||||
|
||||
/compress-commons@5.0.1:
|
||||
resolution: {integrity: sha512-MPh//1cERdLtqwO3pOFLeXtpuai0Y2WCd5AhtKxznqM7WtaMYaOEMSgn45d9D10sIHSfIKE603HlOp8OPGrvag==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
@@ -4661,6 +4721,14 @@ packages:
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/crc32-stream@4.0.3:
|
||||
resolution: {integrity: sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==}
|
||||
engines: {node: '>= 10'}
|
||||
dependencies:
|
||||
crc-32: 1.2.2
|
||||
readable-stream: 3.6.2
|
||||
dev: true
|
||||
|
||||
/crc32-stream@5.0.0:
|
||||
resolution: {integrity: sha512-B0EPa1UK+qnpBZpG+7FgPCu0J2ETLpXq09o9BkLkEAhdB6Z61Qo4pJ3JYu0c+Qi+/SAL7QThqnzS06pmSSyZaw==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
@@ -4926,18 +4994,19 @@ packages:
|
||||
path-type: 4.0.0
|
||||
dev: true
|
||||
|
||||
/dmg-builder@24.9.1:
|
||||
resolution: {integrity: sha512-huC+O6hvHd24Ubj3cy2GMiGLe2xGFKN3klqVMLAdcbB6SWMd1yPSdZvV8W1O01ICzCCRlZDHiv4VrNUgnPUfbQ==}
|
||||
/dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3):
|
||||
resolution: {integrity: sha512-rcJUkMfnJpfCboZoOOPf4L29TRtEieHNOeAbYPWPxlaBw/Z1RKrRA86dOI9rwaI4tQSc/RD82zTNHprfUHXsoQ==}
|
||||
dependencies:
|
||||
app-builder-lib: 24.9.1
|
||||
builder-util: 24.8.1
|
||||
builder-util-runtime: 9.2.3
|
||||
app-builder-lib: 24.13.3(dmg-builder@24.13.3)(electron-builder-squirrel-windows@24.13.3)
|
||||
builder-util: 24.13.1
|
||||
builder-util-runtime: 9.2.4
|
||||
fs-extra: 10.1.0
|
||||
iconv-lite: 0.6.3
|
||||
js-yaml: 4.1.0
|
||||
optionalDependencies:
|
||||
dmg-license: 1.0.11
|
||||
transitivePeerDependencies:
|
||||
- electron-builder-squirrel-windows
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
@@ -5036,16 +5105,28 @@ packages:
|
||||
jake: 10.8.7
|
||||
dev: true
|
||||
|
||||
/electron-builder@24.9.1:
|
||||
resolution: {integrity: sha512-v7BuakDuY6sKMUYM8mfQGrwyjBpZ/ObaqnenU0H+igEL10nc6ht049rsCw2HghRBdEwJxGIBuzs3jbEhNaMDmg==}
|
||||
/electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3):
|
||||
resolution: {integrity: sha512-oHkV0iogWfyK+ah9ZIvMDpei1m9ZRpdXcvde1wTpra2U8AFDNNpqJdnin5z+PM1GbQ5BoaKCWas2HSjtR0HwMg==}
|
||||
dependencies:
|
||||
app-builder-lib: 24.13.3(dmg-builder@24.13.3)(electron-builder-squirrel-windows@24.13.3)
|
||||
archiver: 5.3.2
|
||||
builder-util: 24.13.1
|
||||
fs-extra: 10.1.0
|
||||
transitivePeerDependencies:
|
||||
- dmg-builder
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/electron-builder@24.13.3(electron-builder-squirrel-windows@24.13.3):
|
||||
resolution: {integrity: sha512-yZSgVHft5dNVlo31qmJAe4BVKQfFdwpRw7sFp1iQglDRCDD6r22zfRJuZlhtB5gp9FHUxCMEoWGq10SkCnMAIg==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
app-builder-lib: 24.9.1
|
||||
builder-util: 24.8.1
|
||||
builder-util-runtime: 9.2.3
|
||||
app-builder-lib: 24.13.3(dmg-builder@24.13.3)(electron-builder-squirrel-windows@24.13.3)
|
||||
builder-util: 24.13.1
|
||||
builder-util-runtime: 9.2.4
|
||||
chalk: 4.1.2
|
||||
dmg-builder: 24.9.1
|
||||
dmg-builder: 24.13.3(electron-builder-squirrel-windows@24.13.3)
|
||||
fs-extra: 10.1.0
|
||||
is-ci: 3.0.1
|
||||
lazy-val: 1.0.5
|
||||
@@ -5053,15 +5134,16 @@ packages:
|
||||
simple-update-notifier: 2.0.0
|
||||
yargs: 17.7.2
|
||||
transitivePeerDependencies:
|
||||
- electron-builder-squirrel-windows
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/electron-publish@24.8.1:
|
||||
resolution: {integrity: sha512-IFNXkdxMVzUdweoLJNXSupXkqnvgbrn3J4vognuOY06LaS/m0xvfFYIf+o1CM8if6DuWYWoQFKPcWZt/FUjZPw==}
|
||||
/electron-publish@24.13.1:
|
||||
resolution: {integrity: sha512-2ZgdEqJ8e9D17Hwp5LEq5mLQPjqU3lv/IALvgp+4W8VeNhryfGhYEQC/PgDPMrnWUp+l60Ou5SJLsu+k4mhQ8A==}
|
||||
dependencies:
|
||||
'@types/fs-extra': 9.0.13
|
||||
builder-util: 24.8.1
|
||||
builder-util-runtime: 9.2.3
|
||||
builder-util: 24.13.1
|
||||
builder-util-runtime: 9.2.4
|
||||
chalk: 4.1.2
|
||||
fs-extra: 10.1.0
|
||||
lazy-val: 1.0.5
|
||||
@@ -5874,6 +5956,10 @@ packages:
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/fs-constants@1.0.0:
|
||||
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
|
||||
dev: true
|
||||
|
||||
/fs-extra@10.1.0:
|
||||
resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -6398,7 +6484,6 @@ packages:
|
||||
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/ignore-by-default@1.0.1:
|
||||
resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==}
|
||||
@@ -6981,6 +7066,22 @@ packages:
|
||||
p-locate: 5.0.0
|
||||
dev: true
|
||||
|
||||
/lodash.defaults@4.2.0:
|
||||
resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==}
|
||||
dev: true
|
||||
|
||||
/lodash.difference@4.5.0:
|
||||
resolution: {integrity: sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==}
|
||||
dev: true
|
||||
|
||||
/lodash.flatten@4.4.0:
|
||||
resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==}
|
||||
dev: true
|
||||
|
||||
/lodash.isplainobject@4.0.6:
|
||||
resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
|
||||
dev: true
|
||||
|
||||
/lodash.merge@4.6.2:
|
||||
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
|
||||
dev: true
|
||||
@@ -6989,6 +7090,10 @@ packages:
|
||||
resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==}
|
||||
dev: false
|
||||
|
||||
/lodash.union@4.6.0:
|
||||
resolution: {integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==}
|
||||
dev: true
|
||||
|
||||
/lodash@4.17.21:
|
||||
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
||||
|
||||
@@ -7056,11 +7161,6 @@ packages:
|
||||
yallist: 4.0.0
|
||||
dev: true
|
||||
|
||||
/luxon@3.4.4:
|
||||
resolution: {integrity: sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==}
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/lz-string@1.4.4:
|
||||
resolution: {integrity: sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ==}
|
||||
hasBin: true
|
||||
@@ -8728,6 +8828,17 @@ packages:
|
||||
tslib: 2.6.2
|
||||
dev: true
|
||||
|
||||
/tar-stream@2.2.0:
|
||||
resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==}
|
||||
engines: {node: '>=6'}
|
||||
dependencies:
|
||||
bl: 4.1.0
|
||||
end-of-stream: 1.4.4
|
||||
fs-constants: 1.0.0
|
||||
inherits: 2.0.4
|
||||
readable-stream: 3.6.2
|
||||
dev: true
|
||||
|
||||
/tar-stream@3.1.7:
|
||||
resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==}
|
||||
dependencies:
|
||||
@@ -9053,10 +9164,6 @@ packages:
|
||||
resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==}
|
||||
dev: false
|
||||
|
||||
/typeface-open-sans@1.1.13:
|
||||
resolution: {integrity: sha512-lVGVHvYl7UJDFB9vN8r7NHw3sVm7Rjeow6b9AeABc/J+2mDaCkmcdVtw3QZnsJW39P+xm5zeggIj9gLHYGn9Iw==}
|
||||
dev: false
|
||||
|
||||
/typescript@4.9.5:
|
||||
resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==}
|
||||
engines: {node: '>=4.2.0'}
|
||||
@@ -9634,6 +9741,15 @@ packages:
|
||||
engines: {node: '>=12.20'}
|
||||
dev: true
|
||||
|
||||
/zip-stream@4.1.1:
|
||||
resolution: {integrity: sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==}
|
||||
engines: {node: '>= 10'}
|
||||
dependencies:
|
||||
archiver-utils: 3.0.4
|
||||
compress-commons: 4.1.2
|
||||
readable-stream: 3.6.2
|
||||
dev: true
|
||||
|
||||
/zip-stream@5.0.1:
|
||||
resolution: {integrity: sha512-UfZ0oa0C8LI58wJ+moL46BDIMgCQbnsb+2PoiJYtonhBsMh2bq1eRBVkvjfVsqbEHd9/EgKPUuL9saSSsec8OA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
|
||||
Reference in New Issue
Block a user