Remove alerts from state

This commit is contained in:
Jerko Steiner 2019-11-17 14:28:43 -03:00
parent fcb47a2cc5
commit 58039eb086
10 changed files with 26 additions and 260 deletions

View File

@ -70,57 +70,6 @@ export const clear = (): NotificationClearAction => ({
type: constants.NOTIFY_CLEAR,
})
export interface Alert {
action?: string
dismissable: boolean
message: string
type: NotifyType
}
export interface AlertAddAction {
type: 'ALERT'
payload: Alert
}
export function alert (message: string, dismissable = false): AlertAddAction {
return {
type: constants.ALERT,
payload: {
action: dismissable ? 'Dismiss' : undefined,
dismissable: !!dismissable,
message,
type: 'warning',
},
}
}
export interface AlertDismissAction {
type: 'ALERT_DISMISS'
payload: Alert
}
export const dismissAlert = (alert: Alert): AlertDismissAction => {
return {
type: constants.ALERT_DISMISS,
payload: alert,
}
}
export interface AlertClearAction {
type: 'ALERT_CLEAR'
}
export const clearAlerts = (): AlertClearAction => {
return {
type: constants.ALERT_CLEAR,
}
}
export type AlertActionType =
AlertAddAction |
AlertDismissAction |
AlertClearAction
export type NotificationActionType =
NotificationAddAction |
NotificationDismissAction |

View File

@ -1,48 +1,29 @@
import React from 'react'
import classnames from 'classnames'
import { Alert as AlertType } from '../actions/NotifyActions'
export interface AlertProps {
alert: AlertType
dismiss: (alert: AlertType) => void
children: React.ReactNode
}
export class Alert extends React.PureComponent<AlertProps> {
dismiss = () => {
const { alert, dismiss } = this.props
dismiss(alert)
}
render () {
const { alert } = this.props
export const Alert = React.memo(
function Alert(props: AlertProps) {
return (
<div className={classnames('alert', alert.type)}>
<span>{alert.message}</span>
{alert.dismissable && (
<button
className="action-alert-dismiss"
onClick={this.dismiss}
>{alert.action}</button>
)}
<div className='alert'>
{props.children}
</div>
)
}
}
},
)
export interface AlertsProps {
alerts: AlertType[]
dismiss: (alert: AlertType) => void
children: React.ReactNode
}
export default class Alerts extends React.PureComponent<AlertsProps> {
render () {
const { alerts, dismiss } = this.props
export const Alerts = React.memo(
function Alerts(props: AlertsProps) {
return (
<div className="alerts">
{alerts.map((alert, i) => (
<Alert alert={alert} dismiss={dismiss} key={i} />
))}
{props.children}
</div>
)
}
}
},
)

View File

@ -2,11 +2,10 @@ import map from 'lodash/map'
import React from 'react'
import Peer from 'simple-peer'
import { Message } from '../actions/ChatActions'
import { Alert, Notification, dismissNotification } from '../actions/NotifyActions'
import { Notification, dismissNotification } from '../actions/NotifyActions'
import { TextMessage } from '../actions/PeerActions'
import { AddStreamPayload } from '../actions/StreamActions'
import * as constants from '../constants'
import Alerts from './Alerts'
import Chat from './Chat'
import Notifications from './Notifications'
import Toolbar from './Toolbar'
@ -15,8 +14,6 @@ import { Media } from './Media'
export interface AppProps {
active: string | null
alerts: Alert[]
dismissAlert: (alert: Alert) => void
dismissNotification: typeof dismissNotification
init: () => void
notifications: Record<string, Notification>
@ -62,8 +59,6 @@ export default class App extends React.PureComponent<AppProps, AppState> {
render () {
const {
active,
alerts,
dismissAlert,
dismissNotification,
notifications,
messages,
@ -87,7 +82,6 @@ export default class App extends React.PureComponent<AppProps, AppState> {
onSendFile={onSendFile}
stream={streams[constants.ME]}
/>
<Alerts alerts={alerts} dismiss={dismissAlert} />
<Notifications
dismiss={dismissNotification}
notifications={notifications}

View File

@ -3,6 +3,7 @@ import { connect } from 'react-redux'
import { AudioConstraint, MediaDevice, setAudioConstraint, setVideoConstraint, VideoConstraint, getMediaStream, enumerateDevices, play } from '../actions/MediaActions'
import { MediaState } from '../reducers/media'
import { State } from '../store'
import { Alerts, Alert } from './Alerts'
export type MediaProps = MediaState & {
enumerateDevices: typeof enumerateDevices
@ -99,14 +100,14 @@ export interface AutoplayProps {
export const AutoplayMessage = React.memo(
function Autoplay(props: AutoplayProps) {
return (
<div className='autoplay'>
<React.Fragment>
The browser has blocked video autoplay on this page.
To continue with your call, please press the play button:
&nbsp;
<button className='button' onClick={props.play}>
Play
</button>
</div>
</React.Fragment>
)
},
)
@ -114,6 +115,14 @@ export const AutoplayMessage = React.memo(
export const Media = c(React.memo(function Media(props: MediaProps) {
return (
<div className='media-container'>
<Alerts>
{props.autoplayError && (
<Alert>
<AutoplayMessage play={props.play} />
</Alert>
)}
</Alerts>
{props.autoplayError && <AutoplayMessage play={props.play} />}
<MediaForm {...props} />
</div>

View File

@ -8,7 +8,6 @@ import TestUtils from 'react-dom/test-utils'
import { Provider } from 'react-redux'
import { AnyAction, applyMiddleware, createStore } from 'redux'
import { init } from '../actions/CallActions'
import { Alert } from '../actions/NotifyActions'
import * as constants from '../constants'
import reducers from '../reducers'
import { middlewares, State, Store } from '../store'
@ -66,7 +65,6 @@ describe('App', () => {
})
describe('state', () => {
let alert: Alert
beforeEach(async () => {
state.streams = {
test: {
@ -85,28 +83,9 @@ describe('App', () => {
type: 'warning',
},
}
alert = {
dismissable: true,
action: 'Dismiss',
message: 'test alert',
type: 'info',
}
state.alerts = [alert]
await render()
})
describe('alerts', () => {
it('can be dismissed', () => {
const dismiss = node.querySelector('.action-alert-dismiss')!
dispatchSpy.mockReset()
TestUtils.Simulate.click(dismiss)
expect(dispatchSpy.mock.calls).toEqual([[{
type: constants.ALERT_DISMISS,
payload: alert,
}]])
})
})
describe('video', () => {
it('can be activated', () => {
dispatchSpy.mockReset()

View File

@ -1,7 +1,7 @@
import { connect } from 'react-redux'
import { init } from '../actions/CallActions'
import { play } from '../actions/MediaActions'
import { dismissAlert, dismissNotification } from '../actions/NotifyActions'
import { dismissNotification } from '../actions/NotifyActions'
import { sendFile, sendMessage } from '../actions/PeerActions'
import { toggleActive } from '../actions/StreamActions'
import App from '../components/App'
@ -11,7 +11,6 @@ function mapStateToProps (state: State) {
return {
streams: state.streams,
peers: state.peers,
alerts: state.alerts,
notifications: state.notifications,
messages: state.messages.list,
messagesCount: state.messages.count,
@ -22,7 +21,6 @@ function mapStateToProps (state: State) {
const mapDispatchToProps = {
toggleActive,
sendMessage,
dismissAlert: dismissAlert,
dismissNotification,
init,
onSendFile: sendFile,

View File

@ -1,106 +0,0 @@
import * as NotifyActions from '../actions/NotifyActions'
import { applyMiddleware, createStore, Store, bindActionCreators } from 'redux'
import { create } from '../middlewares'
import reducers from './index'
import values from 'lodash/values'
jest.useFakeTimers()
describe('reducers/alerts', () => {
let store: Store
let notifyActions: typeof NotifyActions
beforeEach(() => {
store = createStore(
reducers,
applyMiddleware(...create()),
)
notifyActions = bindActionCreators(NotifyActions, store.dispatch)
})
describe('clearAlert', () => {
[true, false].forEach(dismissable => {
beforeEach(() => {
notifyActions.clearAlerts()
})
it('adds alert to store', () => {
notifyActions.alert('test', dismissable)
expect(store.getState().alerts).toEqual([{
action: dismissable ? 'Dismiss' : undefined,
dismissable,
message: 'test',
type: 'warning',
}])
})
})
})
describe('dismissAlert', () => {
it('removes an alert', () => {
store.dispatch(NotifyActions.alert('test', true))
expect(store.getState().alerts.length).toBe(1)
store.dispatch(NotifyActions.dismissAlert(store.getState().alerts[0]))
expect(store.getState().alerts.length).toBe(0)
})
it('does not remove an alert when not found', () => {
store.dispatch(NotifyActions.alert('test', true))
expect(store.getState().alerts.length).toBe(1)
store.dispatch(NotifyActions.dismissAlert({
action: undefined,
dismissable: false,
message: 'bla',
type: 'error',
}))
expect(store.getState().alerts.length).toBe(1)
})
})
const methods: Array<'info' | 'warning' | 'error'> = [
'info',
'warning',
'error',
]
methods.forEach(type => {
describe(type, () => {
beforeEach(() => {
notifyActions[type]('Hi {0}!', 'John')
})
it('adds a notification', () => {
expect(values(store.getState().notifications)).toEqual([{
id: jasmine.any(String),
message: 'Hi John!',
type,
}])
})
it('does not fail when no arguments', () => {
notifyActions[type]()
})
})
})
describe('clear', () => {
it('clears all alerts', () => {
notifyActions.info('Hi {0}!', 'John')
notifyActions.warning('Hi {0}!', 'John')
notifyActions.error('Hi {0}!', 'John')
store.dispatch(NotifyActions.clear())
expect(store.getState().notifications).toEqual({})
})
})
})

View File

@ -1,19 +0,0 @@
import * as constants from '../constants'
import { AlertActionType, Alert } from '../actions/NotifyActions'
export type AlertState = Alert[]
const defaultState: AlertState = []
export default function alerts (state = defaultState, action: AlertActionType) {
switch (action.type) {
case constants.ALERT:
return [...state, action.payload]
case constants.ALERT_DISMISS:
return state.filter(a => a !== action.payload)
case constants.ALERT_CLEAR:
return defaultState
default:
return state
}
}

View File

@ -1,5 +1,4 @@
import active from './active'
import alerts from './alerts'
import notifications from './notifications'
import messages from './messages'
import peers from './peers'
@ -9,7 +8,6 @@ import { combineReducers } from 'redux'
export default combineReducers({
active,
alerts,
notifications,
messages,
media,

View File

@ -1,20 +1,3 @@
.media-container .autoplay {
background-color: rgba(0, 0, 0, 0.3);
left: 0;
right: 0;
top: 0;
z-index: 1000;
position: absolute;
color: $color-warning;
text-align: center;
padding: 0.5rem;
button {
padding: 0.5rem;
@include button($color-primary, $color-warning);
}
}
.media-container form.media {
max-width: 440px;
text-align: center;