browserify/sass build working

This commit is contained in:
Jerko Steiner 2017-06-09 00:02:22 -04:00
parent 80c8be2f56
commit 15e446b540
24 changed files with 48251 additions and 182 deletions

View File

@ -1,6 +1,11 @@
export PATH := node_modules/.bin:$(PATH) export PATH := node_modules/.bin:$(PATH)
SHELL=/bin/bash SHELL=/bin/bash
.PHONY: start
start:
chastifol [ make watchify ] [ make sassify ] [ make server ]
.PHONY: build .PHONY: build
build: build:
@ -16,6 +21,24 @@ build:
cp -rv ./src/views ./dist/ cp -rv ./src/views ./dist/
cp -rv ./src/res ./dist/ cp -rv ./src/res ./dist/
.PHONY: watchify
watchify:
mkdir -p build
watchify -d -v -t babelify ./src/client/index.js -o ./build/index.js
.PHONY: sass
sass:
mkdir -p build
node-sass ./src/scss/style.scss -o ./build/
.PHONY: sassify
sassify: sass
mkdir -p build
node-sass --watch ./src/scss/style.scss -o ./build/
.PHONY: lint .PHONY: lint
lint: lint:
@ -41,10 +64,10 @@ coverage:
jest --coverage jest --coverage
.PHONY: run .PHONY: server
run: server:
node ./src/index.js nodemon --ignore src/client ./src/index.js
.PHONY: clean .PHONY: clean
clean: clean:

47689
build/index.js Normal file

File diff suppressed because one or more lines are too long

304
build/style.css Normal file
View File

@ -0,0 +1,304 @@
@charset "UTF-8";
@font-face {
font-family: 'icons';
src: url("./fonts/icons.eot?37351711");
src: url("./fonts/icons.eot?37351711#iefix") format("embedded-opentype"), url("./fonts/icons.woff?37351711") format("woff"), url("./fonts/icons.ttf?37351711") format("truetype"), url("./fonts/icons.svg?37351711#icons") format("svg");
font-weight: normal;
font-style: normal; }
[class^="icon-"]:before, [class*=" icon-"]:before {
font-family: "icons";
font-style: normal;
font-weight: normal;
speak: none;
display: inline-block;
text-decoration: inherit;
width: 1em;
margin-right: .2em;
text-align: center;
/* opacity: .8; */
/* For safety - reset parent styles, that can break glyph codes*/
font-variant: normal;
text-transform: none;
/* fix buttons height, for twitter bootstrap */
line-height: 1em;
/* Animation center compensation - margins should be symmetric */
/* remove if not needed */
margin-left: .2em;
/* you can be more comfortable with increased icons size */
/* font-size: 120%; */
/* Font smoothing. That was taken from TWBS */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
/* Uncomment for 3D effect */
/* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ }
.icon-down-open-big:before {
content: '\e800'; }
/* '' */
.icon-down-open:before {
content: '\e801'; }
/* '' */
.icon-mouse:before {
content: '\e802'; }
/* '' */
.icon-keyboard:before {
content: '\e803'; }
/* '' */
.icon-left-open:before {
content: '\e804'; }
/* '' */
.icon-right-open:before {
content: '\e805'; }
/* '' */
.icon-up-open:before {
content: '\e806'; }
/* '' */
.icon-arrows:before {
content: '\e807'; }
/* '' */
.icon-up-hand:before {
content: '\e808'; }
/* '' */
.icon-check:before {
content: '\e80b'; }
/* '' */
.icon-cancel:before {
content: '\e80c'; }
/* '' */
.icon-level-up:before {
content: '\e80d'; }
/* '' */
.icon-login:before {
content: '\e80e'; }
/* '' */
.icon-left-open-big:before {
content: '\e81d'; }
/* '' */
.icon-right-open-big:before {
content: '\e81e'; }
/* '' */
.icon-up-open-big:before {
content: '\e81f'; }
/* '' */
* {
box-sizing: border-box; }
html, body {
width: 100%;
height: 100%;
z-index: -99; }
body {
background-color: #086788;
color: #07A0C3;
margin: 0 0;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; }
body.call {
background-image: url("/res/peer-calls.svg");
background-size: 200px;
background-position: 50% 50%;
background-repeat: no-repeat;
background-blend-mode: soft-light; }
#github-ribbon {
position: absolute;
top: 0;
right: 0;
border: 0; }
#form {
padding-top: 50px;
text-align: center;
width: 300px;
margin: 0 auto; }
#form h1 {
margin: 0;
line-height: 0; }
#form p {
margin: 50px 0;
color: white; }
#form input {
font-family: Menlo, Monaco, Consolas, "Ubuntu Mono", monospace;
background-color: #F0C808;
border: none;
border-bottom: 2px solid #bf9f06;
border-radius: 4px;
box-sizing: border-box;
color: white;
text-shadow: 0 0 0.35rem rgba(0, 0, 0, 0.6);
padding: 1rem 1rem;
font-size: 1.1rem; }
#form input:hover {
background-color: #d7b307;
border: none;
border-bottom: 2px solid #a68a06;
border-radius: 4px;
box-sizing: border-box;
color: white;
text-shadow: 0 0 0.35rem rgba(0, 0, 0, 0.6);
padding: 1rem 1rem; }
#form input:active {
transform: translate(0px, 1px);
background-color: #bf9f06;
border: none;
border-bottom: 2px solid #8d7605;
border-radius: 4px;
box-sizing: border-box;
color: white;
text-shadow: 0 0 0.35rem rgba(0, 0, 0, 0.6);
padding: 1rem 1rem; }
#form input:active,
#form input:focus {
outline: none; }
#form input[type="submit"] {
cursor: pointer; }
#form ::-webkit-input-placeholder {
color: #07A0C3;
text-align: center; }
#form :-moz-placeholder {
/* Firefox 18- */
color: #07A0C3;
text-align: center; }
#form ::-moz-placeholder {
/* Firefox 19+ */
color: #07A0C3;
text-align: center; }
#form :-ms-input-placeholder {
color: #07A0C3;
text-align: center; }
.warning {
color: #F0C808; }
.error {
color: #EE7600; }
.info {
color: #31EF40; }
.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; }
.app .alert span {
display: inline-block;
margin: 1rem 0;
padding: 0 1rem; }
.app .alert button {
line-height: 1.4rem;
border: none;
border-radius: 0.3rem;
color: #31EF40;
background-color: #07A0C3;
vertical-align: middle; }
.app .alert.hidden {
opacity: 0;
visibility: hidden; }
.app .notifications {
font-family: Menlo, Monaco, Consolas, "Ubuntu Mono", monospace;
font-size: 10px;
left: 1rem;
position: fixed;
right: 1rem;
text-align: right;
top: 1rem;
z-index: 3; }
.app .notifications .notification {
color: #31EF40;
padding: 0.25rem;
background-color: rgba(0, 0, 0, 0.2); }
.app .notifications .notification.error {
color: #EE7600; }
.app .notifications .notification.warning {
color: #F0C808; }
.app .videos {
position: fixed;
height: 100px;
bottom: 15px;
right: 0px;
text-align: right; }
.app .videos .video-container {
background-color: black;
box-shadow: 0px 0px 5px black;
border-radius: 10px;
display: inline-block;
margin-right: 10px;
width: 100px;
height: 100%;
z-index: 2; }
.app .videos .video-container video {
border-radius: 10px;
cursor: pointer;
object-fit: cover;
width: 100%;
height: 100%; }
.app .videos .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; }
.app .videos .video-container.active video {
border-radius: 0;
cursor: inherit; }
.app .input {
position: fixed;
left: 10pxpx;
bottom: 15px;
z-index: 3; }
.app .input input {
box-shadow: 0px 0px 5px black;
background-color: black;
background-color: rgba(0, 0, 0, 0.5);
border: none;
color: #ccc;
padding: 0.5rem;
font-family: Menlo, Monaco, Consolas, "Ubuntu Mono", monospace; }
.fade-enter {
opacity: 0.01; }
.fade-enter.fade-enter-active {
opacity: 1;
transition: opacity 200ms ease-in; }
.fade-leave {
opacity: 1; }
.fade-leave.fade-leave-active {
opacity: 0.01;
transition: opacity 100ms ease-in; }

View File

@ -12,8 +12,11 @@
}, },
"babel": { "babel": {
"presets": [ "presets": [
"es2015",
"es2016", "es2016",
"react", "react"
],
"plugins": [
"transform-object-rest-spread", "transform-object-rest-spread",
"transform-class-properties" "transform-class-properties"
] ]
@ -28,10 +31,11 @@
"prop-types": "^15.5.10", "prop-types": "^15.5.10",
"pug": "^2.0.0-rc.2", "pug": "^2.0.0-rc.2",
"react": "^15.5.4", "react": "^15.5.4",
"react-addons-transition-group": "^15.5.2",
"react-dom": "^15.5.4", "react-dom": "^15.5.4",
"react-redux": "^5.0.5", "react-redux": "^5.0.5",
"react-transition-group": "^1.1.3",
"redux": "^3.6.0", "redux": "^3.6.0",
"redux-logger": "^3.0.6",
"redux-thunk": "^2.2.0", "redux-thunk": "^2.2.0",
"seamless-immutable": "^7.1.2", "seamless-immutable": "^7.1.2",
"simple-peer": "^8.1.0", "simple-peer": "^8.1.0",
@ -45,6 +49,7 @@
"babel-jest": "^20.0.3", "babel-jest": "^20.0.3",
"babel-plugin-transform-class-properties": "^6.24.1", "babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-object-rest-spread": "^6.23.0", "babel-plugin-transform-object-rest-spread": "^6.23.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-es2016": "^6.24.1", "babel-preset-es2016": "^6.24.1",
"babel-preset-react": "^6.24.1", "babel-preset-react": "^6.24.1",
"babelify": "^7.3.0", "babelify": "^7.3.0",
@ -58,9 +63,9 @@
"eslint-plugin-react": "^7.0.1", "eslint-plugin-react": "^7.0.1",
"eslint-plugin-standard": "^3.0.1", "eslint-plugin-standard": "^3.0.1",
"jest-cli": "^20.0.4", "jest-cli": "^20.0.4",
"node-sass": "^4.5.3",
"nodemon": "^1.11.0", "nodemon": "^1.11.0",
"react-addons-test-utils": "^15.5.1", "react-addons-test-utils": "^15.5.1",
"sassify": "^2.0.0",
"uglify-js": "^2.6.2", "uglify-js": "^2.6.2",
"watchify": "^3.9.0" "watchify": "^3.9.0"
}, },

View File

@ -2,9 +2,9 @@ import * as NotifyActions from './NotifyActions.js'
import * as constants from '../constants.js' import * as constants from '../constants.js'
import Promise from 'bluebird' import Promise from 'bluebird'
import callId from '../callId.js' import callId from '../callId.js'
import getUserMedia from './browser/getUserMedia.js' import getUserMedia from '../window/getUserMedia.js'
import handshake from './peer/handshake.js' import handshake from '../peer/handshake.js'
import socket from './socket.js' import socket from '../socket.js'
export const init = () => dispatch => { export const init = () => dispatch => {
return Promise.all([ return Promise.all([
@ -31,7 +31,7 @@ export const connect = () => dispatch => {
export const getCameraStream = () => dispatch => { export const getCameraStream = () => dispatch => {
return getUserMedia({ video: true, audio: true }) return getUserMedia({ video: true, audio: true })
.then(stream => { .then(stream => {
dispatch(addStream(stream)) dispatch(addStream({ stream, userId: constants.ME }))
return stream return stream
}) })
.catch(() => { .catch(() => {
@ -40,10 +40,20 @@ export const getCameraStream = () => dispatch => {
}) })
} }
const addStream = stream => ({ export const addStream = ({ stream, userId }) => ({
type: constants.STREAM_ADD, type: constants.STREAM_ADD,
payload: { payload: {
userId: '_me_', userId,
stream stream
} }
}) })
export const removeStream = userId => ({
type: constants.STREAM_REMOVE,
payload: { userId }
})
export const activateStream = userId => ({
type: constants.STREAM_ACTIVATE,
payload: { userId }
})

View File

@ -1,9 +1,8 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import classnames from 'classnames' import classnames from 'classnames'
const React = require('react')
const AlertPropType = PropTypes.shape({ export const AlertPropType = PropTypes.shape({
dismissable: PropTypes.bool, dismissable: PropTypes.bool,
action: PropTypes.string.isRequired, action: PropTypes.string.isRequired,
message: PropTypes.string.isRequired message: PropTypes.string.isRequired

View File

@ -0,0 +1,38 @@
import Alert from './Alerts.js'
import Input from './Input.js'
import Notifications from './Notifications.js'
import PropTypes from 'prop-types'
import React from 'react'
import Video, { StreamPropType } from './Video.js'
import _ from 'underscore'
export default class App extends React.PureComponent {
static propTypes = {
streams: PropTypes.arrayOf(StreamPropType).isRequired,
activate: PropTypes.func.isRequired,
active: PropTypes.string.isRequired,
init: PropTypes.func.isRequired
}
componentDidMount () {
const { init } = this.props
init()
}
render () {
const { active, activate, streams } = this.props
return (<div className="app">
<Alert />
<Notifications />
<Input />
<div className="videos">
{_.map(streams, (stream, userId) => (
<Video
activate={activate}
active={userId === active}
stream={stream}
/>
))}
</div>
</div>)
}
}

View File

@ -1,8 +1,11 @@
import PropTypes from 'prop-types'
import React from 'react' import React from 'react'
import notify from '../action/notify.js'
import peers from '../peer/peers.js' import peers from '../peer/peers.js'
export default class Input extends React.PureComponent { export default class Input extends React.PureComponent {
static propTypes = {
notify: PropTypes.func.isRequired
}
constructor () { constructor () {
super() super()
this.state = { this.state = {
@ -23,9 +26,10 @@ export default class Input extends React.PureComponent {
e.key === 'Enter' && this.submit() e.key === 'Enter' && this.submit()
} }
submit = () => { submit = () => {
const { notify } = this.props
const { message } = this.state const { message } = this.state
peers.message(message) peers.message(message)
notify.info('You: ' + message) notify('You: ' + message)
this.setState({ message: '' }) this.setState({ message: '' })
} }
render () { render () {

View File

@ -3,13 +3,15 @@ import PropTypes from 'prop-types'
import React from 'react' import React from 'react'
import classnames from 'classnames' import classnames from 'classnames'
export default class Notifications extends React.PureComponent { export const NotificationPropTypes = PropTypes.shape({
static propTypes = {
notifications: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.string.isRequired, id: PropTypes.string.isRequired,
type: PropTypes.string.isRequired, type: PropTypes.string.isRequired,
message: PropTypes.string.isRequired message: PropTypes.string.isRequired
})), })
export default class Notifications extends React.PureComponent {
static propTypes = {
notifications: PropTypes.arrayOf(NotificationPropTypes).isRequired,
max: PropTypes.number.isRequired max: PropTypes.number.isRequired
} }
static defaultProps = { static defaultProps = {

View File

@ -0,0 +1,41 @@
import PropTypes from 'prop-types'
import React from 'react'
import classnames from 'classnames'
import { ME } from '../constants.js'
export const StreamPropType = PropTypes.shape({
userId: PropTypes.string.isRequired,
stream: PropTypes.instanceOf(ArrayBuffer).isRequired,
url: PropTypes.string.isRequired
})
export default class Video extends React.PureComponent {
static propTypes = {
activate: PropTypes.func.isRequired,
active: PropTypes.string.required,
stream: StreamPropType.isRequired
}
activate = e => {
const { activate, stream: { userId } } = this.props
this.play(e)
activate(userId)
}
play = e => {
e.preventDefault()
e.target.play()
}
render () {
const { active, stream: { userId, url } } = this.props
const className = classnames('video-container', { active })
return (
<div className={className}>
<video
muted={userId === ME}
onClick={this.activate}
onLoadedMetadata={this.play}
src={url}
/>
</div>
)
}
}

View File

@ -1,58 +0,0 @@
const Alert = require('./alert.js')
const Input = require('./input.js')
const Notifications = require('./notifications.js')
const React = require('react')
const _ = require('underscore')
const activeStore = require('../store/activeStore.js')
const debug = require('debug')('peer-calls:app')
const dispatcher = require('../dispatcher/dispatcher.js')
const streamStore = require('../store/streamStore.js')
function app () {
let streams = streamStore.getStreams()
function play (event) {
try {
event.target.play()
} catch (e) {
debug('error starting video: %s', e.name)
}
}
let videos = _.map(streams, (stream, userId) => {
let url = stream.url
function markActive (event) {
play(event)
dispatcher.dispatch({
type: 'mark-active',
userId: activeStore.isActive(userId) ? undefined : userId
})
}
let className = 'video-container'
className += activeStore.isActive(userId) ? ' active' : ''
return (
<div className={className} key={userId}>
<video
muted={userId === '_me_'}
onClick={markActive}
onLoadedMetadata={play}
src={url}
/>
</div>
)
})
return (<div className='app'>
<Alert />
<Notifications />
<Input />
<div className='videos'>
{videos}
</div>
</div>)
}
module.exports = app

View File

@ -1,3 +1,5 @@
export const ME = '_me_'
export const ALERT = 'ALERT' export const ALERT = 'ALERT'
export const ALERT_DISMISS = 'ALERT_DISMISS' export const ALERT_DISMISS = 'ALERT_DISMISS'
export const ALERT_CLEAR = 'ALERT_CLEAR' export const ALERT_CLEAR = 'ALERT_CLEAR'

View File

@ -1,26 +1,16 @@
'use strict' 'use strict'
const App = require('./components/app.js') import App from './components/App.js'
const React = require('react') import React from 'react'
const ReactDom = require('react-dom') import ReactDOM from 'react-dom'
const activeStore = require('./store/activeStore.js') import store from './store.js'
const alertStore = require('./store/alertStore.js') import { Provider } from 'react-redux'
const call = require('./call.js') import { play } from './window/video.js'
const debug = require('debug')('peer-calls:index')
const notificationsStore = require('./store/notificationsStore.js')
const play = require('./browser/video.js').play
const streamStore = require('./store/streamStore.js')
function render () { const component = (
debug('rendering') <Provider store={store}>
ReactDom.render(<App />, document.querySelector('#container')) <App />
play() </Provider>
} )
activeStore.addListener(render) ReactDOM.render(component, document.querySelector('#container'))
alertStore.addListener(render) play()
notificationsStore.addListener(render)
streamStore.addListener(render)
render()
call.init()

View File

@ -1,10 +1,12 @@
'use strict' import NotifyActions from '../actions/NotifyActions.js'
const _ = require('underscore') import _ from 'underscore'
const debug = require('debug')('peer-calls:peer') import _debug from 'debug'
const notify = require('../action/notify.js') import peers from './peers.js'
const peers = require('./peers.js') import { dispatch } from '../store.js'
function init (socket, roomName, stream) { const debug = _debug('peercalls')
export function init (socket, roomName, stream) {
function createPeer (user, initiator) { function createPeer (user, initiator) {
return peers.create({ socket, user, initiator, stream }) return peers.create({ socket, user, initiator, stream })
} }
@ -21,7 +23,9 @@ function init (socket, roomName, stream) {
socket.on('users', payload => { socket.on('users', payload => {
let { initiator, users } = payload let { initiator, users } = payload
debug('socket users: %o', users) debug('socket users: %o', users)
notify.info('Connected users: {0}', users.length) dispatch(
NotifyActions.info('Connected users: {0}', users.length)
)
users users
.filter(user => !peers.get(user.id) && user.id !== socket.id) .filter(user => !peers.get(user.id) && user.id !== socket.id)
@ -35,8 +39,8 @@ function init (socket, roomName, stream) {
debug('socket.id: %s', socket.id) debug('socket.id: %s', socket.id)
debug('emit ready for room: %s', roomName) debug('emit ready for room: %s', roomName)
notify.info('Ready for connections') dispatch(
NotifyActions.info('Ready for connections')
)
socket.emit('ready', roomName) socket.emit('ready', roomName)
} }
module.exports = { init }

View File

@ -1,9 +1,13 @@
const _ = require('underscore') import CallActions from '../actions/CallActions.js'
const Peer = require('./Peer.js') import NotifyActions from '../actions/NotifyActions.js'
const debug = require('debug')('peer-calls:peer') import Peer from './Peer.js'
const dispatcher = require('../dispatcher/dispatcher.js') import _ from 'underscore'
const iceServers = require('../iceServers.js') import _debug from 'debug'
const notify = require('../action/notify.js') import iceServers from '../iceServers.js'
import { dispatch } from '../store.js'
import { play } from '../window/video.js'
const debug = _debug('peercalls')
let peers = {} let peers = {}
@ -16,14 +20,18 @@ let peers = {}
*/ */
function create ({ socket, user, initiator, stream }) { function create ({ socket, user, initiator, stream }) {
debug('create peer: %s, stream:', user.id, stream) debug('create peer: %s, stream:', user.id, stream)
notify.warn('Connecting to peer...') dispatch(
NotifyActions.warn('Connecting to peer...')
)
if (peers[user.id]) { if (peers[user.id]) {
notify.info('Cleaning up old connection...') dispatch(
NotifyActions.info('Cleaning up old connection...')
)
destroy(user.id) destroy(user.id)
} }
let peer = peers[user.id] = Peer.init({ const peer = peers[user.id] = Peer.init({
initiator: socket.id === initiator, initiator: socket.id === initiator,
stream, stream,
config: { iceServers } config: { iceServers }
@ -31,48 +39,54 @@ function create ({ socket, user, initiator, stream }) {
peer.once('error', err => { peer.once('error', err => {
debug('peer: %s, error %s', user.id, err.stack) debug('peer: %s, error %s', user.id, err.stack)
notify.error('A peer connection error occurred') dispatch(
NotifyActions.error('A peer connection error occurred')
)
destroy(user.id) destroy(user.id)
}) })
peer.on('signal', signal => { peer.on('signal', signal => {
debug('peer: %s, signal: %o', user.id, signal) debug('peer: %s, signal: %o', user.id, signal)
let payload = { userId: user.id, signal } const payload = { userId: user.id, signal }
socket.emit('signal', payload) socket.emit('signal', payload)
}) })
peer.once('connect', () => { peer.once('connect', () => {
debug('peer: %s, connect', user.id) debug('peer: %s, connect', user.id)
notify.warn('Peer connection established') dispatch(
dispatcher.dispatch({ type: 'play' }) NotifyActions.warn('Peer connection established')
)
play()
}) })
peer.on('stream', stream => { peer.on('stream', stream => {
debug('peer: %s, stream', user.id) debug('peer: %s, stream', user.id)
dispatcher.dispatch({ dispatch(CallActions.addStream({
type: 'add-stream',
userId: user.id, userId: user.id,
stream stream
}) }))
}) })
peer.on('data', object => { peer.on('data', object => {
object = JSON.parse(new window.TextDecoder('utf-8').decode(object)) object = JSON.parse(new window.TextDecoder('utf-8').decode(object))
debug('peer: %s, message: %o', user.id, object) debug('peer: %s, message: %o', user.id, object)
notify.info('' + user.id + ': ' + object.message) dispatch(
NotifyActions.info('' + user.id + ': ' + object.message)
)
}) })
peer.once('close', () => { peer.once('close', () => {
debug('peer: %s, close', user.id) debug('peer: %s, close', user.id)
notify.error('Peer connection closed') dispatch(
dispatcher.dispatch({ NotifyActions.error('Peer connection closed')
type: 'remove-stream', )
userId: user.id dispatch(
}) CallActions.removeStream(user.id)
)
// make sure some other peer with different id didn't take place between // make sure some other peer with same id didn't take place between calling
// calling `destroy()` and `close` event // `destroy()` and `close` event
if (peers[user.id] === peer) delete peers[user.id] if (peers[user.id] === peer) delete peers[user.id]
}) })
} }

View File

@ -1,6 +1,6 @@
import * as constants from '../constants.js' import * as constants from '../constants.js'
import createObjectURL from '../browser/createObjectURL' import createObjectURL from '../window/createObjectURL'
import Immutable from 'seamless-imutable' import Immutable from 'seamless-immutable'
const defaultState = Immutable({ const defaultState = Immutable({
active: null, active: null,
@ -28,6 +28,8 @@ export default function stream (state = defaultState, action) {
switch (action && action.type) { switch (action && action.type) {
case constants.STREAM_ADD: case constants.STREAM_ADD:
return addStream(state, action) return addStream(state, action)
case constants.STREAM_ACTIVATE:
return state.merge({ active: action.payload.userId })
case constants.STREAM_REMOVE: case constants.STREAM_REMOVE:
return removeStream(state, action) return removeStream(state, action)
default: default:

View File

@ -1,8 +1,9 @@
import { applyMiddleware, createStore } from 'redux' import logger from 'redux-logger'
import thunk from 'redux-thunk'
import reducer from './reducers' import reducer from './reducers'
import thunk from 'redux-thunk'
import { applyMiddleware, createStore } from 'redux'
export default createStore( export default createStore(
reducer, reducer,
applyMiddleware(thunk) applyMiddleware(thunk, logger)
) )

View File

@ -1,4 +1,4 @@
const debug = require('debug')('peer-calls:video') const debug = require('debug')('peercalls')
export function play () { export function play () {
let videos = window.document.querySelectorAll('video') let videos = window.document.querySelectorAll('video')

View File

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -1,17 +1,17 @@
@import "fonts.less"; @import "_fonts";
@font-sans-serif: "Helvetica Neue", Helvetica, Arial, sans-serif; $font-sans-serif: "Helvetica Neue", Helvetica, Arial, sans-serif;
@font-monospace: Menlo, Monaco, Consolas, "Ubuntu Mono", monospace; $font-monospace: Menlo, Monaco, Consolas, "Ubuntu Mono", monospace;
@color-bg: #086788; $color-bg: #086788;
@color-fg: #07A0C3; $color-fg: #07A0C3;
@color-btn: #FFF1D0; $color-btn: #FFF1D0;
@icon-size: 48px; $icon-size: 48px;
@color-primary: white; $color-primary: white;
@color-info: #31EF40; $color-info: #31EF40;
@color-warning: #F0C808; $color-warning: #F0C808;
@color-error: #EE7600; $color-error: #EE7600;
* { * {
box-sizing: border-box; box-sizing: border-box;
@ -24,10 +24,10 @@ html, body {
} }
body { body {
background-color: @color-bg; background-color: $color-bg;
color: @color-fg; color: $color-fg;
margin: 0 0; margin: 0 0;
font-family: @font-sans-serif; font-family: $font-sans-serif;
} }
body.call { body.call {
@ -61,34 +61,34 @@ body.call {
p { p {
margin: 50px 0; margin: 50px 0;
color: @color-primary; color: $color-primary;
} }
.button(@color-fg, @color-bg) { @mixin button($color-fg, $color-bg) {
background-color: @color-bg; background-color: $color-bg;
border: none; border: none;
border-bottom: 2px solid darken(@color-bg, 10%); border-bottom: 2px solid darken($color-bg, 10%);
border-radius: 4px; border-radius: 4px;
box-sizing: border-box; box-sizing: border-box;
color: @color-fg; color: $color-fg;
// font-size: 1.2rem; // font-size: 1.2rem;
text-shadow: 0 0 0.35rem rgba(0, 0, 0, 0.6); text-shadow: 0 0 0.35rem rgba(0, 0, 0, 0.6);
padding: 1rem 1rem; padding: 1rem 1rem;
} }
input { input {
font-family: @font-monospace; font-family: $font-monospace;
.button(@color-primary, @color-warning); @include button($color-primary, $color-warning);
font-size: 1.1rem; font-size: 1.1rem;
} }
input:hover { input:hover {
.button(@color-primary, darken(@color-warning, 5%)); @include button($color-primary, darken($color-warning, 5%));
} }
input:active { input:active {
transform: translate(0px, 1px); transform: translate(0px, 1px);
.button(@color-primary, darken(@color-warning, 10%)); @include button($color-primary, darken($color-warning, 10%));
} }
input:active, input:active,
@ -101,36 +101,36 @@ body.call {
} }
::-webkit-input-placeholder { ::-webkit-input-placeholder {
color: @color-fg; color: $color-fg;
text-align: center; text-align: center;
} }
:-moz-placeholder { /* Firefox 18- */ :-moz-placeholder { /* Firefox 18- */
color: @color-fg; color: $color-fg;
text-align: center; text-align: center;
} }
::-moz-placeholder { /* Firefox 19+ */ ::-moz-placeholder { /* Firefox 19+ */
color: @color-fg; color: $color-fg;
text-align: center; text-align: center;
} }
:-ms-input-placeholder { :-ms-input-placeholder {
color: @color-fg; color: $color-fg;
text-align: center; text-align: center;
} }
} }
.warning { .warning {
color: @color-warning; color: $color-warning;
} }
.error { .error {
color: @color-error; color: $color-error;
} }
.info { .info {
color: @color-info; color: $color-info;
} }
.app { .app {
@ -157,8 +157,8 @@ body.call {
line-height: 1.4rem; line-height: 1.4rem;
border: none; border: none;
border-radius: 0.3rem; border-radius: 0.3rem;
color: @color-info; color: $color-info;
background-color: @color-fg; background-color: $color-fg;
vertical-align: middle; vertical-align: middle;
} }
} }
@ -169,7 +169,7 @@ body.call {
} }
.notifications { .notifications {
font-family: @font-monospace; font-family: $font-monospace;
font-size: 10px; font-size: 10px;
left: 1rem; left: 1rem;
position: fixed; position: fixed;
@ -179,17 +179,17 @@ body.call {
z-index: 3; z-index: 3;
.notification { .notification {
color: @color-info; color: $color-info;
padding: 0.25rem; padding: 0.25rem;
background-color: rgba(0, 0, 0, 0.2); background-color: rgba(0, 0, 0, 0.2);
} }
.notification.error { .notification.error {
color: @color-error; color: $color-error;
} }
.notification.warning { .notification.warning {
color: @color-warning; color: $color-warning;
} }
} }
@ -201,7 +201,7 @@ body.call {
right: 0px; right: 0px;
text-align: right; text-align: right;
@video-size: 100px; $video-size: 100px;
.video-container { .video-container {
background-color: black; background-color: black;
@ -209,7 +209,7 @@ body.call {
border-radius: 10px; border-radius: 10px;
display: inline-block; display: inline-block;
margin-right: 10px; margin-right: 10px;
width: @video-size; width: $video-size;
height: 100%; height: 100%;
z-index: 2; z-index: 2;
@ -254,7 +254,7 @@ body.call {
border: none; border: none;
color: #ccc; color: #ccc;
padding: 0.5rem; padding: 0.5rem;
font-family: @font-monospace; font-family: $font-monospace;
} }
} }
@ -277,4 +277,3 @@ body.call {
opacity: 0.01; opacity: 0.01;
transition: opacity 100ms ease-in; transition: opacity 100ms ease-in;
} }