Skip to content

Commit

Permalink
Merge branch 'release51'
Browse files Browse the repository at this point in the history
  • Loading branch information
jstarpl committed Jan 7, 2025
2 parents 9aa62dd + 4253eea commit a7deec9
Show file tree
Hide file tree
Showing 33 changed files with 567 additions and 273 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/node.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -748,6 +748,6 @@ jobs:
CI: true
- name: Run check
run: |
node scripts/checkForMultipleVersions.mjs
yarn validate:versions
env:
CI: true
17 changes: 17 additions & 0 deletions meteor/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@

All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.

### [1.51.5](///compare/v1.51.4...v1.51.5) (2025-01-07)


### Bug Fixes

* **job-worker/playout:** Hold mode doesn't work at all a7d6999

### [1.51.4](///compare/v1.51.3...v1.51.4) (2024-12-04)


### Bug Fixes

* Device Action Studio Context gets lost, Adlib previews are unstable 193815d
* Live Status Gateway Dockerfile (regular) still uses yarn to start 0ae53c4
* release scripts broken on Windows 9636051
* RundownView shows spinner when unMOSing a Rundown from a Playlist 874e85c

### [1.51.2](https://github.com/nrkno/tv-automation-server-core/compare/v1.51.1...v1.51.2) (2024-11-21)


Expand Down
136 changes: 127 additions & 9 deletions meteor/client/lib/ReactMeteorData/ReactMeteorData.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable react/prefer-stateless-function */

import React, { useState, useEffect, useRef } from 'react'
import React, { useState, useEffect, useRef, useCallback } from 'react'
import { Meteor } from 'meteor/meteor'
import { Mongo } from 'meteor/mongo'
import { Tracker } from 'meteor/tracker'
Expand All @@ -13,6 +13,8 @@ const globalTrackerQueue: Array<Function> = []
let globalTrackerTimestamp: number | undefined = undefined
let globalTrackerTimeout: number | undefined = undefined

const SUBSCRIPTION_TIMEOUT = 1000

/**
* Delay an update to be batched with the global tracker invalidation queue
*/
Expand Down Expand Up @@ -370,6 +372,61 @@ export function useTracker<T, K extends undefined | T = undefined>(
return meteorData
}

/**
* A hook to track a boolean state with a sort of histeresis, with preference for `true`. `setState` makes the returned
* `state` be `true` immediately, but `false` only after `resetState` is called and `timeout` elapses. If `setState`
* is called with `true` before `timeout` elapses, then `resetState` is aborted and `state` will remain `ture.
*
* Later `resetState` calls replace earlier unelapsed calls and their timeout periods.
*
* @param {boolean} [initialState=false]
* @return {*} {{
* state: boolean
* setState: (value: boolean) => void
* resetState: (timeout: number) => void
* }}
*/
function useDelayState(initialState = false): {
state: boolean
setState: (value: boolean) => void
resetState: (timeout: number) => void
} {
const [state, setState] = useState(initialState)
const [prevState, setPrevState] = useState(initialState)
const prevReadyTimeoutRef = useRef<number | null>(null)

const setStateAndClearResets = useCallback(
(value: boolean) => {
setState(value)

if (value) {
setPrevState(true)
if (prevReadyTimeoutRef.current !== null) {
window.clearTimeout(prevReadyTimeoutRef.current)
prevReadyTimeoutRef.current = null
}
}
},
[setState, setPrevState]
)

const resetStateAfterDelay = useCallback((timeout: number) => {
if (prevReadyTimeoutRef.current !== null) {
window.clearTimeout(prevReadyTimeoutRef.current)
}
prevReadyTimeoutRef.current = window.setTimeout(() => {
prevReadyTimeoutRef.current = null
setPrevState(false)
}, timeout)
}, [])

return {
state: state || prevState,
setState: setStateAndClearResets,
resetState: resetStateAfterDelay,
}
}

/**
* A Meteor Subscription hook that allows using React Functional Components and the Hooks API with Meteor subscriptions.
* Subscriptions will be torn down 1000ms after unmounting the component.
Expand All @@ -383,20 +440,28 @@ export function useSubscription<K extends keyof AllPubSubTypes>(
sub: K,
...args: Parameters<AllPubSubTypes[K]>
): boolean {
const [ready, setReady] = useState<boolean>(false)
const { state: ready, setState: setReady, resetState: cancelPreviousReady } = useDelayState()

useEffect(() => {
const subscription = Tracker.nonreactive(() => meteorSubscribe(sub, ...args))
const isReadyComp = Tracker.nonreactive(() => Tracker.autorun(() => setReady(subscription.ready())))
const isReadyComp = Tracker.nonreactive(() =>
Tracker.autorun(() => {
const isNowReady = subscription.ready()
setReady(isNowReady)
})
)
return () => {
isReadyComp.stop()
setTimeout(() => {
subscription.stop()
}, 1000)
}, SUBSCRIPTION_TIMEOUT)
cancelPreviousReady(SUBSCRIPTION_TIMEOUT)
}
}, [sub, stringifyObjects(args)])

return ready
const isReady = ready

return isReady
}

/**
Expand All @@ -415,7 +480,7 @@ export function useSubscriptionIfEnabled<K extends keyof AllPubSubTypes>(
enable: boolean,
...args: Parameters<AllPubSubTypes[K]>
): boolean {
const [ready, setReady] = useState<boolean>(false)
const { state: ready, setState: setReady, resetState: cancelPreviousReady } = useDelayState()

useEffect(() => {
if (!enable) {
Expand All @@ -424,16 +489,69 @@ export function useSubscriptionIfEnabled<K extends keyof AllPubSubTypes>(
}

const subscription = Tracker.nonreactive(() => meteorSubscribe(sub, ...args))
const isReadyComp = Tracker.nonreactive(() => Tracker.autorun(() => setReady(subscription.ready())))
const isReadyComp = Tracker.nonreactive(() =>
Tracker.autorun(() => {
const isNowReady = subscription.ready()
setReady(isNowReady)
})
)
return () => {
isReadyComp.stop()
setTimeout(() => {
subscription.stop()
}, 1000)
}, SUBSCRIPTION_TIMEOUT)
cancelPreviousReady(SUBSCRIPTION_TIMEOUT)
}
}, [sub, enable, stringifyObjects(args)])

return !enable || ready
const isReady = !enable || ready

return isReady
}

/**
* A Meteor Subscription hook that allows using React Functional Components and the Hooks API with Meteor subscriptions.
* Subscriptions will be torn down 1000ms after unmounting the component.
* If the subscription is not enabled, the subscription will not be created, and the ready state will always be true.
*
* @export
* @param {PubSub} sub The subscription to be subscribed to
* @param {boolean} enable Whether the subscription is enabled
* @param {...any[]} args A list of arugments for the subscription. This is used for optimizing the subscription across
* renders so that it isn't torn down and created for every render.
*/
export function useSubscriptionIfEnabledReadyOnce<K extends keyof AllPubSubTypes>(
sub: K,
enable: boolean,
...args: Parameters<AllPubSubTypes[K]>
): boolean {
const { state: ready, setState: setReady, resetState: cancelPreviousReady } = useDelayState()

useEffect(() => {
if (!enable) {
setReady(false)
return
}

const subscription = Tracker.nonreactive(() => meteorSubscribe(sub, ...args))
const isReadyComp = Tracker.nonreactive(() =>
Tracker.autorun(() => {
const isNowReady = subscription.ready()
if (isNowReady) setReady(true)
})
)
return () => {
isReadyComp.stop()
setTimeout(() => {
subscription.stop()
}, SUBSCRIPTION_TIMEOUT)
cancelPreviousReady(SUBSCRIPTION_TIMEOUT)
}
}, [sub, enable, stringifyObjects(args)])

const isReady = !enable || ready

return isReady
}

/**
Expand Down
15 changes: 11 additions & 4 deletions meteor/client/ui/RundownView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
Translated,
translateWithTracker,
useSubscriptionIfEnabled,
useSubscriptionIfEnabledReadyOnce,
useSubscriptions,
useTracker,
} from '../lib/ReactMeteorData/react-meteor-data'
Expand Down Expand Up @@ -1227,9 +1228,6 @@ export function RundownView(props: Readonly<IProps>): JSX.Element {

return playlist?.studioId
}, [playlistId])
// Load once the playlist is confirmed to exist
auxSubsReady.push(useSubscriptionIfEnabled(MeteorPubSub.uiSegmentPartNotes, !!playlistStudioId, playlistId))
auxSubsReady.push(useSubscriptionIfEnabled(MeteorPubSub.uiPieceContentStatuses, !!playlistStudioId, playlistId))
// Load only when the studio is known
requiredSubsReady.push(
useSubscriptionIfEnabled(MeteorPubSub.uiStudio, !!playlistStudioId, playlistStudioId ?? protectString(''))
Expand Down Expand Up @@ -1258,7 +1256,12 @@ export function RundownView(props: Readonly<IProps>): JSX.Element {
)
)
requiredSubsReady.push(
useSubscriptionIfEnabled(CorelibPubSub.showStyleVariants, showStyleVariantIds.length > 0, null, showStyleVariantIds)
useSubscriptionIfEnabledReadyOnce(
CorelibPubSub.showStyleVariants,
showStyleVariantIds.length > 0,
null,
showStyleVariantIds
)
)
auxSubsReady.push(
useSubscriptionIfEnabled(MeteorPubSub.rundownLayouts, showStyleBaseIds.length > 0, showStyleBaseIds)
Expand All @@ -1283,6 +1286,10 @@ export function RundownView(props: Readonly<IProps>): JSX.Element {
)
)

// Load once the playlist is confirmed to exist
auxSubsReady.push(useSubscriptionIfEnabled(MeteorPubSub.uiSegmentPartNotes, !!playlistStudioId, playlistId))
auxSubsReady.push(useSubscriptionIfEnabled(MeteorPubSub.uiPieceContentStatuses, !!playlistStudioId, playlistId))

useTracker(() => {
const playlist = RundownPlaylists.findOne(playlistId, {
fields: {
Expand Down
2 changes: 1 addition & 1 deletion meteor/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "automation-core",
"version": "1.51.3",
"version": "1.51.5",
"private": true,
"engines": {
"node": ">=14.19.1"
Expand Down
8 changes: 6 additions & 2 deletions meteor/server/api/deviceTriggers/RundownContentObserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export class RundownContentObserver {
#observers: Meteor.LiveQueryHandle[] = []
#cache: ContentCache
#cancelCache: () => void
#cleanup: () => void = () => {
#cleanup: (() => void) | undefined = () => {
throw new Error('RundownContentObserver.#cleanup has not been set!')
}
#disposed = false
Expand All @@ -45,8 +45,11 @@ export class RundownContentObserver {
) {
logger.silly(`Creating RundownContentObserver for playlist "${rundownPlaylistId}"`)
const { cache, cancel: cancelCache } = createReactiveContentCache(() => {
if (this.#disposed) {
this.#cleanup?.()
return
}
this.#cleanup = onChanged(cache)
if (this.#disposed) this.#cleanup()
}, REACTIVITY_DEBOUNCE)

this.#cache = cache
Expand Down Expand Up @@ -157,5 +160,6 @@ export class RundownContentObserver {
this.#cancelCache()
this.#observers.forEach((observer) => observer.stop())
this.#cleanup?.()
this.#cleanup = undefined
}
}
Loading

0 comments on commit a7deec9

Please sign in to comment.