Implemented socket chat
This commit is contained in:
parent
352a10ab89
commit
0d8d3fbb33
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,5 +1,4 @@
|
||||
.DS_Store
|
||||
yarn.lock
|
||||
*.swp
|
||||
*.swo
|
||||
dist/
|
||||
|
||||
@ -43,6 +43,7 @@
|
||||
"classnames": "^2.2.5",
|
||||
"config": "^1.26.1",
|
||||
"express": "^4.13.3",
|
||||
"moment": "^2.22.2",
|
||||
"prop-types": "^15.5.10",
|
||||
"pug": "^2.0.0-rc.2",
|
||||
"react": "^15.5.4",
|
||||
|
||||
21
src/client/actions/ChatActions.js
Normal file
21
src/client/actions/ChatActions.js
Normal file
@ -0,0 +1,21 @@
|
||||
import * as constants from '../constants.js'
|
||||
import _ from 'underscore'
|
||||
|
||||
export function addMessage ({ userId, message, timestamp }) {
|
||||
return {
|
||||
type: constants.MESSAGE_ADD,
|
||||
payload: {
|
||||
id: _.uniqueId('chat'),
|
||||
userId,
|
||||
message,
|
||||
timestamp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function loadHistory (messages) {
|
||||
return {
|
||||
type: constants.MESSAGES_HISTORY,
|
||||
messages
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
import * as ChatActions from '../actions/ChatActions.js'
|
||||
import * as NotifyActions from '../actions/NotifyActions.js'
|
||||
import * as PeerActions from '../actions/PeerActions.js'
|
||||
import * as constants from '../constants.js'
|
||||
@ -41,6 +42,16 @@ class SocketHandler {
|
||||
.filter(id => !newUsersMap[id])
|
||||
.forEach(id => peers[id].destroy())
|
||||
}
|
||||
handleMessages = ({ messages }) => {
|
||||
const { dispatch } = this
|
||||
debug('socket messages: %o', messages)
|
||||
dispatch(ChatActions.loadHistory(messages))
|
||||
}
|
||||
handleNewMessage = (payload) => {
|
||||
const { dispatch } = this
|
||||
debug('socket message: %o', payload)
|
||||
dispatch(ChatActions.addMessage(payload))
|
||||
}
|
||||
}
|
||||
|
||||
export function handshake ({ socket, roomName, stream }) {
|
||||
@ -55,6 +66,8 @@ export function handshake ({ socket, roomName, stream }) {
|
||||
|
||||
socket.on(constants.SOCKET_EVENT_SIGNAL, handler.handleSignal)
|
||||
socket.on(constants.SOCKET_EVENT_USERS, handler.handleUsers)
|
||||
socket.on(constants.SOCKET_EVENT_MESSAGES, handler.handleMessages)
|
||||
socket.on(constants.SOCKET_EVENT_NEW_MESSAGE, handler.handleNewMessage)
|
||||
|
||||
debug('socket.id: %s', socket.id)
|
||||
debug('emit ready for room: %s', roomName)
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import Alerts, { AlertPropType } from './Alerts.js'
|
||||
import * as constants from '../constants.js'
|
||||
import Toolbar from './Toolbar.js'
|
||||
import Input from './Input.js'
|
||||
import Notifications, { NotificationPropTypes } from './Notifications.js'
|
||||
import Chat, { MessagePropTypes } from './Chat.js'
|
||||
import Input from './Input.js'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import Video, { StreamPropType } from './Video.js'
|
||||
@ -16,6 +17,7 @@ export default class App extends React.PureComponent {
|
||||
init: PropTypes.func.isRequired,
|
||||
notifications: PropTypes.objectOf(NotificationPropTypes).isRequired,
|
||||
notify: PropTypes.func.isRequired,
|
||||
messages: PropTypes.arrayOf(MessagePropTypes).isRequired,
|
||||
peers: PropTypes.object.isRequired,
|
||||
sendMessage: PropTypes.func.isRequired,
|
||||
streams: PropTypes.objectOf(StreamPropType).isRequired,
|
||||
@ -32,35 +34,41 @@ export default class App extends React.PureComponent {
|
||||
dismissAlert,
|
||||
notifications,
|
||||
notify,
|
||||
messages,
|
||||
peers,
|
||||
sendMessage,
|
||||
toggleActive,
|
||||
streams
|
||||
} = this.props
|
||||
|
||||
return (<div className="app">
|
||||
<Toolbar stream={streams[constants.ME]} />
|
||||
<Alerts alerts={alerts} dismiss={dismissAlert} />
|
||||
<Notifications notifications={notifications} />
|
||||
<Input notify={notify} sendMessage={sendMessage} />
|
||||
<div className="videos">
|
||||
<Video
|
||||
active={active === constants.ME}
|
||||
onClick={toggleActive}
|
||||
stream={streams[constants.ME]}
|
||||
userId={constants.ME}
|
||||
/>
|
||||
|
||||
{_.map(peers, (_, userId) => (
|
||||
return (
|
||||
<div className="app">
|
||||
<Toolbar stream={streams[constants.ME]} />
|
||||
<Alerts alerts={alerts} dismiss={dismissAlert} />
|
||||
<Notifications notifications={notifications} />
|
||||
<div id="chat">
|
||||
<Chat messages={messages} />
|
||||
<Input notify={notify} sendMessage={sendMessage} />
|
||||
</div>
|
||||
<div className="videos">
|
||||
<Video
|
||||
active={userId === active}
|
||||
key={userId}
|
||||
active={active === constants.ME}
|
||||
onClick={toggleActive}
|
||||
stream={streams[userId]}
|
||||
userId={userId}
|
||||
stream={streams[constants.ME]}
|
||||
userId={constants.ME}
|
||||
/>
|
||||
))}
|
||||
|
||||
{_.map(peers, (_, userId) => (
|
||||
<Video
|
||||
active={userId === active}
|
||||
key={userId}
|
||||
onClick={toggleActive}
|
||||
stream={streams[userId]}
|
||||
userId={userId}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
55
src/client/components/Chat.js
Normal file
55
src/client/components/Chat.js
Normal file
@ -0,0 +1,55 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import socket from '../socket.js'
|
||||
|
||||
export const MessagePropTypes = PropTypes.shape({
|
||||
userId: PropTypes.string.isRequired,
|
||||
message: PropTypes.string.isRequired,
|
||||
timestamp: PropTypes.string.isRequired
|
||||
})
|
||||
|
||||
export default class Chat extends React.PureComponent {
|
||||
static propTypes = {
|
||||
messages: PropTypes.arrayOf(MessagePropTypes).isRequired
|
||||
}
|
||||
hideChat = e => {
|
||||
document.getElementById('chat').classList.remove('show')
|
||||
document.querySelector('.toolbar .chat').classList.remove('on')
|
||||
}
|
||||
render () {
|
||||
const { messages } = this.props
|
||||
return (
|
||||
<div>
|
||||
<div className="chat-header">
|
||||
<div className="chat-close" onClick={this.hideChat}>
|
||||
<div className="button button-icon">
|
||||
<span className="material-icons">arrow_back</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="chat-title">Chat</div>
|
||||
</div>
|
||||
<div className="chat-content">
|
||||
|
||||
{messages.length ? (
|
||||
messages.map((message, i) => (
|
||||
<div className={message.userId === socket.id ? 'chat-bubble alt' : 'chat-bubble'} key={i}>
|
||||
<div className="txt">
|
||||
<p className="name">{message.userId}</p>
|
||||
<p className="message">{message.message}</p>
|
||||
<span className="timestamp">{message.timestamp}</span>
|
||||
</div>
|
||||
<div className="arrow"></div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="chat-empty">
|
||||
<div className="chat-empty-icon material-icons">chat</div>
|
||||
<div className="chat-empty-message">No Notifications</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,7 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import moment from 'moment'
|
||||
import socket from '../socket.js'
|
||||
|
||||
export default class Input extends React.PureComponent {
|
||||
static propTypes = {
|
||||
@ -30,8 +32,15 @@ export default class Input extends React.PureComponent {
|
||||
submit = () => {
|
||||
const { notify, sendMessage } = this.props
|
||||
const { message } = this.state
|
||||
notify('You: ' + message)
|
||||
sendMessage(message)
|
||||
if (message) {
|
||||
notify('You: ' + message)
|
||||
sendMessage(message)
|
||||
|
||||
const userId = socket.id;
|
||||
const timestamp = moment().format('ddd, D MMM HH:mm a');
|
||||
const payload = { userId, message, timestamp }
|
||||
socket.emit('new_message', payload)
|
||||
}
|
||||
this.setState({ message: '' })
|
||||
}
|
||||
render () {
|
||||
|
||||
@ -5,6 +5,10 @@ export default class Toolbar extends React.PureComponent {
|
||||
static propTypes = {
|
||||
stream: StreamPropType
|
||||
}
|
||||
handleChatClick = e => {
|
||||
document.getElementById('chat').classList.toggle('show')
|
||||
e.currentTarget.classList.toggle('on')
|
||||
}
|
||||
handleMicClick = e => {
|
||||
const { stream } = this.props
|
||||
stream.mediaStream.getAudioTracks().forEach(track => {
|
||||
@ -58,6 +62,12 @@ export default class Toolbar extends React.PureComponent {
|
||||
|
||||
return (
|
||||
<div className="toolbar active">
|
||||
<div onClick={this.handleChatClick}
|
||||
className="button chat"
|
||||
title="Chat"
|
||||
>
|
||||
<span className="material-icons">chat</span>
|
||||
</div>
|
||||
|
||||
{stream && (
|
||||
<div onClick={this.handleMicClick}
|
||||
|
||||
@ -17,6 +17,9 @@ export const NOTIFY = 'NOTIFY'
|
||||
export const NOTIFY_DISMISS = 'NOTIFY_DISMISS'
|
||||
export const NOTIFY_CLEAR = 'NOTIFY_CLEAR'
|
||||
|
||||
export const MESSAGE_ADD = 'MESSAGE_ADD'
|
||||
export const MESSAGES_HISTORY = 'MESSAGES_HISTORY'
|
||||
|
||||
export const PEER_ADD = 'PEER_ADD'
|
||||
export const PEER_REMOVE = 'PEER_REMOVE'
|
||||
export const PEERS_DESTROY = 'PEERS_DESTROY'
|
||||
@ -30,6 +33,8 @@ export const PEER_EVENT_DATA = 'data'
|
||||
|
||||
export const SOCKET_EVENT_SIGNAL = 'signal'
|
||||
export const SOCKET_EVENT_USERS = 'users'
|
||||
export const SOCKET_EVENT_MESSAGES = 'messages'
|
||||
export const SOCKET_EVENT_NEW_MESSAGE = 'new_message'
|
||||
|
||||
export const STREAM_ADD = 'PEER_STREAM_ADD'
|
||||
export const STREAM_REMOVE = 'PEER_STREAM_REMOVE'
|
||||
|
||||
@ -12,6 +12,7 @@ function mapStateToProps (state) {
|
||||
peers: state.peers,
|
||||
alerts: state.alerts,
|
||||
notifications: state.notifications,
|
||||
messages: state.messages,
|
||||
active: state.active
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import active from './active.js'
|
||||
import alerts from './alerts.js'
|
||||
import notifications from './notifications.js'
|
||||
import messages from './messages.js'
|
||||
import peers from './peers.js'
|
||||
import streams from './streams.js'
|
||||
import { combineReducers } from 'redux'
|
||||
@ -9,6 +10,7 @@ export default combineReducers({
|
||||
active,
|
||||
alerts,
|
||||
notifications,
|
||||
messages,
|
||||
peers,
|
||||
streams
|
||||
})
|
||||
|
||||
17
src/client/reducers/messages.js
Normal file
17
src/client/reducers/messages.js
Normal file
@ -0,0 +1,17 @@
|
||||
import * as constants from '../constants.js'
|
||||
import Immutable from 'seamless-immutable'
|
||||
|
||||
const defaultState = Immutable([])
|
||||
|
||||
export default function messages (state = defaultState, action) {
|
||||
switch (action && action.type) {
|
||||
case constants.MESSAGE_ADD:
|
||||
const messages = state.asMutable()
|
||||
messages.push(action.payload)
|
||||
return Immutable(messages)
|
||||
case constants.MESSAGES_HISTORY:
|
||||
return Immutable(action.messages);
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
32
src/scss/_alert.scss
Normal file
32
src/scss/_alert.scss
Normal file
@ -0,0 +1,32 @@
|
||||
.alert {
|
||||
background-color: black;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
left: 0;
|
||||
opacity: 1;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
top: 0;
|
||||
transition: visibility 100ms ease-in, opacity 100ms ease-in;
|
||||
z-index: 4;
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
margin: 1rem 0;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
button {
|
||||
line-height: 1.4rem;
|
||||
border: none;
|
||||
border-radius: 0.3rem;
|
||||
color: $color-info;
|
||||
background-color: $color-fg;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.alert.hidden {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
197
src/scss/_chat.scss
Normal file
197
src/scss/_chat.scss
Normal file
@ -0,0 +1,197 @@
|
||||
.input {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
bottom: 30px;
|
||||
z-index: 2;
|
||||
width: calc(100% - 30px);
|
||||
|
||||
input {
|
||||
box-shadow: 0px 0px 5px black;
|
||||
background-color: #333;
|
||||
border: none;
|
||||
color: #ccc;
|
||||
padding: 0.5rem;
|
||||
font-family: $font-monospace;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
width: 85%;
|
||||
}
|
||||
}
|
||||
|
||||
#chat {
|
||||
background-color: #f2f2f2;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
-ms-transform: translateX(100%);
|
||||
-webkit-transform: translateX(100%);
|
||||
transform: translateX(100%);
|
||||
-webkit-transition: -webkit-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: 360px;
|
||||
z-index: 2;
|
||||
|
||||
&.show {
|
||||
-ms-transform: none;
|
||||
-webkit-transform: none;
|
||||
transform: none;
|
||||
box-shadow: 0 5px 5px 5px rgba(0, 0, 0, 0.19), 0 1px 6px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
@media (max-width: 575.98px) {
|
||||
.chat {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-header {
|
||||
background-color: #333;
|
||||
color: #fff;
|
||||
height: 52px;
|
||||
line-height: 52px;
|
||||
}
|
||||
|
||||
.chat-close {
|
||||
font-size: 24px;
|
||||
height: 100%;
|
||||
line-height: 24px;
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
float: left;
|
||||
width: 64px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.chat-button {
|
||||
float: right;
|
||||
a {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-title {
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.chat-content {
|
||||
background-color: #999;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
overflow-y: auto;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 52px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.chat-empty {
|
||||
color: #eee;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
top: 50%;
|
||||
-ms-transform: translateY(-50%);
|
||||
-webkit-transform: translateY(-50%);
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.chat-empty-icon {
|
||||
font-size: 88px;
|
||||
}
|
||||
|
||||
.chat-empty-message {
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.chat-bubble {
|
||||
max-width: 240px;
|
||||
height: auto;
|
||||
display: block;
|
||||
background: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0px 0px 5px black;
|
||||
position: relative;
|
||||
margin: 0 0 25px;
|
||||
|
||||
&.alt {
|
||||
margin: 0 0 25px 60px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 80px;
|
||||
}
|
||||
|
||||
.txt {
|
||||
padding: 8px 55px 8px 14px;
|
||||
|
||||
.name {
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
margin: 0 0 4px;
|
||||
color: #3498db;
|
||||
|
||||
span {
|
||||
font-weight: normal;
|
||||
color: #b3b3b3;
|
||||
}
|
||||
|
||||
&.alt {
|
||||
color: #2ecc71;
|
||||
}
|
||||
}
|
||||
|
||||
.message {
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
color: #2b2b2b;
|
||||
}
|
||||
|
||||
.timestamp {
|
||||
font-size: 11px;
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
right: 10px;
|
||||
text-transform: uppercase; color: #999
|
||||
}
|
||||
}
|
||||
|
||||
.arrow {
|
||||
position: absolute;
|
||||
width: 0;
|
||||
bottom:42px;
|
||||
left: -16px;
|
||||
height: 0;
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
border: 0 solid transparent;
|
||||
border-top: 9px solid #f5f5f5;
|
||||
border-radius: 0 20px 0;
|
||||
width: 15px;
|
||||
height: 30px;
|
||||
transform: rotate(145deg);
|
||||
}
|
||||
}
|
||||
|
||||
&.alt {
|
||||
.arrow {
|
||||
right: -2px;
|
||||
bottom: 40px;
|
||||
left: auto;
|
||||
|
||||
&:after {
|
||||
transform: rotate(45deg) scaleY(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
24
src/scss/_notification.scss
Normal file
24
src/scss/_notification.scss
Normal file
@ -0,0 +1,24 @@
|
||||
.notifications {
|
||||
font-family: $font-monospace;
|
||||
font-size: 10px;
|
||||
left: 1rem;
|
||||
position: fixed;
|
||||
right: 1rem;
|
||||
text-align: right;
|
||||
top: 1rem;
|
||||
z-index: 3;
|
||||
|
||||
.notification {
|
||||
color: $color-info;
|
||||
padding: 0.25rem;
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.notification.error {
|
||||
color: $color-error;
|
||||
}
|
||||
|
||||
.notification.warning {
|
||||
color: $color-warning;
|
||||
}
|
||||
}
|
||||
85
src/scss/_toolbar.scss
Normal file
85
src/scss/_toolbar.scss
Normal file
@ -0,0 +1,85 @@
|
||||
.toolbar {
|
||||
bottom: 20px;
|
||||
left: 6vw;
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
|
||||
/* on icons are hidden by default */
|
||||
|
||||
.material-icons {
|
||||
&.on {
|
||||
display: none;
|
||||
}
|
||||
&.off {
|
||||
display: block;
|
||||
}
|
||||
color: #fff;
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
left: 12px;
|
||||
}
|
||||
|
||||
/* off icons are displayed by default */
|
||||
|
||||
/* on icons are displayed when parent svg has class 'on' */
|
||||
|
||||
.button {
|
||||
&.on .material-icons {
|
||||
&.on {
|
||||
display: block;
|
||||
}
|
||||
&.off {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 48px;
|
||||
box-shadow: 2px 2px 24px #444;
|
||||
display: block;
|
||||
margin: 0 0 3vh 0;
|
||||
transform: translateX(calc(-6vw - 96px));
|
||||
transition: all .1s;
|
||||
transition-timing-function: ease-in-out;
|
||||
&:hover {
|
||||
box-shadow: 4px 4px 48px #666;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
/* off icons are hidden when parent svg has class 'on' */
|
||||
|
||||
&.active .button {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.chat {
|
||||
&:hover, &.on {
|
||||
background: #407cf7;
|
||||
}
|
||||
}
|
||||
|
||||
.mute-audio {
|
||||
&:hover, &.on {
|
||||
background: #407cf7;
|
||||
}
|
||||
}
|
||||
|
||||
.mute-video {
|
||||
&:hover, &.on {
|
||||
background: #407cf7;
|
||||
}
|
||||
}
|
||||
|
||||
.fullscreen {
|
||||
&:hover, &.on {
|
||||
background: #407cf7;
|
||||
}
|
||||
}
|
||||
|
||||
.hangup {
|
||||
&:hover {
|
||||
background: #dd2c00;
|
||||
}
|
||||
}
|
||||
}
|
||||
12
src/scss/_variables.scss
Normal file
12
src/scss/_variables.scss
Normal file
@ -0,0 +1,12 @@
|
||||
$font-sans-serif: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
$font-monospace: Menlo, Monaco, Consolas, "Ubuntu Mono", monospace;
|
||||
|
||||
$color-bg: #086788;
|
||||
$color-fg: #07A0C3;
|
||||
$color-btn: #FFF1D0;
|
||||
$icon-size: 48px;
|
||||
|
||||
$color-primary: white;
|
||||
$color-info: #31EF40;
|
||||
$color-warning: #F0C808;
|
||||
$color-error: #EE7600;
|
||||
46
src/scss/_video.scss
Normal file
46
src/scss/_video.scss
Normal file
@ -0,0 +1,46 @@
|
||||
.videos {
|
||||
position: fixed;
|
||||
height: 100px;
|
||||
bottom: 15px;
|
||||
right: 0px;
|
||||
text-align: right;
|
||||
|
||||
$video-size: 100px;
|
||||
|
||||
.video-container {
|
||||
background-color: black;
|
||||
box-shadow: 0px 0px 5px black;
|
||||
border-radius: 10px;
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
width: $video-size;
|
||||
height: 100%;
|
||||
z-index: 2;
|
||||
|
||||
video {
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.video-container.active {
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: -1;
|
||||
|
||||
video {
|
||||
border-radius: 0;
|
||||
cursor: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,18 +1,6 @@
|
||||
@import './variables';
|
||||
@import url("https://fonts.googleapis.com/icon?family=Material+Icons");
|
||||
|
||||
$font-sans-serif: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
$font-monospace: Menlo, Monaco, Consolas, "Ubuntu Mono", monospace;
|
||||
|
||||
$color-bg: #086788;
|
||||
$color-fg: #07A0C3;
|
||||
$color-btn: #FFF1D0;
|
||||
$icon-size: 48px;
|
||||
|
||||
$color-primary: white;
|
||||
$color-info: #31EF40;
|
||||
$color-warning: #F0C808;
|
||||
$color-error: #EE7600;
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after {
|
||||
@ -150,212 +138,11 @@ body.call {
|
||||
}
|
||||
|
||||
.app {
|
||||
|
||||
.alert {
|
||||
background-color: black;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
left: 0;
|
||||
opacity: 1;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
top: 0;
|
||||
transition: visibility 100ms ease-in, opacity 100ms ease-in;
|
||||
z-index: 4;
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
margin: 1rem 0;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
button {
|
||||
line-height: 1.4rem;
|
||||
border: none;
|
||||
border-radius: 0.3rem;
|
||||
color: $color-info;
|
||||
background-color: $color-fg;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.alert.hidden {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.notifications {
|
||||
font-family: $font-monospace;
|
||||
font-size: 10px;
|
||||
left: 1rem;
|
||||
position: fixed;
|
||||
right: 1rem;
|
||||
text-align: right;
|
||||
top: 1rem;
|
||||
z-index: 3;
|
||||
|
||||
.notification {
|
||||
color: $color-info;
|
||||
padding: 0.25rem;
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.notification.error {
|
||||
color: $color-error;
|
||||
}
|
||||
|
||||
.notification.warning {
|
||||
color: $color-warning;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.videos {
|
||||
position: fixed;
|
||||
height: 100px;
|
||||
bottom: 15px;
|
||||
right: 0px;
|
||||
text-align: right;
|
||||
|
||||
$video-size: 100px;
|
||||
|
||||
.video-container {
|
||||
background-color: black;
|
||||
box-shadow: 0px 0px 5px black;
|
||||
border-radius: 10px;
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
width: $video-size;
|
||||
height: 100%;
|
||||
z-index: 2;
|
||||
|
||||
video {
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.video-container.active {
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: -1;
|
||||
|
||||
video {
|
||||
border-radius: 0;
|
||||
cursor: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.input {
|
||||
position: fixed;
|
||||
left: 10pxpx;
|
||||
bottom: 15px;
|
||||
z-index: 3;
|
||||
|
||||
input {
|
||||
box-shadow: 0px 0px 5px black;
|
||||
// background-color: black;
|
||||
background-color: #333;
|
||||
border: none;
|
||||
color: #ccc;
|
||||
padding: 0.5rem;
|
||||
font-family: $font-monospace;
|
||||
}
|
||||
|
||||
input[type="submit"] {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
bottom: 80px;
|
||||
left: 6vw;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
|
||||
/* on icons are hidden by default */
|
||||
|
||||
.material-icons {
|
||||
&.on {
|
||||
display: none;
|
||||
}
|
||||
&.off {
|
||||
display: block;
|
||||
}
|
||||
color: #fff;
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
left: 12px;
|
||||
}
|
||||
|
||||
/* off icons are displayed by default */
|
||||
|
||||
/* on icons are displayed when parent svg has class 'on' */
|
||||
|
||||
.button {
|
||||
&.on .material-icons {
|
||||
&.on {
|
||||
display: block;
|
||||
}
|
||||
&.off {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 48px;
|
||||
box-shadow: 2px 2px 24px #444;
|
||||
display: block;
|
||||
margin: 0 0 3vh 0;
|
||||
transform: translateX(calc(-6vw - 96px));
|
||||
transition: all .1s;
|
||||
transition-timing-function: ease-in-out;
|
||||
&:hover {
|
||||
box-shadow: 4px 4px 48px #666;
|
||||
}
|
||||
}
|
||||
|
||||
/* off icons are hidden when parent svg has class 'on' */
|
||||
|
||||
&.active .button {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.mute-audio {
|
||||
&:hover, &.on {
|
||||
background: #407cf7;
|
||||
}
|
||||
}
|
||||
|
||||
.mute-video {
|
||||
&:hover, &.on {
|
||||
background: #407cf7;
|
||||
}
|
||||
}
|
||||
|
||||
.fullscreen {
|
||||
&:hover, &.on {
|
||||
background: #407cf7;
|
||||
}
|
||||
}
|
||||
|
||||
.hangup {
|
||||
&:hover {
|
||||
background: #dd2c00;
|
||||
}
|
||||
}
|
||||
}
|
||||
@import './alert';
|
||||
@import './notification';
|
||||
@import './video';
|
||||
@import './chat';
|
||||
@import './toolbar';
|
||||
}
|
||||
|
||||
.fade-enter {
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
const debug = require('debug')('peer-calls:socket')
|
||||
const _ = require('underscore')
|
||||
|
||||
const messages = {};
|
||||
|
||||
module.exports = function (socket, io) {
|
||||
socket.on('signal', payload => {
|
||||
// debug('signal: %s, payload: %o', socket.id, payload);
|
||||
@ -11,6 +13,11 @@ module.exports = function (socket, io) {
|
||||
})
|
||||
})
|
||||
|
||||
socket.on('new_message', payload => {
|
||||
addMesssage(socket.room, payload);
|
||||
io.to(socket.room).emit('new_message', payload)
|
||||
})
|
||||
|
||||
socket.on('ready', roomName => {
|
||||
debug('ready: %s, room: %s', socket.id, roomName)
|
||||
if (socket.room) socket.leave(socket.room)
|
||||
@ -19,11 +26,13 @@ module.exports = function (socket, io) {
|
||||
socket.room = roomName
|
||||
|
||||
let users = getUsers(roomName)
|
||||
debug('ready: %s, room: %s, users: %o', socket.id, roomName, users)
|
||||
let messages = getMesssages(roomName)
|
||||
debug('ready: %s, room: %s, users: %o, messages: %o', socket.id, roomName, users, messages)
|
||||
io.to(roomName).emit('users', {
|
||||
initiator: socket.id,
|
||||
users
|
||||
})
|
||||
io.to(roomName).emit('messages', { messages })
|
||||
})
|
||||
|
||||
function getUsers (roomName) {
|
||||
@ -31,4 +40,15 @@ module.exports = function (socket, io) {
|
||||
return { id }
|
||||
})
|
||||
}
|
||||
|
||||
function getMesssages (roomName) {
|
||||
if (_.isUndefined(messages[roomName])) {
|
||||
messages[roomName] = [];
|
||||
}
|
||||
return messages[roomName]
|
||||
}
|
||||
|
||||
function addMesssage (roomName, payload) {
|
||||
getMesssages(roomName).push(payload)
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user