Handling and publishing multi-user mode switch

This commit is contained in:
Oleksandr Zhurbenko 2017-08-02 18:24:38 -07:00
parent b207a1275e
commit fa1458c3eb
15 changed files with 395 additions and 178 deletions

View File

@ -0,0 +1 @@
export default new Mongo.Collection('whiteboard-multi-user');

View File

@ -0,0 +1,7 @@
import RedisPubSub from '/imports/startup/server/redis2x';
import handleGetWhiteboardAccess from './handlers/getWhiteboardAccess';
import handleModifyWhiteboardAccess from './handlers/modifyWhiteboardAccess';
RedisPubSub.on('GetWhiteboardAccessRespMsg', handleGetWhiteboardAccess);
RedisPubSub.on('SyncGetWhiteboardAccessRespMsg', handleGetWhiteboardAccess);
RedisPubSub.on('ModifyWhiteboardAccessEvtMsg', handleModifyWhiteboardAccess);

View File

@ -0,0 +1,37 @@
import { check } from 'meteor/check';
import Logger from '/imports/startup/server/logger';
import WhiteboardMultiUser from '/imports/api/2.0/whiteboard-multi-user/';
export default function handleGetWhiteboardAccess({ body }, meetingId) {
const { multiUser } = body;
check(multiUser, Boolean);
check(meetingId, String);
const selector = {
meetingId,
};
const modifier = {
$set: {
meetingId,
multiUser,
},
};
const cb = (err, numChanged) => {
if (err) {
return Logger.error(`Error while adding an entry to Multi-User collection: ${err}`);
}
const { insertedId } = numChanged;
if (insertedId) {
return Logger.info(`Added multiUser flag to the meetingId=${meetingId}`);
}
return Logger.info(`Upserted multiUser flag into meetingId=${meetingId}`);
};
return WhiteboardMultiUser.upsert(selector, modifier, cb);
}

View File

@ -0,0 +1,37 @@
import { check } from 'meteor/check';
import Logger from '/imports/startup/server/logger';
import WhiteboardMultiUser from '/imports/api/2.0/whiteboard-multi-user/';
export default function handleModifyWhiteboardAccess({ body }, meetingId) {
const { multiUser } = body;
check(multiUser, Boolean);
check(meetingId, String);
const selector = {
meetingId,
};
const modifier = {
$set: {
meetingId,
multiUser,
},
};
const cb = (err, numChanged) => {
if (err) {
return Logger.error(`Error while adding an entry to Multi-User collection: ${err}`);
}
const { insertedId } = numChanged;
if (insertedId) {
return Logger.info(`Added multiUser flag to the meetingId=${meetingId}`);
}
return Logger.info(`Upserted multiUser flag into meetingId=${meetingId}`);
};
return WhiteboardMultiUser.upsert(selector, modifier, cb);
}

View File

@ -0,0 +1,3 @@
import './eventHandlers';
import './methods';
import './publishers';

View File

@ -0,0 +1,7 @@
import { Meteor } from 'meteor/meteor';
import mapToAcl from '/imports/startup/mapToAcl';
import changeWhiteboardAccess from './methods/changeWhiteboardAccess';
Meteor.methods(mapToAcl(['methods.modifyWhiteboardAccess'], {
changeWhiteboardAccess,
}));

View File

@ -0,0 +1,28 @@
import RedisPubSub from '/imports/startup/server/redis2x';
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
export default function changeWhiteboardAccess(credentials, multiUser) {
const REDIS_CONFIG = Meteor.settings.redis;
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
const EVENT_NAME = 'ModifyWhiteboardAccessPubMsg';
const { meetingId, requesterUserId, requesterToken } = credentials;
check(meetingId, String);
check(requesterUserId, String);
check(requesterToken, String);
check(multiUser, Boolean);
const header = {
name: EVENT_NAME,
userId: requesterUserId,
meetingId,
};
const payload = {
multiUser,
};
return RedisPubSub.publish(CHANNEL, EVENT_NAME, meetingId, payload, header);
}

View File

@ -0,0 +1,22 @@
import WhiteboardMultiUser from '/imports/api/2.0/whiteboard-multi-user/';
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
import Logger from '/imports/startup/server/logger';
import mapToAcl from '/imports/startup/mapToAcl';
function whiteboardMultiUser(credentials) {
const { meetingId, requesterUserId, requesterToken } = credentials;
check(meetingId, String);
Logger.info(`Publishing whiteboard-multi-user for ${meetingId} ${requesterUserId} ${requesterToken}`);
return WhiteboardMultiUser.find({ meetingId });
}
function publish(...args) {
const boundMultiUser = whiteboardMultiUser.bind(this);
return mapToAcl('subscriptions.whiteboard-multi-user', boundMultiUser)(args);
}
Meteor.publish('whiteboard-multi-user', publish);

View File

@ -89,7 +89,7 @@ Base.defaultProps = defaultProps;
const SUBSCRIPTIONS_NAME = [ const SUBSCRIPTIONS_NAME = [
'users2x', 'users', 'chat', 'chat2x', 'cursor', 'cursor2x', 'screenshare', 'meetings', 'meetings2x', 'users2x', 'users', 'chat', 'chat2x', 'cursor', 'cursor2x', 'screenshare', 'meetings', 'meetings2x',
'polls', 'polls2x', 'presentations', 'presentations2x', 'shapes', 'shapes2x', 'slides', 'slides2x', 'captions', 'polls', 'polls2x', 'presentations', 'presentations2x', 'shapes', 'shapes2x', 'slides', 'slides2x', 'captions',
'captions2x', 'breakouts', 'breakouts2x', 'voiceUsers', 'captions2x', 'breakouts', 'breakouts2x', 'voiceUsers', 'whiteboard-multi-user',
]; ];
const BaseContainer = createContainer(({ params }) => { const BaseContainer = createContainer(({ params }) => {

View File

@ -1,19 +1,26 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types';
import styles from './styles.scss';
import Button from '/imports/ui/components/button/component';
import cx from 'classnames';
import { findDOMNode } from 'react-dom'; import { findDOMNode } from 'react-dom';
import PropTypes from 'prop-types';
import cx from 'classnames';
import Button from '/imports/ui/components/button/component';
import styles from './styles.scss';
export default class WhiteboardToolbar extends Component { export default class WhiteboardToolbar extends Component {
static HEXToINTColor(hexColor) {
const _rrggbb = hexColor.slice(1);
const rrggbb = _rrggbb.substr(0, 2) + _rrggbb.substr(2, 2) + _rrggbb.substr(4, 2);
return parseInt(rrggbb, 16);
}
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
//a variable to control which list is currently open // a variable to control which list is currently open
currentSubmenuOpen: '', currentSubmenuOpen: '',
//variables to keep current selected draw settings // variables to keep current selected draw settings
annotationSelected: { annotationSelected: {
icon: 'hand', icon: 'hand',
sessionValue: 'Hand', sessionValue: 'Hand',
@ -25,14 +32,14 @@ export default class WhiteboardToolbar extends Component {
colorSelected: '#000000', colorSelected: '#000000',
fontSizeSelected: 18, fontSizeSelected: 18,
//keeping the previous color and the thickness icon's radius selected for svg animation // keeping the previous color and the thickness icon's radius selected for svg animation
prevColorSelected: '#000000', prevColorSelected: '#000000',
prevIconRadius: 4, prevIconRadius: 4,
//lists of tools/thickness/colors are not direct children of main toolbar buttons // lists of tools/thickness/colors are not direct children of main toolbar buttons
//and we want the list to close when onBlur fires at the main toolbar button // and we want the list to close when onBlur fires at the main toolbar button
//(click anywhere on the screen) thus we have to control the blur manually by disabling it // (click anywhere on the screen) thus we have to control the blur manually by disabling it
//when you hover over the buttons in the list and enabling when the mouse leaves the list // when you hover over the buttons in the list and enabling when the mouse leaves the list
onBlurEnabled: true, onBlurEnabled: true,
}; };
@ -40,6 +47,7 @@ export default class WhiteboardToolbar extends Component {
this.closeSubMenu = this.closeSubMenu.bind(this); this.closeSubMenu = this.closeSubMenu.bind(this);
this.handleUndo = this.handleUndo.bind(this); this.handleUndo = this.handleUndo.bind(this);
this.handleClearAll = this.handleClearAll.bind(this); this.handleClearAll = this.handleClearAll.bind(this);
this.handleSwitchWhiteboardMode = this.handleSwitchWhiteboardMode.bind(this);
this.handleAnnotationChange = this.handleAnnotationChange.bind(this); this.handleAnnotationChange = this.handleAnnotationChange.bind(this);
this.handleThicknessChange = this.handleThicknessChange.bind(this); this.handleThicknessChange = this.handleThicknessChange.bind(this);
this.handleFontSizeChange = this.handleFontSizeChange.bind(this); this.handleFontSizeChange = this.handleFontSizeChange.bind(this);
@ -49,13 +57,12 @@ export default class WhiteboardToolbar extends Component {
} }
componentWillMount() { componentWillMount() {
// setting default or resetting current drawing settings in the session
//setting default or resetting current drawing settings in the session
const { annotationSelected, thicknessSelected, colorSelected, fontSizeSelected } = this.state; const { annotationSelected, thicknessSelected, colorSelected, fontSizeSelected } = this.state;
this.props.actions.setWhiteboardToolbarValues( this.props.actions.setWhiteboardToolbarValues(
annotationSelected.sessionValue, annotationSelected.sessionValue,
thicknessSelected.sessionRadius, thicknessSelected.sessionRadius,
this.HEXToINTColor(colorSelected), WhiteboardToolbar.HEXToINTColor(colorSelected),
fontSizeSelected, fontSizeSelected,
{ {
textShapeValue: '', textShapeValue: '',
@ -65,27 +72,27 @@ export default class WhiteboardToolbar extends Component {
} }
componentDidMount() { componentDidMount() {
//to let the whiteboard know that the presentation area's size has changed // to let the whiteboard know that the presentation area's size has changed
window.dispatchEvent(new Event('resize')); window.dispatchEvent(new Event('resize'));
if(this.state.annotationSelected.sessionValue != "Text") { if (this.state.annotationSelected.sessionValue !== 'Text') {
//trigger initial animation on the thickness circle, otherwise it stays at 0 // trigger initial animation on the thickness circle, otherwise it stays at 0
var node = findDOMNode(this.thicknessListIconRadius); const node = findDOMNode(this.thicknessListIconRadius);
node.beginElement(); node.beginElement();
} }
} }
componentWillUnmount() {
//to let the whiteboard know that the presentation area's size has changed
window.dispatchEvent(new Event('resize'));
}
componentDidUpdate(prevProps, prevState) { componentDidUpdate(prevProps, prevState) {
// if color or thickness were changed // if color or thickness were changed
// we might need to trigger svg animation for Color and Thickness icons // we might need to trigger svg animation for Color and Thickness icons
this.animateSvgIcons(prevState); this.animateSvgIcons(prevState);
} }
componentWillUnmount() {
// to let the whiteboard know that the presentation area's size has changed
window.dispatchEvent(new Event('resize'));
}
animateSvgIcons(prevState) { animateSvgIcons(prevState) {
/* Animation for the svg icons that we use for thickness (circle) and color (rectangle) /* Animation for the svg icons that we use for thickness (circle) and color (rectangle)
* has to be triggered manually * has to be triggered manually
@ -100,9 +107,9 @@ export default class WhiteboardToolbar extends Component {
*/ */
// 1st case // 1st case
if(this.state.colorSelected != prevState.colorSelected) { if (this.state.colorSelected !== prevState.colorSelected) {
// 1st case a) // 1st case a)
if(this.state.annotationSelected.sessionValue == "Text") { if (this.state.annotationSelected.sessionValue === 'Text') {
const node = findDOMNode(this.colorListIconColor); const node = findDOMNode(this.colorListIconColor);
node.beginElement(); node.beginElement();
// 1st case b) // 1st case b)
@ -113,14 +120,12 @@ export default class WhiteboardToolbar extends Component {
node2.beginElement(); node2.beginElement();
} }
// 2nd case // 2nd case
} else if(this.state.thicknessSelected.iconRadius != prevState.thicknessSelected.iconRadius) { } else if (this.state.thicknessSelected.iconRadius !== prevState.thicknessSelected.iconRadius) {
const node = findDOMNode(this.thicknessListIconRadius); const node = findDOMNode(this.thicknessListIconRadius);
node.beginElement(); node.beginElement();
}
// 3rd case // 3rd case
else if(this.state.annotationSelected.sessionValue != "Text" && } else if (this.state.annotationSelected.sessionValue !== 'Text' &&
prevState.annotationSelected.sessionValue == "Text") { prevState.annotationSelected.sessionValue === 'Text') {
const node = findDOMNode(this.thicknessListIconRadius); const node = findDOMNode(this.thicknessListIconRadius);
const node2 = findDOMNode(this.thicknessListIconColor); const node2 = findDOMNode(this.thicknessListIconColor);
node.beginElement(); node.beginElement();
@ -130,42 +135,44 @@ export default class WhiteboardToolbar extends Component {
// 4th case, initial animation (just thickness) is triggered in componentDidMount // 4th case, initial animation (just thickness) is triggered in componentDidMount
} }
//open a submenu // open a submenu
displaySubMenu(listName) { displaySubMenu(listName) {
this.setState({ this.setState({
currentSubmenuOpen: this.state.currentSubmenuOpen == listName ? '' : listName, currentSubmenuOpen: this.state.currentSubmenuOpen === listName ? '' : listName,
}); });
} }
//close a current submenu (fires onBlur only, when you click anywhere on the screen) // close a current submenu (fires onBlur only, when you click anywhere on the screen)
closeSubMenu() { closeSubMenu() {
// a separate case for the active text shape // a separate case for the active text shape
if(this.state.annotationSelected.sessionValue == "Text" && this.props.textShapeActiveId != '') { if (this.state.annotationSelected.sessionValue === 'Text' && this.props.textShapeActiveId !== '') {
return; return;
} }
if(this.state.onBlurEnabled) { if (this.state.onBlurEnabled) {
this.setState({ this.setState({
currentSubmenuOpen: undefined, currentSubmenuOpen: undefined,
}); });
} }
} }
//undo annotation // undo annotation
handleUndo() { handleUndo() {
this.props.actions.undoAnnotation(this.props.whiteboardId); this.props.actions.undoAnnotation(this.props.whiteboardId);
} }
//clear all annotations // clear all annotations
handleClearAll() { handleClearAll() {
this.props.actions.clearWhiteboard(this.props.whiteboardId); this.props.actions.clearWhiteboard(this.props.whiteboardId);
} }
//changes a current selected annotation both in the state and in the session handleSwitchWhiteboardMode() {
//and closes the annotation list this.props.actions.changeWhiteboardMode(!this.props.multiUser);
handleAnnotationChange(annotation) { }
// changes a current selected annotation both in the state and in the session
// and closes the annotation list
handleAnnotationChange(annotation) {
const obj = { const obj = {
annotationSelected: annotation, annotationSelected: annotation,
onBlurEnabled: true, onBlurEnabled: true,
@ -173,7 +180,7 @@ export default class WhiteboardToolbar extends Component {
}; };
// to animate thickness icon properly when you switch the tool back from Text // to animate thickness icon properly when you switch the tool back from Text
if(annotation.sessionValue == "Text") { if (annotation.sessionValue === 'Text') {
obj.prevIconRadius = 0; obj.prevIconRadius = 0;
} }
@ -181,8 +188,8 @@ export default class WhiteboardToolbar extends Component {
this.setState(obj); this.setState(obj);
} }
//changes a current selected thickness both in the state and in the session // changes a current selected thickness both in the state and in the session
//and closes the thickness list // and closes the thickness list
handleThicknessChange(thicknessObj) { handleThicknessChange(thicknessObj) {
this.props.actions.setThickness(thicknessObj.sessionRadius); this.props.actions.setThickness(thicknessObj.sessionRadius);
@ -204,10 +211,10 @@ export default class WhiteboardToolbar extends Component {
}); });
} }
//changes a current selected color both in the state and in the session // changes a current selected color both in the state and in the session
//and closes the color list // and closes the color list
handleColorChange(color) { handleColorChange(color) {
this.props.actions.setColor(this.HEXToINTColor(color)); this.props.actions.setColor(WhiteboardToolbar.HEXToINTColor(color));
this.setState({ this.setState({
prevColorSelected: this.state.colorSelected, prevColorSelected: this.state.colorSelected,
@ -217,20 +224,14 @@ export default class WhiteboardToolbar extends Component {
}); });
} }
HEXToINTColor(hexColor) { // disabling onBlur flag when mouse is over the items in the lists
var _rrggbb = hexColor.slice(1);
var rrggbb = _rrggbb.substr(0, 2) + _rrggbb.substr(2, 2) + _rrggbb.substr(4, 2);
return parseInt(rrggbb, 16);
}
//disabling onBlur flag when mouse is over the items in the lists
disableOnBlur() { disableOnBlur() {
this.setState({ this.setState({
onBlurEnabled: false, onBlurEnabled: false,
}); });
} }
//enabling the onBlur flag when the mouse leaving the lists // enabling the onBlur flag when the mouse leaving the lists
enableOnBlur() { enableOnBlur() {
this.setState({ this.setState({
onBlurEnabled: true, onBlurEnabled: true,
@ -238,150 +239,158 @@ export default class WhiteboardToolbar extends Component {
} }
renderAnnotationList() { renderAnnotationList() {
const { annotations } = this.props;
return ( return (
<div className={cx(styles.annotationList, styles.toolbarList)}> <div className={cx(styles.annotationList, styles.toolbarList)}>
{ this.props.annotations? this.props.annotations.map((annotation, index) => { annotations ? annotations.map(annotation =>
<Button (<Button
label="Annotation" label="Annotation"
hideLabel={true} hideLabel
color={'default'} color={'default'}
icon={annotation.icon} icon={annotation.icon}
size={'md'} size={'md'}
className={cx(styles.toolbarListButton, this.state.annotationSelected.sessionValue == annotation.sessionRadius ? styles.selectedListButton : '')} className={cx(styles.toolbarListButton, this.state.annotationSelected.sessionValue === annotation.sessionValue ? styles.selectedListButton : '')}
onClick={this.handleAnnotationChange.bind(null, annotation)} onClick={this.handleAnnotationChange.bind(null, annotation)}
onMouseEnter={this.disableOnBlur} onMouseEnter={this.disableOnBlur}
onMouseLeave={this.enableOnBlur} onMouseLeave={this.enableOnBlur}
key={index} key={annotation.sessionValue}
role="button" role="button"
/> />),
) : null} ) : null}
</div> </div>
); );
} }
renderFontSizeList() { renderFontSizeList() {
const { fontSizes } = this.props;
return ( return (
<div className={cx(styles.fontSizeList, styles.toolbarList)}> <div className={cx(styles.fontSizeList, styles.toolbarList)}>
{this.props.fontSizes ? this.props.fontSizes.map((fontSizeObj, index) => {fontSizes ? fontSizes.map(fontSizeObj =>
<Button (<Button
hideLabel={true} hideLabel
label="Radius" label="Radius"
color={'default'} color={'default'}
size={'md'} size={'md'}
className={cx(styles.toolbarListButton, styles.fontSizeListButton, this.state.fontSizeSelected == fontSizeObj.fontSize ? styles.selectedListButton : '')} className={cx(styles.toolbarListButton, styles.fontSizeListButton, this.state.fontSizeSelected === fontSizeObj.fontSize ? styles.selectedListButton : '')}
onClick={this.handleFontSizeChange.bind(null, fontSizeObj)} onClick={this.handleFontSizeChange.bind(null, fontSizeObj)}
onMouseEnter={this.disableOnBlur} onMouseEnter={this.disableOnBlur}
onMouseLeave={this.enableOnBlur} onMouseLeave={this.enableOnBlur}
key={index} key={fontSizeObj.fontSize}
customIcon={ customIcon={
<p className={styles.textThickness} style={{fontSize: fontSizeObj.fontSize}}> <p className={styles.textThickness} style={{ fontSize: fontSizeObj.fontSize }}>
Aa Aa
</p> </p>
} }
> />),
</Button>
) : null} ) : null}
</div> </div>
); );
} }
renderThicknessList() { renderThicknessList() {
const { thicknessRadiuses } = this.props;
return ( return (
<div className={cx(styles.thicknessList, styles.toolbarList)}> <div className={cx(styles.thicknessList, styles.toolbarList)}>
{this.props.thicknessRadiuses ? this.props.thicknessRadiuses.map((thicknessObj, index) => {thicknessRadiuses ? thicknessRadiuses.map(thicknessObj =>
<Button (<Button
label="Radius" label="Radius"
hideLabel={true} hideLabel
color={'default'} color={'default'}
size={'md'} size={'md'}
className={cx(styles.toolbarListButton, this.state.thicknessSelected.sessionRadius == thicknessObj.sessionRadius ? styles.selectedListButton : '')} className={cx(styles.toolbarListButton, this.state.thicknessSelected.sessionRadius === thicknessObj.sessionRadius ? styles.selectedListButton : '')}
onClick={this.handleThicknessChange.bind(null, thicknessObj)} onClick={this.handleThicknessChange.bind(null, thicknessObj)}
onMouseEnter={this.disableOnBlur} onMouseEnter={this.disableOnBlur}
onMouseLeave={this.enableOnBlur} onMouseLeave={this.enableOnBlur}
customIcon={ customIcon={
<svg className={styles.customSvgIcon}> <svg className={styles.customSvgIcon}>
<circle cx="50%" cy="50%" r={thicknessObj.iconRadius} fill="#F3F6F9"/> <circle cx="50%" cy="50%" r={thicknessObj.iconRadius} fill="#F3F6F9" />
</svg> </svg>
} }
key={index} key={thicknessObj.sessionRadius}
/> />),
) : null} ) : null}
</div> </div>
); );
} }
renderColorList() { renderColorList() {
const { colors } = this.props;
return ( return (
<div className={cx(styles.colorList, styles.toolbarList)}> <div className={cx(styles.colorList, styles.toolbarList)}>
{this.props.colors ? this.props.colors.map((color) => {colors ? colors.map(color =>
<Button (<Button
label="Color" label="Color"
hideLabel={true} hideLabel
color={'default'} color={'default'}
size={'md'} size={'md'}
className={cx(styles.toolbarListButton, this.state.colorSelected == color ? styles.selectedListButton : '')} className={cx(styles.toolbarListButton, this.state.colorSelected === color ? styles.selectedListButton : '')}
onClick={this.handleColorChange.bind(null, color)} onClick={this.handleColorChange.bind(null, color)}
onMouseEnter={this.disableOnBlur} onMouseEnter={this.disableOnBlur}
onMouseLeave={this.enableOnBlur} onMouseLeave={this.enableOnBlur}
customIcon={ customIcon={
<svg className={styles.customSvgIcon}> <svg className={styles.customSvgIcon}>
<rect x="20%" y="20%" width="60%" height="60%" fill={color}/> <rect x="20%" y="20%" width="60%" height="60%" fill={color} />
</svg> </svg>
} }
key={color} key={color}
role="button" role="button"
aria-labelledby={`${color}Label`} aria-labelledby={`${color}Label`}
aria-describedby={`${color}Descrip`} aria-describedby={`${color}Descrip`}
/> />),
) : null} ) : null}
</div> </div>
); );
} }
render() { render() {
return ( return (
<div className={styles.toolbarContainer} style={{height: this.props.height}}> <div className={styles.toolbarContainer} style={{ height: this.props.height }}>
<div className={styles.toolbarWrapper}> <div className={styles.toolbarWrapper}>
<div className={styles.buttonWrapper}> <div className={styles.buttonWrapper}>
<Button <Button
label="Tools" label="Tools"
hideLabel={true} hideLabel
role="button" role="button"
color={'default'} color={'default'}
icon={this.state.annotationSelected.icon} icon={this.state.annotationSelected.icon}
size={'md'} size={'md'}
onClick={this.displaySubMenu.bind(null, "annotationList")} onClick={this.displaySubMenu.bind(null, 'annotationList')}
onBlur={this.closeSubMenu} onBlur={this.closeSubMenu}
className={cx(styles.toolbarButton, this.state.currentSubmenuOpen == "annotationList" ? '' : styles.notActive)} className={cx(styles.toolbarButton, this.state.currentSubmenuOpen === 'annotationList' ? '' : styles.notActive)}
/> />
{this.state.currentSubmenuOpen == "annotationList" ? {this.state.currentSubmenuOpen === 'annotationList' ?
this.renderAnnotationList() this.renderAnnotationList()
: null } : null }
</div> </div>
{this.state.annotationSelected.sessionValue == "Text" ? {this.state.annotationSelected.sessionValue === 'Text' ?
<div className={styles.buttonWrapper}> <div className={styles.buttonWrapper}>
<Button <Button
label="Thickness List" label="Thickness List"
hideLabel={true} hideLabel
role="button" role="button"
color={'default'} color={'default'}
size={'md'} size={'md'}
onClick={this.displaySubMenu.bind(null, "fontSizeList")} onClick={this.displaySubMenu.bind(null, 'fontSizeList')}
onBlur={this.closeSubMenu} onBlur={this.closeSubMenu}
className={cx(styles.toolbarButton, this.state.currentSubmenuOpen == "fontSizeList" ? '' : styles.notActive)} className={cx(styles.toolbarButton, this.state.currentSubmenuOpen === 'fontSizeList' ? '' : styles.notActive)}
customIcon={ customIcon={
<p className={styles.textThickness} style={{fontSize: this.state.fontSizeSelected, color: this.state.colorSelected}}> <p
className={styles.textThickness}
style={{
fontSize: this.state.fontSizeSelected,
color: this.state.colorSelected,
}}
>
Aa Aa
</p> </p>
} }
/> />
{this.state.currentSubmenuOpen == "fontSizeList" ? {this.state.currentSubmenuOpen === 'fontSizeList' ?
this.renderFontSizeList() this.renderFontSizeList()
: null } : null }
</div> </div>
@ -389,13 +398,13 @@ export default class WhiteboardToolbar extends Component {
<div className={styles.buttonWrapper}> <div className={styles.buttonWrapper}>
<Button <Button
label="Thickness List" label="Thickness List"
hideLabel={true} hideLabel
role="button" role="button"
color={'default'} color={'default'}
size={'md'} size={'md'}
onClick={this.displaySubMenu.bind(null, "thicknessList")} onClick={this.displaySubMenu.bind(null, 'thicknessList')}
onBlur={this.closeSubMenu} onBlur={this.closeSubMenu}
className={cx(styles.toolbarButton, this.state.currentSubmenuOpen == "thicknessList" ? '' : styles.notActive)} className={cx(styles.toolbarButton, this.state.currentSubmenuOpen === 'thicknessList' ? '' : styles.notActive)}
customIcon={ customIcon={
<svg className={styles.customSvgIcon} shapeRendering="geometricPrecision"> <svg className={styles.customSvgIcon} shapeRendering="geometricPrecision">
<circle <circle
@ -431,7 +440,7 @@ export default class WhiteboardToolbar extends Component {
</svg> </svg>
} }
/> />
{this.state.currentSubmenuOpen == "thicknessList" ? {this.state.currentSubmenuOpen === 'thicknessList' ?
this.renderThicknessList() this.renderThicknessList()
: null } : null }
</div> </div>
@ -439,13 +448,13 @@ export default class WhiteboardToolbar extends Component {
<div className={styles.buttonWrapper}> <div className={styles.buttonWrapper}>
<Button <Button
label="Color List" label="Color List"
hideLabel={true} hideLabel
role="button" role="button"
color={'default'} color={'default'}
size={'md'} size={'md'}
onClick={this.displaySubMenu.bind(null, "colorList")} onClick={this.displaySubMenu.bind(null, 'colorList')}
onBlur={this.closeSubMenu} onBlur={this.closeSubMenu}
className={cx(styles.toolbarButton, this.state.currentSubmenuOpen == "colorList" ? '' : styles.notActive)} className={cx(styles.toolbarButton, this.state.currentSubmenuOpen === 'colorList' ? '' : styles.notActive)}
customIcon={ customIcon={
<svg className={styles.customSvgIcon}> <svg className={styles.customSvgIcon}>
<rect x="25%" y="25%" width="50%" height="50%" stroke="black" strokeWidth="1"> <rect x="25%" y="25%" width="50%" height="50%" stroke="black" strokeWidth="1">
@ -464,14 +473,14 @@ export default class WhiteboardToolbar extends Component {
</svg> </svg>
} }
/> />
{this.state.currentSubmenuOpen == "colorList" ? {this.state.currentSubmenuOpen === 'colorList' ?
this.renderColorList() this.renderColorList()
: null } : null }
</div> </div>
<div className={styles.buttonWrapper}> <div className={styles.buttonWrapper}>
<Button <Button
label="Undo Annotation" label="Undo Annotation"
hideLabel={true} hideLabel
role="button" role="button"
color={'default'} color={'default'}
icon={'undo'} icon={'undo'}
@ -483,7 +492,7 @@ export default class WhiteboardToolbar extends Component {
<div className={styles.buttonWrapper}> <div className={styles.buttonWrapper}>
<Button <Button
label="Clear All Annotations" label="Clear All Annotations"
hideLabel={true} hideLabel
role="button" role="button"
color={'default'} color={'default'}
icon={'circle_close'} icon={'circle_close'}
@ -492,44 +501,89 @@ export default class WhiteboardToolbar extends Component {
className={cx(styles.toolbarButton, styles.notActive)} className={cx(styles.toolbarButton, styles.notActive)}
/> />
</div> </div>
{this.props.isPresenter ?
<div className={styles.buttonWrapper}>
<Button
label={this.props.multiUser ? 'Turn multi-user mode off' : 'Tuen multi-user mode on'}
hideLabel
role="button"
color={'default'}
icon={this.props.multiUser ? 'whiteboard' : 'multi_whiteboard'}
size={'md'}
onClick={this.handleSwitchWhiteboardMode}
className={cx(styles.toolbarButton, styles.notActive)}
/>
</div>
: null}
</div> </div>
</div> </div>
); );
} }
} }
const defaultProps = { WhiteboardToolbar.defaultProps = {
colors: [ colors: [
'#000000', '#FFFFFF', '#FF0000', '#FF8800', '#CCFF00','#00FF00', '#000000', '#FFFFFF', '#FF0000', '#FF8800', '#CCFF00', '#00FF00',
'#00FFFF', '#0088FF', '#0000FF', '#8800FF', '#FF00FF', '#C0C0C0' '#00FFFF', '#0088FF', '#0000FF', '#8800FF', '#FF00FF', '#C0C0C0',
], ],
thicknessRadiuses: [ thicknessRadiuses: [
{iconRadius: 14, sessionRadius: 30}, { iconRadius: 14, sessionRadius: 30 },
{iconRadius: 12, sessionRadius: 22}, { iconRadius: 12, sessionRadius: 22 },
{iconRadius: 10, sessionRadius: 15}, { iconRadius: 10, sessionRadius: 15 },
{iconRadius: 8, sessionRadius: 10}, { iconRadius: 8, sessionRadius: 10 },
{iconRadius: 6, sessionRadius:6}, { iconRadius: 6, sessionRadius: 6 },
{iconRadius: 4, sessionRadius: 3}, { iconRadius: 4, sessionRadius: 3 },
{iconRadius: 2, sessionRadius: 1} { iconRadius: 2, sessionRadius: 1 },
], ],
annotations: [ annotations: [
{icon: 'text_tool', sessionValue: "Text"}, { icon: 'text_tool', sessionValue: 'Text' },
{icon: 'linte_tool', sessionValue:"Line"}, { icon: 'linte_tool', sessionValue: 'Line' },
{icon: 'circle_tool', sessionValue: "Ellipse"}, { icon: 'circle_tool', sessionValue: 'Ellipse' },
{icon: 'triangle_tool', sessionValue: "Triangle"}, { icon: 'triangle_tool', sessionValue: 'Triangle' },
{icon: 'rectangle_tool', sessionValue: "Rectangle"}, { icon: 'rectangle_tool', sessionValue: 'Rectangle' },
{icon: 'pen_tool', sessionValue: "Pencil"}, { icon: 'pen_tool', sessionValue: 'Pencil' },
{icon: 'hand', sessionValue: "Hand"} { icon: 'hand', sessionValue: 'Hand' },
], ],
fontSizes: [ fontSizes: [
{fontSize: 36}, { fontSize: 36 },
{fontSize: 32}, { fontSize: 32 },
{fontSize: 28}, { fontSize: 28 },
{fontSize: 24}, { fontSize: 24 },
{fontSize: 20}, { fontSize: 20 },
{fontSize: 16}, { fontSize: 16 },
{fontSize: 12}, { fontSize: 12 },
], ],
}; };
WhiteboardToolbar.defaultProps = defaultProps; WhiteboardToolbar.propTypes = {
// defines a current mode of the whiteboard, multi/single user
multiUser: PropTypes.bool.isRequired,
// defines whether a current user is a presenter or not
isPresenter: PropTypes.bool.isRequired,
// defines an object of available actions
actions: PropTypes.objectOf(PropTypes.func).isRequired,
// defines the id of the active text shape (if any)
// for the separate onBlur case in the closeSubMenu()
textShapeActiveId: PropTypes.string.isRequired,
// defines a current whiteboard id
whiteboardId: PropTypes.string.isRequired,
// defines an array of icons for the toolbar as well as their corresponding session values
annotations: PropTypes.arrayOf(PropTypes.object).isRequired,
// defines an array of font-sizes for the Font-size submenu of the text shape
fontSizes: PropTypes.arrayOf(PropTypes.object).isRequired,
// defines an array of colors for the toolbar (color submenu)
colors: PropTypes.arrayOf(PropTypes.string).isRequired,
// defines an array of thickness values for the toolbar and their corresponding session values
thicknessRadiuses: PropTypes.arrayOf(PropTypes.object).isRequired,
// defines the physical height of the whiteboard
height: PropTypes.number.isRequired,
};

View File

@ -3,19 +3,13 @@ import { createContainer } from 'meteor/react-meteor-data';
import WhiteboardToolbarService from './service'; import WhiteboardToolbarService from './service';
import WhiteboardToolbar from './component'; import WhiteboardToolbar from './component';
class WhiteboardToolbarContainer extends React.Component { const WhiteboardToolbarContainer = ({ ...props }) => (
constructor(props) { <WhiteboardToolbar {...props} />
super(props);
}
render() {
return (
<WhiteboardToolbar {...this.props}/>
); );
}
}
export default createContainer(() => ({ export default createContainer(() => ({
actions: WhiteboardToolbarService.actions, actions: WhiteboardToolbarService.actions,
textShapeActiveId: WhiteboardToolbarService.getTextShapeActiveId(), textShapeActiveId: WhiteboardToolbarService.getTextShapeActiveId(),
multiUser: WhiteboardToolbarService.getMultiUserStatus(),
isPresenter: WhiteboardToolbarService.isPresenter(),
}), WhiteboardToolbarContainer); }), WhiteboardToolbarContainer);

View File

@ -1,6 +1,8 @@
import { makeCall } from '/imports/ui/services/api'; import { makeCall } from '/imports/ui/services/api';
import Storage from '/imports/ui/services/storage/session'; import Storage from '/imports/ui/services/storage/session';
import Users from '/imports/api/2.0/users';
import Auth from '/imports/ui/services/auth';
import WhiteboardMultiUser from '/imports/api/2.0/whiteboard-multi-user/';
const actions = { const actions = {
undoAnnotation: (whiteboardId) => { undoAnnotation: (whiteboardId) => {
@ -11,53 +13,56 @@ const actions = {
makeCall('clearWhiteboard', whiteboardId); makeCall('clearWhiteboard', whiteboardId);
}, },
changeWhiteboardMode: (multiUser) => {
makeCall('changeWhiteboardAccess', multiUser);
},
setWhiteboardToolbarValues: (tool, thickness, color, fontSize, textShape) => { setWhiteboardToolbarValues: (tool, thickness, color, fontSize, textShape) => {
let drawSettings = { const drawSettings = {
whiteboardAnnotationTool: tool, whiteboardAnnotationTool: tool,
whiteboardAnnotationThickness: thickness, whiteboardAnnotationThickness: thickness,
whiteboardAnnotationColor: color, whiteboardAnnotationColor: color,
textFontSize: fontSize, textFontSize: fontSize,
textShape: textShape, textShape,
}; };
Storage.setItem('drawSettings', JSON.stringify(drawSettings)); Storage.setItem('drawSettings', JSON.stringify(drawSettings));
}, },
setTool: (tool) => { setTool: (tool) => {
let drawSettings = Storage.getItem('drawSettings'); const drawSettings = Storage.getItem('drawSettings');
if(drawSettings) { if (drawSettings) {
drawSettings.whiteboardAnnotationTool = tool; drawSettings.whiteboardAnnotationTool = tool;
Storage.setItem('drawSettings', JSON.stringify(drawSettings)); Storage.setItem('drawSettings', JSON.stringify(drawSettings));
} }
}, },
setThickness: (thickness) => { setThickness: (thickness) => {
let drawSettings = Storage.getItem('drawSettings'); const drawSettings = Storage.getItem('drawSettings');
if(drawSettings) { if (drawSettings) {
drawSettings.whiteboardAnnotationThickness = thickness; drawSettings.whiteboardAnnotationThickness = thickness;
Storage.setItem('drawSettings', JSON.stringify(drawSettings)); Storage.setItem('drawSettings', JSON.stringify(drawSettings));
} }
}, },
setColor: (color) => { setColor: (color) => {
let drawSettings = Storage.getItem('drawSettings'); const drawSettings = Storage.getItem('drawSettings');
if(drawSettings) { if (drawSettings) {
drawSettings.whiteboardAnnotationColor = color; drawSettings.whiteboardAnnotationColor = color;
Storage.setItem('drawSettings', JSON.stringify(drawSettings)); Storage.setItem('drawSettings', JSON.stringify(drawSettings));
} }
}, },
setFontSize: (fontSize) => { setFontSize: (fontSize) => {
let drawSettings = Storage.getItem('drawSettings'); const drawSettings = Storage.getItem('drawSettings');
if(drawSettings) { if (drawSettings) {
drawSettings.textFontSize = fontSize; drawSettings.textFontSize = fontSize;
Storage.setItem('drawSettings', JSON.stringify(drawSettings)); Storage.setItem('drawSettings', JSON.stringify(drawSettings));
} }
}, },
setTextShapeObject: (textShape) => { setTextShapeObject: (textShape) => {
let drawSettings = Storage.getItem('drawSettings'); const drawSettings = Storage.getItem('drawSettings');
if(drawSettings) { if (drawSettings) {
drawSettings.textShape = textShape; drawSettings.textShape = textShape;
Storage.setItem('drawSettings', JSON.stringify(drawSettings)); Storage.setItem('drawSettings', JSON.stringify(drawSettings));
} }
@ -65,13 +70,37 @@ const actions = {
}; };
const getTextShapeActiveId = () => { const getTextShapeActiveId = () => {
let drawSettings = Storage.getItem('drawSettings'); const drawSettings = Storage.getItem('drawSettings');
if(drawSettings) { if (drawSettings) {
return drawSettings.textShape.textShapeActiveId; return drawSettings.textShape.textShapeActiveId;
} }
return '';
};
const getMultiUserStatus = () => {
const data = WhiteboardMultiUser.findOne({ meetingId: Auth.meetingID });
if (data) {
return data.multiUser;
}
return false;
};
const isPresenter = () => {
const currentUser = Users.findOne({ userId: Auth.userID });
if (currentUser && currentUser.user) {
return currentUser.user.presenter;
}
return false;
}; };
export default { export default {
actions, actions,
getTextShapeActiveId, getTextShapeActiveId,
getMultiUserStatus,
isPresenter,
}; };

View File

@ -30,11 +30,6 @@ $number-of-vertical-main-toolbar-buttons: 5;
.toolbarWrapper { .toolbarWrapper {
width: 100%; width: 100%;
height: calc((
#{$toolbar-button-height} +
#{$toolbar-button-margin-top} +
#{$toolbar-button-margin-bottom}) * #{$number-of-vertical-main-toolbar-buttons}
);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;

View File

@ -13,6 +13,7 @@ acl:
- 'captions' - 'captions'
- 'breakouts' - 'breakouts'
- 'voiceUsers' - 'voiceUsers'
- 'whiteboard-multi-user'
methods: methods:
- 'listenOnlyToggle' - 'listenOnlyToggle'
- 'userLogout' - 'userLogout'
@ -29,3 +30,4 @@ acl:
methods: methods:
- 'assignPresenter' - 'assignPresenter'
- 'switchSlide' - 'switchSlide'
- 'modifyWhiteboardAccess'

View File

@ -24,6 +24,7 @@ import '/imports/api/2.0/breakouts/server';
import '/imports/api/2.0/chat/server'; import '/imports/api/2.0/chat/server';
import '/imports/api/2.0/screenshare/server'; import '/imports/api/2.0/screenshare/server';
import '/imports/api/2.0/voice-users/server'; import '/imports/api/2.0/voice-users/server';
import '/imports/api/2.0/whiteboard-multi-user/server';
// Commons // Commons
import '/imports/api/log-client/server'; import '/imports/api/log-client/server';