Merge branch 'feature-material-webpack' into develop

master
Håvar Aambø Fosstveit 2019-04-01 09:30:08 +02:00
commit 63b13e4e33
187 changed files with 4833 additions and 6303 deletions

4
.gitignore vendored
View File

@ -1,7 +1,7 @@
node_modules/
/app/config/
!/app/config/config.example.js
/app/build/
/app/public/config.js
/server/config/
!/server/config/config.example.js
/server/public/

View File

@ -1,26 +0,0 @@
{
"plugins":
[
"@babel/plugin-proposal-object-rest-spread",
"jsx-control-statements",
"@babel/plugin-proposal-class-properties",
"@babel/plugin-transform-runtime"
],
"presets":
[
[
"@babel/preset-env",
{
"targets": {
"browsers": [
"chrome >= 67",
"edge >= 17",
"firefox >= 60",
"safari >= 12"
]
}
}
],
"@babel/react"
]
}

View File

@ -1,231 +0,0 @@
module.exports =
{
env:
{
browser: true,
es6: true,
node: true
},
plugins:
[
'import',
'react',
'jsx-control-statements'
],
extends:
[
'eslint:recommended',
'plugin:react/recommended',
'plugin:jsx-control-statements/recommended'
],
settings:
{
react:
{
pragma: 'React',
version: '16'
}
},
parser: "babel-eslint",
parserOptions:
{
ecmaVersion: 2018,
sourceType: 'module',
ecmaFeatures:
{
impliedStrict: true,
jsx: true
}
},
rules:
{
'array-bracket-spacing': [ 2, 'always',
{
objectsInArrays: true,
arraysInArrays: true
}],
'arrow-parens': [ 2, 'always' ],
'arrow-spacing': 2,
'block-spacing': [ 2, 'always' ],
'brace-style': [ 2, 'allman', { allowSingleLine: true } ],
'camelcase': 2,
'comma-dangle': 2,
'comma-spacing': [ 2, { before: false, after: true } ],
'comma-style': 2,
'computed-property-spacing': 2,
'constructor-super': 2,
'func-call-spacing': 2,
'generator-star-spacing': 2,
'guard-for-in': 2,
'indent': [ 2, 'tab', { 'SwitchCase': 1 } ],
'key-spacing': [ 2,
{
singleLine:
{
beforeColon: false,
afterColon: true
},
multiLine:
{
beforeColon: true,
afterColon: true,
align: 'colon'
}
}],
'keyword-spacing': 2,
'linebreak-style': [ 2, 'unix' ],
'lines-around-comment': [ 2,
{
allowBlockStart: true,
allowObjectStart: true,
beforeBlockComment: true,
beforeLineComment: false
}],
'max-len': [ 2, 90,
{
tabWidth: 2,
comments: 110,
ignoreUrls: true,
ignoreStrings: true,
ignoreTemplateLiterals: true,
ignoreRegExpLiterals: true
}],
'newline-after-var': 2,
'newline-before-return': 2,
'newline-per-chained-call': 2,
'no-alert': 2,
'no-caller': 2,
'no-case-declarations': 2,
'no-catch-shadow': 2,
'no-class-assign': 2,
'no-confusing-arrow': 2,
'no-console': 2,
'no-const-assign': 2,
'no-debugger': 2,
'no-dupe-args': 2,
'no-dupe-keys': 2,
'no-duplicate-case': 2,
'no-div-regex': 2,
'no-empty': [ 2, { allowEmptyCatch: true } ],
'no-empty-pattern': 2,
'no-else-return': 0,
'no-eval': 2,
'no-extend-native': 2,
'no-ex-assign': 2,
'no-extra-bind': 2,
'no-extra-boolean-cast': 2,
'no-extra-label': 2,
'no-extra-semi': 2,
'no-fallthrough': 2,
'no-func-assign': 2,
'no-global-assign': 2,
'no-implicit-coercion': 2,
'no-implicit-globals': 2,
'no-inner-declarations': 2,
'no-invalid-regexp': 2,
'no-irregular-whitespace': 2,
'no-lonely-if': 2,
'no-mixed-operators': 2,
'no-mixed-spaces-and-tabs': 2,
'no-multi-spaces': 2,
'no-multi-str': 2,
'no-multiple-empty-lines': [ 2, { max: 1, maxEOF: 0, maxBOF: 0 } ],
'no-native-reassign': 2,
'no-negated-in-lhs': 2,
'no-new': 2,
'no-new-func': 2,
'no-new-wrappers': 2,
'no-obj-calls': 2,
'no-proto': 2,
'no-prototype-builtins': 0,
'no-redeclare': 2,
'no-regex-spaces': 2,
'no-restricted-imports': 2,
'no-return-assign': 2,
'no-self-assign': 2,
'no-self-compare': 2,
'no-sequences': 2,
'no-shadow': 2,
'no-shadow-restricted-names': 2,
'no-spaced-func': 2,
'no-sparse-arrays': 2,
'no-this-before-super': 2,
'no-throw-literal': 2,
'no-undef': 2,
'no-unexpected-multiline': 2,
'no-unmodified-loop-condition': 2,
'no-unreachable': 2,
'no-unused-vars': [ 1, { vars: 'all', args: 'after-used' }],
'no-use-before-define': [ 2, { functions: false } ],
'no-useless-call': 2,
'no-useless-computed-key': 2,
'no-useless-concat': 2,
'no-useless-rename': 2,
'no-var': 2,
'no-whitespace-before-property': 2,
'object-curly-newline': 0,
'object-curly-spacing': [ 2, 'always' ],
'object-property-newline': [ 2, { allowMultiplePropertiesPerLine: true } ],
'prefer-const': 2,
'prefer-rest-params': 2,
'prefer-spread': 2,
'prefer-template': 2,
'quotes': [ 2, 'single', { avoidEscape: true } ],
'semi': [ 2, 'always' ],
'semi-spacing': 2,
'space-before-blocks': 2,
'space-before-function-paren': [ 2, { anonymous: 'never', named: 'never', 'asyncArrow': 'always'}],
'space-in-parens': [ 2, 'never' ],
'spaced-comment': [ 2, 'always' ],
'strict': 2,
'valid-typeof': 2,
'eol-last': 0,
'yoda': 2,
// eslint-plugin-import options.
'import/extensions': 2,
'import/no-duplicates': 2,
// eslint-plugin-react options.
'jsx-quotes': [ 2, 'prefer-single' ],
'react/display-name': [ 2, { ignoreTranspilerName: false } ],
'react/forbid-prop-types': 0,
'react/jsx-boolean-value': 2,
'react/jsx-closing-bracket-location': 2,
'react/jsx-curly-spacing': 2,
'react/jsx-equals-spacing': 2,
'react/jsx-handler-names': 2,
'react/jsx-indent-props': [ 2, 'tab' ],
'react/jsx-indent': [ 2, 'tab' ],
'react/jsx-key': 2,
'react/jsx-max-props-per-line': 0,
'react/jsx-no-bind': 0,
'react/jsx-no-duplicate-props': 2,
'react/jsx-no-literals': 0,
'react/jsx-no-undef': 0,
'react/jsx-pascal-case': 2,
'react/jsx-sort-prop-types': 0,
'react/jsx-sort-props': 0,
'react/jsx-uses-react': 2,
'react/jsx-uses-vars': 2,
'react/no-danger': 2,
'react/no-deprecated': 2,
'react/no-did-mount-set-state': 2,
'react/no-did-update-set-state': 2,
'react/no-direct-mutation-state': 2,
'react/no-is-mounted': 2,
'react/no-multi-comp': 0,
'react/no-set-state': 0,
'react/no-string-refs': 0,
'react/no-unknown-property': 2,
'react/prefer-es6-class': 2,
'react/prop-types': [ 2, { skipUndeclared: true } ],
'react/react-in-jsx-scope': 2,
'react/self-closing-comp': 2,
'react/sort-comp': 0,
'react/jsx-wrap-multilines': [ 2,
{
declaration: false,
assignment: false,
return: true
}]
}
};

View File

@ -1,7 +0,0 @@
/*
* <%= pkg.name %> v<%= pkg.version %>
* <%= pkg.description %>
* Copyright: 2017-<%= currentYear %> <%= pkg.author %>
* License: <%= pkg.license %>
*/

View File

@ -1,74 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Multiparty Meeting</title>
</head>
<style>
body{
margin:auto;
padding:0.5vmin;
text-align:center;
position: fixed;
left: 50%;
top: 40%;
width: 90%;
transform: translate(-50%, 0%);
background-image: url('/resources/images/background.svg');
background-attachment: fixed;
background-position: center;
background-size: cover;
background-repeat: repeat;
}
input:hover {opacity:0.9;}
input[type=text]{
font-size: 1.5em;
padding: 1.5vmin;
background-color: rgba(0,0,0,0.3);
border: 0;
color: #fff;
margin: 0.8vmin;
width: 50%;
}
button:hover {background-color: #28bd7b;}
button{
font-size: 1.5em;
padding: 1.5vmin;
margin: 0.8vmin;
background-color: #38cd8b;
border-radius: 1.8vmin;
color: #fff;
border: 0;
}
img{
height: 15vmin;
}
</style>
<body>
<a>
<img src='/resources/images/logo.svg'></img><br>
</a>
<input id="room" type="text" onkeypress="checkEnter(event)" value="" placeholder="your room name">
<button onclick = "start(location.href)">Go to room</button>
</body>
<script>
var room = document.getElementById("room");
var stateObj = { foo: "bar" };
room.addEventListener("input", function(e) {
console.log(e.charCode);
history.replaceState(stateObj, "Multiparty Meeting", "/"+room.value);
},true);
room.focus();
function start(target){
location.href;history.replaceState(stateObj, "Multiparty Meeting", "/");
window.location = target;
}
function checkEnter(event){
var x = event.charCode || event.keyCode;
if (x == 13 ) {
start(location.href);
}
}
</script>
</html>

View File

@ -1,299 +0,0 @@
/**
* Tasks:
*
* gulp dist
* Generates the browser app in development mode (unless NODE_ENV is set
* to 'production').
*
* gulp live
* Generates the browser app in development mode (unless NODE_ENV is set
* to 'production'), opens it and watches for changes in the source code.
*
* gulp
* Alias for `gulp live`.
*/
const fs = require('fs');
const path = require('path');
const gulp = require('gulp');
const gulpif = require('gulp-if');
const gutil = require('gulp-util');
const plumber = require('gulp-plumber');
const rename = require('gulp-rename');
const change = require('gulp-change');
const header = require('gulp-header');
const touch = require('gulp-touch-cmd');
const browserify = require('browserify');
const watchify = require('watchify');
const envify = require('envify/custom');
const uglify = require('gulp-uglify-es').default;
const source = require('vinyl-source-stream');
const buffer = require('vinyl-buffer');
const del = require('del');
const mkdirp = require('mkdirp');
const ncp = require('ncp');
const eslint = require('gulp-eslint');
const stylus = require('gulp-stylus');
const cssBase64 = require('gulp-css-base64');
const nib = require('nib');
const browserSync = require('browser-sync');
const PKG = require('./package.json');
const BANNER = fs.readFileSync('banner.txt').toString();
const BANNER_OPTIONS =
{
pkg : PKG,
currentYear : (new Date()).getFullYear()
};
const OUTPUT_DIR = '../server/public';
const appOptions = require('./config/config');
const SERVER_CONFIG = '../server/config/config';
// Set Node 'development' environment (unless externally set).
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
gutil.log(`NODE_ENV: ${process.env.NODE_ENV}`);
function logError(error)
{
gutil.log(gutil.colors.red(error.stack));
}
function bundle(options)
{
options = options || {};
const watch = Boolean(options.watch);
let bundler = browserify(
{
entries : PKG.main,
extensions : [ '.js', '.jsx' ],
// required for sourcemaps (must be false otherwise).
debug : process.env.NODE_ENV === 'development',
// required for watchify.
cache : {},
// required for watchify.
packageCache : {},
// required to be true only for watchify.
fullPaths : watch
})
.transform('babelify')
.transform(envify(
{
NODE_ENV : process.env.NODE_ENV,
_ : 'purge'
}));
if (watch)
{
bundler = watchify(bundler);
bundler.on('update', () =>
{
const start = Date.now();
gutil.log('bundling...');
rebundle();
gutil.log('bundle took %sms', (Date.now() - start));
});
}
function rebundle()
{
return bundler.bundle()
.on('error', logError)
.pipe(plumber())
.pipe(source(`${PKG.name}.js`))
.pipe(buffer())
.pipe(rename(`${PKG.name}.js`))
.pipe(gulpif(process.env.NODE_ENV === 'production',
uglify()
))
.pipe(header(BANNER, BANNER_OPTIONS))
.pipe(gulp.dest(OUTPUT_DIR));
}
return rebundle();
}
function changeHTML(content)
{
return content.replace(/chromeExtension/g, appOptions.chromeExtension);
}
gulp.task('clean', () => del(OUTPUT_DIR, { force: true }));
const LINTING_FILES = [
'gulpfile.js',
'lib/**/*.js',
'lib/**/*.jsx'
];
gulp.task('lint', () =>
{
return gulp.src(LINTING_FILES)
.pipe(plumber())
.pipe(eslint())
.pipe(eslint.format());
});
gulp.task('lint-fix', function()
{
return gulp.src(LINTING_FILES)
.pipe(plumber())
.pipe(eslint({ fix: true }))
.pipe(eslint.format())
.pipe(gulp.dest((file) => file.base));
});
gulp.task('css', () =>
{
return gulp.src('stylus/index.styl')
.pipe(plumber())
.pipe(stylus(
{
use : nib(),
compress : process.env.NODE_ENV === 'production'
}))
.on('error', logError)
.pipe(cssBase64(
{
baseDir : '.',
maxWeightResource : 50000 // So big ttf fonts are not included, nice.
}))
.pipe(rename(`${PKG.name}.css`))
.pipe(gulp.dest(OUTPUT_DIR))
.pipe(touch());
});
gulp.task('html', () =>
{
return gulp.src('*.html')
.pipe(change(changeHTML))
.pipe(gulp.dest(OUTPUT_DIR));
});
gulp.task('resources', (done) =>
{
const dst = path.join(OUTPUT_DIR, 'resources');
mkdirp.sync(dst);
ncp('resources', dst, { stopOnErr: true }, (error) =>
{
if (error && error[0].code !== 'ENOENT')
throw new Error(`resources copy failed: ${error}`);
done();
});
});
gulp.task('bundle', () =>
{
return bundle({ watch: false });
});
gulp.task('bundle:watch', () =>
{
return bundle({ watch: true });
});
gulp.task('livebrowser', (done) =>
{
const config = require(SERVER_CONFIG);
browserSync(
{
open : 'external',
host : config.domain,
port : 3000,
server :
{
baseDir : OUTPUT_DIR
},
https : config.tls,
ghostMode : false,
files : path.join(OUTPUT_DIR, '**', '*')
});
done();
});
gulp.task('browser', (done) =>
{
const config = require(SERVER_CONFIG);
browserSync(
{
open : 'external',
host : config.domain,
port : 3000,
server :
{
baseDir : OUTPUT_DIR
},
https : config.tls,
ghostMode : false
});
done();
});
gulp.task('watch', (done) =>
{
// Watch changes in HTML.
gulp.watch([ '*.html' ], gulp.series(
'html'
));
// Watch changes in Stylus files.
gulp.watch([ 'stylus/**/*.styl' ], gulp.series(
'css'
));
// Watch changes in resources.
gulp.watch([ 'resources/**/*' ], gulp.series(
'resources', 'css'
));
// Watch changes in JS files.
gulp.watch([ 'gulpfile.js', 'lib/**/*.js', 'lib/**/*.jsx' ], gulp.series(
'lint'
));
done();
});
gulp.task('dist', gulp.series(
'clean',
'lint',
'bundle',
'html',
'css',
'resources'
));
gulp.task('dist-watch', gulp.series(
'clean',
'lint',
'bundle:watch',
'html',
'css',
'resources',
'watch',
));
gulp.task('live', gulp.series(
'clean',
'lint',
'bundle:watch',
'html',
'css',
'resources',
'watch',
'livebrowser'
));
gulp.task('open', gulp.series('browser'));
gulp.task('default', gulp.series('live'));

View File

@ -1,30 +0,0 @@
<!doctype html>
<html>
<head>
<title>Multiparty Meeting</title>
<meta charset='UTF-8'>
<meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no'>
<meta name='description' content='multiparty meeting - Cutting Edge WebRTC Video Conferencing'>
<link rel='stylesheet' href='/multiparty-meeting.css'>
<link rel="chrome-webstore-item" href="chromeExtension">
<script src='/resources/js/antiglobal.js'></script>
<script>
window.localStorage.setItem('debug', '* -engine* -socket* -RIE* *WARN* *ERROR*');
if (window.antiglobal)
{
window.antiglobal('__multipartyMeetingScreenShareExtensionAvailable__', '___browserSync___oldSocketIo', 'io', '___browserSync___', '__core-js_shared__');
setInterval(window.antiglobal, 180000);
}
</script>
<script async src='/multiparty-meeting.js'></script>
</head>
<body>
<div id='multiparty-meeting'></div>
<div id='multiparty-meeting-media-query-detector'></div>
</body>
</html>

View File

@ -1,78 +0,0 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import * as stateActions from '../../redux/stateActions';
class HiddenPeers extends Component
{
constructor(props)
{
super(props);
this.state = { className: '' };
}
componentDidUpdate(prevProps)
{
const { hiddenPeersCount } = this.props;
if (hiddenPeersCount !== prevProps.hiddenPeersCount)
{
// eslint-disable-next-line react/no-did-update-set-state
this.setState({ className: 'pulse' }, () =>
{
if (this.timeout)
{
clearTimeout(this.timeout);
}
this.timeout = setTimeout(() =>
{
this.setState({ className: '' });
}, 500);
});
}
}
render()
{
const {
hiddenPeersCount,
openUsersTab
} = this.props;
return (
<div data-component='HiddenPeers'>
<div className={classnames('view-container', this.state.className)} onClick={() => openUsersTab()}>
<p>+{hiddenPeersCount} <br /> participant
{(hiddenPeersCount === 1) ? null : 's'}
</p>
</div>
</div>
);
}
}
HiddenPeers.propTypes =
{
hiddenPeersCount : PropTypes.number,
openUsersTab : PropTypes.func.isRequired
};
const mapDispatchToProps = (dispatch) =>
{
return {
openUsersTab : () =>
{
dispatch(stateActions.openToolArea());
dispatch(stateActions.setToolTab('users'));
}
};
};
const HiddenPeersContainer = connect(
null,
mapDispatchToProps
)(HiddenPeers);
export default HiddenPeersContainer;

View File

@ -1,232 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import classnames from 'classnames';
import * as appPropTypes from '../appPropTypes';
import { withRoomContext } from '../../RoomContext';
import FullScreen from '../FullScreen';
class Sidebar extends Component
{
constructor(props)
{
super(props);
this.fullscreen = new FullScreen(document);
this.state = {
fullscreen : false
};
}
handleToggleFullscreen = () =>
{
if (this.fullscreen.fullscreenElement)
{
this.fullscreen.exitFullscreen();
}
else
{
this.fullscreen.requestFullscreen(document.documentElement);
}
};
handleFullscreenChange = () =>
{
this.setState({
fullscreen : this.fullscreen.fullscreenElement !== null
});
};
componentDidMount()
{
if (this.fullscreen.fullscreenEnabled)
{
this.fullscreen.addEventListener('fullscreenchange', this.handleFullscreenChange);
}
}
componentWillUnmount()
{
if (this.fullscreen.fullscreenEnabled)
{
this.fullscreen.removeEventListener('fullscreenchange', this.handleFullscreenChange);
}
}
render()
{
const {
roomClient,
toolbarsVisible,
me,
screenProducer,
locked
} = this.props;
let screenState;
let screenTip;
let lockState = 'unlocked';
if (me.needExtension)
{
screenState = 'need-extension';
screenTip = 'Install screen sharing extension';
}
else if (!me.canShareScreen)
{
screenState = 'unsupported';
screenTip = 'Screen sharing not supported';
}
else if (screenProducer)
{
screenState = 'on';
screenTip = 'Stop screen sharing';
}
else
{
screenState = 'off';
screenTip = 'Start screen sharing';
}
if (locked)
{
lockState = 'locked';
}
return (
<div
className={classnames('sidebar room-controls', {
'visible' : toolbarsVisible
})}
data-component='Sidebar'
>
<If condition={this.fullscreen.fullscreenEnabled}>
<div
className={classnames('button', 'fullscreen', {
on : this.state.fullscreen
})}
onClick={this.handleToggleFullscreen}
data-tip='Fullscreen'
data-place='right'
data-type='dark'
/>
</If>
<div
className={classnames('button', 'screen', screenState)}
data-tip={screenTip}
data-place='right'
data-type='dark'
onClick={() =>
{
switch (screenState)
{
case 'on':
{
roomClient.disableScreenSharing();
break;
}
case 'off':
{
roomClient.enableScreenSharing();
break;
}
case 'need-extension':
{
roomClient.installExtension();
break;
}
default:
{
break;
}
}
}}
/>
<If condition={me.loginEnabled}>
<Choose>
<When condition={me.loggedIn}>
<div
className='button logout'
data-tip='Logout'
data-place='right'
data-type='dark'
onClick={() => roomClient.logout()}
>
<img src={me.picture || 'resources/images/avatar-empty.jpeg'} />
</div>
</When>
<Otherwise>
<div
className='button login off'
data-tip='Login'
data-place='right'
data-type='dark'
onClick={() => roomClient.login()}
/>
</Otherwise>
</Choose>
</If>
<div
className={classnames('button', 'lock', lockState, {
on : locked
})}
data-tip={`Room is ${lockState}`}
data-place='right'
data-type='dark'
onClick={() =>
{
if (locked)
{
roomClient.unlockRoom();
}
else
{
roomClient.lockRoom();
}
}}
/>
<div
className={classnames('button', 'raise-hand', {
on : me.raiseHand,
disabled : me.raiseHandInProgress
})}
data-tip='Raise hand'
data-place='right'
data-type='dark'
onClick={() => roomClient.sendRaiseHandState(!me.raiseHand)}
/>
<div
className={classnames('button', 'leave-meeting')}
data-tip='Leave meeting'
data-place='right'
data-type='dark'
onClick={() => roomClient.close()}
/>
</div>
);
}
}
Sidebar.propTypes = {
roomClient : PropTypes.any.isRequired,
toolbarsVisible : PropTypes.bool.isRequired,
me : appPropTypes.Me.isRequired,
screenProducer : appPropTypes.Producer,
locked : PropTypes.bool.isRequired
};
const mapStateToProps = (state) =>
({
toolbarsVisible : state.room.toolbarsVisible,
screenProducer : Object.values(state.producers)
.find((producer) => producer.source === 'screen'),
me : state.me,
locked : state.room.locked
});
export default withRoomContext(connect(
mapStateToProps
)(Sidebar));

View File

@ -1,70 +0,0 @@
import React from 'react';
import { connect } from 'react-redux';
import classnames from 'classnames';
import PropTypes from 'prop-types';
import * as appPropTypes from './appPropTypes';
import * as stateActions from '../redux/stateActions';
import { Appear } from './transitions';
const Notifications = ({ notifications, onClick, toolAreaOpen }) =>
{
return (
<div
data-component='Notifications'
className={classnames({
'toolarea-open' : toolAreaOpen
})}
>
{
notifications.map((notification) =>
{
return (
<Appear key={notification.id} duration={250}>
<div
className={classnames('notification', notification.type)}
onClick={() => onClick(notification.id)}
>
<div className='icon' />
<p className='text'>{notification.text}</p>
</div>
</Appear>
);
})
}
</div>
);
};
Notifications.propTypes =
{
notifications : PropTypes.arrayOf(appPropTypes.Notification).isRequired,
onClick : PropTypes.func.isRequired,
toolAreaOpen : PropTypes.bool
};
const mapStateToProps = (state) =>
{
const { notifications } = state;
return {
notifications,
toolAreaOpen : state.toolarea.toolAreaOpen
};
};
const mapDispatchToProps = (dispatch) =>
{
return {
onClick : (notificationId) =>
{
dispatch(stateActions.removeNotification(notificationId));
}
};
};
const NotificationsContainer = connect(
mapStateToProps,
mapDispatchToProps
)(Notifications);
export default NotificationsContainer;

View File

@ -1,263 +0,0 @@
import React, { Fragment } from 'react';
import { connect } from 'react-redux';
import { withRoomContext } from '../RoomContext';
import ReactTooltip from 'react-tooltip';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import CopyToClipboard from 'react-copy-to-clipboard';
import CookieConsent from 'react-cookie-consent';
import * as appPropTypes from './appPropTypes';
import * as requestActions from '../redux/requestActions';
import * as stateActions from '../redux/stateActions';
import { Appear } from './transitions';
import Me from './Containers/Me';
import Peers from './Layouts/Peers';
import AudioPeers from './PeerAudio/AudioPeers';
import Notifications from './Notifications';
import ToolArea from './ToolArea/ToolArea';
import FullScreenView from './VideoContainers/FullScreenView';
import VideoWindow from './VideoWindow/VideoWindow';
import Draggable from 'react-draggable';
import { idle } from '../utils';
import Sidebar from './Controls/Sidebar';
import Filmstrip from './Layouts/Filmstrip';
// Hide toolbars after 10 seconds of inactivity.
const TIMEOUT = 10 * 1000;
class Room extends React.Component
{
/**
* Hides the different toolbars on the page after a
* given amount of time has passed since the
* last time the cursor was moved.
*/
waitForHide = idle(() =>
{
this.props.setToolbarsVisible(false);
}, TIMEOUT);
handleMovement = () =>
{
// If the toolbars were hidden, show them again when
// the user moves their cursor.
if (!this.props.room.toolbarsVisible)
{
this.props.setToolbarsVisible(true);
}
this.waitForHide();
}
componentDidMount()
{
window.addEventListener('mousemove', this.handleMovement);
window.addEventListener('touchstart', this.handleMovement);
}
componentWillUnmount()
{
window.removeEventListener('mousemove', this.handleMovement);
window.removeEventListener('touchstart', this.handleMovement);
}
render()
{
const {
roomClient,
room,
amActiveSpeaker,
onRoomLinkCopy
} = this.props;
const View = {
filmstrip : Filmstrip,
democratic : Peers
}[room.mode];
if (room.audioSuspended)
{
return (
<Fragment>
<Appear duration={300}>
<div data-component='Room'>
<div className='sound-suspended'>
This webpage required sound and video to play, please click to allow.
<div
onClick={() =>
{
roomClient.notify('Joining.');
roomClient.resumeAudio();
}}
className='button'
>
<span>Allow</span>
</div>
</div>
</div>
</Appear>
</Fragment>
);
}
else if (room.lockedOut)
{
return (
<Fragment>
<Appear duration={300}>
<div data-component='Room'>
<div className='locked-out'>
This room is locked at the moment, try again later.
</div>
</div>
</Appear>
</Fragment>
);
}
else
{
return (
<Fragment>
<Appear duration={300}>
<div data-component='Room'>
<CookieConsent>
This website uses cookies to enhance the user experience.
</CookieConsent>
<FullScreenView advancedMode={room.advancedMode} />
<VideoWindow advancedMode={room.advancedMode} />
<div className='room-wrapper'>
<div data-component='Logo' />
<AudioPeers />
<Notifications />
<If condition={room.advancedMode}>
<div className='state' data-tip='Server status'>
<div className={classnames('icon', room.state)} />
<p className={classnames('text', room.state)}>{room.state}</p>
</div>
</If>
<div
className={classnames('room-link-wrapper room-controls', {
'visible' : this.props.room.toolbarsVisible
})}
>
<div className='room-link'>
<CopyToClipboard
text={room.url}
onCopy={onRoomLinkCopy}
>
<a
className='link'
href={room.url}
target='_blank'
data-tip='Click to copy room link'
rel='noopener noreferrer'
onClick={(event) =>
{
// If this is a 'Open in new window/tab' don't prevent
// click default action.
if (
event.ctrlKey || event.shiftKey || event.metaKey ||
// Middle click (IE > 9 and everyone else).
(event.button && event.button === 1)
)
{
return;
}
event.preventDefault();
}}
>
invitation link
</a>
</CopyToClipboard>
</div>
</div>
<View advancedMode={room.advancedMode} />
<Draggable handle='.me-container' bounds='body' cancel='.display-name'>
<div
className={classnames('me-container', {
'active-speaker' : amActiveSpeaker
})}
>
<Me
advancedMode={room.advancedMode}
/>
</div>
</Draggable>
<Sidebar />
<ReactTooltip
effect='solid'
delayShow={100}
delayHide={100}
/>
</div>
<ToolArea />
</div>
</Appear>
</Fragment>
);
}
}
}
Room.propTypes =
{
roomClient : PropTypes.object.isRequired,
room : appPropTypes.Room.isRequired,
me : appPropTypes.Me.isRequired,
amActiveSpeaker : PropTypes.bool.isRequired,
toolAreaOpen : PropTypes.bool.isRequired,
screenProducer : appPropTypes.Producer,
onRoomLinkCopy : PropTypes.func.isRequired,
setToolbarsVisible : PropTypes.func.isRequired
};
const mapStateToProps = (state) =>
{
const producersArray = Object.values(state.producers);
const screenProducer =
producersArray.find((producer) => producer.source === 'screen');
return {
room : state.room,
me : state.me,
toolAreaOpen : state.toolarea.toolAreaOpen,
amActiveSpeaker : state.me.name === state.room.activeSpeakerName,
screenProducer : screenProducer
};
};
const mapDispatchToProps = (dispatch) =>
{
return {
onRoomLinkCopy : () =>
{
dispatch(requestActions.notify(
{
text : 'Room link copied to the clipboard'
}));
},
setToolbarsVisible : (visible) =>
{
dispatch(stateActions.setToolbarsVisible(visible));
}
};
};
const RoomContainer = withRoomContext(connect(
mapStateToProps,
mapDispatchToProps
)(Room));
export default RoomContainer;

View File

@ -1,97 +0,0 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { withRoomContext } from '../../../RoomContext';
import MessageList from './MessageList';
class Chat extends Component
{
createNewMessage(text, sender, name, picture)
{
return {
type : 'message',
text,
time : Date.now(),
name,
sender,
picture
};
}
render()
{
const {
roomClient,
senderPlaceHolder,
autofocus,
displayName,
picture
} = this.props;
return (
<div data-component='Chat'>
<MessageList />
<form
data-component='Sender'
onSubmit={(e) =>
{
e.preventDefault();
const userInput = e.target.message.value;
if (userInput)
{
const message = this.createNewMessage(userInput, 'response', displayName, picture);
roomClient.sendChatMessage(message);
}
e.target.message.value = '';
}}
>
<input
type='text'
className='new-message'
name='message'
placeholder={senderPlaceHolder}
autoFocus={autofocus}
autoComplete='off'
/>
<input
type='submit'
className='send'
value='Send'
/>
</form>
</div>
);
}
}
Chat.propTypes =
{
roomClient : PropTypes.any.isRequired,
senderPlaceHolder : PropTypes.string,
autofocus : PropTypes.bool,
displayName : PropTypes.string,
picture : PropTypes.string
};
Chat.defaultProps =
{
senderPlaceHolder : 'Type a message...',
autofocus : false,
displayName : null
};
const mapStateToProps = (state) =>
{
return {
displayName : state.me.displayName,
picture : state.me.picture
};
};
const ChatContainer = withRoomContext(connect(
mapStateToProps
)(Chat));
export default ChatContainer;

View File

@ -1,98 +0,0 @@
import React, { Component } from 'react';
import { compose } from 'redux';
import PropTypes from 'prop-types';
import marked from 'marked';
import { connect } from 'react-redux';
import scrollToBottom from '../scrollToBottom';
const linkRenderer = new marked.Renderer();
linkRenderer.link = (href, title, text) =>
{
title = title ? title : href;
text = text ? text : href;
return (`<a target='_blank' href='${ href }' title='${ title }'>${ text }</a>`);
};
class MessageList extends Component
{
getTimeString(time)
{
return `${(time.getHours() < 10 ? '0' : '')}${time.getHours()}:${(time.getMinutes() < 10 ? '0' : '')}${time.getMinutes()}`;
}
render()
{
const {
chatmessages
} = this.props;
return (
<div data-component='MessageList' id='messages'>
<Choose>
<When condition={chatmessages.length > 0}>
{
chatmessages.map((message, i) =>
{
const messageTime = new Date(message.time);
const picture = (message.sender === 'response' ?
message.picture : this.props.myPicture) || 'resources/images/avatar-empty.jpeg';
return (
<div className='message' key={i}>
<div className={message.sender}>
<img className='message-avatar' src={picture} />
<div className='message-content'>
<div
className='message-text'
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html : marked.parse(
message.text,
{ sanitize: true, renderer: linkRenderer }
) }}
/>
<span className='message-time'>
{message.name} - {this.getTimeString(messageTime)}
</span>
</div>
</div>
</div>
);
})
}
</When>
<Otherwise>
<div className='empty'>
<p>No one has said anything yet...</p>
</div>
</Otherwise>
</Choose>
</div>
);
}
}
MessageList.propTypes =
{
chatmessages : PropTypes.arrayOf(PropTypes.object).isRequired,
myPicture : PropTypes.string
};
const mapStateToProps = (state) =>
{
return {
chatmessages : state.chatmessages,
myPicture : state.me.picture
};
};
const MessageListContainer = compose(
connect(mapStateToProps),
scrollToBottom()
)(MessageList);
export default MessageListContainer;

View File

@ -1,18 +0,0 @@
import React from 'react';
import WebTorrent from 'webtorrent';
import dragDrop from 'drag-drop';
import { shareFiles } from './index';
export const configureDragDrop = () =>
{
if (WebTorrent.WEBRTC_SUPPORT)
{
dragDrop('body', async (files) => await shareFiles(files));
}
};
export const HoldingOverlay = () => (
<div id='holding-overlay'>
Drop files here to share them
</div>
);

View File

@ -1,113 +0,0 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { withRoomContext } from '../../../RoomContext';
import magnet from 'magnet-uri';
const DEFAULT_PICTURE = 'resources/images/avatar-empty.jpeg';
class File extends Component
{
render()
{
const {
roomClient,
torrentSupport,
file
} = this.props;
return (
<div className='file-entry'>
<img className='file-avatar' src={file.picture || DEFAULT_PICTURE} />
<div className='file-content'>
<Choose>
<When condition={file.me}>
<p>You shared a file.</p>
</When>
<Otherwise>
<p>{file.displayName} shared a file.</p>
</Otherwise>
</Choose>
<If condition={!file.active && !file.files}>
<div className='file-info'>
<Choose>
<When condition={torrentSupport}>
<span
className='button'
onClick={() =>
{
roomClient.handleDownload(file.magnetUri);
}}
>
<img src='resources/images/download-icon.svg' />
</span>
</When>
<Otherwise>
<p>
Your browser does not support downloading files using WebTorrent.
</p>
</Otherwise>
</Choose>
<p>{magnet.decode(file.magnetUri).dn}</p>
</div>
</If>
<If condition={file.timeout}>
<Fragment>
<p>
If this process takes a long time, there might not be anyone seeding
this torrent. Try asking someone to reupload the file that you want.
</p>
</Fragment>
</If>
<If condition={file.active}>
<progress value={file.progress} />
</If>
<If condition={file.files}>
<Fragment>
<p>File finished downloading.</p>
{file.files.map((sharedFile, i) => (
<div className='file-info' key={i}>
<span
className='button'
onClick={() =>
{
roomClient.saveFile(sharedFile);
}}
>
<img src='resources/images/save-icon.svg' />
</span>
<p>{sharedFile.name}</p>
</div>
))}
</Fragment>
</If>
</div>
</div>
);
}
}
File.propTypes = {
roomClient : PropTypes.object.isRequired,
torrentSupport : PropTypes.bool.isRequired,
file : PropTypes.object.isRequired
};
const mapStateToProps = (state, { magnetUri }) =>
{
return {
file : state.files[magnetUri],
torrentSupport : state.room.torrentSupport
};
};
export default withRoomContext(connect(
mapStateToProps
)(File));

View File

@ -1,40 +0,0 @@
import React, { Component } from 'react';
import { compose } from 'redux';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import scrollToBottom from '../scrollToBottom';
import File from './File';
class FileList extends Component
{
render()
{
const {
files
} = this.props;
return (
<div className='shared-files'>
{ Object.keys(files).map((magnetUri) =>
<File key={magnetUri} magnetUri={magnetUri} />
)}
</div>
);
}
}
FileList.propTypes = {
files : PropTypes.object.isRequired
};
const mapStateToProps = (state) =>
{
return {
files : state.files
};
};
export default compose(
connect(mapStateToProps),
scrollToBottom()
)(FileList);

View File

@ -1,88 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import classNames from 'classnames';
import { withRoomContext } from '../../../RoomContext';
import FileList from './FileList';
class FileSharing extends Component
{
constructor(props)
{
super(props);
this._fileInput = React.createRef();
}
handleFileChange = async (event) =>
{
if (event.target.files.length > 0)
{
this.props.roomClient.shareFiles(event.target.files);
}
};
handleClick = () =>
{
if (this.props.torrentSupport)
{
// We want to open the file dialog when we click a button
// instead of actually rendering the input element itself.
this._fileInput.current.click();
}
};
render()
{
const {
torrentSupport
} = this.props;
const buttonDescription = torrentSupport ?
'Share file' : 'File sharing not supported';
return (
<div data-component='FileSharing'>
<div className='sharing-toolbar'>
<input
style={{ display: 'none' }}
ref={this._fileInput}
type='file'
onChange={this.handleFileChange}
multiple
/>
<div
type='button'
onClick={this.handleClick}
className={classNames('share-file', {
disabled : !torrentSupport
})}
>
<span>{buttonDescription}</span>
</div>
</div>
<FileList />
</div>
);
}
}
FileSharing.propTypes = {
roomClient : PropTypes.any.isRequired,
torrentSupport : PropTypes.bool.isRequired,
tabOpen : PropTypes.bool.isRequired
};
const mapStateToProps = (state) =>
{
return {
torrentSupport : state.room.torrentSupport,
tabOpen : state.toolarea.currentToolTab === 'files'
};
};
export default withRoomContext(connect(
mapStateToProps
)(FileSharing));

View File

@ -1,38 +0,0 @@
import React from 'react';
import { connect } from 'react-redux';
import { Me } from '../../appPropTypes';
const ListMe = ({ me }) =>
{
const picture = me.picture || 'resources/images/avatar-empty.jpeg';
return (
<li className='list-item me'>
<div data-component='ListPeer'>
<img className='avatar' src={picture} />
<div className='peer-info'>
{me.displayName}
</div>
<div className='indicators'>
<If condition={me.raisedHand}>
<div className='icon raise-hand on' />
</If>
</div>
</div>
</li>
);
};
ListMe.propTypes = {
me : Me.isRequired
};
const mapStateToProps = (state) => ({
me : state.me
});
export default connect(
mapStateToProps
)(ListMe);

View File

@ -1,124 +0,0 @@
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import * as appPropTypes from '../../appPropTypes';
import { withRoomContext } from '../../../RoomContext';
const ListPeer = (props) =>
{
const {
roomClient,
peer,
micConsumer,
screenConsumer
} = props;
const micEnabled = (
Boolean(micConsumer) &&
!micConsumer.locallyPaused &&
!micConsumer.remotelyPaused
);
const screenVisible = (
Boolean(screenConsumer) &&
!screenConsumer.locallyPaused &&
!screenConsumer.remotelyPaused
);
const picture = peer.picture || 'resources/images/avatar-empty.jpeg';
return (
<div data-component='ListPeer'>
<img className='avatar' src={picture} />
<div className='peer-info'>
{peer.displayName}
</div>
<div className='indicators'>
<If condition={peer.raiseHandState}>
<div className={
classnames(
'icon', 'raise-hand', {
on : peer.raiseHandState,
off : !peer.raiseHandState
}
)
}
/>
</If>
</div>
<div className='volume-container'>
<div className={classnames('bar', `level${micEnabled && micConsumer ? micConsumer.volume:0}`)} />
</div>
<div className='controls'>
<If condition={screenConsumer}>
<div
className={classnames('button', 'screen', {
on : screenVisible,
off : !screenVisible,
disabled : peer.peerScreenInProgress
})}
onClick={(e) =>
{
e.stopPropagation();
screenVisible ?
roomClient.modifyPeerConsumer(peer.name, 'screen', true) :
roomClient.modifyPeerConsumer(peer.name, 'screen', false);
}}
/>
</If>
<div
className={classnames('button', 'mic', {
on : micEnabled,
off : !micEnabled,
disabled : peer.peerAudioInProgress
})}
onClick={(e) =>
{
e.stopPropagation();
micEnabled ?
roomClient.modifyPeerConsumer(peer.name, 'mic', true) :
roomClient.modifyPeerConsumer(peer.name, 'mic', false);
}}
/>
</div>
</div>
);
};
ListPeer.propTypes =
{
roomClient : PropTypes.any.isRequired,
advancedMode : PropTypes.bool,
peer : appPropTypes.Peer.isRequired,
micConsumer : appPropTypes.Consumer,
webcamConsumer : appPropTypes.Consumer,
screenConsumer : appPropTypes.Consumer
};
const mapStateToProps = (state, { name }) =>
{
const peer = state.peers[name];
const consumersArray = peer.consumers
.map((consumerId) => state.consumers[consumerId]);
const micConsumer =
consumersArray.find((consumer) => consumer.source === 'mic');
const webcamConsumer =
consumersArray.find((consumer) => consumer.source === 'webcam');
const screenConsumer =
consumersArray.find((consumer) => consumer.source === 'screen');
return {
peer,
micConsumer,
webcamConsumer,
screenConsumer
};
};
const ListPeerContainer = withRoomContext(connect(
mapStateToProps
)(ListPeer));
export default ListPeerContainer;

View File

@ -1,89 +0,0 @@
import React from 'react';
import { connect } from 'react-redux';
import classNames from 'classnames';
import * as appPropTypes from '../../appPropTypes';
import { withRoomContext } from '../../../RoomContext';
import PropTypes from 'prop-types';
import ListPeer from './ListPeer';
import ListMe from './ListMe';
const ParticipantList =
({
roomClient,
advancedMode,
peers,
selectedPeerName,
spotlights
}) => (
<div data-component='ParticipantList'>
<ul className='list'>
<li className='list-header'>Me:</li>
<ListMe />
</ul>
<br />
<ul className='list'>
<li className='list-header'>Participants in Spotlight:</li>
{peers.filter((peer) =>
{
return (spotlights.find((spotlight) =>
{ return (spotlight === peer.name); }));
}).map((peer) => (
<li
key={peer.name}
className={classNames('list-item', {
selected : peer.name === selectedPeerName
})}
onClick={() => roomClient.setSelectedPeer(peer.name)}
>
<ListPeer name={peer.name} advancedMode={advancedMode} />
</li>
))}
</ul>
<br />
<ul className='list'>
<li className='list-header'>Passive Participants:</li>
{peers.filter((peer) =>
{
return !(spotlights.find((spotlight) =>
{ return (spotlight === peer.name); }));
}).map((peer) => (
<li
key={peer.name}
className={classNames('list-item', {
selected : peer.name === selectedPeerName
})}
onClick={() => roomClient.setSelectedPeer(peer.name)}
>
<ListPeer name={peer.name} advancedMode={advancedMode} />
</li>
))}
</ul>
</div>
);
ParticipantList.propTypes =
{
roomClient : PropTypes.any.isRequired,
advancedMode : PropTypes.bool,
peers : PropTypes.arrayOf(appPropTypes.Peer).isRequired,
selectedPeerName : PropTypes.string,
spotlights : PropTypes.array.isRequired
};
const mapStateToProps = (state) =>
{
const peersArray = Object.values(state.peers);
return {
peers : peersArray,
selectedPeerName : state.room.selectedPeerName,
spotlights : state.room.spotlights
};
};
const ParticipantListContainer = withRoomContext(connect(
mapStateToProps
)(ParticipantList));
export default ParticipantListContainer;

View File

@ -1,123 +0,0 @@
import React from 'react';
import { connect } from 'react-redux';
import * as appPropTypes from '../../appPropTypes';
import { withRoomContext } from '../../../RoomContext';
import * as stateActions from '../../../redux/stateActions';
import PropTypes from 'prop-types';
import Dropdown from 'react-dropdown';
import ReactTooltip from 'react-tooltip';
const modes = [ {
value : 'democratic',
label : 'Democratic view'
}, {
value : 'filmstrip',
label : 'Filmstrip view'
} ];
const findOption = (options, value) => options.find((option) => option.value === value);
const Settings = ({
roomClient,
room,
me,
onToggleAdvancedMode,
handleChangeMode
}) =>
{
let webcams;
if (me.webcamDevices)
webcams = Array.from(me.webcamDevices.values());
else
webcams = [];
let audioDevices;
let audioDevicesText;
if (me.canChangeAudioDevice)
audioDevicesText = 'Select audio input device';
else
audioDevicesText = 'Unable to select audio input device';
if (me.audioDevices)
audioDevices = Array.from(me.audioDevices.values());
else
audioDevices = [];
return (
<div className='settings'>
<Dropdown
options={webcams}
value={findOption(webcams, me.selectedWebcam)}
onChange={(webcam) => roomClient.changeWebcam(webcam.value)}
placeholder={'Select camera'}
/>
<Dropdown
disabled={!me.canChangeAudioDevice}
options={audioDevices}
value={findOption(audioDevices, me.selectedAudioDevice)}
onChange={(device) => roomClient.changeAudioDevice(device.value)}
placeholder={audioDevicesText}
/>
<ReactTooltip
effect='solid'
/>
<div
data-tip='keyboard shortcut: &lsquo;a&lsquo;'
data-type='dark'
data-place='left'
>
<input
id='room-mode'
type='checkbox'
checked={room.advancedMode}
onChange={onToggleAdvancedMode}
/>
<label htmlFor='room-mode'>Advanced mode</label>
</div>
<div
data-tip='keyboard shortcut: type a digit'
data-type='dark'
data-place='left'
>
<Dropdown
options={modes}
value={findOption(modes, room.mode)}
onChange={(mode) => handleChangeMode(mode.value)}
/>
</div>
</div>
);
};
Settings.propTypes =
{
roomClient : PropTypes.any.isRequired,
me : appPropTypes.Me.isRequired,
room : appPropTypes.Room.isRequired,
onToggleAdvancedMode : PropTypes.func.isRequired,
handleChangeMode : PropTypes.func.isRequired
};
const mapStateToProps = (state) =>
{
return {
me : state.me,
room : state.room
};
};
const mapDispatchToProps = {
onToggleAdvancedMode : stateActions.toggleAdvancedMode,
handleChangeMode : stateActions.setDisplayMode
};
const SettingsContainer = withRoomContext(connect(
mapStateToProps,
mapDispatchToProps
)(Settings));
export default SettingsContainer;

View File

@ -1,41 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import classNames from 'classnames';
import * as stateActions from '../../redux/stateActions';
const TabHeader = ({ currentToolTab, setToolTab, id, name, badge }) => (
<div
className={classNames('tab-header', {
checked : currentToolTab === id
})}
onClick={() => setToolTab(id)}
>
{name}
<If condition={badge > 0}>
<span className='badge'>{badge}</span>
</If>
</div>
);
TabHeader.propTypes = {
currentToolTab : PropTypes.string.isRequired,
setToolTab : PropTypes.func.isRequired,
id : PropTypes.string.isRequired,
name : PropTypes.string.isRequired,
badge : PropTypes.number
};
const mapStateToProps = (state) => ({
currentToolTab : state.toolarea.currentToolTab
});
const mapDispatchToProps = {
setToolTab : stateActions.setToolTab
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(TabHeader);

View File

@ -1,134 +0,0 @@
import React, { Fragment } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import * as stateActions from '../../redux/stateActions';
import ParticipantList from './ParticipantList/ParticipantList';
import Chat from './Chat/Chat';
import Settings from './Settings/Settings';
import FileSharing from './FileSharing/FileSharing';
import TabHeader from './TabHeader';
class ToolArea extends React.Component
{
constructor(props)
{
super(props);
}
render()
{
const {
currentToolTab,
toolAreaOpen,
unreadMessages,
unreadFiles,
toggleToolArea,
unread
} = this.props;
const VisibleTab = {
chat : Chat,
files : FileSharing,
users : ParticipantList,
settings : Settings
}[currentToolTab];
return (
<Fragment>
<div
className={classNames('toolarea-shade', {
open : toolAreaOpen
})}
onClick={toggleToolArea}
/>
<div
data-component='ToolArea'
className={classNames({
open : toolAreaOpen
})}
>
<div
className='toolarea-button'
onClick={toggleToolArea}
>
<span className='content'>
<div
className='toolarea-icon'
/>
<p>Toolbox</p>
</span>
<If condition={!toolAreaOpen && unread > 0}>
<span className={classNames('badge', { long: unread >= 10 })}>
{unread}
</span>
</If>
</div>
<div className='tab-headers'>
<TabHeader
id='chat'
name='Chat'
badge={unreadMessages}
/>
<TabHeader
id='files'
name='Files'
badge={unreadFiles}
/>
<TabHeader
id='users'
name='Users'
/>
<TabHeader
id='settings'
name='Settings'
/>
</div>
<div className='tab'>
<VisibleTab />
</div>
</div>
</Fragment>
);
}
}
ToolArea.propTypes =
{
advancedMode : PropTypes.bool,
currentToolTab : PropTypes.string.isRequired,
setToolTab : PropTypes.func.isRequired,
unreadMessages : PropTypes.number.isRequired,
unreadFiles : PropTypes.number.isRequired,
toolAreaOpen : PropTypes.bool,
toggleToolArea : PropTypes.func.isRequired,
closeToolArea : PropTypes.func.isRequired,
unread : PropTypes.number.isRequired
};
const mapStateToProps = (state) => ({
currentToolTab : state.toolarea.currentToolTab,
unreadMessages : state.toolarea.unreadMessages,
unreadFiles : state.toolarea.unreadFiles,
toolAreaOpen : state.toolarea.toolAreaOpen,
unread : state.toolarea.unreadMessages +
state.toolarea.unreadFiles
});
const mapDispatchToProps = {
setToolTab : stateActions.setToolTab,
toggleToolArea : stateActions.toggleToolArea,
closeToolArea : stateActions.closeToolArea
};
const ToolAreaContainer = connect(
mapStateToProps,
mapDispatchToProps
)(ToolArea);
export default ToolAreaContainer;

View File

@ -1,63 +0,0 @@
import React, { Component } from 'react';
import { findDOMNode } from 'react-dom';
/**
* A higher order component which scrolls the user to the bottom of the
* wrapped component, provided that the user already was at the bottom
* of the wrapped component. Useful for chats and similar use cases.
* @param {number} treshold The required distance from the bottom required.
*/
const scrollToBottom = (treshold = 0) => (WrappedComponent) =>
{
return class AutoScroller extends Component
{
constructor(props)
{
super(props);
this.ref = React.createRef();
}
getSnapshotBeforeUpdate()
{
// Check if the user has scrolled close enough to the bottom for
// us to scroll to the bottom or not.
return this.elem.scrollHeight - this.elem.scrollTop <=
this.elem.clientHeight - treshold;
}
scrollToBottom = () =>
{
// Scroll the user to the bottom of the wrapped element.
this.elem.scrollTop = this.elem.scrollHeight;
};
componentDidMount()
{
// eslint-disable-next-line react/no-find-dom-node
this.elem = findDOMNode(this.ref.current);
this.scrollToBottom();
}
componentDidUpdate(prevProps, prevState, atBottom)
{
if (atBottom)
{
this.scrollToBottom();
}
}
render()
{
return (
<WrappedComponent
ref={this.ref}
{...this.props}
/>
);
}
};
};
export default scrollToBottom;

View File

@ -1,95 +0,0 @@
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import * as appPropTypes from '../appPropTypes';
import * as stateActions from '../../redux/stateActions';
import FullView from './FullView';
const FullScreenView = (props) =>
{
const {
advancedMode,
consumer,
toggleConsumerFullscreen,
toolbarsVisible
} = props;
if (!consumer)
return null;
const consumerVisible = (
Boolean(consumer) &&
!consumer.locallyPaused &&
!consumer.remotelyPaused
);
let consumerProfile;
if (consumer)
consumerProfile = consumer.profile;
return (
<div data-component='FullScreenView'>
<If condition={consumerVisible && !consumer.supported}>
<div className='incompatible-video'>
<p>incompatible video</p>
</div>
</If>
<div className='controls'>
<div
className={classnames('button', 'exitfullscreen', 'room-controls', {
visible : toolbarsVisible
})}
onClick={(e) =>
{
e.stopPropagation();
toggleConsumerFullscreen(consumer);
}}
/>
</div>
<FullView
advancedMode={advancedMode}
videoTrack={consumer ? consumer.track : null}
videoVisible={consumerVisible}
videoProfile={consumerProfile}
/>
</div>
);
};
FullScreenView.propTypes =
{
advancedMode : PropTypes.bool,
consumer : appPropTypes.Consumer,
toggleConsumerFullscreen : PropTypes.func.isRequired,
toolbarsVisible : PropTypes.bool
};
const mapStateToProps = (state) =>
{
return {
consumer : state.consumers[state.room.fullScreenConsumer],
toolbarsVisible : state.room.toolbarsVisible
};
};
const mapDispatchToProps = (dispatch) =>
{
return {
toggleConsumerFullscreen : (consumer) =>
{
if (consumer)
dispatch(stateActions.toggleConsumerFullscreen(consumer.id));
}
};
};
const FullScreenViewContainer = connect(
mapStateToProps,
mapDispatchToProps
)(FullScreenView);
export default FullScreenViewContainer;

View File

@ -1,222 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import * as appPropTypes from '../appPropTypes';
import EditableInput from '../Controls/EditableInput';
export default class PeerView extends React.Component
{
constructor(props)
{
super(props);
this.state =
{
volume : 0, // Integer from 0 to 10.,
videoWidth : null,
videoHeight : null
};
// Latest received video track.
// @type {MediaStreamTrack}
this._audioTrack = null;
// Latest received video track.
// @type {MediaStreamTrack}
this._videoTrack = null;
// Periodic timer for showing video resolution.
this._videoResolutionTimer = null;
}
render()
{
const {
isMe,
peer,
volume,
advancedMode,
videoVisible,
videoProfile,
audioCodec,
videoCodec,
onChangeDisplayName
} = this.props;
const {
videoWidth,
videoHeight
} = this.state;
return (
<div data-component='PeerView'>
<div className='info'>
<If condition={advancedMode}>
<div className={classnames('media', { 'is-me': isMe })}>
<div className='box'>
<If condition={audioCodec}>
<p className='codec'>{audioCodec}</p>
</If>
<If condition={videoCodec}>
<p className='codec'>{videoCodec} {videoProfile}</p>
</If>
<If condition={(videoVisible && videoWidth !== null)}>
<p className='resolution'>{videoWidth}x{videoHeight}</p>
</If>
</div>
</div>
</If>
<div className={classnames('peer', { 'is-me': isMe })}>
<Choose>
<When condition={isMe}>
<EditableInput
value={peer.displayName}
propName='displayName'
className='display-name editable'
classLoading='loading'
classInvalid='invalid'
shouldBlockWhileLoading
editProps={{
maxLength : 20,
autoCorrect : false,
spellCheck : false
}}
onChange={({ displayName }) => onChangeDisplayName(displayName)}
/>
</When>
<Otherwise>
<span className='display-name'>
{peer.displayName}
</span>
</Otherwise>
</Choose>
<If condition={advancedMode}>
<div className='row'>
<span
className={classnames('device-icon', peer.device.flag)}
/>
<span className='device-version'>
{peer.device.name} {Math.floor(peer.device.version) || null}
</span>
</div>
</If>
</div>
</div>
<video
ref='video'
className={classnames({
hidden : !videoVisible,
'is-me' : isMe,
loading : videoProfile === 'none'
})}
autoPlay
playsInline
muted={isMe}
/>
<div className='volume-container'>
<div className={classnames('bar', `level${volume}`)} />
</div>
</div>
);
}
componentDidMount()
{
const { audioTrack, videoTrack } = this.props;
this._setTracks(audioTrack, videoTrack);
}
componentWillUnmount()
{
clearInterval(this._videoResolutionTimer);
}
componentWillReceiveProps(nextProps)
{
const { audioTrack, videoTrack } = nextProps;
this._setTracks(audioTrack, videoTrack);
}
_setTracks(audioTrack, videoTrack)
{
if (this._audioTrack === audioTrack && this._videoTrack === videoTrack)
return;
this._audioTrack = audioTrack;
this._videoTrack = videoTrack;
clearInterval(this._videoResolutionTimer);
this._hideVideoResolution();
const { video } = this.refs;
if (audioTrack || videoTrack)
{
const stream = new MediaStream;
if (audioTrack)
stream.addTrack(audioTrack);
if (videoTrack)
stream.addTrack(videoTrack);
video.srcObject = stream;
if (videoTrack)
this._showVideoResolution();
}
else
{
video.srcObject = null;
}
}
_showVideoResolution()
{
this._videoResolutionTimer = setInterval(() =>
{
const { videoWidth, videoHeight } = this.state;
const { video } = this.refs;
// Don't re-render if nothing changed.
if (video.videoWidth === videoWidth && video.videoHeight === videoHeight)
return;
this.setState(
{
videoWidth : video.videoWidth,
videoHeight : video.videoHeight
});
}, 1000);
}
_hideVideoResolution()
{
this.setState({ videoWidth: null, videoHeight: null });
}
}
PeerView.propTypes =
{
isMe : PropTypes.bool,
peer : PropTypes.oneOfType(
[ appPropTypes.Me, appPropTypes.Peer ]).isRequired,
advancedMode : PropTypes.bool,
audioTrack : PropTypes.any,
volume : PropTypes.number,
videoTrack : PropTypes.any,
videoVisible : PropTypes.bool.isRequired,
videoProfile : PropTypes.string,
audioCodec : PropTypes.string,
videoCodec : PropTypes.string,
onChangeDisplayName : PropTypes.func
};

View File

@ -1,157 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
export default class ScreenView extends React.Component
{
constructor(props)
{
super(props);
this.state =
{
screenWidth : null,
screenHeight : null
};
// Latest received screen track.
// @type {MediaStreamTrack}
this._screenTrack = null;
// Periodic timer for showing video resolution.
this._screenResolutionTimer = null;
}
render()
{
const {
isMe,
advancedMode,
screenVisible,
screenProfile,
screenCodec
} = this.props;
const {
screenWidth,
screenHeight
} = this.state;
return (
<div data-component='ScreenView'>
<div className='info'>
<If condition={advancedMode}>
<div className={classnames('media', { 'is-me': isMe })}>
<If condition={screenVisible}>
<div className='box'>
<If condition={screenCodec}>
<p className='codec'>{screenCodec} {screenProfile}</p>
</If>
<If condition={(screenVisible && screenWidth !== null)}>
<p className='resolution'>{screenWidth}x{screenHeight}</p>
</If>
</div>
</If>
</div>
</If>
</div>
<video
ref='video'
className={classnames({
hidden : !screenVisible,
'is-me' : isMe,
loading : screenProfile === 'none'
})}
autoPlay
playsInline
muted={Boolean(true)}
/>
</div>
);
}
componentDidMount()
{
const { screenTrack } = this.props;
this._setTracks(screenTrack);
}
componentWillUnmount()
{
clearInterval(this._screenResolutionTimer);
}
componentWillReceiveProps(nextProps)
{
const { screenTrack } = nextProps;
this._setTracks(screenTrack);
}
_setTracks(screenTrack)
{
if (this._screenTrack === screenTrack)
return;
this._screenTrack = screenTrack;
clearInterval(this._screenResolutionTimer);
this._hideScreenResolution();
const { video } = this.refs;
if (screenTrack)
{
const stream = new MediaStream;
if (screenTrack)
stream.addTrack(screenTrack);
video.srcObject = stream;
if (screenTrack)
this._showScreenResolution();
}
else
{
video.srcObject = null;
}
}
_showScreenResolution()
{
this._screenResolutionTimer = setInterval(() =>
{
const { screenWidth, screenHeight } = this.state;
const { video } = this.refs;
// Don't re-render if nothing changed.
if (video.videoWidth === screenWidth && video.videoHeight === screenHeight)
return;
this.setState(
{
screenWidth : video.videoWidth,
screenHeight : video.videoHeight
});
}, 1000);
}
_hideScreenResolution()
{
this.setState({ screenWidth: null, screenHeight: null });
}
}
ScreenView.propTypes =
{
isMe : PropTypes.bool,
advancedMode : PropTypes.bool,
screenTrack : PropTypes.any,
screenVisible : PropTypes.bool,
screenProfile : PropTypes.string,
screenCodec : PropTypes.string
};

View File

@ -1,22 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { CSSTransition } from 'react-transition-group';
const Appear = ({ duration, children }) => (
<CSSTransition
in
classNames='Appear'
timeout={duration || 1000}
appear
>
{children}
</CSSTransition>
);
Appear.propTypes =
{
duration : PropTypes.number,
children : PropTypes.any
};
export { Appear };

View File

@ -1,101 +0,0 @@
# APP STATE
```js
{
peerWidth : 200,
peerHeight : 150,
room :
{
url : 'https://example.io/?&roomId=d0el8y34',
state : 'connected', // new/connecting/connected/closed
activeSpeakerName : 'alice'
},
me :
{
name : 'bob',
displayName : 'Bob McFLower',
displayNameSet : false, // true if got from cookie or manually set.
device : { flag: 'firefox', name: 'Firefox', version: '61' },
canSendMic : true,
canSendWebcam : true,
webcamInProgress : false,
audioOnly : false,
audioOnlyInProgress : false,
restartIceInProgress : false
},
producers :
{
1111 :
{
id : 1111,
source : 'mic', // mic/webcam,
locallyPaused : true,
remotelyPaused : false,
track : MediaStreamTrack,
codec : 'opus'
},
1112 :
{
id : 1112,
source : 'webcam', // mic/webcam
deviceLabel : 'Macbook Webcam',
type : 'front', // front/back
locallyPaused : false,
remotelyPaused : false,
track : MediaStreamTrack,
codec : 'vp8',
}
},
peers :
{
'alice' :
{
name : 'alice',
displayName : 'Alice Thomsom',
raiseHandState : false,
device : { flag: 'chrome', name: 'Chrome', version: '58' },
consumers : [ 5551, 5552 ]
}
},
consumers :
{
5551 :
{
id : 5551,
peerName : 'alice',
source : 'mic', // mic/webcam
supported : true,
locallyPaused : false,
remotelyPaused : false,
profile : 'default',
track : MediaStreamTrack,
codec : 'opus'
},
5552 :
{
id : 5552,
peerName : 'alice',
source : 'webcam',
supported : false,
locallyPaused : false,
remotelyPaused : true,
profile : 'medium',
track : null,
codec : 'h264'
}
},
notifications :
[
{
id : 'qweasdw43we',
type : 'info' // info/error
text : 'You joined the room'
},
{
id : 'j7sdhkjjkcc',
type : 'error'
text : 'Could not add webcam'
}
]
}
```

View File

@ -1,47 +0,0 @@
import {
applyMiddleware,
createStore,
compose
} from 'redux';
import thunk from 'redux-thunk';
import { createLogger } from 'redux-logger';
import reducers from './redux/reducers';
const reduxMiddlewares =
[
thunk
];
if (process.env.NODE_ENV === 'development')
{
const reduxLogger = createLogger(
{
// filter VOLUME level actions from log
predicate : (getState, action) => ! (action.type == 'SET_PRODUCER_VOLUME'
|| action.type == 'SET_CONSUMER_VOLUME'),
duration : true,
timestamp : false,
level : 'log',
logErrors : true
});
reduxMiddlewares.push(reduxLogger);
}
const composeEnhancers =
typeof window === 'object' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// Specify extensions options like name, actionsBlacklist, actionsCreators, serialize...
}) : compose;
const enhancer = composeEnhancers(
applyMiddleware(...reduxMiddlewares)
// other store enhancers if any
);
export const store = createStore(
reducers,
undefined,
enhancer
);

View File

@ -1,40 +0,0 @@
let mediaQueryDetectorElem;
export function initialize()
{
// Media query detector stuff.
mediaQueryDetectorElem =
document.getElementById('multiparty-meeting-media-query-detector');
return Promise.resolve();
}
export function isDesktop()
{
return Boolean(mediaQueryDetectorElem.offsetParent);
}
export function isMobile()
{
return !mediaQueryDetectorElem.offsetParent;
}
/**
* Create a function which will call the callback function
* after the given amount of milliseconds has passed since
* the last time the callback function was called.
*/
export const idle = (callback, delay) =>
{
let handle;
return () =>
{
if (handle)
{
clearTimeout(handle);
}
handle = setTimeout(callback, delay);
};
};

View File

@ -1,82 +1,380 @@
{
"name": "multiparty-meeting",
"version": "1.1.0",
"private": true,
"description": "multiparty meeting service",
"author": "Håvar Aambø Fosstveit <h@fosstveit.net>",
"license": "MIT",
"main": "lib/index.jsx",
"dependencies": {
"@babel/runtime": "^7.1.2",
"classnames": "^2.2.6",
"create-torrent": "^3.32.1",
"debug": "^4.1.0",
"domready": "^1.0.8",
"drag-drop": "^4.2.0",
"file-saver": "^1.3.8",
"hark": "^1.2.2",
"js-cookie": "^2.2.0",
"magnet-uri": "^5.2.3",
"marked": "^0.5.1",
"mediasoup-client": "^2.4.9",
"prop-types": "^15.6.2",
"random-string": "^0.2.0",
"react": "^16.5.2",
"react-cookie-consent": "^1.9.0",
"react-copy-to-clipboard": "^5.0.1",
"react-dom": "^16.5.2",
"react-draggable": "^3.0.5",
"react-dropdown": "^1.5.0",
"react-redux": "^5.0.7",
"react-spinner": "^0.2.7",
"react-tooltip": "^3.9.0",
"react-transition-group": "^2.5.0",
"redux": "^4.0.1",
"redux-logger": "^3.0.6",
"redux-thunk": "^2.3.0",
"resize-observer-polyfill": "^1.5.0",
"riek": "^1.1.0",
"socket.io-client": "^2.1.1",
"url-parse": "^1.4.3",
"webtorrent": "^0.102.4"
},
"devDependencies": {
"@babel/core": "^7.1.2",
"@babel/plugin-proposal-class-properties": "^7.1.0",
"@babel/plugin-proposal-object-rest-spread": "^7.0.0",
"@babel/plugin-transform-runtime": "^7.1.0",
"@babel/preset-env": "^7.1.0",
"@babel/preset-react": "^7.0.0",
"babel-plugin-jsx-control-statements": "^3.2.8",
"babel-eslint": "^10.0.1",
"babelify": "^10.0.0",
"browser-sync": "^2.26.3",
"browserify": "^16.2.3",
"del": "^3.0.0",
"envify": "^4.1.0",
"eslint": "^5.7.0",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-jsx-control-statements": "^2.2.1",
"eslint-plugin-react": "^7.11.1",
"gulp": "^4.0.0",
"gulp-change": "^1.0.0",
"gulp-css-base64": "^1.3.4",
"gulp-eslint": "^5.0.0",
"gulp-header": "^2.0.5",
"gulp-if": "^2.0.2",
"gulp-plumber": "^1.2.0",
"gulp-rename": "^1.4.0",
"gulp-stylus": "^2.7.0",
"gulp-touch-cmd": "0.0.1",
"gulp-uglify-es": "^1.0.4",
"gulp-util": "^3.0.8",
"lodash": "^4.17.10",
"mkdirp": "^0.5.1",
"ncp": "^2.0.0",
"nib": "^1.1.2",
"supports-color": "^5.5.0",
"vinyl-buffer": "^1.0.1",
"vinyl-source-stream": "^2.0.0",
"watchify": "^3.11.0"
}
"name": "multiparty-meeting",
"version": "1.2.0",
"private": true,
"description": "multiparty meeting service",
"author": "Håvar Aambø Fosstveit <h@fosstveit.net>",
"license": "MIT",
"dependencies": {
"@material-ui/core": "^3.9.2",
"@material-ui/icons": "^3.0.2",
"create-torrent": "^3.33.0",
"domready": "^1.0.8",
"file-saver": "^2.0.1",
"hark": "^1.2.3",
"js-cookie": "^2.2.0",
"marked": "^0.6.1",
"mediasoup-client": "^2.4.9",
"notistack": "^0.5.1",
"prop-types": "^15.7.2",
"random-string": "^0.2.0",
"react": "^16.8.5",
"react-cookie-consent": "^2.2.2",
"react-dom": "^16.8.5",
"react-draggable": "^3.2.1",
"react-redux": "^6.0.1",
"react-scripts": "2.1.8",
"react-tooltip": "^3.10.0",
"redux": "^4.0.1",
"redux-logger": "^3.0.6",
"redux-persist": "^5.10.0",
"redux-thunk": "^2.3.0",
"resize-observer-polyfill": "^1.5.1",
"riek": "^1.1.0",
"socket.io-client": "^2.2.0",
"source-map-explorer": "^1.8.0",
"url-parse": "^1.4.4",
"webtorrent": "^0.103.1"
},
"scripts": {
"analyze-main": "source-map-explorer build/static/js/main.*",
"analyze-chunk": "source-map-explorer build/static/js/2.*",
"start": "HTTPS=true PORT=4443 react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"env": {
"browser": true,
"es6": true,
"node": true
},
"plugins": [
"import",
"react"
],
"extends": [
"eslint:recommended",
"plugin:react/recommended"
],
"settings": {
"react": {
"pragma": "React",
"version": "16"
}
},
"parser": "babel-eslint",
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module",
"ecmaFeatures": {
"impliedStrict": true,
"jsx": true
}
},
"rules": {
"array-bracket-spacing": [
2,
"always",
{
"objectsInArrays": true,
"arraysInArrays": true
}
],
"arrow-parens": [
2,
"always"
],
"arrow-spacing": 2,
"block-spacing": [
2,
"always"
],
"brace-style": [
2,
"allman",
{
"allowSingleLine": true
}
],
"camelcase": 2,
"comma-dangle": 2,
"comma-spacing": [
2,
{
"before": false,
"after": true
}
],
"comma-style": 2,
"computed-property-spacing": 2,
"constructor-super": 2,
"func-call-spacing": 2,
"generator-star-spacing": 2,
"guard-for-in": 2,
"indent": [
2,
"tab",
{
"SwitchCase": 1
}
],
"key-spacing": [
2,
{
"singleLine": {
"beforeColon": false,
"afterColon": true
},
"multiLine": {
"beforeColon": true,
"afterColon": true,
"align": "colon"
}
}
],
"keyword-spacing": 2,
"linebreak-style": [
2,
"unix"
],
"lines-around-comment": [
2,
{
"allowBlockStart": true,
"allowObjectStart": true,
"beforeBlockComment": true,
"beforeLineComment": false
}
],
"max-len": [
2,
90,
{
"tabWidth": 2,
"comments": 110,
"ignoreUrls": true,
"ignoreStrings": true,
"ignoreTemplateLiterals": true,
"ignoreRegExpLiterals": true
}
],
"newline-after-var": 2,
"newline-before-return": 2,
"newline-per-chained-call": 2,
"no-alert": 2,
"no-caller": 2,
"no-case-declarations": 2,
"no-catch-shadow": 2,
"no-class-assign": 2,
"no-confusing-arrow": 2,
"no-console": 2,
"no-const-assign": 2,
"no-debugger": 2,
"no-dupe-args": 2,
"no-dupe-keys": 2,
"no-duplicate-case": 2,
"no-div-regex": 2,
"no-empty": [
2,
{
"allowEmptyCatch": true
}
],
"no-empty-pattern": 2,
"no-else-return": 0,
"no-eval": 2,
"no-extend-native": 2,
"no-ex-assign": 2,
"no-extra-bind": 2,
"no-extra-boolean-cast": 2,
"no-extra-label": 2,
"no-extra-semi": 2,
"no-fallthrough": 2,
"no-func-assign": 2,
"no-global-assign": 2,
"no-implicit-coercion": 2,
"no-implicit-globals": 2,
"no-inner-declarations": 2,
"no-invalid-regexp": 2,
"no-irregular-whitespace": 2,
"no-lonely-if": 2,
"no-mixed-operators": 2,
"no-mixed-spaces-and-tabs": 2,
"no-multi-spaces": 2,
"no-multi-str": 2,
"no-multiple-empty-lines": [
2,
{
"max": 1,
"maxEOF": 0,
"maxBOF": 0
}
],
"no-native-reassign": 2,
"no-negated-in-lhs": 2,
"no-new": 2,
"no-new-func": 2,
"no-new-wrappers": 2,
"no-obj-calls": 2,
"no-proto": 2,
"no-prototype-builtins": 0,
"no-redeclare": 2,
"no-regex-spaces": 2,
"no-restricted-imports": 2,
"no-return-assign": 2,
"no-self-assign": 2,
"no-self-compare": 2,
"no-sequences": 2,
"no-shadow": 2,
"no-shadow-restricted-names": 2,
"no-spaced-func": 2,
"no-sparse-arrays": 2,
"no-this-before-super": 2,
"no-throw-literal": 2,
"no-undef": 2,
"no-unexpected-multiline": 2,
"no-unmodified-loop-condition": 2,
"no-unreachable": 2,
"no-unused-vars": [
1,
{
"vars": "all",
"args": "after-used"
}
],
"no-use-before-define": [
2,
{
"functions": false
}
],
"no-useless-call": 2,
"no-useless-computed-key": 2,
"no-useless-concat": 2,
"no-useless-rename": 2,
"no-var": 2,
"no-whitespace-before-property": 2,
"object-curly-newline": 0,
"object-curly-spacing": [
2,
"always"
],
"object-property-newline": [
2,
{
"allowMultiplePropertiesPerLine": true
}
],
"prefer-const": 2,
"prefer-rest-params": 2,
"prefer-spread": 2,
"prefer-template": 2,
"quotes": [
2,
"single",
{
"avoidEscape": true
}
],
"semi": [
2,
"always"
],
"semi-spacing": 2,
"space-before-blocks": 2,
"space-before-function-paren": [
2,
{
"anonymous": "never",
"named": "never",
"asyncArrow": "always"
}
],
"space-in-parens": [
2,
"never"
],
"spaced-comment": [
2,
"always"
],
"strict": 2,
"valid-typeof": 2,
"eol-last": 0,
"yoda": 2,
"import/extensions": 2,
"import/no-duplicates": 2,
"jsx-quotes": [
2,
"prefer-single"
],
"react/display-name": [
2,
{
"ignoreTranspilerName": false
}
],
"react/forbid-prop-types": 0,
"react/jsx-boolean-value": 2,
"react/jsx-closing-bracket-location": 2,
"react/jsx-curly-spacing": 2,
"react/jsx-equals-spacing": 2,
"react/jsx-handler-names": 2,
"react/jsx-indent-props": [
2,
"tab"
],
"react/jsx-indent": [
2,
"tab"
],
"react/jsx-key": 2,
"react/jsx-max-props-per-line": 0,
"react/jsx-no-bind": 0,
"react/jsx-no-duplicate-props": 2,
"react/jsx-no-literals": 0,
"react/jsx-no-undef": 0,
"react/jsx-pascal-case": 2,
"react/jsx-sort-prop-types": 0,
"react/jsx-sort-props": 0,
"react/jsx-uses-react": 2,
"react/jsx-uses-vars": 2,
"react/no-danger": 2,
"react/no-deprecated": 2,
"react/no-did-mount-set-state": 2,
"react/no-did-update-set-state": 2,
"react/no-direct-mutation-state": 2,
"react/no-is-mounted": 2,
"react/no-multi-comp": 0,
"react/no-set-state": 0,
"react/no-string-refs": 0,
"react/no-unknown-property": 2,
"react/prefer-es6-class": 2,
"react/prop-types": [
2,
{
"skipUndeclared": true
}
],
"react/react-in-jsx-scope": 2,
"react/self-closing-comp": 2,
"react/sort-comp": 0,
"react/jsx-wrap-multilines": [
2,
{
"declaration": false,
"assignment": false,
"return": true
}
]
}
},
"browserslist": [
">0.2%",
"not dead",
"not ie > 0",
"not op_mini all"
]
}

View File

@ -0,0 +1,86 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<title>Multiparty Meeting</title>
</head>
<style>
body
{
margin:auto;
padding:0.5vmin;
text-align:center;
position: fixed;
left: 50%;
top: 40%;
width: 90%;
transform: translate(-50%, 0%);
background-image: url('/images/background.svg');
background-attachment: fixed;
background-position: center;
background-size: cover;
background-repeat: repeat;
}
input:hover { opacity:0.9; }
input[type=text]
{
font-size: 1.5em;
padding: 1.5vmin;
background-color: rgba(0,0,0,0.3);
border: 0;
color: #fff;
margin: 0.8vmin;
width: 50%;
}
button:hover { background-color: #f5f5f5; }
button
{
font-size: 1.5em;
padding: 1.5vmin;
margin: 0.8vmin;
background-color: #fafafa;
border-radius: 1.8vmin;
color: #000;
border: 0;
}
img
{
height: 15vmin;
}
</style>
<body>
<a>
<img src='/images/logo.svg'></img><br />
</a>
<input id='room' type='text' onkeypress='checkEnter(event)' value='' placeholder='your room name' />
<button onclick = 'start(location.href)'>Go to room</button>
</body>
<script>
let room = document.getElementById('room');
let stateObj = { foo: 'bar' };
room.addEventListener('input', (e) =>
{
console.log(e.charCode);
history.replaceState(stateObj, 'Multiparty Meeting', '/'+room.value);
}, true);
room.focus();
function start(target)
{
location.href;history.replaceState(stateObj, 'Multiparty Meeting', '/');
window.location = target;
}
function checkEnter(event)
{
let x = event.charCode || event.keyCode;
if (x == 13 )
{
start(location.href);
}
}
</script>
</html>

View File

@ -1,11 +1,11 @@
module.exports =
var config =
{
chromeExtension : 'https://chrome.google.com/webstore/detail/fckajcjdaabdgnbdcmhhebdglogjfodi',
loginEnabled : false,
developmentPort : 3443,
turnServers : [
{
urls : [
'turn:example.com:443?transport=tcp'
'turn:turn.example.com:443?transport=tcp'
],
username : 'example',
credential : 'example'
@ -15,5 +15,6 @@ module.exports =
transportOptions :
{
tcp : true
}
},
lastN : 4
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 313 KiB

After

Width:  |  Height:  |  Size: 313 KiB

View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='utf-8' />
<meta
name='viewport'
content='width=device-width, initial-scale=1, shrink-to-fit=no'
/>
<meta name='description' content='multiparty meeting - Simple web meetings'>
<meta name='theme-color' content='#000000' />
<link rel='chrome-webstore-item' href='https://chrome.google.com/webstore/detail/fckajcjdaabdgnbdcmhhebdglogjfodi'>
<link href='https://fonts.googleapis.com/css?family=Roboto' rel='stylesheet'>
<link rel='shortcut icon' href='%PUBLIC_URL%/favicon.ico' />
<link rel='manifest' href='%PUBLIC_URL%/manifest.json' />
<title>Multiparty Meeting</title>
<script src='%PUBLIC_URL%/config.js' type='text/javascript'></script>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id='multiparty-meeting'></div>
</body>
</html>

View File

@ -0,0 +1,15 @@
{
"short_name": "Multiparty Meeting",
"name": "Multiparty Meeting - Simple web meetings",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

Binary file not shown.

View File

@ -1 +0,0 @@
<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg enable-background="new 0 0 256 256" height="256px" id="Layer_1" version="1.1" viewBox="0 0 256 256" width="256px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M76.8,217.6c0-1.637,0.625-3.274,1.875-4.524L163.75,128L78.675,42.925c-2.5-2.5-2.5-6.55,0-9.05s6.55-2.5,9.05,0 l89.601,89.6c2.5,2.5,2.5,6.551,0,9.051l-89.601,89.6c-2.5,2.5-6.55,2.5-9.05,0C77.425,220.875,76.8,219.237,76.8,217.6z"/></svg>

Before

Width:  |  Height:  |  Size: 585 B

View File

@ -1,76 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.2" width="67.32mm" height="67.32mm" viewBox="4682 4809 6732 6732" preserveAspectRatio="xMidYMid" fill-rule="evenodd" stroke-width="28.222" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" xmlns:ooo="http://xml.openoffice.org/svg/export" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:presentation="http://sun.com/xmlns/staroffice/presentation" xmlns:smil="http://www.w3.org/2001/SMIL20/" xmlns:anim="urn:oasis:names:tc:opendocument:xmlns:animation:1.0" xml:space="preserve">
<defs class="ClipPathGroup">
<clipPath id="presentation_clip_path" clipPathUnits="userSpaceOnUse">
<rect x="4682" y="4809" width="6732" height="6732"/>
</clipPath>
<clipPath id="presentation_clip_path_shrink" clipPathUnits="userSpaceOnUse">
<rect x="4688" y="4815" width="6719" height="6719"/>
</clipPath>
</defs>
<defs class="TextShapeIndex">
<g ooo:slide="id1" ooo:id-list="id3 id4 id5"/>
</defs>
<defs class="EmbeddedBulletChars">
<g id="bullet-char-template(57356)" transform="scale(0.00048828125,-0.00048828125)">
<path d="M 580,1141 L 1163,571 580,0 -4,571 580,1141 Z"/>
</g>
<g id="bullet-char-template(57354)" transform="scale(0.00048828125,-0.00048828125)">
<path d="M 8,1128 L 1137,1128 1137,0 8,0 8,1128 Z"/>
</g>
<g id="bullet-char-template(10146)" transform="scale(0.00048828125,-0.00048828125)">
<path d="M 174,0 L 602,739 174,1481 1456,739 174,0 Z M 1358,739 L 309,1346 659,739 1358,739 Z"/>
</g>
<g id="bullet-char-template(10132)" transform="scale(0.00048828125,-0.00048828125)">
<path d="M 2015,739 L 1276,0 717,0 1260,543 174,543 174,936 1260,936 717,1481 1274,1481 2015,739 Z"/>
</g>
<g id="bullet-char-template(10007)" transform="scale(0.00048828125,-0.00048828125)">
<path d="M 0,-2 C -7,14 -16,27 -25,37 L 356,567 C 262,823 215,952 215,954 215,979 228,992 255,992 264,992 276,990 289,987 310,991 331,999 354,1012 L 381,999 492,748 772,1049 836,1024 860,1049 C 881,1039 901,1025 922,1006 886,937 835,863 770,784 769,783 710,716 594,584 L 774,223 C 774,196 753,168 711,139 L 727,119 C 717,90 699,76 672,76 641,76 570,178 457,381 L 164,-76 C 142,-110 111,-127 72,-127 30,-127 9,-110 8,-76 1,-67 -2,-52 -2,-32 -2,-23 -1,-13 0,-2 Z"/>
</g>
<g id="bullet-char-template(10004)" transform="scale(0.00048828125,-0.00048828125)">
<path d="M 285,-33 C 182,-33 111,30 74,156 52,228 41,333 41,471 41,549 55,616 82,672 116,743 169,778 240,778 293,778 328,747 346,684 L 369,508 C 377,444 397,411 428,410 L 1163,1116 C 1174,1127 1196,1133 1229,1133 1271,1133 1292,1118 1292,1087 L 1292,965 C 1292,929 1282,901 1262,881 L 442,47 C 390,-6 338,-33 285,-33 Z"/>
</g>
<g id="bullet-char-template(9679)" transform="scale(0.00048828125,-0.00048828125)">
<path d="M 813,0 C 632,0 489,54 383,161 276,268 223,411 223,592 223,773 276,916 383,1023 489,1130 632,1184 813,1184 992,1184 1136,1130 1245,1023 1353,916 1407,772 1407,592 1407,412 1353,268 1245,161 1136,54 992,0 813,0 Z"/>
</g>
<g id="bullet-char-template(8226)" transform="scale(0.00048828125,-0.00048828125)">
<path d="M 346,457 C 273,457 209,483 155,535 101,586 74,649 74,723 74,796 101,859 155,911 209,963 273,989 346,989 419,989 480,963 531,910 582,859 608,796 608,723 608,648 583,586 532,535 482,483 420,457 346,457 Z"/>
</g>
<g id="bullet-char-template(8211)" transform="scale(0.00048828125,-0.00048828125)">
<path d="M -4,459 L 1135,459 1135,606 -4,606 -4,459 Z"/>
</g>
<g id="bullet-char-template(61548)" transform="scale(0.00048828125,-0.00048828125)">
<path d="M 173,740 C 173,903 231,1043 346,1159 462,1274 601,1332 765,1332 928,1332 1067,1274 1183,1159 1299,1043 1357,903 1357,740 1357,577 1299,437 1183,322 1067,206 928,148 765,148 601,148 462,206 346,322 231,437 173,577 173,740 Z"/>
</g>
</defs>
<defs class="TextEmbeddedBitmaps"/>
<g class="SlideGroup">
<g>
<g id="container-id1">
<g id="id1" class="Slide" clip-path="url(#presentation_clip_path)">
<g class="Page">
<g class="com.sun.star.drawing.LineShape">
<g id="id3">
<rect class="BoundingBox" stroke="none" fill="none" x="6253" y="6380" width="3525" height="3525"/>
<path fill="none" stroke="rgb(204,0,0)" stroke-width="1016" stroke-linejoin="round" stroke-linecap="round" d="M 6761,9395 L 9268,6888"/>
</g>
</g>
<g class="com.sun.star.drawing.LineShape">
<g id="id4">
<rect class="BoundingBox" stroke="none" fill="none" x="6253" y="6398" width="3525" height="3526"/>
<path fill="none" stroke="rgb(204,0,0)" stroke-width="1016" stroke-linejoin="round" stroke-linecap="round" d="M 9269,9414 L 6762,6907"/>
</g>
</g>
<g class="com.sun.star.drawing.CustomShape">
<g id="id5">
<rect class="BoundingBox" stroke="none" fill="none" x="4681" y="4808" width="6734" height="6734"/>
<path fill="none" stroke="rgb(204,0,0)" stroke-width="508" stroke-linejoin="round" d="M 8047,5063 C 9811,5063 11159,6410 11159,8174 11159,9938 9811,11286 8047,11286 6283,11286 4936,9938 4936,8174 4936,6410 6283,5063 8047,5063 Z"/>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 5.1 KiB

View File

@ -1,37 +0,0 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" width="512px" height="512px" viewBox="0 0 511.626 511.626" style="enable-background:new 0 0 511.626 511.626;" xml:space="preserve">
<g>
<path d="M477.371,127.44c-22.843-28.074-53.871-50.249-93.076-66.523c-39.204-16.272-82.035-24.41-128.478-24.41 c-34.643,0-67.762,4.805-99.357,14.417c-31.595,9.611-58.812,22.602-81.653,38.97c-22.845,16.37-41.018,35.832-54.534,58.385 C6.757,170.833,0,194.484,0,219.228c0,28.549,8.61,55.3,25.837,80.234c17.227,24.931,40.778,45.871,70.664,62.811 c-2.096,7.611-4.57,14.846-7.426,21.693c-2.855,6.852-5.424,12.474-7.708,16.851c-2.286,4.377-5.376,9.233-9.281,14.562 c-3.899,5.328-6.849,9.089-8.848,11.275c-1.997,2.19-5.28,5.812-9.851,10.849c-4.565,5.048-7.517,8.329-8.848,9.855 c-0.193,0.089-0.953,0.952-2.285,2.567c-1.331,1.615-1.999,2.423-1.999,2.423l-1.713,2.566c-0.953,1.431-1.381,2.334-1.287,2.707 c0.096,0.373-0.094,1.331-0.57,2.851c-0.477,1.526-0.428,2.669,0.142,3.433v0.284c0.765,3.429,2.43,6.187,4.998,8.277 c2.568,2.092,5.474,2.95,8.708,2.563c12.375-1.522,23.223-3.606,32.548-6.276c49.87-12.758,93.649-35.782,131.334-69.097 c14.272,1.522,28.072,2.286,41.396,2.286c46.442,0,89.271-8.138,128.479-24.417c39.208-16.272,70.233-38.448,93.072-66.517 c22.843-28.062,34.263-58.663,34.263-91.781C511.626,186.108,500.207,155.509,477.371,127.44z" fill="#FFFFFF"/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -1,4 +0,0 @@
<svg fill="#FFFFFF" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
<path d="M0 0h24v24H0z" fill="none"/>
</svg>

Before

Width:  |  Height:  |  Size: 265 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 861 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 365 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 946 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 632 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 917 B

View File

@ -1,4 +0,0 @@
<svg fill="#FFFFFF" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M17 3h-1v5h1V3zm-2 2h-2V4h2V3h-3v3h2v1h-2v1h3V5zm3-2v5h1V6h2V3h-3zm2 2h-1V4h1v1zm0 10.5c-1.25 0-2.45-.2-3.57-.57-.35-.11-.74-.03-1.01.24l-2.2 2.2c-2.83-1.44-5.15-3.75-6.59-6.59l2.2-2.21c.27-.26.35-.65.24-1C8.7 6.45 8.5 5.25 8.5 4c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1 0 9.39 7.61 17 17 17 .55 0 1-.45 1-1v-3.5c0-.55-.45-1-1-1z"/>
</svg>

Before

Width:  |  Height:  |  Size: 487 B

View File

@ -1,4 +0,0 @@
<svg fill="#FFFFFF" fill-opacity="0.5" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M11 18h2v-2h-2v2zm1-16C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm0-14c-2.21 0-4 1.79-4 4h2c0-1.1.9-2 2-2s2 .9 2 2c0 2-3 1.75-3 5h2c0-2.25 3-2.5 3-5 0-2.21-1.79-4-4-4z"/>
</svg>

Before

Width:  |  Height:  |  Size: 427 B

View File

@ -1,4 +0,0 @@
<svg fill="#FFF" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M19 12v7H5v-7H3v7c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2v-7h-2zm-6 .67l2.59-2.58L17 11.5l-5 5-5-5 1.41-1.41L11 12.67V3h2z"/>
<path fill="none" d="M0 0h24v24H0z"/>
</svg>

Before

Width:  |  Height:  |  Size: 276 B

View File

@ -1,4 +0,0 @@
<svg fill="#000000" height="48" viewBox="0 0 24 24" width="48" xmlns="http://www.w3.org/2000/svg">
<path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/>
<path d="M0 0h24v24H0z" fill="none"/>
</svg>

Before

Width:  |  Height:  |  Size: 351 B

View File

@ -1,4 +0,0 @@
<svg fill="#FFFFFF" height="48" viewBox="0 0 24 24" width="48" xmlns="http://www.w3.org/2000/svg">
<path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/>
<path d="M0 0h24v24H0z" fill="none"/>
</svg>

Before

Width:  |  Height:  |  Size: 352 B

View File

@ -1,4 +0,0 @@
<svg fill="#000000" height="36" viewBox="0 0 24 24" width="36" xmlns="http://www.w3.org/2000/svg">
<path d="M19 8l-4 4h3c0 3.31-2.69 6-6 6-1.01 0-1.97-.25-2.8-.7l-1.46 1.46C8.97 19.54 10.43 20 12 20c4.42 0 8-3.58 8-8h3l-4-4zM6 12c0-3.31 2.69-6 6-6 1.01 0 1.97.25 2.8.7l1.46-1.46C15.03 4.46 13.57 4 12 4c-4.42 0-8 3.58-8 8H1l4 4 4-4H6z"/>
<path d="M0 0h24v24H0z" fill="none"/>
</svg>

Before

Width:  |  Height:  |  Size: 390 B

View File

@ -1,4 +0,0 @@
<svg fill="#FFFFFF" fill-opacity="0.5" height="36" viewBox="0 0 24 24" width="36" xmlns="http://www.w3.org/2000/svg">
<path d="M19 8l-4 4h3c0 3.31-2.69 6-6 6-1.01 0-1.97-.25-2.8-.7l-1.46 1.46C8.97 19.54 10.43 20 12 20c4.42 0 8-3.58 8-8h3l-4-4zM6 12c0-3.31 2.69-6 6-6 1.01 0 1.97.25 2.8.7l1.46-1.46C15.03 4.46 13.57 4 12 4c-4.42 0-8 3.58-8 8H1l4 4 4-4H6z"/>
<path d="M0 0h24v24H0z" fill="none"/>
</svg>

Before

Width:  |  Height:  |  Size: 410 B

View File

@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/>
</svg>

Before

Width:  |  Height:  |  Size: 228 B

View File

@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"/>
</svg>

Before

Width:  |  Height:  |  Size: 227 B

View File

@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#FFF">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"/>
</svg>

Before

Width:  |  Height:  |  Size: 239 B

View File

@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#FFF">
<path d="M0 0h24v24H0z" fill="none" />
<path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z" />
</svg>

Before

Width:  |  Height:  |  Size: 242 B

View File

@ -1,4 +0,0 @@
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24px" height="24px">
<path d="M 12 1 C 8.6761905 1 6 3.6761905 6 7 L 6 8 C 4.9 8 4 8.9 4 10 L 4 20 C 4 21.1 4.9 22 6 22 L 18 22 C 19.1 22 20 21.1 20 20 L 20 10 C 20 8.9 19.1 8 18 8 L 18 7 C 18 3.6761905 15.32381 1 12 1 z M 12 3 C 14.27619 3 16 4.7238095 16 7 L 16 8 L 8 8 L 8 7 C 8 4.7238095 9.7238095 3 12 3 z M 12 13 C 13.1 13 14 13.9 14 15 C 14 16.1 13.1 17 12 17 C 10.9 17 10 16.1 10 15 C 10 13.9 10.9 13 12 13 z" fill="#FFFFFF"/>
</svg>

Before

Width:  |  Height:  |  Size: 535 B

View File

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/>
</svg>

Before

Width:  |  Height:  |  Size: 217 B

View File

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z" fill="#FFFFFF"/>
</svg>

Before

Width:  |  Height:  |  Size: 232 B

View File

@ -1,4 +0,0 @@
<svg fill="#000000" height="36" viewBox="0 0 24 24" width="36" xmlns="http://www.w3.org/2000/svg">
<path d="M12 14c1.66 0 2.99-1.34 2.99-3L15 5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5.3-3c0 3-2.54 5.1-5.3 5.1S6.7 14 6.7 11H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c3.28-.48 6-3.3 6-6.72h-1.7z"/>
<path d="M0 0h24v24H0z" fill="none"/>
</svg>

Before

Width:  |  Height:  |  Size: 355 B

View File

@ -1,4 +0,0 @@
<svg fill="#FFFFFF" height="36" viewBox="0 0 24 24" width="36" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0h24v24H0zm0 0h24v24H0z" fill="none"/>
<path d="M19 11h-1.7c0 .74-.16 1.43-.43 2.05l1.23 1.23c.56-.98.9-2.09.9-3.28zm-4.02.17c0-.06.02-.11.02-.17V5c0-1.66-1.34-3-3-3S9 3.34 9 5v.18l5.98 5.99zM4.27 3L3 4.27l6.01 6.01V11c0 1.66 1.33 3 2.99 3 .22 0 .44-.03.65-.08l1.66 1.66c-.71.33-1.5.52-2.31.52-2.76 0-5.3-2.1-5.3-5.1H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c.91-.13 1.77-.45 2.54-.9L19.73 21 21 19.73 4.27 3z"/>
</svg>

Before

Width:  |  Height:  |  Size: 534 B

View File

@ -1,4 +0,0 @@
<svg fill="#FFFFFF" fill-opacity="0.5" height="36" viewBox="0 0 24 24" width="36" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0h24v24H0zm0 0h24v24H0z" fill="none"/>
<path d="M19 11h-1.7c0 .74-.16 1.43-.43 2.05l1.23 1.23c.56-.98.9-2.09.9-3.28zm-4.02.17c0-.06.02-.11.02-.17V5c0-1.66-1.34-3-3-3S9 3.34 9 5v.18l5.98 5.99zM4.27 3L3 4.27l6.01 6.01V11c0 1.66 1.33 3 2.99 3 .22 0 .44-.03.65-.08l1.66 1.66c-.71.33-1.5.52-2.31.52-2.76 0-5.3-2.1-5.3-5.1H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c.91-.13 1.77-.45 2.54-.9L19.73 21 21 19.73 4.27 3z"/>
</svg>

Before

Width:  |  Height:  |  Size: 553 B

View File

@ -1,8 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<g>
<path
d="M23.78700473 7.7610694C23.78700473 7.7610694 19.32738673 2.3867608 19.32738673 2.3867608C19.13984773 2.1607585 18.91713373 2.0882379 18.63693973 2.2176245C18.36703673 2.3422016 18.22715573 2.5841415 18.22715573 2.9368427C18.22715573 2.9368427 18.22715573 5.6735307 18.22715573 5.6735307C16.60026573 6.0262319 15.09099273 6.6909889 13.72054573 7.6906705C12.03598073 8.9196554 10.69300173 10.4408914 9.74082913 12.3174694C9.56878473 12.6565904 9.61472063 13.0060384 9.85793833 13.2765524C10.07145773 13.5142954 10.41981173 13.5447094 10.65379573 13.3611024C10.66553573 13.3469524 10.66553573 13.3469524 10.67727573 13.3469524C10.84110373 13.2202534 11.00363973 13.1043994 11.15725573 13.0083964C11.31983073 12.9069714 11.66067273 12.7122784 12.18733073 12.4444524C12.71395073 12.1762954 13.21736773 11.9365244 13.72058873 11.7532424C14.22400573 11.5696774 14.85607473 11.3852164 15.59343573 11.2311224C16.34257473 11.0749524 17.07997473 10.9913044 17.79417273 10.9913044C17.79417273 10.9913044 18.22715973 10.9913044 18.22715973 10.9913044C18.22715973 10.9913044 18.22715973 13.6574994 18.22715973 13.6574994C18.22715973 14.0102004 18.36723673 14.2509614 18.63694373 14.3767174C18.73010373 14.4203774 18.81250973 14.4332974 18.87092773 14.4332974C19.04645473 14.4332974 19.19826973 14.3622874 19.32739073 14.2076254C19.32739073 14.2076254 23.78708673 8.8613254 23.78708673 8.8613254C24.03351273 8.5660564 24.03300473 8.0574684 23.78700873 7.7610674C23.78700873 7.7610674 23.78700873 7.7610674 23.78700873 7.7610674M19.51465573 11.7673384C19.51465573 11.7673384 19.51465573 10.2720364 19.51465573 10.2720364C19.51465573 9.8630814 19.26897373 9.5103804 18.94108373 9.4962344C18.68362273 9.4537944 18.29731573 9.4396544 17.79417273 9.4396544C16.12032873 9.4396544 14.43478673 9.7782104 12.76090373 10.4694194C14.54011773 8.6497074 16.61200773 7.5365769 18.96455973 7.1263967C19.28071173 7.0713227 19.51473473 6.7454039 19.51473473 6.3364497C19.51473473 6.3364497 19.51473473 4.8128558 19.51473473 4.8128558C19.51473473 4.8128558 22.41761773 8.3111524 22.41761773 8.3111524C22.41761773 8.3111524 19.51465573 11.7673414 19.51465573 11.7673414" />
<path
d="M17.17360373 20.0049884C17.17360373 20.0615284 17.10341373 20.1461164 17.04499073 20.1461164C17.04499073 20.1461164 1.44180703 20.1461164 1.44180703 20.1461164C1.35991303 20.1461164 1.32469783 20.1036764 1.32469783 20.0049884C1.32469783 20.0049884 1.32469783 6.3082043 1.32469783 6.3082043C1.32469783 6.2095143 1.35991283 6.1670295 1.44180703 6.1670295C1.44180703 6.1670295 14.34107173 6.1670295 14.34107173 6.1670295C14.34107173 6.1670295 14.34107173 4.5871826 14.34107173 4.5871826C14.34107173 4.5871826 1.44180703 4.5871826 1.44180703 4.5871826C0.70440653 4.5871826 0.10735711 5.2784393 0.02542373 6.139068C0.02542373 6.139068 0.02542373 6.3082043 0.02542373 6.3082043C0.02542373 6.3082043 0.02542373 20.0050354 0.02542373 20.0050354C0.02542373 20.0050354 0.02542373 20.1744544 0.02542373 20.1744544C0.10731799 21.0348004 0.70440653 21.7118644 1.44180703 21.7118644C1.44180703 21.7118644 17.04499073 21.7118644 17.04499073 21.7118644C17.44280173 21.7118644 17.78516873 21.5460284 18.05162873 21.2180354C18.32380073 20.8830634 18.46141273 20.4705254 18.46141273 20.0049884C18.46141273 20.0049884 18.46141273 15.3501814 18.46141273 15.3501814C18.46141273 15.3501814 17.17364273 15.3501814 17.17364273 15.3501814C17.17364273 15.3501814 17.17364273 20.0049884 17.17364273 20.0049884C17.17364273 20.0049884 17.17360373 20.0049884 17.17360373 20.0049884" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -1,4 +0,0 @@
<svg fill="#FFFFFF" height="36" viewBox="0 0 24 24" width="36" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/>
</svg>

Before

Width:  |  Height:  |  Size: 214 B

View File

@ -1,4 +0,0 @@
<svg fill="#FFFFFF" height="36" viewBox="0 0 24 24" width="36" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M11 17h2v-6h-2v6zm1-15C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zM11 9h2V7h-2v2z"/>
</svg>

Before

Width:  |  Height:  |  Size: 320 B

View File

@ -1,4 +0,0 @@
<svg fill="#FFFFFF" height="36" viewBox="0 0 24 24" width="36" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0h24v24H0zm0 0h24v24H0z" fill="none"/>
<path d="M19 11h-1.7c0 .74-.16 1.43-.43 2.05l1.23 1.23c.56-.98.9-2.09.9-3.28zm-4.02.17c0-.06.02-.11.02-.17V5c0-1.66-1.34-3-3-3S9 3.34 9 5v.18l5.98 5.99zM4.27 3L3 4.27l6.01 6.01V11c0 1.66 1.33 3 2.99 3 .22 0 .44-.03.65-.08l1.66 1.66c-.71.33-1.5.52-2.31.52-2.76 0-5.3-2.1-5.3-5.1H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c.91-.13 1.77-.45 2.54-.9L19.73 21 21 19.73 4.27 3z"/>
</svg>

Before

Width:  |  Height:  |  Size: 534 B

View File

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 96 96"
style="enable-background:new 0 0 96 96;"
xml:space="preserve">
<metadata
id="metadata11"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata>
<defs
id="defs9" />
<path
style="fill:#ffffff;stroke-width:0.40677965"
d="m 33.894283,77.837288 c -1.428534,-1.845763 -3.909722,-5.220659 -5.513751,-7.499764 -1.60403,-2.279109 -4.323663,-5.940126 -6.043631,-8.135593 -5.698554,-7.273973 -6.224902,-8.044795 -6.226676,-9.118803 -0.0034,-2.075799 2.81181,-4.035355 4.9813,-3.467247 0.50339,0.131819 2.562712,1.72771 4.576272,3.546423 4.238418,3.828283 6.617166,5.658035 7.355654,5.658035 0.82497,0 1.045415,-1.364294 0.567453,-3.511881 C 33.348583,54.219654 31.1088,48.20339 28.613609,41.938983 23.524682,29.162764 23.215312,27.731034 25.178629,26.04226 c 2.443255,-2.101599 4.670178,-1.796504 6.362271,0.87165 0.639176,1.007875 2.666245,5.291978 4.504599,9.520229 1.838354,4.228251 3.773553,8.092718 4.300442,8.587705 l 0.957981,0.899977 0.419226,-1.102646 c 0.255274,-0.671424 0.419225,-6.068014 0.419225,-13.799213 0,-13.896836 -0.0078,-13.84873 2.44517,-15.1172 1.970941,-1.019214 4.2259,-0.789449 5.584354,0.569005 l 1.176852,1.176852 0.483523,11.738402 c 0.490017,11.896027 0.826095,14.522982 1.911266,14.939402 1.906224,0.731486 2.21601,-0.184677 4.465407,-13.206045 1.239206,-7.173539 1.968244,-10.420721 2.462128,-10.966454 1.391158,-1.537215 4.742705,-1.519809 6.295208,0.03269 1.147387,1.147388 1.05469,3.124973 -0.669503,14.283063 -0.818745,5.298489 -1.36667,10.090163 -1.220432,10.67282 0.14596,0.581557 0.724796,1.358395 1.286298,1.726306 0.957759,0.627548 1.073422,0.621575 1.86971,-0.09655 0.466837,-0.421011 1.761787,-2.595985 2.877665,-4.833273 2.564176,-5.141059 3.988466,-6.711864 6.085822,-6.711864 2.769954,0 3.610947,2.927256 2.139316,7.446329 C 78.799497,44.318351 66.752066,77.28024 65.51653,80.481356 65.262041,81.140709 64.18139,81.19322 50.866695,81.19322 H 36.491617 Z"
id="path3710"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -1,4 +0,0 @@
<svg fill="#FFFFFF" height="36" viewBox="0 0 24 24" width="36" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0h24v24H0zm0 0h24v24H0z" fill="none"/>
<path d="M21 6.5l-4 4V7c0-.55-.45-1-1-1H9.82L21 17.18V6.5zM3.27 2L2 3.27 4.73 6H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.21 0 .39-.08.54-.18L19.73 21 21 19.73 3.27 2z"/>
</svg>

Before

Width:  |  Height:  |  Size: 335 B

View File

@ -1,4 +0,0 @@
<svg fill="#FFFFFF" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M1 9l2 2c4.97-4.97 13.03-4.97 18 0l2-2C16.93 2.93 7.08 2.93 1 9zm8 8l3 3 3-3c-1.65-1.66-4.34-1.66-6 0zm-4-4l2 2c2.76-2.76 7.24-2.76 10 0l2-2C15.14 9.14 8.87 9.14 5 13z"/>
</svg>

Before

Width:  |  Height:  |  Size: 332 B

View File

@ -1,4 +0,0 @@
<svg fill="#000000" height="48" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path fill="none" d="M0 0h20v20H0V0z"/>
<path d="M15.95 10.78c.03-.25.05-.51.05-.78s-.02-.53-.06-.78l1.69-1.32c.15-.12.19-.34.1-.51l-1.6-2.77c-.1-.18-.31-.24-.49-.18l-1.99.8c-.42-.32-.86-.58-1.35-.78L12 2.34c-.03-.2-.2-.34-.4-.34H8.4c-.2 0-.36.14-.39.34l-.3 2.12c-.49.2-.94.47-1.35.78l-1.99-.8c-.18-.07-.39 0-.49.18l-1.6 2.77c-.1.18-.06.39.1.51l1.69 1.32c-.04.25-.07.52-.07.78s.02.53.06.78L2.37 12.1c-.15.12-.19.34-.1.51l1.6 2.77c.1.18.31.24.49.18l1.99-.8c.42.32.86.58 1.35.78l.3 2.12c.04.2.2.34.4.34h3.2c.2 0 .37-.14.39-.34l.3-2.12c.49-.2.94-.47 1.35-.78l1.99.8c.18.07.39 0 .49-.18l1.6-2.77c.1-.18.06-.39-.1-.51l-1.67-1.32zM10 13c-1.65 0-3-1.35-3-3s1.35-3 3-3 3 1.35 3 3-1.35 3-3 3z"/>
</svg>

Before

Width:  |  Height:  |  Size: 790 B

View File

@ -1,3 +0,0 @@
<svg fill="#FFFFFF" height="24" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path d="M15.95 10.78c.03-.25.05-.51.05-.78s-.02-.53-.06-.78l1.69-1.32c.15-.12.19-.34.1-.51l-1.6-2.77c-.1-.18-.31-.24-.49-.18l-1.99.8c-.42-.32-.86-.58-1.35-.78L12 2.34c-.03-.2-.2-.34-.4-.34H8.4c-.2 0-.36.14-.39.34l-.3 2.12c-.49.2-.94.47-1.35.78l-1.99-.8c-.18-.07-.39 0-.49.18l-1.6 2.77c-.1.18-.06.39.1.51l1.69 1.32c-.04.25-.07.52-.07.78s.02.53.06.78L2.37 12.1c-.15.12-.19.34-.1.51l1.6 2.77c.1.18.31.24.49.18l1.99-.8c.42.32.86.58 1.35.78l.3 2.12c.04.2.2.34.4.34h3.2c.2 0 .37-.14.39-.34l.3-2.12c.49-.2.94-.47 1.35-.78l1.99.8c.18.07.39 0 .49-.18l1.6-2.77c.1-.18.06-.39-.1-.51l-1.67-1.32zM10 13c-1.65 0-3-1.35-3-3s1.35-3 3-3 3 1.35 3 3-1.35 3-3 3z"/>
</svg>

Before

Width:  |  Height:  |  Size: 746 B

View File

@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 0 24 24" width="48">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"/>
</svg>

Before

Width:  |  Height:  |  Size: 195 B

View File

@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="#FFFFFF" height="48" viewBox="0 0 24 24" width="48">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"/>
</svg>

Before

Width:  |  Height:  |  Size: 210 B

View File

@ -1,4 +0,0 @@
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24px" height="24px">
<path style="line-height:normal;text-indent:0;text-align:start;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000;text-transform:none;block-progression:tb;isolation:auto;mix-blend-mode:normal" d="M 12 1 C 9.1277778 1 6.7189086 3.0461453 6.1230469 5.7871094 L 8.078125 6.2128906 C 8.4822632 4.3538547 10.072222 3 12 3 C 14.27619 3 16 4.7238095 16 7 L 16 8 L 6 8 C 4.9 8 4 8.9 4 10 L 4 20 C 4 21.1 4.9 22 6 22 L 18 22 C 19.1 22 20 21.1 20 20 L 20 10 C 20 8.9 19.1 8 18 8 L 18 7 C 18 3.6761905 15.32381 1 12 1 z M 12 13 C 13.1 13 14 13.9 14 15 C 14 16.1 13.1 17 12 17 C 10.9 17 10 16.1 10 15 C 10 13.9 10.9 13 12 13 z" font-weight="400" font-family="sans-serif" white-space="normal" overflow="visible" fill="#FFFFFF"/>
</svg>

Before

Width:  |  Height:  |  Size: 867 B

View File

@ -1,4 +0,0 @@
<svg fill="#000000" height="36" viewBox="0 0 24 24" width="36" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M17 10.5V7c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-3.5l4 4v-11l-4 4z"/>
</svg>

Before

Width:  |  Height:  |  Size: 265 B

View File

@ -1,4 +0,0 @@
<svg fill="#FFFFFF" height="36" viewBox="0 0 24 24" width="36" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M17 10.5V7c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-3.5l4 4v-11l-4 4z"/>
</svg>

Before

Width:  |  Height:  |  Size: 266 B

View File

@ -1,4 +0,0 @@
<svg fill="#FFFFFF" fill-opacity="0.5" height="36" viewBox="0 0 24 24" width="36" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0h24v24H0zm0 0h24v24H0z" fill="none"/>
<path d="M21 6.5l-4 4V7c0-.55-.45-1-1-1H9.82L21 17.18V6.5zM3.27 2L2 3.27 4.73 6H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.21 0 .39-.08.54-.18L19.73 21 21 19.73 3.27 2z"/>
</svg>

Before

Width:  |  Height:  |  Size: 354 B

View File

@ -1,38 +0,0 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" width="512px" height="512px" viewBox="0 0 435.933 435.933" style="enable-background:new 0 0 435.933 435.933;" xml:space="preserve">
<g>
<path d="M286.482,279.088c0.944,1.249,0.939,2.973-0.023,4.225l-65.723,85.483c-0.658,0.867-1.682,1.368-2.762,1.368 c-1.081,0-2.102-0.501-2.764-1.368l-65.727-85.483c-0.476-0.621-0.722-1.368-0.722-2.12c0-0.741,0.236-1.485,0.705-2.104 c0.941-1.253,2.607-1.724,4.069-1.137l43.516,17.176V194.393c0-1.926,1.556-3.48,3.486-3.48h34.877c1.92,0,3.485,1.554,3.485,3.48 v100.74l43.516-17.177C283.873,277.37,285.54,277.841,286.482,279.088z M196.93,65.927C-28.392,70.252-1.475,181.812,3.69,198.193 c5.532,27.833,26.852,41.76,48.488,33.864l41.166-12.881c22.179-8.096,36.25-38.272,31.408-67.418l-0.917-5.518 c74.849-21.183,139.407-15.746,189.768-1.727l-0.396,2.353c-4.819,29.149,9.231,59.333,31.423,67.423l41.164,17.16 c18.604,6.791,36.965-6.287,45.271-27.171c0.107,0.09,0.173,0.15,0.173,0.15s1.139-2.812,2.288-7.625 c0.093-0.375,0.163-0.786,0.262-1.155c0.191-0.881,0.383-1.792,0.575-2.785c0.115-0.561,0.246-1.092,0.353-1.658l-0.041-0.035 C440.692,155.657,434.471,61.393,196.93,65.927z" fill="#D80027"/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 96 96" style="enable-background:new 0 0 96 96;" xml:space="preserve">
<style type="text/css">
.st0{fill:#000000;}
</style>
<g id="XMLID_2_">
<path id="XMLID_8_" class="st0" d="M69.1,61.3l4.6,4.6h1.8v-4.6H69.1z M70.9,56.7l0-22.9c0-2.5-2.1-4.6-4.6-4.6H37l12,12
c0.4-0.1,0.8-0.2,1.3-0.2v-4.9l9.2,8.5L55.8,48l12.7,12.7C69.9,59.9,70.9,58.4,70.9,56.7z M26,23.9L23,26.8l3.5,3.5
c-0.9,0.8-1.5,2-1.5,3.4v22.9c0,2.5,2,4.6,4.6,4.6h-9.2v4.6H62l6.2,6.2l2.9-2.9L26,23.9z M36.5,54.4c0.7-3.4,2.1-6.8,4.7-9.3
l3.6,3.6C41.4,49.6,38.7,51.4,36.5,54.4z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 733 B

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 96 96" style="enable-background:new 0 0 96 96;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<g id="XMLID_2_">
<path id="XMLID_8_" class="st0" d="M69.1,61.3l4.6,4.6h1.8v-4.6H69.1z M70.9,56.7l0-22.9c0-2.5-2.1-4.6-4.6-4.6H37l12,12
c0.4-0.1,0.8-0.2,1.3-0.2v-4.9l9.2,8.5L55.8,48l12.7,12.7C69.9,59.9,70.9,58.4,70.9,56.7z M26,23.9L23,26.8l3.5,3.5
c-0.9,0.8-1.5,2-1.5,3.4v22.9c0,2.5,2,4.6,4.6,4.6h-9.2v4.6H62l6.2,6.2l2.9-2.9L26,23.9z M36.5,54.4c0.7-3.4,2.1-6.8,4.7-9.3
l3.6,3.6C41.4,49.6,38.7,51.4,36.5,54.4z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 733 B

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Tiny//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="tiny" x="0px" y="0px" width="480px" height="480px" viewBox="0 0 480 480" xml:space="preserve">
<g id="all_friends">
<path d="M185.161,285.818c-9.453-7.317-17-18.963-20.438-28.691c-0.406-0.371-0.949-0.846-1.373-1.23 c-5.297-4.638-15.156-13.284-15.156-32.656c0-9.143,1.748-15.652,5.514-20.48c0.518-0.651,1.174-1.407,1.984-2.188v-19.394 c0-23.428,16.861-54.488,53.471-65.856c-6.391-14.644-22.232-30.543-53.656-30.543c-43.455,0-57.205,30.422-57.205,45.851 c0,8.313,0,22.479,0,23.514c0,1.049-6.188-2.826-6.188,11.153c0,15.946,10.752,17.047,12.658,23.061 c3.375,10.691,13.877,22.314,20.826,22.314c0,0,0.531,2.209,0.531,6.278c0,3.296-0.078,3.24-2.402,3.24 c-37.77,0-63.721,39.909-63.721,52.653c0,16.292,0,34.201,0,34.201h80.076C153.303,297.144,168.805,289.123,185.161,285.818z"/>
<path d="M356.256,220.19c-2.312,0-2.408,0.056-2.408-3.24c0-4.069,0.531-6.278,0.531-6.278c6.955,0,17.469-11.623,20.844-22.314 c1.906-6.014,12.658-7.114,12.658-23.061c0-13.979-6.174-10.104-6.174-11.153c0-1.035,0-15.2,0-23.514 c0-15.429-13.781-45.851-57.232-45.851c-31.486,0-47.342,15.985-53.701,30.659c36.389,11.459,53.154,42.386,53.154,65.74v19.397 c4.125,3.957,7.486,10.812,7.486,22.664c0,19.377-9.859,28.022-15.158,32.661c-0.439,0.384-0.971,0.854-1.375,1.229 c-3.422,9.733-10.986,21.37-20.439,28.688c16.379,3.313,31.877,11.334,45.078,21.227h80.488c0,0,0-17.909,0-34.201 C420.008,260.1,394.04,220.19,356.256,220.19z"/>
<path d="M278.368,298.86c-2.814,0-2.926-8.948-2.926-12.952c0-4.927,0.643-7.619,0.643-7.619c8.439,0,21.189-14.091,25.283-27.064 c2.312-7.296,15.355-8.634,15.355-27.984c0-16.956-7.498-12.257-7.498-13.525c0-1.26,0-18.445,0-28.536 c0-18.712-16.703-55.618-69.422-55.618c-52.705,0-69.406,36.906-69.406,55.618c0,10.091,0,27.276,0,28.536 c0,1.269-7.516-3.431-7.516,13.525c0,19.351,13.047,20.688,15.359,27.984c4.107,12.974,16.844,27.064,25.283,27.064 c0,0,0.639,2.692,0.639,7.619c0,4.004-0.096,12.952-2.922,12.952c-45.811,0-86.328,48.422-86.328,63.89c0,19.765,0,32.467,0,32.467 h124.891h124.893c0,0,0-12.702,0-32.467C364.696,347.282,324.178,298.86,278.368,298.86z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -1,4 +0,0 @@
<svg fill="#FFF" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M17 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V7l-4-4zm-5 16c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm3-10H5V5h10v4z"/>
</svg>

Before

Width:  |  Height:  |  Size: 304 B

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 96 96" style="enable-background:new 0 0 96 96;" xml:space="preserve">
<style type="text/css">
.st0{fill:#000000;}
</style>
<g id="XMLID_2_">
<path id="XMLID_6_" class="st0" d="M66.3,61.8c2.5,0,4.6-2.1,4.6-4.6l0-22.9c0-2.5-2.1-4.6-4.6-4.6H29.7c-2.5,0-4.6,2-4.6,4.6v22.9
c0,2.5,2,4.6,4.6,4.6h-9.2v4.6h55v-4.6H66.3z M50.3,53.7v-5c-6.4,0-10.6,1.9-13.8,6.2c1.3-6.1,4.8-12.2,13.8-13.5v-4.9l9.2,8.5
L50.3,53.7z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 603 B

View File

@ -1,53 +0,0 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 83.208 83.208" style="enable-background:new 0 0 83.208 83.208;" xml:space="preserve">
<g>
<g>
<g>
<polygon style="fill:#D80027;" points="25.052,69.154 17.894,76.312 53.683,76.312 46.525,69.154 "/>
</g>
<g>
<path style="fill:#D80027;" d="M64.419,6.896c-7.831,0-14.537,4.814-17.357,11.631H0v46.525h71.577v-22.01
c6.814-2.82,11.631-9.53,11.631-17.357C83.208,15.325,74.78,6.896,64.419,6.896z M64.419,42.685c-9.373,0-17-7.627-17-17
s7.627-17,17-17s17,7.627,17,17S73.792,42.685,64.419,42.685z"/>
</g>
<g>
<polygon style="fill:#D80027;" points="66.338,29.372 67.068,14.258 61.764,14.258 62.533,29.372 "/>
</g>
<g>
<path style="fill:#D80027;" d="M64.434,31.039c-1.764,0-3.003,1.267-3.003,3.035c0,1.732,1.199,3.035,3.003,3.035
c1.804,0,2.97-1.303,2.97-3.035C67.368,32.306,66.202,31.039,64.434,31.039z"/>
</g>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 96 96" style="enable-background:new 0 0 96 96;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<g id="XMLID_2_">
<path id="XMLID_6_" class="st0" d="M66.3,61.8c2.5,0,4.6-2.1,4.6-4.6l0-22.9c0-2.5-2.1-4.6-4.6-4.6H29.7c-2.5,0-4.6,2-4.6,4.6v22.9
c0,2.5,2,4.6,4.6,4.6h-9.2v4.6h55v-4.6H66.3z M50.3,53.7v-5c-6.4,0-10.6,1.9-13.8,6.2c1.3-6.1,4.8-12.2,13.8-13.5v-4.9l9.2,8.5
L50.3,53.7z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 603 B

View File

@ -1,4 +0,0 @@
<svg fill="#FFFFFF" height="36" viewBox="0 0 24 24" width="36" xmlns="http://www.w3.org/2000/svg">
<path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9 17H7v-7h2v7zm4 0h-2V7h2v10zm4 0h-2v-4h2v4z"/>
<path d="M0 0h24v24H0z" fill="none"/>
</svg>

Before

Width:  |  Height:  |  Size: 285 B

View File

@ -1 +0,0 @@
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var n;n="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,n.antiglobal=e()}}(function(){return function e(n,o,t){function r(i,u){if(!o[i]){if(!n[i]){var l="function"==typeof require&&require;if(!u&&l)return l(i,!0);if(f)return f(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var d=o[i]={exports:{}};n[i][0].call(d.exports,function(e){var o=n[i][1][e];return r(o?o:e)},d,d.exports,e,n,o,t)}return o[i].exports}for(var f="function"==typeof require&&require,i=0;i<t.length;i++)r(t[i]);return r}({1:[function(e,n,o){(function(e){function o(){var e,n,o,u=t(),l=Array.prototype.slice.call(arguments),a=[],d=[],c=!1;for(e=0,n=u.length;e<n;e++)o=u[e],r.indexOf(o)===-1&&l.indexOf(o)===-1&&(a.push(o),c=!0);for(e=0,n=r.length;e<n;e++)o=r[e],u.indexOf(o)===-1&&(d.push(o),c=!0);if(r=u.concat(l),c){var s="antiglobal() | globals do not match:";for(e=0,n=a.length;e<n;e++)o=a[e],s=s+"\n+ "+o;for(e=0,n=d.length;e<n;e++)o=d[e],s=s+"\n- "+o;if(f&&console.error(s),i)throw new Error(s)}return!c}function t(){var n=[];for(var o in e)e.hasOwnProperty(o)&&"antiglobal"!==o&&n.push(o);return n}var r=t(),f=!0,i=!1;o.reset=function(){r=t()},Object.defineProperties(o,{log:{get:function(){return f},set:function(e){f=Boolean(e)}},throw:{get:function(){return i},set:function(e){i=Boolean(e)}}}),n.exports=o}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}]},{},[1])(1)});

Some files were not shown because too many files have changed in this diff Show More