Got all the single-line comments back.
This commit is contained in:
parent
a844cb9356
commit
cc6ffe15df
@ -1,49 +1,56 @@
|
||||
// Methods return a reference to itself to allow chaining
|
||||
this.NotificationControl = (() => {
|
||||
let container, notifications;
|
||||
|
||||
container = '';
|
||||
container = ''; // holds where the alerts will go
|
||||
|
||||
notifications = {};
|
||||
|
||||
class NotificationControl {
|
||||
constructor(c) {
|
||||
container = c[0] === '#' ? c.substr(1) : c;
|
||||
$("#whiteboard").prepend(
|
||||
container = c[0] === '#' ? c.substr(1) : c; // prepend '#' to the identifier
|
||||
$("#whiteboard").prepend( // create container for notifications
|
||||
`<!-- Drawing area for notifications. Must have "data-alert" atrribute, I do not know why, typically only for actual notifications -->${"<div id=\"" + container + "\" data-alert></div>"}`
|
||||
);
|
||||
}
|
||||
|
||||
// id: name of the notification
|
||||
// type: optional style classes
|
||||
// content: the notification's message
|
||||
// nDuration: how many milliseconds the notification will stay (less than 1 implies permanent)
|
||||
// nFadeTime: how many milliseconds it takes the notification to be removed
|
||||
create(id, type, content, nDuration, nFadeTime) {
|
||||
let elementId;
|
||||
elementId = id[0] === '#' ? id.substr(1) : id;
|
||||
elementId = id[0] === '#' ? id.substr(1) : id; // remove prepended '#' from the identifier
|
||||
notifications[elementId] = {};
|
||||
notifications[elementId].element = '';
|
||||
notifications[elementId].element += `<div id="${elementId}" data-alert class='bbbNotification alert-box ${type}' tabindex='0' aria-live='assertive' role='dialogalert'>`;
|
||||
notifications[elementId].element += `${content}`;
|
||||
notifications[elementId].element += '<button href="#" tabindex="0" class="close" aria-label="Close Alert">×</button>';
|
||||
notifications[elementId].element += '</div>';
|
||||
notifications[elementId].duration = nDuration || -1;
|
||||
notifications[elementId].duration = nDuration || -1; // if no time is specified, it must be dismissed by the user
|
||||
notifications[elementId].fadeTime = nFadeTime || 1000;
|
||||
return this;
|
||||
}
|
||||
|
||||
registerShow(elementId, nShowNotification) {
|
||||
registerShow(elementId, nShowNotification) { // register the method to be called when showing the notification
|
||||
notifications[elementId].showNotification = nShowNotification;
|
||||
return this;
|
||||
}
|
||||
|
||||
registerHide(elementId, nHideNotification) {
|
||||
registerHide(elementId, nHideNotification) { // register the method called when hiding the notification
|
||||
notifications[elementId].hideNotification = nHideNotification;
|
||||
return this;
|
||||
}
|
||||
|
||||
display(elementId) {
|
||||
display(elementId) { // called the registered methods
|
||||
let base;
|
||||
$(`#${container}`).append(notifications[elementId].element);
|
||||
$(`#${container}`).append(notifications[elementId].element); // display the notification
|
||||
if(typeof (base = notifications[elementId]).showNotification === "function") {
|
||||
base.showNotification();
|
||||
}
|
||||
setTimeout((_this => {
|
||||
// remove the notification if the user selected to
|
||||
return function() {
|
||||
let base1;
|
||||
if(notifications[elementId].duration > 0) {
|
||||
@ -52,12 +59,13 @@ this.NotificationControl = (() => {
|
||||
if(typeof (base1 = notifications[elementId]).hideNotification === "function") {
|
||||
base1.hideNotification();
|
||||
}
|
||||
return notifications[elementId] = {};
|
||||
return notifications[elementId] = {}; // delete all notification data
|
||||
};
|
||||
})(this), notifications[elementId].duration);
|
||||
return this;
|
||||
}
|
||||
|
||||
// hides a notification that may have been left over
|
||||
hideANotification(elementId) {
|
||||
let base;
|
||||
$(`#${elementId}`).fadeOut(notifications[elementId].fadeTime, () => {
|
||||
@ -70,24 +78,30 @@ this.NotificationControl = (() => {
|
||||
}
|
||||
}
|
||||
|
||||
// static icon members
|
||||
NotificationControl.icons = {
|
||||
// RaphaelJS "settings" icon
|
||||
'settings_IconPath': 'M17.41,20.395l-0.778-2.723c0.228-0.2,0.442-0.414,0.644-0.643l2.721,0.778c0.287-0.418,0.534-0.862,0.755-1.323l-2.025-1.96c0.097-0.288,0.181-0.581,0.241-0.883l2.729-0.684c0.02-0.252,0.039-0.505,0.039-0.763s-0.02-0.51-0.039-0.762l-2.729-0.684c-0.061-0.302-0.145-0.595-0.241-0.883l2.026-1.96c-0.222-0.46-0.469-0.905-0.756-1.323l-2.721,0.777c-0.201-0.228-0.416-0.442-0.644-0.643l0.778-2.722c-0.418-0.286-0.863-0.534-1.324-0.755l-1.96,2.026c-0.287-0.097-0.581-0.18-0.883-0.241l-0.683-2.73c-0.253-0.019-0.505-0.039-0.763-0.039s-0.51,0.02-0.762,0.039l-0.684,2.73c-0.302,0.061-0.595,0.144-0.883,0.241l-1.96-2.026C7.048,3.463,6.604,3.71,6.186,3.997l0.778,2.722C6.736,6.919,6.521,7.134,6.321,7.361L3.599,6.583C3.312,7.001,3.065,7.446,2.844,7.907l2.026,1.96c-0.096,0.288-0.18,0.581-0.241,0.883l-2.73,0.684c-0.019,0.252-0.039,0.505-0.039,0.762s0.02,0.51,0.039,0.763l2.73,0.684c0.061,0.302,0.145,0.595,0.241,0.883l-2.026,1.96c0.221,0.46,0.468,0.905,0.755,1.323l2.722-0.778c0.2,0.229,0.415,0.442,0.643,0.643l-0.778,2.723c0.418,0.286,0.863,0.533,1.323,0.755l1.96-2.026c0.288,0.097,0.581,0.181,0.883,0.241l0.684,2.729c0.252,0.02,0.505,0.039,0.763,0.039s0.51-0.02,0.763-0.039l0.683-2.729c0.302-0.061,0.596-0.145,0.883-0.241l1.96,2.026C16.547,20.928,16.992,20.681,17.41,20.395zM11.798,15.594c-1.877,0-3.399-1.522-3.399-3.399s1.522-3.398,3.399-3.398s3.398,1.521,3.398,3.398S13.675,15.594,11.798,15.594zM27.29,22.699c0.019-0.547-0.06-1.104-0.23-1.654l1.244-1.773c-0.188-0.35-0.4-0.682-0.641-0.984l-2.122,0.445c-0.428-0.364-0.915-0.648-1.436-0.851l-0.611-2.079c-0.386-0.068-0.777-0.105-1.173-0.106l-0.974,1.936c-0.279,0.054-0.558,0.128-0.832,0.233c-0.257,0.098-0.497,0.22-0.727,0.353L17.782,17.4c-0.297,0.262-0.568,0.545-0.813,0.852l0.907,1.968c-0.259,0.495-0.437,1.028-0.519,1.585l-1.891,1.06c0.019,0.388,0.076,0.776,0.164,1.165l2.104,0.519c0.231,0.524,0.541,0.993,0.916,1.393l-0.352,2.138c0.32,0.23,0.66,0.428,1.013,0.6l1.715-1.32c0.536,0.141,1.097,0.195,1.662,0.15l1.452,1.607c0.2-0.057,0.399-0.118,0.596-0.193c0.175-0.066,0.34-0.144,0.505-0.223l0.037-2.165c0.455-0.339,0.843-0.747,1.152-1.206l2.161-0.134c0.152-0.359,0.279-0.732,0.368-1.115L27.29,22.699zM23.127,24.706c-1.201,0.458-2.545-0.144-3.004-1.345s0.143-2.546,1.344-3.005c1.201-0.458,2.547,0.144,3.006,1.345C24.931,22.902,24.328,24.247,23.127,24.706z',
|
||||
// RaphaelJS "Safari" icon
|
||||
'Safari_IconPath': 'M16.154,5.135c-0.504,0-1,0.031-1.488,0.089l-0.036-0.18c-0.021-0.104-0.06-0.198-0.112-0.283c0.381-0.308,0.625-0.778,0.625-1.306c0-0.927-0.751-1.678-1.678-1.678s-1.678,0.751-1.678,1.678c0,0.745,0.485,1.376,1.157,1.595c-0.021,0.105-0.021,0.216,0,0.328l0.033,0.167C7.645,6.95,3.712,11.804,3.712,17.578c0,6.871,5.571,12.441,12.442,12.441c6.871,0,12.441-5.57,12.441-12.441C28.596,10.706,23.025,5.135,16.154,5.135zM16.369,8.1c4.455,0,8.183,3.116,9.123,7.287l-0.576,0.234c-0.148-0.681-0.755-1.191-1.48-1.191c-0.837,0-1.516,0.679-1.516,1.516c0,0.075,0.008,0.148,0.018,0.221l-2.771-0.028c-0.054-0.115-0.114-0.226-0.182-0.333l3.399-5.11l0.055-0.083l-4.766,4.059c-0.352-0.157-0.74-0.248-1.148-0.256l0.086-0.018l-1.177-2.585c0.64-0.177,1.111-0.763,1.111-1.459c0-0.837-0.678-1.515-1.516-1.515c-0.075,0-0.147,0.007-0.219,0.018l0.058-0.634C15.357,8.141,15.858,8.1,16.369,8.1zM12.146,3.455c0-0.727,0.591-1.318,1.318-1.318c0.727,0,1.318,0.591,1.318,1.318c0,0.425-0.203,0.802-0.516,1.043c-0.183-0.123-0.413-0.176-0.647-0.13c-0.226,0.045-0.413,0.174-0.535,0.349C12.542,4.553,12.146,4.049,12.146,3.455zM7.017,17.452c0-4.443,3.098-8.163,7.252-9.116l0.297,0.573c-0.61,0.196-1.051,0.768-1.051,1.442c0,0.837,0.678,1.516,1.515,1.516c0.068,0,0.135-0.006,0.2-0.015l-0.058,2.845l0.052-0.011c-0.442,0.204-0.824,0.513-1.116,0.895l0.093-0.147l-1.574-0.603l1.172,1.239l0.026-0.042c-0.19,0.371-0.306,0.788-0.324,1.229l-0.003-0.016l-2.623,1.209c-0.199-0.604-0.767-1.041-1.438-1.041c-0.837,0-1.516,0.678-1.516,1.516c0,0.064,0.005,0.128,0.013,0.191l-0.783-0.076C7.063,18.524,7.017,17.994,7.017,17.452zM16.369,26.805c-4.429,0-8.138-3.078-9.106-7.211l0.691-0.353c0.146,0.686,0.753,1.2,1.482,1.2c0.837,0,1.515-0.679,1.515-1.516c0-0.105-0.011-0.207-0.031-0.307l2.858,0.03c0.045,0.095,0.096,0.187,0.15,0.276l-3.45,5.277l0.227-0.195l4.529-3.92c0.336,0.153,0.705,0.248,1.094,0.266l-0.019,0.004l1.226,2.627c-0.655,0.166-1.142,0.76-1.142,1.468c0,0.837,0.678,1.515,1.516,1.515c0.076,0,0.151-0.007,0.225-0.018l0.004,0.688C17.566,26.746,16.975,26.805,16.369,26.805zM18.662,26.521l-0.389-0.6c0.661-0.164,1.152-0.759,1.152-1.47c0-0.837-0.68-1.516-1.516-1.516c-0.066,0-0.13,0.005-0.193,0.014v-2.86l-0.025,0.004c0.409-0.185,0.77-0.459,1.055-0.798l1.516,0.659l-1.104-1.304c0.158-0.335,0.256-0.704,0.278-1.095l2.552-1.164c0.19,0.618,0.766,1.068,1.447,1.068c0.838,0,1.516-0.679,1.516-1.516c0-0.069-0.006-0.137-0.016-0.204l0.65,0.12c0.089,0.517,0.136,1.049,0.136,1.591C25.722,21.826,22.719,25.499,18.662,26.521z',
|
||||
// RaphaelJS "Internet Explorer" icon
|
||||
'IE_IconPath': 'M27.998,2.266c-2.12-1.91-6.925,0.382-9.575,1.93c-0.76-0.12-1.557-0.185-2.388-0.185c-3.349,0-6.052,0.985-8.106,2.843c-2.336,2.139-3.631,4.94-3.631,8.177c0,0.028,0.001,0.056,0.001,0.084c3.287-5.15,8.342-7.79,9.682-8.487c0.212-0.099,0.338,0.155,0.141,0.253c-0.015,0.042-0.015,0,0,0c-2.254,1.35-6.434,5.259-9.146,10.886l-0.003-0.007c-1.717,3.547-3.167,8.529-0.267,10.358c2.197,1.382,6.13-0.248,9.295-2.318c0.764,0.108,1.567,0.165,2.415,0.165c5.84,0,9.937-3.223,11.399-7.924l-8.022-0.014c-0.337,1.661-1.464,2.548-3.223,2.548c-2.21,0-3.729-1.211-3.828-4.012l15.228-0.014c0.028-0.578-0.042-0.985-0.042-1.436c0-5.251-3.143-9.355-8.255-10.663c2.081-1.294,5.974-3.209,7.848-1.681c1.407,1.14,0.633,3.533,0.295,4.518c-0.056,0.254,0.24,0.296,0.296,0.057C28.814,5.573,29.026,3.194,27.998,2.266zM13.272,25.676c-2.469,1.475-5.873,2.539-7.539,1.289c-1.243-0.935-0.696-3.468,0.398-5.938c0.664,0.992,1.495,1.886,2.473,2.63C9.926,24.651,11.479,25.324,13.272,25.676zM12.714,13.046c0.042-2.435,1.787-3.49,3.617-3.49c1.928,0,3.49,1.112,3.49,3.49H12.714z'
|
||||
};
|
||||
|
||||
return NotificationControl;
|
||||
})();
|
||||
|
||||
this.notification_WebRTCAudioExited = function() {
|
||||
this.notification_WebRTCAudioExited = function() { // used when the user can join audio
|
||||
return Meteor.NotificationControl.create("webRTC_AudioExited", ' ', 'You have exited audio', 2500).display("webRTC_AudioExited");
|
||||
};
|
||||
|
||||
this.notification_WebRTCAudioJoining = function() {
|
||||
this.notification_WebRTCAudioJoining = function() { // used when the user can join audio
|
||||
// display joining notification
|
||||
Meteor.NotificationControl.create("webRTC_AudioJoining", '', 'Connecting to the audio call...', -1).registerShow("webRTC_AudioJoining", () => {}).display("webRTC_AudioJoining");
|
||||
return Tracker.autorun(comp => {
|
||||
if(BBB.amIInAudio()) {
|
||||
comp.stop();
|
||||
// joined. Displayed joined notification and hide the joining notification
|
||||
return Tracker.autorun(comp => { // wait until user is in
|
||||
if(BBB.amIInAudio()) { // display notification when you are in audio
|
||||
comp.stop(); // prevents computation from running twice (which can happen occassionally)
|
||||
return Meteor.NotificationControl.create("webRTC_AudioJoined", 'success ', '', 2500).registerShow("webRTC_AudioJoined", () => {
|
||||
Meteor.NotificationControl.hideANotification('webRTC_AudioJoining');
|
||||
return $("#webRTC_AudioJoined").prepend(`You've joined the ${BBB.amIListenOnlyAudio() ? 'Listen Only' : ''} audio`);
|
||||
@ -96,10 +110,11 @@ this.notification_WebRTCAudioJoining = function() {
|
||||
});
|
||||
};
|
||||
|
||||
this.notification_WebRTCNotSupported = function() {
|
||||
this.notification_WebRTCNotSupported = function() { // shown when the user's browser does not support WebRTC
|
||||
// create a new notification at the audio button they clicked to trigger the event
|
||||
return Meteor.NotificationControl.create("webRTC_NotSupported", 'alert', '', -1).registerShow("webRTC_NotSupported", () => {
|
||||
let browserName, ref;
|
||||
if(((ref = (browserName = getBrowserName())) === 'Safari' || ref === 'IE') || (browserName = "settings")) {
|
||||
if(((ref = (browserName = getBrowserName())) === 'Safari' || ref === 'IE') || (browserName = "settings")) { // show either the browser icon or cog gears
|
||||
$("#webRTC_NotSupported").prepend(
|
||||
`<div id="browser-icon-container"></div>${"Sorry,<br/>" + (browserName !== 'settings' ? browserName : 'your browser') + " doesn't support WebRTC"}`
|
||||
);
|
||||
|
@ -14,6 +14,7 @@ this.getBuildInformation = function() {
|
||||
};
|
||||
};
|
||||
|
||||
// Convert a color `value` as integer to a hex color (e.g. 255 to #0000ff)
|
||||
this.colourToHex = function(value) {
|
||||
let hex;
|
||||
hex = parseInt(value).toString(16);
|
||||
@ -23,9 +24,10 @@ this.colourToHex = function(value) {
|
||||
return `#${hex}`;
|
||||
};
|
||||
|
||||
// color can be a number (a hex converted to int) or a string (e.g. "#ffff00")
|
||||
this.formatColor = function(color) {
|
||||
if(color == null) {
|
||||
color = "0";
|
||||
color = "0"; // default value
|
||||
}
|
||||
if(!color.toString().match(/\#.*/)) {
|
||||
color = colourToHex(color);
|
||||
@ -37,22 +39,27 @@ this.getInSession = function(k) {
|
||||
return SessionAmplify.get(k);
|
||||
};
|
||||
|
||||
// returns epoch in ms
|
||||
this.getTime = function() {
|
||||
return (new Date).valueOf();
|
||||
};
|
||||
|
||||
// checks if the pan gesture is mostly horizontal
|
||||
this.isPanHorizontal = function(event) {
|
||||
return Math.abs(event.deltaX) > Math.abs(event.deltaY);
|
||||
};
|
||||
|
||||
// helper to determine whether user has joined any type of audio
|
||||
Handlebars.registerHelper("amIInAudio", () => {
|
||||
return BBB.amIInAudio();
|
||||
});
|
||||
|
||||
// helper to determine whether the user is in the listen only audio stream
|
||||
Handlebars.registerHelper("amIListenOnlyAudio", () => {
|
||||
return BBB.amIListenOnlyAudio();
|
||||
});
|
||||
|
||||
// helper to determine whether the user is in the listen only audio stream
|
||||
Handlebars.registerHelper("isMyMicLocked", () => {
|
||||
return BBB.isMyMicLocked();
|
||||
});
|
||||
@ -63,7 +70,7 @@ Handlebars.registerHelper("colourToHex", (_this => {
|
||||
};
|
||||
})(this));
|
||||
|
||||
Handlebars.registerHelper('equals', (a, b) => {
|
||||
Handlebars.registerHelper('equals', (a, b) => { // equals operator was dropped in Meteor's migration from Handlebars to Spacebars
|
||||
return a === b;
|
||||
});
|
||||
|
||||
@ -74,9 +81,11 @@ Handlebars.registerHelper("getCurrentMeeting", () => {
|
||||
Handlebars.registerHelper("getCurrentSlide", () => {
|
||||
let result;
|
||||
result = BBB.getCurrentSlide("helper getCurrentSlide");
|
||||
// console.log "result=#{JSON.stringify result}"
|
||||
return result;
|
||||
});
|
||||
|
||||
// Allow access through all templates
|
||||
Handlebars.registerHelper("getInSession", k => {
|
||||
return SessionAmplify.get(k);
|
||||
});
|
||||
@ -88,11 +97,14 @@ Handlebars.registerHelper("getMeetingName", () => {
|
||||
Handlebars.registerHelper("getShapesForSlide", () => {
|
||||
let currentSlide, ref;
|
||||
currentSlide = BBB.getCurrentSlide("helper getShapesForSlide");
|
||||
|
||||
// try to reuse the lines above
|
||||
return Meteor.Shapes.find({
|
||||
whiteboardId: currentSlide != null ? (ref = currentSlide.slide) != null ? ref.id : void 0 : void 0
|
||||
});
|
||||
});
|
||||
|
||||
// retrieves all users in the meeting
|
||||
Handlebars.registerHelper("getUsersInMeeting", () => {
|
||||
let users;
|
||||
users = Meteor.Users.find().fetch();
|
||||
@ -121,6 +133,7 @@ Handlebars.registerHelper("isCurrentUserMuted", () => {
|
||||
return BBB.amIMuted();
|
||||
});
|
||||
|
||||
//Retreives a username for a private chat tab from the database if it exists
|
||||
Handlebars.registerHelper("privateChatName", () => {
|
||||
let obj, ref;
|
||||
obj = Meteor.Users.findOne({
|
||||
@ -249,6 +262,7 @@ Handlebars.registerHelper('containerPosition', section => {
|
||||
}
|
||||
});
|
||||
|
||||
// vertically shrinks the whiteboard if the slide navigation controllers are present
|
||||
Handlebars.registerHelper('whiteboardSize', section => {
|
||||
if(BBB.isUserPresenter(getInSession('userId'))) {
|
||||
return 'presenter-whiteboard';
|
||||
@ -319,6 +333,11 @@ this.getSortedUserList = function(users) {
|
||||
} else if(!b.user.phone_user) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
//Check name (case-insensitive) in the event of a tie up above. If the name
|
||||
//is the same then use userID which should be unique making the order the same
|
||||
//across all clients.
|
||||
|
||||
if(a.user._sort_name < b.user._sort_name) {
|
||||
return -1;
|
||||
} else if(a.user._sort_name > b.user._sort_name) {
|
||||
@ -333,6 +352,7 @@ this.getSortedUserList = function(users) {
|
||||
return users;
|
||||
};
|
||||
|
||||
// transform plain text links into HTML tags compatible with Flash client
|
||||
this.linkify = function(str) {
|
||||
return str = str.replace(re_weburl, "<a href='event:$&'><u>$&</u></a>");
|
||||
};
|
||||
@ -347,7 +367,10 @@ this.safeString = function(str) {
|
||||
}
|
||||
};
|
||||
|
||||
this.toggleCam = function(event) {};
|
||||
this.toggleCam = function(event) {
|
||||
// Meteor.Users.update {_id: context._id} , {$set:{"user.sharingVideo": !context.sharingVideo}}
|
||||
// Meteor.call('userToggleCam', context._id, !context.sharingVideo)
|
||||
};
|
||||
|
||||
this.toggleChatbar = function() {
|
||||
setInSession("display_chatbar", !getInSession("display_chatbar"));
|
||||
@ -378,6 +401,8 @@ this.populateNotifications = function(msg) {
|
||||
let chat, chats, initChats, j, l, len, len1, myPrivateChats, myUserId, new_msg_userid, results, u, uniqueArray, users;
|
||||
myUserId = getInSession("userId");
|
||||
users = Meteor.Users.find().fetch();
|
||||
|
||||
// assuming that I only have access only to private messages where I am the sender or the recipient
|
||||
myPrivateChats = Meteor.Chat.find({
|
||||
'message.chat_type': 'PRIVATE_CHAT'
|
||||
}).fetch();
|
||||
@ -397,6 +422,8 @@ this.populateNotifications = function(msg) {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//keep unique entries only
|
||||
uniqueArray = uniqueArray.filter((itm, i, a) => {
|
||||
return i === a.indexOf(itm);
|
||||
});
|
||||
@ -418,6 +445,7 @@ this.populateNotifications = function(msg) {
|
||||
setInSession('chats', initChats);
|
||||
}
|
||||
results = [];
|
||||
//insert the unique entries in the collection
|
||||
for(l = 0, len1 = uniqueArray.length; l < len1; l++) {
|
||||
u = uniqueArray[l];
|
||||
chats = getInSession('chats');
|
||||
@ -510,13 +538,23 @@ this.closeMenus = function() {
|
||||
}
|
||||
};
|
||||
|
||||
// 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
|
||||
this.exitVoiceCall = function(event, afterExitCall) {
|
||||
let checkToHangupCall, hangupCallback;
|
||||
|
||||
// To be called when the hangup is initiated
|
||||
hangupCallback = function() {
|
||||
return console.log("Exiting Voice Conference");
|
||||
};
|
||||
|
||||
// Checks periodically until a call is established so we can successfully end the call
|
||||
// clean state
|
||||
getInSession("triedHangup", false);
|
||||
// function to initiate call
|
||||
(checkToHangupCall = function(context) {
|
||||
// if an attempt to hang up the call is made when the current session is not yet finished, the request has no effect
|
||||
// keep track in the session if we haven't tried a hangup
|
||||
if(BBB.getCallStatus() !== null && !getInSession("triedHangup")) {
|
||||
console.log("Attempting to hangup on WebRTC call");
|
||||
if(BBB.amIListenOnlyAudio()) {
|
||||
@ -540,10 +578,11 @@ this.exitVoiceCall = function(event, afterExitCall) {
|
||||
);
|
||||
return setTimeout(checkToHangupCall, Meteor.config.app.WebRTCHangupRetryInterval);
|
||||
}
|
||||
})(this);
|
||||
})(this); // automatically run function
|
||||
return false;
|
||||
};
|
||||
|
||||
// close the daudio UI, then join the conference. If listen only send the request to the server
|
||||
this.joinVoiceCall = function(event, arg) {
|
||||
let isListenOnly, joinCallback;
|
||||
isListenOnly = (arg != null ? arg : {}).isListenOnly;
|
||||
@ -554,6 +593,8 @@ this.joinVoiceCall = function(event, arg) {
|
||||
if(isListenOnly == null) {
|
||||
isListenOnly = true;
|
||||
}
|
||||
|
||||
// create voice call params
|
||||
joinCallback = function(message) {
|
||||
return console.log("Beginning WebRTC Conference Call");
|
||||
};
|
||||
@ -567,14 +608,17 @@ this.joinVoiceCall = function(event, arg) {
|
||||
true
|
||||
);
|
||||
}
|
||||
BBB.joinVoiceConference(joinCallback, isListenOnly);
|
||||
BBB.joinVoiceConference(joinCallback, isListenOnly); // make the call //TODO should we apply role permissions to this action?
|
||||
return false;
|
||||
};
|
||||
|
||||
// Starts the entire logout procedure.
|
||||
// meeting: the meeting the user is in
|
||||
// the user's userId
|
||||
this.userLogout = function(meeting, user) {
|
||||
Meteor.call("userLogout", meeting, user, getInSession("authToken"));
|
||||
console.log("logging out");
|
||||
return clearSessionVar(document.location = getInSession('logoutURL'));
|
||||
return clearSessionVar(document.location = getInSession('logoutURL')); // navigate to logout
|
||||
};
|
||||
|
||||
this.kickUser = function(meetingId, toKickUserId, requesterUserId, authToken) {
|
||||
@ -590,9 +634,10 @@ this.setUserPresenter = function(
|
||||
return Meteor.call("setUserPresenter", meetingId, newPresenterId, requesterSetPresenter, newPresenterName, authToken);
|
||||
};
|
||||
|
||||
// Clear the local user session
|
||||
this.clearSessionVar = function(callback) {
|
||||
amplify.store('authToken', null);
|
||||
amplify.store('bbbServerVersion', null);
|
||||
amplify.store('bbbServerVer1sion', null);
|
||||
amplify.store('chats', null);
|
||||
amplify.store('dateOfBuild', null);
|
||||
amplify.store('display_chatPane', null);
|
||||
@ -612,15 +657,18 @@ this.clearSessionVar = function(callback) {
|
||||
}
|
||||
};
|
||||
|
||||
// assign the default values for the Session vars
|
||||
this.setDefaultSettings = function() {
|
||||
let initChats;
|
||||
setInSession("display_navbar", true);
|
||||
setInSession("display_chatbar", true);
|
||||
setInSession("display_whiteboard", true);
|
||||
setInSession("display_chatPane", true);
|
||||
|
||||
//if it is a desktop version of the client
|
||||
if(isPortraitMobile() || isLandscapeMobile()) {
|
||||
setInSession("messageFontSize", Meteor.config.app.mobileFont);
|
||||
} else {
|
||||
} else { //if this is a mobile version of the client
|
||||
setInSession("messageFontSize", Meteor.config.app.desktopFont);
|
||||
}
|
||||
setInSession('display_slidingMenu', false);
|
||||
@ -632,6 +680,9 @@ this.setDefaultSettings = function() {
|
||||
}
|
||||
setInSession('display_menu', false);
|
||||
setInSession('chatInputMinHeight', 0);
|
||||
|
||||
//keep notifications and an opened private chat tab if page was refreshed
|
||||
//reset to default if that's a new user
|
||||
if(loginOrRefresh()) {
|
||||
initChats = [
|
||||
{
|
||||
@ -643,9 +694,10 @@ this.setDefaultSettings = function() {
|
||||
setInSession('chats', initChats);
|
||||
setInSession("inChatWith", 'PUBLIC_CHAT');
|
||||
}
|
||||
return TimeSync.loggingEnabled = false;
|
||||
return TimeSync.loggingEnabled = false; // suppresses the log messages from timesync
|
||||
};
|
||||
|
||||
//true if it is a new user, false if the client was just refreshed
|
||||
this.loginOrRefresh = function() {
|
||||
let checkId, userId;
|
||||
userId = getInSession('userId');
|
||||
@ -681,50 +733,82 @@ this.onLoadComplete = function() {
|
||||
});
|
||||
};
|
||||
|
||||
// Detects a mobile device
|
||||
this.isMobile = function() {
|
||||
return navigator.userAgent.match(/Android/i) || navigator.userAgent.match(/iPhone|iPad|iPod/i) || navigator.userAgent.match(/BlackBerry/i) || navigator.userAgent.match(/Windows Phone/i) || navigator.userAgent.match(/IEMobile/i) || navigator.userAgent.match(/BlackBerry/i) || navigator.userAgent.match(/webOS/i);
|
||||
return navigator.userAgent.match(/Android/i) ||
|
||||
navigator.userAgent.match(/iPhone|iPad|iPod/i) ||
|
||||
navigator.userAgent.match(/BlackBerry/i) ||
|
||||
navigator.userAgent.match(/Windows Phone/i) ||
|
||||
navigator.userAgent.match(/IEMobile/i) ||
|
||||
navigator.userAgent.match(/BlackBerry/i) ||
|
||||
navigator.userAgent.match(/webOS/i);
|
||||
};
|
||||
|
||||
this.isLandscape = function() {
|
||||
return !isMobile() && window.matchMedia('(orientation: landscape)').matches && window.matchMedia('(min-device-aspect-ratio: 1/1)').matches;
|
||||
return !isMobile() &&
|
||||
window.matchMedia('(orientation: landscape)').matches && // browser is landscape
|
||||
window.matchMedia('(min-device-aspect-ratio: 1/1)').matches; // device is landscape
|
||||
};
|
||||
|
||||
this.isPortrait = function() {
|
||||
return !isMobile() && window.matchMedia('(orientation: portrait)').matches && window.matchMedia('(min-device-aspect-ratio: 1/1)').matches;
|
||||
return !isMobile() &&
|
||||
window.matchMedia('(orientation: portrait)').matches && // browser is portrait
|
||||
window.matchMedia('(min-device-aspect-ratio: 1/1)').matches; // device is landscape
|
||||
};
|
||||
|
||||
// Checks if the view is portrait and a mobile device is being used
|
||||
this.isPortraitMobile = function() {
|
||||
return isMobile() && window.matchMedia('(orientation: portrait)').matches && window.matchMedia('(max-device-aspect-ratio: 1/1)').matches;
|
||||
return isMobile() &&
|
||||
window.matchMedia('(orientation: portrait)').matches && // browser is portrait
|
||||
window.matchMedia('(max-device-aspect-ratio: 1/1)').matches; // device is portrait
|
||||
};
|
||||
|
||||
// Checks if the view is landscape and mobile device is being used
|
||||
this.isLandscapeMobile = function() {
|
||||
return isMobile() && window.matchMedia('(orientation: landscape)').matches && window.matchMedia('(min-device-aspect-ratio: 1/1)').matches;
|
||||
return isMobile() &&
|
||||
window.matchMedia('(orientation: landscape)').matches && // browser is landscape
|
||||
window.matchMedia('(min-device-aspect-ratio: 1/1)').matches; // device is landscape
|
||||
};
|
||||
|
||||
this.isLandscapePhone = function() {
|
||||
return window.matchMedia('(orientation: landscape)').matches && window.matchMedia('(min-device-aspect-ratio: 1/1)').matches && window.matchMedia('(max-device-width: 959px)').matches;
|
||||
// @phone-landscape media query:
|
||||
return window.matchMedia('(orientation: landscape)').matches &&
|
||||
window.matchMedia('(min-device-aspect-ratio: 1/1)').matches &&
|
||||
window.matchMedia('(max-device-width: 959px)').matches;
|
||||
};
|
||||
|
||||
this.isPortraitPhone = function() {
|
||||
return (window.matchMedia('(orientation: portrait)').matches && window.matchMedia('(max-device-aspect-ratio: 1/1)').matches && window.matchMedia('(max-device-width: 480px)').matches) || (window.matchMedia('(orientation: landscape)').matches && window.matchMedia('(max-device-aspect-ratio: 1/1)').matches && window.matchMedia('(max-device-width: 480px)').matches);
|
||||
// @phone-portrait media query:
|
||||
return (window.matchMedia('(orientation: portrait)').matches &&
|
||||
window.matchMedia('(max-device-aspect-ratio: 1/1)').matches &&
|
||||
window.matchMedia('(max-device-width: 480px)').matches) ||
|
||||
// @phone-portrait-with-keyboard media query:
|
||||
(window.matchMedia('(orientation: landscape)').matches &&
|
||||
window.matchMedia('(max-device-aspect-ratio: 1/1)').matches &&
|
||||
window.matchMedia('(max-device-width: 480px)').matches);
|
||||
};
|
||||
|
||||
this.isPhone = function() {
|
||||
return isLandscapePhone() || isPortraitPhone();
|
||||
};
|
||||
|
||||
// The webpage orientation is now landscape
|
||||
this.orientationBecameLandscape = function() {
|
||||
return adjustChatInputHeight();
|
||||
};
|
||||
|
||||
// The webpage orientation is now portrait
|
||||
this.orientationBecamePortrait = function() {
|
||||
return adjustChatInputHeight();
|
||||
};
|
||||
|
||||
// Checks if only one panel (userlist/whiteboard/chatbar) is currently open
|
||||
this.isOnlyOnePanelOpen = function() {
|
||||
//return (getInSession("display_usersList") ? 1 : 0) + (getInSession("display_whiteboard") ? 1 : 0) + (getInSession("display_chatbar") ? 1 : 0) === 1
|
||||
return getInSession("display_usersList") + getInSession("display_whiteboard") + getInSession("display_chatbar") === 1;
|
||||
};
|
||||
|
||||
// determines which browser is being used
|
||||
this.getBrowserName = function() {
|
||||
if(navigator.userAgent.match(/Chrome/i)) {
|
||||
return 'Chrome';
|
||||
@ -744,16 +828,22 @@ this.scrollChatDown = function() {
|
||||
return $('#chatbody').scrollTop((ref = $('#chatbody')[0]) != null ? ref.scrollHeight : void 0);
|
||||
};
|
||||
|
||||
// changes the height of the chat input area if needed (based on the textarea content)
|
||||
this.adjustChatInputHeight = function() {
|
||||
let projectedHeight, ref;
|
||||
if(isLandscape()) {
|
||||
$('#newMessageInput').css('height', 'auto');
|
||||
projectedHeight = $('#newMessageInput')[0].scrollHeight + 23;
|
||||
if(projectedHeight !== $('.panel-footer').height() && projectedHeight >= getInSession('chatInputMinHeight')) {
|
||||
$('#newMessageInput').css('overflow', 'hidden');
|
||||
$('#newMessageInput').css('overflow', 'hidden'); // prevents a scroll bar
|
||||
|
||||
// resizes the chat input area
|
||||
$('.panel-footer').css('top', `${-(projectedHeight - 70)}px`);
|
||||
$('.panel-footer').css('height', `${projectedHeight}px`);
|
||||
|
||||
$('#newMessageInput').height($('#newMessageInput')[0].scrollHeight);
|
||||
|
||||
// resizes the chat messages container
|
||||
$('#chatbody').height($('#chat').height() - projectedHeight - 45);
|
||||
$('#chatbody').scrollTop((ref = $('#chatbody')[0]) != null ? ref.scrollHeight : void 0);
|
||||
}
|
||||
@ -788,11 +878,14 @@ this.toggleEmojisFAB = function() {
|
||||
};
|
||||
|
||||
this.toggleUserlistMenu = function() {
|
||||
// menu
|
||||
if($('.userlistMenu').hasClass('menuOut')) {
|
||||
$('.userlistMenu').removeClass('menuOut');
|
||||
} else {
|
||||
$('.userlistMenu').addClass('menuOut');
|
||||
}
|
||||
|
||||
// icon
|
||||
if($('.toggleUserlistButton').hasClass('menuToggledOn')) {
|
||||
return $('.toggleUserlistButton').removeClass('menuToggledOn');
|
||||
} else {
|
||||
@ -801,11 +894,14 @@ this.toggleUserlistMenu = function() {
|
||||
};
|
||||
|
||||
this.toggleSettingsMenu = function() {
|
||||
// menu
|
||||
if($('.settingsMenu').hasClass('menuOut')) {
|
||||
$('.settingsMenu').removeClass('menuOut');
|
||||
} else {
|
||||
$('.settingsMenu').addClass('menuOut');
|
||||
}
|
||||
|
||||
// icon
|
||||
if($('.toggleMenuButton').hasClass('menuToggledOn')) {
|
||||
return $('.toggleMenuButton').removeClass('menuToggledOn');
|
||||
} else {
|
||||
|
@ -72,6 +72,7 @@ this.BBB = (function() {
|
||||
for AM_I_SHARING_CAM_RESP (see below).
|
||||
*/
|
||||
BBB.amISharingWebcam = function(callback) {
|
||||
// BBB.isUserSharingWebcam BBB.getCurrentUser()?.userId
|
||||
return false;
|
||||
};
|
||||
|
||||
@ -86,21 +87,30 @@ this.BBB = (function() {
|
||||
IS_USER_PUBLISHING_CAM_RESP (see below).
|
||||
*/
|
||||
BBB.isUserSharingWebcam = function(userId, callback) {
|
||||
// BBB.getUser(userId)?.user?.webcam_stream?.length isnt 0
|
||||
return false;
|
||||
};
|
||||
|
||||
// returns whether the user has joined any type of audio
|
||||
BBB.amIInAudio = function(callback) {
|
||||
let ref, ref1, ref2, user;
|
||||
user = BBB.getCurrentUser();
|
||||
return (user != null ? (ref = user.user) != null ? ref.listenOnly : void 0 : void 0) || (user != null ? (ref1 = user.user) != null ? (ref2 = ref1.voiceUser) != null ? ref2.joined : void 0 : void 0 : void 0);
|
||||
};
|
||||
|
||||
// returns true if the user has joined the listen only audio stream
|
||||
BBB.amIListenOnlyAudio = function(callback) {
|
||||
let ref, ref1;
|
||||
return (ref = BBB.getCurrentUser()) != null ? (ref1 = ref.user) != null ? ref1.listenOnly : void 0 : void 0;
|
||||
};
|
||||
|
||||
// returns whether the user has joined the voice conference and is sharing audio through a microphone
|
||||
BBB.amISharingAudio = function(callback) {
|
||||
let ref;
|
||||
return BBB.isUserSharingAudio((ref = BBB.getCurrentUser()) != null ? ref.userId : void 0);
|
||||
};
|
||||
|
||||
// returns whether the user is currently talking
|
||||
BBB.amITalking = function(callback) {
|
||||
let ref;
|
||||
return BBB.isUserTalking((ref = BBB.getCurrentUser()) != null ? ref.userId : void 0);
|
||||
@ -126,13 +136,20 @@ this.BBB = (function() {
|
||||
let ref, ref1;
|
||||
return (ref = BBB.getUser(userId)) != null ? (ref1 = ref.user) != null ? ref1.presenter : void 0 : void 0;
|
||||
};
|
||||
|
||||
// returns true if the current user is marked as locked
|
||||
BBB.amILocked = function() {
|
||||
let ref;
|
||||
return (ref = BBB.getCurrentUser()) != null ? ref.user.locked : void 0;
|
||||
};
|
||||
|
||||
// check whether the user is locked AND the current lock settings for the room
|
||||
// includes locking the microphone of viewers (listenOnly is still alowed)
|
||||
BBB.isMyMicLocked = function() {
|
||||
let lockedMicForRoom, ref;
|
||||
lockedMicForRoom = (ref = Meteor.Meetings.findOne()) != null ? ref.roomLockSettings.disableMic : void 0;
|
||||
// note that voiceUser.locked is not used in BigBlueButton at this stage (April 2015)
|
||||
|
||||
return lockedMicForRoom && BBB.amILocked();
|
||||
};
|
||||
BBB.getCurrentSlide = function(callingLocaton) {
|
||||
@ -145,6 +162,7 @@ this.BBB = (function() {
|
||||
"presentationId": presentationId,
|
||||
"slide.current": true
|
||||
});
|
||||
// console.log "trigger:#{callingLocaton} currentSlideId=#{currentSlide?._id}"
|
||||
return currentSlide;
|
||||
};
|
||||
BBB.getMeetingName = function() {
|
||||
@ -461,9 +479,13 @@ this.BBB = (function() {
|
||||
presentationID - the presentation to delete
|
||||
*/
|
||||
BBB.deletePresentation = function(presentationID) {};
|
||||
|
||||
// Request to switch the presentation to the previous slide
|
||||
BBB.goToPreviousPage = function() {
|
||||
return Meteor.call('publishSwitchToPreviousSlideMessage', getInSession('meetingId'), getInSession('userId'), getInSession('authToken'));
|
||||
};
|
||||
|
||||
// Request to switch the presentation to the next slide
|
||||
BBB.goToNextPage = function() {
|
||||
return Meteor.call('publishSwitchToNextSlideMessage', getInSession('meetingId'), getInSession('userId'), getInSession('authToken'));
|
||||
};
|
||||
@ -485,6 +507,12 @@ this.BBB = (function() {
|
||||
BBB.webRTCWebcamRequestSuccess = function() {};
|
||||
BBB.webRTCWebcamRequestFail = function(reason) {};
|
||||
|
||||
// Third-party JS apps should use this to query if the BBB SWF file is ready to handle calls.
|
||||
|
||||
// ***********************************************************************************
|
||||
// * Broadcasting of events to 3rd-party apps.
|
||||
// ************************************************************************************
|
||||
|
||||
/*
|
||||
Stores the 3rd-party app event listeners **
|
||||
*/
|
||||
|
@ -1,15 +1,19 @@
|
||||
let loadLib;
|
||||
|
||||
// Helper to load javascript libraries from the BBB server
|
||||
loadLib = function(libname) {
|
||||
let retryMessageCallback, successCallback;
|
||||
successCallback = function() {};
|
||||
retryMessageCallback = function(param) {
|
||||
//return(Meteor.log.info("Failed to load library"), param);
|
||||
return console.log("Failed to load library", param);
|
||||
};
|
||||
return Meteor.Loader.loadJs(`${window.location.origin}/client/lib/${libname}`, successCallback, 10000).fail(retryMessageCallback);
|
||||
};
|
||||
|
||||
// These settings can just be stored locally in session, created at start up
|
||||
Meteor.startup(() => {
|
||||
// Load SIP libraries before the application starts
|
||||
loadLib('sip.js');
|
||||
loadLib('bbb_webrtc_bridge_sip.js');
|
||||
loadLib('bbblogger.js');
|
||||
@ -24,6 +28,7 @@ Meteor.startup(() => {
|
||||
});
|
||||
});
|
||||
|
||||
//
|
||||
Template.header.events({
|
||||
"click .chatBarIcon"(event) {
|
||||
$(".tooltip").hide();
|
||||
@ -126,6 +131,8 @@ Template.main.rendered = function() {
|
||||
lastOrientationWasLandscape = isLandscape();
|
||||
$(window).resize(() => {
|
||||
$('#dialog').dialog('close');
|
||||
|
||||
// when the orientation switches call the handler
|
||||
if(isLandscape() && !lastOrientationWasLandscape) {
|
||||
orientationBecameLandscape();
|
||||
return lastOrientationWasLandscape = true;
|
||||
@ -214,7 +221,7 @@ Template.main.gestures({
|
||||
$('.right-drawer').removeClass('menuOut');
|
||||
$('.right-drawer').css('transform', '');
|
||||
$('.toggleMenuButton').removeClass('menuToggledOn');
|
||||
$('.shield').removeClass('darken');
|
||||
$('.shield').removeClass('darken'); // in case it was opened by clicking a button
|
||||
} else {
|
||||
$('.shield').css('opacity', 0.5);
|
||||
$('.right-drawer').css('transform', `translateX(${screenWidth - $('.right-drawer').width()}px)`);
|
||||
@ -232,16 +239,26 @@ Template.main.gestures({
|
||||
'panright #container, panleft #container'(event, template) {
|
||||
let initTransformValue, leftDrawerWidth, menuPanned, panIsValid, rightDrawerWidth, screenWidth;
|
||||
if(isPortraitMobile() && isPanHorizontal(event)) {
|
||||
|
||||
// panright/panleft is always triggered once right before panstart
|
||||
if(!getInSession('panStarted')) {
|
||||
|
||||
// opening the left-hand menu
|
||||
if(event.type === 'panright' && event.center.x <= $('#container').width() * 0.1) {
|
||||
setInSession('panIsValid', true);
|
||||
setInSession('menuPanned', 'left');
|
||||
|
||||
// closing the left-hand menu
|
||||
} else if(event.type === 'panleft' && event.center.x < $('#container').width() * 0.9) {
|
||||
setInSession('panIsValid', true);
|
||||
setInSession('menuPanned', 'left');
|
||||
|
||||
// opening the right-hand menu
|
||||
} else if(event.type === 'panleft' && event.center.x >= $('#container').width() * 0.9) {
|
||||
setInSession('panIsValid', true);
|
||||
setInSession('menuPanned', 'right');
|
||||
|
||||
// closing the right-hand menu
|
||||
} else if(event.type === 'panright' && event.center.x > $('#container').width() * 0.1) {
|
||||
setInSession('panIsValid', true);
|
||||
setInSession('menuPanned', 'right');
|
||||
@ -250,11 +267,11 @@ Template.main.gestures({
|
||||
}
|
||||
setInSession('eventType', event.type);
|
||||
if(getInSession('menuPanned') === 'left') {
|
||||
if($('.userlistMenu').css('transform') !== 'none') {
|
||||
if($('.userlistMenu').css('transform') !== 'none') { // menu is already transformed
|
||||
setInSession(
|
||||
'initTransform',
|
||||
parseInt($('.userlistMenu').css('transform').split(',')[4])
|
||||
);
|
||||
); // translateX value
|
||||
} else if($('.userlistMenu').hasClass('menuOut')) {
|
||||
setInSession('initTransform', $('.userlistMenu').width());
|
||||
} else {
|
||||
@ -263,11 +280,11 @@ Template.main.gestures({
|
||||
$('.userlistMenu').addClass('left-drawer');
|
||||
$('.left-drawer').removeClass('userlistMenu');
|
||||
} else if(getInSession('menuPanned') === 'right') {
|
||||
if($('.settingsMenu').css('transform') !== 'none') {
|
||||
if($('.settingsMenu').css('transform') !== 'none') { // menu is already transformed
|
||||
setInSession(
|
||||
'initTransform',
|
||||
parseInt($('.settingsMenu').css('transform').split(',')[4])
|
||||
);
|
||||
); // translateX value
|
||||
} else if($('.settingsMenu').hasClass('menuOut')) {
|
||||
setInSession('initTransform', $('.settingsMenu').width());
|
||||
} else {
|
||||
@ -283,7 +300,12 @@ Template.main.gestures({
|
||||
leftDrawerWidth = $('.left-drawer').width();
|
||||
rightDrawerWidth = $('.right-drawer').width();
|
||||
screenWidth = $('#container').width();
|
||||
if(panIsValid && menuPanned === 'left' && initTransformValue + event.deltaX >= 0 && initTransformValue + event.deltaX <= leftDrawerWidth) {
|
||||
|
||||
// moving the left-hand menu
|
||||
if(panIsValid &&
|
||||
menuPanned === 'left' &&
|
||||
initTransformValue + event.deltaX >= 0 &&
|
||||
initTransformValue + event.deltaX <= leftDrawerWidth) {
|
||||
if($('.settingsMenu').hasClass('menuOut')) {
|
||||
toggleSettingsMenu();
|
||||
}
|
||||
@ -292,7 +314,10 @@ Template.main.gestures({
|
||||
$('.shield').addClass('animatedShield');
|
||||
}
|
||||
return $('.shield').css('opacity', 0.5 * (initTransformValue + event.deltaX) / leftDrawerWidth);
|
||||
} else if(panIsValid && menuPanned === 'right' && initTransformValue + event.deltaX >= screenWidth - rightDrawerWidth && initTransformValue + event.deltaX <= screenWidth) {
|
||||
} else if(panIsValid &&
|
||||
menuPanned === 'right' &&
|
||||
initTransformValue + event.deltaX >= screenWidth - rightDrawerWidth &&
|
||||
initTransformValue + event.deltaX <= screenWidth) { // moving the right-hand menu
|
||||
if($('.userlistMenu').hasClass('menuOut')) {
|
||||
toggleUserlistMenu();
|
||||
}
|
||||
|
@ -1,12 +1,21 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// If a function's last line is the statement false that represents the function returning false
|
||||
// A function such as a click handler will continue along with the propogation and default behaivour if not stopped
|
||||
// Returning false stops propogation/prevents default. You cannot always use the event object to call these methods
|
||||
// Because most Meteor event handlers set the event object to the exact context of the event which does not
|
||||
// allow you to simply call these methods.
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
this.activateBreakLines = function(str) {
|
||||
let res;
|
||||
if(typeof str === 'string') {
|
||||
if(typeof str === 'string') { // turn '\r' carriage return characters into '<br/>' break lines
|
||||
res = str.replace(new RegExp(CARRIAGE_RETURN, 'g'), BREAK_LINE);
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
this.detectUnreadChat = function() {
|
||||
//if the current tab is not the same as the tab we just published in
|
||||
return Meteor.Chat.find({}).observe({
|
||||
added: (_this => {
|
||||
return function(chatMessage) {
|
||||
@ -23,7 +32,7 @@ this.detectUnreadChat = function() {
|
||||
let destinationTab, tabsTime;
|
||||
tabsTime = getInSession('userListRenderedTime');
|
||||
if((tabsTime != null) && chatMessage.message.from_userid !== "SYSTEM_MESSAGE" && chatMessage.message.from_time - tabsTime > 0) {
|
||||
populateNotifications(chatMessage);
|
||||
populateNotifications(chatMessage); // check if we need to show a new notification
|
||||
destinationTab = findDestinationTab();
|
||||
if(destinationTab !== getInSession("inChatWith")) {
|
||||
setInSession('chats', getInSession('chats').map(tab => {
|
||||
@ -42,10 +51,12 @@ this.detectUnreadChat = function() {
|
||||
});
|
||||
};
|
||||
|
||||
// This method returns all messages for the user. It looks at the session to determine whether the user is in
|
||||
// private or public chat. If true is passed, messages returned are from before the user joined. Else, the messages are from after the user joined
|
||||
this.getFormattedMessagesForChat = function() {
|
||||
let chattingWith;
|
||||
chattingWith = getInSession('inChatWith');
|
||||
if(chattingWith === 'PUBLIC_CHAT') {
|
||||
if(chattingWith === 'PUBLIC_CHAT') { // find all public and system messages
|
||||
return Meteor.Chat.find({
|
||||
'message.chat_type': {
|
||||
$in: ["SYSTEM_MESSAGE", "PUBLIC_CHAT"]
|
||||
@ -69,12 +80,13 @@ this.getFormattedMessagesForChat = function() {
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.registerHelper("autoscroll", () => {
|
||||
Handlebars.registerHelper("autoscroll", () => { // Scrolls the message container to the bottom. The number of pixels to scroll down is the height of the container
|
||||
let ref;
|
||||
$('#chatbody').scrollTop((ref = $('#chatbody')[0]) != null ? ref.scrollHeight : void 0);
|
||||
return false;
|
||||
});
|
||||
|
||||
// true if the lock settings limit public chat and the current user is locked
|
||||
Handlebars.registerHelper("publicChatDisabled", () => {
|
||||
let presenter, publicChatIsDisabled, ref, ref1, ref2, userIsLocked;
|
||||
userIsLocked = (ref = Meteor.Users.findOne({
|
||||
@ -87,6 +99,7 @@ Handlebars.registerHelper("publicChatDisabled", () => {
|
||||
return userIsLocked && publicChatIsDisabled && !presenter;
|
||||
});
|
||||
|
||||
// true if the lock settings limit private chat and the current user is locked
|
||||
Handlebars.registerHelper("privateChatDisabled", () => {
|
||||
let presenter, privateChatIsDisabled, ref, ref1, ref2, userIsLocked;
|
||||
userIsLocked = (ref = Meteor.Users.findOne({
|
||||
@ -99,17 +112,18 @@ Handlebars.registerHelper("privateChatDisabled", () => {
|
||||
return userIsLocked && privateChatIsDisabled && !presenter;
|
||||
});
|
||||
|
||||
// return whether the user's chat pane is open in Private chat
|
||||
Handlebars.registerHelper("inPrivateChat", () => {
|
||||
return (getInSession('inChatWith')) !== 'PUBLIC_CHAT';
|
||||
});
|
||||
|
||||
this.sendMessage = function() {
|
||||
let chattingWith, color, message, ref, toUsername;
|
||||
message = linkify($('#newMessageInput').val());
|
||||
if(!((message != null ? message.length : void 0) > 0 && (/\S/.test(message)))) {
|
||||
return;
|
||||
message = linkify($('#newMessageInput').val()); // get the message from the input box
|
||||
if(!((message != null ? message.length : void 0) > 0 && (/\S/.test(message)))) { // check the message has content and it is not whitespace
|
||||
return; // do nothing if invalid message
|
||||
}
|
||||
color = "0x000000";
|
||||
color = "0x000000"; //"0x#{getInSession("messageColor")}"
|
||||
if((chattingWith = getInSession('inChatWith')) !== "PUBLIC_CHAT") {
|
||||
toUsername = (ref = Meteor.Users.findOne({
|
||||
userId: chattingWith
|
||||
@ -118,33 +132,35 @@ this.sendMessage = function() {
|
||||
} else {
|
||||
BBB.sendPublicChatMessage(color, "en", message);
|
||||
}
|
||||
return $('#newMessageInput').val('');
|
||||
return $('#newMessageInput').val(''); // Clear message box
|
||||
};
|
||||
|
||||
Template.chatbar.helpers({
|
||||
getCombinedMessagesForChat() {
|
||||
let deleted, i, j, len, msgs;
|
||||
msgs = getFormattedMessagesForChat();
|
||||
len = msgs != null ? msgs.length : void 0;
|
||||
len = msgs != null ? msgs.length : void 0; // get length of messages
|
||||
i = 0;
|
||||
while(i < len) {
|
||||
if(msgs[i].message.from_userid !== 'System') {
|
||||
j = i + 1;
|
||||
while(i < len) { // Must be a do while, for loop compiles and stores the length of array which can change inside the loop!
|
||||
if(msgs[i].message.from_userid !== 'System') { // skip system messages
|
||||
j = i + 1; // Start looking at messages right after the current one
|
||||
while(j < len) {
|
||||
deleted = false;
|
||||
if(msgs[j].message.from_userid !== 'System') {
|
||||
if((parseFloat(msgs[j].message.from_time) - parseFloat(msgs[i].message.from_time)) >= 60000) {
|
||||
break;
|
||||
if(msgs[j].message.from_userid !== 'System') { // Ignore system messages
|
||||
// Check if the time discrepancy between the two messages exceeds window for grouping
|
||||
if((parseFloat(msgs[j].message.from_time) - parseFloat(msgs[i].message.from_time)) >= 60000) { // 60 seconds/1 minute
|
||||
break; // Messages are too far between, so them seperated and stop joining here
|
||||
}
|
||||
if(msgs[i].message.from_userid === msgs[j].message.from_userid) {
|
||||
msgs[i].message.message += `${CARRIAGE_RETURN}${msgs[j].message.message}`;
|
||||
msgs.splice(j, 1);
|
||||
if(msgs[i].message.from_userid === msgs[j].message.from_userid) { // Both messages are from the same user
|
||||
// insert a '\r' carriage return character between messages to put them on a new line
|
||||
msgs[i].message.message += `${CARRIAGE_RETURN}${msgs[j].message.message}`; // Combine the messages
|
||||
msgs.splice(j, 1); // Delete the message from the collection
|
||||
deleted = true;
|
||||
} else {
|
||||
break;
|
||||
break; // Messages are from different people, move on
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
break; // This is the break point in the chat, don't merge
|
||||
}
|
||||
len = msgs.length;
|
||||
if(!deleted) {
|
||||
@ -168,10 +184,12 @@ Template.chatbar.helpers({
|
||||
}
|
||||
});
|
||||
|
||||
// When chatbar gets rendered, launch the auto-check for unread chat
|
||||
Template.chatbar.rendered = function() {
|
||||
return detectUnreadChat();
|
||||
};
|
||||
|
||||
// When "< Public" is clicked, go to public chat
|
||||
Template.chatbar.events({
|
||||
'click .toPublic'(event) {
|
||||
setInSession('inChatWith', 'PUBLIC_CHAT');
|
||||
@ -191,6 +209,7 @@ Template.privateChatTab.rendered = function() {
|
||||
}
|
||||
};
|
||||
|
||||
// When message gets rendered, scroll to the bottom
|
||||
Template.message.rendered = function() {
|
||||
let ref;
|
||||
$('#chatbody').scrollTop((ref = $('#chatbody')[0]) != null ? ref.scrollHeight : void 0);
|
||||
@ -204,7 +223,7 @@ Template.chatInput.rendered = function() {
|
||||
resize(event, ui) {
|
||||
let ref;
|
||||
if($('.panel-footer').css('top') === '0px') {
|
||||
$('.panel-footer').height(70);
|
||||
$('.panel-footer').height(70); // prevents the element from shrinking vertically for 1-2 px
|
||||
} else {
|
||||
$('.panel-footer').css('top', `${parseInt($('.panel-footer').css('top'))}${1}px`);
|
||||
}
|
||||
@ -227,15 +246,16 @@ Template.chatInput.events({
|
||||
sendMessage();
|
||||
return adjustChatInputHeight();
|
||||
},
|
||||
'keypress #newMessageInput'(event) {
|
||||
'keypress #newMessageInput'(event) { // user pressed a button inside the chatbox
|
||||
let key;
|
||||
key = event.charCode ? event.charCode : (event.keyCode ? event.keyCode : 0);
|
||||
if(event.shiftKey && (key === 13)) {
|
||||
event.preventDefault();
|
||||
document.getElementById("newMessageInput").value += CARRIAGE_RETURN;
|
||||
// append a '\r' carriage return character to the input box dropping the cursor to a new line
|
||||
document.getElementById("newMessageInput").value += CARRIAGE_RETURN; // Change newline character
|
||||
return;
|
||||
}
|
||||
if(key === 13) {
|
||||
if(key === 13) { // Check for pressing enter to submit message
|
||||
event.preventDefault();
|
||||
sendMessage();
|
||||
$('#newMessageInput').val("");
|
||||
@ -255,7 +275,7 @@ Template.chatInputControls.rendered = function() {
|
||||
Template.message.helpers({
|
||||
sanitizeAndFormat(str) {
|
||||
let res;
|
||||
if(typeof str === 'string') {
|
||||
if(typeof str === 'string') { // First, replace replace all tags with the ascii equivalent (excluding those involved in anchor tags)
|
||||
res = str.replace(/&/g, '&').replace(/<(?![au\/])/g, '<').replace(/\/([^au])>/g, '$1>').replace(/([^=])"(?!>)/g, '$1"');
|
||||
res = toClickable(res);
|
||||
return res = activateBreakLines(res);
|
||||
@ -268,7 +288,7 @@ Template.message.helpers({
|
||||
}
|
||||
local = new Date();
|
||||
offset = local.getTimezoneOffset();
|
||||
epochTime = epochTime - offset * 60000;
|
||||
epochTime = epochTime - offset * 60000; // 1 min = 60 s = 60,000 ms
|
||||
dateObj = new Date(epochTime);
|
||||
hours = dateObj.getUTCHours();
|
||||
minutes = dateObj.getUTCMinutes();
|
||||
@ -279,6 +299,7 @@ Template.message.helpers({
|
||||
}
|
||||
});
|
||||
|
||||
// make links received from Flash client clickable in HTML
|
||||
this.toClickable = function(str) {
|
||||
let res;
|
||||
if(typeof str === 'string') {
|
||||
|
@ -45,7 +45,7 @@ Template.settingsModal.events({
|
||||
|
||||
Template.optionsFontSize.events({
|
||||
"click #decreaseFontSize"(event) {
|
||||
if(getInSession("messageFontSize") === 8) {
|
||||
if(getInSession("messageFontSize") === 8) { // min
|
||||
$('#decreaseFontSize').disabled = true;
|
||||
$('#decreaseFontSize').removeClass('icon fi-minus');
|
||||
return $('#decreaseFontSize').html('MIN');
|
||||
@ -60,7 +60,7 @@ Template.optionsFontSize.events({
|
||||
}
|
||||
},
|
||||
"click #increaseFontSize"(event) {
|
||||
if(getInSession("messageFontSize") === 40) {
|
||||
if(getInSession("messageFontSize") === 40) { // max
|
||||
$('#increaseFontSize').disabled = true;
|
||||
$('#increaseFontSize').removeClass('icon fi-plus');
|
||||
return $('#increaseFontSize').html('MAX');
|
||||
|
@ -3,6 +3,10 @@ Template.displayUserIcons.events({
|
||||
return toggleMic(this);
|
||||
},
|
||||
'click .raisedHandIcon'(event) {
|
||||
// the function to call 'userLowerHand'
|
||||
// the meeting id
|
||||
// the _id of the person whose land is to be lowered
|
||||
// the userId of the person who is lowering the hand
|
||||
return BBB.lowerHand(getInSession("meetingId"), this.userId, getInSession("userId"), getInSession("authToken"));
|
||||
},
|
||||
'click .kickUser'(event) {
|
||||
@ -12,6 +16,8 @@ Template.displayUserIcons.events({
|
||||
|
||||
Template.displayUserIcons.helpers({
|
||||
userLockedIconApplicable(userId) {
|
||||
// the lock settings affect the user (and requiire a lock icon) if
|
||||
// the user is set to be locked and there is a relevant lock in place
|
||||
let lockInAction, locked, ref, ref1, settings;
|
||||
locked = (ref = BBB.getUser(userId)) != null ? ref.user.locked : void 0;
|
||||
settings = (ref1 = Meteor.Meetings.findOne()) != null ? ref1.roomLockSettings : void 0;
|
||||
@ -20,6 +26,7 @@ Template.displayUserIcons.helpers({
|
||||
}
|
||||
});
|
||||
|
||||
// Opens a private chat tab when a username from the userlist is clicked
|
||||
Template.usernameEntry.events({
|
||||
'click .usernameEntry'(event) {
|
||||
let ref, userIdSelected;
|
||||
@ -35,7 +42,7 @@ Template.usernameEntry.events({
|
||||
toggleUserlistMenu();
|
||||
toggleShield();
|
||||
}
|
||||
return setTimeout(() => {
|
||||
return setTimeout(() => { // waits until the end of execution queue
|
||||
return $("#newMessageInput").focus();
|
||||
}, 0);
|
||||
},
|
||||
|
@ -5,6 +5,7 @@ Template.usersList.helpers({
|
||||
if (numberUsers > 8) {
|
||||
return `Users: ${numberUsers}`;
|
||||
}
|
||||
// do not display the label if there are just a few users
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -12,7 +12,8 @@ this.reactOnSlideChange = (_this => {
|
||||
setInSession('slideOriginalWidth', this.width);
|
||||
setInSession('slideOriginalHeight', this.height);
|
||||
$(window).resize(() => {
|
||||
if(!$('.panel-footer').hasClass('ui-resizable-resizing')) {
|
||||
// redraw the whiteboard to adapt to the resized window
|
||||
if(!$('.panel-footer').hasClass('ui-resizable-resizing')) { // not in the middle of resizing the message input
|
||||
return scaleWhiteboard();
|
||||
}
|
||||
});
|
||||
@ -28,6 +29,7 @@ this.reactOnSlideChange = (_this => {
|
||||
})(this);
|
||||
|
||||
this.createWhiteboardPaper = (_this => {
|
||||
// console.log "CREATING WPM"
|
||||
return function(callback) {
|
||||
_this.whiteboardPaperModel = new Meteor.WhiteboardPaperModel('whiteboard-paper');
|
||||
return callback(_this.whiteboardPaperModel);
|
||||
@ -67,7 +69,7 @@ this.manuallyDisplayShapes = function() {
|
||||
shapeType = shapeInfo != null ? shapeInfo.type : void 0;
|
||||
if(shapeType !== "text") {
|
||||
len = shapeInfo.points.length;
|
||||
for(num = j = 0, ref2 = len; 0 <= ref2 ? j <= ref2 : j >= ref2; num = 0 <= ref2 ? ++j : --j) {
|
||||
for(num = j = 0, ref2 = len; 0 <= ref2 ? j <= ref2 : j >= ref2; num = 0 <= ref2 ? ++j : --j) { // the coordinates must be in the range 0 to 1
|
||||
if(shapeInfo != null) {
|
||||
shapeInfo.points[num] = (shapeInfo != null ? shapeInfo.points[num] : void 0) / 100;
|
||||
}
|
||||
@ -81,17 +83,29 @@ this.manuallyDisplayShapes = function() {
|
||||
return results;
|
||||
};
|
||||
|
||||
// calculates and returns the best fitting {width, height} pair
|
||||
// based on the image's original width and height
|
||||
this.scaleSlide = function(originalWidth, originalHeight) {
|
||||
let adjustedHeight, adjustedWidth, boardHeight, boardWidth;
|
||||
|
||||
// set the size of the whiteboard space (frame) where the slide will be displayed
|
||||
if(window.matchMedia('(orientation: landscape)').matches) {
|
||||
// for landscape orientation we want "fit to height" so that we can
|
||||
// minimize the empty space above and below the slide (for best readability)
|
||||
boardWidth = $("#whiteboard-container").width();
|
||||
boardHeight = $("#whiteboard-container").height();
|
||||
} else {
|
||||
// for portrait orientation we want "fit to width" so that we can
|
||||
// minimize the empty space on the sides of the slide (for best readability)
|
||||
boardWidth = $("#whiteboard-container").width();
|
||||
boardHeight = 1.4 * $("#whiteboard-container").width();
|
||||
}
|
||||
|
||||
// this is the best fitting pair
|
||||
adjustedWidth = null;
|
||||
adjustedHeight = null;
|
||||
|
||||
// the slide image is in portrait orientation
|
||||
if(originalWidth <= originalHeight) {
|
||||
adjustedWidth = boardHeight * originalWidth / originalHeight;
|
||||
if (boardWidth < adjustedWidth) {
|
||||
@ -100,6 +114,8 @@ this.scaleSlide = function(originalWidth, originalHeight) {
|
||||
} else {
|
||||
adjustedHeight = boardHeight;
|
||||
}
|
||||
|
||||
// ths slide image is in landscape orientation
|
||||
} else {
|
||||
adjustedHeight = boardWidth * originalHeight / originalWidth;
|
||||
if (boardHeight < adjustedHeight) {
|
||||
@ -123,13 +139,16 @@ Template.slide.helpers({
|
||||
}
|
||||
});
|
||||
|
||||
//// SHAPE ////
|
||||
Template.shape.rendered = function() {
|
||||
let i, len, num, ref, ref1, shapeInfo, shapeType, wpm;
|
||||
|
||||
// @data is the shape object coming from the {{#each}} in the html file
|
||||
shapeInfo = ((ref = this.data.shape) != null ? ref.shape : void 0) || this.data.shape;
|
||||
shapeType = shapeInfo != null ? shapeInfo.type : void 0;
|
||||
if(shapeType !== "text") {
|
||||
len = shapeInfo.points.length;
|
||||
for (num = i = 0, ref1 = len; 0 <= ref1 ? i <= ref1 : i >= ref1; num = 0 <= ref1 ? ++i : --i) {
|
||||
for (num = i = 0, ref1 = len; 0 <= ref1 ? i <= ref1 : i >= ref1; num = 0 <= ref1 ? ++i : --i) { // the coordinates must be in the range 0 to 1
|
||||
shapeInfo.points[num] = shapeInfo.points[num] / 100;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
let fakeUpload;
|
||||
|
||||
// scale the whiteboard to adapt to the resized window
|
||||
this.scaleWhiteboard = function(callback) {
|
||||
let adjustedDimensions;
|
||||
adjustedDimensions = scaleSlide(getInSession('slideOriginalWidth'), getInSession('slideOriginalHeight'));
|
||||
@ -27,9 +28,13 @@ this.scaleWhiteboard = function(callback) {
|
||||
},
|
||||
clearSlide() {
|
||||
let ref;
|
||||
|
||||
//clear the slide
|
||||
if(typeof whiteboardPaperModel !== "undefined" && whiteboardPaperModel !== null) {
|
||||
whiteboardPaperModel.removeAllImagesFromPaper();
|
||||
}
|
||||
|
||||
// hide the cursor
|
||||
return typeof whiteboardPaperModel !== "undefined" && whiteboardPaperModel !== null ? (ref = whiteboardPaperModel.cursor) != null ? ref.remove() : void 0 : void 0;
|
||||
}
|
||||
};
|
||||
@ -212,25 +217,29 @@ Template.whiteboard.rendered = function() {
|
||||
return adjustChatInputHeight();
|
||||
},
|
||||
start() {
|
||||
if($('#chat').width() / $('#panels').width() > 0.2) {
|
||||
return $('#whiteboard').resizable('option', 'maxWidth', $('#panels').width() - 200);
|
||||
if($('#chat').width() / $('#panels').width() > 0.2) { // chat shrinking can't make it smaller than one fifth of the whiteboard-chat area
|
||||
return $('#whiteboard').resizable('option', 'maxWidth', $('#panels').width() - 200); // gives the chat enough space (200px)
|
||||
} else {
|
||||
return $('#whiteboard').resizable('option', 'maxWidth', $('#whiteboard').width());
|
||||
}
|
||||
},
|
||||
stop() {
|
||||
$('#whiteboard').css('width', `${100 * $('#whiteboard').width() / $('#panels').width()}%`);
|
||||
$('#whiteboard').css('width', `${100 * $('#whiteboard').width() / $('#panels').width()}%`); // transforms width to %
|
||||
return $('#whiteboard').resizable('option', 'maxWidth', null);
|
||||
}
|
||||
});
|
||||
|
||||
// whiteboard element needs to be available
|
||||
Meteor.NotificationControl = new NotificationControl('notificationArea');
|
||||
return $(document).foundation();
|
||||
|
||||
return $(document).foundation(); // initialize foundation javascript
|
||||
};
|
||||
|
||||
Template.presenterUploaderControl.created = function() {
|
||||
this.isOpen = new ReactiveVar(false);
|
||||
this.files = new ReactiveList({
|
||||
sort(a, b) {
|
||||
// Put the ones who still uploading first
|
||||
let ref, ref1;
|
||||
return (ref = a.isUploading === b.isUploading) != null ? ref : {
|
||||
0: (ref1 = a.isUploading) != null ? ref1 : -{
|
||||
@ -261,7 +270,7 @@ fakeUpload = function(file, list) {
|
||||
if(file.isUploading === true) {
|
||||
return fakeUpload(file, list);
|
||||
} else {
|
||||
return list.remove(file.name);
|
||||
return list.remove(file.name); // TODO: Here we should remove and update te presentation on mongo
|
||||
}
|
||||
}), 200);
|
||||
};
|
||||
|
@ -1,3 +1,4 @@
|
||||
// A base class for whiteboard tools
|
||||
this.WhiteboardToolModel = (function() {
|
||||
class WhiteboardToolModel {
|
||||
constructor() {}
|
||||
@ -8,9 +9,13 @@ this.WhiteboardToolModel = (function() {
|
||||
this.gh = 0;
|
||||
this.gw = 0;
|
||||
this.obj = 0;
|
||||
// the defintion of this shape, kept so we can redraw the shape whenever needed
|
||||
return this.definition = [];
|
||||
}
|
||||
|
||||
//set the size of the paper
|
||||
// @param {number} @gh gh parameter
|
||||
// @param {number} @gw gw parameter
|
||||
setPaperSize(gh, gw) {
|
||||
this.gh = gh;
|
||||
this.gw = gw;
|
||||
@ -22,6 +27,7 @@ this.WhiteboardToolModel = (function() {
|
||||
}
|
||||
|
||||
setPaperDimensions(paperWidth, paperHeight) {
|
||||
// TODO: can't we simply take the width and the height from `@paper`?
|
||||
this.paperWidth = paperWidth;
|
||||
this.paperHeight = paperHeight;
|
||||
}
|
||||
|
@ -1,4 +1,11 @@
|
||||
// General utility methods
|
||||
|
||||
Meteor.methods({
|
||||
// POST request using javascript
|
||||
// @param {string} path path of submission
|
||||
// @param {string} params parameters to submit
|
||||
// @param {string} method method of submission ("post" is default)
|
||||
// @return {undefined}
|
||||
postToUrl(path, params, method="post") {
|
||||
let $hiddenField, form, key;
|
||||
form = $("<form></form>");
|
||||
@ -22,16 +29,18 @@ Meteor.methods({
|
||||
}
|
||||
});
|
||||
|
||||
// thickness can be a number (e.g. "2") or a string (e.g. "2px")
|
||||
this.formatThickness = function(thickness) {
|
||||
if(thickness == null) {
|
||||
thickness = "1";
|
||||
thickness = "1"; // default value
|
||||
}
|
||||
if(!thickness.toString().match(/.*px$/)) {
|
||||
`#${thickness}px`;
|
||||
`#${thickness}px`; // leading "#" - to be compatible with Firefox
|
||||
}
|
||||
return thickness;
|
||||
};
|
||||
|
||||
// applies zooming to the stroke thickness
|
||||
this.zoomStroke = function(thickness) {
|
||||
let currentSlide, ratio;
|
||||
currentSlide = BBB.getCurrentSlide("zoomStroke");
|
||||
|
@ -4,6 +4,7 @@ const bind = function(fn, me) {
|
||||
};
|
||||
};
|
||||
|
||||
// The cursor/pointer in the whiteboard
|
||||
this.WhiteboardCursorModel = (function() {
|
||||
class WhiteboardCursorModel {
|
||||
constructor(paper, radius, color) {
|
||||
@ -15,7 +16,7 @@ this.WhiteboardCursorModel = (function() {
|
||||
this.radius = 6;
|
||||
}
|
||||
if(this.color == null) {
|
||||
this.color = "#ff6666";
|
||||
this.color = "#ff6666"; // a pinkish red
|
||||
}
|
||||
this.cursor = null;
|
||||
}
|
||||
|
@ -1,12 +1,23 @@
|
||||
// An ellipse in the whiteboard
|
||||
this.WhiteboardEllipseModel = (function() {
|
||||
class WhiteboardEllipseModel extends WhiteboardToolModel {
|
||||
constructor(paper) {
|
||||
super(paper);
|
||||
this.paper = paper;
|
||||
|
||||
// the defintion of this shape, kept so we can redraw the shape whenever needed
|
||||
// format: top left x, top left y, bottom right x, bottom right y, stroke color, thickness
|
||||
this.definition = [0, 0, 0, 0, "#000", "0px"];
|
||||
}
|
||||
|
||||
// Make an ellipse on the whiteboard
|
||||
// @param {[type]} x the x value of the top left corner
|
||||
// @param {[type]} y the y value of the top left corner
|
||||
// @param {string} colour the colour of the object
|
||||
// @param {number} thickness the thickness of the object's line(s)
|
||||
make(info) {
|
||||
//console.log "Whiteboard - Making ellipse: "
|
||||
//console.log info
|
||||
let color, thickness, x, y;
|
||||
if((info != null ? info.points : void 0) != null) {
|
||||
x = info.points[0];
|
||||
@ -21,7 +32,14 @@ this.WhiteboardEllipseModel = (function() {
|
||||
return this.obj;
|
||||
}
|
||||
|
||||
// Update ellipse drawn
|
||||
// @param {number} x1 the x value of the top left corner in percent of current slide size
|
||||
// @param {number} y1 the y value of the top left corner in percent of current slide size
|
||||
// @param {number} x2 the x value of the bottom right corner in percent of current slide size
|
||||
// @param {number} y2 the y value of the bottom right corner in percent of current slide size
|
||||
// @param {boolean} square (draw a circle or not
|
||||
update(info) {
|
||||
//console.log info
|
||||
let circle, coords, r, ref, ref1, reversed, rx, ry, x1, x2, y1, y2;
|
||||
if((info != null ? info.points : void 0) != null) {
|
||||
x1 = info.points[0];
|
||||
@ -37,8 +55,11 @@ this.WhiteboardEllipseModel = (function() {
|
||||
ref1 = [y2, y1], y1 = ref1[0], y2 = ref1[1];
|
||||
reversed = true;
|
||||
}
|
||||
|
||||
//if the control key is pressed then the width and height of the ellipse are equal (a circle)
|
||||
//we calculate this by making the y2 coord equal to the y1 coord plus the width of x2-x1 and corrected for the slide size
|
||||
if(circle) {
|
||||
if(reversed) {
|
||||
if(reversed) { // if reveresed, the y1 coordinate gets updated, not the y2 coordinate
|
||||
y1 = y2 - (x2 - x1) * this.gw / this.gh;
|
||||
} else {
|
||||
y2 = y1 + (x2 - x1) * this.gw / this.gh;
|
||||
@ -50,6 +71,9 @@ this.WhiteboardEllipseModel = (function() {
|
||||
y1: y1,
|
||||
y2: y2
|
||||
};
|
||||
|
||||
//console.log(coords)
|
||||
|
||||
rx = (x2 - x1) / 2;
|
||||
ry = (y2 - y1) / 2;
|
||||
r = {
|
||||
@ -59,6 +83,9 @@ this.WhiteboardEllipseModel = (function() {
|
||||
cy: (ry + y1) * this.gh + this.yOffset
|
||||
};
|
||||
this.obj.attr(r);
|
||||
|
||||
//console.log( "@gw: " + @gw + "\n@gh: " + @gh + "\n@xOffset: " + @xOffset + "\n@yOffset: " + @yOffset );
|
||||
// we need to update all these values, specially for when shapes are drawn backwards
|
||||
this.definition[0] = x1;
|
||||
this.definition[1] = y1;
|
||||
this.definition[2] = x2;
|
||||
@ -67,6 +94,13 @@ this.WhiteboardEllipseModel = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
// Draw an ellipse on the whiteboard
|
||||
// @param {number} x1 the x value of the top left corner
|
||||
// @param {number} y1 the y value of the top left corner
|
||||
// @param {number} x2 the x value of the bottom right corner
|
||||
// @param {number} y2 the y value of the bottom right corner
|
||||
// @param {string} colour the colour of the object
|
||||
// @param {number} thickness the thickness of the object's line(s)
|
||||
draw(x1, y1, x2, y2, colour, thickness) {
|
||||
let elip, ref, ref1, rx, ry, x, y;
|
||||
if(x2 < x1) {
|
||||
@ -84,9 +118,53 @@ this.WhiteboardEllipseModel = (function() {
|
||||
return elip;
|
||||
}
|
||||
|
||||
dragOnStart(x, y) {}
|
||||
dragOnMove(dx, dy, x, y, e) {}
|
||||
dragOnStop(e) {}
|
||||
// When first starting drawing the ellipse
|
||||
// @param {number} x the x value of cursor at the time in relation to the left side of the browser
|
||||
// @param {number} y the y value of cursor at the time in relation to the top of the browser
|
||||
// TODO: moved here but not finished
|
||||
dragOnStart(x, y) {
|
||||
// sx = (@paperWidth - @gw) / 2
|
||||
// sy = (@paperHeight - @gh) / 2
|
||||
// // find the x and y values in relation to the whiteboard
|
||||
// @ellipseX = (x - @containerOffsetLeft - sx + @xOffset)
|
||||
// @ellipseY = (y - @containerOffsetTop - sy + @yOffset)
|
||||
// globals.connection.emitMakeShape "ellipse",
|
||||
// [ @ellipseX / @paperWidth, @ellipseY / @paperHeight, @currentColour, @currentThickness ]
|
||||
}
|
||||
|
||||
// When first starting to draw an ellipse
|
||||
// @param {number} dx the difference in the x value at the start as opposed to the x value now
|
||||
// @param {number} dy the difference in the y value at the start as opposed to the y value now
|
||||
// @param {number} x the x value of cursor at the time in relation to the left side of the browser
|
||||
// @param {number} y the y value of cursor at the time in relation to the top of the browser
|
||||
// @param {Event} e the mouse event
|
||||
// TODO: moved here but not finished
|
||||
dragOnMove(dx, dy, x, y, e) {
|
||||
// // if shift is pressed, draw a circle instead of ellipse
|
||||
// dy = dx if @shiftPressed
|
||||
// dx = dx / 2
|
||||
// dy = dy / 2
|
||||
// // adjust for negative values as well
|
||||
// x = @ellipseX + dx
|
||||
// y = @ellipseY + dy
|
||||
// dx = (if dx < 0 then -dx else dx)
|
||||
// dy = (if dy < 0 then -dy else dy)
|
||||
// globals.connection.emitUpdateShape "ellipse",
|
||||
// [ x / @paperWidth, y / @paperHeight, dx / @paperWidth, dy / @paperHeight ]
|
||||
}
|
||||
|
||||
// When releasing the mouse after drawing the ellipse
|
||||
// @param {Event} e the mouse event
|
||||
// TODO: moved here but not finished
|
||||
dragOnStop(e) {
|
||||
// attrs = undefined
|
||||
// attrs = @currentEllipse.attrs if @currentEllipse?
|
||||
// if attrs?
|
||||
// globals.connection.emitPublishShape "ellipse",
|
||||
// [ attrs.cx / @gw, attrs.cy / @gh, attrs.rx / @gw, attrs.ry / @gh,
|
||||
// @currentColour, @currentThickness ]
|
||||
// @currentEllipse = null # late updates will be blocked by this
|
||||
}
|
||||
}
|
||||
|
||||
return WhiteboardEllipseModel;
|
||||
|
@ -1,13 +1,27 @@
|
||||
let MAX_PATHS_IN_SEQUENCE = 30;
|
||||
|
||||
this.WhiteboardLineModel = (function() {
|
||||
|
||||
// A line in the whiteboard
|
||||
// Note: is used to draw lines from the pencil tool and from the line tool, this is why some
|
||||
// methods can receive different set of parameters.
|
||||
// TODO: Maybe this should be split in WhiteboardPathModel for the pencil and
|
||||
// WhiteboardLineModel for the line tool
|
||||
class WhiteboardLineModel extends WhiteboardToolModel {
|
||||
constructor(paper) {
|
||||
super(paper);
|
||||
this.paper = paper;
|
||||
|
||||
// the defintion of this shape, kept so we can redraw the shape whenever needed
|
||||
// format: svg path, stroke color, thickness
|
||||
this.definition = ["", "#000", "0px"];
|
||||
}
|
||||
|
||||
// Creates a line in the paper
|
||||
// @param {number} x the x value of the line start point as a percentage of the original width
|
||||
// @param {number} y the y value of the line start point as a percentage of the original height
|
||||
// @param {string} colour the colour of the shape to be drawn
|
||||
// @param {number} thickness the thickness of the line to be drawn
|
||||
make(info) {
|
||||
let color, path, pathPercent, thickness, x, x1, y, y1;
|
||||
if((info != null ? info.points : void 0) != null) {
|
||||
@ -31,6 +45,16 @@ this.WhiteboardLineModel = (function() {
|
||||
return this.obj;
|
||||
}
|
||||
|
||||
// Update the line dimensions
|
||||
// @param {number} x1 1) the x of the first point
|
||||
// 2) the next x point to be added to the line
|
||||
// @param {number} y1 1) the y of the first point
|
||||
// 2) the next y point to be added to the line
|
||||
// @param {number,boolean} x2 1) the x of the second point
|
||||
// 2) true if the line should be added to the current line,
|
||||
// false if it should replace the last point
|
||||
// @param {number} y2 1) the y of the second point
|
||||
// 2) undefined
|
||||
update(info) {
|
||||
let path, x1, x2, y1, y2;
|
||||
if((info != null ? info.points : void 0) != null) {
|
||||
@ -49,10 +73,99 @@ this.WhiteboardLineModel = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
draw(x1, y1, x2, y2, colour, thickness) {}
|
||||
dragOnStart(x, y) {}
|
||||
dragOnMove(dx, dy, x, y) {}
|
||||
dragOnEnd(e) {}
|
||||
// Draw a line on the paper
|
||||
// @param {number,string} x1 1) the x value of the first point
|
||||
// 2) the string path
|
||||
// @param {number,string} y1 1) the y value of the first point
|
||||
// 2) the colour
|
||||
// @param {number} x2 1) the x value of the second point
|
||||
// 2) the thickness
|
||||
// @param {number} y2 1) the y value of the second point
|
||||
// 2) undefined
|
||||
// @param {string} colour 1) the colour of the shape to be drawn
|
||||
// 2) undefined
|
||||
// @param {number} thickness 1) the thickness of the line to be drawn
|
||||
// 2) undefined
|
||||
draw(x1, y1, x2, y2, colour, thickness) {
|
||||
// if the drawing is from the pencil tool, it comes as a path first
|
||||
// if _.isString(x1)
|
||||
// colour = y1
|
||||
// thickness = x2
|
||||
// path = x1
|
||||
|
||||
// // if the drawing is from the line tool, it comes with two points
|
||||
// else
|
||||
// path = @_buildPath(points)
|
||||
|
||||
// line = @paper.path(@_scaleLinePath(path, @gw, @gh, @xOffset, @yOffset))
|
||||
// line.attr Utils.strokeAndThickness(colour, thickness)
|
||||
// line.attr({"stroke-linejoin": "round"})
|
||||
// line
|
||||
}
|
||||
|
||||
// When dragging for drawing lines starts
|
||||
// @param {number} x the x value of the cursor
|
||||
// @param {number} y the y value of the cursor
|
||||
// TODO: moved here but not finished
|
||||
dragOnStart(x, y) {
|
||||
// // find the x and y values in relation to the whiteboard
|
||||
// sx = (@paperWidth - @gw) / 2
|
||||
// sy = (@paperHeight - @gh) / 2
|
||||
// @lineX = x - @containerOffsetLeft - sx + @xOffset
|
||||
// @lineY = y - @containerOffsetTop - sy + @yOffset
|
||||
// values = [ @lineX / @paperWidth, @lineY / @paperHeight, @currentColour, @currentThickness ]
|
||||
// globals.connection.emitMakeShape "line", values
|
||||
}
|
||||
|
||||
// As line drawing drag continues
|
||||
// @param {number} dx the difference between the x value from _lineDragStart and now
|
||||
// @param {number} dy the difference between the y value from _lineDragStart and now
|
||||
// @param {number} x the x value of the cursor
|
||||
// @param {number} y the y value of the cursor
|
||||
// TODO: moved here but not finished
|
||||
dragOnMove(dx, dy, x, y) {
|
||||
// sx = (@paperWidth - @gw) / 2
|
||||
// sy = (@paperHeight - @gh) / 2
|
||||
// [cx, cy] = @_currentSlideOffsets()
|
||||
// // find the x and y values in relation to the whiteboard
|
||||
// @cx2 = x - @containerOffsetLeft - sx + @xOffset
|
||||
// @cy2 = y - @containerOffsetTop - sy + @yOffset
|
||||
// if @shiftPressed
|
||||
// globals.connection.emitUpdateShape "line", [ @cx2 / @paperWidth, @cy2 / @paperHeight, false ]
|
||||
// else
|
||||
// @currentPathCount++
|
||||
// if @currentPathCount < MAX_PATHS_IN_SEQUENCE
|
||||
// globals.connection.emitUpdateShape "line", [ @cx2 / @paperHeight, @cy2 / @paperHeight, true ]
|
||||
// else if @obj?
|
||||
// @currentPathCount = 0
|
||||
// // save the last path of the line
|
||||
// @obj.attrs.path.pop()
|
||||
// path = @obj.attrs.path.join(" ")
|
||||
// @obj.attr path: (path + "L" + @lineX + " " + @lineY)
|
||||
|
||||
// // scale the path appropriately before sending
|
||||
// pathStr = @obj.attrs.path.join(",")
|
||||
// globals.connection.emitPublishShape "path",
|
||||
// [ @_scaleLinePath(pathStr, 1 / @gw, 1 / @gh),
|
||||
// @currentColour, @currentThickness ]
|
||||
// globals.connection.emitMakeShape "line",
|
||||
// [ @lineX / @paperWidth, @lineY / @paperHeight, @currentColour, @currentThickness ]
|
||||
// @lineX = @cx2
|
||||
// @lineY = @cy2
|
||||
}
|
||||
|
||||
// Drawing line has ended
|
||||
// @param {Event} e the mouse event
|
||||
// TODO: moved here but not finished
|
||||
dragOnEnd(e) {
|
||||
// if @obj?
|
||||
// path = @obj.attrs.path
|
||||
// @obj = null # any late updates will be blocked by this
|
||||
// // scale the path appropriately before sending
|
||||
// globals.connection.emitPublishShape "path",
|
||||
// [ @_scaleLinePath(path.join(","), 1 / @gw, 1 / @gh),
|
||||
// @currentColour, @currentThickness ]
|
||||
}
|
||||
|
||||
_buildPath(points) {
|
||||
let i, path;
|
||||
@ -69,6 +182,10 @@ this.WhiteboardLineModel = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
// Scales a path string to fit within a width and height of the new paper size
|
||||
// @param {number} w width of the shape as a percentage of the original width
|
||||
// @param {number} h height of the shape as a percentage of the original height
|
||||
// @return {string} the path string after being manipulated to new paper size
|
||||
_scaleLinePath(string, w, h, xOffset, yOffset) {
|
||||
let j, len, path, points;
|
||||
if(xOffset == null) {
|
||||
@ -81,6 +198,8 @@ this.WhiteboardLineModel = (function() {
|
||||
points = string.match(/(\d+[.]?\d*)/g);
|
||||
len = points.length;
|
||||
j = 0;
|
||||
|
||||
// go through each point and multiply it by the new height and width
|
||||
while(j < len) {
|
||||
if(j !== 0) {
|
||||
path += `${points[j + 1] * h}${yOffset}L${points[j] * w + xOffset},${points[j + 1] * h + yOffset}`;
|
||||
|
@ -1,14 +1,29 @@
|
||||
// "Paper" which is the Raphael term for the entire SVG object on the webpage.
|
||||
// This class deals with this SVG component only.
|
||||
Meteor.WhiteboardPaperModel = (function() {
|
||||
class WhiteboardPaperModel {
|
||||
|
||||
// Container must be a DOM element
|
||||
constructor(container) {
|
||||
this.container = container;
|
||||
|
||||
// a WhiteboardCursorModel
|
||||
this.cursor = null;
|
||||
|
||||
// all slides in the presentation indexed by url
|
||||
this.slides = {};
|
||||
this.panX = null;
|
||||
this.panY = null;
|
||||
this.current = {};
|
||||
|
||||
// the slide being shown
|
||||
this.current.slide = null;
|
||||
|
||||
// a raphaeljs set with all the shapes in the current slide
|
||||
this.current.shapes = null;
|
||||
|
||||
// a list of shapes as passed to this client when it receives `all_slides`
|
||||
// (se we are able to redraw the shapes whenever needed)
|
||||
this.current.shapeDefinitions = [];
|
||||
this.zoomLevel = 1;
|
||||
this.shiftPressed = false;
|
||||
@ -21,7 +36,13 @@ Meteor.WhiteboardPaperModel = (function() {
|
||||
this.heightRatio = 100;
|
||||
}
|
||||
|
||||
// Initializes the paper in the page.
|
||||
// Can't do these things in initialize() because by then some elements
|
||||
// are not yet created in the page.
|
||||
create() {
|
||||
// paper is embedded within the div#slide of the page.
|
||||
// @raphaelObj ?= ScaleRaphael(@container, "900", "500")
|
||||
|
||||
let h, w;
|
||||
h = $(`#${this.container}`).height();
|
||||
w = $(`#${this.container}`).width();
|
||||
@ -36,7 +57,7 @@ Meteor.WhiteboardPaperModel = (function() {
|
||||
if(this.slides) {
|
||||
this.rebuild();
|
||||
} else {
|
||||
this.slides = {};
|
||||
this.slides = {}; // if previously loaded
|
||||
}
|
||||
if(navigator.userAgent.indexOf("Firefox") !== -1) {
|
||||
this.raphaelObj.renderfix();
|
||||
@ -44,6 +65,8 @@ Meteor.WhiteboardPaperModel = (function() {
|
||||
return this.raphaelObj;
|
||||
}
|
||||
|
||||
// Re-add the images to the paper that are found
|
||||
// in the slides array (an object of urls and dimensions).
|
||||
rebuild() {
|
||||
let results, url;
|
||||
this.current.slide = null;
|
||||
@ -65,14 +88,29 @@ Meteor.WhiteboardPaperModel = (function() {
|
||||
return (ref = this.raphaelObj) != null ? ref.changeSize(width, height) : void 0;
|
||||
}
|
||||
|
||||
// Add an image to the paper.
|
||||
// @param {string} url the URL of the image to add to the paper
|
||||
// @param {number} width the width of the image (in pixels)
|
||||
// @param {number} height the height of the image (in pixels)
|
||||
// @return {Raphael.image} the image object added to the whiteboard
|
||||
addImageToPaper(url, width, height) {
|
||||
let cx, cy, img, max, sh, sw;
|
||||
this._updateContainerDimensions();
|
||||
|
||||
// solve for the ratio of what length is going to fit more than the other
|
||||
max = Math.max(width / this.containerWidth, height / this.containerHeight);
|
||||
// fit it all in appropriately
|
||||
url = this._slideUrl(url);
|
||||
sw = width / max;
|
||||
sh = height / max;
|
||||
//cx = (@containerWidth / 2) - (width / 2)
|
||||
//cy = (@containerHeight / 2) - (height / 2)
|
||||
img = this.raphaelObj.image(url, cx = 0, cy = 0, width, height);
|
||||
|
||||
// sw slide width as percentage of original width of paper
|
||||
// sh slide height as a percentage of original height of paper
|
||||
// x-offset from top left corner as percentage of original width of paper
|
||||
// y-offset from top left corner as percentage of original height of paper
|
||||
this.slides[url] = new WhiteboardSlideModel(img.id, url, img, width, height, sw, sh, cx, cy);
|
||||
if(this.current.slide == null) {
|
||||
img.toBack();
|
||||
@ -82,9 +120,12 @@ Meteor.WhiteboardPaperModel = (function() {
|
||||
} else {
|
||||
img.hide();
|
||||
}
|
||||
|
||||
// TODO: other places might also required an update in these dimensions
|
||||
this._updateContainerDimensions();
|
||||
|
||||
this._updateZoomRatios();
|
||||
if(this.raphaelObj.w === 100) {
|
||||
if(this.raphaelObj.w === 100) { // on first load: Raphael object is initially tiny
|
||||
this.cursor.setRadius(0.65 * this.widthRatio / 100);
|
||||
} else {
|
||||
this.cursor.setRadius(6 * this.widthRatio / 100);
|
||||
@ -92,6 +133,7 @@ Meteor.WhiteboardPaperModel = (function() {
|
||||
return img;
|
||||
}
|
||||
|
||||
// Removes all the images from the Raphael paper.
|
||||
removeAllImagesFromPaper() {
|
||||
let ref, ref1, url;
|
||||
for(url in this.slides) {
|
||||
@ -99,12 +141,17 @@ Meteor.WhiteboardPaperModel = (function() {
|
||||
if ((ref = this.raphaelObj.getById((ref1 = this.slides[url]) != null ? ref1.getId() : void 0)) != null) {
|
||||
ref.remove();
|
||||
}
|
||||
//@trigger('paper:image:removed', @slides[url].getId()) # Removes the previous image preventing images from being redrawn over each other repeatedly
|
||||
}
|
||||
}
|
||||
this.slides = {};
|
||||
return this.current.slide = null;
|
||||
}
|
||||
|
||||
// Switches the tool and thus the functions that get
|
||||
// called when certain events are fired from Raphael.
|
||||
// @param {string} tool the tool to turn on
|
||||
// @return {undefined}
|
||||
setCurrentTool(tool) {
|
||||
this.currentTool = tool;
|
||||
console.log("setting current tool to", tool);
|
||||
@ -122,6 +169,7 @@ Meteor.WhiteboardPaperModel = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
// Clear all shapes from this paper.
|
||||
clearShapes() {
|
||||
if(this.current.shapes != null) {
|
||||
this.current.shapes.forEach(element => {
|
||||
@ -140,7 +188,7 @@ Meteor.WhiteboardPaperModel = (function() {
|
||||
}
|
||||
|
||||
createCursor() {
|
||||
if(this.raphaelObj.w === 100) {
|
||||
if(this.raphaelObj.w === 100) { // on first load: Raphael object is initially tiny
|
||||
this.cursor = new WhiteboardCursorModel(this.raphaelObj, 0.65);
|
||||
this.cursor.setRadius(0.65 * this.widthRatio / 100);
|
||||
} else {
|
||||
@ -150,14 +198,20 @@ Meteor.WhiteboardPaperModel = (function() {
|
||||
return this.cursor.draw();
|
||||
}
|
||||
|
||||
// Updated a shape `shape` with the data in `data`.
|
||||
// TODO: check if the objects exist before calling update, if they don't they should be created
|
||||
updateShape(shape, data) {
|
||||
return this.current[shape].update(data);
|
||||
}
|
||||
|
||||
// Make a shape `shape` with the data in `data`.
|
||||
makeShape(shape, data) {
|
||||
let base, base1, i, len, obj, tool, toolModel;
|
||||
data.thickness *= this.adjustedWidth / 1000;
|
||||
tool = null;
|
||||
//TODO pay attention to this array, data in this array slows down the whiteboard
|
||||
//console.log @current
|
||||
//console.log @
|
||||
this.current[shape] = this._createTool(shape);
|
||||
toolModel = this.current[shape];
|
||||
tool = this.current[shape].make(data);
|
||||
@ -168,6 +222,8 @@ Meteor.WhiteboardPaperModel = (function() {
|
||||
this.current.shapes.push(tool);
|
||||
this.current.shapeDefinitions.push(toolModel.getDefinition());
|
||||
}
|
||||
|
||||
//We have a separate case for Poll as it returns an array instead of just one object
|
||||
if((tool != null) && shape === "poll_result") {
|
||||
if((base1 = this.current).shapes == null) {
|
||||
base1.shapes = this.raphaelObj.set();
|
||||
@ -180,17 +236,23 @@ Meteor.WhiteboardPaperModel = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
// Update the cursor position on screen
|
||||
// @param {number} x the x value of the cursor as a percentage of the width
|
||||
// @param {number} y the y value of the cursor as a percentage of the height
|
||||
moveCursor(x, y) {
|
||||
let cx, cy, ref, ref1, slideHeight, slideWidth;
|
||||
ref = this._currentSlideOffsets(), cx = ref[0], cy = ref[1];
|
||||
ref1 = this._currentSlideOriginalDimensions(), slideWidth = ref1[0], slideHeight = ref1[1];
|
||||
this.cursor.setPosition(x * slideWidth + cx, y * slideHeight + cy);
|
||||
|
||||
//if the slide is zoomed in then move the cursor based on where the viewBox is looking
|
||||
if((this.viewBoxXpos != null) && (this.viewBoxYPos != null) && (this.viewBoxWidth != null) && (this.viewBoxHeight != null)) {
|
||||
return this.cursor.setPosition(this.viewBoxXpos + x * this.viewBoxWidth, this.viewBoxYPos + y * this.viewBoxHeight);
|
||||
}
|
||||
}
|
||||
|
||||
zoomAndPan(widthRatio, heightRatio, xOffset, yOffset) {
|
||||
// console.log "zoomAndPan #{widthRatio} #{heightRatio} #{xOffset} #{yOffset}"
|
||||
let newHeight, newWidth, newX, newY;
|
||||
newX = -xOffset * 2 * this.adjustedWidth / 100;
|
||||
newY = -yOffset * 2 * this.adjustedHeight / 100;
|
||||
@ -204,6 +266,7 @@ Meteor.WhiteboardPaperModel = (function() {
|
||||
return this.adjustedHeight = height;
|
||||
}
|
||||
|
||||
// Update the dimensions of the container.
|
||||
_updateContainerDimensions() {
|
||||
let $container, containerDimensions, ref, ref1;
|
||||
$container = $('#whiteboard-paper');
|
||||
@ -229,6 +292,10 @@ Meteor.WhiteboardPaperModel = (function() {
|
||||
return this.heightRatio = currentSlideDoc != null ? currentSlideDoc.slide.height_ratio : void 0;
|
||||
}
|
||||
|
||||
// Retrieves an image element from the paper.
|
||||
// The url must be in the slides array.
|
||||
// @param {string} url the url of the image (must be in slides array)
|
||||
// @return {Raphael.image} return the image or null if not found
|
||||
_getImageFromPaper(url) {
|
||||
let id;
|
||||
if(this.slides[url]) {
|
||||
@ -264,6 +331,7 @@ Meteor.WhiteboardPaperModel = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapper method to create a tool for the whiteboard
|
||||
_createTool(type) {
|
||||
let height, model, ref, ref1, ref2, slideHeight, slideWidth, tool, width, xOffset, yOffset;
|
||||
switch(type) {
|
||||
@ -294,6 +362,7 @@ Meteor.WhiteboardPaperModel = (function() {
|
||||
ref1 = this._currentSlideOffsets(), xOffset = ref1[0], yOffset = ref1[1];
|
||||
ref2 = this._currentSlideDimensions(), width = ref2[0], height = ref2[1];
|
||||
tool = new model(this.raphaelObj);
|
||||
// TODO: why are the parameters inverted and it works?
|
||||
tool.setPaperSize(slideHeight, slideWidth);
|
||||
tool.setOffsets(xOffset, yOffset);
|
||||
tool.setPaperDimensions(width, height);
|
||||
@ -303,14 +372,18 @@ Meteor.WhiteboardPaperModel = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
// Adds the base url (the protocol+server part) to `url` if needed.
|
||||
_slideUrl(url) {
|
||||
if(url != null ? url.match(/http[s]?:/) : void 0) {
|
||||
return url;
|
||||
} else {
|
||||
return console.log(`The url '${url}'' did not match the expected format of: http/s`);
|
||||
//globals.presentationServer + url
|
||||
}
|
||||
}
|
||||
|
||||
//Changes the currently displayed page/slide (if any) with this one
|
||||
//@param {data} message object containing the "presentation" object
|
||||
_displayPage(data, originalWidth, originalHeight) {
|
||||
let _this, boardHeight, boardWidth, currentPresentation, currentSlide, currentSlideCursor, presentationId, ref;
|
||||
this.removeAllImagesFromPaper();
|
||||
@ -330,7 +403,7 @@ Meteor.WhiteboardPaperModel = (function() {
|
||||
this.zoomObserver.stop();
|
||||
}
|
||||
_this = this;
|
||||
this.zoomObserver = currentSlideCursor.observe({
|
||||
this.zoomObserver = currentSlideCursor.observe({ // watching the current slide changes
|
||||
changed(newDoc, oldDoc) {
|
||||
let newRatio, oldRatio, ref1, ref2;
|
||||
if(originalWidth <= originalHeight) {
|
||||
@ -357,7 +430,7 @@ Meteor.WhiteboardPaperModel = (function() {
|
||||
}
|
||||
}
|
||||
}
|
||||
if(_this.raphaelObj === 100) {
|
||||
if(_this.raphaelObj === 100) { // on first load: Raphael object is initially tiny
|
||||
return _this.cursor.setRadius(0.65 * newDoc.slide.width_ratio / 100);
|
||||
} else {
|
||||
return _this.cursor.setRadius(6 * newDoc.slide.width_ratio / 100);
|
||||
@ -365,6 +438,7 @@ Meteor.WhiteboardPaperModel = (function() {
|
||||
}
|
||||
});
|
||||
if(originalWidth <= originalHeight) {
|
||||
// square => boardHeight is the shortest side
|
||||
this.adjustedWidth = boardHeight * originalWidth / originalHeight;
|
||||
$('#whiteboard-paper').width(this.adjustedWidth);
|
||||
this.addImageToPaper(data, this.adjustedWidth, boardHeight);
|
||||
|
@ -5,17 +5,30 @@ let bind = function(fn, me) {
|
||||
};
|
||||
};
|
||||
|
||||
// A poll in the whiteboard
|
||||
this.WhiteboardPollModel = (function() {
|
||||
class WhiteboardPollModel extends WhiteboardToolModel {
|
||||
constructor(paper1) {
|
||||
super(paper1);
|
||||
this.paper = paper1;
|
||||
this.make = bind(this.make, this);
|
||||
|
||||
// the defintion of this shape, kept so we can redraw the shape whenever needed
|
||||
// format: x1, y1, x2, y2, stroke color, thickness, fill
|
||||
this.definition = [0, 0, 0, 0, "#333333", "2px", "#ffffff"];
|
||||
}
|
||||
|
||||
// Creates a polling in the paper
|
||||
// @param {number} x1 the x value of the top left corner
|
||||
// @param {number} y1 the y value of the top left corner
|
||||
// @param {number} x2 the x value of the bottom right corner
|
||||
// @param {number} y2 the y value of the bottom right corner
|
||||
// @param {number} thickness the thickness of the object's line(s)
|
||||
// @param {string} backgroundColor the background color of the base poll rectangle
|
||||
// @param {number} calcFontSize the default font-size of the text objects
|
||||
make(startingData) {
|
||||
let backgroundColor, barHeight, barWidth, calcFontSize, calculatedData, centerCell, color, height, horizontalPadding, i, k, l, leftCell, m, magicNumber, maxBarWidth, maxDigitWidth, maxLeftWidth, maxLineHeight, maxNumVotes, maxRightWidth, n, objects, percResult, ref, ref1, ref2, ref3, ref4, ref5, rightCell, svgNSi, tempSpanEl, tempTextNode, textArray, thickness, verticalPadding, votesTotal, width, x, x1, x2, xBar, xLeft, xNumVotes, xNumVotesDefault, xNumVotesMovedRight, xRight, y, y1, y2, yBar, yLeft, yNumVotes, yRight;
|
||||
//data needed to create the first base rectangle filled with white color
|
||||
x1 = startingData.points[0];
|
||||
y1 = startingData.points[1];
|
||||
x2 = startingData.points[2] + startingData.points[0] - 0.001;
|
||||
@ -28,7 +41,10 @@ this.WhiteboardPollModel = (function() {
|
||||
votesTotal = 0;
|
||||
maxNumVotes = 0;
|
||||
textArray = [];
|
||||
|
||||
//creating an array of text objects for the labels, percentages and number inside line bars
|
||||
if(startingData.result != null) {
|
||||
//counting the total number of votes and finding the biggest number of votes
|
||||
for(i = k = 0, ref = startingData.result.length - 1; 0 <= ref ? k <= ref : k >= ref; i = 0 <= ref ? ++k : --k) {
|
||||
votesTotal += startingData.result[i].num_votes;
|
||||
if(maxNumVotes < startingData.result[i].num_votes) {
|
||||
@ -36,6 +52,7 @@ this.WhiteboardPollModel = (function() {
|
||||
}
|
||||
textArray[i] = [];
|
||||
}
|
||||
//filling the array with proper text objects to display
|
||||
for(i = l = 0, ref1 = startingData.result.length - 1; 0 <= ref1 ? l <= ref1 : l >= ref1; i = 0 <= ref1 ? ++l : --l) {
|
||||
textArray[i].push(startingData.result[i].key, `${startingData.result[i].num_votes}`);
|
||||
if(votesTotal === 0) {
|
||||
@ -46,16 +63,26 @@ this.WhiteboardPollModel = (function() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//if coordinates are reversed - change them back
|
||||
if(x2 < x1) {
|
||||
ref2 = [x2, x1], x1 = ref2[0], x2 = ref2[1];
|
||||
}
|
||||
if(y2 < y1) {
|
||||
ref3 = [y2, y1], y1 = ref3[0], y2 = ref3[1];
|
||||
}
|
||||
|
||||
//Params:
|
||||
//x - the actual calculated x value of the top left corner of the polling area
|
||||
//y - the actual calculated y value of the top left corner of the polling area
|
||||
//width - the width of the polling area
|
||||
//height - the height of the polling area
|
||||
x = x1 * this.gw + this.xOffset;
|
||||
y = y1 * this.gh + this.yOffset;
|
||||
width = (x2 * this.gw + this.xOffset) - x;
|
||||
height = (y2 * this.gh + this.yOffset) - y;
|
||||
|
||||
//creating a base outer rectangle
|
||||
this.obj = this.paper.rect(x, y, width, height, 0);
|
||||
this.obj.attr("fill", backgroundColor);
|
||||
this.obj.attr("stroke-width", 0);
|
||||
@ -63,10 +90,14 @@ this.WhiteboardPollModel = (function() {
|
||||
shape: "poll_result",
|
||||
data: [x1, y1, x2, y2, this.obj.attrs["stroke"], this.obj.attrs["stroke-width"], this.obj.attrs["fill"]]
|
||||
};
|
||||
|
||||
//recalculated coordinates, width and height for the inner rectangle
|
||||
width = width * 0.95;
|
||||
height = height - width * 0.05;
|
||||
x = x + width * 0.025;
|
||||
y = y + width * 0.025;
|
||||
|
||||
//creating a base inner rectangle
|
||||
this.obj1 = this.paper.rect(x, y, width, height, 0);
|
||||
this.obj1.attr("stroke", "#333333");
|
||||
this.obj1.attr("fill", backgroundColor);
|
||||
@ -75,6 +106,8 @@ this.WhiteboardPollModel = (function() {
|
||||
shape: "poll_result",
|
||||
data: [x1, y1, x2, y2, this.obj.attrs["stroke"], this.obj1.attrs["stroke-width"], this.obj1.attrs["fill"]]
|
||||
};
|
||||
|
||||
//Calculating a proper font-size, and the maximum widht and height of the objects
|
||||
calculatedData = calculateFontAndWidth(textArray, calcFontSize, width, height, x, y);
|
||||
calcFontSize = calculatedData[0];
|
||||
maxLeftWidth = calculatedData[1];
|
||||
@ -84,6 +117,8 @@ this.WhiteboardPollModel = (function() {
|
||||
maxBarWidth = width * 0.9 - maxLeftWidth - maxRightWidth;
|
||||
barHeight = height * 0.75 / textArray.length;
|
||||
svgNSi = "http://www.w3.org/2000/svg";
|
||||
|
||||
//Initializing a text element for further calculations and for the left column of keys
|
||||
this.obj2 = this.paper.text(x, y, "");
|
||||
this.obj2.attr({
|
||||
"fill": "#333333",
|
||||
@ -96,6 +131,8 @@ this.WhiteboardPollModel = (function() {
|
||||
while((leftCell != null) && leftCell.hasChildNodes()) {
|
||||
leftCell.removeChild(leftCell.firstChild);
|
||||
}
|
||||
|
||||
//Initializing a text element for the right column of percentages
|
||||
this.obj3 = this.paper.text(x, y, "");
|
||||
this.obj3.attr({
|
||||
"fill": "#333333",
|
||||
@ -108,19 +145,39 @@ this.WhiteboardPollModel = (function() {
|
||||
while((rightCell != null) && rightCell.hasChildNodes()) {
|
||||
rightCell.removeChild(rightCell.firstChild);
|
||||
}
|
||||
|
||||
//setting a font size for the text elements on the left and on the right
|
||||
leftCell.style['font-size'] = calcFontSize;
|
||||
rightCell.style['font-size'] = calcFontSize;
|
||||
//Horizontal padding
|
||||
horizontalPadding = width * 0.1 / 4;
|
||||
//Vertical padding
|
||||
verticalPadding = height * 0.25 / (textArray.length + 1);
|
||||
|
||||
//*****************************************************************************************************
|
||||
//******************************************MAGIC NUMBER***********************************************
|
||||
//There is no automatic vertical centering in SVG.
|
||||
//To center the text element we have to move it down by the half of its height.
|
||||
//But every text element has its own padding by default.
|
||||
//The height we receive by calling getBBox() includes padding, but the anchor point doesn't consider it.
|
||||
//This way the text element is moved down a little bit too much and we have to move it up a bit.
|
||||
//Number 3.5 seems to work fine.
|
||||
// Oleksandr Zhurbenko. August 19, 2015
|
||||
magicNumber = 3.5;
|
||||
//*****************************************************************************************************
|
||||
|
||||
//Initial coordinates of the key column
|
||||
yLeft = y + verticalPadding + barHeight / 2 - magicNumber;
|
||||
xLeft = x + horizontalPadding + 1;
|
||||
//Initial coordinates of the line bar column
|
||||
xBar = x + maxLeftWidth + horizontalPadding * 2;
|
||||
yBar = y + verticalPadding;
|
||||
//Initial coordinates of the percentage column
|
||||
yRight = y + verticalPadding + barHeight / 2 - magicNumber;
|
||||
xRight = x + horizontalPadding * 3 + maxLeftWidth + maxRightWidth + maxBarWidth + 1;
|
||||
objects = [this.obj, this.obj1, this.obj2, this.obj3];
|
||||
for(i = m = 0, ref4 = textArray.length - 1; 0 <= ref4 ? m <= ref4 : m >= ref4; i = 0 <= ref4 ? ++m : --m) {
|
||||
//Adding an element to the left column
|
||||
tempSpanEl = document.createElementNS(svgNSi, "tspan");
|
||||
tempSpanEl.setAttributeNS(null, "x", xLeft);
|
||||
tempSpanEl.setAttributeNS(null, "y", yLeft);
|
||||
@ -128,6 +185,8 @@ this.WhiteboardPollModel = (function() {
|
||||
tempTextNode = document.createTextNode(textArray[i][0]);
|
||||
tempSpanEl.appendChild(tempTextNode);
|
||||
leftCell.appendChild(tempSpanEl);
|
||||
|
||||
//drawing a black graph bar
|
||||
if(maxNumVotes === 0 || startingData.result[i].num_votes === 0) {
|
||||
barWidth = 2;
|
||||
} else {
|
||||
@ -138,6 +197,8 @@ this.WhiteboardPollModel = (function() {
|
||||
this.obj4.attr("fill", "#333333");
|
||||
this.obj4.attr("stroke-width", zoomStroke(formatThickness(0)));
|
||||
objects.push(this.obj4);
|
||||
|
||||
//Adding an element to the right column
|
||||
tempSpanEl = document.createElementNS(svgNSi, "tspan");
|
||||
tempSpanEl.setAttributeNS(null, "x", xRight);
|
||||
tempSpanEl.setAttributeNS(null, "y", yRight);
|
||||
@ -145,10 +206,14 @@ this.WhiteboardPollModel = (function() {
|
||||
tempTextNode = document.createTextNode(textArray[i][2]);
|
||||
tempSpanEl.appendChild(tempTextNode);
|
||||
rightCell.appendChild(tempSpanEl);
|
||||
|
||||
//changing the Y coordinate for all the objects
|
||||
yBar = yBar + barHeight + verticalPadding;
|
||||
yLeft = yLeft + barHeight + verticalPadding;
|
||||
yRight = yRight + barHeight + verticalPadding;
|
||||
}
|
||||
|
||||
//Initializing a text element for the number of votes text field inside the line bar
|
||||
this.obj5 = this.paper.text(x, y, "");
|
||||
this.obj5.attr({
|
||||
"fill": "#333333",
|
||||
@ -159,10 +224,14 @@ this.WhiteboardPollModel = (function() {
|
||||
while((centerCell != null) && centerCell.hasChildNodes()) {
|
||||
centerCell.removeChild(centerCell.firstChild);
|
||||
}
|
||||
|
||||
//Initial coordinates of the text inside the bar column
|
||||
xNumVotesDefault = x + maxLeftWidth + horizontalPadding * 2;
|
||||
xNumVotesMovedRight = xNumVotesDefault + barWidth / 2 + horizontalPadding + maxDigitWidth / 2;
|
||||
yNumVotes = y + verticalPadding - magicNumber;
|
||||
color = "white";
|
||||
//Drawing the text element with the number of votes inside of the black line bars
|
||||
//Or outside if a line bar is too small
|
||||
for(i = n = 0, ref5 = textArray.length - 1; 0 <= ref5 ? n <= ref5 : n >= ref5; i = 0 <= ref5 ? ++n : --n) {
|
||||
if(maxNumVotes === 0 || startingData.result[i].num_votes === 0) {
|
||||
barWidth = 2;
|
||||
@ -190,6 +259,7 @@ this.WhiteboardPollModel = (function() {
|
||||
return objects;
|
||||
}
|
||||
|
||||
// Update the poll dimensions. Does nothing.
|
||||
update(startingData) {}
|
||||
}
|
||||
|
||||
@ -199,8 +269,12 @@ this.WhiteboardPollModel = (function() {
|
||||
calculateFontAndWidth = function(textArray, calcFontSize, width, height, x, y) {
|
||||
let calculatedData, flag, i, j, k, l, len, line, m, maxDigitWidth, maxLeftWidth, maxLineHeight, maxLineWidth, maxRightWidth, ref, ref1, spanHeight, spanWidth, test;
|
||||
calculatedData = [];
|
||||
//maximum line width can be either 1/3 of the line or 40px
|
||||
//maximum line height is 75% of the initial size of the box divided by the number of lines
|
||||
maxLineWidth = width / 3;
|
||||
maxLineHeight = height * 0.75 / (textArray != null ? textArray.length : void 0);
|
||||
|
||||
//calculating a proper font-size
|
||||
flag = true;
|
||||
while(flag) {
|
||||
flag = false;
|
||||
@ -217,6 +291,8 @@ calculateFontAndWidth = function(textArray, calcFontSize, width, height, x, y) {
|
||||
}
|
||||
}
|
||||
calculatedData.push(calcFontSize);
|
||||
|
||||
//looking for a maximum width and height of the left and right text elements
|
||||
maxLeftWidth = 0;
|
||||
maxRightWidth = 0;
|
||||
maxLineHeight = 0;
|
||||
|
@ -4,15 +4,24 @@ const bind = function(fn, me) {
|
||||
};
|
||||
};
|
||||
|
||||
// A rectangle in the whiteboard
|
||||
this.WhiteboardRectModel = (function() {
|
||||
class WhiteboardRectModel extends WhiteboardToolModel{
|
||||
constructor(paper) {
|
||||
super(paper);
|
||||
this.paper = paper;
|
||||
this.make = bind(this.make, this);
|
||||
|
||||
// the defintion of this shape, kept so we can redraw the shape whenever needed
|
||||
// format: x1, y1, x2, y2, stroke color, thickness
|
||||
this.definition = [0, 0, 0, 0, "#000", "0px"];
|
||||
}
|
||||
|
||||
// Creates a rectangle in the paper
|
||||
// @param {number} x the x value of the top left corner
|
||||
// @param {number} y the y value of the top left corner
|
||||
// @param {string} colour the colour of the object
|
||||
// @param {number} thickness the thickness of the object's line(s)
|
||||
make(startingData) {
|
||||
let color, thickness, x, y;
|
||||
x = startingData.points[0];
|
||||
@ -29,6 +38,12 @@ this.WhiteboardRectModel = (function() {
|
||||
return this.obj;
|
||||
}
|
||||
|
||||
// Update the rectangle dimensions
|
||||
// @param {number} x1 the x value of the top left corner
|
||||
// @param {number} y1 the y value of the top left corner
|
||||
// @param {number} x2 the x value of the bottom right corner
|
||||
// @param {number} y2 the y value of the bottom right corner
|
||||
// @param {boolean} square (draw a square or not)
|
||||
update(startingData) {
|
||||
let height, ref, ref1, reversed, square, width, x, x1, x2, y, y1, y2;
|
||||
x1 = startingData.points[0];
|
||||
@ -45,7 +60,7 @@ this.WhiteboardRectModel = (function() {
|
||||
reversed = true;
|
||||
}
|
||||
if(square) {
|
||||
if(reversed) {
|
||||
if(reversed) { //if reveresed, the y1 coordinate gets updated, not the y2 coordinate
|
||||
y1 = y2 - (x2 - x1) * this.gw / this.gh;
|
||||
} else {
|
||||
y2 = y1 + (x2 - x1) * this.gw / this.gh;
|
||||
@ -55,6 +70,7 @@ this.WhiteboardRectModel = (function() {
|
||||
y = y1 * this.gh + this.yOffset;
|
||||
width = (x2 * this.gw + this.xOffset) - x;
|
||||
height = (y2 * this.gh + this.yOffset) - y;
|
||||
//if !square
|
||||
this.obj.attr({
|
||||
x: x,
|
||||
y: y,
|
||||
@ -69,6 +85,8 @@ this.WhiteboardRectModel = (function() {
|
||||
width: width
|
||||
height: width
|
||||
*/
|
||||
|
||||
// we need to update all these values, specially for when shapes are drawn backwards
|
||||
this.definition.data[0] = x1;
|
||||
this.definition.data[1] = y1;
|
||||
this.definition.data[2] = x2;
|
||||
@ -76,6 +94,13 @@ this.WhiteboardRectModel = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
// Draw a rectangle on the paper
|
||||
// @param {number} x1 the x value of the top left corner
|
||||
// @param {number} y1 the y value of the top left corner
|
||||
// @param {number} x2 the x value of the bottom right corner
|
||||
// @param {number} y2 the y value of the bottom right corner
|
||||
// @param {string} colour the colour of the object
|
||||
// @param {number} thickness the thickness of the object's line(s)
|
||||
draw(x1, y1, x2, y2, colour, thickness) {
|
||||
let r, ref, ref1, x, y;
|
||||
if(x2 < x1) {
|
||||
@ -91,9 +116,58 @@ this.WhiteboardRectModel = (function() {
|
||||
return r;
|
||||
}
|
||||
|
||||
dragOnStart(x, y) {}
|
||||
dragOnMove(dx, dy, x, y, e) {}
|
||||
dragOnEnd(e) {}
|
||||
// Creating a rectangle has started
|
||||
// @param {number} x the x value of cursor at the time in relation to the left side of the browser
|
||||
// @param {number} y the y value of cursor at the time in relation to the top of the browser
|
||||
// TODO: moved here but not finished
|
||||
dragOnStart(x, y) {
|
||||
// sx = (@paperWidth - @gw) / 2
|
||||
// sy = (@paperHeight - @gh) / 2
|
||||
// // find the x and y values in relation to the whiteboard
|
||||
// @cx2 = (x - @containerOffsetLeft - sx + @xOffset) / @paperWidth
|
||||
// @cy2 = (y - @containerOffsetTop - sy + @yOffset) / @paperHeight
|
||||
// globals.connection.emitMakeShape "rect",
|
||||
// [ @cx2, @cy2, @currentColour, @currentThickness ]
|
||||
}
|
||||
|
||||
// Adjusting rectangle continues
|
||||
// @param {number} dx the difference in the x value at the start as opposed to the x value now
|
||||
// @param {number} dy the difference in the y value at the start as opposed to the y value now
|
||||
// @param {number} x the x value of cursor at the time in relation to the left side of the browser
|
||||
// @param {number} y the y value of cursor at the time in relation to the top of the browser
|
||||
// @param {Event} e the mouse event
|
||||
// TODO: moved here but not finished
|
||||
dragOnMove(dx, dy, x, y, e) {
|
||||
// // if shift is pressed, make it a square
|
||||
// dy = dx if @shiftPressed
|
||||
// dx = dx / @paperWidth
|
||||
// dy = dy / @paperHeight
|
||||
// // adjust for negative values as well
|
||||
// if dx >= 0
|
||||
// x1 = @cx2
|
||||
// else
|
||||
// x1 = @cx2 + dx
|
||||
// dx = -dx
|
||||
// if dy >= 0
|
||||
// y1 = @cy2
|
||||
// else
|
||||
// y1 = @cy2 + dy
|
||||
// dy = -dy
|
||||
// globals.connection.emitUpdateShape "rect", [ x1, y1, dx, dy ]
|
||||
}
|
||||
|
||||
// When rectangle finished being drawn
|
||||
// @param {Event} e the mouse event
|
||||
// TODO: moved here but not finished
|
||||
dragOnEnd(e) {
|
||||
// if @obj?
|
||||
// attrs = @obj.attrs
|
||||
// if attrs?
|
||||
// globals.connection.emitPublishShape "rect",
|
||||
// [ attrs.x / @gw, attrs.y / @gh, attrs.width / @gw, attrs.height / @gh,
|
||||
// @currentColour, @currentThickness ]
|
||||
// @obj = null
|
||||
}
|
||||
}
|
||||
|
||||
return WhiteboardRectModel;
|
||||
|
@ -1,5 +1,8 @@
|
||||
// A slide in the whiteboard
|
||||
this.WhiteboardSlideModel = (function() {
|
||||
class WhiteboardSlideModel {
|
||||
|
||||
// TODO: check if we really need original and display width and heights separate or if they can be the same
|
||||
constructor(
|
||||
id,
|
||||
url,
|
||||
|
@ -1,3 +1,4 @@
|
||||
// A text in the whiteboard
|
||||
this.WhiteboardTextModel = (function() {
|
||||
let checkDashPosition, checkWidth;
|
||||
|
||||
@ -5,10 +6,15 @@ this.WhiteboardTextModel = (function() {
|
||||
constructor(paper) {
|
||||
super(paper);
|
||||
this.paper = paper;
|
||||
|
||||
// the defintion of this shape, kept so we can redraw the shape whenever needed
|
||||
// format: x, y, width, height, colour, fontSize, calcFontSize, text
|
||||
this.definition = [0, 0, 0, 0, "#000", 0, 0, ""];
|
||||
}
|
||||
|
||||
// Make a text on the whiteboard
|
||||
make(startingData) {
|
||||
//console.log "making text:" + JSON.stringify startingData
|
||||
let calcFontSize, colour, fontSize, height, text, width, x, y;
|
||||
x = startingData.x;
|
||||
y = startingData.y;
|
||||
@ -22,21 +28,26 @@ this.WhiteboardTextModel = (function() {
|
||||
shape: "text",
|
||||
data: [x, y, width, height, colour, fontSize, calcFontSize, text]
|
||||
};
|
||||
|
||||
//calcFontSize = (calcFontSize/100 * @gh)
|
||||
x = (x * this.gw) + this.xOffset;
|
||||
y = (y * this.gh) + this.yOffset + calcFontSize;
|
||||
width = width / 100 * this.gw;
|
||||
this.obj = this.paper.text(x / 100, y / 100, "");
|
||||
this.obj.attr({
|
||||
"fill": colour,
|
||||
"font-family": "Arial",
|
||||
"font-family": "Arial", // TODO: make dynamic
|
||||
"font-size": calcFontSize
|
||||
});
|
||||
this.obj.node.style["text-anchor"] = "start";
|
||||
this.obj.node.style["textAnchor"] = "start";
|
||||
this.obj.node.style["text-anchor"] = "start"; // force left align
|
||||
this.obj.node.style["textAnchor"] = "start"; // for firefox, 'cause they like to be different
|
||||
return this.obj;
|
||||
}
|
||||
|
||||
// Update text shape drawn
|
||||
// @param {object} the object containing the shape info
|
||||
update(startingData) {
|
||||
//console.log "updating text" + JSON.stringify startingData
|
||||
let calcFontSize, cell, colour, computedTextLength, cumulY, curNumChars, dashArray, dashFound, dy, fontSize, height, i, indexPos, line, maxWidth, myText, myTextNode, result, svgNS, tempText, tspanEl, word, words, x, y;
|
||||
x = startingData.x;
|
||||
y = startingData.y;
|
||||
@ -53,13 +64,18 @@ this.WhiteboardTextModel = (function() {
|
||||
maxWidth = maxWidth / 100 * this.gw;
|
||||
this.obj.attr({
|
||||
"fill": colour,
|
||||
"font-family": "Arial",
|
||||
"font-family": "Arial", // TODO: make dynamic
|
||||
"font-size": calcFontSize
|
||||
});
|
||||
cell = this.obj.node;
|
||||
while((cell != null) && cell.hasChildNodes()) {
|
||||
cell.removeChild(cell.firstChild);
|
||||
}
|
||||
|
||||
// used code from textFlow lib http://www.carto.net/papers/svg/textFlow/
|
||||
// but had to merge it here because "cell" was bigger than what the stack could take
|
||||
|
||||
//extract and add line breaks for start
|
||||
dashArray = new Array();
|
||||
dashFound = true;
|
||||
indexPos = 0;
|
||||
@ -68,12 +84,14 @@ this.WhiteboardTextModel = (function() {
|
||||
while(dashFound === true) {
|
||||
result = myText.indexOf("-", indexPos);
|
||||
if(result === -1) {
|
||||
//could not find a dash
|
||||
dashFound = false;
|
||||
} else {
|
||||
dashArray.push(result);
|
||||
indexPos = result + 1;
|
||||
}
|
||||
}
|
||||
//split the text at all spaces and dashes
|
||||
words = myText.split(/[\s-]/);
|
||||
line = "";
|
||||
dy = 0;
|
||||
@ -82,6 +100,8 @@ this.WhiteboardTextModel = (function() {
|
||||
myTextNode = void 0;
|
||||
tspanEl = void 0;
|
||||
i = 0;
|
||||
|
||||
//checking if any of the words exceed the width of a textBox
|
||||
words = checkWidth(words, maxWidth, x, dy, cell);
|
||||
while(i < words.length) {
|
||||
word = words[i];
|
||||
@ -89,13 +109,15 @@ this.WhiteboardTextModel = (function() {
|
||||
if(computedTextLength > maxWidth || i === 0) {
|
||||
if(computedTextLength > maxWidth) {
|
||||
tempText = tspanEl.firstChild.nodeValue;
|
||||
tempText = tempText.slice(0, tempText.length - words[i - 1].length - 2);
|
||||
tempText = tempText.slice(0, tempText.length - words[i - 1].length - 2); //the -2 is because we also strip off white space
|
||||
tspanEl.firstChild.nodeValue = tempText;
|
||||
}
|
||||
//setting up coordinates for the first line of text
|
||||
if(i === 0) {
|
||||
dy = calcFontSize;
|
||||
cumulY += dy;
|
||||
}
|
||||
//alternatively one could use textLength and lengthAdjust, however, currently this is not too well supported in SVG UA's
|
||||
tspanEl = document.createElementNS(svgNS, "tspan");
|
||||
tspanEl.setAttributeNS(null, "x", x);
|
||||
tspanEl.setAttributeNS(null, "dy", dy);
|
||||
@ -140,6 +162,7 @@ this.WhiteboardTextModel = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
//this function checks if there should be a dash at the given position, instead of a blank
|
||||
checkDashPosition = function(dashArray, pos) {
|
||||
let i, result;
|
||||
result = false;
|
||||
@ -153,6 +176,8 @@ this.WhiteboardTextModel = (function() {
|
||||
return result;
|
||||
};
|
||||
|
||||
//this function checks the width of the word and adds a " " if the width of the word exceeds the width of the textbox
|
||||
//in order for the word to be split and shown properly
|
||||
checkWidth = function(words, maxWidth, x, dy, cell) {
|
||||
let count, num, partWord, start, str, svgNSi, temp, temp3, tempArray, tempSpanEl, tempTextNode, tempWord;
|
||||
count = 0;
|
||||
@ -166,16 +191,21 @@ this.WhiteboardTextModel = (function() {
|
||||
tempTextNode = document.createTextNode(str);
|
||||
tempSpanEl.appendChild(tempTextNode);
|
||||
num = 0;
|
||||
//creating a textNode and adding it to the cell to check the width
|
||||
while(num < temp.length) {
|
||||
tempSpanEl.firstChild.nodeValue = temp[num];
|
||||
cell.appendChild(tempSpanEl);
|
||||
//if width is bigger than maxWidth + whitespace between textBox borders and a word
|
||||
if(tempSpanEl.getComputedTextLength() + 10 > maxWidth) {
|
||||
tempWord = temp[num];
|
||||
cell.removeChild(cell.firstChild);
|
||||
|
||||
//initializing temp variables
|
||||
count = 1;
|
||||
start = 0;
|
||||
partWord = `${tempWord[0]}`;
|
||||
tempArray = [];
|
||||
//check the width by increasing the word character by character
|
||||
while(count < tempWord.length) {
|
||||
partWord += tempWord[count];
|
||||
tempSpanEl.firstChild.nodeValue = partWord;
|
||||
|
@ -1,11 +1,20 @@
|
||||
// A triangle in the whiteboard
|
||||
this.WhiteboardTriangleModel = (function() {
|
||||
class WhiteboardTriangleModel extends WhiteboardToolModel {
|
||||
constructor(paper) {
|
||||
super(paper);
|
||||
this.paper = paper;
|
||||
|
||||
// the defintion of this shape, kept so we can redraw the shape whenever needed
|
||||
// format: x1, y1, x2, y2, stroke color, thickness
|
||||
this.definition = [0, 0, 0, 0, "#000", "0px"];
|
||||
}
|
||||
|
||||
// Make a triangle on the whiteboard
|
||||
// @param {[type]} x the x value of the top left corner
|
||||
// @param {[type]} y the y value of the top left corner
|
||||
// @param {string} colour the colour of the object
|
||||
// @param {number} thickness the thickness of the object's line(s)
|
||||
make(info) {
|
||||
let color, path, thickness, x, y;
|
||||
if((info != null ? info.points : void 0) != null) {
|
||||
@ -25,6 +34,11 @@ this.WhiteboardTriangleModel = (function() {
|
||||
return this.obj;
|
||||
}
|
||||
|
||||
// Update triangle drawn
|
||||
// @param {number} x1 the x value of the top left corner
|
||||
// @param {number} y1 the y value of the top left corner
|
||||
// @param {number} x2 the x value of the bottom right corner
|
||||
// @param {number} y2 the y value of the bottom right corner
|
||||
update(info) {
|
||||
let path, ref, x1, x2, xBottomLeft, xBottomRight, xTop, y1, y2, yBottomLeft, yBottomRight, yTop;
|
||||
if((info != null ? info.points : void 0) != null) {
|
||||
@ -46,6 +60,13 @@ this.WhiteboardTriangleModel = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
// Draw a triangle on the whiteboard
|
||||
// @param {number} x1 the x value of the top left corner
|
||||
// @param {number} y1 the y value of the top left corner
|
||||
// @param {number} x2 the x value of the bottom right corner
|
||||
// @param {number} y2 the y value of the bottom right corner
|
||||
// @param {string} colour the colour of the object
|
||||
// @param {number} thickness the thickness of the object's line(s)
|
||||
draw(x1, y1, x2, y2, colour, thickness) {
|
||||
let path, ref, triangle, xBottomLeft, xBottomRight, xTop, yBottomLeft, yBottomRight, yTop;
|
||||
ref = this._getCornersFromPoints(x1, y1, x2, y2), xTop = ref[0], yTop = ref[1], xBottomLeft = ref[2], yBottomLeft = ref[3], xBottomRight = ref[4], yBottomRight = ref[5];
|
||||
@ -74,6 +95,10 @@ this.WhiteboardTriangleModel = (function() {
|
||||
return `M${xTop},${yTop},${xBottomLeft},${yBottomLeft},${xBottomRight},${yBottomRight}z`;
|
||||
}
|
||||
|
||||
// Scales a triangle path string to fit within a width and height of the new paper size
|
||||
// @param {number} w width of the shape as a percentage of the original width
|
||||
// @param {number} h height of the shape as a percentage of the original height
|
||||
// @return {string} the path string after being manipulated to new paper size
|
||||
_scaleTrianglePath(string, w, h, xOffset, yOffset) {
|
||||
let j, len, path, points;
|
||||
if(xOffset == null) {
|
||||
@ -86,6 +111,8 @@ this.WhiteboardTriangleModel = (function() {
|
||||
points = string.match(/(\d+[.]?\d*)/g);
|
||||
len = points.length;
|
||||
j = 0;
|
||||
|
||||
// go through each point and multiply it by the new height and width
|
||||
path = "M";
|
||||
while(j < len) {
|
||||
if(j !== 0) {
|
||||
|
@ -1,7 +1,12 @@
|
||||
// TODO: should be split on server and client side
|
||||
// // Global configurations file
|
||||
|
||||
let config, file, ref, transports, winston;
|
||||
|
||||
config = {};
|
||||
|
||||
// Default global variables
|
||||
|
||||
config.appName = 'BigBlueButton HTML5 Client';
|
||||
|
||||
config.bbbServerVersion = '1.0-beta';
|
||||
@ -20,20 +25,30 @@ config.maxChatLength = 140;
|
||||
|
||||
config.lockOnJoin = true;
|
||||
|
||||
//// Application configurations
|
||||
|
||||
config.app = {};
|
||||
|
||||
//default font sizes for mobile / desktop
|
||||
|
||||
config.app.mobileFont = 16;
|
||||
|
||||
config.app.desktopFont = 14;
|
||||
|
||||
// Will offer the user to join the audio when entering the meeting
|
||||
|
||||
config.app.autoJoinAudio = false;
|
||||
|
||||
config.app.listenOnly = false;
|
||||
|
||||
config.app.skipCheck = false;
|
||||
|
||||
// The amount of time the client will wait before making another call to successfully hangup the WebRTC conference call
|
||||
|
||||
config.app.WebRTCHangupRetryInterval = 2000;
|
||||
|
||||
// Configs for redis
|
||||
|
||||
config.redis = {};
|
||||
|
||||
config.redis.host = "127.0.0.1";
|
||||
@ -64,11 +79,14 @@ config.redis.channels.toBBBApps.whiteboard = "bigbluebutton:to-bbb-apps:whiteboa
|
||||
|
||||
config.redis.channels.toBBBApps.polling = "bigbluebutton:to-bbb-apps:polling";
|
||||
|
||||
// Logging
|
||||
|
||||
config.log = {};
|
||||
|
||||
if(Meteor.isServer) {
|
||||
config.log.path = (typeof process !== "undefined" && process !== null ? (ref = process.env) != null ? ref.NODE_ENV : void 0 : void 0) === "production" ? "/var/log/bigbluebutton/bbbnode.log" : `${process.env.PWD}/../log/development.log`;
|
||||
winston = Winston;
|
||||
// Setting up a logger in Meteor.log
|
||||
winston = Winston; //Meteor.require 'winston'
|
||||
file = config.log.path;
|
||||
transports = [
|
||||
new winston.transports.Console(), new winston.transports.File({
|
||||
|
@ -1,5 +1,8 @@
|
||||
// used in Flash and HTML to show a legitimate break in the line
|
||||
this.BREAK_LINE = '<br/>';
|
||||
|
||||
// soft return in HTML to signify a broken line without displaying the escaped '<br/>' line break text
|
||||
this.CARRIAGE_RETURN = '\r';
|
||||
|
||||
// handle this the same as carriage return, in case text copied has this
|
||||
this.NEW_LINE = '\n';
|
||||
|
@ -3,6 +3,7 @@ this.Router.configure({
|
||||
});
|
||||
|
||||
this.Router.map(function() {
|
||||
// this is how we handle login attempts
|
||||
this.route("main", {
|
||||
path: "/html5client/:meeting_id/:user_id/:auth_token",
|
||||
where: "client",
|
||||
@ -12,7 +13,10 @@ this.Router.map(function() {
|
||||
userId = this.params.user_id;
|
||||
authToken = this.params.auth_token;
|
||||
setInSession("loginUrl", this.originalUrl);
|
||||
|
||||
// catch if any of the user's meeting data is invalid
|
||||
if ((authToken == null) || (meetingId == null) || (userId == null)) {
|
||||
// if their data is invalid, redirect the user to the logout page
|
||||
document.location = getInSession('logoutURL');
|
||||
} else {
|
||||
Meteor.call("validateAuthToken", meetingId, userId, authToken);
|
||||
@ -27,6 +31,8 @@ this.Router.map(function() {
|
||||
return this.next();
|
||||
}
|
||||
});
|
||||
|
||||
// the user successfully logged in
|
||||
this.route("signedin", {
|
||||
path: "/html5client",
|
||||
where: "client",
|
||||
@ -37,8 +43,13 @@ this.Router.map(function() {
|
||||
authToken = getInSession("authToken");
|
||||
onErrorFunction = function(error, result) {
|
||||
console.log("ONERRORFUNCTION");
|
||||
|
||||
//make sure the user is not let through
|
||||
Meteor.call("userLogout", meetingId, userId, authToken);
|
||||
|
||||
clearSessionVar();
|
||||
|
||||
// Attempt to log back in
|
||||
if (error == null) {
|
||||
window.location.href = getInSession('loginUrl') || getInSession('logoutURL');
|
||||
}
|
||||
@ -65,21 +76,24 @@ this.Router.map(function() {
|
||||
return Meteor.subscribe('bbb_cursor', meetingId, {
|
||||
onReady: function() {
|
||||
let a, handleLogourUrlError;
|
||||
// done subscribing, start rendering the client and set default settings
|
||||
_this.render('main');
|
||||
onLoadComplete();
|
||||
handleLogourUrlError = function() {
|
||||
alert("Error: could not find the logoutURL");
|
||||
setInSession("logoutURL", document.location.hostname);
|
||||
};
|
||||
|
||||
// obtain the logoutURL
|
||||
a = $.ajax({
|
||||
dataType: 'json',
|
||||
url: '/bigbluebutton/api/enter'
|
||||
});
|
||||
a.done(data => {
|
||||
if (data.response.logoutURL != null) {
|
||||
if (data.response.logoutURL != null) { // for a meeting with 0 users
|
||||
setInSession("logoutURL", data.response.logoutURL);
|
||||
} else {
|
||||
if (data.response.logoutUrl != null) {
|
||||
if (data.response.logoutUrl != null) { // for a running meeting
|
||||
setInSession("logoutURL", data.response.logoutUrl);
|
||||
} else {
|
||||
return handleLogourUrlError();
|
||||
@ -111,6 +125,8 @@ this.Router.map(function() {
|
||||
return this.render('loading');
|
||||
}
|
||||
});
|
||||
|
||||
// endpoint - is the html5client running (ready to handle a user)
|
||||
this.route('meteorEndpoint', {
|
||||
path: '/check',
|
||||
where: 'server',
|
||||
@ -118,6 +134,8 @@ this.Router.map(function() {
|
||||
this.response.writeHead(200, {
|
||||
'Content-Type': 'application/json'
|
||||
});
|
||||
|
||||
// reply that the html5client is running
|
||||
this.response.end(JSON.stringify({
|
||||
"html5clientStatus": "running"
|
||||
}));
|
||||
|
@ -1,4 +1,8 @@
|
||||
Meteor.methods({
|
||||
// meetingId: the id of the meeting
|
||||
// chatObject: the object including info on the chat message, including the text
|
||||
// requesterUserId: the userId of the user sending chat
|
||||
// requesterToken: the authToken of the requester
|
||||
sendChatMessagetoServer(meetingId, chatObject, requesterUserId, requesterToken) {
|
||||
let action, chatType, eventName, message, recipient;
|
||||
chatType = chatObject.chat_type;
|
||||
@ -11,7 +15,7 @@ Meteor.methods({
|
||||
} else {
|
||||
eventName = "send_private_chat_message";
|
||||
if(recipient === requesterUserId) {
|
||||
return 'chatSelf';
|
||||
return 'chatSelf'; //not allowed
|
||||
} else {
|
||||
return 'chatPrivate';
|
||||
}
|
||||
@ -35,6 +39,7 @@ Meteor.methods({
|
||||
}
|
||||
},
|
||||
deletePrivateChatMessages(userId, contact_id) {
|
||||
// if authorized pass through
|
||||
let contact, requester;
|
||||
requester = Meteor.Users.findOne({
|
||||
userId: userId
|
||||
@ -46,8 +51,13 @@ Meteor.methods({
|
||||
}
|
||||
});
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// Private methods on server
|
||||
// --------------------------------------------------------------------------------------------
|
||||
this.addChatToCollection = function(meetingId, messageObject) {
|
||||
let id;
|
||||
// manually convert time from 1.408645053653E12 to 1408645053653 if necessary
|
||||
// (this is the time_from that the Flash client outputs)
|
||||
messageObject.from_time = messageObject.from_time.toString().split('.').join("").split("E")[0];
|
||||
if((messageObject.from_userid != null) && (messageObject.to_userid != null)) {
|
||||
messageObject.message = translateFlashToHTML5(messageObject.message);
|
||||
@ -81,6 +91,7 @@ this.addChatToCollection = function(meetingId, messageObject) {
|
||||
}
|
||||
};
|
||||
|
||||
// called on server start and meeting end
|
||||
this.clearChatCollection = function(meetingId) {
|
||||
if(meetingId != null) {
|
||||
return Meteor.Chat.remove({
|
||||
@ -91,6 +102,11 @@ this.clearChatCollection = function(meetingId) {
|
||||
}
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// end Private methods on server
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
// translate '\n' newline character and '\r' carriage returns to '<br/>' breakline character for Flash
|
||||
this.translateHTML5ToFlash = function(message) {
|
||||
let result;
|
||||
result = message;
|
||||
@ -99,6 +115,7 @@ this.translateHTML5ToFlash = function(message) {
|
||||
return result;
|
||||
};
|
||||
|
||||
// translate '<br/>' breakline character to '\r' carriage return character for HTML5
|
||||
this.translateFlashToHTML5 = function(message) {
|
||||
let result;
|
||||
result = message;
|
||||
|
@ -1,3 +1,6 @@
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// Private methods on server
|
||||
// --------------------------------------------------------------------------------------------
|
||||
this.initializeCursor = function(meetingId) {
|
||||
return Meteor.Cursor.upsert({
|
||||
meetingId: meetingId
|
||||
@ -9,7 +12,7 @@ this.initializeCursor = function(meetingId) {
|
||||
if(err) {
|
||||
return Meteor.log.error(`err upserting cursor for ${meetingId}`);
|
||||
} else {
|
||||
|
||||
// Meteor.log.info "ok upserting cursor for #{meetingId}"
|
||||
}
|
||||
});
|
||||
};
|
||||
@ -26,11 +29,12 @@ this.updateCursorLocation = function(meetingId, cursorObject) {
|
||||
if(err != null) {
|
||||
return Meteor.log.error(`_unsucc update of cursor for ${meetingId} ${JSON.stringify(cursorObject)} err=${JSON.stringify(err)}`);
|
||||
} else {
|
||||
|
||||
// Meteor.log.info "updated cursor for #{meetingId} #{JSON.stringify cursorObject}"
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// called on server start and meeting end
|
||||
this.clearCursorCollection = function(meetingId) {
|
||||
if(meetingId != null) {
|
||||
return Meteor.Cursor.remove({
|
||||
@ -44,3 +48,7 @@ this.clearCursorCollection = function(meetingId) {
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// end Private methods on server
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
@ -1,4 +1,10 @@
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// Private methods on server
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
this.addMeetingToCollection = function(meetingId, name, intendedForRecording, voiceConf, duration, callback) {
|
||||
//check if the meeting is already in the collection
|
||||
|
||||
Meteor.Meetings.upsert({
|
||||
meetingId: meetingId
|
||||
}, {
|
||||
@ -9,13 +15,14 @@ this.addMeetingToCollection = function(meetingId, name, intendedForRecording, vo
|
||||
voiceConf: voiceConf,
|
||||
duration: duration,
|
||||
roomLockSettings: {
|
||||
// by default the lock settings will be disabled on meeting create
|
||||
disablePrivateChat: false,
|
||||
disableCam: false,
|
||||
disableMic: false,
|
||||
lockOnJoin: Meteor.config.lockOnJoin,
|
||||
lockedLayout: false,
|
||||
disablePublicChat: false,
|
||||
lockOnJoinConfigurable: false
|
||||
lockOnJoinConfigurable: false // TODO
|
||||
}
|
||||
}
|
||||
}, (_this => {
|
||||
@ -33,6 +40,8 @@ this.addMeetingToCollection = function(meetingId, name, intendedForRecording, vo
|
||||
}
|
||||
};
|
||||
})(this));
|
||||
|
||||
// initialize the cursor in the meeting
|
||||
return initializeCursor(meetingId);
|
||||
};
|
||||
|
||||
@ -46,18 +55,33 @@ this.clearMeetingsCollection = function(meetingId) {
|
||||
}
|
||||
};
|
||||
|
||||
//clean up upon a meeting's end
|
||||
this.removeMeetingFromCollection = function(meetingId, callback) {
|
||||
let funct;
|
||||
if(Meteor.Meetings.findOne({
|
||||
meetingId: meetingId
|
||||
}) != null) {
|
||||
Meteor.log.info(`end of meeting ${meetingId}. Clear the meeting data from all collections`);
|
||||
|
||||
// delete all users in the meeting
|
||||
clearUsersCollection(meetingId);
|
||||
|
||||
// delete all slides in the meeting
|
||||
clearSlidesCollection(meetingId);
|
||||
|
||||
// delete all shapes in the meeting
|
||||
clearShapesCollection(meetingId);
|
||||
|
||||
// delete all presentations in the meeting
|
||||
clearPresentationsCollection(meetingId);
|
||||
|
||||
// delete all chat messages in the meeting
|
||||
clearChatCollection(meetingId);
|
||||
|
||||
// delete the meeting
|
||||
clearMeetingsCollection(meetingId);
|
||||
|
||||
// delete the cursor for the meeting
|
||||
clearCursorCollection(meetingId);
|
||||
return callback();
|
||||
} else {
|
||||
@ -68,3 +92,9 @@ this.removeMeetingFromCollection = function(meetingId, callback) {
|
||||
return funct(callback);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// end Private methods on server
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
@ -1,3 +1,6 @@
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// Public methods on server
|
||||
// --------------------------------------------------------------------------------------------
|
||||
Meteor.methods({
|
||||
publishVoteMessage(meetingId, pollAnswerId, requesterUserId, requesterToken) {
|
||||
let _poll_id, eventName, message, result;
|
||||
@ -44,20 +47,29 @@ Meteor.methods({
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// Private methods on server
|
||||
// --------------------------------------------------------------------------------------------
|
||||
this.addPollToCollection = function(poll, requester_id, users, meetingId) {
|
||||
let _users, answer, entry, i, j, len, len1, ref, user;
|
||||
//copying all the userids into an array
|
||||
_users = [];
|
||||
for (i = 0, len = users.length; i < len; i++) {
|
||||
user = users[i];
|
||||
_users.push(user.user.userid);
|
||||
}
|
||||
//adding the initial number of votes for each answer
|
||||
ref = poll.answers;
|
||||
for (j = 0, len1 = ref.length; j < len1; j++) {
|
||||
answer = ref[j];
|
||||
answer.num_votes = 0;
|
||||
}
|
||||
//adding the initial number of responders and respondents to the poll, which will be displayed for presenter (in HTML5 client) when he starts the poll
|
||||
poll.num_responders = -1;
|
||||
poll.num_respondents = -1;
|
||||
|
||||
//adding all together and inserting into the Polls collection
|
||||
entry = {
|
||||
poll_info: {
|
||||
"meetingId": meetingId,
|
||||
|
@ -59,8 +59,12 @@ Meteor.methods({
|
||||
}
|
||||
});
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// Private methods on server
|
||||
// --------------------------------------------------------------------------------------------
|
||||
this.addPresentationToCollection = function(meetingId, presentationObject) {
|
||||
let entry, id;
|
||||
//check if the presentation is already in the collection
|
||||
if(Meteor.Presentations.findOne({
|
||||
meetingId: meetingId,
|
||||
'presentation.id': presentationObject.id
|
||||
@ -74,6 +78,7 @@ this.addPresentationToCollection = function(meetingId, presentationObject) {
|
||||
}
|
||||
};
|
||||
return id = Meteor.Presentations.insert(entry);
|
||||
//Meteor.log.info "presentation added id =[#{id}]:#{presentationObject.id} in #{meetingId}. Presentations.size is now #{Meteor.Presentations.find({meetingId: meetingId}).count()}"
|
||||
}
|
||||
};
|
||||
|
||||
@ -97,6 +102,7 @@ this.removePresentationFromCollection = function(meetingId, presentationId) {
|
||||
}
|
||||
};
|
||||
|
||||
// called on server start and meeting end
|
||||
this.clearPresentationsCollection = function(meetingId) {
|
||||
if(meetingId != null) {
|
||||
return Meteor.Presentations.remove({
|
||||
@ -106,3 +112,7 @@ this.clearPresentationsCollection = function(meetingId) {
|
||||
return Meteor.Presentations.remove({}, Meteor.log.info("cleared Presentations Collection (all meetings)!"));
|
||||
}
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// end Private methods on server
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
@ -1,3 +1,6 @@
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// Private methods on server
|
||||
// --------------------------------------------------------------------------------------------
|
||||
this.addShapeToCollection = function(meetingId, whiteboardId, shapeObject) {
|
||||
let entry, id, removeTempTextShape;
|
||||
if((shapeObject != null ? shapeObject.shape_type : void 0) === "text") {
|
||||
@ -24,19 +27,25 @@ this.addShapeToCollection = function(meetingId, whiteboardId, shapeObject) {
|
||||
}
|
||||
};
|
||||
if(shapeObject.status === "textEdited" || shapeObject.status === "textPublished") {
|
||||
// only keep the final version of the text shape
|
||||
removeTempTextShape = function(callback) {
|
||||
Meteor.Shapes.remove({
|
||||
'shape.id': shapeObject.shape.id
|
||||
});
|
||||
// for s in Meteor.Shapes.find({'shape.id':shapeObject.shape.id}).fetch()
|
||||
// Meteor.log.info "there is this shape: #{s.shape.text}"
|
||||
return callback();
|
||||
};
|
||||
return removeTempTextShape(() => {
|
||||
// display as the prestenter is typing
|
||||
let id;
|
||||
id = Meteor.Shapes.insert(entry);
|
||||
return Meteor.log.info(`${shapeObject.status} substituting the temp shapes with the newer one`);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// the mouse button was released - the drawing is complete
|
||||
// TODO: pencil messages currently don't send draw_end and are labeled all as DRAW_START
|
||||
if((shapeObject != null ? shapeObject.status : void 0) === "DRAW_END" || ((shapeObject != null ? shapeObject.status : void 0) === "DRAW_START" && (shapeObject != null ? shapeObject.shape_type : void 0) === "pencil")) {
|
||||
entry = {
|
||||
meetingId: meetingId,
|
||||
@ -78,6 +87,8 @@ this.removeAllShapesFromSlide = function(meetingId, whiteboardId) {
|
||||
whiteboardId: whiteboardId
|
||||
}, () => {
|
||||
Meteor.log.info("clearing all shapes from slide");
|
||||
|
||||
// After shapes are cleared, wait 1 second and set cleaning off
|
||||
return Meteor.setTimeout(() => {
|
||||
return Meteor.WhiteboardCleanStatus.update({
|
||||
meetingId: meetingId
|
||||
@ -108,6 +119,7 @@ whiteboardId: whiteboardId
|
||||
}
|
||||
};
|
||||
|
||||
// called on server start and meeting end
|
||||
this.clearShapesCollection = function(meetingId) {
|
||||
if(meetingId != null) {
|
||||
return Meteor.Shapes.remove({
|
||||
@ -135,3 +147,7 @@ this.clearShapesCollection = function(meetingId) {
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// end Private methods on server
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
@ -1,6 +1,10 @@
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// Private methods on server
|
||||
// --------------------------------------------------------------------------------------------
|
||||
this.displayThisSlide = function(meetingId, newSlideId, slideObject) {
|
||||
let presentationId;
|
||||
presentationId = newSlideId.split("/")[0];
|
||||
presentationId = newSlideId.split("/")[0]; // grab the presentationId part of the slideId
|
||||
// change current to false for the old slide
|
||||
Meteor.Slides.update({
|
||||
presentationId: presentationId,
|
||||
"slide.current": true
|
||||
@ -9,7 +13,10 @@ this.displayThisSlide = function(meetingId, newSlideId, slideObject) {
|
||||
"slide.current": false
|
||||
}
|
||||
});
|
||||
// for the new slide: remove the version which came with presentation_shared_message from the Collection
|
||||
// to avoid using old data (this message contains everything we need for the new slide)
|
||||
removeSlideFromCollection(meetingId, newSlideId);
|
||||
// add the new slide to the collection
|
||||
return addSlideToCollection(meetingId, presentationId, slideObject);
|
||||
};
|
||||
|
||||
@ -37,6 +44,7 @@ this.addSlideToCollection = function(meetingId, presentationId, slideObject) {
|
||||
}
|
||||
};
|
||||
return id = Meteor.Slides.insert(entry);
|
||||
//Meteor.log.info "added slide id =[#{id}]:#{slideObject.id} in #{meetingId}. Now there are #{Meteor.Slides.find({meetingId: meetingId}).count()} slides in the meeting"
|
||||
}
|
||||
};
|
||||
|
||||
@ -57,6 +65,7 @@ this.removeSlideFromCollection = function(meetingId, slideId) {
|
||||
}
|
||||
};
|
||||
|
||||
// called on server start and meeting end
|
||||
this.clearSlidesCollection = function(meetingId) {
|
||||
if(meetingId != null) {
|
||||
return Meteor.Slides.remove({
|
||||
@ -66,3 +75,7 @@ this.clearSlidesCollection = function(meetingId) {
|
||||
return Meteor.Slides.remove({}, Meteor.log.info("cleared Slides Collection (all meetings)!"));
|
||||
}
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// end Private methods on server
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
@ -1,4 +1,15 @@
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// Public methods on server
|
||||
// All these method must first authenticate the user before it calls the private function counterpart below
|
||||
// which sends the request to bbbApps. If the method is modifying the media the current user is sharing,
|
||||
// you should perform the request before sending the request to bbbApps. This allows the user request to be performed
|
||||
// immediately, since they do not require permission for things such as muting themsevles.
|
||||
// --------------------------------------------------------------------------------------------
|
||||
Meteor.methods({
|
||||
// meetingId: the meetingId of the meeting the user is in
|
||||
// toSetUserId: the userId of the user joining
|
||||
// requesterUserId: the userId of the requester
|
||||
// requesterToken: the authToken of the requester
|
||||
listenOnlyRequestToggle(meetingId, userId, authToken, isJoining) {
|
||||
let message, ref, ref1, username, voiceConf;
|
||||
voiceConf = (ref = Meteor.Meetings.findOne({
|
||||
@ -46,6 +57,11 @@ Meteor.methods({
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// meetingId: the meetingId of the meeting the user[s] is in
|
||||
// toMuteUserId: the userId of the user to be muted
|
||||
// requesterUserId: the userId of the requester
|
||||
// requesterToken: the authToken of the requester
|
||||
muteUser(meetingId, toMuteUserId, requesterUserId, requesterToken) {
|
||||
let action, message;
|
||||
action = function() {
|
||||
@ -78,6 +94,11 @@ Meteor.methods({
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// meetingId: the meetingId of the meeting the user[s] is in
|
||||
// toMuteUserId: the userId of the user to be unmuted
|
||||
// requesterUserId: the userId of the requester
|
||||
// requesterToken: the authToken of the requester
|
||||
unmuteUser(meetingId, toMuteUserId, requesterUserId, requesterToken) {
|
||||
let action, message;
|
||||
action = function() {
|
||||
@ -125,15 +146,26 @@ Meteor.methods({
|
||||
version: "0.0.1"
|
||||
}
|
||||
};
|
||||
|
||||
// publish to pubsub
|
||||
publish(Meteor.config.redis.channels.toBBBApps.users, message);
|
||||
}
|
||||
},
|
||||
|
||||
// meetingId: the meeting where the user is
|
||||
// userId: the userid of the user logging out
|
||||
// authToken: the authToken of the user
|
||||
userLogout(meetingId, userId, authToken) {
|
||||
if(isAllowedTo('logoutSelf', meetingId, userId, authToken)) {
|
||||
Meteor.log.info(`a user is logging out from ${meetingId}:${userId}`);
|
||||
return requestUserLeaving(meetingId, userId);
|
||||
}
|
||||
},
|
||||
|
||||
//meetingId: the meeting where the user is
|
||||
//toKickUserId: the userid of the user to kick
|
||||
//requesterUserId: the userid of the user that wants to kick
|
||||
//authToken: the authToken of the user that wants to kick
|
||||
kickUser(meetingId, toKickUserId, requesterUserId, authToken) {
|
||||
let message;
|
||||
if(isAllowedTo('kickUser', meetingId, requesterUserId, authToken)) {
|
||||
@ -150,6 +182,12 @@ Meteor.methods({
|
||||
return publish(Meteor.config.redis.channels.toBBBApps.users, message);
|
||||
}
|
||||
},
|
||||
|
||||
//meetingId: the meeting where the user is
|
||||
//newPresenterId: the userid of the new presenter
|
||||
//requesterSetPresenter: the userid of the user that wants to change the presenter
|
||||
//newPresenterName: user name of the new presenter
|
||||
//authToken: the authToken of the user that wants to kick
|
||||
setUserPresenter(
|
||||
meetingId,
|
||||
newPresenterId,
|
||||
@ -174,7 +212,18 @@ Meteor.methods({
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// Private methods on server
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
// Only callable from server
|
||||
// Received information from BBB-Apps that a user left
|
||||
// Need to update the collection
|
||||
// params: meetingid, userid as defined in BBB-Apps
|
||||
// callback
|
||||
this.markUserOffline = function(meetingId, userId, callback) {
|
||||
// mark the user as offline. remove from the collection on meeting_end #TODO
|
||||
let user;
|
||||
user = Meteor.Users.findOne({
|
||||
meetingId: meetingId,
|
||||
@ -227,6 +276,9 @@ this.markUserOffline = function(meetingId, userId, callback) {
|
||||
}
|
||||
};
|
||||
|
||||
// Corresponds to a valid action on the HTML clientside
|
||||
// After authorization, publish a user_leaving_request in redis
|
||||
// params: meetingid, userid as defined in BBB-App
|
||||
this.requestUserLeaving = function(meetingId, userId) {
|
||||
let listenOnlyMessage, message, ref, userObject, voiceConf;
|
||||
userObject = Meteor.Users.findOne({
|
||||
@ -237,6 +289,8 @@ this.requestUserLeaving = function(meetingId, userId) {
|
||||
meetingId: meetingId
|
||||
})) != null ? ref.voiceConf : void 0;
|
||||
if((userObject != null) && (voiceConf != null) && (userId != null) && (meetingId != null)) {
|
||||
|
||||
// end listenOnly audio for the departing user
|
||||
if(userObject.user.listenOnly) {
|
||||
listenOnlyMessage = {
|
||||
payload: {
|
||||
@ -252,6 +306,8 @@ this.requestUserLeaving = function(meetingId, userId) {
|
||||
};
|
||||
publish(Meteor.config.redis.channels.toBBBApps.meeting, listenOnlyMessage);
|
||||
}
|
||||
|
||||
// remove user from meeting
|
||||
message = {
|
||||
payload: {
|
||||
meeting_id: meetingId,
|
||||
@ -269,6 +325,7 @@ this.requestUserLeaving = function(meetingId, userId) {
|
||||
}
|
||||
};
|
||||
|
||||
//update a voiceUser - a helper method
|
||||
this.updateVoiceUser = function(meetingId, voiceUserObject, callback) {
|
||||
let u;
|
||||
u = Meteor.Users.findOne({
|
||||
@ -289,7 +346,7 @@ this.updateVoiceUser = function(meetingId, voiceUserObject, callback) {
|
||||
}
|
||||
return callback();
|
||||
});
|
||||
}
|
||||
} // talking
|
||||
if(voiceUserObject.joined != null) {
|
||||
Meteor.Users.update({
|
||||
meetingId: meetingId,
|
||||
@ -306,7 +363,7 @@ this.updateVoiceUser = function(meetingId, voiceUserObject, callback) {
|
||||
}
|
||||
return callback();
|
||||
});
|
||||
}
|
||||
} // joined
|
||||
if(voiceUserObject.locked != null) {
|
||||
Meteor.Users.update({
|
||||
meetingId: meetingId,
|
||||
@ -321,7 +378,7 @@ this.updateVoiceUser = function(meetingId, voiceUserObject, callback) {
|
||||
}
|
||||
return callback();
|
||||
});
|
||||
}
|
||||
} // locked
|
||||
if(voiceUserObject.muted != null) {
|
||||
Meteor.Users.update({
|
||||
meetingId: meetingId,
|
||||
@ -336,7 +393,7 @@ this.updateVoiceUser = function(meetingId, voiceUserObject, callback) {
|
||||
}
|
||||
return callback();
|
||||
});
|
||||
}
|
||||
} // muted
|
||||
if(voiceUserObject.listen_only != null) {
|
||||
return Meteor.Users.update({
|
||||
meetingId: meetingId,
|
||||
@ -351,7 +408,7 @@ this.updateVoiceUser = function(meetingId, voiceUserObject, callback) {
|
||||
}
|
||||
return callback();
|
||||
});
|
||||
}
|
||||
} // listenOnly
|
||||
} else {
|
||||
Meteor.log.error("ERROR! did not find such voiceUser!");
|
||||
return callback();
|
||||
@ -365,6 +422,10 @@ this.userJoined = function(meetingId, user, callback) {
|
||||
userId: user.userid,
|
||||
meetingId: meetingId
|
||||
});
|
||||
// the collection already contains an entry for this user
|
||||
// because the user is reconnecting OR
|
||||
// in the case of an html5 client user we added a dummy user on
|
||||
// register_user_message (to save authToken)
|
||||
if((u != null) && (u.authToken != null)) {
|
||||
Meteor.Users.update({
|
||||
userId: user.userid,
|
||||
@ -385,7 +446,7 @@ this.userJoined = function(meetingId, user, callback) {
|
||||
extern_userid: user.extern_userid,
|
||||
locked: user.locked,
|
||||
time_of_joining: user.timeOfJoining,
|
||||
connection_status: "online",
|
||||
connection_status: "online", // TODO consider other default value
|
||||
voiceUser: {
|
||||
web_userid: user.voiceUser.web_userid,
|
||||
callernum: user.voiceUser.callernum,
|
||||
@ -416,6 +477,7 @@ this.userJoined = function(meetingId, user, callback) {
|
||||
meetingId: meetingId
|
||||
})) != null ? ref.meetingName : void 0);
|
||||
welcomeMessage = welcomeMessage + Meteor.config.defaultWelcomeMessageFooter;
|
||||
// add the welcome message if it's not there already OR update time_of_joining
|
||||
return Meteor.Chat.upsert({
|
||||
meetingId: meetingId,
|
||||
userId: userId,
|
||||
@ -439,8 +501,12 @@ this.userJoined = function(meetingId, user, callback) {
|
||||
} else {
|
||||
return Meteor.log.info(`_added/updated a system message in chat for user ${userId}`);
|
||||
}
|
||||
// note that we already called callback() when updating the user. Adding
|
||||
// the welcome message in the chat is not as vital and we can afford to
|
||||
// complete it when possible, without blocking the serial event messages processing
|
||||
});
|
||||
} else {
|
||||
// Meteor.log.info "NOTE: got user_joined_message #{user.name} #{user.userid}"
|
||||
return Meteor.Users.upsert({
|
||||
meetingId: meetingId,
|
||||
userId: userId
|
||||
@ -506,7 +572,7 @@ this.createDummyUser = function(meetingId, userId, authToken) {
|
||||
userId: userId,
|
||||
authToken: authToken,
|
||||
clientType: "HTML5",
|
||||
validated: false
|
||||
validated: false //will be validated on validate_auth_token_reply
|
||||
}, (err, id) => {
|
||||
return Meteor.log.info(`_added a dummy html5 user with: userId=[${userId}] Users.size is now ${Meteor.Users.find({
|
||||
meetingId: meetingId
|
||||
@ -515,7 +581,10 @@ this.createDummyUser = function(meetingId, userId, authToken) {
|
||||
}
|
||||
};
|
||||
|
||||
// when new lock settings including disableMic are set,
|
||||
// all viewers that are in the audio bridge with a mic should be muted and locked
|
||||
this.handleLockingMic = function(meetingId, newSettings) {
|
||||
// send mute requests for the viewer users joined with mic
|
||||
let i, len, ref, ref1, results, u;
|
||||
ref1 = (ref = Meteor.Users.find({
|
||||
meetingId: meetingId,
|
||||
@ -528,11 +597,13 @@ this.handleLockingMic = function(meetingId, newSettings) {
|
||||
results = [];
|
||||
for(i = 0, len = ref1.length; i < len; i++) {
|
||||
u = ref1[i];
|
||||
results.push(Meteor.call('muteUser', meetingId, u.userId, u.userId, u.authToken, true));
|
||||
// Meteor.log.info u.user.name #
|
||||
results.push(Meteor.call('muteUser', meetingId, u.userId, u.userId, u.authToken, true)); //true for muted
|
||||
}
|
||||
return results;
|
||||
};
|
||||
|
||||
// change the locked status of a user (lock settings)
|
||||
this.setUserLockedStatus = function(meetingId, userId, isLocked) {
|
||||
let u;
|
||||
u = Meteor.Users.findOne({
|
||||
@ -554,14 +625,17 @@ this.setUserLockedStatus = function(meetingId, userId, isLocked) {
|
||||
return Meteor.log.info(`_setting user locked status for userid:[${userId}] from [${meetingId}] locked=${isLocked}`);
|
||||
}
|
||||
});
|
||||
// if the user is sharing audio, he should be muted upon locking involving disableMic
|
||||
if(u.user.role === 'VIEWER' && !u.user.listenOnly && u.user.voiceUser.joined && !u.user.voiceUser.muted && isLocked) {
|
||||
return Meteor.call('muteUser', meetingId, u.userId, u.userId, u.authToken, true);
|
||||
return Meteor.call('muteUser', meetingId, u.userId, u.userId, u.authToken, true); //true for muted
|
||||
}
|
||||
} else {
|
||||
return Meteor.log.error(`(unsuccessful-no such user) setting user locked status for userid:[${userId}] from [${meetingId}] locked=${isLocked}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// called on server start and on meeting end
|
||||
this.clearUsersCollection = function(meetingId) {
|
||||
if(meetingId != null) {
|
||||
return Meteor.Users.remove({
|
||||
|
@ -1,3 +1,5 @@
|
||||
// Publish only the online users that are in the particular meetingId
|
||||
// On the client side we pass the meetingId parameter
|
||||
Meteor.publish('users', function(meetingId, userid, authToken) {
|
||||
let ref, ref1, u, username;
|
||||
Meteor.log.info(`attempt publishing users for ${meetingId}, ${userid}, ${authToken}`);
|
||||
@ -10,6 +12,8 @@ Meteor.publish('users', function(meetingId, userid, authToken) {
|
||||
if(isAllowedTo('subscribeUsers', meetingId, userid, authToken)) {
|
||||
Meteor.log.info(`${userid} was allowed to subscribe to 'users'`);
|
||||
username = (u != null ? (ref = u.user) != null ? ref.name : void 0 : void 0) || "UNKNOWN";
|
||||
|
||||
// offline -> online
|
||||
if(((ref1 = u.user) != null ? ref1.connection_status : void 0) !== 'online') {
|
||||
Meteor.call("validateAuthToken", meetingId, userid, authToken);
|
||||
}
|
||||
@ -37,6 +41,8 @@ Meteor.publish('users', function(meetingId, userid, authToken) {
|
||||
return requestUserLeaving(meetingId, userid);
|
||||
};
|
||||
})(this)));
|
||||
|
||||
//publish the users which are not offline
|
||||
return Meteor.Users.find({
|
||||
meetingId: meetingId,
|
||||
'user.connection_status': {
|
||||
@ -51,7 +57,7 @@ Meteor.publish('users', function(meetingId, userid, authToken) {
|
||||
Meteor.log.warn("was not authorized to subscribe to 'users'");
|
||||
return this.error(new Meteor.Error(402, "The user was not authorized to subscribe to 'users'"));
|
||||
}
|
||||
} else {
|
||||
} else { //subscribing before the user was added to the collection
|
||||
Meteor.call("validateAuthToken", meetingId, userid, authToken);
|
||||
Meteor.log.error(`there was no such user ${userid} in ${meetingId}`);
|
||||
return Meteor.Users.find({
|
||||
@ -90,7 +96,9 @@ Meteor.publish('chat', function(meetingId, userid, authToken) {
|
||||
});
|
||||
|
||||
Meteor.publish('bbb_poll', function(meetingId, userid, authToken) {
|
||||
//checking if it is allowed to see Poll Collection in general
|
||||
if(isAllowedTo('subscribePoll', meetingId, userid, authToken)) {
|
||||
//checking if it is allowed to see a number of votes (presenter only)
|
||||
if(isAllowedTo('subscribeAnswers', meetingId, userid, authToken)) {
|
||||
Meteor.log.info("publishing Poll for presenter: " + meetingId + " " + userid + " " + authToken);
|
||||
return Meteor.Polls.find({
|
||||
|
@ -1,6 +1,7 @@
|
||||
const bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }, indexOf = [].indexOf || function(item) { for (let i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
|
||||
|
||||
Meteor.methods({
|
||||
// Construct and send a message to bbb-web to validate the user
|
||||
validateAuthToken(meetingId, userId, authToken) {
|
||||
let message;
|
||||
Meteor.log.info("sending a validate_auth_token with", {
|
||||
@ -47,11 +48,13 @@ Meteor.RedisPubSub = (function() {
|
||||
_onSubscribe(channel, count) {
|
||||
let message;
|
||||
Meteor.log.info(`Subscribed to ${channel}`);
|
||||
|
||||
//grab data about all active meetings on the server
|
||||
message = {
|
||||
"header": {
|
||||
"name": "get_all_meetings_request"
|
||||
},
|
||||
"payload": {}
|
||||
"payload": {} // I need this, otherwise bbb-apps won't recognize the message
|
||||
};
|
||||
return publish(Meteor.config.redis.channels.toBBBApps.meeting, message);
|
||||
}
|
||||
@ -75,6 +78,11 @@ Meteor.RedisPubSub = (function() {
|
||||
return RedisPubSub;
|
||||
})();
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// Private methods on server
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
// message should be an object
|
||||
this.publish = function(channel, message) {
|
||||
Meteor.log.info(`redis outgoing message ${message.header.name}`, {
|
||||
channel: channel,
|
||||
|
@ -2,6 +2,8 @@ const indexOf = [].indexOf || function(item) { for(let i = 0, l = this.length; i
|
||||
|
||||
Meteor.startup(() => {
|
||||
Meteor.log.info("server start");
|
||||
|
||||
//remove all data
|
||||
Meteor.WhiteboardCleanStatus.remove({});
|
||||
clearUsersCollection();
|
||||
clearChatCollection();
|
||||
@ -11,10 +13,15 @@ Meteor.startup(() => {
|
||||
clearPresentationsCollection();
|
||||
clearPollCollection();
|
||||
clearCursorCollection();
|
||||
|
||||
// create create a PubSub connection, start listening
|
||||
Meteor.redisPubSub = new Meteor.RedisPubSub(function() {
|
||||
return Meteor.log.info("created pubsub");
|
||||
});
|
||||
Meteor.myQueue = new PowerQueue({});
|
||||
Meteor.myQueue = new PowerQueue({
|
||||
// autoStart:true
|
||||
// isPaused: true
|
||||
});
|
||||
Meteor.myQueue.taskHandler = function(data, next, failures) {
|
||||
let eventName, ref;
|
||||
eventName = (ref = JSON.parse(data.jsonMsg)) != null ? ref.header.name : void 0;
|
||||
@ -36,11 +43,45 @@ Meteor.startup(() => {
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// To ensure that we process the redis json event messages serially we use a
|
||||
// callback. This callback is to be called when the Meteor collection is
|
||||
// updated with the information coming in the payload of the json message. The
|
||||
// callback signalizes to the queue that we are done processing the current
|
||||
// message in the queue and are ready to move on to the next one. If we do not
|
||||
// use the callback mechanism we may encounter a race condition situation
|
||||
// due to not following the order of events coming through the redis pubsub.
|
||||
// for example: a user_left event reaching the collection before a user_joined
|
||||
// for the same user.
|
||||
return this.handleRedisMessage = function(data, callback) {
|
||||
let chatMessage, currentlyBeingRecorded, cursor, dbUser, duration, emojiStatus, eventName, heightRatio, i, intendedForRecording, isLocked, j, k, l, len, len1, len2, len3, len4, listOfMeetings, m, meetingId, meetingName, message, messageObject, newPresenterId, newSettings, newSlide, notLoggedEventTypes, oldSettings, page, pollObj, poll_id, presentation, presentationId, processMeeting, processUser, ref, ref1, ref10, ref11, ref12, ref13, ref14, ref15, ref16, ref17, ref18, ref19, ref2, ref20, ref21, ref3, ref4, ref5, ref6, ref7, ref8, ref9, replyTo, requesterId, set_emoji_time, shape, shapeId, slide, slideId, status, user, userId, userObj, users, validStatus, voiceConf, voiceUserObj, whiteboardId, widthRatio, xOffset, yOffset;
|
||||
message = JSON.parse(data.jsonMsg);
|
||||
// correlationId = message.payload?.reply_to or message.header?.reply_to
|
||||
meetingId = (ref = message.payload) != null ? ref.meeting_id : void 0;
|
||||
notLoggedEventTypes = ["keep_alive_reply", "page_resized_message", "presentation_page_resized_message", "presentation_cursor_updated_message", "get_presentation_info_reply", "get_chat_history_reply", "get_whiteboard_shapes_reply", "presentation_shared_message", "presentation_conversion_done_message", "presentation_conversion_progress_message", "presentation_page_generated_message", "BbbPubSubPongMessage", "bbb_apps_is_alive_message", "user_voice_talking_message", "meeting_state_message", "get_recording_status_reply"];
|
||||
|
||||
// Avoid cluttering the log with json messages carrying little or repetitive
|
||||
// information. Comment out a message type in the array to be able to see it
|
||||
// in the log upon restarting of the Meteor process.
|
||||
notLoggedEventTypes = [
|
||||
"keep_alive_reply",
|
||||
"page_resized_message",
|
||||
"presentation_page_resized_message",
|
||||
"presentation_cursor_updated_message",
|
||||
"get_presentation_info_reply",
|
||||
//"get_users_reply"
|
||||
"get_chat_history_reply",
|
||||
//"get_all_meetings_reply"
|
||||
"get_whiteboard_shapes_reply",
|
||||
"presentation_shared_message",
|
||||
"presentation_conversion_done_message",
|
||||
"presentation_conversion_progress_message",
|
||||
"presentation_page_generated_message",
|
||||
//"presentation_page_changed_message"
|
||||
"BbbPubSubPongMessage",
|
||||
"bbb_apps_is_alive_message",
|
||||
"user_voice_talking_message",
|
||||
"meeting_state_message",
|
||||
"get_recording_status_reply"];
|
||||
eventName = message.header.name;
|
||||
meetingId = (ref1 = message.payload) != null ? ref1.meeting_id : void 0;
|
||||
if(!(((message != null ? message.header : void 0) != null) && (message.payload != null))) {
|
||||
@ -52,13 +93,18 @@ Meteor.startup(() => {
|
||||
message: data.jsonMsg
|
||||
});
|
||||
}
|
||||
|
||||
// we currently disregard the pattern and channel
|
||||
if(((message != null ? message.header : void 0) != null) && (message.payload != null)) {
|
||||
if(eventName === 'meeting_created_message') {
|
||||
// Meteor.log.error JSON.stringify message
|
||||
meetingName = message.payload.name;
|
||||
intendedForRecording = message.payload.recorded;
|
||||
voiceConf = message.payload.voice_conf;
|
||||
duration = message.payload.duration;
|
||||
return addMeetingToCollection(meetingId, meetingName, intendedForRecording, voiceConf, duration, callback);
|
||||
|
||||
// handle voice events
|
||||
} else if ((message.payload.user != null) && (eventName === 'user_left_voice_message' || eventName === 'user_joined_voice_message' || eventName === 'user_voice_talking_message' || eventName === 'user_voice_muted_message')) {
|
||||
voiceUserObj = {
|
||||
'web_userid': message.payload.user.voiceUser.web_userid,
|
||||
@ -79,13 +125,16 @@ Meteor.startup(() => {
|
||||
Meteor.log.info("Let's store some data for the running meetings so that when an HTML5 client joins everything is ready!");
|
||||
Meteor.log.info(JSON.stringify(message));
|
||||
listOfMeetings = message.payload.meetings;
|
||||
|
||||
// Processing the meetings recursively with a callback to notify us,
|
||||
// ensuring that we update the meeting collection serially
|
||||
processMeeting = function() {
|
||||
let meeting;
|
||||
meeting = listOfMeetings.pop();
|
||||
if(meeting != null) {
|
||||
return addMeetingToCollection(meeting.meetingID, meeting.meetingName, meeting.recorded, meeting.voiceBridge, meeting.duration, processMeeting);
|
||||
} else {
|
||||
return callback();
|
||||
return callback(); // all meeting arrays (if any) have been processed
|
||||
}
|
||||
};
|
||||
return processMeeting();
|
||||
@ -95,11 +144,16 @@ Meteor.startup(() => {
|
||||
userId: userObj.userid,
|
||||
meetingId: message.payload.meeting_id
|
||||
});
|
||||
|
||||
// On attempting reconnection of Flash clients (in voiceBridge) we receive
|
||||
// an extra user_joined_message. Ignore it as it will add an extra user
|
||||
// in the user list, creating discrepancy with the list in the Flash client
|
||||
if((dbUser != null ? (ref3 = dbUser.user) != null ? ref3.connection_status : void 0 : void 0) === "offline" && ((ref4 = message.payload.user) != null ? ref4.phone_user : void 0)) {
|
||||
Meteor.log.error("offline AND phone user");
|
||||
return callback();
|
||||
return callback(); //return without joining the user
|
||||
} else {
|
||||
if((dbUser != null ? dbUser.clientType : void 0) === "HTML5") {
|
||||
if((dbUser != null ? dbUser.clientType : void 0) === "HTML5") { // typically html5 users will be in
|
||||
// the db [as a dummy user] before the joining message
|
||||
status = dbUser != null ? dbUser.validated : void 0;
|
||||
Meteor.log.info(`in user_joined_message the validStatus of the user was ${status}`);
|
||||
userObj.timeOfJoining = message.header.current_time;
|
||||
@ -108,8 +162,17 @@ Meteor.startup(() => {
|
||||
return userJoined(meetingId, userObj, callback);
|
||||
}
|
||||
}
|
||||
|
||||
// only process if requester is nodeJSapp means only process in the case when
|
||||
// we explicitly request the users
|
||||
} else if(eventName === 'get_users_reply' && message.payload.requester_id === 'nodeJSapp') {
|
||||
users = message.payload.users;
|
||||
|
||||
//TODO make the serialization be split per meeting. This will allow us to
|
||||
// use N threads vs 1 and we'll take advantage of Mongo's concurrency tricks
|
||||
|
||||
// Processing the users recursively with a callback to notify us,
|
||||
// ensuring that we update the users collection serially
|
||||
processUser = function() {
|
||||
let user;
|
||||
user = users.pop();
|
||||
@ -120,10 +183,11 @@ Meteor.startup(() => {
|
||||
user.set_emoji_time = new Date();
|
||||
return userJoined(meetingId, user, processUser);
|
||||
} else {
|
||||
// console.error("this is not supposed to happen")
|
||||
return userJoined(meetingId, user, processUser);
|
||||
}
|
||||
} else {
|
||||
return callback();
|
||||
return callback(); // all meeting arrays (if any) have been processed
|
||||
}
|
||||
};
|
||||
return processUser();
|
||||
@ -134,7 +198,10 @@ Meteor.startup(() => {
|
||||
meetingId: meetingId
|
||||
});
|
||||
validStatus = message.payload.valid;
|
||||
|
||||
// if the user already exists in the db
|
||||
if((user != null ? user.clientType : void 0) === "HTML5") {
|
||||
//if the html5 client user was validated successfully, add a flag
|
||||
return Meteor.Users.update({
|
||||
userId: userId,
|
||||
meetingId: message.payload.meeting_id
|
||||
@ -168,11 +235,14 @@ Meteor.startup(() => {
|
||||
if((userId != null) && (meetingId != null)) {
|
||||
return markUserOffline(meetingId, userId, callback);
|
||||
} else {
|
||||
return callback();
|
||||
return callback(); //TODO check how to get these cases out and reuse code
|
||||
}
|
||||
|
||||
// for now not handling this serially #TODO
|
||||
} else if(eventName === 'presenter_assigned_message') {
|
||||
newPresenterId = message.payload.new_presenter_id;
|
||||
if(newPresenterId != null) {
|
||||
// reset the previous presenter
|
||||
Meteor.Users.update({
|
||||
"user.presenter": true,
|
||||
meetingId: meetingId
|
||||
@ -183,6 +253,7 @@ Meteor.startup(() => {
|
||||
}, (err, numUpdated) => {
|
||||
return Meteor.log.info(` Updating old presenter numUpdated=${numUpdated}, err=${err}`);
|
||||
});
|
||||
// set the new presenter
|
||||
Meteor.Users.update({
|
||||
"user.userid": newPresenterId,
|
||||
meetingId: meetingId
|
||||
@ -195,6 +266,8 @@ Meteor.startup(() => {
|
||||
});
|
||||
}
|
||||
return callback();
|
||||
|
||||
// for now not handling this serially #TODO
|
||||
} else if(eventName === 'user_emoji_status_message') {
|
||||
userId = message.payload.userid;
|
||||
meetingId = message.payload.meeting_id;
|
||||
@ -213,11 +286,15 @@ Meteor.startup(() => {
|
||||
});
|
||||
}
|
||||
return callback();
|
||||
|
||||
// for now not handling this serially #TODO
|
||||
} else if(eventName === 'user_locked_message' || eventName === 'user_unlocked_message') {
|
||||
userId = message.payload.userid;
|
||||
isLocked = message.payload.locked;
|
||||
setUserLockedStatus(meetingId, userId, isLocked);
|
||||
return callback();
|
||||
|
||||
// for now not handling this serially #TODO
|
||||
} else if(eventName === "meeting_ended_message" || eventName === "meeting_destroyed_event" || eventName === "end_and_kick_all_message" || eventName === "disconnect_all_users_message") {
|
||||
Meteor.log.info(`DESTROYING MEETING ${meetingId}`);
|
||||
return removeMeetingFromCollection(meetingId, callback);
|
||||
@ -232,6 +309,8 @@ Meteor.startup(() => {
|
||||
unless eventName is "disconnect_all_users_message"
|
||||
removeMeetingFromCollection meetingId
|
||||
*/
|
||||
|
||||
// for now not handling this serially #TODO
|
||||
} else if(eventName === "get_chat_history_reply" && message.payload.requester_id === "nodeJSapp") {
|
||||
if(Meteor.Meetings.findOne({
|
||||
MeetingId: message.payload.meeting_id
|
||||
@ -243,13 +322,19 @@ Meteor.startup(() => {
|
||||
}
|
||||
}
|
||||
return callback();
|
||||
|
||||
// for now not handling this serially #TODO
|
||||
} else if(eventName === "send_public_chat_message" || eventName === "send_private_chat_message") {
|
||||
messageObject = message.payload.message;
|
||||
// use current_time instead of message.from_time so that the chats from Flash and HTML5 have uniform times
|
||||
messageObject.from_time = message.header.current_time;
|
||||
addChatToCollection(meetingId, messageObject);
|
||||
return callback();
|
||||
|
||||
// for now not handling this serially #TODO
|
||||
} else if(eventName === "presentation_shared_message") {
|
||||
presentationId = (ref7 = message.payload.presentation) != null ? ref7.id : void 0;
|
||||
// change the currently displayed presentation to presentation.current = false
|
||||
Meteor.Presentations.update({
|
||||
"presentation.current": true,
|
||||
meetingId: meetingId
|
||||
@ -258,6 +343,8 @@ Meteor.startup(() => {
|
||||
"presentation.current": false
|
||||
}
|
||||
});
|
||||
|
||||
//update(if already present) entirely the presentation with the fresh data
|
||||
removePresentationFromCollection(meetingId, presentationId);
|
||||
addPresentationToCollection(meetingId, message.payload.presentation);
|
||||
ref9 = (ref8 = message.payload.presentation) != null ? ref8.pages : void 0;
|
||||
@ -273,6 +360,8 @@ Meteor.startup(() => {
|
||||
}
|
||||
}
|
||||
return callback();
|
||||
|
||||
// for now not handling this serially #TODO
|
||||
} else if(eventName === "get_presentation_info_reply" && message.payload.requester_id === "nodeJSapp") {
|
||||
ref11 = message.payload.presentations;
|
||||
for(k = 0, len2 = ref11.length; k < len2; k++) {
|
||||
@ -281,8 +370,14 @@ Meteor.startup(() => {
|
||||
ref12 = presentation.pages;
|
||||
for(l = 0, len3 = ref12.length; l < len3; l++) {
|
||||
page = ref12[l];
|
||||
|
||||
//add the slide to the collection
|
||||
addSlideToCollection(meetingId, presentation.id, page);
|
||||
whiteboardId = `${presentation.id}/${page.num}`;
|
||||
|
||||
//request for shapes
|
||||
whiteboardId = `${presentation.id}/${page.num}`; // d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1404411622872/1
|
||||
//Meteor.log.info "the whiteboard_id here is:" + whiteboardId
|
||||
|
||||
replyTo = `${meetingId}/nodeJSapp`;
|
||||
message = {
|
||||
"payload": {
|
||||
@ -304,16 +399,23 @@ Meteor.startup(() => {
|
||||
}
|
||||
}
|
||||
return callback();
|
||||
|
||||
// for now not handling this serially #TODO
|
||||
} else if(eventName === "presentation_page_changed_message") {
|
||||
newSlide = message.payload.page;
|
||||
displayThisSlide(meetingId, newSlide != null ? newSlide.id : void 0, newSlide);
|
||||
return callback();
|
||||
|
||||
// for now not handling this serially #TODO
|
||||
} else if(eventName === "presentation_removed_message") {
|
||||
presentationId = message.payload.presentation_id;
|
||||
meetingId = message.payload.meeting_id;
|
||||
removePresentationFromCollection(meetingId, presentationId);
|
||||
return callback();
|
||||
|
||||
// for now not handling this serially #TODO
|
||||
} else if(eventName === "get_whiteboard_shapes_reply" && message.payload.requester_id === "nodeJSapp") {
|
||||
// Create a whiteboard clean status or find one for the current meeting
|
||||
if(Meteor.WhiteboardCleanStatus.findOne({
|
||||
meetingId: meetingId
|
||||
}) == null) {
|
||||
@ -329,7 +431,11 @@ Meteor.startup(() => {
|
||||
addShapeToCollection(meetingId, whiteboardId, shape);
|
||||
}
|
||||
return callback();
|
||||
|
||||
// for now not handling this serially #TODO
|
||||
} else if(eventName === "send_whiteboard_shape_message") {
|
||||
//Meteor stringifies an array of JSONs (...shape.result) in this message
|
||||
//parsing the String and reassigning the value
|
||||
if(message.payload.shape.shape_type === "poll_result" && typeof message.payload.shape.shape.result === 'string') {
|
||||
message.payload.shape.shape.result = JSON.parse(message.payload.shape.shape.result);
|
||||
}
|
||||
@ -337,13 +443,19 @@ Meteor.startup(() => {
|
||||
whiteboardId = shape != null ? shape.wb_id : void 0;
|
||||
addShapeToCollection(meetingId, whiteboardId, shape);
|
||||
return callback();
|
||||
|
||||
// for now not handling this serially #TODO
|
||||
} else if(eventName === "presentation_cursor_updated_message") {
|
||||
cursor = {
|
||||
x: message.payload.x_percent,
|
||||
y: message.payload.y_percent
|
||||
};
|
||||
|
||||
// update the location of the cursor on the whiteboard
|
||||
updateCursorLocation(meetingId, cursor);
|
||||
return callback();
|
||||
|
||||
// for now not handling this serially #TODO
|
||||
} else if(eventName === "whiteboard_cleared_message") {
|
||||
whiteboardId = message.payload.whiteboard_id;
|
||||
Meteor.WhiteboardCleanStatus.update({
|
||||
@ -355,11 +467,15 @@ Meteor.startup(() => {
|
||||
});
|
||||
removeAllShapesFromSlide(meetingId, whiteboardId);
|
||||
return callback();
|
||||
|
||||
// for now not handling this serially #TODO
|
||||
} else if(eventName === "undo_whiteboard_request") {
|
||||
whiteboardId = message.payload.whiteboard_id;
|
||||
shapeId = message.payload.shape_id;
|
||||
removeShapeFromSlide(meetingId, whiteboardId, shapeId);
|
||||
return callback();
|
||||
|
||||
// for now not handling this serially #TODO
|
||||
} else if(eventName === "presentation_page_resized_message") {
|
||||
slideId = (ref14 = message.payload.page) != null ? ref14.id : void 0;
|
||||
heightRatio = (ref15 = message.payload.page) != null ? ref15.height_ratio : void 0;
|
||||
@ -379,6 +495,8 @@ Meteor.startup(() => {
|
||||
}
|
||||
});
|
||||
return callback();
|
||||
|
||||
// for now not handling this serially #TODO
|
||||
} else if(eventName === "recording_status_changed_message") {
|
||||
intendedForRecording = message.payload.recorded;
|
||||
currentlyBeingRecorded = message.payload.recording;
|
||||
@ -391,16 +509,26 @@ Meteor.startup(() => {
|
||||
}
|
||||
});
|
||||
return callback();
|
||||
|
||||
// --------------------------------------------------
|
||||
// lock settings ------------------------------------
|
||||
// for now not handling this serially #TODO
|
||||
} else if(eventName === "eject_voice_user_message") {
|
||||
return callback();
|
||||
|
||||
// for now not handling this serially #TODO
|
||||
} else if(eventName === "new_permission_settings") {
|
||||
oldSettings = (ref19 = Meteor.Meetings.findOne({
|
||||
meetingId: meetingId
|
||||
})) != null ? ref19.roomLockSettings : void 0;
|
||||
newSettings = (ref20 = message.payload) != null ? ref20.permissions : void 0;
|
||||
|
||||
// if the disableMic setting was turned on
|
||||
if(!(oldSettings != null ? oldSettings.disableMic : void 0) && newSettings.disableMic) {
|
||||
handleLockingMic(meetingId, newSettings);
|
||||
}
|
||||
|
||||
// substitute with the new lock settings
|
||||
Meteor.Meetings.update({
|
||||
meetingId: meetingId
|
||||
}, {
|
||||
@ -415,6 +543,8 @@ Meteor.startup(() => {
|
||||
}
|
||||
});
|
||||
return callback();
|
||||
|
||||
// for now not handling this serially #TODO
|
||||
} else if(eventName === "poll_started_message") {
|
||||
if((message.payload.meeting_id != null) && (message.payload.requester_id != null) && (message.payload.poll != null)) {
|
||||
if(Meteor.Meetings.findOne({
|
||||
@ -437,11 +567,15 @@ Meteor.startup(() => {
|
||||
}
|
||||
}
|
||||
return callback();
|
||||
|
||||
// for now not handling this serially #TODO
|
||||
} else if(eventName === "poll_stopped_message") {
|
||||
meetingId = message.payload.meeting_id;
|
||||
poll_id = message.payload.poll_id;
|
||||
clearPollCollection(meetingId, poll_id);
|
||||
return callback();
|
||||
|
||||
// for now not handling this serially #TODO
|
||||
} else if(eventName === "user_voted_poll_message") {
|
||||
if((((ref21 = message.payload) != null ? ref21.poll : void 0) != null) && (message.payload.meeting_id != null) && (message.payload.presenter_id != null)) {
|
||||
pollObj = message.payload.poll;
|
||||
@ -450,6 +584,8 @@ Meteor.startup(() => {
|
||||
updatePollCollection(pollObj, meetingId, requesterId);
|
||||
return callback();
|
||||
}
|
||||
|
||||
// for now not handling this serially #TODO
|
||||
} else if(eventName === "poll_show_result_message") {
|
||||
if((message.payload.poll.id != null) && (message.payload.meeting_id != null)) {
|
||||
poll_id = message.payload.poll.id;
|
||||
@ -457,7 +593,7 @@ Meteor.startup(() => {
|
||||
clearPollCollection(meetingId, poll_id);
|
||||
}
|
||||
return callback();
|
||||
} else {
|
||||
} else { // keep moving in the queue
|
||||
if(indexOf.call(notLoggedEventTypes, eventName) < 0) {
|
||||
Meteor.log.info(`WARNING!!! THE JSON MESSAGE WAS NOT OF TYPE SUPPORTED BY THIS APPLICATION
|
||||
${eventName} {JSON.stringify message}`);
|
||||
|
@ -2,37 +2,71 @@ let moderator, presenter, viewer;
|
||||
|
||||
presenter = {
|
||||
switchSlide: true,
|
||||
|
||||
//poll
|
||||
subscribePoll: true,
|
||||
subscribeAnswers: true
|
||||
};
|
||||
|
||||
// holds the values for whether the moderator user is allowed to perform an action (true)
|
||||
// or false if not allowed. Some actions have dynamic values depending on the current lock settings
|
||||
moderator = {
|
||||
// audio listen only
|
||||
joinListenOnly: true,
|
||||
leaveListenOnly: true,
|
||||
|
||||
// join audio with mic cannot be controlled on the server side as it is
|
||||
// a client side only functionality
|
||||
|
||||
// raising/lowering hand
|
||||
raiseOwnHand: true,
|
||||
lowerOwnHand: true,
|
||||
|
||||
// muting
|
||||
muteSelf: true,
|
||||
unmuteSelf: true,
|
||||
|
||||
logoutSelf: true,
|
||||
|
||||
//subscribing
|
||||
subscribeUsers: true,
|
||||
subscribeChat: true,
|
||||
|
||||
//chat
|
||||
chatPublic: true,
|
||||
chatPrivate: true,
|
||||
|
||||
//poll
|
||||
subscribePoll: true,
|
||||
subscribeAnswers: false,
|
||||
|
||||
//emojis
|
||||
setEmojiStatus: true,
|
||||
clearEmojiStatus: true,
|
||||
|
||||
//user control
|
||||
kickUser: true,
|
||||
setPresenter: true
|
||||
};
|
||||
|
||||
// holds the values for whether the viewer user is allowed to perform an action (true)
|
||||
// or false if not allowed. Some actions have dynamic values depending on the current lock settings
|
||||
viewer = function(meetingId, userId) {
|
||||
let ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7;
|
||||
return {
|
||||
|
||||
// listen only
|
||||
joinListenOnly: true,
|
||||
leaveListenOnly: true,
|
||||
|
||||
// join audio with mic cannot be controlled on the server side as it is
|
||||
// a client side only functionality
|
||||
|
||||
// raising/lowering hand
|
||||
raiseOwnHand: true,
|
||||
lowerOwnHand: true,
|
||||
|
||||
// muting
|
||||
muteSelf: true,
|
||||
unmuteSelf: !((ref = Meteor.Meetings.findOne({
|
||||
meetingId: meetingId
|
||||
@ -40,9 +74,14 @@ viewer = function(meetingId, userId) {
|
||||
meetingId: meetingId,
|
||||
userId: userId
|
||||
})) != null ? ref1.user.locked : void 0),
|
||||
|
||||
logoutSelf: true,
|
||||
|
||||
//subscribing
|
||||
subscribeUsers: true,
|
||||
subscribeChat: true,
|
||||
|
||||
//chat
|
||||
chatPublic: !((ref2 = Meteor.Meetings.findOne({
|
||||
meetingId: meetingId
|
||||
})) != null ? ref2.roomLockSettings.disablePublicChat : void 0) || !((ref3 = Meteor.Users.findOne({
|
||||
@ -61,13 +100,19 @@ viewer = function(meetingId, userId) {
|
||||
meetingId: meetingId,
|
||||
userId: userId
|
||||
})) != null ? ref7.user.presenter : void 0),
|
||||
|
||||
//poll
|
||||
subscribePoll: true,
|
||||
subscribeAnswers: false,
|
||||
|
||||
//emojis
|
||||
setEmojiStatus: true,
|
||||
clearEmojiStatus: true
|
||||
};
|
||||
};
|
||||
|
||||
// carries out the decision making for actions affecting users. For the list of
|
||||
// actions and the default value - see 'viewer' and 'moderator' in the beginning of the file
|
||||
this.isAllowedTo = function(action, meetingId, userId, authToken) {
|
||||
let ref, ref1, ref2, ref3, user, validated;
|
||||
validated = (ref = Meteor.Users.findOne({
|
||||
@ -79,14 +124,22 @@ this.isAllowedTo = function(action, meetingId, userId, authToken) {
|
||||
meetingId: meetingId,
|
||||
userId: userId
|
||||
});
|
||||
if((user != null) && authToken === user.authToken) {
|
||||
// Meteor.log.info "user=" + JSON.stringify user
|
||||
if((user != null) && authToken === user.authToken) { // check if the user is who he claims to be
|
||||
if(user.validated && user.clientType === "HTML5") {
|
||||
|
||||
// PRESENTER
|
||||
// check presenter specific actions or fallback to regular viewer actions
|
||||
if((ref1 = user.user) != null ? ref1.presenter : void 0) {
|
||||
Meteor.log.info("user permissions presenter case");
|
||||
return presenter[action] || viewer(meetingId, userId)[action] || false;
|
||||
|
||||
// VIEWER
|
||||
} else if(((ref2 = user.user) != null ? ref2.role : void 0) === 'VIEWER') {
|
||||
Meteor.log.info("user permissions viewer case");
|
||||
return viewer(meetingId, userId)[action] || false;
|
||||
|
||||
// MODERATOR
|
||||
} else if(((ref3 = user.user) != null ? ref3.role : void 0) === 'MODERATOR') {
|
||||
Meteor.log.info("user permissions moderator case");
|
||||
return moderator[action] || false;
|
||||
@ -95,7 +148,9 @@ this.isAllowedTo = function(action, meetingId, userId, authToken) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// user was not validated
|
||||
if(action === "logoutSelf") {
|
||||
// on unsuccessful sign-in
|
||||
Meteor.log.warn("a user was successfully removed from the meeting following an unsuccessful login");
|
||||
return true;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user