Skip to content

Commit 1729c7d

Browse files
committed
chore: refactor SystemStatus to be functional
1 parent b625b33 commit 1729c7d

File tree

2 files changed

+156
-148
lines changed

2 files changed

+156
-148
lines changed

packages/webui/src/client/ui/Status.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useSubscription } from '../lib/ReactMeteorData/react-meteor-data'
22
import { useTranslation } from 'react-i18next'
33
import { Route, Switch, Redirect, NavLink } from 'react-router-dom'
4-
import SystemStatus from './Status/SystemStatus/SystemStatus'
4+
import { SystemStatus } from './Status/SystemStatus/SystemStatus'
55
import { MediaManagerStatus } from './Status/MediaManager'
66
import { ExternalMessages } from './Status/ExternalMessages'
77
import { UserActivity } from './Status/UserActivity'
Lines changed: 155 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
import React from 'react'
2-
import { Translated, useSubscription, useTracker } from '../../../lib/ReactMeteorData/react-meteor-data'
1+
import { useEffect, useMemo, useState } from 'react'
2+
import { useSubscription, useTracker } from '../../../lib/ReactMeteorData/react-meteor-data'
33
import { PeripheralDevice, PeripheralDeviceType } from '@sofie-automation/corelib/dist/dataModel/PeripheralDevice'
4-
import { withTranslation } from 'react-i18next'
4+
import { useTranslation } from 'react-i18next'
55
import { protectString, unprotectString } from '../../../lib/tempLib'
66
import * as _ from 'underscore'
77
import { NotificationCenter, NoticeLevel, Notification } from '../../../lib/notifications/notifications'
8-
import { ICoreSystem } from '@sofie-automation/meteor-lib/dist/collections/CoreSystem'
98
import { StatusResponse } from '@sofie-automation/meteor-lib/dist/api/systemStatus'
109
import { MeteorCall } from '../../../lib/meteorApi'
1110
import { CoreSystem, PeripheralDevices } from '../../../collections'
@@ -16,75 +15,59 @@ import { stringifyError } from '@sofie-automation/shared-lib/dist/lib/stringifyE
1615
import { CoreItem } from './CoreItem'
1716
import { DeviceItem } from './DeviceItem'
1817

19-
interface ISystemStatusProps {}
20-
interface ISystemStatusState {
21-
systemStatus: StatusResponse | undefined
22-
deviceDebugState: Map<PeripheralDeviceId, object>
23-
}
24-
25-
interface ISystemStatusTrackedProps {
26-
coreSystem: ICoreSystem | undefined
27-
devices: Array<PeripheralDevice>
28-
}
29-
30-
interface DeviceInHierarchy {
31-
device: PeripheralDevice
32-
children: Array<DeviceInHierarchy>
33-
}
18+
export function SystemStatus(): JSX.Element {
19+
const { t } = useTranslation()
3420

35-
export default function SystemStatus(props: Readonly<ISystemStatusProps>): JSX.Element {
3621
// Subscribe to data:
3722
useSubscription(CorelibPubSub.peripheralDevices, null)
3823

3924
const coreSystem = useTracker(() => CoreSystem.findOne(), [])
4025
const devices = useTracker(() => PeripheralDevices.find({}, { sort: { lastConnected: -1 } }).fetch(), [], [])
4126

42-
return <SystemStatusContent {...props} coreSystem={coreSystem} devices={devices} />
27+
const systemStatus = useSystemStatus()
28+
const playoutDebugStates = usePlayoutDebugStates(devices)
29+
30+
const devicesHeirarchy = convertDevicesIntoHeirarchy(devices)
31+
32+
return (
33+
<div className="mhl gutter system-status">
34+
<header className="mbs">
35+
<h1>{t('System Status')}</h1>
36+
</header>
37+
<div className="mod mvl">
38+
{coreSystem && <CoreItem coreSystem={coreSystem} systemStatus={systemStatus} />}
39+
40+
{devicesHeirarchy.map((d) => (
41+
<DeviceItemWithChildren playoutDebugStates={playoutDebugStates} parentDevice={null} device={d} />
42+
))}
43+
</div>
44+
</div>
45+
)
4346
}
4447

45-
const SystemStatusContent = withTranslation()(
46-
class SystemStatusContent extends React.Component<
47-
Translated<ISystemStatusProps & ISystemStatusTrackedProps>,
48-
ISystemStatusState
49-
> {
50-
private refreshInterval: NodeJS.Timer | undefined = undefined
51-
private refreshDebugStatesInterval: NodeJS.Timer | undefined = undefined
52-
private destroyed = false
53-
54-
constructor(props: Translated<ISystemStatusProps & ISystemStatusTrackedProps>) {
55-
super(props)
56-
57-
this.state = {
58-
systemStatus: undefined,
59-
deviceDebugState: new Map(),
60-
}
61-
}
48+
interface DeviceInHierarchy {
49+
device: PeripheralDevice
50+
children: Array<DeviceInHierarchy>
51+
}
6252

63-
componentDidMount(): void {
64-
this.refreshSystemStatus()
65-
this.refreshInterval = setInterval(this.refreshSystemStatus, 5000)
66-
this.refreshDebugStatesInterval = setInterval(this.refreshDebugStates, 1000)
67-
}
53+
function useSystemStatus(): StatusResponse | undefined {
54+
const { t } = useTranslation()
6855

69-
componentWillUnmount(): void {
70-
if (this.refreshInterval) clearInterval(this.refreshInterval)
71-
if (this.refreshDebugStatesInterval) clearInterval(this.refreshDebugStatesInterval)
72-
this.destroyed = true
73-
}
56+
const [sytemStatus, setSystemStatus] = useState<StatusResponse | undefined>()
7457

75-
refreshSystemStatus = () => {
76-
const { t } = this.props
58+
useEffect(() => {
59+
let destroyed = false
60+
61+
const refreshSystemStatus = () => {
7762
MeteorCall.systemStatus
7863
.getSystemStatus()
79-
.then((systemStatus: StatusResponse) => {
80-
if (this.destroyed) return
64+
.then((newSystemStatus: StatusResponse) => {
65+
if (destroyed) return
8166

82-
this.setState({
83-
systemStatus: systemStatus,
84-
})
67+
setSystemStatus(newSystemStatus)
8568
})
8669
.catch((err) => {
87-
if (this.destroyed) return
70+
if (destroyed) return
8871

8972
logger.error('systemStatus.getSystemStatus', err)
9073
NotificationCenter.push(
@@ -98,106 +81,131 @@ const SystemStatusContent = withTranslation()(
9881
})
9982
}
10083

101-
refreshDebugStates = () => {
102-
for (const device of this.props.devices) {
103-
if (device.type === PeripheralDeviceType.PLAYOUT && device.settings && (device.settings as any)['debugState']) {
104-
MeteorCall.systemStatus
105-
.getDebugStates(device._id)
106-
.then((res) => {
107-
const states: Map<PeripheralDeviceId, object> = new Map()
84+
refreshSystemStatus()
85+
86+
const interval = setInterval(refreshSystemStatus, 5000)
87+
88+
return () => {
89+
clearInterval(interval)
90+
destroyed = true
91+
}
92+
}, [t])
93+
94+
return sytemStatus
95+
}
96+
97+
function usePlayoutDebugStates(devices: PeripheralDevice[]): Map<PeripheralDeviceId, object> {
98+
const { t } = useTranslation()
99+
100+
const [playoutDebugStates, setPlayoutDebugStates] = useState<Map<PeripheralDeviceId, object>>(new Map())
101+
102+
const playoutDeviceIds = useMemo(() => {
103+
const deviceIds: PeripheralDeviceId[] = []
104+
105+
for (const device of devices) {
106+
if (device.type === PeripheralDeviceType.PLAYOUT && device.settings && (device.settings as any)['debugState']) {
107+
deviceIds.push(device._id)
108+
}
109+
}
110+
111+
deviceIds.sort()
112+
return deviceIds
113+
}, [devices])
114+
115+
useEffect(() => {
116+
let destroyed = false
117+
118+
const refreshDebugStates = () => {
119+
for (const deviceId of playoutDeviceIds) {
120+
MeteorCall.systemStatus
121+
.getDebugStates(deviceId)
122+
.then((res) => {
123+
if (destroyed) return
124+
125+
setPlayoutDebugStates((oldState) => {
126+
// Create a new map based on the old one
127+
const newStates = new Map(oldState.entries())
108128
for (const [key, state] of Object.entries<any>(res)) {
109-
states.set(protectString(key), state)
129+
newStates.set(protectString(key), state)
110130
}
111-
this.setState({
112-
deviceDebugState: states,
113-
})
131+
return newStates
114132
})
115-
.catch((err) => console.log(`Error fetching device states: ${stringifyError(err)}`))
116-
}
133+
})
134+
.catch((err) => console.log(`Error fetching device states: ${stringifyError(err)}`))
117135
}
118136
}
119137

120-
renderPeripheralDevices() {
121-
const devices: Array<DeviceInHierarchy> = []
122-
const refs: Record<string, DeviceInHierarchy | undefined> = {}
123-
const devicesToAdd: Record<string, DeviceInHierarchy> = {}
124-
// First, add all as references:
125-
_.each(this.props.devices, (device) => {
126-
const d: DeviceInHierarchy = {
127-
device: device,
128-
children: [],
129-
}
130-
refs[unprotectString(device._id)] = d
131-
devicesToAdd[unprotectString(device._id)] = d
132-
})
133-
// Then, map and add devices:
134-
_.each(devicesToAdd, (d: DeviceInHierarchy) => {
135-
if (d.device.parentDeviceId) {
136-
const parent = refs[unprotectString(d.device.parentDeviceId)]
137-
if (parent) {
138-
parent.children.push(d)
139-
} else {
140-
// not found, add on top level then:
141-
devices.push(d)
142-
}
143-
} else {
144-
devices.push(d)
145-
}
146-
})
147-
148-
const getDeviceContent = (parentDevice: DeviceInHierarchy | null, d: DeviceInHierarchy): JSX.Element => {
149-
const content: JSX.Element[] = [
150-
<DeviceItem
151-
key={'device' + d.device._id}
152-
parentDevice={parentDevice?.device ?? null}
153-
device={d.device}
154-
hasChildren={d.children.length !== 0}
155-
debugState={this.state.deviceDebugState.get(d.device._id)}
156-
/>,
157-
]
158-
if (d.children.length) {
159-
const children: JSX.Element[] = []
160-
_.each(d.children, (child: DeviceInHierarchy) =>
161-
children.push(
162-
<li key={'childdevice' + child.device._id} className="child-device-li">
163-
{getDeviceContent(d, child)}
164-
</li>
165-
)
166-
)
167-
content.push(
168-
<div key={d.device._id + '_children'} className="children">
169-
<ul className="childlist">{children}</ul>
170-
</div>
171-
)
172-
}
173-
return (
174-
<div key={d.device._id + '_parent'} className="device-item-container">
175-
{content}
176-
</div>
177-
)
178-
}
138+
const interval = setInterval(refreshDebugStates, 1000)
179139

180-
return (
181-
<React.Fragment>
182-
{this.props.coreSystem && (
183-
<CoreItem coreSystem={this.props.coreSystem} systemStatus={this.state.systemStatus} />
184-
)}
185-
{_.map(devices, (d) => getDeviceContent(null, d))}
186-
</React.Fragment>
187-
)
140+
return () => {
141+
clearInterval(interval)
142+
destroyed = true
188143
}
144+
}, [t, JSON.stringify(playoutDeviceIds)])
189145

190-
render(): JSX.Element {
191-
const { t } = this.props
146+
return playoutDebugStates
147+
}
192148

193-
return (
194-
<div className="mhl gutter system-status">
195-
<header className="mbs">
196-
<h1>{t('System Status')}</h1>
197-
</header>
198-
<div className="mod mvl">{this.renderPeripheralDevices()}</div>
199-
</div>
200-
)
149+
function convertDevicesIntoHeirarchy(devices: PeripheralDevice[]): DeviceInHierarchy[] {
150+
const devicesMap = new Map<PeripheralDeviceId, DeviceInHierarchy>()
151+
const devicesToAdd: DeviceInHierarchy[] = []
152+
153+
// First, add all as references:
154+
for (const device of devices) {
155+
const entry: DeviceInHierarchy = {
156+
device: device,
157+
children: [],
201158
}
159+
devicesMap.set(device._id, entry)
160+
devicesToAdd.push(entry)
202161
}
203-
)
162+
163+
// Then, map and add devices:
164+
const devicesHeirarchy: Array<DeviceInHierarchy> = []
165+
for (const entry of devicesToAdd) {
166+
if (entry.device.parentDeviceId) {
167+
const parent = devicesMap.get(entry.device.parentDeviceId)
168+
if (parent) {
169+
parent.children.push(entry)
170+
} else {
171+
// not found, add on top level then:
172+
devicesHeirarchy.push(entry)
173+
}
174+
} else {
175+
devicesHeirarchy.push(entry)
176+
}
177+
}
178+
179+
return devicesHeirarchy
180+
}
181+
182+
interface DeviceItemWithChildrenProps {
183+
playoutDebugStates: Map<PeripheralDeviceId, object>
184+
parentDevice: DeviceInHierarchy | null
185+
device: DeviceInHierarchy
186+
}
187+
188+
function DeviceItemWithChildren({ playoutDebugStates, device, parentDevice }: DeviceItemWithChildrenProps) {
189+
return (
190+
<div key={device.device._id + '_parent'} className="device-item-container">
191+
<DeviceItem
192+
parentDevice={parentDevice?.device ?? null}
193+
device={device.device}
194+
hasChildren={device.children.length !== 0}
195+
debugState={playoutDebugStates.get(device.device._id)}
196+
/>
197+
198+
{device.children.length > 0 && (
199+
<div className="children">
200+
<ul className="childlist">
201+
{device.children.map((child) => (
202+
<li key={unprotectString(child.device._id)} className="child-device-li">
203+
<DeviceItemWithChildren playoutDebugStates={playoutDebugStates} parentDevice={device} device={child} />
204+
</li>
205+
))}
206+
</ul>
207+
</div>
208+
)}
209+
</div>
210+
)
211+
}

0 commit comments

Comments
 (0)