Skip to content
This repository has been archived by the owner on Nov 3, 2020. It is now read-only.

Commit

Permalink
feat(setup): Hardware Errors and Warnings
Browse files Browse the repository at this point in the history
  • Loading branch information
beausmith committed Jan 9, 2020
1 parent 4821fd0 commit 055babe
Show file tree
Hide file tree
Showing 7 changed files with 264 additions and 3 deletions.
24 changes: 21 additions & 3 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,16 @@ window.oncontextmenu = (e: MouseEvent): void => {
}

export interface Props {
accesssibleController?: AppRootProps['accesssibleController']
battery?: AppRootProps['battery']
card?: AppRootProps['card']
cardReader?: AppRootProps['cardReader']
printer?: AppRootProps['printer']
storage?: AppRootProps['storage']
tts?: {
enabled: TextToSpeech
disabled: TextToSpeech
}
card?: AppRootProps['card']
storage?: AppRootProps['storage']
}

const App = ({
Expand All @@ -37,6 +41,10 @@ const App = ({
},
card = new WebServiceCard(),
storage = new LocalStorage<AppStorage>(),
accesssibleController = { status: () => ({ connected: true }) }, // TODO: define
battery = { status: () => ({ charging: true, level: 0.8 }) }, // TODO: define
cardReader = { status: () => ({ connected: true }) }, // TODO: define
printer = { status: () => ({ connected: true, error: 'tbd' }) }, // TODO: define
}: Props) => {
const [screenReaderEnabled, setScreenReaderEnabled] = useState(false)
const [screenReader, setScreenReader] = useState<ScreenReader>(
Expand Down Expand Up @@ -121,7 +129,17 @@ const App = ({
>
<Route
path="/"
render={props => <AppRoot card={card} storage={storage} {...props} />}
render={props => (
<AppRoot
accesssibleController={accesssibleController}
battery={battery}
card={card}
cardReader={cardReader}
printer={printer}
storage={storage}
{...props}
/>
)}
/>
</FocusManager>
</BrowserRouter>
Expand Down
84 changes: 84 additions & 0 deletions src/AppRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ import InvalidCardScreen from './pages/InvalidCardScreen'
import InsertCardScreen from './pages/InsertCardScreen'
import PollWorkerScreen from './pages/PollWorkerScreen'
import PrintOnlyScreen from './pages/PrintOnlyScreen'
import SetupCardReaderPage from './pages/SetupCardReaderPage'
import SetupPrinterPage from './pages/SetupPrinterPage'
import SetupPrinterErrorPage from './pages/SetupPrinterErrorPage'
import SetupPowerPage from './pages/SetupPowerPage'
import UnconfiguredScreen from './pages/UnconfiguredScreen'
import UsedCardScreen from './pages/UsedCardScreen'
import { getBallotStyle, getContests, getZeroTally } from './utils/election'
Expand Down Expand Up @@ -88,6 +92,12 @@ interface SharedState {
appPrecinctId: string
ballotsPrintedCount: number
election: OptionalElection
hasAccessibleControllerAttached: boolean
hasCardReaderAttached: boolean
hasChargerAttached: boolean
hasLowBattery: boolean
hasPrinterAttached: boolean
hasPrinterError: boolean
isFetchingElection: boolean
isLiveMode: boolean
isPollsOpen: boolean
Expand All @@ -106,7 +116,15 @@ export interface AppStorage {
}

export interface Props extends RouteComponentProps {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
accesssibleController: any // TODO: define
// eslint-disable-next-line @typescript-eslint/no-explicit-any
battery: any // TODO: define
card: Card
// eslint-disable-next-line @typescript-eslint/no-explicit-any
cardReader: any // TODO: define
// eslint-disable-next-line @typescript-eslint/no-explicit-any
printer: any // TODO: define
storage: Storage<AppStorage>
}

Expand All @@ -121,6 +139,7 @@ class AppRoot extends React.Component<Props, State> {
private machineIdAbortController = new AbortController()

private cardPoller?: Poller
private statusPoller?: Poller
private lastVoteUpdateAt = 0
private lastVoteSaveToCardAt = 0
private cardWriteInterval = 0
Expand Down Expand Up @@ -152,6 +171,12 @@ class AppRoot extends React.Component<Props, State> {
appPrecinctId: '',
ballotsPrintedCount: 0,
election: undefined,
hasAccessibleControllerAttached: false,
hasCardReaderAttached: false,
hasChargerAttached: false,
hasLowBattery: false,
hasPrinterAttached: false,
hasPrinterError: false,
isFetchingElection: false,
isLiveMode: false,
isPollsOpen: false,
Expand Down Expand Up @@ -456,6 +481,39 @@ class AppRoot extends React.Component<Props, State> {
return true
}

public startStatusPolling = () => {
/* istanbul ignore else */
if (!this.statusPoller) {
this.statusPoller = IntervalPoller.start(
GLOBALS.CARD_POLLING_INTERVAL,
async () => {
try {
// Possible implementation
const accesssibleController = await this.props.accesssibleController.status()
const battery = await this.props.battery.status()
const cardReader = await this.props.cardReader.status()
const printer = await this.props.printer.status()
this.setState({
hasAccessibleControllerAttached: accesssibleController.connected,
hasCardReaderAttached: cardReader.connected,
hasChargerAttached: battery.charging,
hasLowBattery: battery.level < 0.25,
hasPrinterAttached: printer.connected,
hasPrinterError: printer.error,
})
} catch (error) {
this.stopStatusPolling() // Assume backend is unavailable.
}
}
)
}
}

public stopStatusPolling = () => {
this.statusPoller?.stop()
this.statusPoller = undefined
}

public componentDidMount = () => {
const election = this.getElection()
const { ballotStyleId, precinctId } = this.getBallotActivation()
Expand Down Expand Up @@ -497,12 +555,14 @@ class AppRoot extends React.Component<Props, State> {
this.setMachineId()
this.startShortValueReadPolling()
this.startLongValueWritePolling()
this.startStatusPolling()
}

public componentWillUnmount = /* istanbul ignore next - triggering keystrokes issue - https://github.com/votingworks/bmd/issues/62 */ () => {
this.machineIdAbortController.abort()
document.removeEventListener('keydown', handleGamepadKeyboardEvent)
this.stopShortValueReadPolling()
this.stopStatusPolling()
}

public setMachineId = async () => {
Expand Down Expand Up @@ -793,11 +853,31 @@ class AppRoot extends React.Component<Props, State> {
isVoterCardPrinted,
isRecentVoterPrint,
machineId,
hasAccessibleControllerAttached,
hasCardReaderAttached,
hasChargerAttached,
hasLowBattery,
hasPrinterAttached,
hasPrinterError,
precinctId,
tally,
userSettings,
votes,
} = this.state
if (hasLowBattery && !hasChargerAttached) {
return <SetupPowerPage setUserSettings={this.setUserSettings} />
}
if (!hasCardReaderAttached) {
return <SetupCardReaderPage setUserSettings={this.setUserSettings} />
}
if (appMode.isVxPrint) {
if (!hasPrinterAttached) {
return <SetupPrinterPage setUserSettings={this.setUserSettings} />
}
if (hasPrinterError) {
return <SetupPrinterErrorPage setUserSettings={this.setUserSettings} />
}
}
if (isClerkCardPresent) {
return (
<ClerkScreen
Expand Down Expand Up @@ -898,6 +978,10 @@ class AppRoot extends React.Component<Props, State> {
<InsertCardScreen
appPrecinctId={appPrecinctId}
election={election}
showNoAccessibleControllerWarning={
appMode === VxMarkOnly && !hasAccessibleControllerAttached
}
showNoChargerAttachedWarning={!hasChargerAttached}
isLiveMode={isLiveMode}
isPollsOpen={isPollsOpen}
/>
Expand Down
17 changes: 17 additions & 0 deletions src/pages/InsertCardScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Prose from '../components/Prose'
import Screen from '../components/Screen'
import Sidebar from '../components/Sidebar'
import TestMode from '../components/TestMode'
import Text from '../components/Text'
import ElectionInfo from '../components/ElectionInfo'

const InsertCardImage = styled.img`
Expand All @@ -17,15 +18,19 @@ const InsertCardImage = styled.img`
interface Props {
appPrecinctId: string
election: Election
showNoChargerAttachedWarning: boolean
isLiveMode: boolean
isPollsOpen: boolean
showNoAccessibleControllerWarning: boolean
}

const ActivationScreen = ({
appPrecinctId,
election,
showNoChargerAttachedWarning,
isLiveMode,
isPollsOpen,
showNoAccessibleControllerWarning,
}: Props) => {
return (
<Screen flexDirection="row-reverse" white>
Expand All @@ -36,6 +41,12 @@ const ActivationScreen = ({
<MainChild center>
<Prose textCenter>
<TestMode isLiveMode={isLiveMode} />
{showNoChargerAttachedWarning && (
<Text warning small>
<strong>No Power Detected.</strong> Please ask a poll worker to
plug-in the power cord for this machine.
</Text>
)}
<p>
<InsertCardImage
aria-hidden
Expand All @@ -54,6 +65,12 @@ const ActivationScreen = ({
<p>Insert Poll Worker card to open.</p>
</React.Fragment>
)}
{showNoAccessibleControllerWarning && (
<Text muted small>
Voting with an accessible controller is not supported on this
machine.
</Text>
)}
</Prose>
</MainChild>
</Main>
Expand Down
34 changes: 34 additions & 0 deletions src/pages/SetupCardReaderPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React, { useEffect } from 'react'
import Prose from '../components/Prose'
import Main, { MainChild } from '../components/Main'
import Screen from '../components/Screen'
import { PartialUserSettings } from '../config/types'
import { DEFAULT_FONT_SIZE, LARGE_DISPLAY_FONT_SIZE } from '../config/globals'

interface Props {
setUserSettings: (partial: PartialUserSettings) => void
}

const ExpiredCardScreen = ({ setUserSettings }: Props) => {
useEffect(() => {
setUserSettings({ textSize: LARGE_DISPLAY_FONT_SIZE })
return () => {
setUserSettings({ textSize: DEFAULT_FONT_SIZE })
}
}, [setUserSettings])

return (
<Screen white>
<Main>
<MainChild center>
<Prose textCenter>
<h1>Card Reader Not Detected</h1>
<p>Please ask a poll worker to connect card reader.</p>
</Prose>
</MainChild>
</Main>
</Screen>
)
}

export default ExpiredCardScreen
40 changes: 40 additions & 0 deletions src/pages/SetupPowerPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React, { useEffect } from 'react'
import Prose from '../components/Prose'
import Main, { MainChild } from '../components/Main'
import Screen from '../components/Screen'
import { NoWrap } from '../components/Text'
import { PartialUserSettings } from '../config/types'
import { DEFAULT_FONT_SIZE, LARGE_DISPLAY_FONT_SIZE } from '../config/globals'

interface Props {
setUserSettings: (partial: PartialUserSettings) => void
}

const SetupPowerPage = ({ setUserSettings }: Props) => {
useEffect(() => {
setUserSettings({ textSize: LARGE_DISPLAY_FONT_SIZE })
return () => {
setUserSettings({ textSize: DEFAULT_FONT_SIZE })
}
}, [setUserSettings])

return (
<Screen white>
<Main padded>
<MainChild center>
<Prose textCenter>
<h1>
No Power Detected <NoWrap>and Battery is Low</NoWrap>
</h1>
<p>
Please ask a poll worker to plug-in the power cord for this
machine.
</p>
</Prose>
</MainChild>
</Main>
</Screen>
)
}

export default SetupPowerPage
34 changes: 34 additions & 0 deletions src/pages/SetupPrinterErrorPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React, { useEffect } from 'react'
import Prose from '../components/Prose'
import Main, { MainChild } from '../components/Main'
import Screen from '../components/Screen'
import { PartialUserSettings } from '../config/types'
import { DEFAULT_FONT_SIZE, LARGE_DISPLAY_FONT_SIZE } from '../config/globals'

interface Props {
setUserSettings: (partial: PartialUserSettings) => void
}

const SetupPrinterErrorPage = ({ setUserSettings }: Props) => {
useEffect(() => {
setUserSettings({ textSize: LARGE_DISPLAY_FONT_SIZE })
return () => {
setUserSettings({ textSize: DEFAULT_FONT_SIZE })
}
}, [setUserSettings])

return (
<Screen white>
<Main padded>
<MainChild center>
<Prose textCenter>
<h1>Printer Error Detected</h1>
<p>Please ask a poll worker to resolve printer error.</p>
</Prose>
</MainChild>
</Main>
</Screen>
)
}

export default SetupPrinterErrorPage

0 comments on commit 055babe

Please sign in to comment.