Merge after git conflict/lost object
This commit is contained in:
parent
b33e26a43d
commit
1c0132ed5b
@ -4,7 +4,7 @@ import { clearShapesCollection } from '/imports/api/shapes/server/modifiers/clea
|
||||
import { clearSlidesCollection } from '/imports/api/slides/server/modifiers/clearSlidesCollection';
|
||||
import { clearPresentationsCollection }
|
||||
from '/imports/api/presentations/server/modifiers/clearPresentationsCollection';
|
||||
import { clearMeetingsCollection}
|
||||
import { clearMeetingsCollection }
|
||||
from '/imports/api/meetings/server/modifiers/clearMeetingsCollection';
|
||||
import { clearPollCollection } from '/imports/api/polls/server/modifiers/clearPollCollection';
|
||||
import { clearCursorCollection } from '/imports/api/cursor/server/modifiers/clearCursorCollection';
|
||||
@ -23,6 +23,16 @@ export function appendMessageHeader(eventName, messageObj) {
|
||||
|
||||
export function clearCollections() {
|
||||
console.log('in function clearCollections');
|
||||
|
||||
/*
|
||||
This is to prevent collection clearing in development environment when the server
|
||||
refreshes. Related to: https://github.com/meteor/meteor/issues/6576
|
||||
*/
|
||||
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
return;
|
||||
}
|
||||
|
||||
clearUsersCollection();
|
||||
clearChatCollection();
|
||||
clearMeetingsCollection();
|
||||
|
@ -1,14 +1,14 @@
|
||||
import React from 'react';
|
||||
import { Router, Route, Redirect, IndexRoute,
|
||||
IndexRedirect, useRouterHistory } from 'react-router';
|
||||
import { Router, Route, Redirect, IndexRoute, useRouterHistory } from 'react-router';
|
||||
import { createHistory } from 'history';
|
||||
|
||||
// route components
|
||||
import AppContainer from '../../ui/components/app/container';
|
||||
import {setCredentials, subscribeForData} from '../../ui/components/app/service';
|
||||
import AppContainer from '/imports/ui/components/app/container';
|
||||
import { subscribeToCollections, setCredentials } from '/imports/ui/components/app/service';
|
||||
|
||||
import ChatContainer from '../../ui/components/chat/container';
|
||||
import UserListContainer from '../../ui/components/user-list/container';
|
||||
import ChatContainer from '/imports/ui/components/chat/container';
|
||||
import UserListContainer from '/imports/ui/components/user-list/container';
|
||||
import Loader from '/imports/ui/components/loader/component';
|
||||
|
||||
const browserHistory = useRouterHistory(createHistory)({
|
||||
basename: '/html5client',
|
||||
@ -16,22 +16,30 @@ const browserHistory = useRouterHistory(createHistory)({
|
||||
|
||||
export const renderRoutes = () => (
|
||||
<Router history={browserHistory}>
|
||||
<Route path="/join/:meetingID/:userID/:authToken" onEnter={setCredentials} >
|
||||
<IndexRedirect to="/" />
|
||||
<Route path="/" component={AppContainer} onEnter={subscribeForData} >
|
||||
<IndexRoute components={{}} />
|
||||
<Route path="/join/:meetingID/:userID/:authToken" onEnter={setCredentials} />
|
||||
<Route path="/" onEnter={() => {
|
||||
subscribeToCollections()
|
||||
}}
|
||||
getComponent={(nextState, cb) => {
|
||||
subscribeToCollections(() => cb(null, AppContainer));
|
||||
}}>
|
||||
<IndexRoute components={{}} />
|
||||
|
||||
<Route name="users" path="users" components={{
|
||||
<Route name="users" path="users" getComponents={(nextState, cb) => {
|
||||
subscribeToCollections(() => cb(null, {
|
||||
userList: UserListContainer,
|
||||
}} />
|
||||
}));
|
||||
}} />
|
||||
|
||||
<Route name="chat" path="users/chat/:chatID" components={{
|
||||
<Route name="chat" path="users/chat/:chatID" getComponents={(nextState, cb) => {
|
||||
subscribeToCollections(() => cb(null, {
|
||||
userList: UserListContainer,
|
||||
chat: ChatContainer,
|
||||
}} />
|
||||
<Redirect from="users/chat" to="/users/chat/public" />
|
||||
</Route>
|
||||
<Redirect from="*" to="/" />
|
||||
}));
|
||||
}} />
|
||||
|
||||
<Redirect from="users/chat" to="/users/chat/public" />
|
||||
</Route>
|
||||
<Redirect from="*" to="/" />
|
||||
</Router>
|
||||
);
|
||||
|
@ -1,5 +1,8 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import styles from './styles.scss';
|
||||
import Chats from '/imports/api/chat';
|
||||
|
||||
import ChatsService from '/imports/ui/components/chat/service';
|
||||
|
||||
import Button from '../button/component';
|
||||
|
||||
@ -9,7 +12,20 @@ export default class ActionsBar extends Component {
|
||||
}
|
||||
|
||||
handleClick() {
|
||||
console.log('dummy handler');
|
||||
const SYSTEM_CHAT_TYPE = 'SYSTEM_MESSAGE';
|
||||
const PUBLIC_CHAT_TYPE = 'PUBLIC_CHAT';
|
||||
const PRIVATE_CHAT_TYPE = 'PRIVATE_CHAT';
|
||||
|
||||
console.log(Chats.find({
|
||||
'message.chat_type': { $in: [PUBLIC_CHAT_TYPE, SYSTEM_CHAT_TYPE] },
|
||||
}, {
|
||||
sort: ['message.from_time'],
|
||||
})
|
||||
.fetch());
|
||||
}
|
||||
|
||||
handleClick2() {
|
||||
console.log(ChatsService.getPublicMessages());
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -35,7 +51,7 @@ export default class ActionsBar extends Component {
|
||||
circle={true}
|
||||
/>
|
||||
<Button
|
||||
onClick={this.handleClick}
|
||||
onClick={this.handleClick2}
|
||||
label={'Cam Off'}
|
||||
color={'primary'}
|
||||
icon={'video-off'}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import Loader from '../loader/component';
|
||||
import styles from './styles';
|
||||
|
||||
const propTypes = {
|
||||
@ -111,11 +112,15 @@ export default class App extends Component {
|
||||
|
||||
renderAudioElement() {
|
||||
return (
|
||||
<audio id="remote-media" autoplay="autoplay"></audio>
|
||||
<audio id="remote-media" autoPlay="autoplay"></audio>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if(this.props.isLoading) {
|
||||
return <Loader/>;
|
||||
}
|
||||
|
||||
return (
|
||||
<main className={styles.main}>
|
||||
<section className={styles.wrapper}>
|
||||
|
@ -1,13 +1,10 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { createContainer } from 'meteor/react-meteor-data';
|
||||
|
||||
import App from './component';
|
||||
import { pollExists } from './service';
|
||||
|
||||
import { subscribeForData, pollExists } from './service';
|
||||
import NavBarContainer from '../nav-bar/container';
|
||||
import ActionsBarContainer from '../actions-bar/container';
|
||||
import MediaContainer from '../media/container';
|
||||
import PollingContainer from '../polling/container';
|
||||
import SettingsModal from '../modals/settings/SettingsModal';
|
||||
|
||||
const defaultProps = {
|
||||
@ -20,11 +17,6 @@ const defaultProps = {
|
||||
class AppContainer extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
meetingID: localStorage.getItem('meetingID'),
|
||||
userID: localStorage.getItem('userID'),
|
||||
authToken: localStorage.getItem('authToken'),
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -36,8 +28,6 @@ class AppContainer extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
AppContainer.defaultProps = defaultProps;
|
||||
|
||||
const actionControlsToShow = () => {
|
||||
if (pollExists()) {
|
||||
return <PollingContainer />;
|
||||
@ -46,7 +36,32 @@ const actionControlsToShow = () => {
|
||||
}
|
||||
};
|
||||
|
||||
let loading = true;
|
||||
const loadingDep = new Tracker.Dependency;
|
||||
|
||||
const getLoading = () => {
|
||||
loadingDep.depend()
|
||||
return loading;
|
||||
};
|
||||
|
||||
const setLoading = (val) => {
|
||||
if (val !== loading) {
|
||||
loading = val;
|
||||
loadingDep.changed();
|
||||
}
|
||||
};
|
||||
|
||||
export default createContainer(() => {
|
||||
const data = { actionsbar: actionControlsToShow() };
|
||||
return data;
|
||||
Promise.all(subscribeForData())
|
||||
.then(() => {
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(reason => console.error(reason));
|
||||
|
||||
return {
|
||||
isLoading: getLoading(),
|
||||
actionsbar: <ActionsBarContainer />
|
||||
};
|
||||
}, AppContainer);
|
||||
|
||||
AppContainer.defaultProps = defaultProps;
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import Users from '/imports/api/users';
|
||||
import Chat from '/imports/api/chat';
|
||||
@ -11,58 +10,61 @@ function setCredentials(nextState, replace) {
|
||||
if (nextState && nextState.params.authToken) {
|
||||
const { meetingID, userID, authToken } = nextState.params;
|
||||
Auth.setCredentials(meetingID, userID, authToken);
|
||||
replace({
|
||||
pathname: '/'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let dataSubscriptions = null;
|
||||
function subscribeForData() {
|
||||
subscribeFor('users');
|
||||
if(dataSubscriptions) {
|
||||
return dataSubscriptions;
|
||||
}
|
||||
|
||||
Meteor.setTimeout(() => {
|
||||
subscribeFor('chat');
|
||||
subscribeFor('cursor');
|
||||
subscribeFor('deskshare');
|
||||
subscribeFor('meetings');
|
||||
subscribeFor('polls');
|
||||
subscribeFor('presentations');
|
||||
subscribeFor('shapes');
|
||||
subscribeFor('slides');
|
||||
subscribeFor('users');
|
||||
const subNames = ['users', 'chat', 'cursor', 'deskshare', 'meetings',
|
||||
'polls', 'presentations', 'shapes', 'slides'];
|
||||
|
||||
window.Users = Users; // for debug purposes TODO remove
|
||||
window.Chat = Chat; // for debug purposes TODO remove
|
||||
window.Meetings = Meetings; // for debug purposes TODO remove
|
||||
window.Cursor = Cursor; // for debug purposes TODO remove
|
||||
window.Polls = Polls; // for debug purposes TODO remove
|
||||
let subs = [];
|
||||
subNames.forEach(name => subs.push(subscribeFor(name)));
|
||||
|
||||
Auth.setLogOut();
|
||||
}, 2000); //To avoid race condition where we subscribe before receiving auth from BBB
|
||||
dataSubscriptions = subs;
|
||||
|
||||
Auth.setLogOut();
|
||||
return subs;
|
||||
};
|
||||
|
||||
function subscribeFor(collectionName) {
|
||||
const credentials = Auth.getCredentials();
|
||||
|
||||
// console.log("subscribingForData", collectionName, meetingID, userID, authToken);
|
||||
|
||||
Meteor.subscribe(collectionName, credentials, onError, onReady);
|
||||
return new Promise((resolve, reject) => {
|
||||
Meteor.subscribe(collectionName, credentials, {
|
||||
onReady: (...args) => resolve(...args),
|
||||
onStop: (...args) => reject(...args),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function onError(error, result) {
|
||||
function subscribeToCollections(cb) {
|
||||
subscribeFor('users').then(() => {
|
||||
Promise.all(subscribeForData()).then(() => {
|
||||
if(cb) {
|
||||
cb();
|
||||
}
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
// console.log("OnError", error, result);
|
||||
function onStop(error, result) {
|
||||
console.log('OnError', error, result);
|
||||
Auth.completeLogout();
|
||||
};
|
||||
|
||||
function onReady() {
|
||||
// console.log("OnReady", Users.find().fetch());
|
||||
console.log("OnReady");
|
||||
};
|
||||
|
||||
function pollExists() {
|
||||
return !!(Polls.findOne({}));
|
||||
}
|
||||
|
||||
export {
|
||||
pollExists,
|
||||
subscribeForData,
|
||||
setCredentials,
|
||||
subscribeFor,
|
||||
subscribeToCollections,
|
||||
};
|
||||
|
@ -56,7 +56,7 @@ export default injectIntl(createContainer(({ params, intl }) => {
|
||||
title = intl.formatMessage(intlMessages.titlePrivate, { name: user.name });
|
||||
} else {
|
||||
// let partnerName = messages.find(m => m.user && m.user.id === chatID).map(m => m.user.name);
|
||||
let partnerName = '{{NAME}}s'; // placeholder until the server sends the name
|
||||
let partnerName = '{{NAME}}'; // placeholder until the server sends the name
|
||||
messages.push({
|
||||
content: [intl.formatMessage(intlMessages.partnerDisconnected, { name: partnerName })],
|
||||
time: Date.now(),
|
||||
@ -79,7 +79,7 @@ export default injectIntl(createContainer(({ params, intl }) => {
|
||||
actions: {
|
||||
handleSendMessage: message => {
|
||||
let sentMessage = ChatService.sendMessage(chatID, message);
|
||||
ChatService.updateScrollPosition(chatID, undefined); //undefined so its scrolls to bottom
|
||||
ChatService.updateScrollPosition(chatID, null); //null so its scrolls to bottom
|
||||
// ChatService.updateUnreadMessage(chatID, sentMessage.from_time);
|
||||
},
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { findDOMscrollArea } from 'react-dom';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import _ from 'underscore';
|
||||
import styles from './styles';
|
||||
@ -28,25 +27,35 @@ class MessageList extends Component {
|
||||
this.lastKnowScrollPosition = 0;
|
||||
this.ticking = false;
|
||||
|
||||
this.handleScrollChange = this.handleScrollChange.bind(this);
|
||||
this.handleScrollChange = _.debounce(this.handleScrollChange.bind(this), 150);
|
||||
this.handleScrollUpdate = _.debounce(this.handleScrollUpdate.bind(this), 150);
|
||||
}
|
||||
|
||||
scrollTo(position) {
|
||||
scrollTo(position = null) {
|
||||
const { scrollArea } = this.refs;
|
||||
|
||||
if (position === undefined) {
|
||||
if (position === null) {
|
||||
position = scrollArea.scrollHeight - scrollArea.clientHeight;
|
||||
}
|
||||
|
||||
scrollArea.scrollTop = position;
|
||||
}
|
||||
|
||||
handleScrollUpdate(position, target) {
|
||||
if (position !== null && position + target.offsetHeight === target.scrollHeight) {
|
||||
position = null; //update with null so it keeps auto scrolling
|
||||
}
|
||||
|
||||
this.props.handleScrollUpdate(position);
|
||||
}
|
||||
|
||||
handleScrollChange(e) {
|
||||
this.lastKnowScrollPosition = e.target.scrollTop;
|
||||
|
||||
if (!this.ticking) {
|
||||
window.requestAnimationFrame(() => {
|
||||
this.props.handleScrollUpdate(this.lastKnowScrollPosition);
|
||||
let position = this.lastKnowScrollPosition;
|
||||
this.handleScrollUpdate(position, e.target);
|
||||
this.ticking = false;
|
||||
});
|
||||
}
|
||||
@ -56,7 +65,8 @@ class MessageList extends Component {
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.chatId !== nextProps.chatId) {
|
||||
this.props.handleScrollUpdate(this.refs.scrollArea.scrollTop);
|
||||
const { scrollArea } = this.refs;
|
||||
this.handleScrollUpdate(scrollArea.scrollTop, scrollArea);
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,20 +107,21 @@ class MessageList extends Component {
|
||||
componentWillUnmount() {
|
||||
const { scrollArea } = this.refs;
|
||||
|
||||
this.props.handleScrollUpdate(scrollArea.scrollTop);
|
||||
this.handleScrollUpdate(scrollArea.scrollTop, scrollArea);
|
||||
scrollArea.removeEventListener('scroll', this.handleScrollChange, false);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { messages } = this.props;
|
||||
|
||||
return (
|
||||
<div className={styles.messageListWrapper}>
|
||||
<div {...this.props} ref="scrollArea" className={styles.messageList}>
|
||||
{messages.map((message, index) => (
|
||||
{messages.map((message) => (
|
||||
<MessageListItem
|
||||
handleReadMessage={this.props.handleReadMessage}
|
||||
className={styles.messageListItem}
|
||||
key={index}
|
||||
key={message.id}
|
||||
messages={message.content}
|
||||
user={message.sender}
|
||||
time={message.time}
|
||||
@ -125,9 +136,9 @@ class MessageList extends Component {
|
||||
}
|
||||
|
||||
renderUnreadNotification() {
|
||||
const { intl, hasUnreadMessages } = this.props;
|
||||
const { intl, hasUnreadMessages, scrollPosition } = this.props;
|
||||
|
||||
if (hasUnreadMessages) {
|
||||
if (hasUnreadMessages && scrollPosition !== null) {
|
||||
return (
|
||||
<Button
|
||||
className={styles.unreadButton}
|
||||
|
@ -9,8 +9,8 @@ import Message from './message/component';
|
||||
import styles from './styles';
|
||||
|
||||
const propTypes = {
|
||||
user: React.PropTypes.object,
|
||||
messages: React.PropTypes.array.isRequired,
|
||||
user: PropTypes.object,
|
||||
messages: PropTypes.array.isRequired,
|
||||
time: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
@ -53,9 +53,10 @@ export default class MessageListItem extends Component {
|
||||
{messages.map((message, i) => (
|
||||
<Message
|
||||
className={styles.message}
|
||||
key={i}
|
||||
key={message.id}
|
||||
text={message.text}
|
||||
time={message.time}
|
||||
unread={message.unread}
|
||||
chatAreaId={this.props.chatAreaId}
|
||||
handleReadMessage={this.props.handleReadMessage}
|
||||
/>
|
||||
|
@ -1,12 +1,15 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import _ from 'underscore';
|
||||
import { findDOMNode } from 'react-dom';
|
||||
|
||||
const propTypes = {
|
||||
text: React.PropTypes.string.isRequired,
|
||||
text: PropTypes.string.isRequired,
|
||||
time: PropTypes.number.isRequired,
|
||||
unread: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
unread: true,
|
||||
};
|
||||
|
||||
const eventsToBeBound = [
|
||||
@ -31,7 +34,7 @@ export default class MessageListItem extends Component {
|
||||
|
||||
this.ticking = false;
|
||||
|
||||
this.handleMessageInViewport = this.handleMessageInViewport.bind(this);
|
||||
this.handleMessageInViewport = _.debounce(this.handleMessageInViewport.bind(this), 50);
|
||||
}
|
||||
|
||||
handleMessageInViewport(e) {
|
||||
@ -53,6 +56,10 @@ export default class MessageListItem extends Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (!this.props.unread) {
|
||||
return;
|
||||
}
|
||||
|
||||
const node = findDOMNode(this);
|
||||
|
||||
if (isElementInViewport(node)) {
|
||||
@ -64,6 +71,10 @@ export default class MessageListItem extends Component {
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (!this.props.unread) {
|
||||
return;
|
||||
}
|
||||
|
||||
const node = findDOMNode(this);
|
||||
const scrollArea = document.getElementById(this.props.chatAreaId);
|
||||
|
||||
|
@ -34,12 +34,17 @@ const mapUser = (user) => ({
|
||||
isLocked: user.locked,
|
||||
});
|
||||
|
||||
const mapMessage = (message) => {
|
||||
const mapMessage = (messagePayload, isPublic = false) => {
|
||||
const { message } = messagePayload;
|
||||
|
||||
let mappedMessage = {
|
||||
id: messagePayload._id,
|
||||
content: [
|
||||
{
|
||||
id: messagePayload._id,
|
||||
text: message.message,
|
||||
time: message.from_time,
|
||||
unread: message.from_time > UnreadMessages.get(isPublic ? PUBLIC_CHAT_USERID : message.from_userid),
|
||||
},
|
||||
],
|
||||
time: message.from_time, //+ message.from_tz_offset,
|
||||
@ -94,8 +99,7 @@ const getPublicMessages = () => {
|
||||
let systemMessage = Chats.findOne({ 'message.chat_type': SYSTEM_CHAT_TYPE });
|
||||
|
||||
return publicMessages
|
||||
.map(m => m.message)
|
||||
.map(mapMessage)
|
||||
.map(mapMessage, true)
|
||||
.reduce(reduceMessages, []);
|
||||
};
|
||||
|
||||
@ -111,7 +115,6 @@ const getPrivateMessages = (userID) => {
|
||||
}).fetch();
|
||||
|
||||
return messages
|
||||
.map(m => m.message)
|
||||
.map(mapMessage)
|
||||
.reduce(reduceMessages, []);
|
||||
};
|
||||
|
@ -20,7 +20,8 @@ class NavBarContainer extends Component {
|
||||
}
|
||||
|
||||
export default createContainer(() => {
|
||||
let meetingTitle, meetingRecorded;
|
||||
let meetingTitle;
|
||||
let meetingRecorded;
|
||||
|
||||
const meetingId = Auth.getMeeting();
|
||||
const meetingObject = Meetings.findOne({
|
||||
|
@ -1,16 +1,61 @@
|
||||
import React from 'react';
|
||||
import Button from '../button/component';
|
||||
import Button from '/imports/ui/components/button/component';
|
||||
import styles from './styles.scss';
|
||||
|
||||
export default class PollingComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
getStyles() {
|
||||
const number = this.props.poll.answers.length + 1;
|
||||
const buttonStyle =
|
||||
{
|
||||
width: `calc(75%/ ${number} )`,
|
||||
marginLeft: `calc(25%/${number * 2})`,
|
||||
marginRight: `calc(25%/${number * 2})`,
|
||||
};
|
||||
|
||||
return buttonStyle;
|
||||
}
|
||||
|
||||
render() {
|
||||
const poll = this.props.poll;
|
||||
const calculatedStyles = this.getStyles();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.pollingContainer}>
|
||||
<div className={styles.pollingTitle}>
|
||||
<p>
|
||||
Polling Options
|
||||
</p>
|
||||
</div>
|
||||
{poll.answers.map((pollAnswer, index) =>
|
||||
<Button className="button mediumFont" key={index}
|
||||
onClick={() => this.props.handleVote(poll.pollId, pollAnswer)} componentClass="span">
|
||||
{pollAnswer.key}
|
||||
</Button>
|
||||
<div style={calculatedStyles} className={styles.pollButtonWrapper}>
|
||||
<Button
|
||||
className={styles.pollingButton}
|
||||
label={pollAnswer.key}
|
||||
size="lg"
|
||||
color="primary"
|
||||
key={index}
|
||||
onClick={() => this.props.handleVote(poll.pollId, pollAnswer)}
|
||||
componentClass="span"
|
||||
aria-labelledby={`pollAnswerLabel${pollAnswer.key}`}
|
||||
aria-describedby={`pollAnswerDesc${pollAnswer.key}`}
|
||||
/>
|
||||
<div
|
||||
className={styles.hidden}
|
||||
id={`pollAnswerLabel${pollAnswer.key}`}
|
||||
>
|
||||
{`Poll answer ${pollAnswer.key}`}
|
||||
</div>
|
||||
<div
|
||||
className={styles.hidden}
|
||||
id={`pollAnswerDesc${pollAnswer.key}`}
|
||||
>
|
||||
{`Select this option to vote for ${pollAnswer.key}`}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
@ -12,6 +12,7 @@ const propTypes = {
|
||||
chat: React.PropTypes.shape({
|
||||
id: React.PropTypes.string.isRequired,
|
||||
name: React.PropTypes.string.isRequired,
|
||||
unreadCounter: React.PropTypes.number.isRequired,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
@ -26,10 +27,10 @@ class ChatListItem extends Component {
|
||||
} = this.props;
|
||||
|
||||
const linkPath = [PRIVATE_CHAT_PATH, chat.id].join('');
|
||||
let fakeUnreadCount = Math.round(Math.random() * 33);
|
||||
|
||||
let linkClasses = {};
|
||||
linkClasses[styles.active] = chat.id === openChat;
|
||||
|
||||
return (
|
||||
<li {...this.props}>
|
||||
<Link to={linkPath} className={cx(styles.chatListItem, linkClasses)}>
|
||||
@ -37,9 +38,11 @@ class ChatListItem extends Component {
|
||||
<div className={styles.chatName}>
|
||||
<h3 className={styles.chatNameMain}>{chat.name}</h3>
|
||||
</div>
|
||||
<div className={styles.unreadMessages}>
|
||||
<p className={styles.unreadMessagesText}>{fakeUnreadCount}</p>
|
||||
</div>
|
||||
{(chat.unreadCounter > 0) ?
|
||||
<div className={styles.unreadMessages}>
|
||||
<p className={styles.unreadMessagesText}>{chat.unreadCounter}</p>
|
||||
</div>
|
||||
: null}
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Users from '/imports/api/users';
|
||||
import Chat from '/imports/api/chat';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import UnreadMessages from '/imports/ui/services/unread-messages';
|
||||
|
||||
import { callServer } from '/imports/ui/services/api';
|
||||
|
||||
@ -182,12 +183,17 @@ const getOpenChats = chatID => {
|
||||
openChats = Users
|
||||
.find({ 'user.userid': { $in: openChats } })
|
||||
.map(u => u.user)
|
||||
.map(mapUser);
|
||||
.map(mapUser)
|
||||
.map(op => {
|
||||
op.unreadCounter = UnreadMessages.count(op.id);
|
||||
return op;
|
||||
});
|
||||
|
||||
openChats.push({
|
||||
id: 'public',
|
||||
name: 'Public Chat',
|
||||
icon: 'group-chat',
|
||||
unreadCounter: UnreadMessages.count('public_chat_userid'),
|
||||
});
|
||||
|
||||
return openChats
|
||||
|
@ -4,6 +4,7 @@ import { createContainer } from 'meteor/react-meteor-data';
|
||||
import Slide from './slide/component.jsx';
|
||||
import styles from './styles.scss';
|
||||
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
|
||||
import PollingContainer from '/imports/ui/components/polling/container';
|
||||
|
||||
export default class Whiteboard extends React.Component {
|
||||
constructor(props) {
|
||||
@ -36,7 +37,10 @@ export default class Whiteboard extends React.Component {
|
||||
<svg
|
||||
viewBox={`${x} ${y} ${viewBoxWidth} ${viewBoxHeight}`}
|
||||
version="1.1"
|
||||
xmlNS="http://www.w3.org/2000/svg"
|
||||
|
||||
//it's supposed to be here in theory
|
||||
//but now it's ignored by all the browsers and it's not supported by React
|
||||
//xmlNS="http://www.w3.org/2000/svg"
|
||||
className={styles.svgStyles}
|
||||
key={slideObj.id}
|
||||
>
|
||||
@ -69,8 +73,13 @@ export default class Whiteboard extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={styles.whiteboardPaper}>
|
||||
{this.renderWhiteboard()}
|
||||
<div className={styles.whiteboardContainer}>
|
||||
<div className={styles.whiteboardWrapper}>
|
||||
<div className={styles.whiteboardPaper}>
|
||||
{this.renderWhiteboard()}
|
||||
</div>
|
||||
</div>
|
||||
<PollingContainer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -77,13 +77,13 @@ export default class PollDrawComponent extends React.Component {
|
||||
|
||||
//counting the total number of votes, finding the biggest number of votes
|
||||
this.props.shape.result.reduce(function (previousValue, currentValue, currentIndex, array) {
|
||||
votesTotal += currentValue.num_votes;
|
||||
votesTotal = previousValue + currentValue.num_votes;
|
||||
if (maxNumVotes < currentValue.num_votes) {
|
||||
maxNumVotes = currentValue.num_votes;
|
||||
}
|
||||
|
||||
return votesTotal;
|
||||
});
|
||||
}, 0);
|
||||
|
||||
//filling the textArray with data to display
|
||||
//adding value of the iterator to each line needed to create unique
|
||||
|
@ -12,9 +12,9 @@ export default class Slide extends React.Component {
|
||||
<image x="0" y="0"
|
||||
width={this.props.currentSlide.slide.width}
|
||||
height={this.props.currentSlide.slide.height}
|
||||
xlink="http://www.w3.org/1999/xlink"
|
||||
xlinkHref={this.props.currentSlide.slide.img_uri}
|
||||
stroke-width="0.8">
|
||||
strokeWidth="0.8"
|
||||
>
|
||||
</image>
|
||||
: null }
|
||||
</g>
|
||||
|
@ -32,3 +32,18 @@
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.whiteboardContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.whiteboardWrapper {
|
||||
order: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import Storage from '/imports/ui/services/storage/session';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import Chats from '/imports/api/chat';
|
||||
|
||||
const PUBLIC_CHAT_USERID = 'public_chat_userid';
|
||||
const STORAGE_KEY = 'UNREAD_CHATS';
|
||||
|
||||
const get = (chatID) => {
|
||||
@ -20,18 +21,27 @@ const update = (chatID, timestamp = 0) => {
|
||||
return unreadChats[chatID];
|
||||
};
|
||||
|
||||
const count = (chatID) => Chats.find({
|
||||
const count = (chatID) => {
|
||||
let filter = {
|
||||
'message.from_time': {
|
||||
$gt: get(chatID),
|
||||
},
|
||||
'message.from_userid': { $ne: Auth.getUser() },
|
||||
$or: [
|
||||
{ 'message.to_userid': chatID },
|
||||
{ 'message.from_userid': chatID },
|
||||
],
|
||||
}).count();
|
||||
};
|
||||
|
||||
// Minimongo does not support $eq. See https://github.com/meteor/meteor/issues/4142
|
||||
if (chatID === PUBLIC_CHAT_USERID) {
|
||||
filter['message.to_userid'] = { $not: { $ne: chatID } };
|
||||
} else {
|
||||
filter['message.to_userid'] = { $not: { $ne: Auth.getUser() } };
|
||||
filter['message.from_userid'].$not = { $ne: chatID };
|
||||
}
|
||||
|
||||
return Chats.find(filter).count();
|
||||
};
|
||||
|
||||
export default {
|
||||
get,
|
||||
count,
|
||||
update,
|
||||
};
|
||||
|
@ -12,16 +12,17 @@
|
||||
"classnames": "^2.2.3",
|
||||
"history": "^2.1.1",
|
||||
"meteor-node-stubs": "^0.2.3",
|
||||
"react": "^15.0.1",
|
||||
"react-addons-pure-render-mixin": "^15.0.1",
|
||||
"react-dom": "^15.0.1",
|
||||
"react": "~15.2.0",
|
||||
"react-addons-pure-render-mixin": "~15.2.0",
|
||||
"react-dom": "~15.2.0",
|
||||
"image-size": "~0.5.0",
|
||||
"react-intl": "^2.1.2",
|
||||
"react-modal": "^1.2.1",
|
||||
"react-router": "^2.4.0",
|
||||
"react-addons-css-transition-group": "^15.1.0",
|
||||
"react-intl": "~2.1.3",
|
||||
"react-modal": "~1.4.0",
|
||||
"react-router": "~2.5.2",
|
||||
"react-addons-css-transition-group": "~15.2.0",
|
||||
"underscore": "~1.8.3",
|
||||
"react-autosize-textarea": "~0.3.1"
|
||||
"react-autosize-textarea": "~0.3.1",
|
||||
"grunt-cli": "~1.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^6.3.6",
|
||||
@ -34,11 +35,23 @@
|
||||
"grunt-shell": "~1.2.1",
|
||||
"jscs": "~2.11.0",
|
||||
"load-grunt-tasks": "~3.4.1",
|
||||
"grunt-newer": "~1.2.0"
|
||||
"grunt-newer": "~1.2.0",
|
||||
"postcss-modules-extract-imports": "1.0.0",
|
||||
"postcss-modules-local-by-default": "1.0.0",
|
||||
"postcss-modules-scope": "1.0.0",
|
||||
"postcss-modules-values": "1.1.1",
|
||||
"postcss-nested": "1.0.0"
|
||||
},
|
||||
"cssModules": {
|
||||
"extensions": [
|
||||
"scss"
|
||||
]
|
||||
],
|
||||
"postcssPlugins": {
|
||||
"postcss-nested": {},
|
||||
"postcss-modules-local-by-default": {},
|
||||
"postcss-modules-extract-imports": {},
|
||||
"postcss-modules-scope": {},
|
||||
"autoprefixer": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user