Refactor Toolbar, move video & notifications
This commit is contained in:
parent
9d68e4c1f4
commit
b88889665f
@ -107,10 +107,7 @@ export function setMediaVisible(visible: boolean): MediaVisibleAction {
|
||||
export const play = makeAction('MEDIA_PLAY', async () => {
|
||||
const promises = Array
|
||||
.from(document.querySelectorAll('video'))
|
||||
.filter(video => {
|
||||
console.log('video', video.paused, video)
|
||||
return video.paused
|
||||
})
|
||||
.filter(video => video.paused)
|
||||
.map(video => video.play())
|
||||
await Promise.all(promises)
|
||||
})
|
||||
|
||||
@ -75,9 +75,13 @@ export default class App extends React.PureComponent<AppProps, AppState> {
|
||||
|
||||
const { videos } = this.state
|
||||
|
||||
const chatVisibleClassName = classnames({
|
||||
'chat-visible': this.state.chatVisible,
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="app">
|
||||
<Side align='end' left zIndex={1}>
|
||||
<Side align='end' left zIndex={2}>
|
||||
<Toolbar
|
||||
chatVisible={this.state.chatVisible}
|
||||
messagesCount={messagesCount}
|
||||
@ -86,7 +90,7 @@ export default class App extends React.PureComponent<AppProps, AppState> {
|
||||
stream={streams[constants.ME]}
|
||||
/>
|
||||
</Side>
|
||||
<Side top zIndex={2}>
|
||||
<Side className={chatVisibleClassName} top zIndex={1}>
|
||||
<Notifications
|
||||
dismiss={dismissNotification}
|
||||
notifications={notifications}
|
||||
@ -99,11 +103,7 @@ export default class App extends React.PureComponent<AppProps, AppState> {
|
||||
sendMessage={sendMessage}
|
||||
visible={this.state.chatVisible}
|
||||
/>
|
||||
<div
|
||||
className={classnames('videos', {
|
||||
'chat-visible': this.state.chatVisible,
|
||||
})}
|
||||
>
|
||||
<div className={classnames('videos', chatVisibleClassName)}>
|
||||
{streams[constants.ME] && (
|
||||
<Video
|
||||
videos={videos}
|
||||
|
||||
@ -123,7 +123,6 @@ export const Media = c(React.memo(function Media(props: MediaProps) {
|
||||
)}
|
||||
</Alerts>
|
||||
|
||||
{props.autoplayError && <AutoplayMessage play={props.play} />}
|
||||
<MediaForm {...props} />
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -7,6 +7,7 @@ export type Top = { top: true }
|
||||
export type Bottom = { bottom: true }
|
||||
|
||||
export type SideProps = (Left | Right | Top | Bottom) & {
|
||||
className?: string
|
||||
zIndex: number
|
||||
children: React.ReactNode
|
||||
align?: 'baseline' | 'center' | 'end'
|
||||
@ -14,11 +15,11 @@ export type SideProps = (Left | Right | Top | Bottom) & {
|
||||
|
||||
export const Side = React.memo(
|
||||
function Side(props: SideProps) {
|
||||
const className = classnames('side', { ...props })
|
||||
const { className, zIndex, ...otherProps } = props
|
||||
return (
|
||||
<div
|
||||
className={className}
|
||||
style={{alignItems: props.align || 'center', zIndex: props.zIndex}}
|
||||
className={classnames('side', className, { ...otherProps })}
|
||||
style={{alignItems: props.align || 'center', zIndex }}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
|
||||
@ -22,6 +22,43 @@ export interface ToolbarState {
|
||||
fullScreenEnabled: boolean
|
||||
}
|
||||
|
||||
export interface ToolbarButtonProps {
|
||||
className?: string
|
||||
badge?: string | number
|
||||
blink?: boolean
|
||||
onClick: () => void
|
||||
icon: string
|
||||
offIcon?: string
|
||||
on?: boolean
|
||||
title: string
|
||||
}
|
||||
|
||||
|
||||
function ToolbarButton(props: ToolbarButtonProps) {
|
||||
const { blink, on } = props
|
||||
const icon = !on && props.offIcon ? props.offIcon : props.icon
|
||||
|
||||
function onClick(event: React.MouseEvent<HTMLElement>) {
|
||||
props.onClick()
|
||||
document.activeElement &&
|
||||
document.activeElement instanceof HTMLElement &&
|
||||
document.activeElement.blur()
|
||||
}
|
||||
|
||||
return (
|
||||
<a
|
||||
className={classnames('button', props.className, { blink, on })}
|
||||
onClick={onClick}
|
||||
href='#'
|
||||
>
|
||||
<span className={classnames('icon', icon)}>
|
||||
{!!props.badge && <span className='badge'>{props.badge}</span>}
|
||||
</span>
|
||||
<span className="tooltip">{props.title}</span>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
export default class Toolbar
|
||||
extends React.PureComponent<ToolbarProps, ToolbarState> {
|
||||
file = React.createRef<HTMLInputElement>()
|
||||
@ -61,7 +98,7 @@ extends React.PureComponent<ToolbarProps, ToolbarState> {
|
||||
screenfull.toggle()
|
||||
this.setState({
|
||||
...this.state,
|
||||
fullScreenEnabled: !this.state.fullScreenEnabled,
|
||||
fullScreenEnabled: !screenfull.isFullscreen,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -84,88 +121,73 @@ extends React.PureComponent<ToolbarProps, ToolbarState> {
|
||||
}
|
||||
render () {
|
||||
const { messagesCount, stream } = this.props
|
||||
const unreadCount = messagesCount - this.state.readMessages
|
||||
const hasUnread = unreadCount > 0
|
||||
|
||||
return (
|
||||
<div className="toolbar active">
|
||||
<a onClick={this.handleToggleChat}
|
||||
className={classnames('button chat', {
|
||||
on: this.props.chatVisible,
|
||||
})}
|
||||
href='#'
|
||||
data-blink={!this.props.chatVisible &&
|
||||
messagesCount > this.state.readMessages}
|
||||
title="Chat"
|
||||
>
|
||||
<span className="icon icon-question_answer" />
|
||||
<span className="tooltip">Toggle Chat</span>
|
||||
</a>
|
||||
<a
|
||||
className="button send-file"
|
||||
<input
|
||||
style={hidden}
|
||||
type="file"
|
||||
multiple
|
||||
ref={this.file}
|
||||
onChange={this.handleSelectFiles}
|
||||
/>
|
||||
|
||||
<ToolbarButton
|
||||
badge={unreadCount}
|
||||
className='chat'
|
||||
icon='icon-question_answer'
|
||||
blink={!this.props.chatVisible && hasUnread}
|
||||
onClick={this.handleToggleChat}
|
||||
on={this.props.chatVisible}
|
||||
title='Toggle Chat'
|
||||
/>
|
||||
|
||||
<ToolbarButton
|
||||
className='send-file'
|
||||
icon='icon-file-text2'
|
||||
onClick={this.handleSendFile}
|
||||
title="Send file"
|
||||
href='#'
|
||||
>
|
||||
<input
|
||||
style={hidden}
|
||||
type="file"
|
||||
multiple
|
||||
ref={this.file}
|
||||
onChange={this.handleSelectFiles}
|
||||
/>
|
||||
<span className="icon icon-file-text2" />
|
||||
<span className="tooltip">Send File</span>
|
||||
</a>
|
||||
title='Send File'
|
||||
/>
|
||||
|
||||
{stream && (
|
||||
<React.Fragment>
|
||||
<a
|
||||
<ToolbarButton
|
||||
onClick={this.handleMicClick}
|
||||
className={classnames('button mute-audio', {
|
||||
on: this.state.micMuted,
|
||||
})}
|
||||
href='#'
|
||||
title="Mute audio"
|
||||
>
|
||||
<span className="on icon icon-mic_off" />
|
||||
<span className="off icon icon-mic" />
|
||||
<span className="tooltip">Toggle Microphone</span>
|
||||
</a>
|
||||
<a onClick={this.handleCamClick}
|
||||
className={classnames('button mute-video', {
|
||||
on: this.state.camDisabled,
|
||||
})}
|
||||
href='#'
|
||||
title="Mute video"
|
||||
>
|
||||
<span className="on icon icon-videocam_off" />
|
||||
<span className="off icon icon-videocam" />
|
||||
<span className="tooltip">Toggle Camera</span>
|
||||
</a>
|
||||
className='mute-audio'
|
||||
on={this.state.micMuted}
|
||||
icon='icon-mic_off'
|
||||
offIcon='icon-mic'
|
||||
title='Toggle Microphone'
|
||||
/>
|
||||
<ToolbarButton
|
||||
onClick={this.handleCamClick}
|
||||
className='mute-video'
|
||||
on={this.state.camDisabled}
|
||||
icon='icon-videocam_off'
|
||||
offIcon='icon-videocam'
|
||||
title='Toggle Camera'
|
||||
/>
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
||||
<a
|
||||
<ToolbarButton
|
||||
onClick={this.handleFullscreenClick}
|
||||
href='#'
|
||||
className={classnames('button fullscreen', {
|
||||
on: this.state.fullScreenEnabled,
|
||||
})}
|
||||
title="Enter fullscreen"
|
||||
>
|
||||
<span className="on icon icon-fullscreen_exit" />
|
||||
<span className="off icon icon-fullscreen" />
|
||||
<span className="tooltip">Fullscreen</span>
|
||||
</a>
|
||||
className='fullscreen'
|
||||
icon='icon-fullscreen_exit'
|
||||
offIcon='icon-fullscreen'
|
||||
on={this.state.fullScreenEnabled}
|
||||
title='Toggle Fullscreen'
|
||||
/>
|
||||
|
||||
<a
|
||||
<ToolbarButton
|
||||
onClick={this.handleHangoutClick}
|
||||
className="button hangup"
|
||||
href='#'
|
||||
className='hangup'
|
||||
icon='icon-call_end'
|
||||
title="Hang Up"
|
||||
>
|
||||
<span className="icon icon-call_end" />
|
||||
<span className="tooltip">Hang Up</span>
|
||||
</a>
|
||||
/>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -52,10 +52,7 @@ export default class Video extends React.PureComponent<VideoProps> {
|
||||
id={`video-${socket.id}`}
|
||||
autoPlay
|
||||
onClick={this.handleClick}
|
||||
onLoadedMetadata={() => {
|
||||
console.log('onLoadedMetadata')
|
||||
this.props.play()
|
||||
}}
|
||||
onLoadedMetadata={() => this.props.play()}
|
||||
playsInline
|
||||
ref={this.videoRef}
|
||||
muted={muted}
|
||||
|
||||
@ -28,6 +28,15 @@
|
||||
border-radius: 4px;
|
||||
border-bottom: 2px solid darken(#fff, 10%);
|
||||
text-align: center;
|
||||
color: black;
|
||||
background-color: white;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: darken(white, 5%);
|
||||
}
|
||||
&:active {
|
||||
background-color: darken(white, 10%);
|
||||
}
|
||||
}
|
||||
|
||||
select, button {
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 48px;
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 2px 2px 24px #444;
|
||||
transition: all .1s;
|
||||
transition-timing-function: ease-in-out;
|
||||
@ -21,8 +22,13 @@
|
||||
top: 12px;
|
||||
}
|
||||
|
||||
&.on {
|
||||
display: none;
|
||||
.badge {
|
||||
font-family: sans-serif;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
position: relative;
|
||||
font-size: 12px;
|
||||
top: 23px;
|
||||
}
|
||||
|
||||
}
|
||||
@ -35,17 +41,18 @@
|
||||
text-shadow: 0 0 5px black;
|
||||
transition: opacity 200ms ease-in 25ms, transform 100ms ease-in;
|
||||
transform: translateX(-100%);
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.button {
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
&:hover, &:focus {
|
||||
.icon {
|
||||
box-shadow: 4px 4px 48px #666;
|
||||
cursor: pointer;
|
||||
@ -62,12 +69,6 @@
|
||||
}
|
||||
&.on .icon {
|
||||
background: lighten(#407cf7, 10%);
|
||||
&.on {
|
||||
display: inherit;
|
||||
}
|
||||
&.off {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,12 +76,10 @@
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.chat {
|
||||
&[data-blink="true"] .icon {
|
||||
-webkit-animation: bg-blink 1s infinite;
|
||||
-moz-animation: bg-blink 1s infinite;
|
||||
animation: bg-blink 1s infinite;
|
||||
}
|
||||
.chat.blink .icon {
|
||||
-webkit-animation: bg-blink 1s infinite;
|
||||
-moz-animation: bg-blink 1s infinite;
|
||||
animation: bg-blink 1s infinite;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -2,8 +2,9 @@
|
||||
position: fixed;
|
||||
height: 100px;
|
||||
bottom: 1rem;
|
||||
right: 1rem;
|
||||
right: 0;
|
||||
text-align: right;
|
||||
transition: right cubic-bezier(0.55, 0, 0, 1) 500ms;
|
||||
|
||||
$video-size: 100px;
|
||||
|
||||
@ -15,6 +16,7 @@
|
||||
width: $video-size;
|
||||
height: 100%;
|
||||
z-index: 3;
|
||||
margin-right: 1rem;
|
||||
|
||||
video {
|
||||
border-radius: 10px;
|
||||
@ -25,10 +27,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.video-container + .video-container {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.video-container.active {
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
@ -40,6 +38,7 @@
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: -1;
|
||||
transform: none;
|
||||
|
||||
video {
|
||||
border-radius: 0;
|
||||
|
||||
@ -165,6 +165,7 @@ body.call {
|
||||
.side {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
transition: right cubic-bezier(0.55, 0, 0, 1) 500ms;
|
||||
|
||||
&.left {
|
||||
flex-direction: row;
|
||||
@ -195,6 +196,6 @@ body.call {
|
||||
}
|
||||
}
|
||||
|
||||
.chat-visible {
|
||||
transform: translateX(-330px);
|
||||
.app .chat-visible {
|
||||
right: 320px;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user