Fix conflicts
This commit is contained in:
commit
306ef94237
1
bigbluebutton-html5/client/main.html
Normal file → Executable file
1
bigbluebutton-html5/client/main.html
Normal file → Executable file
@ -1,4 +1,5 @@
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>BBB - HTML5 Client</title>
|
||||
</head>
|
||||
<body>
|
||||
|
@ -29,7 +29,7 @@ export function clearCollections() {
|
||||
refreshes. Related to: https://github.com/meteor/meteor/issues/6576
|
||||
*/
|
||||
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -4,41 +4,48 @@ import Slides from '/imports/api/slides';
|
||||
export function addSlideToCollection(meetingId, presentationId, slideObject) {
|
||||
const url = Npm.require('url');
|
||||
const http = Npm.require('http');
|
||||
|
||||
const imageUri = slideObject.svg_uri != null ? slideObject.svg_uri : slideObject.png_uri;
|
||||
if (Slides.findOne({
|
||||
meetingId: meetingId,
|
||||
'slide.id': slideObject.id,
|
||||
}) == null) {
|
||||
const options = url.parse(slideObject.svg_uri);
|
||||
const options = url.parse(imageUri);
|
||||
http.get(options, Meteor.bindEnvironment(function (response) {
|
||||
let chunks = [];
|
||||
response.on('data', Meteor.bindEnvironment(function (chunk) {
|
||||
chunks.push(chunk);
|
||||
})).on('end', Meteor.bindEnvironment(function () {
|
||||
let buffer = Buffer.concat(chunks);
|
||||
const dimensions = sizeOf(buffer);
|
||||
const entry = {
|
||||
meetingId: meetingId,
|
||||
presentationId: presentationId,
|
||||
slide: {
|
||||
height_ratio: slideObject.height_ratio,
|
||||
y_offset: slideObject.y_offset,
|
||||
num: slideObject.num,
|
||||
x_offset: slideObject.x_offset,
|
||||
current: slideObject.current,
|
||||
img_uri: slideObject.svg_uri != null ? slideObject.svg_uri : slideObject.png_uri,
|
||||
txt_uri: slideObject.txt_uri,
|
||||
id: slideObject.id,
|
||||
width_ratio: slideObject.width_ratio,
|
||||
swf_uri: slideObject.swf_uri,
|
||||
thumb_uri: slideObject.thumb_uri,
|
||||
width: dimensions.width,
|
||||
height: dimensions.height,
|
||||
},
|
||||
};
|
||||
Slides.insert(entry);
|
||||
let contentType = response.headers['content-type'];
|
||||
|
||||
if (contentType.match(/svg/gi) || contentType.match(/png/gi)) {
|
||||
let chunks = [];
|
||||
response.on('data', Meteor.bindEnvironment(function (chunk) {
|
||||
chunks.push(chunk);
|
||||
})).on('end', Meteor.bindEnvironment(function () {
|
||||
let buffer = Buffer.concat(chunks);
|
||||
const dimensions = sizeOf(buffer);
|
||||
const entry = {
|
||||
meetingId: meetingId,
|
||||
presentationId: presentationId,
|
||||
slide: {
|
||||
height_ratio: slideObject.height_ratio,
|
||||
y_offset: slideObject.y_offset,
|
||||
num: slideObject.num,
|
||||
x_offset: slideObject.x_offset,
|
||||
current: slideObject.current,
|
||||
img_uri: slideObject.svg_uri != null ? slideObject.svg_uri : slideObject.png_uri,
|
||||
txt_uri: slideObject.txt_uri,
|
||||
id: slideObject.id,
|
||||
width_ratio: slideObject.width_ratio,
|
||||
swf_uri: slideObject.swf_uri,
|
||||
thumb_uri: slideObject.thumb_uri,
|
||||
width: dimensions.width,
|
||||
height: dimensions.height,
|
||||
},
|
||||
};
|
||||
Slides.insert(entry);
|
||||
}));
|
||||
} else {
|
||||
console.log(`Slide file is not accessible or not ready yet`);
|
||||
console.log(`response content-type`, response.headers['content-type']);
|
||||
}
|
||||
}));
|
||||
}));
|
||||
|
||||
//logger.info "added slide id =[#{id}]:#{slideObject.id} in #{meetingId}. Now there
|
||||
// are #{Slides.find({meetingId: meetingId}).count()} slides in the meeting"
|
||||
|
@ -18,8 +18,9 @@ export const renderRoutes = () => (
|
||||
<Router history={browserHistory}>
|
||||
<Route path="/join/:meetingID/:userID/:authToken" onEnter={setCredentials} />
|
||||
<Route path="/" onEnter={() => {
|
||||
subscribeToCollections()
|
||||
}}
|
||||
subscribeToCollections();
|
||||
}}
|
||||
|
||||
getComponent={(nextState, cb) => {
|
||||
subscribeToCollections(() => cb(null, AppContainer));
|
||||
}}>
|
||||
|
@ -117,7 +117,7 @@ export default class App extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
if(this.props.isLoading) {
|
||||
if (this.props.isLoading) {
|
||||
return <Loader/>;
|
||||
}
|
||||
|
||||
|
@ -40,7 +40,7 @@ let loading = true;
|
||||
const loadingDep = new Tracker.Dependency;
|
||||
|
||||
const getLoading = () => {
|
||||
loadingDep.depend()
|
||||
loadingDep.depend();
|
||||
return loading;
|
||||
};
|
||||
|
||||
@ -60,7 +60,7 @@ export default createContainer(() => {
|
||||
|
||||
return {
|
||||
isLoading: getLoading(),
|
||||
actionsbar: <ActionsBarContainer />
|
||||
actionsbar: <ActionsBarContainer />,
|
||||
};
|
||||
}, AppContainer);
|
||||
|
||||
|
@ -22,7 +22,7 @@
|
||||
}
|
||||
|
||||
.avatar {
|
||||
flex-basis: 1.7rem;
|
||||
flex-basis: 2.2rem;
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
@ -24,6 +24,10 @@ const ScrollCollection = new Mongo.Collection(null);
|
||||
const mapUser = (user) => ({
|
||||
id: user.userid,
|
||||
name: user.name,
|
||||
emoji: {
|
||||
status: user.emoji_status,
|
||||
changedAt: user.set_emoji_time,
|
||||
},
|
||||
isPresenter: user.presenter,
|
||||
isModerator: user.role === 'MODERATOR',
|
||||
isCurrent: user.userid === Auth.userID,
|
||||
|
@ -0,0 +1,108 @@
|
||||
//This is the code to generate the colors for the user avatar when no image is provided
|
||||
|
||||
const stringToPastelColour = (str) => {
|
||||
str = str.trim().toLowerCase();
|
||||
|
||||
let baseRed = 128;
|
||||
let baseGreen = 128;
|
||||
let baseBlue = 128;
|
||||
|
||||
let seed = 0;
|
||||
for (let i = 0; i < str.length; seed = str.charCodeAt(i++) + ((seed << 5) - seed));
|
||||
let a = Math.abs((Math.sin(seed++) * 10000)) % 256;
|
||||
let b = Math.abs((Math.sin(seed++) * 10000)) % 256;
|
||||
let c = Math.abs((Math.sin(seed++) * 10000)) % 256;
|
||||
|
||||
//build colour
|
||||
let red = Math.round((a + baseRed) / 2);
|
||||
let green = Math.round((b + baseGreen) / 2);
|
||||
let blue = Math.round((c + baseBlue) / 2);
|
||||
|
||||
return {
|
||||
r: red,
|
||||
g: green,
|
||||
b: blue,
|
||||
};
|
||||
};
|
||||
|
||||
// https://www.w3.org/TR/WCAG20/#relativeluminancedef
|
||||
// http://entropymine.com/imageworsener/srgbformula/
|
||||
const relativeLuminance = (rgb) => {
|
||||
let tmp = {};
|
||||
|
||||
Object.keys(rgb).forEach((i) => {
|
||||
let c = rgb[i] / 255;
|
||||
if (c <= 0.03928) {
|
||||
tmp[i] = c / 12.92;
|
||||
} else {
|
||||
tmp[i] = Math.pow(((c + 0.055) / 1.055), 2.4);
|
||||
}
|
||||
});
|
||||
|
||||
return (0.2126 * tmp.r + 0.7152 * tmp.g + 0.0722 * tmp.b);
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate contrast ratio acording to WCAG 2.0 formula
|
||||
* Will return a value between 1 (no contrast) and 21 (max contrast)
|
||||
* @link http://www.w3.org/TR/WCAG20/#contrast-ratiodef
|
||||
*/
|
||||
const contrastRatio = (a, b) => {
|
||||
let c;
|
||||
|
||||
a = relativeLuminance(a);
|
||||
b = relativeLuminance(b);
|
||||
|
||||
//Arrange so a is lightest
|
||||
if (a < b) {
|
||||
c = a;
|
||||
a = b;
|
||||
b = c;
|
||||
}
|
||||
|
||||
return (a + 0.05) / (b + 0.05);
|
||||
};
|
||||
|
||||
const shadeColor = (rgb, amt) => {
|
||||
let r = rgb.r + amt;
|
||||
if (r > 255) r = 255;
|
||||
else if (r < 0) r = 0;
|
||||
|
||||
let b = rgb.b + amt;
|
||||
if (b > 255) b = 255;
|
||||
else if (b < 0) b = 0;
|
||||
|
||||
let g = rgb.g + amt;
|
||||
if (g > 255) g = 255;
|
||||
else if (g < 0) g = 0;
|
||||
|
||||
return {
|
||||
r: r,
|
||||
g: g,
|
||||
b: b,
|
||||
};
|
||||
};
|
||||
|
||||
const addShadeIfNoContrast = (rgb) => {
|
||||
let base = {
|
||||
r: 255,
|
||||
g: 255,
|
||||
b: 255,
|
||||
}; // white
|
||||
|
||||
let cr = contrastRatio(base, rgb);
|
||||
|
||||
if (cr > 4.5) {
|
||||
return rgb;
|
||||
}
|
||||
|
||||
return addShadeIfNoContrast(shadeColor(rgb, -25));
|
||||
};
|
||||
|
||||
const getColor = (username) => {
|
||||
let rgb = stringToPastelColour(username);
|
||||
rgb = addShadeIfNoContrast(rgb);
|
||||
return 'rgb(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ')';
|
||||
};
|
||||
|
||||
export default getColor;
|
@ -1,6 +1,8 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import Icon from '/imports/ui/components/icon/component';
|
||||
import styles from './styles.scss';
|
||||
import cx from 'classnames';
|
||||
import getColor from './color-generator';
|
||||
|
||||
const propTypes = {
|
||||
user: React.PropTypes.shape({
|
||||
@ -17,21 +19,73 @@ const defaultProps = {
|
||||
|
||||
export default class UserAvatar extends Component {
|
||||
render() {
|
||||
let user = this.props.user;
|
||||
const {
|
||||
user
|
||||
} = this.props;
|
||||
|
||||
let avatarClasses = {};
|
||||
avatarClasses[styles.presenter] = user.isPresenter;
|
||||
avatarClasses[styles.voiceUser] = user.isVoiceUser;
|
||||
avatarClasses[styles.moderator] = user.isModerator;
|
||||
let avatarStyles = {
|
||||
'backgroundColor': getColor(user.name),
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cx(avatarClasses, styles.userAvatar)}>
|
||||
<div className={styles.userAvatar} style={avatarStyles}>
|
||||
<span>
|
||||
{user.name.slice(0, 2)}
|
||||
{this.renderAvatarContent()}
|
||||
</span>
|
||||
{this.renderUserStatus()}
|
||||
{this.renderUserMediaStatus()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderAvatarContent() {
|
||||
const user = this.props.user;
|
||||
|
||||
let content = user.name.slice(0, 2);
|
||||
|
||||
if (user.emoji.status !== "none") {
|
||||
content = <Icon iconName={user.emoji.status}/>
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
renderUserStatus() {
|
||||
const user = this.props.user;
|
||||
let userStatus;
|
||||
|
||||
let userStatusClasses = {};
|
||||
userStatusClasses[styles.moderator] = user.isModerator;
|
||||
userStatusClasses[styles.presenter] = user.isPresenter;
|
||||
|
||||
if (user.isModerator || user.isPresenter) {
|
||||
userStatus = (
|
||||
<span className={cx(styles.userStatus, userStatusClasses)}>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return userStatus;
|
||||
}
|
||||
|
||||
renderUserMediaStatus() {
|
||||
const user = this.props.user;
|
||||
let userMediaStatus;
|
||||
|
||||
let userMediaClasses = {};
|
||||
userMediaClasses[styles.voiceOnly] = user.isListenOnly;
|
||||
userMediaClasses[styles.microphone] = user.isVoiceUser;
|
||||
|
||||
if (user.isListenOnly || user.isVoiceUser) {
|
||||
userMediaStatus = (
|
||||
<span className={cx(styles.userMediaStatus, userMediaClasses)}>
|
||||
{user.isMuted ? <div className={styles.microphoneMuted}/> : null}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return userMediaStatus;
|
||||
}
|
||||
}
|
||||
|
||||
UserAvatar.propTypes = propTypes;
|
||||
|
@ -13,34 +13,77 @@ $moderator-text: $color-white;
|
||||
$moderator-bg: $color-primary;
|
||||
|
||||
.userAvatar {
|
||||
@extend .flex-column;
|
||||
transition: all 0.3s;
|
||||
flex-basis: 1.7rem;
|
||||
// @extend .flex-column;
|
||||
flex-basis: 2.2rem;
|
||||
height: 2.2rem;
|
||||
flex-shrink: 0;
|
||||
height: 1.7rem;
|
||||
line-height: 2.2rem;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
font-size: 1rem;
|
||||
font-size: 1.1rem;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
border: 2px solid $user-avatar-border;
|
||||
color: $user-avatar-text;
|
||||
color: $color-white;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.voiceUser {
|
||||
background-color: $voice-user-bg;
|
||||
color: $voice-user-text;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.presenter {
|
||||
.userStatus {
|
||||
position: absolute;
|
||||
background-color: $color-white;
|
||||
width: 0.625rem;
|
||||
height: 0.625rem;
|
||||
border-radius: 2px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
-webkit-box-shadow: 0 0 0 1px $color-white;
|
||||
-moz-box-shadow: 0 0 0 1px $color-white;
|
||||
box-shadow: 0 0 0 1px $color-white;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.moderator {
|
||||
border: 1px solid $color-gray-light;
|
||||
background-color: $color-white;
|
||||
}
|
||||
|
||||
.presenter {
|
||||
background-color: $moderator-bg;
|
||||
color: $moderator-text;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.userMediaStatus {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
width: 0.625rem;
|
||||
height: 0.625rem;
|
||||
top: 1.575rem;
|
||||
left: 1.575rem;
|
||||
-webkit-box-shadow: 0 0 0 1px $color-white;
|
||||
-moz-box-shadow: 0 0 0 1px $color-white;
|
||||
box-shadow: 0 0 0 1px $color-white;
|
||||
transition: all 0.3s;
|
||||
background-color: $color-success;
|
||||
}
|
||||
|
||||
.voiceOnly {
|
||||
border: 1px solid $color-gray-light;
|
||||
background-color: $color-white;
|
||||
}
|
||||
|
||||
.microphone {
|
||||
}
|
||||
|
||||
.microphoneMuted {
|
||||
margin-top: 0.05rem;
|
||||
width: 2px;
|
||||
transform: rotate(45deg);
|
||||
height: 0.5rem;
|
||||
background-color: $color-white;
|
||||
}
|
||||
|
||||
// 10px x
|
||||
// 16px 1
|
||||
|
@ -32,8 +32,8 @@ class ChatListItem extends Component {
|
||||
linkClasses[styles.active] = chat.id === openChat;
|
||||
|
||||
return (
|
||||
<li {...this.props}>
|
||||
<Link to={linkPath} className={cx(styles.chatListItem, linkClasses)}>
|
||||
<li className={cx(styles.chatListItem, linkClasses)} {...this.props}>
|
||||
<Link to={linkPath} className={styles.chatListItemLink}>
|
||||
{chat.icon ? this.renderChatIcon() : this.renderChatAvatar()}
|
||||
<div className={styles.chatName}>
|
||||
<h3 className={styles.chatNameMain}>{chat.name}</h3>
|
||||
|
@ -2,9 +2,17 @@
|
||||
|
||||
.chatListItem {
|
||||
@extend %list-item;
|
||||
padding-right: 1.2rem;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.chatListItemLink {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
text-decoration: none;
|
||||
padding: 0.3rem;
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.3rem;
|
||||
}
|
||||
|
||||
.unreadMessages {
|
||||
|
@ -202,12 +202,9 @@ const getOpenChats = chatID => {
|
||||
|
||||
getCurrentUser = () => {
|
||||
let currentUserId = Auth.userID;
|
||||
let currentUser = Users.findOne({ 'user.userid': currentUserId });
|
||||
|
||||
return mapUser(
|
||||
Users
|
||||
.findOne({ 'user.userid': currentUserId })
|
||||
.user
|
||||
);
|
||||
return (currentUser) ? mapUser(currentUser.user) : null;
|
||||
};
|
||||
|
||||
const userActions = {
|
||||
|
@ -83,8 +83,10 @@ class UserListItem extends Component {
|
||||
|
||||
handleClickOutsideDropdown(e) {
|
||||
const node = findDOMNode(this);
|
||||
|
||||
if (e.target !== node && !node.contains(e.target)) {
|
||||
const shouldUpdateState = e.target !== node &&
|
||||
!node.contains(e.target) &&
|
||||
this.state.visibleActions;
|
||||
if (shouldUpdateState) {
|
||||
this.setState({ visibleActions: false });
|
||||
}
|
||||
}
|
||||
@ -129,23 +131,14 @@ class UserListItem extends Component {
|
||||
userNameSub = userNameSub.join(' ');
|
||||
|
||||
return (
|
||||
<ReactCSSTransitionGroup
|
||||
className={styles.userName}
|
||||
transitionName={userNameSubTransition}
|
||||
transitionAppear={true}
|
||||
transitionEnter={true}
|
||||
transitionLeave={true}
|
||||
transitionAppearTimeout={0}
|
||||
transitionEnterTimeout={0}
|
||||
transitionLeaveTimeout={0}
|
||||
component='div'>
|
||||
<div className={styles.userName}>
|
||||
<h3 className={styles.userNameMain}>
|
||||
{user.name}
|
||||
</h3>
|
||||
<p className={styles.userNameSub}>
|
||||
{userNameSub}
|
||||
</p>
|
||||
</ReactCSSTransitionGroup>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -9,13 +9,23 @@ export default class Slide extends React.Component {
|
||||
return (
|
||||
<g>
|
||||
{this.props.currentSlide ?
|
||||
<image x="0" y="0"
|
||||
width={this.props.currentSlide.slide.width}
|
||||
height={this.props.currentSlide.slide.height}
|
||||
xlinkHref={this.props.currentSlide.slide.img_uri}
|
||||
strokeWidth="0.8"
|
||||
>
|
||||
</image>
|
||||
<g>
|
||||
<rect
|
||||
x="0"
|
||||
y="0"
|
||||
width={this.props.currentSlide.slide.width}
|
||||
height={this.props.currentSlide.slide.height}
|
||||
fill="white"
|
||||
>
|
||||
</rect>
|
||||
<image x="0" y="0"
|
||||
width={this.props.currentSlide.slide.width}
|
||||
height={this.props.currentSlide.slide.height}
|
||||
xlinkHref={this.props.currentSlide.slide.img_uri}
|
||||
strokeWidth="0.8"
|
||||
>
|
||||
</image>
|
||||
</g>
|
||||
: null }
|
||||
</g>
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user