Remove alerts from state
This commit is contained in:
parent
fcb47a2cc5
commit
58039eb086
@ -70,57 +70,6 @@ export const clear = (): NotificationClearAction => ({
|
|||||||
type: constants.NOTIFY_CLEAR,
|
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 =
|
export type NotificationActionType =
|
||||||
NotificationAddAction |
|
NotificationAddAction |
|
||||||
NotificationDismissAction |
|
NotificationDismissAction |
|
||||||
|
|||||||
@ -1,48 +1,29 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import classnames from 'classnames'
|
|
||||||
import { Alert as AlertType } from '../actions/NotifyActions'
|
|
||||||
|
|
||||||
export interface AlertProps {
|
export interface AlertProps {
|
||||||
alert: AlertType
|
children: React.ReactNode
|
||||||
dismiss: (alert: AlertType) => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Alert extends React.PureComponent<AlertProps> {
|
export const Alert = React.memo(
|
||||||
dismiss = () => {
|
function Alert(props: AlertProps) {
|
||||||
const { alert, dismiss } = this.props
|
|
||||||
dismiss(alert)
|
|
||||||
}
|
|
||||||
render () {
|
|
||||||
const { alert } = this.props
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classnames('alert', alert.type)}>
|
<div className='alert'>
|
||||||
<span>{alert.message}</span>
|
{props.children}
|
||||||
{alert.dismissable && (
|
|
||||||
<button
|
|
||||||
className="action-alert-dismiss"
|
|
||||||
onClick={this.dismiss}
|
|
||||||
>{alert.action}</button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
}
|
)
|
||||||
|
|
||||||
export interface AlertsProps {
|
export interface AlertsProps {
|
||||||
alerts: AlertType[]
|
children: React.ReactNode
|
||||||
dismiss: (alert: AlertType) => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Alerts extends React.PureComponent<AlertsProps> {
|
export const Alerts = React.memo(
|
||||||
render () {
|
function Alerts(props: AlertsProps) {
|
||||||
const { alerts, dismiss } = this.props
|
|
||||||
return (
|
return (
|
||||||
<div className="alerts">
|
<div className="alerts">
|
||||||
{alerts.map((alert, i) => (
|
{props.children}
|
||||||
<Alert alert={alert} dismiss={dismiss} key={i} />
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
}
|
)
|
||||||
|
|||||||
@ -2,11 +2,10 @@ import map from 'lodash/map'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import Peer from 'simple-peer'
|
import Peer from 'simple-peer'
|
||||||
import { Message } from '../actions/ChatActions'
|
import { Message } from '../actions/ChatActions'
|
||||||
import { Alert, Notification, dismissNotification } from '../actions/NotifyActions'
|
import { Notification, dismissNotification } from '../actions/NotifyActions'
|
||||||
import { TextMessage } from '../actions/PeerActions'
|
import { TextMessage } from '../actions/PeerActions'
|
||||||
import { AddStreamPayload } from '../actions/StreamActions'
|
import { AddStreamPayload } from '../actions/StreamActions'
|
||||||
import * as constants from '../constants'
|
import * as constants from '../constants'
|
||||||
import Alerts from './Alerts'
|
|
||||||
import Chat from './Chat'
|
import Chat from './Chat'
|
||||||
import Notifications from './Notifications'
|
import Notifications from './Notifications'
|
||||||
import Toolbar from './Toolbar'
|
import Toolbar from './Toolbar'
|
||||||
@ -15,8 +14,6 @@ import { Media } from './Media'
|
|||||||
|
|
||||||
export interface AppProps {
|
export interface AppProps {
|
||||||
active: string | null
|
active: string | null
|
||||||
alerts: Alert[]
|
|
||||||
dismissAlert: (alert: Alert) => void
|
|
||||||
dismissNotification: typeof dismissNotification
|
dismissNotification: typeof dismissNotification
|
||||||
init: () => void
|
init: () => void
|
||||||
notifications: Record<string, Notification>
|
notifications: Record<string, Notification>
|
||||||
@ -62,8 +59,6 @@ export default class App extends React.PureComponent<AppProps, AppState> {
|
|||||||
render () {
|
render () {
|
||||||
const {
|
const {
|
||||||
active,
|
active,
|
||||||
alerts,
|
|
||||||
dismissAlert,
|
|
||||||
dismissNotification,
|
dismissNotification,
|
||||||
notifications,
|
notifications,
|
||||||
messages,
|
messages,
|
||||||
@ -87,7 +82,6 @@ export default class App extends React.PureComponent<AppProps, AppState> {
|
|||||||
onSendFile={onSendFile}
|
onSendFile={onSendFile}
|
||||||
stream={streams[constants.ME]}
|
stream={streams[constants.ME]}
|
||||||
/>
|
/>
|
||||||
<Alerts alerts={alerts} dismiss={dismissAlert} />
|
|
||||||
<Notifications
|
<Notifications
|
||||||
dismiss={dismissNotification}
|
dismiss={dismissNotification}
|
||||||
notifications={notifications}
|
notifications={notifications}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { connect } from 'react-redux'
|
|||||||
import { AudioConstraint, MediaDevice, setAudioConstraint, setVideoConstraint, VideoConstraint, getMediaStream, enumerateDevices, play } from '../actions/MediaActions'
|
import { AudioConstraint, MediaDevice, setAudioConstraint, setVideoConstraint, VideoConstraint, getMediaStream, enumerateDevices, play } from '../actions/MediaActions'
|
||||||
import { MediaState } from '../reducers/media'
|
import { MediaState } from '../reducers/media'
|
||||||
import { State } from '../store'
|
import { State } from '../store'
|
||||||
|
import { Alerts, Alert } from './Alerts'
|
||||||
|
|
||||||
export type MediaProps = MediaState & {
|
export type MediaProps = MediaState & {
|
||||||
enumerateDevices: typeof enumerateDevices
|
enumerateDevices: typeof enumerateDevices
|
||||||
@ -99,14 +100,14 @@ export interface AutoplayProps {
|
|||||||
export const AutoplayMessage = React.memo(
|
export const AutoplayMessage = React.memo(
|
||||||
function Autoplay(props: AutoplayProps) {
|
function Autoplay(props: AutoplayProps) {
|
||||||
return (
|
return (
|
||||||
<div className='autoplay'>
|
<React.Fragment>
|
||||||
The browser has blocked video autoplay on this page.
|
The browser has blocked video autoplay on this page.
|
||||||
To continue with your call, please press the play button:
|
To continue with your call, please press the play button:
|
||||||
|
|
||||||
<button className='button' onClick={props.play}>
|
<button className='button' onClick={props.play}>
|
||||||
Play
|
Play
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -114,6 +115,14 @@ export const AutoplayMessage = React.memo(
|
|||||||
export const Media = c(React.memo(function Media(props: MediaProps) {
|
export const Media = c(React.memo(function Media(props: MediaProps) {
|
||||||
return (
|
return (
|
||||||
<div className='media-container'>
|
<div className='media-container'>
|
||||||
|
<Alerts>
|
||||||
|
{props.autoplayError && (
|
||||||
|
<Alert>
|
||||||
|
<AutoplayMessage play={props.play} />
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
</Alerts>
|
||||||
|
|
||||||
{props.autoplayError && <AutoplayMessage play={props.play} />}
|
{props.autoplayError && <AutoplayMessage play={props.play} />}
|
||||||
<MediaForm {...props} />
|
<MediaForm {...props} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import TestUtils from 'react-dom/test-utils'
|
|||||||
import { Provider } from 'react-redux'
|
import { Provider } from 'react-redux'
|
||||||
import { AnyAction, applyMiddleware, createStore } from 'redux'
|
import { AnyAction, applyMiddleware, createStore } from 'redux'
|
||||||
import { init } from '../actions/CallActions'
|
import { init } from '../actions/CallActions'
|
||||||
import { Alert } from '../actions/NotifyActions'
|
|
||||||
import * as constants from '../constants'
|
import * as constants from '../constants'
|
||||||
import reducers from '../reducers'
|
import reducers from '../reducers'
|
||||||
import { middlewares, State, Store } from '../store'
|
import { middlewares, State, Store } from '../store'
|
||||||
@ -66,7 +65,6 @@ describe('App', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('state', () => {
|
describe('state', () => {
|
||||||
let alert: Alert
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
state.streams = {
|
state.streams = {
|
||||||
test: {
|
test: {
|
||||||
@ -85,28 +83,9 @@ describe('App', () => {
|
|||||||
type: 'warning',
|
type: 'warning',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
alert = {
|
|
||||||
dismissable: true,
|
|
||||||
action: 'Dismiss',
|
|
||||||
message: 'test alert',
|
|
||||||
type: 'info',
|
|
||||||
}
|
|
||||||
state.alerts = [alert]
|
|
||||||
await render()
|
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', () => {
|
describe('video', () => {
|
||||||
it('can be activated', () => {
|
it('can be activated', () => {
|
||||||
dispatchSpy.mockReset()
|
dispatchSpy.mockReset()
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import { init } from '../actions/CallActions'
|
import { init } from '../actions/CallActions'
|
||||||
import { play } from '../actions/MediaActions'
|
import { play } from '../actions/MediaActions'
|
||||||
import { dismissAlert, dismissNotification } from '../actions/NotifyActions'
|
import { dismissNotification } from '../actions/NotifyActions'
|
||||||
import { sendFile, sendMessage } from '../actions/PeerActions'
|
import { sendFile, sendMessage } from '../actions/PeerActions'
|
||||||
import { toggleActive } from '../actions/StreamActions'
|
import { toggleActive } from '../actions/StreamActions'
|
||||||
import App from '../components/App'
|
import App from '../components/App'
|
||||||
@ -11,7 +11,6 @@ function mapStateToProps (state: State) {
|
|||||||
return {
|
return {
|
||||||
streams: state.streams,
|
streams: state.streams,
|
||||||
peers: state.peers,
|
peers: state.peers,
|
||||||
alerts: state.alerts,
|
|
||||||
notifications: state.notifications,
|
notifications: state.notifications,
|
||||||
messages: state.messages.list,
|
messages: state.messages.list,
|
||||||
messagesCount: state.messages.count,
|
messagesCount: state.messages.count,
|
||||||
@ -22,7 +21,6 @@ function mapStateToProps (state: State) {
|
|||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
toggleActive,
|
toggleActive,
|
||||||
sendMessage,
|
sendMessage,
|
||||||
dismissAlert: dismissAlert,
|
|
||||||
dismissNotification,
|
dismissNotification,
|
||||||
init,
|
init,
|
||||||
onSendFile: sendFile,
|
onSendFile: sendFile,
|
||||||
|
|||||||
@ -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({})
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
||||||
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,5 +1,4 @@
|
|||||||
import active from './active'
|
import active from './active'
|
||||||
import alerts from './alerts'
|
|
||||||
import notifications from './notifications'
|
import notifications from './notifications'
|
||||||
import messages from './messages'
|
import messages from './messages'
|
||||||
import peers from './peers'
|
import peers from './peers'
|
||||||
@ -9,7 +8,6 @@ import { combineReducers } from 'redux'
|
|||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
active,
|
active,
|
||||||
alerts,
|
|
||||||
notifications,
|
notifications,
|
||||||
messages,
|
messages,
|
||||||
media,
|
media,
|
||||||
|
|||||||
@ -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 {
|
.media-container form.media {
|
||||||
max-width: 440px;
|
max-width: 440px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user