Skip to content

Commit 91d2b48

Browse files
authored
Merge pull request Sofie-Automation#1278 from bbc/upstream/rework-ui-permissions-flow
2 parents ad8e1d6 + 1729c7d commit 91d2b48

21 files changed

+986
-955
lines changed

packages/webui/src/client/lib/KeyboardFocusIndicator.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import { Meteor } from 'meteor/meteor'
22
import * as React from 'react'
33

4-
import { getAllowStudio, getAllowConfigure, getAllowService } from '../lib/localStorage'
5-
64
import { MeteorCall } from '../lib/meteorApi'
75
import { getCurrentTime } from './systemTime'
86
import { catchError } from './lib'
7+
import { UserPermissions } from '../ui/UserPermissions'
98

109
interface IKeyboardFocusIndicatorState {
1110
inFocus: boolean
1211
}
1312
interface IKeyboardFocusIndicatorProps {
1413
showWhenFocused?: boolean
14+
userPermissions: Readonly<UserPermissions>
1515
}
1616

1717
export class KeyboardFocusIndicator extends React.Component<
@@ -54,9 +54,9 @@ export class KeyboardFocusIndicator extends React.Component<
5454
url: window.location.href + window.location.search,
5555
width: window.innerWidth,
5656
height: window.innerHeight,
57-
studio: getAllowStudio(),
58-
configure: getAllowConfigure(),
59-
service: getAllowService(),
57+
studio: this.props.userPermissions.studio,
58+
configure: this.props.userPermissions.configure,
59+
service: this.props.userPermissions.service,
6060
}
6161
if (focusNow) {
6262
MeteorCall.userAction

packages/webui/src/client/lib/localStorage.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,35 +47,35 @@ function localStorageUnsetCachedItem(key: LocalStorageProperty): void {
4747
export function setAllowStudio(studioMode: boolean): void {
4848
localStorageSetCachedItem(LocalStorageProperty.STUDIO, studioMode ? '1' : '0')
4949
}
50-
export function getAllowStudio(): boolean {
50+
export function getLocalAllowStudio(): boolean {
5151
return localStorageGetCachedItem(LocalStorageProperty.STUDIO) === '1'
5252
}
5353

5454
export function setAllowConfigure(configureMode: boolean): void {
5555
localStorageSetCachedItem(LocalStorageProperty.CONFIGURE, configureMode ? '1' : '0')
5656
}
57-
export function getAllowConfigure(): boolean {
57+
export function getLocalAllowConfigure(): boolean {
5858
return localStorageGetCachedItem(LocalStorageProperty.CONFIGURE) === '1'
5959
}
6060

6161
export function setAllowService(serviceMode: boolean): void {
6262
localStorageSetCachedItem(LocalStorageProperty.SERVICE, serviceMode ? '1' : '0')
6363
}
64-
export function getAllowService(): boolean {
64+
export function getLocalAllowService(): boolean {
6565
return localStorageGetCachedItem(LocalStorageProperty.SERVICE) === '1'
6666
}
6767

6868
export function setAllowDeveloper(developerMode: boolean): void {
6969
localStorageSetCachedItem(LocalStorageProperty.DEVELOPER, developerMode ? '1' : '0')
7070
}
71-
export function getAllowDeveloper(): boolean {
71+
export function getLocalAllowDeveloper(): boolean {
7272
return localStorageGetCachedItem(LocalStorageProperty.DEVELOPER) === '1'
7373
}
7474

7575
export function setAllowTesting(testingMode: boolean): void {
7676
localStorageSetCachedItem(LocalStorageProperty.TESTING, testingMode ? '1' : '0')
7777
}
78-
export function getAllowTesting(): boolean {
78+
export function getLocalAllowTesting(): boolean {
7979
return localStorageGetCachedItem(LocalStorageProperty.TESTING) === '1'
8080
}
8181

packages/webui/src/client/lib/logStatus.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { Meteor } from 'meteor/meteor'
22
import { Tracker } from 'meteor/tracker'
33
import { getRandomString } from './tempLib'
4-
import { getAllowStudio } from './localStorage'
54
import { logger } from './logging'
5+
import { getLocalAllowStudio } from './localStorage'
66

77
/*
88
* This file sets up logging of the connection status, for troubleshooting purposes.
@@ -12,7 +12,7 @@ import { logger } from './logging'
1212
const browserSessionId = getRandomString(8)
1313

1414
// Only log status for studio users
15-
const logStatusEnable = getAllowStudio()
15+
const logStatusEnable = getLocalAllowStudio() // Future: this needs to be setup to be reactive if this is wanted to work correctly in environments with authentication
1616

1717
const previouslyLogged: {
1818
connected?: boolean

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

Lines changed: 90 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,8 @@ import 'moment/min/locales'
44
import { parse as queryStringParse } from 'query-string'
55
import Header from './Header'
66
import {
7-
setAllowStudio,
8-
setAllowConfigure,
9-
getAllowStudio,
10-
getAllowConfigure,
11-
setAllowDeveloper,
12-
setAllowTesting,
13-
getAllowTesting,
14-
getAllowDeveloper,
157
setAllowSpeaking,
168
setAllowVibrating,
17-
setAllowService,
18-
getAllowService,
199
setHelpMode,
2010
setUIZoom,
2111
getUIZoom,
@@ -48,6 +38,7 @@ import { Settings } from '../lib/Settings'
4838
import { DocumentTitleProvider } from '../lib/DocumentTitleProvider'
4939
import { catchError, firstIfArray, isRunningInPWA } from '../lib/lib'
5040
import { protectString } from '@sofie-automation/shared-lib/dist/lib/protectedString'
41+
import { useUserPermissions, UserPermissionsContext } from './UserPermissions'
5142

5243
const NullComponent = () => null
5344

@@ -61,7 +52,7 @@ export const App: React.FC = function App() {
6152

6253
const [lastStart] = useState(Date.now())
6354

64-
const roles = useRoles()
55+
const roles = useUserPermissions()
6556
const featureFlags = useFeatureFlags()
6657

6758
useEffect(() => {
@@ -155,138 +146,97 @@ export const App: React.FC = function App() {
155146
}, [])
156147

157148
return (
158-
<Router getUserConfirmation={onNavigationUserConfirmation}>
159-
<div className="container-fluid header-clear">
160-
{/* Header switch - render the usual header for all pages but the rundown view */}
161-
<ErrorBoundary>
162-
<Switch>
163-
<Route path="/rundown/:playlistId" component={NullComponent} />
164-
<Route path="/countdowns/:studioId" component={NullComponent} />
165-
<Route path="/activeRundown" component={NullComponent} />
166-
<Route path="/prompter/:studioId" component={NullComponent} />
167-
<Route
168-
path="/"
169-
render={(props) => (
170-
<Header
171-
{...props}
172-
allowConfigure={roles.configure}
173-
allowTesting={roles.testing}
174-
allowDeveloper={roles.developer}
175-
/>
176-
)}
177-
/>
178-
</Switch>
179-
</ErrorBoundary>
180-
{/* Main app switch */}
181-
<ErrorBoundary>
182-
<Switch>
183-
<Route exact path="/" component={RundownList} />
184-
<Route path="/rundowns" render={() => <RundownList />} />
185-
<Route
186-
path="/rundown/:playlistId/shelf"
187-
exact
188-
render={(props) => (
189-
<RundownView
190-
playlistId={protectString(decodeURIComponent(props.match.params.playlistId))}
191-
onlyShelf={true}
192-
/>
193-
)}
194-
/>
195-
<Route
196-
path="/rundown/:playlistId"
197-
render={(props) => (
198-
<RundownView playlistId={protectString(decodeURIComponent(props.match.params.playlistId))} />
199-
)}
200-
/>
201-
<Route
202-
path="/activeRundown/:studioId"
203-
render={(props) => (
204-
<ActiveRundownView studioId={protectString(decodeURIComponent(props.match.params.studioId))} />
205-
)}
206-
/>
207-
<Route
208-
path="/prompter/:studioId"
209-
render={(props) => (
210-
<PrompterView studioId={protectString(decodeURIComponent(props.match.params.studioId))} />
211-
)}
212-
/>
213-
{/* We switch to the general ClockView component, and allow it to do the switch between various types of countdowns */}
214-
<Route
215-
path="/countdowns/:studioId"
216-
render={(props) => (
217-
<ClockView studioId={protectString(decodeURIComponent(props.match.params.studioId))} />
218-
)}
219-
/>
220-
<Route path="/status" render={() => <Status />} />
221-
<Route path="/settings" render={() => <SettingsView />} />
222-
<Route path="/testTools" render={() => <TestTools />} />
223-
<Route>
224-
<Redirect to="/" />
225-
</Route>
226-
</Switch>
227-
</ErrorBoundary>
228-
<ErrorBoundary>
229-
<Switch>
230-
{/* Put views that should NOT have the Notification center here: */}
231-
<Route path="/countdowns/:studioId" component={NullComponent} />
232-
<Route path="/prompter/:studioId" component={NullComponent} />
233-
<Route path="/" component={ConnectionStatusNotification} />
234-
</Switch>
235-
</ErrorBoundary>
236-
<ErrorBoundary>
237-
<DocumentTitleProvider />
238-
</ErrorBoundary>
239-
<ErrorBoundary>
240-
<ModalDialogGlobalContainer />
241-
</ErrorBoundary>
242-
</div>
243-
</Router>
149+
<UserPermissionsContext.Provider value={roles}>
150+
<Router getUserConfirmation={onNavigationUserConfirmation}>
151+
<div className="container-fluid header-clear">
152+
{/* Header switch - render the usual header for all pages but the rundown view */}
153+
<ErrorBoundary>
154+
<Switch>
155+
<Route path="/rundown/:playlistId" component={NullComponent} />
156+
<Route path="/countdowns/:studioId" component={NullComponent} />
157+
<Route path="/activeRundown" component={NullComponent} />
158+
<Route path="/prompter/:studioId" component={NullComponent} />
159+
<Route
160+
path="/"
161+
render={(props) => (
162+
<Header
163+
{...props}
164+
allowConfigure={roles.configure}
165+
allowTesting={roles.testing}
166+
allowDeveloper={roles.developer}
167+
/>
168+
)}
169+
/>
170+
</Switch>
171+
</ErrorBoundary>
172+
{/* Main app switch */}
173+
<ErrorBoundary>
174+
<Switch>
175+
<Route exact path="/" component={RundownList} />
176+
<Route path="/rundowns" render={() => <RundownList />} />
177+
<Route
178+
path="/rundown/:playlistId/shelf"
179+
exact
180+
render={(props) => (
181+
<RundownView
182+
playlistId={protectString(decodeURIComponent(props.match.params.playlistId))}
183+
onlyShelf={true}
184+
/>
185+
)}
186+
/>
187+
<Route
188+
path="/rundown/:playlistId"
189+
render={(props) => (
190+
<RundownView playlistId={protectString(decodeURIComponent(props.match.params.playlistId))} />
191+
)}
192+
/>
193+
<Route
194+
path="/activeRundown/:studioId"
195+
render={(props) => (
196+
<ActiveRundownView studioId={protectString(decodeURIComponent(props.match.params.studioId))} />
197+
)}
198+
/>
199+
<Route
200+
path="/prompter/:studioId"
201+
render={(props) => (
202+
<PrompterView studioId={protectString(decodeURIComponent(props.match.params.studioId))} />
203+
)}
204+
/>
205+
{/* We switch to the general ClockView component, and allow it to do the switch between various types of countdowns */}
206+
<Route
207+
path="/countdowns/:studioId"
208+
render={(props) => (
209+
<ClockView studioId={protectString(decodeURIComponent(props.match.params.studioId))} />
210+
)}
211+
/>
212+
<Route path="/status" render={() => <Status />} />
213+
<Route path="/settings" render={() => <SettingsView />} />
214+
<Route path="/testTools" render={() => <TestTools />} />
215+
<Route>
216+
<Redirect to="/" />
217+
</Route>
218+
</Switch>
219+
</ErrorBoundary>
220+
<ErrorBoundary>
221+
<Switch>
222+
{/* Put views that should NOT have the Notification center here: */}
223+
<Route path="/countdowns/:studioId" component={NullComponent} />
224+
<Route path="/prompter/:studioId" component={NullComponent} />
225+
<Route path="/" component={ConnectionStatusNotification} />
226+
</Switch>
227+
</ErrorBoundary>
228+
<ErrorBoundary>
229+
<DocumentTitleProvider />
230+
</ErrorBoundary>
231+
<ErrorBoundary>
232+
<ModalDialogGlobalContainer />
233+
</ErrorBoundary>
234+
</div>
235+
</Router>
236+
</UserPermissionsContext.Provider>
244237
)
245238
}
246239

247-
function useRoles() {
248-
const location = window.location
249-
250-
const [roles, setRoles] = useState({
251-
studio: getAllowStudio(),
252-
configure: getAllowConfigure(),
253-
developer: getAllowDeveloper(),
254-
testing: getAllowTesting(),
255-
service: getAllowService(),
256-
})
257-
258-
useEffect(() => {
259-
if (!location.search) return
260-
261-
const params = queryStringParse(location.search)
262-
263-
if (params['studio']) setAllowStudio(params['studio'] === '1')
264-
if (params['configure']) setAllowConfigure(params['configure'] === '1')
265-
if (params['develop']) setAllowDeveloper(params['develop'] === '1')
266-
if (params['testing']) setAllowTesting(params['testing'] === '1')
267-
if (params['service']) setAllowService(params['service'] === '1')
268-
269-
if (params['admin']) {
270-
const val = params['admin'] === '1'
271-
setAllowStudio(val)
272-
setAllowConfigure(val)
273-
setAllowDeveloper(val)
274-
setAllowTesting(val)
275-
setAllowService(val)
276-
}
277-
278-
setRoles({
279-
studio: getAllowStudio(),
280-
configure: getAllowConfigure(),
281-
developer: getAllowDeveloper(),
282-
testing: getAllowTesting(),
283-
service: getAllowService(),
284-
})
285-
}, [location.search])
286-
287-
return roles
288-
}
289-
290240
function useFeatureFlags() {
291241
const location = window.location
292242

0 commit comments

Comments
 (0)