Skip to content

Commit ae5d572

Browse files
committed
chore: replace a few class components with hooks
1 parent 4b6a43c commit ae5d572

File tree

12 files changed

+1113
-1340
lines changed

12 files changed

+1113
-1340
lines changed
Lines changed: 88 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import * as React from 'react'
2-
import { WithTranslation } from 'react-i18next'
1+
import React, { useCallback, useState } from 'react'
2+
import { useTranslation } from 'react-i18next'
33
import { Link } from 'react-router-dom'
44
import { NotificationCenterPanelToggle, NotificationCenterPanel } from '../lib/notifications/NotificationCenterPanel.js'
55
import { NotificationCenter, NoticeLevel } from '../lib/notifications/notifications.js'
66
import { ErrorBoundary } from '../lib/ErrorBoundary.js'
77
import { SupportPopUpToggle, SupportPopUp } from './SupportPopUp.js'
8-
import { translateWithTracker, Translated } from '../lib/ReactMeteorData/ReactMeteorData.js'
8+
import { useTracker } from '../lib/ReactMeteorData/ReactMeteorData.js'
99
import { CoreSystem } from '../collections/index.js'
1010
import Container from 'react-bootstrap/Container'
1111
import Nav from 'react-bootstrap/Nav'
@@ -19,125 +19,97 @@ interface IPropsHeader {
1919
allowDeveloper?: boolean
2020
}
2121

22-
interface ITrackedPropsHeader {
23-
name: string | undefined
24-
}
25-
26-
interface IStateHeader {
27-
isNotificationCenterOpen: NoticeLevel | undefined
28-
isSupportPanelOpen: boolean
29-
}
22+
export default function Header({ allowConfigure, allowTesting }: IPropsHeader): JSX.Element {
23+
const { t } = useTranslation()
3024

31-
class Header extends React.Component<Translated<IPropsHeader & ITrackedPropsHeader>, IStateHeader> {
32-
constructor(props: Translated<IPropsHeader & ITrackedPropsHeader>) {
33-
super(props)
25+
const sofieName = useTracker(() => {
26+
const coreSystem = CoreSystem.findOne()
3427

35-
this.state = {
36-
isNotificationCenterOpen: undefined,
37-
isSupportPanelOpen: false,
38-
}
39-
}
28+
return coreSystem?.name
29+
}, [])
4030

41-
onToggleNotifications = (_e: React.MouseEvent<HTMLButtonElement>, filter: NoticeLevel | undefined) => {
42-
if (this.state.isNotificationCenterOpen === filter) {
43-
filter = undefined
44-
}
45-
NotificationCenter.isOpen = filter !== undefined ? true : false
31+
const [isNotificationCenterOpen, setIsNotificationCenterOpen] = useState<NoticeLevel | undefined>(undefined)
32+
const onToggleNotifications = useCallback(
33+
(_e: React.MouseEvent<HTMLButtonElement>, filter: NoticeLevel | undefined) => {
34+
setIsNotificationCenterOpen((isNotificationCenterOpen) => {
35+
if (isNotificationCenterOpen === filter) {
36+
filter = undefined
37+
}
38+
NotificationCenter.isOpen = filter !== undefined ? true : false
4639

47-
this.setState({
48-
isNotificationCenterOpen: filter,
49-
})
50-
}
40+
return filter
41+
})
42+
},
43+
[]
44+
)
5145

52-
onToggleSupportPanel = () => {
53-
this.setState({
54-
isSupportPanelOpen: !this.state.isSupportPanelOpen,
55-
})
56-
}
46+
const [isSupportPanelOpen, setIsSupportPanelOpen] = useState(false)
47+
const onToggleSupportPanel = useCallback(() => setIsSupportPanelOpen((prev) => !prev), [])
5748

58-
render(): JSX.Element {
59-
const { t } = this.props
60-
61-
return (
62-
<React.Fragment>
63-
<ErrorBoundary>
64-
<AnimatePresence>
65-
{this.state.isNotificationCenterOpen !== undefined && (
66-
<NotificationCenterPanel limitCount={15} filter={this.state.isNotificationCenterOpen} />
67-
)}
68-
{this.state.isSupportPanelOpen && <SupportPopUp />}
69-
</AnimatePresence>
70-
</ErrorBoundary>
71-
<ErrorBoundary>
72-
<div className="status-bar">
73-
<NotificationCenterPanelToggle
74-
onClick={(e) => this.onToggleNotifications(e, NoticeLevel.CRITICAL)}
75-
isOpen={this.state.isNotificationCenterOpen === NoticeLevel.CRITICAL}
76-
filter={NoticeLevel.CRITICAL}
77-
className="type-critical"
78-
title={t('Critical Problems')}
79-
/>
80-
<NotificationCenterPanelToggle
81-
onClick={(e) => this.onToggleNotifications(e, NoticeLevel.WARNING)}
82-
isOpen={this.state.isNotificationCenterOpen === NoticeLevel.WARNING}
83-
filter={NoticeLevel.WARNING}
84-
className="type-warning"
85-
title={t('Warnings')}
86-
/>
87-
<NotificationCenterPanelToggle
88-
onClick={(e) => this.onToggleNotifications(e, NoticeLevel.NOTIFICATION | NoticeLevel.TIP)}
89-
isOpen={
90-
this.state.isNotificationCenterOpen === ((NoticeLevel.NOTIFICATION | NoticeLevel.TIP) as NoticeLevel)
91-
}
92-
filter={NoticeLevel.NOTIFICATION | NoticeLevel.TIP}
93-
className="type-notification"
94-
title={t('Notes')}
95-
/>
96-
<SupportPopUpToggle onClick={this.onToggleSupportPanel} isOpen={this.state.isSupportPanelOpen} />
97-
</div>
98-
</ErrorBoundary>
99-
<Navbar data-bs-theme="dark" fixed="top" expand className="bg-body-tertiary">
100-
<Container fluid className="mx-5">
101-
<Navbar.Brand>
102-
<Link className="badge-sofie" to="/">
103-
<div className="media-elem me-2 sofie-logo" />
104-
<div className="logo-text">Sofie {this.props.name ? ' - ' + this.props.name : null}</div>
105-
</Link>
106-
</Navbar.Brand>
107-
<Nav className="justify-content-end">
108-
<LinkContainer to="/rundowns" activeClassName="active">
109-
<Nav.Link>{t('Rundowns')}</Nav.Link>
49+
return (
50+
<React.Fragment>
51+
<ErrorBoundary>
52+
<AnimatePresence>
53+
{isNotificationCenterOpen !== undefined && (
54+
<NotificationCenterPanel limitCount={15} filter={isNotificationCenterOpen} />
55+
)}
56+
{isSupportPanelOpen && <SupportPopUp />}
57+
</AnimatePresence>
58+
</ErrorBoundary>
59+
<ErrorBoundary>
60+
<div className="status-bar">
61+
<NotificationCenterPanelToggle
62+
onClick={(e) => onToggleNotifications(e, NoticeLevel.CRITICAL)}
63+
isOpen={isNotificationCenterOpen === NoticeLevel.CRITICAL}
64+
filter={NoticeLevel.CRITICAL}
65+
className="type-critical"
66+
title={t('Critical Problems')}
67+
/>
68+
<NotificationCenterPanelToggle
69+
onClick={(e) => onToggleNotifications(e, NoticeLevel.WARNING)}
70+
isOpen={isNotificationCenterOpen === NoticeLevel.WARNING}
71+
filter={NoticeLevel.WARNING}
72+
className="type-warning"
73+
title={t('Warnings')}
74+
/>
75+
<NotificationCenterPanelToggle
76+
onClick={(e) => onToggleNotifications(e, NoticeLevel.NOTIFICATION | NoticeLevel.TIP)}
77+
isOpen={isNotificationCenterOpen === ((NoticeLevel.NOTIFICATION | NoticeLevel.TIP) as NoticeLevel)}
78+
filter={NoticeLevel.NOTIFICATION | NoticeLevel.TIP}
79+
className="type-notification"
80+
title={t('Notes')}
81+
/>
82+
<SupportPopUpToggle onClick={onToggleSupportPanel} isOpen={isSupportPanelOpen} />
83+
</div>
84+
</ErrorBoundary>
85+
<Navbar data-bs-theme="dark" fixed="top" expand className="bg-body-tertiary">
86+
<Container fluid className="mx-5">
87+
<Navbar.Brand>
88+
<Link className="badge-sofie" to="/">
89+
<div className="media-elem me-2 sofie-logo" />
90+
<div className="logo-text">Sofie {sofieName ? ' - ' + sofieName : null}</div>
91+
</Link>
92+
</Navbar.Brand>
93+
<Nav className="justify-content-end">
94+
<LinkContainer to="/rundowns" activeClassName="active">
95+
<Nav.Link>{t('Rundowns')}</Nav.Link>
96+
</LinkContainer>
97+
{allowTesting && (
98+
<LinkContainer to="/testTools" activeClassName="active">
99+
<Nav.Link> {t('Test Tools')}</Nav.Link>
110100
</LinkContainer>
111-
{this.props.allowTesting && (
112-
<LinkContainer to="/testTools" activeClassName="active">
113-
<Nav.Link> {t('Test Tools')}</Nav.Link>
114-
</LinkContainer>
115-
)}
116-
<LinkContainer to="/status" activeClassName="active">
117-
<Nav.Link> {t('Status')}</Nav.Link>
101+
)}
102+
<LinkContainer to="/status" activeClassName="active">
103+
<Nav.Link> {t('Status')}</Nav.Link>
104+
</LinkContainer>
105+
{allowConfigure && (
106+
<LinkContainer to="/settings" activeClassName="active">
107+
<Nav.Link> {t('Settings')}</Nav.Link>
118108
</LinkContainer>
119-
{this.props.allowConfigure && (
120-
<LinkContainer to="/settings" activeClassName="active">
121-
<Nav.Link> {t('Settings')}</Nav.Link>
122-
</LinkContainer>
123-
)}
124-
</Nav>
125-
</Container>
126-
</Navbar>
127-
</React.Fragment>
128-
)
129-
}
109+
)}
110+
</Nav>
111+
</Container>
112+
</Navbar>
113+
</React.Fragment>
114+
)
130115
}
131-
132-
export default translateWithTracker((_props: IPropsHeader & WithTranslation): ITrackedPropsHeader => {
133-
const coreSystem = CoreSystem.findOne()
134-
let name: string | undefined = undefined
135-
136-
if (coreSystem) {
137-
name = coreSystem.name
138-
}
139-
140-
return {
141-
name,
142-
}
143-
})(Header)

packages/webui/src/client/ui/RundownView/RundownNotifier.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import { ReactiveVar } from 'meteor/reactive-var'
2121
import { Rundown, getRundownNrcsName } from '@sofie-automation/corelib/dist/dataModel/Rundown'
2222
import { doModalDialog } from '../../lib/ModalDialog.js'
2323
import { doUserAction, UserAction } from '../../lib/clientUserAction.js'
24-
// import { withTranslation, getI18n, getDefaults } from 'react-i18next'
2524
import { i18nTranslator as t } from '../i18n.js'
2625
import { PieceStatusCode } from '@sofie-automation/corelib/dist/dataModel/Piece'
2726
import { PeripheralDevicesAPI } from '../../lib/clientAPI.js'

packages/webui/src/client/ui/RundownView/RundownTiming/withTiming.tsx

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,21 @@ export const RundownTimingProviderContext = React.createContext<IRundownTimingPr
4545
})
4646

4747
/**
48-
* Wrap a component in a HOC that will inject a the timing context as a prop. Takes an optional options object that
49-
* allows a high timing resolution or filtering of the changes in the context, so that the child component only
50-
* re-renders when a change to the filtered value happens.
51-
* The options object can also be replaced with an options generator function that will take the incoming props
52-
* as an argument and produce a {WithTimingOptions} object
53-
* @export
54-
* @template IProps The props interface of the child component
55-
* @template IState The state interface of the child component
56-
* @param {(WithTimingOptions | ((props: IProps) => WithTimingOptions))} [options] The options object or the options object generator
57-
* @return (WrappedComponent: IWrappedComponent<IProps, IState>) =>
58-
* new (props: IProps, context: any ) => React.Component<IProps, IState>
48+
* React hook that subscribes to rundown timing events and returns the
49+
* currently selected timing data.
50+
*
51+
* The hook listens for timing update events determined by `tickResolution`.
52+
* It returns timing data selected by `dataResolution` (high-resolution or
53+
* synced). When a `filter` is provided (function, property path string, or
54+
* array of path segments), the hook will only trigger re-renders if the
55+
* value returned by the filter changes between ticks; otherwise it will
56+
* update on every timing event for the chosen `tickResolution`.
57+
*
58+
* @param tickResolution - which timing event resolution to subscribe to
59+
* @param dataResolution - whether to return `High` or `Synced` timing data
60+
* @param filter - optional selector (function | property path | path array)
61+
* @returns the appropriate `RundownTimingContext` for the selected resolution
62+
* @deprecated use the `useTiming` hook instead
5963
*/
6064
export function withTiming<IProps, IState>(
6165
options?: Partial<WithTimingOptions> | ((props: IProps) => Partial<WithTimingOptions>)
@@ -166,6 +170,19 @@ function getFilterFunction(
166170
return undefined
167171
}
168172

173+
/**
174+
* Wrap a component in a HOC that will inject a the timing context as a prop. Takes an optional options object that
175+
* allows a high timing resolution or filtering of the changes in the context, so that the child component only
176+
* re-renders when a change to the filtered value happens.
177+
* The options object can also be replaced with an options generator function that will take the incoming props
178+
* as an argument and produce a {WithTimingOptions} object
179+
* @export
180+
* @template IProps The props interface of the child component
181+
* @template IState The state interface of the child component
182+
* @param {(WithTimingOptions | ((props: IProps) => WithTimingOptions))} [options] The options object or the options object generator
183+
* @return (WrappedComponent: IWrappedComponent<IProps, IState>) =>
184+
* new (props: IProps, context: any ) => React.Component<IProps, IState>
185+
*/
169186
export function useTiming(
170187
tickResolution: TimingTickResolution = TimingTickResolution.Synced,
171188
dataResolution: TimingDataResolution = TimingDataResolution.Synced,

0 commit comments

Comments
 (0)