Use <Side /> instead of position absolute

Some components still use position fixed. This could change in the
future.
This commit is contained in:
Jerko Steiner 2019-11-17 17:24:23 -03:00
parent 58039eb086
commit fddb88f5b8
13 changed files with 167 additions and 81 deletions

View File

@ -1,16 +1,18 @@
import classnames from 'classnames'
import map from 'lodash/map' 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 { Notification, dismissNotification } from '../actions/NotifyActions' import { dismissNotification, Notification } 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 Chat from './Chat' import Chat from './Chat'
import { Media } from './Media'
import Notifications from './Notifications' import Notifications from './Notifications'
import { Side } from './Side'
import Toolbar from './Toolbar' import Toolbar from './Toolbar'
import Video from './Video' import Video from './Video'
import { Media } from './Media'
export interface AppProps { export interface AppProps {
active: string | null active: string | null
@ -75,47 +77,61 @@ export default class App extends React.PureComponent<AppProps, AppState> {
return ( return (
<div className="app"> <div className="app">
<Toolbar <Side align='end' left zIndex={1}>
chatVisible={this.state.chatVisible} <Toolbar
messagesCount={messagesCount} chatVisible={this.state.chatVisible}
onToggleChat={this.handleToggleChat} messagesCount={messagesCount}
onSendFile={onSendFile} onToggleChat={this.handleToggleChat}
stream={streams[constants.ME]} onSendFile={onSendFile}
/> stream={streams[constants.ME]}
/>
</Side>
<Side top zIndex={2}>
<Notifications <Notifications
dismiss={dismissNotification} dismiss={dismissNotification}
notifications={notifications} notifications={notifications}
/> />
<Media /> <Media />
</Side>
<Chat <Chat
messages={messages} messages={messages}
onClose={this.handleHideChat} onClose={this.handleHideChat}
sendMessage={sendMessage} sendMessage={sendMessage}
visible={this.state.chatVisible} visible={this.state.chatVisible}
/> />
<div className="videos"> <div
<Video className={classnames('videos', {
videos={videos} 'chat-visible': this.state.chatVisible,
active={active === constants.ME} })}
onClick={toggleActive} >
play={play} {streams[constants.ME] && (
stream={streams[constants.ME]}
userId={constants.ME}
muted
mirrored
/>
{map(peers, (_, userId) => (
<Video <Video
active={userId === active} videos={videos}
key={userId} active={active === constants.ME}
onClick={toggleActive} onClick={toggleActive}
play={play} play={play}
stream={streams[userId]} stream={streams[constants.ME]}
userId={userId} userId={constants.ME}
videos={videos} muted
mirrored
/> />
))} )}
{
map(peers, (_, userId) => userId)
.filter(stream => !!stream)
.map(userId =>
<Video
active={userId === active}
key={userId}
onClick={toggleActive}
play={play}
stream={streams[userId]}
userId={userId}
videos={videos}
/>,
)
}
</div> </div>
</div> </div>
) )

View File

@ -101,7 +101,7 @@ export const AutoplayMessage = React.memo(
function Autoplay(props: AutoplayProps) { function Autoplay(props: AutoplayProps) {
return ( return (
<React.Fragment> <React.Fragment>
The browser has blocked video autoplay on this page. Your 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:
&nbsp; &nbsp;
<button className='button' onClick={props.play}> <button className='button' onClick={props.play}>

View File

@ -42,7 +42,7 @@ const Notification = React.memo(
export default class Notifications export default class Notifications
extends React.PureComponent<NotificationsProps> { extends React.PureComponent<NotificationsProps> {
static defaultProps = { static defaultProps = {
max: 10, max: 5,
} }
render () { render () {
const { dismiss, notifications, max } = this.props const { dismiss, notifications, max } = this.props

View File

@ -0,0 +1,27 @@
import React from 'react'
import classnames from 'classnames'
export type Left = { left: true }
export type Right = { right: true }
export type Top = { top: true }
export type Bottom = { bottom: true }
export type SideProps = (Left | Right | Top | Bottom) & {
zIndex: number
children: React.ReactNode
align?: 'baseline' | 'center' | 'end'
}
export const Side = React.memo(
function Side(props: SideProps) {
const className = classnames('side', { ...props })
return (
<div
className={className}
style={{alignItems: props.align || 'center', zIndex: props.zIndex}}
>
{props.children}
</div>
)
},
)

View File

@ -87,7 +87,7 @@ extends React.PureComponent<ToolbarProps, ToolbarState> {
return ( return (
<div className="toolbar active"> <div className="toolbar active">
<div onClick={this.handleToggleChat} <a onClick={this.handleToggleChat}
className={classnames('button chat', { className={classnames('button chat', {
on: this.props.chatVisible, on: this.props.chatVisible,
})} })}
@ -96,8 +96,8 @@ extends React.PureComponent<ToolbarProps, ToolbarState> {
title="Chat" title="Chat"
> >
<span className="icon icon-question_answer" /> <span className="icon icon-question_answer" />
</div> </a>
<div <a
className="button send-file" className="button send-file"
onClick={this.handleSendFile} onClick={this.handleSendFile}
title="Send file" title="Send file"
@ -110,11 +110,11 @@ extends React.PureComponent<ToolbarProps, ToolbarState> {
onChange={this.handleSelectFiles} onChange={this.handleSelectFiles}
/> />
<span className="icon icon-file-text2" /> <span className="icon icon-file-text2" />
</div> </a>
{stream && ( {stream && (
<div> <React.Fragment>
<div <a
onClick={this.handleMicClick} onClick={this.handleMicClick}
className={classnames('button mute-audio', { className={classnames('button mute-audio', {
on: this.state.micMuted, on: this.state.micMuted,
@ -123,8 +123,8 @@ extends React.PureComponent<ToolbarProps, ToolbarState> {
> >
<span className="on icon icon-mic_off" /> <span className="on icon icon-mic_off" />
<span className="off icon icon-mic" /> <span className="off icon icon-mic" />
</div> </a>
<div onClick={this.handleCamClick} <a onClick={this.handleCamClick}
className={classnames('button mute-video', { className={classnames('button mute-video', {
on: this.state.camDisabled, on: this.state.camDisabled,
})} })}
@ -132,11 +132,11 @@ extends React.PureComponent<ToolbarProps, ToolbarState> {
> >
<span className="on icon icon-videocam_off" /> <span className="on icon icon-videocam_off" />
<span className="off icon icon-videocam" /> <span className="off icon icon-videocam" />
</div> </a>
</div> </React.Fragment>
)} )}
<div onClick={this.handleFullscreenClick} <a onClick={this.handleFullscreenClick}
className={classnames('button fullscreen', { className={classnames('button fullscreen', {
on: this.state.fullScreenEnabled, on: this.state.fullScreenEnabled,
})} })}
@ -144,14 +144,14 @@ extends React.PureComponent<ToolbarProps, ToolbarState> {
> >
<span className="on icon icon-fullscreen_exit" /> <span className="on icon icon-fullscreen_exit" />
<span className="off icon icon-fullscreen" /> <span className="off icon icon-fullscreen" />
</div> </a>
<div onClick={this.handleHangoutClick} <a onClick={this.handleHangoutClick}
className="button hangup" className="button hangup"
title="Hangup" title="Hangup"
> >
<span className="icon icon-call_end" /> <span className="icon icon-call_end" />
</div> </a>
</div> </div>
) )
} }

View File

@ -64,11 +64,26 @@ describe('App', () => {
}) })
}) })
describe('chat toggle', () => {
it('toggles chat state', async () => {
await render()
const chatButton = node.querySelector('.toolbar .button.chat')!
expect(chatButton).toBeTruthy()
TestUtils.Simulate.click(chatButton)
TestUtils.Simulate.click(chatButton)
})
})
describe('state', () => { describe('state', () => {
beforeEach(async () => { beforeEach(async () => {
state.streams = { state.streams = {
test: { [constants.ME]: {
userId: 'test', userId: constants.ME,
stream: new MediaStream(),
url: 'blob://',
},
'other-user': {
userId: 'other-user',
stream: new MediaStream(), stream: new MediaStream(),
url: 'blob://', url: 'blob://',
}, },

View File

@ -1,15 +1,13 @@
.alert { .alert {
background-color: black;
background-color: rgba(0, 0, 0, 0.3);
left: 0; left: 0;
opacity: 1; opacity: 1;
position: fixed;
right: 0; right: 0;
text-align: center; text-align: center;
top: 0;
transition: visibility 100ms ease-in, opacity 100ms ease-in; transition: visibility 100ms ease-in, opacity 100ms ease-in;
z-index: 4; color: $color-warning;
pointer-events: none; text-shadow: 0px 0px 3px black;
padding: 1rem;
font-size: 1.2rem;
span { span {
display: inline-block; display: inline-block;
@ -18,12 +16,9 @@
} }
button { button {
line-height: 1.4rem; @include button($color-primary, $color-warning);
border: none; font-size: 1.2rem;
border-radius: 0.3rem; padding: 1rem;
color: $color-info;
background-color: $color-fg;
vertical-align: middle;
} }
} }

View File

@ -10,7 +10,7 @@
transition: transform 0.5s cubic-bezier(0.55, 0, 0, 1), box-shadow 0.5s cubic-bezier(0.55, 0, 0, 1); transition: transform 0.5s cubic-bezier(0.55, 0, 0, 1), box-shadow 0.5s cubic-bezier(0.55, 0, 0, 1);
width: 320px; width: 320px;
margin: 0 auto; margin: 0 auto;
z-index: 3; z-index: 4;
&.show { &.show {
-ms-transform: none; -ms-transform: none;

View File

@ -1,11 +1,7 @@
.media-container form.media { .media-container form.media {
margin: 1rem auto 0;
max-width: 440px; max-width: 440px;
text-align: center; text-align: center;
position: absolute;
z-index: 100;
left: 50%;
top: 100px;
transform: translateX(-50%);
& > * { & > * {
margin: 0.25rem; margin: 0.25rem;

View File

@ -2,17 +2,15 @@
pointer-events: none; pointer-events: none;
font-family: $font-monospace; font-family: $font-monospace;
font-size: 10px; font-size: 10px;
left: 1rem;
position: fixed;
right: 1rem;
text-align: right; text-align: right;
top: 1rem; width: 100%;
z-index: 3; padding: 1rem;
height: 100px;
.notification { .notification {
color: $color-info; color: $color-info;
text-shadow: 0px 0px 3px black;
padding: 0.25rem; padding: 0.25rem;
background-color: rgba(0, 0, 0, 0.2);
} }
.notification.error { .notification.error {

View File

@ -1,9 +1,5 @@
.toolbar { .toolbar {
bottom: 20px; margin: 0 0 1rem 1rem;
left: 6vw;
position: absolute;
z-index: 3;
/* on icons are hidden by default */ /* on icons are hidden by default */
.icon { .icon {
@ -38,7 +34,6 @@
border-radius: 48px; border-radius: 48px;
box-shadow: 2px 2px 24px #444; box-shadow: 2px 2px 24px #444;
display: block; display: block;
margin: 0 0 3vh 0;
transform: translateX(calc(-6vw - 96px)); transform: translateX(calc(-6vw - 96px));
transition: all .1s; transition: all .1s;
transition-timing-function: ease-in-out; transition-timing-function: ease-in-out;
@ -48,6 +43,10 @@
} }
} }
.button + .button {
margin-top: 1rem;
}
/* off icons are hidden when parent svg has class 'on' */ /* off icons are hidden when parent svg has class 'on' */
&.active .button { &.active .button {

View File

@ -1,8 +1,8 @@
.videos { .videos {
position: fixed; position: fixed;
height: 100px; height: 100px;
bottom: 15px; bottom: 1rem;
right: 0px; right: 1rem;
text-align: right; text-align: right;
$video-size: 100px; $video-size: 100px;
@ -12,10 +12,9 @@
box-shadow: 0px 0px 5px black; box-shadow: 0px 0px 5px black;
border-radius: 10px; border-radius: 10px;
display: inline-block; display: inline-block;
margin-right: 10px;
width: $video-size; width: $video-size;
height: 100%; height: 100%;
z-index: 2; z-index: 3;
video { video {
border-radius: 10px; border-radius: 10px;
@ -26,6 +25,10 @@
} }
} }
.video-container + .video-container {
margin-left: 1rem;
}
.video-container.active { .video-container.active {
background-color: transparent; background-color: transparent;
box-shadow: none; box-shadow: none;

View File

@ -161,3 +161,40 @@ body.call {
opacity: 0.01; opacity: 0.01;
transition: opacity 100ms ease-in; transition: opacity 100ms ease-in;
} }
.side {
position: absolute;
display: flex;
&.left {
flex-direction: row;
left: 0;
top: 0;
bottom: 0;
}
&.right {
flex-direction: row;
right: 0;
top: 0;
bottom: 0;
}
&.top {
flex-direction: column;
top: 0;
left: 0;
right: 0;
}
&.bottom {
flex-direction: column;
bottom: 0;
left: 0;
right: 0;
}
}
.chat-visible {
transform: translateX(-330px);
}