From c89886bbfa5976d674a3559dc344a5fb62e8f974 Mon Sep 17 00:00:00 2001 From: Jerko Steiner Date: Mon, 18 Nov 2019 09:35:37 -0300 Subject: [PATCH] Fix toolbar icons. Hangup removes video stream --- src/client/actions/MediaActions.ts | 24 ++++++++---------------- src/client/actions/NotifyActions.ts | 16 +++++++--------- src/client/components/App.tsx | 9 +++++++-- src/client/components/Media.tsx | 15 +++++++++++++-- src/client/components/Side.tsx | 2 +- src/client/components/Toolbar.test.tsx | 14 ++++++++++++++ src/client/components/Toolbar.tsx | 24 ++++++++++-------------- src/client/constants.ts | 1 - src/client/containers/App.tsx | 3 ++- src/client/reducers/media.ts | 11 +---------- src/scss/_media.scss | 2 +- src/scss/_toolbar.scss | 9 ++++++++- 12 files changed, 72 insertions(+), 58 deletions(-) diff --git a/src/client/actions/MediaActions.ts b/src/client/actions/MediaActions.ts index db59a78..39002e4 100644 --- a/src/client/actions/MediaActions.ts +++ b/src/client/actions/MediaActions.ts @@ -1,5 +1,8 @@ import { makeAction, AsyncAction } from '../async' -import { MEDIA_AUDIO_CONSTRAINT_SET, MEDIA_VIDEO_CONSTRAINT_SET, MEDIA_ENUMERATE, MEDIA_STREAM, MEDIA_VISIBLE_SET } from '../constants' +import { MEDIA_AUDIO_CONSTRAINT_SET, MEDIA_VIDEO_CONSTRAINT_SET, MEDIA_ENUMERATE, MEDIA_STREAM } from '../constants' +import _debug from 'debug' + +const debug = _debug('peercalls') export interface MediaDevice { id: string @@ -91,19 +94,6 @@ export function setAudioConstraint( } } -export interface MediaVisibleAction { - type: 'MEDIA_VISIBLE_SET' - payload: { visible: boolean } -} - -export function setMediaVisible(visible: boolean): MediaVisibleAction { - return { - type: MEDIA_VISIBLE_SET, - payload: { visible }, - } -} - - export const play = makeAction('MEDIA_PLAY', async () => { const promises = Array .from(document.querySelectorAll('video')) @@ -114,7 +104,10 @@ export const play = makeAction('MEDIA_PLAY', async () => { export const getMediaStream = makeAction( MEDIA_STREAM, - async (constraints: GetMediaConstraints) => getUserMedia(constraints), + async (constraints: GetMediaConstraints) => { + debug('getMediaStream', constraints) + return getUserMedia(constraints) + }, ) export type MediaEnumerateAction = AsyncAction<'MEDIA_ENUMERATE', MediaDevice[]> @@ -126,5 +119,4 @@ export type MediaAction = MediaAudioConstraintAction | MediaEnumerateAction | MediaStreamAction | - MediaVisibleAction | MediaPlayAction diff --git a/src/client/actions/NotifyActions.ts b/src/client/actions/NotifyActions.ts index f6ed8ae..b47536d 100644 --- a/src/client/actions/NotifyActions.ts +++ b/src/client/actions/NotifyActions.ts @@ -1,7 +1,5 @@ import uniqueId from 'lodash/uniqueId' -import { Dispatch } from 'redux' import * as constants from '../constants' -import { ThunkResult } from '../store' function format (string: string, args: string[]) { string = args @@ -11,7 +9,7 @@ function format (string: string, args: string[]) { export type NotifyType = 'info' | 'warning' | 'error' -function notify(dispatch: Dispatch, type: NotifyType, args: string[]) { +function notify(type: NotifyType, args: string[]) { const string = args[0] || '' const message = format(string, Array.prototype.slice.call(args, 1)) const id = uniqueId('notification') @@ -20,16 +18,16 @@ function notify(dispatch: Dispatch, type: NotifyType, args: string[]) { return addNotification(payload) } -export const info = (...args: any[]): ThunkResult => { - return dispatch => dispatch(notify(dispatch, 'info', args)) +export const info = (...args: any[]): NotificationAddAction => { + return notify('info', args) } -export const warning = (...args: any[]): ThunkResult => { - return dispatch => dispatch(notify(dispatch, 'warning', args)) +export const warning = (...args: any[]): NotificationAddAction => { + return notify('warning', args) } -export const error = (...args: any[]): ThunkResult => { - return dispatch => dispatch(notify(dispatch, 'error', args)) +export const error = (...args: any[]): NotificationAddAction => { + return notify('error', args) } function addNotification(payload: Notification): NotificationAddAction { diff --git a/src/client/components/App.tsx b/src/client/components/App.tsx index 21df3dc..630d1f5 100644 --- a/src/client/components/App.tsx +++ b/src/client/components/App.tsx @@ -5,7 +5,7 @@ import Peer from 'simple-peer' import { Message } from '../actions/ChatActions' import { dismissNotification, Notification } from '../actions/NotifyActions' import { TextMessage } from '../actions/PeerActions' -import { AddStreamPayload } from '../actions/StreamActions' +import { AddStreamPayload, removeStream } from '../actions/StreamActions' import * as constants from '../constants' import Chat from './Chat' import { Media } from './Media' @@ -25,6 +25,7 @@ export interface AppProps { play: () => void sendMessage: (message: TextMessage) => void streams: Record + removeStream: typeof removeStream onSendFile: (file: File) => void toggleActive: (userId: string) => void } @@ -58,6 +59,9 @@ export default class App extends React.PureComponent { const { init } = this.props init() } + onHangup = () => { + this.props.removeStream(constants.ME) + } render () { const { active, @@ -81,12 +85,13 @@ export default class App extends React.PureComponent { return (
- + diff --git a/src/client/components/Media.tsx b/src/client/components/Media.tsx index 8ab173a..9c0986e 100644 --- a/src/client/components/Media.tsx +++ b/src/client/components/Media.tsx @@ -4,18 +4,27 @@ import { AudioConstraint, MediaDevice, setAudioConstraint, setVideoConstraint, V import { MediaState } from '../reducers/media' import { State } from '../store' import { Alerts, Alert } from './Alerts' +import { info, warning, error } from '../actions/NotifyActions' +import { ME } from '../constants' export type MediaProps = MediaState & { + visible: boolean enumerateDevices: typeof enumerateDevices onSetVideoConstraint: typeof setVideoConstraint onSetAudioConstraint: typeof setAudioConstraint getMediaStream: typeof getMediaStream play: typeof play + logInfo: typeof info + logWarning: typeof warning + logError: typeof error } function mapStateToProps(state: State) { + const localStream = state.streams[ME] + const visible = !localStream return { ...state.media, + visible, } } @@ -25,6 +34,9 @@ const mapDispatchToProps = { onSetAudioConstraint: setAudioConstraint, getMediaStream, play, + logInfo: info, + logWarning: warning, + logError: error, } const c = connect(mapStateToProps, mapDispatchToProps) @@ -42,8 +54,7 @@ export const MediaForm = React.memo(function MediaForm(props: MediaProps) { try { await props.getMediaStream({ audio, video }) } catch (err) { - console.error(err.stack) - // TODO display a message + props.logError('Error: {0}', err) } } diff --git a/src/client/components/Side.tsx b/src/client/components/Side.tsx index 1e269e0..b86cd54 100644 --- a/src/client/components/Side.tsx +++ b/src/client/components/Side.tsx @@ -10,7 +10,7 @@ export type SideProps = (Left | Right | Top | Bottom) & { className?: string zIndex: number children: React.ReactNode - align?: 'baseline' | 'center' | 'end' + align?: 'baseline' | 'center' | 'flex-end' } export const Side = React.memo( diff --git a/src/client/components/Toolbar.test.tsx b/src/client/components/Toolbar.test.tsx index 87ce9ed..a83f8e4 100644 --- a/src/client/components/Toolbar.test.tsx +++ b/src/client/components/Toolbar.test.tsx @@ -20,6 +20,7 @@ describe('components/Toolbar', () => { return { let url: string let onToggleChat: jest.Mock<() => void> let onSendFile: jest.Mock<(file: File) => void> + let onHangup: jest.Mock<() => void> async function render () { mediaStream = new MediaStream() onToggleChat = jest.fn() onSendFile = jest.fn() + onHangup = jest.fn() const div = document.createElement('div') await new Promise(resolve => { ReactDOM.render( resolve(instance!)} chatVisible + onHangup={onHangup} onToggleChat={onToggleChat} onSendFile={onSendFile} messagesCount={1} @@ -122,4 +126,14 @@ describe('components/Toolbar', () => { }) }) + describe('onHangup', () => { + it('calls onHangup callback', () => { + expect(onHangup.mock.calls.length).toBe(0) + const hangup = node.querySelector('.hangup')! + expect(hangup).toBeDefined() + TestUtils.Simulate.click(hangup) + expect(onHangup.mock.calls.length).toBe(1) + }) + }) + }) diff --git a/src/client/components/Toolbar.tsx b/src/client/components/Toolbar.tsx index 8a9c501..69994ae 100644 --- a/src/client/components/Toolbar.tsx +++ b/src/client/components/Toolbar.tsx @@ -12,6 +12,7 @@ export interface ToolbarProps { stream: AddStreamPayload onToggleChat: () => void onSendFile: (file: File) => void + onHangup: () => void chatVisible: boolean } @@ -38,17 +39,10 @@ function ToolbarButton(props: ToolbarButtonProps) { const { blink, on } = props const icon = !on && props.offIcon ? props.offIcon : props.icon - function onClick(event: React.MouseEvent) { - props.onClick() - document.activeElement && - document.activeElement instanceof HTMLElement && - document.activeElement.blur() - } - return ( @@ -181,12 +175,14 @@ extends React.PureComponent { title='Toggle Fullscreen' /> - + {this.props.stream && this.props.stream.stream && ( + + )}
) diff --git a/src/client/constants.ts b/src/client/constants.ts index 489e215..3cea48d 100644 --- a/src/client/constants.ts +++ b/src/client/constants.ts @@ -20,7 +20,6 @@ export const MEDIA_STREAM = 'MEDIA_STREAM' export const MEDIA_VIDEO_CONSTRAINT_SET = 'MEDIA_VIDEO_CONSTRAINT_SET' export const MEDIA_AUDIO_CONSTRAINT_SET = 'MEDIA_AUDIO_CONSTRAINT_SET' export const MEDIA_PLAY = 'MEDIA_PLAY' -export const MEDIA_VISIBLE_SET = 'MEDIA_VISIBLE_SET' export const PEER_ADD = 'PEER_ADD' export const PEER_REMOVE = 'PEER_REMOVE' diff --git a/src/client/containers/App.tsx b/src/client/containers/App.tsx index 5ccbee9..a59ec49 100644 --- a/src/client/containers/App.tsx +++ b/src/client/containers/App.tsx @@ -3,7 +3,7 @@ import { init } from '../actions/CallActions' import { play } from '../actions/MediaActions' import { dismissNotification } from '../actions/NotifyActions' import { sendFile, sendMessage } from '../actions/PeerActions' -import { toggleActive } from '../actions/StreamActions' +import { toggleActive, removeStream } from '../actions/StreamActions' import App from '../components/App' import { State } from '../store' @@ -22,6 +22,7 @@ const mapDispatchToProps = { toggleActive, sendMessage, dismissNotification, + removeStream, init, onSendFile: sendFile, play, diff --git a/src/client/reducers/media.ts b/src/client/reducers/media.ts index 90f47f9..c169c9a 100644 --- a/src/client/reducers/media.ts +++ b/src/client/reducers/media.ts @@ -1,5 +1,5 @@ import { MediaDevice, AudioConstraint, VideoConstraint, MediaAction, MediaEnumerateAction, MediaStreamAction, MediaPlayAction } from '../actions/MediaActions' -import { MEDIA_ENUMERATE, MEDIA_AUDIO_CONSTRAINT_SET, MEDIA_VIDEO_CONSTRAINT_SET, MEDIA_VISIBLE_SET, MEDIA_STREAM, MEDIA_PLAY } from '../constants' +import { MEDIA_ENUMERATE, MEDIA_AUDIO_CONSTRAINT_SET, MEDIA_VIDEO_CONSTRAINT_SET, MEDIA_STREAM, MEDIA_PLAY } from '../constants' export interface MediaState { devices: MediaDevice[] @@ -8,7 +8,6 @@ export interface MediaState { loading: boolean error: string autoplayError: boolean - visible: boolean } const defaultState: MediaState = { @@ -18,7 +17,6 @@ const defaultState: MediaState = { loading: false, error: '', autoplayError: false, - visible: true, } export function handleEnumerate( @@ -54,13 +52,11 @@ export function handleMediaStream( case 'resolved': return { ...state, - visible: false, } case 'rejected': return { ...state, error: action.payload.message, - visible: true, } default: return state @@ -105,11 +101,6 @@ export default function media( ...state, video: action.payload, } - case MEDIA_VISIBLE_SET: - return { - ...state, - visible: action.payload.visible, - } case MEDIA_STREAM: return handleMediaStream(state, action) case MEDIA_PLAY: diff --git a/src/scss/_media.scss b/src/scss/_media.scss index 195da61..87abcea 100644 --- a/src/scss/_media.scss +++ b/src/scss/_media.scss @@ -1,5 +1,5 @@ .media-container form.media { - margin: 1rem auto 0; + margin: 4rem auto 0; max-width: 450px; text-align: center; diff --git a/src/scss/_toolbar.scss b/src/scss/_toolbar.scss index 22d460a..ef08f8e 100644 --- a/src/scss/_toolbar.scss +++ b/src/scss/_toolbar.scss @@ -42,6 +42,12 @@ transition: opacity 200ms ease-in 25ms, transform 100ms ease-in; transform: translateX(-100%); pointer-events: none; + position: absolute; + left: 100%; + top: 0; + bottom: 0; + white-space: nowrap; + line-height: 48px; } .button { @@ -51,8 +57,9 @@ flex-direction: row; align-items: center; cursor: pointer; + position: relative; - &:hover, &:focus { + &:hover { .icon { box-shadow: 4px 4px 48px #666; cursor: pointer;