Merge branch 'master' of https://github.com/bigbluebutton/bigbluebutton into upgrade-red5-nov-22-2016-snapshot
This commit is contained in:
commit
faa2e35307
@ -0,0 +1,4 @@
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import handleCursorUpdate from './handlers/cursorUpdate';
|
||||
|
||||
RedisPubSub.on('presentation_cursor_updated_message', handleCursorUpdate);
|
@ -0,0 +1,15 @@
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { check } from 'meteor/check';
|
||||
import updateCursor from '../modifiers/updateCursor';
|
||||
|
||||
export default function handleCursorUpdate({ payload }) {
|
||||
const meetingId = payload.meeting_id;
|
||||
const x = payload.x_percent;
|
||||
const y = payload.y_percent;
|
||||
|
||||
check(meetingId, String);
|
||||
check(x, Number);
|
||||
check(y, Number);
|
||||
|
||||
return updateCursor(meetingId, x, y);
|
||||
};
|
3
bigbluebutton-html5/imports/api/cursor/server/index.js
Normal file
3
bigbluebutton-html5/imports/api/cursor/server/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
import './eventHandlers';
|
||||
import './methods';
|
||||
import './publishers';
|
4
bigbluebutton-html5/imports/api/cursor/server/methods.js
Normal file
4
bigbluebutton-html5/imports/api/cursor/server/methods.js
Normal file
@ -0,0 +1,4 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
Meteor.methods({
|
||||
});
|
10
bigbluebutton-html5/imports/api/cursor/server/modifiers/clearCursor.js
Executable file
10
bigbluebutton-html5/imports/api/cursor/server/modifiers/clearCursor.js
Executable file
@ -0,0 +1,10 @@
|
||||
import Cursor from '/imports/api/cursor';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
export default function clearCursor(meetingId) {
|
||||
if (meetingId) {
|
||||
return Cursor.remove({ meetingId }, Logger.info(`Cleared Cursor (${meetingId})`));
|
||||
}
|
||||
|
||||
return Cursor.remove({}, Logger.info('Cleared Cursor (all)'));
|
||||
};
|
@ -1,14 +0,0 @@
|
||||
import Cursor from '/imports/api/cursor';
|
||||
import { logger } from '/imports/startup/server/logger';
|
||||
|
||||
// called on server start and meeting end
|
||||
export function clearCursorCollection() {
|
||||
const meetingId = arguments[0];
|
||||
if (meetingId != null) {
|
||||
return Cursor.remove({
|
||||
meetingId: meetingId,
|
||||
}, () => logger.info(`cleared Cursor Collection (meetingId: ${meetingId})!`));
|
||||
} else {
|
||||
return Cursor.remove({}, () => logger.info('cleared Cursor Collection (all meetings)!'));
|
||||
}
|
||||
};
|
@ -1,14 +0,0 @@
|
||||
import { updateCursorLocation } from './updateCursorLocation';
|
||||
import { eventEmitter } from '/imports/startup/server';
|
||||
|
||||
eventEmitter.on('presentation_cursor_updated_message', function (arg) {
|
||||
const meetingId = arg.payload.meeting_id;
|
||||
const cursor = {
|
||||
x: arg.payload.x_percent,
|
||||
y: arg.payload.y_percent,
|
||||
};
|
||||
|
||||
// update the location of the cursor on the whiteboard
|
||||
updateCursorLocation(meetingId, cursor);
|
||||
return arg.callback();
|
||||
});
|
@ -1,17 +1,8 @@
|
||||
import Cursor from '/imports/api/cursor';
|
||||
import { logger } from '/imports/startup/server/logger';
|
||||
import updateCursor from './updateCursor';
|
||||
|
||||
export function initializeCursor(meetingId) {
|
||||
return Cursor.upsert({
|
||||
meetingId: meetingId,
|
||||
}, {
|
||||
meetingId: meetingId,
|
||||
x: 0,
|
||||
y: 0,
|
||||
}, (err, numChanged) => {
|
||||
if (err) {
|
||||
return logger.error(`err upserting cursor for ${meetingId}`);
|
||||
}
|
||||
export default function initializeCursor(meetingId) {
|
||||
check(meetingId, String);
|
||||
|
||||
});
|
||||
return updateCursor(meetingId, 0, 0);
|
||||
};
|
||||
|
37
bigbluebutton-html5/imports/api/cursor/server/modifiers/updateCursor.js
Executable file
37
bigbluebutton-html5/imports/api/cursor/server/modifiers/updateCursor.js
Executable file
@ -0,0 +1,37 @@
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import Cursor from '/imports/api/cursor';
|
||||
|
||||
export default function updateCursor(meetingId, x = 0, y = 0) {
|
||||
check(meetingId, String);
|
||||
check(x, Number);
|
||||
check(y, Number);
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
};
|
||||
|
||||
const modifier = {
|
||||
$set: {
|
||||
meetingId,
|
||||
x,
|
||||
y,
|
||||
},
|
||||
};
|
||||
|
||||
const cb = (err, numChanged) => {
|
||||
if (err) {
|
||||
return Logger.error(`Upserting cursor to collection: ${err}`);
|
||||
}
|
||||
|
||||
const { insertedId } = numChanged;
|
||||
if (insertedId) {
|
||||
return Logger.info(`Initialized cursor meeting=${meetingId}`);
|
||||
}
|
||||
|
||||
if (numChanged) {
|
||||
return Logger.info(`Updated cursor meeting=${meetingId}`);
|
||||
}
|
||||
};
|
||||
|
||||
return Cursor.upsert(selector, modifier, cb);
|
||||
};
|
@ -1,19 +0,0 @@
|
||||
import Cursor from '/imports/api/cursor';
|
||||
import { logger } from '/imports/startup/server/logger';
|
||||
|
||||
export function updateCursorLocation(meetingId, cursorObject) {
|
||||
return Cursor.upsert({
|
||||
meetingId: meetingId,
|
||||
}, {
|
||||
$set: {
|
||||
meetingId: meetingId,
|
||||
x: cursorObject.x,
|
||||
y: cursorObject.y,
|
||||
},
|
||||
}, (err, numChanged) => {
|
||||
if (err != null) {
|
||||
return logger.error(`_unsucc update of cursor for ${meetingId} err=${JSON.stringify(err)}`);
|
||||
}
|
||||
|
||||
});
|
||||
};
|
@ -1,10 +0,0 @@
|
||||
import Cursor from '/imports/api/cursor';
|
||||
import { logger } from '/imports/startup/server/logger';
|
||||
|
||||
Meteor.publish('cursor', function (credentials) {
|
||||
const { meetingId } = credentials;
|
||||
logger.info(`publishing cursor for ${meetingId}`);
|
||||
return Cursor.find({
|
||||
meetingId: meetingId,
|
||||
});
|
||||
});
|
22
bigbluebutton-html5/imports/api/cursor/server/publishers.js
Normal file
22
bigbluebutton-html5/imports/api/cursor/server/publishers.js
Normal file
@ -0,0 +1,22 @@
|
||||
import Cursor from '/imports/api/cursor';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { isAllowedTo } from '/imports/startup/server/userPermissions';
|
||||
|
||||
Meteor.publish('cursor', (credentials) => {
|
||||
// TODO: Some publishers have ACL and others dont
|
||||
// if (!isAllowedTo('@@@', credentials)) {
|
||||
// this.error(new Meteor.Error(402, "The user was not authorized to subscribe for 'cursor'"));
|
||||
// }
|
||||
|
||||
const { meetingId, requesterUserId, requesterToken } = credentials;
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(requesterToken, String);
|
||||
|
||||
Logger.info(`Publishing Cursor for ${meetingId} ${requesterUserId} ${requesterToken}`);
|
||||
|
||||
return Cursor.find({ meetingId });
|
||||
});
|
@ -1,7 +1,7 @@
|
||||
import { check } from 'meteor/check';
|
||||
import Meetings from '/imports/api/meetings';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { initializeCursor } from '/imports/api/cursor/server/modifiers/initializeCursor';
|
||||
import initializeCursor from '/imports/api/cursor/server/modifiers/initializeCursor';
|
||||
|
||||
export default function addMeeting(meeting) {
|
||||
const APP_CONFIG = Meteor.settings.public.app;
|
||||
|
@ -4,10 +4,10 @@ import removeMeeting from './removeMeeting';
|
||||
|
||||
import { clearUsersCollection } from '/imports/api/users/server/modifiers/clearUsersCollection';
|
||||
import clearChats from '/imports/api/chat/server/modifiers/clearChats';
|
||||
import { clearShapesCollection } from '/imports/api/shapes/server/modifiers/clearShapesCollection';
|
||||
import clearShapes from '/imports/api/shapes/server/modifiers/clearShapes';
|
||||
import clearSlides from '/imports/api/slides/server/modifiers/clearSlides';
|
||||
import clearPolls from '/imports/api/polls/server/modifiers/clearPolls';
|
||||
import { clearCursorCollection } from '/imports/api/cursor/server/modifiers/clearCursorCollection';
|
||||
import clearCursor from '/imports/api/cursor/server/modifiers/clearCursor';
|
||||
import { clearCaptionsCollection }
|
||||
from '/imports/api/captions/server/modifiers/clearCaptionsCollection';
|
||||
import clearPresentations from '/imports/api/presentations/server/modifiers/clearPresentations';
|
||||
@ -16,10 +16,10 @@ export default function clearMeetings() {
|
||||
return Meetings.remove({}, (err) => {
|
||||
clearCaptionsCollection();
|
||||
clearChats();
|
||||
clearCursorCollection();
|
||||
clearCursor();
|
||||
clearPresentations();
|
||||
clearPolls();
|
||||
clearShapesCollection();
|
||||
clearShapes();
|
||||
clearSlides();
|
||||
clearUsersCollection();
|
||||
|
||||
|
@ -4,10 +4,10 @@ import Logger from '/imports/startup/server/logger';
|
||||
|
||||
import { clearUsersCollection } from '/imports/api/users/server/modifiers/clearUsersCollection';
|
||||
import clearChats from '/imports/api/chat/server/modifiers/clearChats';
|
||||
import { clearShapesCollection } from '/imports/api/shapes/server/modifiers/clearShapesCollection';
|
||||
import clearShapes from '/imports/api/shapes/server/modifiers/clearShapes';
|
||||
import clearSlides from '/imports/api/slides/server/modifiers/clearSlides';
|
||||
import clearPolls from '/imports/api/polls/server/modifiers/clearPolls';
|
||||
import { clearCursorCollection } from '/imports/api/cursor/server/modifiers/clearCursorCollection';
|
||||
import clearCursor from '/imports/api/cursor/server/modifiers/clearCursor';
|
||||
import { clearCaptionsCollection }
|
||||
from '/imports/api/captions/server/modifiers/clearCaptionsCollection';
|
||||
import clearPresentations from '/imports/api/presentations/server/modifiers/clearPresentations';
|
||||
@ -27,10 +27,10 @@ export default function removeMeeting(meetingId) {
|
||||
if (numChanged) {
|
||||
clearCaptionsCollection(meetingId);
|
||||
clearChats(meetingId);
|
||||
clearCursorCollection(meetingId);
|
||||
clearCursor(meetingId);
|
||||
clearPresentations(meetingId);
|
||||
clearPolls(meetingId);
|
||||
clearShapesCollection(meetingId);
|
||||
clearShapes(meetingId);
|
||||
clearSlides(meetingId);
|
||||
clearUsersCollection(meetingId);
|
||||
|
||||
|
@ -21,7 +21,7 @@ function amIListenOnly() {
|
||||
// Periodically check the status of the WebRTC call, when a call has been established attempt to
|
||||
// hangup, retry if a call is in progress, send the leave voice conference message to BBB
|
||||
|
||||
function exitAudio(afterExitCall) {
|
||||
function exitAudio(afterExitCall = () => {}) {
|
||||
if (!MEDIA_CONFIG.useSIPAudio) {
|
||||
vertoExitAudio();
|
||||
return;
|
||||
@ -36,7 +36,7 @@ function exitAudio(afterExitCall) {
|
||||
triedHangup = false;
|
||||
|
||||
// function to initiate call
|
||||
const checkToHangupCall = (function (context, afterExitCall) {
|
||||
const checkToHangupCall = ((context, afterExitCall = () => {}) => {
|
||||
|
||||
// if an attempt to hang up the call is made when the current session is not yet finished,
|
||||
// the request has no effect
|
||||
|
@ -1,21 +1,21 @@
|
||||
import { publish } from '/imports/api/common/server/helpers';
|
||||
import { isAllowedTo } from '/imports/startup/server/userPermissions';
|
||||
import { appendMessageHeader } from '/imports/api/common/server/helpers';
|
||||
import { logger } from '/imports/startup/server/logger';
|
||||
import Meetings from '/imports/api/meetings';
|
||||
|
||||
export default function getStun (credentials) {
|
||||
const REDIS_CONFIG = Meteor.settings.redis;
|
||||
const { meetingId, requesterUserId } = credentials;
|
||||
const eventName = 'send_stun_turn_info_request_message';
|
||||
|
||||
let message = {
|
||||
payload: {
|
||||
meeting_id: meetingId,
|
||||
requester_id: requesterUserId,
|
||||
},
|
||||
};
|
||||
|
||||
message = appendMessageHeader(eventName, message);
|
||||
return publish(REDIS_CONFIG.channels.fromBBBUsers, message);
|
||||
};
|
||||
import { publish } from '/imports/api/common/server/helpers';
|
||||
import { isAllowedTo } from '/imports/startup/server/userPermissions';
|
||||
import { appendMessageHeader } from '/imports/api/common/server/helpers';
|
||||
import { logger } from '/imports/startup/server/logger';
|
||||
import Meetings from '/imports/api/meetings';
|
||||
|
||||
export default function getStun(credentials) {
|
||||
const REDIS_CONFIG = Meteor.settings.redis;
|
||||
const { meetingId, requesterUserId } = credentials;
|
||||
const eventName = 'send_stun_turn_info_request_message';
|
||||
|
||||
let message = {
|
||||
payload: {
|
||||
meeting_id: meetingId,
|
||||
requester_id: requesterUserId,
|
||||
},
|
||||
};
|
||||
|
||||
message = appendMessageHeader(eventName, message);
|
||||
return publish(REDIS_CONFIG.channels.fromBBBUsers, message);
|
||||
};
|
||||
|
@ -26,11 +26,9 @@ export default function addPresentation(meetingId, presentation) {
|
||||
const modifier = {
|
||||
$set: {
|
||||
meetingId,
|
||||
presentation: {
|
||||
id: presentation.id,
|
||||
name: presentation.name,
|
||||
current: presentation.current,
|
||||
},
|
||||
'presentation.id': presentation.id,
|
||||
'presentation.name': presentation.name,
|
||||
'presentation.current': presentation.current,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -10,7 +10,7 @@ export default function removePresentation(meetingId, presentationId) {
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
presentationId,
|
||||
'presentation.id': presentationId,
|
||||
};
|
||||
|
||||
const cb = (err, numChanged) => {
|
||||
|
@ -0,0 +1,10 @@
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import handleWhiteboardGetReply from './handlers/whiteboardGetReply';
|
||||
import handleWhiteboardSend from './handlers/whiteboardSend';
|
||||
import handleWhiteboardCleared from './handlers/whiteboardCleared';
|
||||
import handleWhiteboardUndo from './handlers/whiteboardUndo';
|
||||
|
||||
RedisPubSub.on('get_whiteboard_shapes_reply', handleWhiteboardGetReply);
|
||||
RedisPubSub.on('send_whiteboard_shape_message', handleWhiteboardSend);
|
||||
RedisPubSub.on('whiteboard_cleared_message', handleWhiteboardCleared);
|
||||
RedisPubSub.on('undo_whiteboard_request', handleWhiteboardUndo);
|
@ -0,0 +1,14 @@
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { check } from 'meteor/check';
|
||||
|
||||
import clearShapesWhiteboard from '../modifiers/clearShapesWhiteboard';
|
||||
|
||||
export default function handleWhiteboardCleared({ payload }) {
|
||||
const meetingId = payload.meeting_id;
|
||||
const whiteboardId = payload.whiteboard_id;
|
||||
|
||||
check(meetingId, String);
|
||||
check(whiteboardId, String);
|
||||
|
||||
return clearShapesWhiteboard(meetingId, whiteboardId);
|
||||
};
|
@ -0,0 +1,25 @@
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { check } from 'meteor/check';
|
||||
import { inReplyToHTML5Client } from '/imports/api/common/server/helpers';
|
||||
|
||||
import addShape from '../modifiers/addShape';
|
||||
|
||||
export default function handleWhiteboardGetReply({ payload }) {
|
||||
if (!inReplyToHTML5Client({ payload })) {
|
||||
return;
|
||||
}
|
||||
|
||||
const meetingId = payload.meeting_id;
|
||||
const shapes = payload.shapes;
|
||||
|
||||
check(meetingId, String);
|
||||
check(shapes, Array);
|
||||
|
||||
let shapesAdded = [];
|
||||
shapes.forEach(shape => {
|
||||
let whiteboardId = shape.wb_id;
|
||||
shapesAdded.push(addShape(meetingId, whiteboardId, shape));
|
||||
});
|
||||
|
||||
return shapesAdded;
|
||||
};
|
@ -0,0 +1,18 @@
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { check } from 'meteor/check';
|
||||
|
||||
import addShape from '../modifiers/addShape';
|
||||
|
||||
export default function handleWhiteboardSend({ payload }) {
|
||||
const meetingId = payload.meeting_id;
|
||||
const shape = payload.shape;
|
||||
|
||||
check(meetingId, String);
|
||||
check(shape, Object);
|
||||
|
||||
const whiteboardId = shape.wb_id;
|
||||
|
||||
check(whiteboardId, String);
|
||||
|
||||
return addShape(meetingId, whiteboardId, shape);
|
||||
};
|
@ -0,0 +1,16 @@
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { check } from 'meteor/check';
|
||||
|
||||
import removeShape from '../modifiers/removeShape';
|
||||
|
||||
export default function handleWhiteboardUndo({ payload }) {
|
||||
const meetingId = payload.meeting_id;
|
||||
const whiteboardId = payload.whiteboard_id;
|
||||
const shapeId = payload.shape_id;
|
||||
|
||||
check(meetingId, String);
|
||||
check(whiteboardId, String);
|
||||
check(shapeId, String);
|
||||
|
||||
return removeShape(meetingId, whiteboardId, shapeId);
|
||||
};
|
3
bigbluebutton-html5/imports/api/shapes/server/index.js
Normal file
3
bigbluebutton-html5/imports/api/shapes/server/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
import './eventHandlers';
|
||||
import './methods';
|
||||
import './publishers';
|
4
bigbluebutton-html5/imports/api/shapes/server/methods.js
Normal file
4
bigbluebutton-html5/imports/api/shapes/server/methods.js
Normal file
@ -0,0 +1,4 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
Meteor.methods({
|
||||
});
|
@ -0,0 +1,85 @@
|
||||
import { check } from 'meteor/check';
|
||||
import Shapes from '/imports/api/shapes';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
const SHAPE_TYPE_TEXT = 'text';
|
||||
const SHAPE_TYPE_POLL_RESULT = 'poll_result';
|
||||
|
||||
export default function addShape(meetingId, whiteboardId, shape) {
|
||||
check(meetingId, String);
|
||||
check(whiteboardId, String);
|
||||
check(shape, Object);
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
'shape.id': shape.id,
|
||||
};
|
||||
|
||||
let modifier = {
|
||||
$set: {
|
||||
meetingId,
|
||||
whiteboardId,
|
||||
'shape.id': shape.id,
|
||||
'shape.wb_id': shape.wb_id,
|
||||
'shape.shape_type': shape.shape_type,
|
||||
'shape.status': shape.status,
|
||||
'shape.shape.type': shape.shape.type,
|
||||
'shape.shape.status': shape.shape.status,
|
||||
},
|
||||
};
|
||||
|
||||
const shapeType = shape.shape_type;
|
||||
|
||||
switch (shapeType) {
|
||||
case SHAPE_TYPE_TEXT:
|
||||
modifier.$set = Object.assign(modifier.$set, {
|
||||
'shape.shape.textBoxHeight': shape.shape.textBoxHeight,
|
||||
'shape.shape.fontColor': shape.shape.fontColor,
|
||||
'shape.shape.dataPoints': shape.shape.dataPoints,
|
||||
'shape.shape.x': shape.shape.x,
|
||||
'shape.shape.textBoxWidth': shape.shape.textBoxWidth,
|
||||
'shape.shape.whiteboardId': shape.shape.whiteboardId,
|
||||
'shape.shape.fontSize': shape.shape.fontSize,
|
||||
'shape.shape.id': shape.shape.id,
|
||||
'shape.shape.y': shape.shape.y,
|
||||
'shape.shape.calcedFontSize': shape.shape.calcedFontSize,
|
||||
'shape.shape.text': shape.shape.text,
|
||||
});
|
||||
break;
|
||||
|
||||
case SHAPE_TYPE_POLL_RESULT:
|
||||
shape.shape.result = JSON.parse(shape.shape.result);
|
||||
|
||||
default:
|
||||
modifier.$set = Object.assign(modifier.$set, {
|
||||
'shape.shape.points': shape.shape.points,
|
||||
'shape.shape.whiteboardId': shape.shape.whiteboardId,
|
||||
'shape.shape.id': shape.shape.id,
|
||||
'shape.shape.square': shape.shape.square,
|
||||
'shape.shape.transparency': shape.shape.transparency,
|
||||
'shape.shape.thickness': shape.shape.thickness,
|
||||
'shape.shape.color': shape.shape.color,
|
||||
'shape.shape.result': shape.shape.result,
|
||||
'shape.shape.num_respondents': shape.shape.num_respondents,
|
||||
'shape.shape.num_responders': shape.shape.num_responders,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
const cb = (err, numChanged) => {
|
||||
if (err) {
|
||||
return Logger.error(`Adding shape to collection: ${err}`);
|
||||
}
|
||||
|
||||
const { insertedId } = numChanged;
|
||||
if (insertedId) {
|
||||
return Logger.info(`Added shape id=${shape.id} whiteboard=${whiteboardId}`);
|
||||
}
|
||||
|
||||
if (numChanged) {
|
||||
return Logger.info(`Upserted shape id=${shape.id} whiteboard=${whiteboardId}`);
|
||||
}
|
||||
};
|
||||
|
||||
return Shapes.upsert(selector, modifier, cb);
|
||||
};
|
@ -1,106 +0,0 @@
|
||||
import Shapes from '/imports/api/shapes';
|
||||
import { logger } from '/imports/startup/server/logger';
|
||||
|
||||
export function addShapeToCollection(meetingId, whiteboardId, shapeObject) {
|
||||
//A separate check for the polling shape before adding the object to the collection
|
||||
//Meteor stringifies an array of JSONs (...shape.result) in this message
|
||||
//Parsing the String and reassigning the value
|
||||
if (shapeObject != null && shapeObject.shape_type === 'poll_result' &&
|
||||
typeof shapeObject.shape.result === 'string') {
|
||||
shapeObject.shape.result = JSON.parse(shapeObject.shape.result);
|
||||
}
|
||||
|
||||
if (shapeObject != null && shapeObject.shape_type === 'text') {
|
||||
logger.info(`we are dealing with a text shape and the event is:${shapeObject.status}`);
|
||||
const entry = {
|
||||
meetingId: meetingId,
|
||||
whiteboardId: whiteboardId,
|
||||
shape: {
|
||||
wb_id: shapeObject.wb_id,
|
||||
shape_type: shapeObject.shape_type,
|
||||
status: shapeObject.status,
|
||||
id: shapeObject.id,
|
||||
shape: {
|
||||
type: shapeObject.shape.type,
|
||||
textBoxHeight: shapeObject.shape.textBoxHeight,
|
||||
fontColor: shapeObject.shape.fontColor,
|
||||
status: shapeObject.shape.status,
|
||||
dataPoints: shapeObject.shape.dataPoints,
|
||||
x: shapeObject.shape.x,
|
||||
textBoxWidth: shapeObject.shape.textBoxWidth,
|
||||
whiteboardId: shapeObject.shape.whiteboardId,
|
||||
fontSize: shapeObject.shape.fontSize,
|
||||
id: shapeObject.shape.id,
|
||||
y: shapeObject.shape.y,
|
||||
calcedFontSize: shapeObject.shape.calcedFontSize,
|
||||
text: shapeObject.shape.text,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (shapeObject.status === 'textCreated') {
|
||||
Shapes.insert(entry);
|
||||
return logger.info(`${shapeObject.status} adding an initial text shape to the collection`);
|
||||
} else if (shapeObject.status === 'textEdited' || shapeObject.status === 'textPublished') {
|
||||
//check if the shape with this id exists in the collection
|
||||
//this check and 'else' block can be removed once issue #3170 is fixed
|
||||
let _shape = Shapes.findOne({ 'shape.id': shapeObject.shape.id });
|
||||
|
||||
if (_shape != null) {
|
||||
Shapes.update({
|
||||
'shape.id': entry.shape.id,
|
||||
}, {
|
||||
$set: {
|
||||
shape: entry.shape,
|
||||
},
|
||||
});
|
||||
|
||||
return logger.info(`${shapeObject.status} substituting the temp shapes with the newer one`);
|
||||
} else {
|
||||
Shapes.insert(entry);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: pencil messages currently don't send draw_end and are labeled all as DRAW_START
|
||||
} else if (shapeObject != null && (shapeObject.status === 'DRAW_START' ||
|
||||
shapeObject.status === 'DRAW_UPDATE' || shapeObject.status === 'DRAW_END')) {
|
||||
shape = Shapes.findOne({
|
||||
'shape.id': shapeObject.shape.id,
|
||||
});
|
||||
if (shape != null) {
|
||||
Shapes.update({
|
||||
'shape.id': shapeObject.shape.id,
|
||||
}, {
|
||||
$set: {
|
||||
'shape.shape.points': shapeObject.shape.points,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
const entry = {
|
||||
meetingId: meetingId,
|
||||
whiteboardId: whiteboardId,
|
||||
shape: {
|
||||
wb_id: shapeObject.wb_id,
|
||||
shape_type: shapeObject.shape_type,
|
||||
status: shapeObject.status,
|
||||
id: shapeObject.id,
|
||||
shape: {
|
||||
type: shapeObject.shape.type,
|
||||
status: shapeObject.shape.status,
|
||||
points: shapeObject.shape.points,
|
||||
whiteboardId: shapeObject.shape.whiteboardId,
|
||||
id: shapeObject.shape.id,
|
||||
square: shapeObject.shape.square,
|
||||
transparency: shapeObject.shape.transparency,
|
||||
thickness: shapeObject.shape.thickness,
|
||||
color: shapeObject.shape.color,
|
||||
result: shapeObject.shape.result,
|
||||
num_respondents: shapeObject.shape.num_respondents,
|
||||
num_responders: shapeObject.shape.num_responders,
|
||||
},
|
||||
},
|
||||
};
|
||||
Shapes.insert(entry);
|
||||
}
|
||||
}
|
||||
};
|
10
bigbluebutton-html5/imports/api/shapes/server/modifiers/clearShapes.js
Executable file
10
bigbluebutton-html5/imports/api/shapes/server/modifiers/clearShapes.js
Executable file
@ -0,0 +1,10 @@
|
||||
import Shapes from '/imports/api/shapes';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
export default function clearShapes(meetingId) {
|
||||
if (meetingId) {
|
||||
return Shapes.remove({ meetingId, }, Logger.info(`Cleared Shapes (${meetingId})`));
|
||||
}
|
||||
|
||||
return Shapes.remove({}, Logger.info('Cleared Shapes (all)'));
|
||||
};
|
@ -1,18 +0,0 @@
|
||||
import Shapes from '/imports/api/shapes';
|
||||
import { logger } from '/imports/startup/server/logger';
|
||||
|
||||
// called on server start and meeting end
|
||||
export function clearShapesCollection() {
|
||||
const meetingId = arguments[0];
|
||||
if (meetingId != null) {
|
||||
return Shapes.remove({
|
||||
meetingId: meetingId,
|
||||
}, () => {
|
||||
logger.info(`cleared Shapes Collection (meetingId: ${meetingId}!`);
|
||||
});
|
||||
} else {
|
||||
return Shapes.remove({}, () => {
|
||||
logger.info('cleared Shapes Collection (all meetings)!');
|
||||
});
|
||||
}
|
||||
};
|
@ -0,0 +1,23 @@
|
||||
import Shapes from '/imports/api/shapes';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { check } from 'meteor/check';
|
||||
|
||||
export default function clearShapesWhiteboard(meetingId, whiteboardId) {
|
||||
check(meetingId, String);
|
||||
check(whiteboardId, String);
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
whiteboardId,
|
||||
};
|
||||
|
||||
const cb = (err) => {
|
||||
if (err) {
|
||||
return Logger.error(`Removing Shapes from collection: ${err}`);
|
||||
}
|
||||
|
||||
return Logger.info(`Removed Shapes where whiteboard=${whiteboardId}`);
|
||||
};
|
||||
|
||||
return Shapes.remove(selector, cb);
|
||||
};
|
@ -1,48 +0,0 @@
|
||||
import { eventEmitter } from '/imports/startup/server';
|
||||
import { inReplyToHTML5Client } from '/imports/api/common/server/helpers';
|
||||
import { addShapeToCollection } from './addShapeToCollection';
|
||||
import { removeAllShapesFromSlide } from './removeAllShapesFromSlide';
|
||||
import { removeShapeFromSlide } from './removeShapeFromSlide';
|
||||
|
||||
eventEmitter.on('get_whiteboard_shapes_reply', function (arg) {
|
||||
if (inReplyToHTML5Client(arg)) {
|
||||
const meetingId = arg.payload.meeting_id;
|
||||
const shapes = arg.payload.shapes;
|
||||
const shapesLength = shapes.length;
|
||||
for (let m = 0; m < shapesLength; m++) {
|
||||
let shape = shapes[m];
|
||||
let whiteboardId = shape.wb_id;
|
||||
addShapeToCollection(meetingId, whiteboardId, shape);
|
||||
}
|
||||
}
|
||||
|
||||
return arg.callback();
|
||||
});
|
||||
|
||||
eventEmitter.on('send_whiteboard_shape_message', function (arg) {
|
||||
const payload = arg.payload;
|
||||
const meetingId = payload.meeting_id;
|
||||
|
||||
const shape = payload.shape;
|
||||
if (!!shape && !!shape.wb_id) {
|
||||
const whiteboardId = shape.wb_id;
|
||||
addShapeToCollection(meetingId, whiteboardId, shape);
|
||||
}
|
||||
|
||||
return arg.callback();
|
||||
});
|
||||
|
||||
eventEmitter.on('whiteboard_cleared_message', function (arg) {
|
||||
const meetingId = arg.payload.meeting_id;
|
||||
const whiteboardId = arg.payload.whiteboard_id;
|
||||
removeAllShapesFromSlide(meetingId, whiteboardId);
|
||||
return arg.callback();
|
||||
});
|
||||
|
||||
eventEmitter.on('undo_whiteboard_request', function (arg) {
|
||||
const meetingId = arg.payload.meeting_id;
|
||||
const whiteboardId = arg.payload.whiteboard_id;
|
||||
const shapeId = arg.payload.shape_id;
|
||||
removeShapeFromSlide(meetingId, whiteboardId, shapeId);
|
||||
return arg.callback();
|
||||
});
|
@ -1,18 +0,0 @@
|
||||
import Shapes from '/imports/api/shapes';
|
||||
import { logger } from '/imports/startup/server/logger';
|
||||
|
||||
export function removeAllShapesFromSlide(meetingId, whiteboardId) {
|
||||
logger.info(`removeAllShapesFromSlide__${whiteboardId}`);
|
||||
|
||||
if ((meetingId != null) && (whiteboardId != null) && (Shapes.find({
|
||||
meetingId: meetingId,
|
||||
whiteboardId: whiteboardId,
|
||||
}) != null)) {
|
||||
return Shapes.remove({
|
||||
meetingId: meetingId,
|
||||
whiteboardId: whiteboardId,
|
||||
}, () => {
|
||||
logger.info('clearing all shapes from slide');
|
||||
});
|
||||
}
|
||||
};
|
@ -0,0 +1,27 @@
|
||||
import { check } from 'meteor/check';
|
||||
import Shapes from '/imports/api/shapes';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
export default function removeShape(meetingId, whiteboardId, shapeId) {
|
||||
check(meetingId, String);
|
||||
check(whiteboardId, String);
|
||||
check(shapeId, String);
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
whiteboardId,
|
||||
'shape.id': shapeId,
|
||||
};
|
||||
|
||||
const cb = (err, numChanged) => {
|
||||
if (err) {
|
||||
return Logger.error(`Removing shape from collection: ${err}`);
|
||||
}
|
||||
|
||||
if (numChanged) {
|
||||
return Logger.info(`Removed shape id=${shapeId} whiteboard=${whiteboardId}`);
|
||||
}
|
||||
};
|
||||
|
||||
return Shapes.remove(selector, cb);
|
||||
};
|
@ -1,22 +0,0 @@
|
||||
import Shapes from '/imports/api/shapes';
|
||||
import { logger } from '/imports/startup/server/logger';
|
||||
|
||||
export function removeShapeFromSlide(meetingId, whiteboardId, shapeId) {
|
||||
let shapeToRemove;
|
||||
if (meetingId != null && whiteboardId != null && shapeId != null) {
|
||||
shapeToRemove = Shapes.findOne({
|
||||
meetingId: meetingId,
|
||||
whiteboardId: whiteboardId,
|
||||
'shape.id': shapeId,
|
||||
});
|
||||
if (shapeToRemove != null) {
|
||||
Shapes.remove(shapeToRemove._id);
|
||||
logger.info(`----removed shape[${shapeId}] from ${whiteboardId}`);
|
||||
return logger.info(`remaining shapes on the slide: ${
|
||||
Shapes.find({
|
||||
meetingId: meetingId,
|
||||
whiteboardId: whiteboardId,
|
||||
}).count()}`);
|
||||
}
|
||||
}
|
||||
};
|
@ -1,8 +0,0 @@
|
||||
import Shapes from '/imports/api/shapes';
|
||||
|
||||
Meteor.publish('shapes', function (credentials) {
|
||||
const { meetingId } = credentials;
|
||||
return Shapes.find({
|
||||
meetingId: meetingId,
|
||||
});
|
||||
});
|
22
bigbluebutton-html5/imports/api/shapes/server/publishers.js
Normal file
22
bigbluebutton-html5/imports/api/shapes/server/publishers.js
Normal file
@ -0,0 +1,22 @@
|
||||
import Shapes from '/imports/api/shapes';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { isAllowedTo } from '/imports/startup/server/userPermissions';
|
||||
|
||||
Meteor.publish('shapes', (credentials) => {
|
||||
// TODO: Some publishers have ACL and others dont
|
||||
// if (!isAllowedTo('@@@', credentials)) {
|
||||
// this.error(new Meteor.Error(402, "The user was not authorized to subscribe for 'shapes'"));
|
||||
// }
|
||||
|
||||
const { meetingId, requesterUserId, requesterToken } = credentials;
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(requesterToken, String);
|
||||
|
||||
Logger.info(`Publishing Shapes for ${meetingId} ${requesterUserId} ${requesterToken}`);
|
||||
|
||||
return Shapes.find({ meetingId });
|
||||
});
|
@ -3,8 +3,8 @@ import Logger from '/imports/startup/server/logger';
|
||||
|
||||
export default function clearSlides(meetingId) {
|
||||
if (meetingId) {
|
||||
return Slides.remove({ meetingId: meetingId }, Logger.info(`Cleared Slides (${meetingId})`));
|
||||
} else {
|
||||
return Slides.remove({}, Logger.info('Cleared Slides (all)'));
|
||||
return Slides.remove({ meetingId }, Logger.info(`Cleared Slides (${meetingId})`));
|
||||
}
|
||||
|
||||
return Slides.remove({}, Logger.info('Cleared Slides (all)'));
|
||||
};
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Slides from '/imports/api/slides';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { check } from 'meteor/check';
|
||||
import clearShapesWhiteboard from '/imports/api/shapes/server/modifiers/clearShapesWhiteboard';
|
||||
|
||||
export default function clearSlidesPresentation(meetingId, presentationId) {
|
||||
check(meetingId, String);
|
||||
@ -11,12 +12,18 @@ export default function clearSlidesPresentation(meetingId, presentationId) {
|
||||
presentationId,
|
||||
};
|
||||
|
||||
const cb = (err) => {
|
||||
const whiteboardIds = Slides.find(selector).map(row => row.slide.id);
|
||||
|
||||
const cb = (err, numChanged) => {
|
||||
if (err) {
|
||||
return Logger.error(`Removing Slides from collection: ${err}`);
|
||||
}
|
||||
|
||||
return Logger.info(`Removed Slides where presentationId=${presentationId}`);
|
||||
if (numChanged) {
|
||||
whiteboardIds.forEach(whiteboardId => clearShapesWhiteboard(meetingId, whiteboardId));
|
||||
|
||||
return Logger.info(`Removed Slides where presentationId=${presentationId}`);
|
||||
}
|
||||
};
|
||||
|
||||
return Slides.remove(selector, cb);
|
||||
|
@ -20,7 +20,7 @@ Meteor.methods({
|
||||
}
|
||||
};
|
||||
|
||||
if (isAllowedTo(action(), meetingId, requesterUserId, requesterToken)) {
|
||||
if (isAllowedTo(action(), credentials)) {
|
||||
let message = {
|
||||
payload: {
|
||||
user_id: toMuteUserId,
|
||||
|
@ -2,7 +2,7 @@ import Users from '/imports/api/users';
|
||||
import { logger } from '/imports/startup/server/logger';
|
||||
|
||||
//update a voiceUser - a helper method
|
||||
export function updateVoiceUser(meetingId, voiceUserObject, callback) {
|
||||
export function updateVoiceUser(meetingId, voiceUserObject, callback = () => {}) {
|
||||
let userObject;
|
||||
userObject = Users.findOne({
|
||||
userId: voiceUserObject.web_userid,
|
||||
|
@ -34,7 +34,7 @@ export function userJoined(meetingId, user, callback) {
|
||||
if (userObject != null && userObject.authToken != null) {
|
||||
getStun({
|
||||
meetingId: meetingId,
|
||||
requesterUserId: userId
|
||||
requesterUserId: userId,
|
||||
});
|
||||
|
||||
Users.update({
|
||||
@ -131,7 +131,7 @@ export function userJoined(meetingId, user, callback) {
|
||||
// logger.info "NOTE: got user_joined_message #{user.name} #{user.userid}"
|
||||
getStun({
|
||||
meetingId: meetingId,
|
||||
requesterUserId: userId
|
||||
requesterUserId: userId,
|
||||
});
|
||||
|
||||
return Users.upsert({
|
||||
|
@ -2,7 +2,7 @@ import en from './en.json';
|
||||
import ptBR from './pt-BR.json';
|
||||
|
||||
export default {
|
||||
'en': en,
|
||||
en: en,
|
||||
'en-US': en,
|
||||
'pt-BR': ptBR,
|
||||
};
|
||||
|
@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
import { createContainer } from 'meteor/react-meteor-data';
|
||||
import Button from '/imports/ui/components/button/component';
|
||||
import Users from '/imports/api/users/index';
|
||||
import Auth from '/imports/ui/services/auth/index';
|
||||
import MuteAudioContainer from '../mute-button/container';
|
||||
|
||||
export default class JoinAudio extends React.Component {
|
||||
|
||||
renderLeaveButton() {
|
||||
return (
|
||||
<span>
|
||||
<Button
|
||||
onClick={this.props.close}
|
||||
label={'Leave Audio'}
|
||||
color={'danger'}
|
||||
icon={'audio'}
|
||||
size={'lg'}
|
||||
circle={true}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.props.isInAudio) {
|
||||
return (
|
||||
<span>
|
||||
<MuteAudioContainer/>
|
||||
{this.renderLeaveButton()}
|
||||
</span>
|
||||
);
|
||||
} else if (this.props.isInListenOnly) {
|
||||
return this.renderLeaveButton();
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
onClick={this.props.open}
|
||||
label={'Join Audio'}
|
||||
color={'primary'}
|
||||
icon={'audio'}
|
||||
size={'lg'}
|
||||
circle={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,57 +1,26 @@
|
||||
import React from 'react';
|
||||
import { createContainer } from 'meteor/react-meteor-data';
|
||||
import Button from '/imports/ui/components/button/component';
|
||||
import Users from '/imports/api/users/index';
|
||||
import Auth from '/imports/ui/services/auth/index';
|
||||
|
||||
class JoinAudioContainer extends React.Component {
|
||||
|
||||
handleClick() {
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.props.isInAudio) {
|
||||
return (
|
||||
<span>
|
||||
<Button
|
||||
onClick={this.handleClick}
|
||||
label={'Mute'}
|
||||
color={'primary'}
|
||||
icon={'audio'}
|
||||
size={'lg'}
|
||||
circle={true}
|
||||
/>
|
||||
<Button
|
||||
onClick={this.props.close}
|
||||
label={'Leave Audio'}
|
||||
color={'primary'}
|
||||
icon={'audio'}
|
||||
size={'lg'}
|
||||
circle={true}
|
||||
/>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
else {
|
||||
return (
|
||||
<Button
|
||||
onClick={this.props.open}
|
||||
label={'Join Audio'}
|
||||
color={'primary'}
|
||||
icon={'audio'}
|
||||
size={'lg'}
|
||||
circle={true}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default createContainer((params) => {
|
||||
const data = {
|
||||
isInAudio: Users.findOne({userId: Auth.userID}).user.voiceUser.joined,
|
||||
open: params.open,
|
||||
close: params.close,
|
||||
};
|
||||
return data;
|
||||
}, JoinAudioContainer);
|
||||
import React from 'react';
|
||||
import { createContainer } from 'meteor/react-meteor-data';
|
||||
import Button from '/imports/ui/components/button/component';
|
||||
import Users from '/imports/api/users/index';
|
||||
import Auth from '/imports/ui/services/auth/index';
|
||||
import JoinAudio from './component';
|
||||
|
||||
class JoinAudioContainer extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<JoinAudio {...this.props} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default createContainer((params) => {
|
||||
const user = Users.findOne({ userId: Auth.userID }).user;
|
||||
|
||||
return {
|
||||
isInAudio: user.voiceUser.joined,
|
||||
isInListenOnly: user.listenOnly,
|
||||
open: params.open,
|
||||
close: params.close,
|
||||
};
|
||||
}, JoinAudioContainer);
|
||||
|
@ -10,7 +10,7 @@ import Users from '/imports/api/users/index';
|
||||
import JoinAudioContainer from './audio-menu/container';
|
||||
import { exitAudio } from '/imports/api/phone';
|
||||
|
||||
const openJoinAudio = () => {showModal(<Audio />)};
|
||||
const openJoinAudio = () => showModal(<Audio />);
|
||||
|
||||
export default class ActionsBar extends Component {
|
||||
constructor(props) {
|
||||
@ -29,7 +29,8 @@ export default class ActionsBar extends Component {
|
||||
<div className={styles.center}>
|
||||
<JoinAudioContainer
|
||||
open={openJoinAudio.bind(this)}
|
||||
close={exitAudio}
|
||||
close={() => {exitAudio();}}
|
||||
|
||||
/>
|
||||
|
||||
<Button
|
||||
|
@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import Button from '/imports/ui/components/button/component';
|
||||
|
||||
export default class MuteAudioComponent extends React.Component {
|
||||
|
||||
render() {
|
||||
const { isMuted, muteUser, unmuteUser } = this.props;
|
||||
let onClick = muteUser;
|
||||
let label = 'Mute';
|
||||
|
||||
if (isMuted) {
|
||||
onClick = unmuteUser;
|
||||
label = 'Unmute';
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
onClick={onClick}
|
||||
label={label}
|
||||
color={'primary'}
|
||||
icon={'audio'}
|
||||
size={'lg'}
|
||||
circle={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import {createContainer} from 'meteor/react-meteor-data';
|
||||
import {callServer} from '/imports/ui/services/api';
|
||||
import Button from '/imports/ui/components/button/component';
|
||||
import Users from '/imports/api/users/index';
|
||||
import Auth from '/imports/ui/services/auth/index';
|
||||
import MuteAudioComponent from './component';
|
||||
|
||||
class MuteAudioContainer extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<MuteAudioComponent
|
||||
isMuted = {this.props.isMuted}
|
||||
muteUser = {this.props.muteUser}
|
||||
unmuteUser = {this.props.unmuteUser}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default createContainer((params) => {
|
||||
const data = {
|
||||
isMuted: Users.findOne({ userId: Auth.userID }).user.voiceUser.muted,
|
||||
muteUser: () => callServer('muteUser', Auth.userID),
|
||||
unmuteUser: () => callServer('unmuteUser', Auth.userID),
|
||||
};
|
||||
return data;
|
||||
}, MuteAudioContainer);
|
@ -1,87 +1,86 @@
|
||||
import React from 'react';
|
||||
import Button from '/imports/ui/components/button/component';
|
||||
import { clearModal } from '/imports/ui/components/app/service';
|
||||
import { joinMicrophone } from '/imports/api/phone';
|
||||
import classNames from 'classnames';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { callServer } from '/imports/ui/services/api';
|
||||
import styles from '../styles.scss';
|
||||
|
||||
export default class AudioSettings extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.chooseAudio = this.chooseAudio.bind(this);
|
||||
this.joinAudio = this.joinAudio.bind(this);
|
||||
}
|
||||
|
||||
handleClick() {
|
||||
}
|
||||
|
||||
chooseAudio() {
|
||||
this.props.changeMenu(this.props.JOIN_AUDIO);
|
||||
}
|
||||
|
||||
joinAudio() {
|
||||
clearModal();
|
||||
joinMicrophone();
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.center}>
|
||||
<Button className={styles.backBtn}
|
||||
label={'Back'}
|
||||
icon={'left-arrow'}
|
||||
size={'md'}
|
||||
color={'primary'}
|
||||
ghost={true}
|
||||
onClick={this.chooseAudio}
|
||||
/>
|
||||
<div>
|
||||
Choose your audio settings
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.half}>
|
||||
<label htmlFor='microphone'>Microphone source</label><br />
|
||||
<select id='microphone' defaultValue='0'>
|
||||
<option value='0' disabled>Default</option>
|
||||
<option value='1' disabled>1</option>
|
||||
<option value='2' disabled>2</option>
|
||||
<option value='3' disabled>3</option>
|
||||
</select><br />
|
||||
<label htmlFor='speaker'>Speaker source</label><br />
|
||||
<select id='speaker' defaultValue='0'>
|
||||
<option value='0' disabled>Default</option>
|
||||
<option value='1' disabled>1</option>
|
||||
<option value='2' disabled>2</option>
|
||||
<option value='3' disabled>3</option>
|
||||
</select><br />
|
||||
<Button className={styles.playSound}
|
||||
label={'Play sound'}
|
||||
icon={'audio'}
|
||||
size={'md'}
|
||||
color={'primary'}
|
||||
ghost={true}
|
||||
onClick={this.handleClick}
|
||||
/><br />
|
||||
</div>
|
||||
<div className={styles.half}>
|
||||
Please note, a dialog will appear in your browser,
|
||||
requiring you to accept your microphone.
|
||||
<br />
|
||||
<img src='resources/images/allow-mic.png' alt='allow microphone image' width='100%'/>
|
||||
<br />
|
||||
<Button className={styles.enterBtn}
|
||||
label={'Enter Session'}
|
||||
size={'md'}
|
||||
color={'primary'}
|
||||
onClick={this.joinAudio}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
import React from 'react';
|
||||
import Button from '/imports/ui/components/button/component';
|
||||
import { clearModal } from '/imports/ui/components/app/service';
|
||||
import { joinMicrophone } from '/imports/api/phone';
|
||||
import classNames from 'classnames';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { callServer } from '/imports/ui/services/api';
|
||||
import styles from '../styles.scss';
|
||||
|
||||
export default class AudioSettings extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.chooseAudio = this.chooseAudio.bind(this);
|
||||
this.joinAudio = this.joinAudio.bind(this);
|
||||
}
|
||||
|
||||
handleClick() {
|
||||
}
|
||||
|
||||
chooseAudio() {
|
||||
this.props.changeMenu(this.props.JOIN_AUDIO);
|
||||
}
|
||||
|
||||
joinAudio() {
|
||||
clearModal();
|
||||
joinMicrophone();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.center}>
|
||||
<Button className={styles.backBtn}
|
||||
label={'Back'}
|
||||
icon={'left-arrow'}
|
||||
size={'md'}
|
||||
color={'primary'}
|
||||
ghost={true}
|
||||
onClick={this.chooseAudio}
|
||||
/>
|
||||
<div>
|
||||
Choose your audio settings
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.half}>
|
||||
<label htmlFor='microphone'>Microphone source</label><br />
|
||||
<select id='microphone' defaultValue='0'>
|
||||
<option value='0' disabled>Default</option>
|
||||
<option value='1' disabled>1</option>
|
||||
<option value='2' disabled>2</option>
|
||||
<option value='3' disabled>3</option>
|
||||
</select><br />
|
||||
<label htmlFor='speaker'>Speaker source</label><br />
|
||||
<select id='speaker' defaultValue='0'>
|
||||
<option value='0' disabled>Default</option>
|
||||
<option value='1' disabled>1</option>
|
||||
<option value='2' disabled>2</option>
|
||||
<option value='3' disabled>3</option>
|
||||
</select><br />
|
||||
<Button className={styles.playSound}
|
||||
label={'Play sound'}
|
||||
icon={'audio'}
|
||||
size={'md'}
|
||||
color={'primary'}
|
||||
ghost={true}
|
||||
onClick={this.handleClick}
|
||||
/><br />
|
||||
</div>
|
||||
<div className={styles.half}>
|
||||
Please note, a dialog will appear in your browser,
|
||||
requiring you to accept your microphone.
|
||||
<br />
|
||||
<img src='resources/images/allow-mic.png' alt='allow microphone image' width='100%'/>
|
||||
<br />
|
||||
<Button className={styles.enterBtn}
|
||||
label={'Enter Session'}
|
||||
size={'md'}
|
||||
color={'primary'}
|
||||
onClick={this.joinAudio}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@ -1,65 +1,65 @@
|
||||
import React from 'react';
|
||||
import Icon from '/imports/ui/components/icon/component';
|
||||
import Button from '/imports/ui/components/button/component';
|
||||
import ModalBase from '../modal/base/component';
|
||||
import { clearModal } from '/imports/ui/components/app/service';
|
||||
import classNames from 'classnames';
|
||||
import ReactDOM from 'react-dom';
|
||||
import styles from './styles.scss';
|
||||
import JoinAudio from './join-audio/component';
|
||||
import ListenOnly from './listen-only/component';
|
||||
import AudioSettings from './audio-settings/component';
|
||||
|
||||
export default class Audio extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.JOIN_AUDIO = 0;
|
||||
this.AUDIO_SETTINGS = 1;
|
||||
this.LISTEN_ONLY = 2;
|
||||
|
||||
this.submenus = [];
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
/* activeSubmenu represents the submenu in the submenus array to be displayed to the user,
|
||||
* initialized to 0
|
||||
*/
|
||||
this.setState({ activeSubmenu: 0 });
|
||||
this.submenus.push({ componentName: JoinAudio, });
|
||||
this.submenus.push({ componentName: AudioSettings, });
|
||||
this.submenus.push({ componentName: ListenOnly, });
|
||||
}
|
||||
|
||||
changeMenu(i) {
|
||||
this.setState({ activeSubmenu: i });
|
||||
}
|
||||
|
||||
createMenu() {
|
||||
const curr = this.state.activeSubmenu === undefined ? 0 : this.state.activeSubmenu;
|
||||
|
||||
let props = {
|
||||
changeMenu: this.changeMenu.bind(this),
|
||||
JOIN_AUDIO: this.JOIN_AUDIO,
|
||||
AUDIO_SETTINGS: this.AUDIO_SETTINGS,
|
||||
LISTEN_ONLY: this.LISTEN_ONLY,
|
||||
}
|
||||
|
||||
const Submenu = this.submenus[curr].componentName;
|
||||
return <Submenu {...props}/>;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ModalBase
|
||||
isOpen={true}
|
||||
onHide={null}
|
||||
import React from 'react';
|
||||
import Icon from '/imports/ui/components/icon/component';
|
||||
import Button from '/imports/ui/components/button/component';
|
||||
import ModalBase from '../modal/base/component';
|
||||
import { clearModal } from '/imports/ui/components/app/service';
|
||||
import classNames from 'classnames';
|
||||
import ReactDOM from 'react-dom';
|
||||
import styles from './styles.scss';
|
||||
import JoinAudio from './join-audio/component';
|
||||
import ListenOnly from './listen-only/component';
|
||||
import AudioSettings from './audio-settings/component';
|
||||
|
||||
export default class Audio extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.JOIN_AUDIO = 0;
|
||||
this.AUDIO_SETTINGS = 1;
|
||||
this.LISTEN_ONLY = 2;
|
||||
|
||||
this.submenus = [];
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
/* activeSubmenu represents the submenu in the submenus array to be displayed to the user,
|
||||
* initialized to 0
|
||||
*/
|
||||
this.setState({ activeSubmenu: 0 });
|
||||
this.submenus.push({ componentName: JoinAudio, });
|
||||
this.submenus.push({ componentName: AudioSettings, });
|
||||
this.submenus.push({ componentName: ListenOnly, });
|
||||
}
|
||||
|
||||
changeMenu(i) {
|
||||
this.setState({ activeSubmenu: i });
|
||||
}
|
||||
|
||||
createMenu() {
|
||||
const curr = this.state.activeSubmenu === undefined ? 0 : this.state.activeSubmenu;
|
||||
|
||||
let props = {
|
||||
changeMenu: this.changeMenu.bind(this),
|
||||
JOIN_AUDIO: this.JOIN_AUDIO,
|
||||
AUDIO_SETTINGS: this.AUDIO_SETTINGS,
|
||||
LISTEN_ONLY: this.LISTEN_ONLY,
|
||||
};
|
||||
|
||||
const Submenu = this.submenus[curr].componentName;
|
||||
return <Submenu {...props}/>;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ModalBase
|
||||
isOpen={true}
|
||||
onHide={null}
|
||||
onShow={null}
|
||||
className={styles.inner}>
|
||||
<div>
|
||||
{this.createMenu()}
|
||||
</div>
|
||||
</ModalBase>
|
||||
);
|
||||
}
|
||||
};
|
||||
className={styles.inner}>
|
||||
<div>
|
||||
{this.createMenu()}
|
||||
</div>
|
||||
</ModalBase>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@ -1,67 +1,65 @@
|
||||
import React from 'react';
|
||||
import Button from '/imports/ui/components/button/component';
|
||||
import { clearModal } from '/imports/ui/components/app/service';
|
||||
import classNames from 'classnames';
|
||||
import ReactDOM from 'react-dom';
|
||||
import styles from '../styles.scss';
|
||||
|
||||
export default class JoinAudio extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handleClose = this.handleClose.bind(this);
|
||||
this.openAudio = this.openAudio.bind(this);
|
||||
this.openListen = this.openListen.bind(this);
|
||||
}
|
||||
|
||||
handleClose() {
|
||||
this.setState({ isOpen: false });
|
||||
clearModal();
|
||||
}
|
||||
|
||||
openAudio() {
|
||||
this.props.changeMenu(this.props.AUDIO_SETTINGS);
|
||||
}
|
||||
|
||||
openListen() {
|
||||
this.props.changeMenu(this.props.LISTEN_ONLY);
|
||||
}
|
||||
|
||||
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.center}>
|
||||
<Button className={styles.closeBtn}
|
||||
label={'Close'}
|
||||
icon={'close'}
|
||||
size={'lg'}
|
||||
circle={true}
|
||||
hideLabel={true}
|
||||
onClick={this.handleClose}
|
||||
/>
|
||||
<div>
|
||||
How would you like to join the audio?
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.center}>
|
||||
<Button className={styles.audioBtn}
|
||||
label={'Audio'}
|
||||
icon={'audio'}
|
||||
circle={true}
|
||||
size={'jumbo'}
|
||||
onClick={this.openAudio}
|
||||
/>
|
||||
<Button className={styles.audioBtn}
|
||||
label={'Listen Only'}
|
||||
icon={'listen'}
|
||||
circle={true}
|
||||
size={'jumbo'}
|
||||
onClick={this.openListen}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
import React from 'react';
|
||||
import Button from '/imports/ui/components/button/component';
|
||||
import { clearModal } from '/imports/ui/components/app/service';
|
||||
import classNames from 'classnames';
|
||||
import ReactDOM from 'react-dom';
|
||||
import styles from '../styles.scss';
|
||||
|
||||
export default class JoinAudio extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handleClose = this.handleClose.bind(this);
|
||||
this.openAudio = this.openAudio.bind(this);
|
||||
this.openListen = this.openListen.bind(this);
|
||||
}
|
||||
|
||||
handleClose() {
|
||||
this.setState({ isOpen: false });
|
||||
clearModal();
|
||||
}
|
||||
|
||||
openAudio() {
|
||||
this.props.changeMenu(this.props.AUDIO_SETTINGS);
|
||||
}
|
||||
|
||||
openListen() {
|
||||
this.props.changeMenu(this.props.LISTEN_ONLY);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.center}>
|
||||
<Button className={styles.closeBtn}
|
||||
label={'Close'}
|
||||
icon={'close'}
|
||||
size={'lg'}
|
||||
circle={true}
|
||||
hideLabel={true}
|
||||
onClick={this.handleClose}
|
||||
/>
|
||||
<div>
|
||||
How would you like to join the audio?
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.center}>
|
||||
<Button className={styles.audioBtn}
|
||||
label={'Audio'}
|
||||
icon={'audio'}
|
||||
circle={true}
|
||||
size={'jumbo'}
|
||||
onClick={this.openAudio}
|
||||
/>
|
||||
<Button className={styles.audioBtn}
|
||||
label={'Listen Only'}
|
||||
icon={'listen'}
|
||||
circle={true}
|
||||
size={'jumbo'}
|
||||
onClick={this.openListen}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@ -1,54 +1,53 @@
|
||||
import React from 'react';
|
||||
import Button from '/imports/ui/components/button/component';
|
||||
import { clearModal } from '/imports/ui/components/app/service';
|
||||
import { joinListenOnly } from '/imports/api/phone';
|
||||
import classNames from 'classnames';
|
||||
import ReactDOM from 'react-dom';
|
||||
import styles from '../styles.scss';
|
||||
|
||||
export default class ListenOnly extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.chooseAudio = this.chooseAudio.bind(this);
|
||||
}
|
||||
|
||||
chooseAudio() {
|
||||
this.props.changeMenu(this.props.JOIN_AUDIO);
|
||||
}
|
||||
|
||||
joinListen() {
|
||||
joinListenOnly();
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.center}>
|
||||
<Button className={styles.backBtn}
|
||||
label={'Back'}
|
||||
icon={'left-arrow'}
|
||||
size={'md'}
|
||||
color={'primary'}
|
||||
ghost={true}
|
||||
onClick={this.chooseAudio}
|
||||
/>
|
||||
<div>
|
||||
Listen only message
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
Content goes here<br /><br />
|
||||
Volume Slider Here
|
||||
<Button className={styles.enterBtn}
|
||||
label={'Enter Session'}
|
||||
size={'md'}
|
||||
color={'primary'}
|
||||
onClick={this.joinListen}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
import React from 'react';
|
||||
import Button from '/imports/ui/components/button/component';
|
||||
import { clearModal } from '/imports/ui/components/app/service';
|
||||
import { joinListenOnly } from '/imports/api/phone';
|
||||
import classNames from 'classnames';
|
||||
import ReactDOM from 'react-dom';
|
||||
import styles from '../styles.scss';
|
||||
|
||||
export default class ListenOnly extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.chooseAudio = this.chooseAudio.bind(this);
|
||||
}
|
||||
|
||||
chooseAudio() {
|
||||
this.props.changeMenu(this.props.JOIN_AUDIO);
|
||||
}
|
||||
|
||||
joinListen() {
|
||||
joinListenOnly();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.center}>
|
||||
<Button className={styles.backBtn}
|
||||
label={'Back'}
|
||||
icon={'left-arrow'}
|
||||
size={'md'}
|
||||
color={'primary'}
|
||||
ghost={true}
|
||||
onClick={this.chooseAudio}
|
||||
/>
|
||||
<div>
|
||||
Listen only message
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
Content goes here<br /><br />
|
||||
Volume Slider Here
|
||||
<Button className={styles.enterBtn}
|
||||
label={'Enter Session'}
|
||||
size={'md'}
|
||||
color={'primary'}
|
||||
onClick={this.joinListen}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
0
bigbluebutton-html5/imports/ui/components/chat/message-form/component.jsx
Normal file → Executable file
0
bigbluebutton-html5/imports/ui/components/chat/message-form/component.jsx
Normal file → Executable file
3
bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/component.jsx
Normal file → Executable file
3
bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/component.jsx
Normal file → Executable file
@ -41,8 +41,9 @@ export default class MessageListItem extends Component {
|
||||
</div>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.meta}>
|
||||
<div className={styles.name}>
|
||||
<div className={user.isLogin ? styles.name : styles.logout}>
|
||||
<span>{user.name}</span>
|
||||
{!user.isLogin ? <span className={styles.offline}> (offline)</span> : null}
|
||||
</div>
|
||||
<time className={styles.time} dateTime={dateTime}>
|
||||
<FormattedTime value={dateTime}/>
|
||||
|
@ -57,6 +57,26 @@
|
||||
}
|
||||
}
|
||||
|
||||
.logout {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
font-weight: 600;
|
||||
color: $color-gray-light;
|
||||
text-transform: capitalize;
|
||||
font-style: italic;
|
||||
|
||||
> span {
|
||||
@extend %text-elipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.offline {
|
||||
font-weight: 100;
|
||||
color: $color-gray-light;
|
||||
text-transform: capitalize;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.time {
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
|
@ -37,6 +37,20 @@ const mapUser = (user) => ({
|
||||
isListenOnly: user.listenOnly,
|
||||
isSharingWebcam: user.webcam_stream.length,
|
||||
isLocked: user.locked,
|
||||
isLogin: true,
|
||||
});
|
||||
|
||||
const logoutUser = (userID, userName) => ({
|
||||
id: userID,
|
||||
name: userName,
|
||||
emoji: {
|
||||
status: 'none',
|
||||
},
|
||||
isPresenter: false,
|
||||
isModerator: false,
|
||||
isCurrent: false,
|
||||
isVoiceUser: false,
|
||||
isLogin: false,
|
||||
});
|
||||
|
||||
const mapMessage = (messagePayload) => {
|
||||
@ -50,7 +64,7 @@ const mapMessage = (messagePayload) => {
|
||||
};
|
||||
|
||||
if (message.chat_type !== SYSTEM_CHAT_TYPE) {
|
||||
mappedMessage.sender = getUser(message.from_userid);
|
||||
mappedMessage.sender = getUser(message.from_userid, message.from_username);
|
||||
}
|
||||
|
||||
return mappedMessage;
|
||||
@ -86,12 +100,12 @@ const reduceMessages = (previous, current, index, array) => {
|
||||
}
|
||||
};
|
||||
|
||||
const getUser = (userID) => {
|
||||
const getUser = (userID, userName) => {
|
||||
const user = Users.findOne({ userId: userID });
|
||||
if (user) {
|
||||
return mapUser(user.user);
|
||||
} else {
|
||||
return null;
|
||||
return logoutUser(userID, userName);
|
||||
}
|
||||
};
|
||||
|
||||
|
77
bigbluebutton-html5/imports/ui/components/settings/submenus/users/component.jsx
Normal file → Executable file
77
bigbluebutton-html5/imports/ui/components/settings/submenus/users/component.jsx
Normal file → Executable file
@ -1,62 +1,15 @@
|
||||
import React from 'react';
|
||||
import Modal from 'react-modal';
|
||||
import Icon from '/imports/ui/components/icon/component';
|
||||
import Button from '/imports/ui/components/button/component';
|
||||
import BaseMenu from '../base/component';
|
||||
import styles from '../styles.scss';
|
||||
|
||||
export default class UsersMenu extends BaseMenu {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
getContent() {
|
||||
return (
|
||||
<div className={styles.full} role='presentation'>
|
||||
<div className={styles.row} role='presentation'>
|
||||
<label><input className={styles.checkboxOffset} type='checkbox' tabIndex='7'
|
||||
aria-labelledby='muteALlLabel' aria-describedby='muteAllDesc' />
|
||||
Mute all except the presenter</label>
|
||||
</div>
|
||||
<div id='muteAllLabel' hidden>Mute all</div>
|
||||
<div id='muteAllDesc' hidden>Mutes all participants except the presenter.</div>
|
||||
|
||||
<div className={styles.row} role='presentation'>
|
||||
<label><input className={styles.checkboxOffset} type="checkbox" tabIndex='8'
|
||||
aria-labelledby='lockAllLabel' aria-describedby='lockAllDesc' />
|
||||
Lock all participants</label>
|
||||
</div>
|
||||
<div id='lockAllLabel' hidden>Lock all</div>
|
||||
<div id='lockAllDesc' hidden>Toggles locked status for all participants.</div>
|
||||
|
||||
<div className={styles.indentedRow} role='presentation'>
|
||||
<label><input className={styles.checkboxOffset} type='checkbox' tabIndex='9'
|
||||
aria-labelledby='webcamLabel' aria-describedby='webcamDesc' />Webcam</label>
|
||||
</div>
|
||||
<div id='webcamLabel' hidden>Webcam lock</div>
|
||||
<div id='webcamDesc' hidden>Disables the webcam for all locked participants.</div>
|
||||
|
||||
<div className={styles.indentedRow} role='presentation'>
|
||||
<label><input className={styles.checkboxOffset} type='checkbox' tabIndex='10'
|
||||
aria-labelledby='micLabel' aria-describedby='micDesc' />Microphone</label>
|
||||
</div>
|
||||
<div id='micLabel' hidden>Microphone lock</div>
|
||||
<div id='micDesc' hidden>Disables the microphone for all locked participants.</div>
|
||||
|
||||
<div className={styles.indentedRow} role='presentation'>
|
||||
<label><input className={styles.checkboxOffset} type='checkbox' tabIndex='11'
|
||||
aria-labelledby='pubChatLabel' aria-describedby='pubChatDesc' />Public chat</label>
|
||||
</div>
|
||||
<div id='pubChatLabel' hidden>Public chat lock</div>
|
||||
<div id='pubChatDesc' hidden>Disables public chat for all locked participants.</div>
|
||||
|
||||
<div className={styles.indentedRow} role='presentation'>
|
||||
<label><input className={styles.checkboxOffset} type='checkbox' tabIndex='12'
|
||||
aria-labelledby='privChatLabel' aria-describedby='privChatDesc' />Private chat</label>
|
||||
</div>
|
||||
<div id='privChatLabel' hidden>Private chat lock</div>
|
||||
<div id='privChatDesc' hidden>Disables private chat for all locked participants.</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
import React from 'react';
|
||||
import BaseMenu from '../base/component';
|
||||
import ParticipantsMenuContainer from './participants/container';
|
||||
|
||||
export default class UsersMenu extends BaseMenu {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ParticipantsMenuContainer />
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@ -0,0 +1,115 @@
|
||||
import React from 'react';
|
||||
import Modal from 'react-modal';
|
||||
import Icon from '/imports/ui/components/icon/component';
|
||||
import Button from '/imports/ui/components/button/component';
|
||||
import styles from '../../styles.scss';
|
||||
import Service from './service';
|
||||
|
||||
const ROLE_MODERATOR = 'moderator';
|
||||
|
||||
const LOCK_OPTIONS = {
|
||||
MUTE_ALL: {
|
||||
label: 'Mute all except the presenter',
|
||||
ariaLabelledBy: 'muteALlLabel',
|
||||
ariaDescribedBy: 'muteAllDesc',
|
||||
ariaLabel: 'Mute all',
|
||||
ariaDesc: 'Mutes all participants except the presenter.',
|
||||
tabIndex: 7,
|
||||
},
|
||||
LOCK_ALL: {
|
||||
label: 'Lock all participants',
|
||||
ariaLabelledBy: 'lockAllLabel',
|
||||
ariaDescribedBy: 'lockAllDesc',
|
||||
ariaLabel: 'Lock all',
|
||||
ariaDesc: 'Toggles locked status for all participants.',
|
||||
tabIndex: 8,
|
||||
},
|
||||
WEBCAM_LOCK: {
|
||||
label: 'Webcam',
|
||||
ariaLabelledBy: 'webcamLabel',
|
||||
ariaDescribedBy: 'webcamDesc',
|
||||
ariaLabel: 'Webcam lock',
|
||||
ariaDesc: 'Disables the webcam for all locked participants.',
|
||||
tabIndex: 9,
|
||||
},
|
||||
MIC_LOCK: {
|
||||
label: 'Microphone',
|
||||
ariaLabelledBy: 'micLabel',
|
||||
ariaDescribedBy: 'micDesc',
|
||||
ariaLabel: 'Microphone lock',
|
||||
ariaDesc: 'Disables the microphone for all locked participants.',
|
||||
tabIndex: 10,
|
||||
},
|
||||
PUBLIC_CHAT_LOCK: {
|
||||
label: 'Public chat',
|
||||
ariaLabelledBy: 'pubChatLabel',
|
||||
ariaDescribedBy: 'pubChatDesc',
|
||||
ariaLabel: 'Public chat lock',
|
||||
ariaDesc: 'Disables public chat for all locked participants.',
|
||||
tabIndex: 11,
|
||||
},
|
||||
PRIVATE_CHAT_LOCK: {
|
||||
label: 'Private chat',
|
||||
ariaLabelledBy: 'privChatLabel',
|
||||
ariaDescribedBy: 'privChatDesc',
|
||||
ariaLabel: 'Private chat lock',
|
||||
ariaDesc: 'Disables private chat for all locked participants.',
|
||||
tabIndex: 12,
|
||||
},
|
||||
};
|
||||
|
||||
export default class ParticipantsMenu extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
renderLockItem(lockOption) {
|
||||
return (
|
||||
<div className={styles.row} role='presentation' key={lockOption.label} >
|
||||
<label>
|
||||
<input
|
||||
className={styles.checkboxOffset}
|
||||
type="checkbox"
|
||||
tabIndex={lockOption.tabIndex}
|
||||
aria-labelledby={lockOption.ariaLabelledBy}
|
||||
aria-describedby={lockOption.ariaDescribedBy} />
|
||||
{lockOption.label}
|
||||
</label>
|
||||
<div id={lockOption.ariaLabelledBy} hidden>{lockOption.ariaLabel}</div>
|
||||
<div id={lockOption.ariaDescribedBy} hidden>{lockOption.ariaDesc}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderLockOptions () {
|
||||
|
||||
const { isPresenter, role } = this.props;
|
||||
|
||||
let roleBasedOptions = [];
|
||||
|
||||
if (isPresenter || role === ROLE_MODERATOR) {
|
||||
roleBasedOptions.push(
|
||||
this.renderLockItem(LOCK_OPTIONS.LOCK_ALL),
|
||||
this.renderLockItem(LOCK_OPTIONS.WEBCAM_LOCK),
|
||||
this.renderLockItem(LOCK_OPTIONS.MIC_LOCK),
|
||||
this.renderLockItem(LOCK_OPTIONS.PUBLIC_CHAT_LOCK),
|
||||
this.renderLockItem(LOCK_OPTIONS.PRIVATE_CHAT_LOCK),
|
||||
);
|
||||
}
|
||||
|
||||
return _.compact([
|
||||
this.renderLockItem(LOCK_OPTIONS.MUTE_ALL),
|
||||
...roleBasedOptions,
|
||||
]);
|
||||
}
|
||||
|
||||
render () {
|
||||
|
||||
return (
|
||||
<div>
|
||||
{this.renderLockOptions()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
};
|
@ -0,0 +1,24 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { createContainer } from 'meteor/react-meteor-data';
|
||||
|
||||
import ParticipantsMenu from './component';
|
||||
import Service from './service';
|
||||
|
||||
class ParticipantsMenuContainer extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ParticipantsMenu {...this.props}>
|
||||
{this.props.children}
|
||||
</ParticipantsMenu>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default createContainer(() => {
|
||||
let data = Service.checkUserRoles();
|
||||
return data;
|
||||
}, ParticipantsMenuContainer);
|
@ -0,0 +1,17 @@
|
||||
import Users from '/imports/api/users';
|
||||
import AuthSingleton from '/imports/ui/services/auth/index.js';
|
||||
|
||||
checkUserRoles = () => {
|
||||
const user = Users.findOne({
|
||||
userId: AuthSingleton.getCredentials().requesterUserId,
|
||||
}).user;
|
||||
|
||||
return {
|
||||
isPresenter: user.presenter,
|
||||
role: user.role,
|
||||
};
|
||||
};
|
||||
|
||||
export default {
|
||||
checkUserRoles,
|
||||
};
|
26
bigbluebutton-html5/imports/ui/components/user-avatar/color-generator.js
Normal file → Executable file
26
bigbluebutton-html5/imports/ui/components/user-avatar/color-generator.js
Normal file → Executable file
@ -25,6 +25,19 @@ const stringToPastelColour = (str) => {
|
||||
};
|
||||
};
|
||||
|
||||
const whiteColour = (str) => {
|
||||
|
||||
let baseRed = 255;
|
||||
let baseGreen = 255;
|
||||
let baseBlue = 255;
|
||||
|
||||
return {
|
||||
r: baseRed,
|
||||
g: baseGreen,
|
||||
b: baseBlue,
|
||||
};
|
||||
};
|
||||
|
||||
// https://www.w3.org/TR/WCAG20/#relativeluminancedef
|
||||
// http://entropymine.com/imageworsener/srgbformula/
|
||||
const relativeLuminance = (rgb) => {
|
||||
@ -99,10 +112,15 @@ const addShadeIfNoContrast = (rgb) => {
|
||||
return addShadeIfNoContrast(shadeColor(rgb, -25));
|
||||
};
|
||||
|
||||
const getColor = (username) => {
|
||||
let rgb = stringToPastelColour(username);
|
||||
rgb = addShadeIfNoContrast(rgb);
|
||||
return 'rgb(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ')';
|
||||
const getColor = (username, chkLogin) => {
|
||||
if (chkLogin) {
|
||||
let rgb = stringToPastelColour(username);
|
||||
rgb = addShadeIfNoContrast(rgb);
|
||||
return 'rgb(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ')';
|
||||
} else {
|
||||
let rgb = whiteColour();
|
||||
return 'rgb(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ')';
|
||||
}
|
||||
};
|
||||
|
||||
export default getColor;
|
||||
|
@ -24,11 +24,12 @@ export default class UserAvatar extends Component {
|
||||
} = this.props;
|
||||
|
||||
let avatarStyles = {
|
||||
backgroundColor: getColor(user.name),
|
||||
backgroundColor: getColor(user.name, user.isLogin),
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.userAvatar} style={avatarStyles}>
|
||||
<div className={user.isLogin ? styles.userAvatar : styles.userLogout}
|
||||
style={avatarStyles}>
|
||||
<span>
|
||||
{this.renderAvatarContent()}
|
||||
</span>
|
||||
|
18
bigbluebutton-html5/imports/ui/components/user-avatar/styles.scss
Normal file → Executable file
18
bigbluebutton-html5/imports/ui/components/user-avatar/styles.scss
Normal file → Executable file
@ -29,6 +29,24 @@ $moderator-bg: $color-primary;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.userLogout {
|
||||
flex-basis: 2.2rem;
|
||||
height: 2.2rem;
|
||||
flex-shrink: 0;
|
||||
line-height: 2.2rem;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
font-size: 1.1rem;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
border: 1px solid $color-gray-lighter;
|
||||
color: $color-gray-light;
|
||||
font-style: italic;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.userStatus {
|
||||
position: absolute;
|
||||
background-color: $color-white;
|
||||
|
@ -28,6 +28,7 @@ const mapUser = user => ({
|
||||
isListenOnly: user.listenOnly,
|
||||
isSharingWebcam: user.webcam_stream.length,
|
||||
isPhoneUser: user.phone_user,
|
||||
isLogin: user ? true : false,
|
||||
});
|
||||
|
||||
const mapOpenChats = chat => {
|
||||
|
@ -1,11 +1,7 @@
|
||||
import '/imports/startup/server';
|
||||
import '/imports/api/chat/server';
|
||||
|
||||
import '/imports/api/cursor/server/publications';
|
||||
import '/imports/api/cursor/server/modifiers/clearCursorCollection';
|
||||
import '/imports/api/cursor/server/modifiers/initializeCursor';
|
||||
import '/imports/api/cursor/server/modifiers/updateCursorLocation';
|
||||
import '/imports/api/cursor/server/modifiers/eventHandlers';
|
||||
import '/imports/api/cursor/server';
|
||||
|
||||
import '/imports/api/deskshare/server/publications';
|
||||
import '/imports/api/deskshare/server/modifiers/clearDeskshareCollection';
|
||||
@ -21,12 +17,7 @@ import '/imports/api/polls/server';
|
||||
|
||||
import '/imports/api/presentations/server';
|
||||
|
||||
import '/imports/api/shapes/server/publications';
|
||||
import '/imports/api/shapes/server/modifiers/addShapeToCollection';
|
||||
import '/imports/api/shapes/server/modifiers/clearShapesCollection';
|
||||
import '/imports/api/shapes/server/modifiers/removeAllShapesFromSlide';
|
||||
import '/imports/api/shapes/server/modifiers/removeShapeFromSlide';
|
||||
import '/imports/api/shapes/server/modifiers/eventHandlers';
|
||||
import '/imports/api/shapes/server';
|
||||
|
||||
import '/imports/api/slides/server';
|
||||
|
||||
|
@ -280,7 +280,7 @@ module BigBlueButton
|
||||
return {} if !info[:format]
|
||||
|
||||
info[:video] = info[:streams].find do |stream|
|
||||
stream[:codec_type] == 'audio'
|
||||
stream[:codec_type] == 'video'
|
||||
end
|
||||
|
||||
return {} if !info[:video]
|
||||
|
Loading…
Reference in New Issue
Block a user