Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 112 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,46 @@ export function useTracker<T, K extends undefined | T = undefined>(
return meteorData
}

function useReadyState(): {
ready: boolean
setReady: (value: boolean) => void
cancelPreviousReady: (timeout: number) => void
} {
const [ready, setReady] = useState(false)
const [prevReady, setPrevReady] = useState(false)
const prevReadyTimeoutRef = useRef<number | null>(null)

const setIsReady = useCallback(
(value: boolean) => {
setReady(value)

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

const cancelPrevReady = useCallback(
(timeout: number) => {
prevReadyTimeoutRef.current = window.setTimeout(() => {
setPrevReady(false)
}, timeout)
},
[prevReadyTimeoutRef]
)

return {
ready: ready || prevReady,
setReady: setIsReady,
cancelPreviousReady: cancelPrevReady,
}
}

/**
* 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 +425,28 @@ export function useSubscription<K extends keyof AllPubSubTypes>(
sub: K,
...args: Parameters<AllPubSubTypes[K]>
): boolean {
const [ready, setReady] = useState<boolean>(false)
const { ready, setReady, cancelPreviousReady } = useReadyState()

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 +465,7 @@ export function useSubscriptionIfEnabled<K extends keyof AllPubSubTypes>(
enable: boolean,
...args: Parameters<AllPubSubTypes[K]>
): boolean {
const [ready, setReady] = useState<boolean>(false)
const { ready, setReady, cancelPreviousReady } = useReadyState()

useEffect(() => {
if (!enable) {
Expand All @@ -424,16 +474,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 { ready, setReady, cancelPreviousReady } = useReadyState()

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