browserify/sass build working
This commit is contained in:
parent
80c8be2f56
commit
15e446b540
29
Makefile
29
Makefile
@ -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
47689
build/index.js
Normal file
File diff suppressed because one or more lines are too long
304
build/style.css
Normal file
304
build/style.css
Normal 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; }
|
||||||
11
package.json
11
package.json
@ -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"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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 }
|
||||||
|
})
|
||||||
|
|||||||
@ -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
|
||||||
38
src/client/components/App.js
Normal file
38
src/client/components/App.js
Normal 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>)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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 () {
|
||||||
|
|||||||
@ -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 = {
|
||||||
|
|||||||
41
src/client/components/Video.js
Normal file
41
src/client/components/Video.js
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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
|
|
||||||
@ -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'
|
||||||
|
|||||||
@ -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()
|
|
||||||
|
|||||||
@ -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 }
|
|
||||||
|
|||||||
@ -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]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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:
|
||||||
@ -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)
|
||||||
)
|
)
|
||||||
|
|||||||
@ -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')
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user