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 = (() => {
|
this.NotificationControl = (() => {
|
||||||
let container, notifications;
|
let container, notifications;
|
||||||
|
|
||||||
container = '';
|
container = ''; // holds where the alerts will go
|
||||||
|
|
||||||
notifications = {};
|
notifications = {};
|
||||||
|
|
||||||
class NotificationControl {
|
class NotificationControl {
|
||||||
constructor(c) {
|
constructor(c) {
|
||||||
container = c[0] === '#' ? c.substr(1) : c;
|
container = c[0] === '#' ? c.substr(1) : c; // prepend '#' to the identifier
|
||||||
$("#whiteboard").prepend(
|
$("#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>"}`
|
`<!-- 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) {
|
create(id, type, content, nDuration, nFadeTime) {
|
||||||
let elementId;
|
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] = {};
|
||||||
notifications[elementId].element = '';
|
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 += `<div id="${elementId}" data-alert class='bbbNotification alert-box ${type}' tabindex='0' aria-live='assertive' role='dialogalert'>`;
|
||||||
notifications[elementId].element += `${content}`;
|
notifications[elementId].element += `${content}`;
|
||||||
notifications[elementId].element += '<button href="#" tabindex="0" class="close" aria-label="Close Alert">×</button>';
|
notifications[elementId].element += '<button href="#" tabindex="0" class="close" aria-label="Close Alert">×</button>';
|
||||||
notifications[elementId].element += '</div>';
|
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;
|
notifications[elementId].fadeTime = nFadeTime || 1000;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
registerShow(elementId, nShowNotification) {
|
registerShow(elementId, nShowNotification) { // register the method to be called when showing the notification
|
||||||
notifications[elementId].showNotification = nShowNotification;
|
notifications[elementId].showNotification = nShowNotification;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
registerHide(elementId, nHideNotification) {
|
registerHide(elementId, nHideNotification) { // register the method called when hiding the notification
|
||||||
notifications[elementId].hideNotification = nHideNotification;
|
notifications[elementId].hideNotification = nHideNotification;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
display(elementId) {
|
display(elementId) { // called the registered methods
|
||||||
let base;
|
let base;
|
||||||
$(`#${container}`).append(notifications[elementId].element);
|
$(`#${container}`).append(notifications[elementId].element); // display the notification
|
||||||
if(typeof (base = notifications[elementId]).showNotification === "function") {
|
if(typeof (base = notifications[elementId]).showNotification === "function") {
|
||||||
base.showNotification();
|
base.showNotification();
|
||||||
}
|
}
|
||||||
setTimeout((_this => {
|
setTimeout((_this => {
|
||||||
|
// remove the notification if the user selected to
|
||||||
return function() {
|
return function() {
|
||||||
let base1;
|
let base1;
|
||||||
if(notifications[elementId].duration > 0) {
|
if(notifications[elementId].duration > 0) {
|
||||||
@ -52,12 +59,13 @@ this.NotificationControl = (() => {
|
|||||||
if(typeof (base1 = notifications[elementId]).hideNotification === "function") {
|
if(typeof (base1 = notifications[elementId]).hideNotification === "function") {
|
||||||
base1.hideNotification();
|
base1.hideNotification();
|
||||||
}
|
}
|
||||||
return notifications[elementId] = {};
|
return notifications[elementId] = {}; // delete all notification data
|
||||||
};
|
};
|
||||||
})(this), notifications[elementId].duration);
|
})(this), notifications[elementId].duration);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hides a notification that may have been left over
|
||||||
hideANotification(elementId) {
|
hideANotification(elementId) {
|
||||||
let base;
|
let base;
|
||||||
$(`#${elementId}`).fadeOut(notifications[elementId].fadeTime, () => {
|
$(`#${elementId}`).fadeOut(notifications[elementId].fadeTime, () => {
|
||||||
@ -70,24 +78,30 @@ this.NotificationControl = (() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// static icon members
|
||||||
NotificationControl.icons = {
|
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',
|
'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',
|
'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'
|
'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;
|
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");
|
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");
|
Meteor.NotificationControl.create("webRTC_AudioJoining", '', 'Connecting to the audio call...', -1).registerShow("webRTC_AudioJoining", () => {}).display("webRTC_AudioJoining");
|
||||||
return Tracker.autorun(comp => {
|
// joined. Displayed joined notification and hide the joining notification
|
||||||
if(BBB.amIInAudio()) {
|
return Tracker.autorun(comp => { // wait until user is in
|
||||||
comp.stop();
|
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", () => {
|
return Meteor.NotificationControl.create("webRTC_AudioJoined", 'success ', '', 2500).registerShow("webRTC_AudioJoined", () => {
|
||||||
Meteor.NotificationControl.hideANotification('webRTC_AudioJoining');
|
Meteor.NotificationControl.hideANotification('webRTC_AudioJoining');
|
||||||
return $("#webRTC_AudioJoined").prepend(`You've joined the ${BBB.amIListenOnlyAudio() ? 'Listen Only' : ''} audio`);
|
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", () => {
|
return Meteor.NotificationControl.create("webRTC_NotSupported", 'alert', '', -1).registerShow("webRTC_NotSupported", () => {
|
||||||
let browserName, ref;
|
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(
|
$("#webRTC_NotSupported").prepend(
|
||||||
`<div id="browser-icon-container"></div>${"Sorry,<br/>" + (browserName !== 'settings' ? browserName : 'your browser') + " doesn't support WebRTC"}`
|
`<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) {
|
this.colourToHex = function(value) {
|
||||||
let hex;
|
let hex;
|
||||||
hex = parseInt(value).toString(16);
|
hex = parseInt(value).toString(16);
|
||||||
@ -23,9 +24,10 @@ this.colourToHex = function(value) {
|
|||||||
return `#${hex}`;
|
return `#${hex}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// color can be a number (a hex converted to int) or a string (e.g. "#ffff00")
|
||||||
this.formatColor = function(color) {
|
this.formatColor = function(color) {
|
||||||
if(color == null) {
|
if(color == null) {
|
||||||
color = "0";
|
color = "0"; // default value
|
||||||
}
|
}
|
||||||
if(!color.toString().match(/\#.*/)) {
|
if(!color.toString().match(/\#.*/)) {
|
||||||
color = colourToHex(color);
|
color = colourToHex(color);
|
||||||
@ -37,22 +39,27 @@ this.getInSession = function(k) {
|
|||||||
return SessionAmplify.get(k);
|
return SessionAmplify.get(k);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// returns epoch in ms
|
||||||
this.getTime = function() {
|
this.getTime = function() {
|
||||||
return (new Date).valueOf();
|
return (new Date).valueOf();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// checks if the pan gesture is mostly horizontal
|
||||||
this.isPanHorizontal = function(event) {
|
this.isPanHorizontal = function(event) {
|
||||||
return Math.abs(event.deltaX) > Math.abs(event.deltaY);
|
return Math.abs(event.deltaX) > Math.abs(event.deltaY);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// helper to determine whether user has joined any type of audio
|
||||||
Handlebars.registerHelper("amIInAudio", () => {
|
Handlebars.registerHelper("amIInAudio", () => {
|
||||||
return BBB.amIInAudio();
|
return BBB.amIInAudio();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// helper to determine whether the user is in the listen only audio stream
|
||||||
Handlebars.registerHelper("amIListenOnlyAudio", () => {
|
Handlebars.registerHelper("amIListenOnlyAudio", () => {
|
||||||
return BBB.amIListenOnlyAudio();
|
return BBB.amIListenOnlyAudio();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// helper to determine whether the user is in the listen only audio stream
|
||||||
Handlebars.registerHelper("isMyMicLocked", () => {
|
Handlebars.registerHelper("isMyMicLocked", () => {
|
||||||
return BBB.isMyMicLocked();
|
return BBB.isMyMicLocked();
|
||||||
});
|
});
|
||||||
@ -63,7 +70,7 @@ Handlebars.registerHelper("colourToHex", (_this => {
|
|||||||
};
|
};
|
||||||
})(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;
|
return a === b;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -74,9 +81,11 @@ Handlebars.registerHelper("getCurrentMeeting", () => {
|
|||||||
Handlebars.registerHelper("getCurrentSlide", () => {
|
Handlebars.registerHelper("getCurrentSlide", () => {
|
||||||
let result;
|
let result;
|
||||||
result = BBB.getCurrentSlide("helper getCurrentSlide");
|
result = BBB.getCurrentSlide("helper getCurrentSlide");
|
||||||
|
// console.log "result=#{JSON.stringify result}"
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Allow access through all templates
|
||||||
Handlebars.registerHelper("getInSession", k => {
|
Handlebars.registerHelper("getInSession", k => {
|
||||||
return SessionAmplify.get(k);
|
return SessionAmplify.get(k);
|
||||||
});
|
});
|
||||||
@ -88,11 +97,14 @@ Handlebars.registerHelper("getMeetingName", () => {
|
|||||||
Handlebars.registerHelper("getShapesForSlide", () => {
|
Handlebars.registerHelper("getShapesForSlide", () => {
|
||||||
let currentSlide, ref;
|
let currentSlide, ref;
|
||||||
currentSlide = BBB.getCurrentSlide("helper getShapesForSlide");
|
currentSlide = BBB.getCurrentSlide("helper getShapesForSlide");
|
||||||
|
|
||||||
|
// try to reuse the lines above
|
||||||
return Meteor.Shapes.find({
|
return Meteor.Shapes.find({
|
||||||
whiteboardId: currentSlide != null ? (ref = currentSlide.slide) != null ? ref.id : void 0 : void 0
|
whiteboardId: currentSlide != null ? (ref = currentSlide.slide) != null ? ref.id : void 0 : void 0
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// retrieves all users in the meeting
|
||||||
Handlebars.registerHelper("getUsersInMeeting", () => {
|
Handlebars.registerHelper("getUsersInMeeting", () => {
|
||||||
let users;
|
let users;
|
||||||
users = Meteor.Users.find().fetch();
|
users = Meteor.Users.find().fetch();
|
||||||
@ -121,6 +133,7 @@ Handlebars.registerHelper("isCurrentUserMuted", () => {
|
|||||||
return BBB.amIMuted();
|
return BBB.amIMuted();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//Retreives a username for a private chat tab from the database if it exists
|
||||||
Handlebars.registerHelper("privateChatName", () => {
|
Handlebars.registerHelper("privateChatName", () => {
|
||||||
let obj, ref;
|
let obj, ref;
|
||||||
obj = Meteor.Users.findOne({
|
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 => {
|
Handlebars.registerHelper('whiteboardSize', section => {
|
||||||
if(BBB.isUserPresenter(getInSession('userId'))) {
|
if(BBB.isUserPresenter(getInSession('userId'))) {
|
||||||
return 'presenter-whiteboard';
|
return 'presenter-whiteboard';
|
||||||
@ -319,6 +333,11 @@ this.getSortedUserList = function(users) {
|
|||||||
} else if(!b.user.phone_user) {
|
} else if(!b.user.phone_user) {
|
||||||
return 1;
|
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) {
|
if(a.user._sort_name < b.user._sort_name) {
|
||||||
return -1;
|
return -1;
|
||||||
} else if(a.user._sort_name > b.user._sort_name) {
|
} else if(a.user._sort_name > b.user._sort_name) {
|
||||||
@ -333,6 +352,7 @@ this.getSortedUserList = function(users) {
|
|||||||
return users;
|
return users;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// transform plain text links into HTML tags compatible with Flash client
|
||||||
this.linkify = function(str) {
|
this.linkify = function(str) {
|
||||||
return str = str.replace(re_weburl, "<a href='event:$&'><u>$&</u></a>");
|
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() {
|
this.toggleChatbar = function() {
|
||||||
setInSession("display_chatbar", !getInSession("display_chatbar"));
|
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;
|
let chat, chats, initChats, j, l, len, len1, myPrivateChats, myUserId, new_msg_userid, results, u, uniqueArray, users;
|
||||||
myUserId = getInSession("userId");
|
myUserId = getInSession("userId");
|
||||||
users = Meteor.Users.find().fetch();
|
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({
|
myPrivateChats = Meteor.Chat.find({
|
||||||
'message.chat_type': 'PRIVATE_CHAT'
|
'message.chat_type': 'PRIVATE_CHAT'
|
||||||
}).fetch();
|
}).fetch();
|
||||||
@ -397,6 +422,8 @@ this.populateNotifications = function(msg) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//keep unique entries only
|
||||||
uniqueArray = uniqueArray.filter((itm, i, a) => {
|
uniqueArray = uniqueArray.filter((itm, i, a) => {
|
||||||
return i === a.indexOf(itm);
|
return i === a.indexOf(itm);
|
||||||
});
|
});
|
||||||
@ -418,6 +445,7 @@ this.populateNotifications = function(msg) {
|
|||||||
setInSession('chats', initChats);
|
setInSession('chats', initChats);
|
||||||
}
|
}
|
||||||
results = [];
|
results = [];
|
||||||
|
//insert the unique entries in the collection
|
||||||
for(l = 0, len1 = uniqueArray.length; l < len1; l++) {
|
for(l = 0, len1 = uniqueArray.length; l < len1; l++) {
|
||||||
u = uniqueArray[l];
|
u = uniqueArray[l];
|
||||||
chats = getInSession('chats');
|
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) {
|
this.exitVoiceCall = function(event, afterExitCall) {
|
||||||
let checkToHangupCall, hangupCallback;
|
let checkToHangupCall, hangupCallback;
|
||||||
|
|
||||||
|
// To be called when the hangup is initiated
|
||||||
hangupCallback = function() {
|
hangupCallback = function() {
|
||||||
return console.log("Exiting Voice Conference");
|
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);
|
getInSession("triedHangup", false);
|
||||||
|
// function to initiate call
|
||||||
(checkToHangupCall = function(context) {
|
(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")) {
|
if(BBB.getCallStatus() !== null && !getInSession("triedHangup")) {
|
||||||
console.log("Attempting to hangup on WebRTC call");
|
console.log("Attempting to hangup on WebRTC call");
|
||||||
if(BBB.amIListenOnlyAudio()) {
|
if(BBB.amIListenOnlyAudio()) {
|
||||||
@ -540,10 +578,11 @@ this.exitVoiceCall = function(event, afterExitCall) {
|
|||||||
);
|
);
|
||||||
return setTimeout(checkToHangupCall, Meteor.config.app.WebRTCHangupRetryInterval);
|
return setTimeout(checkToHangupCall, Meteor.config.app.WebRTCHangupRetryInterval);
|
||||||
}
|
}
|
||||||
})(this);
|
})(this); // automatically run function
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// close the daudio UI, then join the conference. If listen only send the request to the server
|
||||||
this.joinVoiceCall = function(event, arg) {
|
this.joinVoiceCall = function(event, arg) {
|
||||||
let isListenOnly, joinCallback;
|
let isListenOnly, joinCallback;
|
||||||
isListenOnly = (arg != null ? arg : {}).isListenOnly;
|
isListenOnly = (arg != null ? arg : {}).isListenOnly;
|
||||||
@ -554,6 +593,8 @@ this.joinVoiceCall = function(event, arg) {
|
|||||||
if(isListenOnly == null) {
|
if(isListenOnly == null) {
|
||||||
isListenOnly = true;
|
isListenOnly = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// create voice call params
|
||||||
joinCallback = function(message) {
|
joinCallback = function(message) {
|
||||||
return console.log("Beginning WebRTC Conference Call");
|
return console.log("Beginning WebRTC Conference Call");
|
||||||
};
|
};
|
||||||
@ -567,14 +608,17 @@ this.joinVoiceCall = function(event, arg) {
|
|||||||
true
|
true
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
BBB.joinVoiceConference(joinCallback, isListenOnly);
|
BBB.joinVoiceConference(joinCallback, isListenOnly); // make the call //TODO should we apply role permissions to this action?
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Starts the entire logout procedure.
|
||||||
|
// meeting: the meeting the user is in
|
||||||
|
// the user's userId
|
||||||
this.userLogout = function(meeting, user) {
|
this.userLogout = function(meeting, user) {
|
||||||
Meteor.call("userLogout", meeting, user, getInSession("authToken"));
|
Meteor.call("userLogout", meeting, user, getInSession("authToken"));
|
||||||
console.log("logging out");
|
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) {
|
this.kickUser = function(meetingId, toKickUserId, requesterUserId, authToken) {
|
||||||
@ -590,9 +634,10 @@ this.setUserPresenter = function(
|
|||||||
return Meteor.call("setUserPresenter", meetingId, newPresenterId, requesterSetPresenter, newPresenterName, authToken);
|
return Meteor.call("setUserPresenter", meetingId, newPresenterId, requesterSetPresenter, newPresenterName, authToken);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Clear the local user session
|
||||||
this.clearSessionVar = function(callback) {
|
this.clearSessionVar = function(callback) {
|
||||||
amplify.store('authToken', null);
|
amplify.store('authToken', null);
|
||||||
amplify.store('bbbServerVersion', null);
|
amplify.store('bbbServerVer1sion', null);
|
||||||
amplify.store('chats', null);
|
amplify.store('chats', null);
|
||||||
amplify.store('dateOfBuild', null);
|
amplify.store('dateOfBuild', null);
|
||||||
amplify.store('display_chatPane', 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() {
|
this.setDefaultSettings = function() {
|
||||||
let initChats;
|
let initChats;
|
||||||
setInSession("display_navbar", true);
|
setInSession("display_navbar", true);
|
||||||
setInSession("display_chatbar", true);
|
setInSession("display_chatbar", true);
|
||||||
setInSession("display_whiteboard", true);
|
setInSession("display_whiteboard", true);
|
||||||
setInSession("display_chatPane", true);
|
setInSession("display_chatPane", true);
|
||||||
|
|
||||||
|
//if it is a desktop version of the client
|
||||||
if(isPortraitMobile() || isLandscapeMobile()) {
|
if(isPortraitMobile() || isLandscapeMobile()) {
|
||||||
setInSession("messageFontSize", Meteor.config.app.mobileFont);
|
setInSession("messageFontSize", Meteor.config.app.mobileFont);
|
||||||
} else {
|
} else { //if this is a mobile version of the client
|
||||||
setInSession("messageFontSize", Meteor.config.app.desktopFont);
|
setInSession("messageFontSize", Meteor.config.app.desktopFont);
|
||||||
}
|
}
|
||||||
setInSession('display_slidingMenu', false);
|
setInSession('display_slidingMenu', false);
|
||||||
@ -632,6 +680,9 @@ this.setDefaultSettings = function() {
|
|||||||
}
|
}
|
||||||
setInSession('display_menu', false);
|
setInSession('display_menu', false);
|
||||||
setInSession('chatInputMinHeight', 0);
|
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()) {
|
if(loginOrRefresh()) {
|
||||||
initChats = [
|
initChats = [
|
||||||
{
|
{
|
||||||
@ -643,9 +694,10 @@ this.setDefaultSettings = function() {
|
|||||||
setInSession('chats', initChats);
|
setInSession('chats', initChats);
|
||||||
setInSession("inChatWith", 'PUBLIC_CHAT');
|
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() {
|
this.loginOrRefresh = function() {
|
||||||
let checkId, userId;
|
let checkId, userId;
|
||||||
userId = getInSession('userId');
|
userId = getInSession('userId');
|
||||||
@ -681,50 +733,82 @@ this.onLoadComplete = function() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Detects a mobile device
|
||||||
this.isMobile = function() {
|
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() {
|
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() {
|
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() {
|
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() {
|
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() {
|
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() {
|
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() {
|
this.isPhone = function() {
|
||||||
return isLandscapePhone() || isPortraitPhone();
|
return isLandscapePhone() || isPortraitPhone();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// The webpage orientation is now landscape
|
||||||
this.orientationBecameLandscape = function() {
|
this.orientationBecameLandscape = function() {
|
||||||
return adjustChatInputHeight();
|
return adjustChatInputHeight();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// The webpage orientation is now portrait
|
||||||
this.orientationBecamePortrait = function() {
|
this.orientationBecamePortrait = function() {
|
||||||
return adjustChatInputHeight();
|
return adjustChatInputHeight();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Checks if only one panel (userlist/whiteboard/chatbar) is currently open
|
||||||
this.isOnlyOnePanelOpen = function() {
|
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;
|
return getInSession("display_usersList") + getInSession("display_whiteboard") + getInSession("display_chatbar") === 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// determines which browser is being used
|
||||||
this.getBrowserName = function() {
|
this.getBrowserName = function() {
|
||||||
if(navigator.userAgent.match(/Chrome/i)) {
|
if(navigator.userAgent.match(/Chrome/i)) {
|
||||||
return 'Chrome';
|
return 'Chrome';
|
||||||
@ -744,16 +828,22 @@ this.scrollChatDown = function() {
|
|||||||
return $('#chatbody').scrollTop((ref = $('#chatbody')[0]) != null ? ref.scrollHeight : void 0);
|
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() {
|
this.adjustChatInputHeight = function() {
|
||||||
let projectedHeight, ref;
|
let projectedHeight, ref;
|
||||||
if(isLandscape()) {
|
if(isLandscape()) {
|
||||||
$('#newMessageInput').css('height', 'auto');
|
$('#newMessageInput').css('height', 'auto');
|
||||||
projectedHeight = $('#newMessageInput')[0].scrollHeight + 23;
|
projectedHeight = $('#newMessageInput')[0].scrollHeight + 23;
|
||||||
if(projectedHeight !== $('.panel-footer').height() && projectedHeight >= getInSession('chatInputMinHeight')) {
|
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('top', `${-(projectedHeight - 70)}px`);
|
||||||
$('.panel-footer').css('height', `${projectedHeight}px`);
|
$('.panel-footer').css('height', `${projectedHeight}px`);
|
||||||
|
|
||||||
$('#newMessageInput').height($('#newMessageInput')[0].scrollHeight);
|
$('#newMessageInput').height($('#newMessageInput')[0].scrollHeight);
|
||||||
|
|
||||||
|
// resizes the chat messages container
|
||||||
$('#chatbody').height($('#chat').height() - projectedHeight - 45);
|
$('#chatbody').height($('#chat').height() - projectedHeight - 45);
|
||||||
$('#chatbody').scrollTop((ref = $('#chatbody')[0]) != null ? ref.scrollHeight : void 0);
|
$('#chatbody').scrollTop((ref = $('#chatbody')[0]) != null ? ref.scrollHeight : void 0);
|
||||||
}
|
}
|
||||||
@ -788,11 +878,14 @@ this.toggleEmojisFAB = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.toggleUserlistMenu = function() {
|
this.toggleUserlistMenu = function() {
|
||||||
|
// menu
|
||||||
if($('.userlistMenu').hasClass('menuOut')) {
|
if($('.userlistMenu').hasClass('menuOut')) {
|
||||||
$('.userlistMenu').removeClass('menuOut');
|
$('.userlistMenu').removeClass('menuOut');
|
||||||
} else {
|
} else {
|
||||||
$('.userlistMenu').addClass('menuOut');
|
$('.userlistMenu').addClass('menuOut');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// icon
|
||||||
if($('.toggleUserlistButton').hasClass('menuToggledOn')) {
|
if($('.toggleUserlistButton').hasClass('menuToggledOn')) {
|
||||||
return $('.toggleUserlistButton').removeClass('menuToggledOn');
|
return $('.toggleUserlistButton').removeClass('menuToggledOn');
|
||||||
} else {
|
} else {
|
||||||
@ -801,11 +894,14 @@ this.toggleUserlistMenu = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.toggleSettingsMenu = function() {
|
this.toggleSettingsMenu = function() {
|
||||||
|
// menu
|
||||||
if($('.settingsMenu').hasClass('menuOut')) {
|
if($('.settingsMenu').hasClass('menuOut')) {
|
||||||
$('.settingsMenu').removeClass('menuOut');
|
$('.settingsMenu').removeClass('menuOut');
|
||||||
} else {
|
} else {
|
||||||
$('.settingsMenu').addClass('menuOut');
|
$('.settingsMenu').addClass('menuOut');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// icon
|
||||||
if($('.toggleMenuButton').hasClass('menuToggledOn')) {
|
if($('.toggleMenuButton').hasClass('menuToggledOn')) {
|
||||||
return $('.toggleMenuButton').removeClass('menuToggledOn');
|
return $('.toggleMenuButton').removeClass('menuToggledOn');
|
||||||
} else {
|
} else {
|
||||||
|
@ -72,6 +72,7 @@ this.BBB = (function() {
|
|||||||
for AM_I_SHARING_CAM_RESP (see below).
|
for AM_I_SHARING_CAM_RESP (see below).
|
||||||
*/
|
*/
|
||||||
BBB.amISharingWebcam = function(callback) {
|
BBB.amISharingWebcam = function(callback) {
|
||||||
|
// BBB.isUserSharingWebcam BBB.getCurrentUser()?.userId
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -86,21 +87,30 @@ this.BBB = (function() {
|
|||||||
IS_USER_PUBLISHING_CAM_RESP (see below).
|
IS_USER_PUBLISHING_CAM_RESP (see below).
|
||||||
*/
|
*/
|
||||||
BBB.isUserSharingWebcam = function(userId, callback) {
|
BBB.isUserSharingWebcam = function(userId, callback) {
|
||||||
|
// BBB.getUser(userId)?.user?.webcam_stream?.length isnt 0
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// returns whether the user has joined any type of audio
|
||||||
BBB.amIInAudio = function(callback) {
|
BBB.amIInAudio = function(callback) {
|
||||||
let ref, ref1, ref2, user;
|
let ref, ref1, ref2, user;
|
||||||
user = BBB.getCurrentUser();
|
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);
|
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) {
|
BBB.amIListenOnlyAudio = function(callback) {
|
||||||
let ref, ref1;
|
let ref, ref1;
|
||||||
return (ref = BBB.getCurrentUser()) != null ? (ref1 = ref.user) != null ? ref1.listenOnly : void 0 : void 0;
|
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) {
|
BBB.amISharingAudio = function(callback) {
|
||||||
let ref;
|
let ref;
|
||||||
return BBB.isUserSharingAudio((ref = BBB.getCurrentUser()) != null ? ref.userId : void 0);
|
return BBB.isUserSharingAudio((ref = BBB.getCurrentUser()) != null ? ref.userId : void 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// returns whether the user is currently talking
|
||||||
BBB.amITalking = function(callback) {
|
BBB.amITalking = function(callback) {
|
||||||
let ref;
|
let ref;
|
||||||
return BBB.isUserTalking((ref = BBB.getCurrentUser()) != null ? ref.userId : void 0);
|
return BBB.isUserTalking((ref = BBB.getCurrentUser()) != null ? ref.userId : void 0);
|
||||||
@ -126,13 +136,20 @@ this.BBB = (function() {
|
|||||||
let ref, ref1;
|
let ref, ref1;
|
||||||
return (ref = BBB.getUser(userId)) != null ? (ref1 = ref.user) != null ? ref1.presenter : void 0 : void 0;
|
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() {
|
BBB.amILocked = function() {
|
||||||
let ref;
|
let ref;
|
||||||
return (ref = BBB.getCurrentUser()) != null ? ref.user.locked : void 0;
|
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() {
|
BBB.isMyMicLocked = function() {
|
||||||
let lockedMicForRoom, ref;
|
let lockedMicForRoom, ref;
|
||||||
lockedMicForRoom = (ref = Meteor.Meetings.findOne()) != null ? ref.roomLockSettings.disableMic : void 0;
|
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();
|
return lockedMicForRoom && BBB.amILocked();
|
||||||
};
|
};
|
||||||
BBB.getCurrentSlide = function(callingLocaton) {
|
BBB.getCurrentSlide = function(callingLocaton) {
|
||||||
@ -145,6 +162,7 @@ this.BBB = (function() {
|
|||||||
"presentationId": presentationId,
|
"presentationId": presentationId,
|
||||||
"slide.current": true
|
"slide.current": true
|
||||||
});
|
});
|
||||||
|
// console.log "trigger:#{callingLocaton} currentSlideId=#{currentSlide?._id}"
|
||||||
return currentSlide;
|
return currentSlide;
|
||||||
};
|
};
|
||||||
BBB.getMeetingName = function() {
|
BBB.getMeetingName = function() {
|
||||||
@ -461,9 +479,13 @@ this.BBB = (function() {
|
|||||||
presentationID - the presentation to delete
|
presentationID - the presentation to delete
|
||||||
*/
|
*/
|
||||||
BBB.deletePresentation = function(presentationID) {};
|
BBB.deletePresentation = function(presentationID) {};
|
||||||
|
|
||||||
|
// Request to switch the presentation to the previous slide
|
||||||
BBB.goToPreviousPage = function() {
|
BBB.goToPreviousPage = function() {
|
||||||
return Meteor.call('publishSwitchToPreviousSlideMessage', getInSession('meetingId'), getInSession('userId'), getInSession('authToken'));
|
return Meteor.call('publishSwitchToPreviousSlideMessage', getInSession('meetingId'), getInSession('userId'), getInSession('authToken'));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Request to switch the presentation to the next slide
|
||||||
BBB.goToNextPage = function() {
|
BBB.goToNextPage = function() {
|
||||||
return Meteor.call('publishSwitchToNextSlideMessage', getInSession('meetingId'), getInSession('userId'), getInSession('authToken'));
|
return Meteor.call('publishSwitchToNextSlideMessage', getInSession('meetingId'), getInSession('userId'), getInSession('authToken'));
|
||||||
};
|
};
|
||||||
@ -485,6 +507,12 @@ this.BBB = (function() {
|
|||||||
BBB.webRTCWebcamRequestSuccess = function() {};
|
BBB.webRTCWebcamRequestSuccess = function() {};
|
||||||
BBB.webRTCWebcamRequestFail = function(reason) {};
|
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 **
|
Stores the 3rd-party app event listeners **
|
||||||
*/
|
*/
|
||||||
|
@ -1,15 +1,19 @@
|
|||||||
let loadLib;
|
let loadLib;
|
||||||
|
|
||||||
|
// Helper to load javascript libraries from the BBB server
|
||||||
loadLib = function(libname) {
|
loadLib = function(libname) {
|
||||||
let retryMessageCallback, successCallback;
|
let retryMessageCallback, successCallback;
|
||||||
successCallback = function() {};
|
successCallback = function() {};
|
||||||
retryMessageCallback = function(param) {
|
retryMessageCallback = function(param) {
|
||||||
|
//return(Meteor.log.info("Failed to load library"), param);
|
||||||
return console.log("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);
|
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(() => {
|
Meteor.startup(() => {
|
||||||
|
// Load SIP libraries before the application starts
|
||||||
loadLib('sip.js');
|
loadLib('sip.js');
|
||||||
loadLib('bbb_webrtc_bridge_sip.js');
|
loadLib('bbb_webrtc_bridge_sip.js');
|
||||||
loadLib('bbblogger.js');
|
loadLib('bbblogger.js');
|
||||||
@ -24,6 +28,7 @@ Meteor.startup(() => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//
|
||||||
Template.header.events({
|
Template.header.events({
|
||||||
"click .chatBarIcon"(event) {
|
"click .chatBarIcon"(event) {
|
||||||
$(".tooltip").hide();
|
$(".tooltip").hide();
|
||||||
@ -126,6 +131,8 @@ Template.main.rendered = function() {
|
|||||||
lastOrientationWasLandscape = isLandscape();
|
lastOrientationWasLandscape = isLandscape();
|
||||||
$(window).resize(() => {
|
$(window).resize(() => {
|
||||||
$('#dialog').dialog('close');
|
$('#dialog').dialog('close');
|
||||||
|
|
||||||
|
// when the orientation switches call the handler
|
||||||
if(isLandscape() && !lastOrientationWasLandscape) {
|
if(isLandscape() && !lastOrientationWasLandscape) {
|
||||||
orientationBecameLandscape();
|
orientationBecameLandscape();
|
||||||
return lastOrientationWasLandscape = true;
|
return lastOrientationWasLandscape = true;
|
||||||
@ -214,7 +221,7 @@ Template.main.gestures({
|
|||||||
$('.right-drawer').removeClass('menuOut');
|
$('.right-drawer').removeClass('menuOut');
|
||||||
$('.right-drawer').css('transform', '');
|
$('.right-drawer').css('transform', '');
|
||||||
$('.toggleMenuButton').removeClass('menuToggledOn');
|
$('.toggleMenuButton').removeClass('menuToggledOn');
|
||||||
$('.shield').removeClass('darken');
|
$('.shield').removeClass('darken'); // in case it was opened by clicking a button
|
||||||
} else {
|
} else {
|
||||||
$('.shield').css('opacity', 0.5);
|
$('.shield').css('opacity', 0.5);
|
||||||
$('.right-drawer').css('transform', `translateX(${screenWidth - $('.right-drawer').width()}px)`);
|
$('.right-drawer').css('transform', `translateX(${screenWidth - $('.right-drawer').width()}px)`);
|
||||||
@ -232,16 +239,26 @@ Template.main.gestures({
|
|||||||
'panright #container, panleft #container'(event, template) {
|
'panright #container, panleft #container'(event, template) {
|
||||||
let initTransformValue, leftDrawerWidth, menuPanned, panIsValid, rightDrawerWidth, screenWidth;
|
let initTransformValue, leftDrawerWidth, menuPanned, panIsValid, rightDrawerWidth, screenWidth;
|
||||||
if(isPortraitMobile() && isPanHorizontal(event)) {
|
if(isPortraitMobile() && isPanHorizontal(event)) {
|
||||||
|
|
||||||
|
// panright/panleft is always triggered once right before panstart
|
||||||
if(!getInSession('panStarted')) {
|
if(!getInSession('panStarted')) {
|
||||||
|
|
||||||
|
// opening the left-hand menu
|
||||||
if(event.type === 'panright' && event.center.x <= $('#container').width() * 0.1) {
|
if(event.type === 'panright' && event.center.x <= $('#container').width() * 0.1) {
|
||||||
setInSession('panIsValid', true);
|
setInSession('panIsValid', true);
|
||||||
setInSession('menuPanned', 'left');
|
setInSession('menuPanned', 'left');
|
||||||
|
|
||||||
|
// closing the left-hand menu
|
||||||
} else if(event.type === 'panleft' && event.center.x < $('#container').width() * 0.9) {
|
} else if(event.type === 'panleft' && event.center.x < $('#container').width() * 0.9) {
|
||||||
setInSession('panIsValid', true);
|
setInSession('panIsValid', true);
|
||||||
setInSession('menuPanned', 'left');
|
setInSession('menuPanned', 'left');
|
||||||
|
|
||||||
|
// opening the right-hand menu
|
||||||
} else if(event.type === 'panleft' && event.center.x >= $('#container').width() * 0.9) {
|
} else if(event.type === 'panleft' && event.center.x >= $('#container').width() * 0.9) {
|
||||||
setInSession('panIsValid', true);
|
setInSession('panIsValid', true);
|
||||||
setInSession('menuPanned', 'right');
|
setInSession('menuPanned', 'right');
|
||||||
|
|
||||||
|
// closing the right-hand menu
|
||||||
} else if(event.type === 'panright' && event.center.x > $('#container').width() * 0.1) {
|
} else if(event.type === 'panright' && event.center.x > $('#container').width() * 0.1) {
|
||||||
setInSession('panIsValid', true);
|
setInSession('panIsValid', true);
|
||||||
setInSession('menuPanned', 'right');
|
setInSession('menuPanned', 'right');
|
||||||
@ -250,11 +267,11 @@ Template.main.gestures({
|
|||||||
}
|
}
|
||||||
setInSession('eventType', event.type);
|
setInSession('eventType', event.type);
|
||||||
if(getInSession('menuPanned') === 'left') {
|
if(getInSession('menuPanned') === 'left') {
|
||||||
if($('.userlistMenu').css('transform') !== 'none') {
|
if($('.userlistMenu').css('transform') !== 'none') { // menu is already transformed
|
||||||
setInSession(
|
setInSession(
|
||||||
'initTransform',
|
'initTransform',
|
||||||
parseInt($('.userlistMenu').css('transform').split(',')[4])
|
parseInt($('.userlistMenu').css('transform').split(',')[4])
|
||||||
);
|
); // translateX value
|
||||||
} else if($('.userlistMenu').hasClass('menuOut')) {
|
} else if($('.userlistMenu').hasClass('menuOut')) {
|
||||||
setInSession('initTransform', $('.userlistMenu').width());
|
setInSession('initTransform', $('.userlistMenu').width());
|
||||||
} else {
|
} else {
|
||||||
@ -263,11 +280,11 @@ Template.main.gestures({
|
|||||||
$('.userlistMenu').addClass('left-drawer');
|
$('.userlistMenu').addClass('left-drawer');
|
||||||
$('.left-drawer').removeClass('userlistMenu');
|
$('.left-drawer').removeClass('userlistMenu');
|
||||||
} else if(getInSession('menuPanned') === 'right') {
|
} else if(getInSession('menuPanned') === 'right') {
|
||||||
if($('.settingsMenu').css('transform') !== 'none') {
|
if($('.settingsMenu').css('transform') !== 'none') { // menu is already transformed
|
||||||
setInSession(
|
setInSession(
|
||||||
'initTransform',
|
'initTransform',
|
||||||
parseInt($('.settingsMenu').css('transform').split(',')[4])
|
parseInt($('.settingsMenu').css('transform').split(',')[4])
|
||||||
);
|
); // translateX value
|
||||||
} else if($('.settingsMenu').hasClass('menuOut')) {
|
} else if($('.settingsMenu').hasClass('menuOut')) {
|
||||||
setInSession('initTransform', $('.settingsMenu').width());
|
setInSession('initTransform', $('.settingsMenu').width());
|
||||||
} else {
|
} else {
|
||||||
@ -283,7 +300,12 @@ Template.main.gestures({
|
|||||||
leftDrawerWidth = $('.left-drawer').width();
|
leftDrawerWidth = $('.left-drawer').width();
|
||||||
rightDrawerWidth = $('.right-drawer').width();
|
rightDrawerWidth = $('.right-drawer').width();
|
||||||
screenWidth = $('#container').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')) {
|
if($('.settingsMenu').hasClass('menuOut')) {
|
||||||
toggleSettingsMenu();
|
toggleSettingsMenu();
|
||||||
}
|
}
|
||||||
@ -292,7 +314,10 @@ Template.main.gestures({
|
|||||||
$('.shield').addClass('animatedShield');
|
$('.shield').addClass('animatedShield');
|
||||||
}
|
}
|
||||||
return $('.shield').css('opacity', 0.5 * (initTransformValue + event.deltaX) / leftDrawerWidth);
|
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')) {
|
if($('.userlistMenu').hasClass('menuOut')) {
|
||||||
toggleUserlistMenu();
|
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) {
|
this.activateBreakLines = function(str) {
|
||||||
let res;
|
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);
|
res = str.replace(new RegExp(CARRIAGE_RETURN, 'g'), BREAK_LINE);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.detectUnreadChat = function() {
|
this.detectUnreadChat = function() {
|
||||||
|
//if the current tab is not the same as the tab we just published in
|
||||||
return Meteor.Chat.find({}).observe({
|
return Meteor.Chat.find({}).observe({
|
||||||
added: (_this => {
|
added: (_this => {
|
||||||
return function(chatMessage) {
|
return function(chatMessage) {
|
||||||
@ -23,7 +32,7 @@ this.detectUnreadChat = function() {
|
|||||||
let destinationTab, tabsTime;
|
let destinationTab, tabsTime;
|
||||||
tabsTime = getInSession('userListRenderedTime');
|
tabsTime = getInSession('userListRenderedTime');
|
||||||
if((tabsTime != null) && chatMessage.message.from_userid !== "SYSTEM_MESSAGE" && chatMessage.message.from_time - tabsTime > 0) {
|
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();
|
destinationTab = findDestinationTab();
|
||||||
if(destinationTab !== getInSession("inChatWith")) {
|
if(destinationTab !== getInSession("inChatWith")) {
|
||||||
setInSession('chats', getInSession('chats').map(tab => {
|
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() {
|
this.getFormattedMessagesForChat = function() {
|
||||||
let chattingWith;
|
let chattingWith;
|
||||||
chattingWith = getInSession('inChatWith');
|
chattingWith = getInSession('inChatWith');
|
||||||
if(chattingWith === 'PUBLIC_CHAT') {
|
if(chattingWith === 'PUBLIC_CHAT') { // find all public and system messages
|
||||||
return Meteor.Chat.find({
|
return Meteor.Chat.find({
|
||||||
'message.chat_type': {
|
'message.chat_type': {
|
||||||
$in: ["SYSTEM_MESSAGE", "PUBLIC_CHAT"]
|
$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;
|
let ref;
|
||||||
$('#chatbody').scrollTop((ref = $('#chatbody')[0]) != null ? ref.scrollHeight : void 0);
|
$('#chatbody').scrollTop((ref = $('#chatbody')[0]) != null ? ref.scrollHeight : void 0);
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// true if the lock settings limit public chat and the current user is locked
|
||||||
Handlebars.registerHelper("publicChatDisabled", () => {
|
Handlebars.registerHelper("publicChatDisabled", () => {
|
||||||
let presenter, publicChatIsDisabled, ref, ref1, ref2, userIsLocked;
|
let presenter, publicChatIsDisabled, ref, ref1, ref2, userIsLocked;
|
||||||
userIsLocked = (ref = Meteor.Users.findOne({
|
userIsLocked = (ref = Meteor.Users.findOne({
|
||||||
@ -87,6 +99,7 @@ Handlebars.registerHelper("publicChatDisabled", () => {
|
|||||||
return userIsLocked && publicChatIsDisabled && !presenter;
|
return userIsLocked && publicChatIsDisabled && !presenter;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// true if the lock settings limit private chat and the current user is locked
|
||||||
Handlebars.registerHelper("privateChatDisabled", () => {
|
Handlebars.registerHelper("privateChatDisabled", () => {
|
||||||
let presenter, privateChatIsDisabled, ref, ref1, ref2, userIsLocked;
|
let presenter, privateChatIsDisabled, ref, ref1, ref2, userIsLocked;
|
||||||
userIsLocked = (ref = Meteor.Users.findOne({
|
userIsLocked = (ref = Meteor.Users.findOne({
|
||||||
@ -99,17 +112,18 @@ Handlebars.registerHelper("privateChatDisabled", () => {
|
|||||||
return userIsLocked && privateChatIsDisabled && !presenter;
|
return userIsLocked && privateChatIsDisabled && !presenter;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// return whether the user's chat pane is open in Private chat
|
||||||
Handlebars.registerHelper("inPrivateChat", () => {
|
Handlebars.registerHelper("inPrivateChat", () => {
|
||||||
return (getInSession('inChatWith')) !== 'PUBLIC_CHAT';
|
return (getInSession('inChatWith')) !== 'PUBLIC_CHAT';
|
||||||
});
|
});
|
||||||
|
|
||||||
this.sendMessage = function() {
|
this.sendMessage = function() {
|
||||||
let chattingWith, color, message, ref, toUsername;
|
let chattingWith, color, message, ref, toUsername;
|
||||||
message = linkify($('#newMessageInput').val());
|
message = linkify($('#newMessageInput').val()); // get the message from the input box
|
||||||
if(!((message != null ? message.length : void 0) > 0 && (/\S/.test(message)))) {
|
if(!((message != null ? message.length : void 0) > 0 && (/\S/.test(message)))) { // check the message has content and it is not whitespace
|
||||||
return;
|
return; // do nothing if invalid message
|
||||||
}
|
}
|
||||||
color = "0x000000";
|
color = "0x000000"; //"0x#{getInSession("messageColor")}"
|
||||||
if((chattingWith = getInSession('inChatWith')) !== "PUBLIC_CHAT") {
|
if((chattingWith = getInSession('inChatWith')) !== "PUBLIC_CHAT") {
|
||||||
toUsername = (ref = Meteor.Users.findOne({
|
toUsername = (ref = Meteor.Users.findOne({
|
||||||
userId: chattingWith
|
userId: chattingWith
|
||||||
@ -118,33 +132,35 @@ this.sendMessage = function() {
|
|||||||
} else {
|
} else {
|
||||||
BBB.sendPublicChatMessage(color, "en", message);
|
BBB.sendPublicChatMessage(color, "en", message);
|
||||||
}
|
}
|
||||||
return $('#newMessageInput').val('');
|
return $('#newMessageInput').val(''); // Clear message box
|
||||||
};
|
};
|
||||||
|
|
||||||
Template.chatbar.helpers({
|
Template.chatbar.helpers({
|
||||||
getCombinedMessagesForChat() {
|
getCombinedMessagesForChat() {
|
||||||
let deleted, i, j, len, msgs;
|
let deleted, i, j, len, msgs;
|
||||||
msgs = getFormattedMessagesForChat();
|
msgs = getFormattedMessagesForChat();
|
||||||
len = msgs != null ? msgs.length : void 0;
|
len = msgs != null ? msgs.length : void 0; // get length of messages
|
||||||
i = 0;
|
i = 0;
|
||||||
while(i < len) {
|
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') {
|
if(msgs[i].message.from_userid !== 'System') { // skip system messages
|
||||||
j = i + 1;
|
j = i + 1; // Start looking at messages right after the current one
|
||||||
while(j < len) {
|
while(j < len) {
|
||||||
deleted = false;
|
deleted = false;
|
||||||
if(msgs[j].message.from_userid !== 'System') {
|
if(msgs[j].message.from_userid !== 'System') { // Ignore system messages
|
||||||
if((parseFloat(msgs[j].message.from_time) - parseFloat(msgs[i].message.from_time)) >= 60000) {
|
// Check if the time discrepancy between the two messages exceeds window for grouping
|
||||||
break;
|
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) {
|
if(msgs[i].message.from_userid === msgs[j].message.from_userid) { // Both messages are from the same user
|
||||||
msgs[i].message.message += `${CARRIAGE_RETURN}${msgs[j].message.message}`;
|
// insert a '\r' carriage return character between messages to put them on a new line
|
||||||
msgs.splice(j, 1);
|
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;
|
deleted = true;
|
||||||
} else {
|
} else {
|
||||||
break;
|
break; // Messages are from different people, move on
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
break;
|
break; // This is the break point in the chat, don't merge
|
||||||
}
|
}
|
||||||
len = msgs.length;
|
len = msgs.length;
|
||||||
if(!deleted) {
|
if(!deleted) {
|
||||||
@ -168,10 +184,12 @@ Template.chatbar.helpers({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// When chatbar gets rendered, launch the auto-check for unread chat
|
||||||
Template.chatbar.rendered = function() {
|
Template.chatbar.rendered = function() {
|
||||||
return detectUnreadChat();
|
return detectUnreadChat();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// When "< Public" is clicked, go to public chat
|
||||||
Template.chatbar.events({
|
Template.chatbar.events({
|
||||||
'click .toPublic'(event) {
|
'click .toPublic'(event) {
|
||||||
setInSession('inChatWith', 'PUBLIC_CHAT');
|
setInSession('inChatWith', 'PUBLIC_CHAT');
|
||||||
@ -191,6 +209,7 @@ Template.privateChatTab.rendered = function() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// When message gets rendered, scroll to the bottom
|
||||||
Template.message.rendered = function() {
|
Template.message.rendered = function() {
|
||||||
let ref;
|
let ref;
|
||||||
$('#chatbody').scrollTop((ref = $('#chatbody')[0]) != null ? ref.scrollHeight : void 0);
|
$('#chatbody').scrollTop((ref = $('#chatbody')[0]) != null ? ref.scrollHeight : void 0);
|
||||||
@ -204,7 +223,7 @@ Template.chatInput.rendered = function() {
|
|||||||
resize(event, ui) {
|
resize(event, ui) {
|
||||||
let ref;
|
let ref;
|
||||||
if($('.panel-footer').css('top') === '0px') {
|
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 {
|
} else {
|
||||||
$('.panel-footer').css('top', `${parseInt($('.panel-footer').css('top'))}${1}px`);
|
$('.panel-footer').css('top', `${parseInt($('.panel-footer').css('top'))}${1}px`);
|
||||||
}
|
}
|
||||||
@ -227,15 +246,16 @@ Template.chatInput.events({
|
|||||||
sendMessage();
|
sendMessage();
|
||||||
return adjustChatInputHeight();
|
return adjustChatInputHeight();
|
||||||
},
|
},
|
||||||
'keypress #newMessageInput'(event) {
|
'keypress #newMessageInput'(event) { // user pressed a button inside the chatbox
|
||||||
let key;
|
let key;
|
||||||
key = event.charCode ? event.charCode : (event.keyCode ? event.keyCode : 0);
|
key = event.charCode ? event.charCode : (event.keyCode ? event.keyCode : 0);
|
||||||
if(event.shiftKey && (key === 13)) {
|
if(event.shiftKey && (key === 13)) {
|
||||||
event.preventDefault();
|
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;
|
return;
|
||||||
}
|
}
|
||||||
if(key === 13) {
|
if(key === 13) { // Check for pressing enter to submit message
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
sendMessage();
|
sendMessage();
|
||||||
$('#newMessageInput').val("");
|
$('#newMessageInput').val("");
|
||||||
@ -255,7 +275,7 @@ Template.chatInputControls.rendered = function() {
|
|||||||
Template.message.helpers({
|
Template.message.helpers({
|
||||||
sanitizeAndFormat(str) {
|
sanitizeAndFormat(str) {
|
||||||
let res;
|
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 = str.replace(/&/g, '&').replace(/<(?![au\/])/g, '<').replace(/\/([^au])>/g, '$1>').replace(/([^=])"(?!>)/g, '$1"');
|
||||||
res = toClickable(res);
|
res = toClickable(res);
|
||||||
return res = activateBreakLines(res);
|
return res = activateBreakLines(res);
|
||||||
@ -268,7 +288,7 @@ Template.message.helpers({
|
|||||||
}
|
}
|
||||||
local = new Date();
|
local = new Date();
|
||||||
offset = local.getTimezoneOffset();
|
offset = local.getTimezoneOffset();
|
||||||
epochTime = epochTime - offset * 60000;
|
epochTime = epochTime - offset * 60000; // 1 min = 60 s = 60,000 ms
|
||||||
dateObj = new Date(epochTime);
|
dateObj = new Date(epochTime);
|
||||||
hours = dateObj.getUTCHours();
|
hours = dateObj.getUTCHours();
|
||||||
minutes = dateObj.getUTCMinutes();
|
minutes = dateObj.getUTCMinutes();
|
||||||
@ -279,6 +299,7 @@ Template.message.helpers({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// make links received from Flash client clickable in HTML
|
||||||
this.toClickable = function(str) {
|
this.toClickable = function(str) {
|
||||||
let res;
|
let res;
|
||||||
if(typeof str === 'string') {
|
if(typeof str === 'string') {
|
||||||
|
@ -45,7 +45,7 @@ Template.settingsModal.events({
|
|||||||
|
|
||||||
Template.optionsFontSize.events({
|
Template.optionsFontSize.events({
|
||||||
"click #decreaseFontSize"(event) {
|
"click #decreaseFontSize"(event) {
|
||||||
if(getInSession("messageFontSize") === 8) {
|
if(getInSession("messageFontSize") === 8) { // min
|
||||||
$('#decreaseFontSize').disabled = true;
|
$('#decreaseFontSize').disabled = true;
|
||||||
$('#decreaseFontSize').removeClass('icon fi-minus');
|
$('#decreaseFontSize').removeClass('icon fi-minus');
|
||||||
return $('#decreaseFontSize').html('MIN');
|
return $('#decreaseFontSize').html('MIN');
|
||||||
@ -60,7 +60,7 @@ Template.optionsFontSize.events({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"click #increaseFontSize"(event) {
|
"click #increaseFontSize"(event) {
|
||||||
if(getInSession("messageFontSize") === 40) {
|
if(getInSession("messageFontSize") === 40) { // max
|
||||||
$('#increaseFontSize').disabled = true;
|
$('#increaseFontSize').disabled = true;
|
||||||
$('#increaseFontSize').removeClass('icon fi-plus');
|
$('#increaseFontSize').removeClass('icon fi-plus');
|
||||||
return $('#increaseFontSize').html('MAX');
|
return $('#increaseFontSize').html('MAX');
|
||||||
|
@ -3,6 +3,10 @@ Template.displayUserIcons.events({
|
|||||||
return toggleMic(this);
|
return toggleMic(this);
|
||||||
},
|
},
|
||||||
'click .raisedHandIcon'(event) {
|
'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"));
|
return BBB.lowerHand(getInSession("meetingId"), this.userId, getInSession("userId"), getInSession("authToken"));
|
||||||
},
|
},
|
||||||
'click .kickUser'(event) {
|
'click .kickUser'(event) {
|
||||||
@ -12,6 +16,8 @@ Template.displayUserIcons.events({
|
|||||||
|
|
||||||
Template.displayUserIcons.helpers({
|
Template.displayUserIcons.helpers({
|
||||||
userLockedIconApplicable(userId) {
|
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;
|
let lockInAction, locked, ref, ref1, settings;
|
||||||
locked = (ref = BBB.getUser(userId)) != null ? ref.user.locked : void 0;
|
locked = (ref = BBB.getUser(userId)) != null ? ref.user.locked : void 0;
|
||||||
settings = (ref1 = Meteor.Meetings.findOne()) != null ? ref1.roomLockSettings : 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({
|
Template.usernameEntry.events({
|
||||||
'click .usernameEntry'(event) {
|
'click .usernameEntry'(event) {
|
||||||
let ref, userIdSelected;
|
let ref, userIdSelected;
|
||||||
@ -35,7 +42,7 @@ Template.usernameEntry.events({
|
|||||||
toggleUserlistMenu();
|
toggleUserlistMenu();
|
||||||
toggleShield();
|
toggleShield();
|
||||||
}
|
}
|
||||||
return setTimeout(() => {
|
return setTimeout(() => { // waits until the end of execution queue
|
||||||
return $("#newMessageInput").focus();
|
return $("#newMessageInput").focus();
|
||||||
}, 0);
|
}, 0);
|
||||||
},
|
},
|
||||||
|
@ -5,6 +5,7 @@ Template.usersList.helpers({
|
|||||||
if (numberUsers > 8) {
|
if (numberUsers > 8) {
|
||||||
return `Users: ${numberUsers}`;
|
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('slideOriginalWidth', this.width);
|
||||||
setInSession('slideOriginalHeight', this.height);
|
setInSession('slideOriginalHeight', this.height);
|
||||||
$(window).resize(() => {
|
$(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();
|
return scaleWhiteboard();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -28,6 +29,7 @@ this.reactOnSlideChange = (_this => {
|
|||||||
})(this);
|
})(this);
|
||||||
|
|
||||||
this.createWhiteboardPaper = (_this => {
|
this.createWhiteboardPaper = (_this => {
|
||||||
|
// console.log "CREATING WPM"
|
||||||
return function(callback) {
|
return function(callback) {
|
||||||
_this.whiteboardPaperModel = new Meteor.WhiteboardPaperModel('whiteboard-paper');
|
_this.whiteboardPaperModel = new Meteor.WhiteboardPaperModel('whiteboard-paper');
|
||||||
return callback(_this.whiteboardPaperModel);
|
return callback(_this.whiteboardPaperModel);
|
||||||
@ -67,7 +69,7 @@ this.manuallyDisplayShapes = function() {
|
|||||||
shapeType = shapeInfo != null ? shapeInfo.type : void 0;
|
shapeType = shapeInfo != null ? shapeInfo.type : void 0;
|
||||||
if(shapeType !== "text") {
|
if(shapeType !== "text") {
|
||||||
len = shapeInfo.points.length;
|
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) {
|
if(shapeInfo != null) {
|
||||||
shapeInfo.points[num] = (shapeInfo != null ? shapeInfo.points[num] : void 0) / 100;
|
shapeInfo.points[num] = (shapeInfo != null ? shapeInfo.points[num] : void 0) / 100;
|
||||||
}
|
}
|
||||||
@ -81,17 +83,29 @@ this.manuallyDisplayShapes = function() {
|
|||||||
return results;
|
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) {
|
this.scaleSlide = function(originalWidth, originalHeight) {
|
||||||
let adjustedHeight, adjustedWidth, boardHeight, boardWidth;
|
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) {
|
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();
|
boardWidth = $("#whiteboard-container").width();
|
||||||
boardHeight = $("#whiteboard-container").height();
|
boardHeight = $("#whiteboard-container").height();
|
||||||
} else {
|
} 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();
|
boardWidth = $("#whiteboard-container").width();
|
||||||
boardHeight = 1.4 * $("#whiteboard-container").width();
|
boardHeight = 1.4 * $("#whiteboard-container").width();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this is the best fitting pair
|
||||||
adjustedWidth = null;
|
adjustedWidth = null;
|
||||||
adjustedHeight = null;
|
adjustedHeight = null;
|
||||||
|
|
||||||
|
// the slide image is in portrait orientation
|
||||||
if(originalWidth <= originalHeight) {
|
if(originalWidth <= originalHeight) {
|
||||||
adjustedWidth = boardHeight * originalWidth / originalHeight;
|
adjustedWidth = boardHeight * originalWidth / originalHeight;
|
||||||
if (boardWidth < adjustedWidth) {
|
if (boardWidth < adjustedWidth) {
|
||||||
@ -100,6 +114,8 @@ this.scaleSlide = function(originalWidth, originalHeight) {
|
|||||||
} else {
|
} else {
|
||||||
adjustedHeight = boardHeight;
|
adjustedHeight = boardHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ths slide image is in landscape orientation
|
||||||
} else {
|
} else {
|
||||||
adjustedHeight = boardWidth * originalHeight / originalWidth;
|
adjustedHeight = boardWidth * originalHeight / originalWidth;
|
||||||
if (boardHeight < adjustedHeight) {
|
if (boardHeight < adjustedHeight) {
|
||||||
@ -123,13 +139,16 @@ Template.slide.helpers({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//// SHAPE ////
|
||||||
Template.shape.rendered = function() {
|
Template.shape.rendered = function() {
|
||||||
let i, len, num, ref, ref1, shapeInfo, shapeType, wpm;
|
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;
|
shapeInfo = ((ref = this.data.shape) != null ? ref.shape : void 0) || this.data.shape;
|
||||||
shapeType = shapeInfo != null ? shapeInfo.type : void 0;
|
shapeType = shapeInfo != null ? shapeInfo.type : void 0;
|
||||||
if(shapeType !== "text") {
|
if(shapeType !== "text") {
|
||||||
len = shapeInfo.points.length;
|
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;
|
shapeInfo.points[num] = shapeInfo.points[num] / 100;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
let fakeUpload;
|
let fakeUpload;
|
||||||
|
|
||||||
|
// scale the whiteboard to adapt to the resized window
|
||||||
this.scaleWhiteboard = function(callback) {
|
this.scaleWhiteboard = function(callback) {
|
||||||
let adjustedDimensions;
|
let adjustedDimensions;
|
||||||
adjustedDimensions = scaleSlide(getInSession('slideOriginalWidth'), getInSession('slideOriginalHeight'));
|
adjustedDimensions = scaleSlide(getInSession('slideOriginalWidth'), getInSession('slideOriginalHeight'));
|
||||||
@ -27,9 +28,13 @@ this.scaleWhiteboard = function(callback) {
|
|||||||
},
|
},
|
||||||
clearSlide() {
|
clearSlide() {
|
||||||
let ref;
|
let ref;
|
||||||
|
|
||||||
|
//clear the slide
|
||||||
if(typeof whiteboardPaperModel !== "undefined" && whiteboardPaperModel !== null) {
|
if(typeof whiteboardPaperModel !== "undefined" && whiteboardPaperModel !== null) {
|
||||||
whiteboardPaperModel.removeAllImagesFromPaper();
|
whiteboardPaperModel.removeAllImagesFromPaper();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hide the cursor
|
||||||
return typeof whiteboardPaperModel !== "undefined" && whiteboardPaperModel !== null ? (ref = whiteboardPaperModel.cursor) != null ? ref.remove() : void 0 : void 0;
|
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();
|
return adjustChatInputHeight();
|
||||||
},
|
},
|
||||||
start() {
|
start() {
|
||||||
if($('#chat').width() / $('#panels').width() > 0.2) {
|
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);
|
return $('#whiteboard').resizable('option', 'maxWidth', $('#panels').width() - 200); // gives the chat enough space (200px)
|
||||||
} else {
|
} else {
|
||||||
return $('#whiteboard').resizable('option', 'maxWidth', $('#whiteboard').width());
|
return $('#whiteboard').resizable('option', 'maxWidth', $('#whiteboard').width());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
stop() {
|
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);
|
return $('#whiteboard').resizable('option', 'maxWidth', null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// whiteboard element needs to be available
|
||||||
Meteor.NotificationControl = new NotificationControl('notificationArea');
|
Meteor.NotificationControl = new NotificationControl('notificationArea');
|
||||||
return $(document).foundation();
|
|
||||||
|
return $(document).foundation(); // initialize foundation javascript
|
||||||
};
|
};
|
||||||
|
|
||||||
Template.presenterUploaderControl.created = function() {
|
Template.presenterUploaderControl.created = function() {
|
||||||
this.isOpen = new ReactiveVar(false);
|
this.isOpen = new ReactiveVar(false);
|
||||||
this.files = new ReactiveList({
|
this.files = new ReactiveList({
|
||||||
sort(a, b) {
|
sort(a, b) {
|
||||||
|
// Put the ones who still uploading first
|
||||||
let ref, ref1;
|
let ref, ref1;
|
||||||
return (ref = a.isUploading === b.isUploading) != null ? ref : {
|
return (ref = a.isUploading === b.isUploading) != null ? ref : {
|
||||||
0: (ref1 = a.isUploading) != null ? ref1 : -{
|
0: (ref1 = a.isUploading) != null ? ref1 : -{
|
||||||
@ -261,7 +270,7 @@ fakeUpload = function(file, list) {
|
|||||||
if(file.isUploading === true) {
|
if(file.isUploading === true) {
|
||||||
return fakeUpload(file, list);
|
return fakeUpload(file, list);
|
||||||
} else {
|
} else {
|
||||||
return list.remove(file.name);
|
return list.remove(file.name); // TODO: Here we should remove and update te presentation on mongo
|
||||||
}
|
}
|
||||||
}), 200);
|
}), 200);
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
// A base class for whiteboard tools
|
||||||
this.WhiteboardToolModel = (function() {
|
this.WhiteboardToolModel = (function() {
|
||||||
class WhiteboardToolModel {
|
class WhiteboardToolModel {
|
||||||
constructor() {}
|
constructor() {}
|
||||||
@ -8,9 +9,13 @@ this.WhiteboardToolModel = (function() {
|
|||||||
this.gh = 0;
|
this.gh = 0;
|
||||||
this.gw = 0;
|
this.gw = 0;
|
||||||
this.obj = 0;
|
this.obj = 0;
|
||||||
|
// the defintion of this shape, kept so we can redraw the shape whenever needed
|
||||||
return this.definition = [];
|
return this.definition = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//set the size of the paper
|
||||||
|
// @param {number} @gh gh parameter
|
||||||
|
// @param {number} @gw gw parameter
|
||||||
setPaperSize(gh, gw) {
|
setPaperSize(gh, gw) {
|
||||||
this.gh = gh;
|
this.gh = gh;
|
||||||
this.gw = gw;
|
this.gw = gw;
|
||||||
@ -22,6 +27,7 @@ this.WhiteboardToolModel = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setPaperDimensions(paperWidth, paperHeight) {
|
setPaperDimensions(paperWidth, paperHeight) {
|
||||||
|
// TODO: can't we simply take the width and the height from `@paper`?
|
||||||
this.paperWidth = paperWidth;
|
this.paperWidth = paperWidth;
|
||||||
this.paperHeight = paperHeight;
|
this.paperHeight = paperHeight;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,11 @@
|
|||||||
|
// General utility methods
|
||||||
|
|
||||||
Meteor.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") {
|
postToUrl(path, params, method="post") {
|
||||||
let $hiddenField, form, key;
|
let $hiddenField, form, key;
|
||||||
form = $("<form></form>");
|
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) {
|
this.formatThickness = function(thickness) {
|
||||||
if(thickness == null) {
|
if(thickness == null) {
|
||||||
thickness = "1";
|
thickness = "1"; // default value
|
||||||
}
|
}
|
||||||
if(!thickness.toString().match(/.*px$/)) {
|
if(!thickness.toString().match(/.*px$/)) {
|
||||||
`#${thickness}px`;
|
`#${thickness}px`; // leading "#" - to be compatible with Firefox
|
||||||
}
|
}
|
||||||
return thickness;
|
return thickness;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// applies zooming to the stroke thickness
|
||||||
this.zoomStroke = function(thickness) {
|
this.zoomStroke = function(thickness) {
|
||||||
let currentSlide, ratio;
|
let currentSlide, ratio;
|
||||||
currentSlide = BBB.getCurrentSlide("zoomStroke");
|
currentSlide = BBB.getCurrentSlide("zoomStroke");
|
||||||
|
@ -4,6 +4,7 @@ const bind = function(fn, me) {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// The cursor/pointer in the whiteboard
|
||||||
this.WhiteboardCursorModel = (function() {
|
this.WhiteboardCursorModel = (function() {
|
||||||
class WhiteboardCursorModel {
|
class WhiteboardCursorModel {
|
||||||
constructor(paper, radius, color) {
|
constructor(paper, radius, color) {
|
||||||
@ -15,7 +16,7 @@ this.WhiteboardCursorModel = (function() {
|
|||||||
this.radius = 6;
|
this.radius = 6;
|
||||||
}
|
}
|
||||||
if(this.color == null) {
|
if(this.color == null) {
|
||||||
this.color = "#ff6666";
|
this.color = "#ff6666"; // a pinkish red
|
||||||
}
|
}
|
||||||
this.cursor = null;
|
this.cursor = null;
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,23 @@
|
|||||||
|
// An ellipse in the whiteboard
|
||||||
this.WhiteboardEllipseModel = (function() {
|
this.WhiteboardEllipseModel = (function() {
|
||||||
class WhiteboardEllipseModel extends WhiteboardToolModel {
|
class WhiteboardEllipseModel extends WhiteboardToolModel {
|
||||||
constructor(paper) {
|
constructor(paper) {
|
||||||
super(paper);
|
super(paper);
|
||||||
this.paper = 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"];
|
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) {
|
make(info) {
|
||||||
|
//console.log "Whiteboard - Making ellipse: "
|
||||||
|
//console.log info
|
||||||
let color, thickness, x, y;
|
let color, thickness, x, y;
|
||||||
if((info != null ? info.points : void 0) != null) {
|
if((info != null ? info.points : void 0) != null) {
|
||||||
x = info.points[0];
|
x = info.points[0];
|
||||||
@ -21,7 +32,14 @@ this.WhiteboardEllipseModel = (function() {
|
|||||||
return this.obj;
|
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) {
|
update(info) {
|
||||||
|
//console.log info
|
||||||
let circle, coords, r, ref, ref1, reversed, rx, ry, x1, x2, y1, y2;
|
let circle, coords, r, ref, ref1, reversed, rx, ry, x1, x2, y1, y2;
|
||||||
if((info != null ? info.points : void 0) != null) {
|
if((info != null ? info.points : void 0) != null) {
|
||||||
x1 = info.points[0];
|
x1 = info.points[0];
|
||||||
@ -37,8 +55,11 @@ this.WhiteboardEllipseModel = (function() {
|
|||||||
ref1 = [y2, y1], y1 = ref1[0], y2 = ref1[1];
|
ref1 = [y2, y1], y1 = ref1[0], y2 = ref1[1];
|
||||||
reversed = true;
|
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(circle) {
|
||||||
if(reversed) {
|
if(reversed) { // if reveresed, the y1 coordinate gets updated, not the y2 coordinate
|
||||||
y1 = y2 - (x2 - x1) * this.gw / this.gh;
|
y1 = y2 - (x2 - x1) * this.gw / this.gh;
|
||||||
} else {
|
} else {
|
||||||
y2 = y1 + (x2 - x1) * this.gw / this.gh;
|
y2 = y1 + (x2 - x1) * this.gw / this.gh;
|
||||||
@ -50,6 +71,9 @@ this.WhiteboardEllipseModel = (function() {
|
|||||||
y1: y1,
|
y1: y1,
|
||||||
y2: y2
|
y2: y2
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//console.log(coords)
|
||||||
|
|
||||||
rx = (x2 - x1) / 2;
|
rx = (x2 - x1) / 2;
|
||||||
ry = (y2 - y1) / 2;
|
ry = (y2 - y1) / 2;
|
||||||
r = {
|
r = {
|
||||||
@ -59,6 +83,9 @@ this.WhiteboardEllipseModel = (function() {
|
|||||||
cy: (ry + y1) * this.gh + this.yOffset
|
cy: (ry + y1) * this.gh + this.yOffset
|
||||||
};
|
};
|
||||||
this.obj.attr(r);
|
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[0] = x1;
|
||||||
this.definition[1] = y1;
|
this.definition[1] = y1;
|
||||||
this.definition[2] = x2;
|
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) {
|
draw(x1, y1, x2, y2, colour, thickness) {
|
||||||
let elip, ref, ref1, rx, ry, x, y;
|
let elip, ref, ref1, rx, ry, x, y;
|
||||||
if(x2 < x1) {
|
if(x2 < x1) {
|
||||||
@ -84,9 +118,53 @@ this.WhiteboardEllipseModel = (function() {
|
|||||||
return elip;
|
return elip;
|
||||||
}
|
}
|
||||||
|
|
||||||
dragOnStart(x, y) {}
|
// When first starting drawing the ellipse
|
||||||
dragOnMove(dx, dy, x, y, e) {}
|
// @param {number} x the x value of cursor at the time in relation to the left side of the browser
|
||||||
dragOnStop(e) {}
|
// @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;
|
return WhiteboardEllipseModel;
|
||||||
|
@ -1,13 +1,27 @@
|
|||||||
let MAX_PATHS_IN_SEQUENCE = 30;
|
let MAX_PATHS_IN_SEQUENCE = 30;
|
||||||
|
|
||||||
this.WhiteboardLineModel = (function() {
|
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 {
|
class WhiteboardLineModel extends WhiteboardToolModel {
|
||||||
constructor(paper) {
|
constructor(paper) {
|
||||||
super(paper);
|
super(paper);
|
||||||
this.paper = 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"];
|
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) {
|
make(info) {
|
||||||
let color, path, pathPercent, thickness, x, x1, y, y1;
|
let color, path, pathPercent, thickness, x, x1, y, y1;
|
||||||
if((info != null ? info.points : void 0) != null) {
|
if((info != null ? info.points : void 0) != null) {
|
||||||
@ -31,6 +45,16 @@ this.WhiteboardLineModel = (function() {
|
|||||||
return this.obj;
|
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) {
|
update(info) {
|
||||||
let path, x1, x2, y1, y2;
|
let path, x1, x2, y1, y2;
|
||||||
if((info != null ? info.points : void 0) != null) {
|
if((info != null ? info.points : void 0) != null) {
|
||||||
@ -49,10 +73,99 @@ this.WhiteboardLineModel = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
draw(x1, y1, x2, y2, colour, thickness) {}
|
// Draw a line on the paper
|
||||||
dragOnStart(x, y) {}
|
// @param {number,string} x1 1) the x value of the first point
|
||||||
dragOnMove(dx, dy, x, y) {}
|
// 2) the string path
|
||||||
dragOnEnd(e) {}
|
// @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) {
|
_buildPath(points) {
|
||||||
let i, path;
|
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) {
|
_scaleLinePath(string, w, h, xOffset, yOffset) {
|
||||||
let j, len, path, points;
|
let j, len, path, points;
|
||||||
if(xOffset == null) {
|
if(xOffset == null) {
|
||||||
@ -81,6 +198,8 @@ this.WhiteboardLineModel = (function() {
|
|||||||
points = string.match(/(\d+[.]?\d*)/g);
|
points = string.match(/(\d+[.]?\d*)/g);
|
||||||
len = points.length;
|
len = points.length;
|
||||||
j = 0;
|
j = 0;
|
||||||
|
|
||||||
|
// go through each point and multiply it by the new height and width
|
||||||
while(j < len) {
|
while(j < len) {
|
||||||
if(j !== 0) {
|
if(j !== 0) {
|
||||||
path += `${points[j + 1] * h}${yOffset}L${points[j] * w + xOffset},${points[j + 1] * h + yOffset}`;
|
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() {
|
Meteor.WhiteboardPaperModel = (function() {
|
||||||
class WhiteboardPaperModel {
|
class WhiteboardPaperModel {
|
||||||
|
|
||||||
|
// Container must be a DOM element
|
||||||
constructor(container) {
|
constructor(container) {
|
||||||
this.container = container;
|
this.container = container;
|
||||||
|
|
||||||
|
// a WhiteboardCursorModel
|
||||||
this.cursor = null;
|
this.cursor = null;
|
||||||
|
|
||||||
|
// all slides in the presentation indexed by url
|
||||||
this.slides = {};
|
this.slides = {};
|
||||||
this.panX = null;
|
this.panX = null;
|
||||||
this.panY = null;
|
this.panY = null;
|
||||||
this.current = {};
|
this.current = {};
|
||||||
|
|
||||||
|
// the slide being shown
|
||||||
this.current.slide = null;
|
this.current.slide = null;
|
||||||
|
|
||||||
|
// a raphaeljs set with all the shapes in the current slide
|
||||||
this.current.shapes = null;
|
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.current.shapeDefinitions = [];
|
||||||
this.zoomLevel = 1;
|
this.zoomLevel = 1;
|
||||||
this.shiftPressed = false;
|
this.shiftPressed = false;
|
||||||
@ -21,7 +36,13 @@ Meteor.WhiteboardPaperModel = (function() {
|
|||||||
this.heightRatio = 100;
|
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() {
|
create() {
|
||||||
|
// paper is embedded within the div#slide of the page.
|
||||||
|
// @raphaelObj ?= ScaleRaphael(@container, "900", "500")
|
||||||
|
|
||||||
let h, w;
|
let h, w;
|
||||||
h = $(`#${this.container}`).height();
|
h = $(`#${this.container}`).height();
|
||||||
w = $(`#${this.container}`).width();
|
w = $(`#${this.container}`).width();
|
||||||
@ -36,7 +57,7 @@ Meteor.WhiteboardPaperModel = (function() {
|
|||||||
if(this.slides) {
|
if(this.slides) {
|
||||||
this.rebuild();
|
this.rebuild();
|
||||||
} else {
|
} else {
|
||||||
this.slides = {};
|
this.slides = {}; // if previously loaded
|
||||||
}
|
}
|
||||||
if(navigator.userAgent.indexOf("Firefox") !== -1) {
|
if(navigator.userAgent.indexOf("Firefox") !== -1) {
|
||||||
this.raphaelObj.renderfix();
|
this.raphaelObj.renderfix();
|
||||||
@ -44,6 +65,8 @@ Meteor.WhiteboardPaperModel = (function() {
|
|||||||
return this.raphaelObj;
|
return this.raphaelObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Re-add the images to the paper that are found
|
||||||
|
// in the slides array (an object of urls and dimensions).
|
||||||
rebuild() {
|
rebuild() {
|
||||||
let results, url;
|
let results, url;
|
||||||
this.current.slide = null;
|
this.current.slide = null;
|
||||||
@ -65,14 +88,29 @@ Meteor.WhiteboardPaperModel = (function() {
|
|||||||
return (ref = this.raphaelObj) != null ? ref.changeSize(width, height) : void 0;
|
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) {
|
addImageToPaper(url, width, height) {
|
||||||
let cx, cy, img, max, sh, sw;
|
let cx, cy, img, max, sh, sw;
|
||||||
this._updateContainerDimensions();
|
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);
|
max = Math.max(width / this.containerWidth, height / this.containerHeight);
|
||||||
|
// fit it all in appropriately
|
||||||
url = this._slideUrl(url);
|
url = this._slideUrl(url);
|
||||||
sw = width / max;
|
sw = width / max;
|
||||||
sh = height / 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);
|
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);
|
this.slides[url] = new WhiteboardSlideModel(img.id, url, img, width, height, sw, sh, cx, cy);
|
||||||
if(this.current.slide == null) {
|
if(this.current.slide == null) {
|
||||||
img.toBack();
|
img.toBack();
|
||||||
@ -82,9 +120,12 @@ Meteor.WhiteboardPaperModel = (function() {
|
|||||||
} else {
|
} else {
|
||||||
img.hide();
|
img.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: other places might also required an update in these dimensions
|
||||||
this._updateContainerDimensions();
|
this._updateContainerDimensions();
|
||||||
|
|
||||||
this._updateZoomRatios();
|
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);
|
this.cursor.setRadius(0.65 * this.widthRatio / 100);
|
||||||
} else {
|
} else {
|
||||||
this.cursor.setRadius(6 * this.widthRatio / 100);
|
this.cursor.setRadius(6 * this.widthRatio / 100);
|
||||||
@ -92,6 +133,7 @@ Meteor.WhiteboardPaperModel = (function() {
|
|||||||
return img;
|
return img;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Removes all the images from the Raphael paper.
|
||||||
removeAllImagesFromPaper() {
|
removeAllImagesFromPaper() {
|
||||||
let ref, ref1, url;
|
let ref, ref1, url;
|
||||||
for(url in this.slides) {
|
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) {
|
if ((ref = this.raphaelObj.getById((ref1 = this.slides[url]) != null ? ref1.getId() : void 0)) != null) {
|
||||||
ref.remove();
|
ref.remove();
|
||||||
}
|
}
|
||||||
|
//@trigger('paper:image:removed', @slides[url].getId()) # Removes the previous image preventing images from being redrawn over each other repeatedly
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.slides = {};
|
this.slides = {};
|
||||||
return this.current.slide = null;
|
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) {
|
setCurrentTool(tool) {
|
||||||
this.currentTool = tool;
|
this.currentTool = tool;
|
||||||
console.log("setting current tool to", tool);
|
console.log("setting current tool to", tool);
|
||||||
@ -122,6 +169,7 @@ Meteor.WhiteboardPaperModel = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear all shapes from this paper.
|
||||||
clearShapes() {
|
clearShapes() {
|
||||||
if(this.current.shapes != null) {
|
if(this.current.shapes != null) {
|
||||||
this.current.shapes.forEach(element => {
|
this.current.shapes.forEach(element => {
|
||||||
@ -140,7 +188,7 @@ Meteor.WhiteboardPaperModel = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
createCursor() {
|
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 = new WhiteboardCursorModel(this.raphaelObj, 0.65);
|
||||||
this.cursor.setRadius(0.65 * this.widthRatio / 100);
|
this.cursor.setRadius(0.65 * this.widthRatio / 100);
|
||||||
} else {
|
} else {
|
||||||
@ -150,14 +198,20 @@ Meteor.WhiteboardPaperModel = (function() {
|
|||||||
return this.cursor.draw();
|
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) {
|
updateShape(shape, data) {
|
||||||
return this.current[shape].update(data);
|
return this.current[shape].update(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make a shape `shape` with the data in `data`.
|
||||||
makeShape(shape, data) {
|
makeShape(shape, data) {
|
||||||
let base, base1, i, len, obj, tool, toolModel;
|
let base, base1, i, len, obj, tool, toolModel;
|
||||||
data.thickness *= this.adjustedWidth / 1000;
|
data.thickness *= this.adjustedWidth / 1000;
|
||||||
tool = null;
|
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);
|
this.current[shape] = this._createTool(shape);
|
||||||
toolModel = this.current[shape];
|
toolModel = this.current[shape];
|
||||||
tool = this.current[shape].make(data);
|
tool = this.current[shape].make(data);
|
||||||
@ -168,6 +222,8 @@ Meteor.WhiteboardPaperModel = (function() {
|
|||||||
this.current.shapes.push(tool);
|
this.current.shapes.push(tool);
|
||||||
this.current.shapeDefinitions.push(toolModel.getDefinition());
|
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((tool != null) && shape === "poll_result") {
|
||||||
if((base1 = this.current).shapes == null) {
|
if((base1 = this.current).shapes == null) {
|
||||||
base1.shapes = this.raphaelObj.set();
|
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) {
|
moveCursor(x, y) {
|
||||||
let cx, cy, ref, ref1, slideHeight, slideWidth;
|
let cx, cy, ref, ref1, slideHeight, slideWidth;
|
||||||
ref = this._currentSlideOffsets(), cx = ref[0], cy = ref[1];
|
ref = this._currentSlideOffsets(), cx = ref[0], cy = ref[1];
|
||||||
ref1 = this._currentSlideOriginalDimensions(), slideWidth = ref1[0], slideHeight = ref1[1];
|
ref1 = this._currentSlideOriginalDimensions(), slideWidth = ref1[0], slideHeight = ref1[1];
|
||||||
this.cursor.setPosition(x * slideWidth + cx, y * slideHeight + cy);
|
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)) {
|
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);
|
return this.cursor.setPosition(this.viewBoxXpos + x * this.viewBoxWidth, this.viewBoxYPos + y * this.viewBoxHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
zoomAndPan(widthRatio, heightRatio, xOffset, yOffset) {
|
zoomAndPan(widthRatio, heightRatio, xOffset, yOffset) {
|
||||||
|
// console.log "zoomAndPan #{widthRatio} #{heightRatio} #{xOffset} #{yOffset}"
|
||||||
let newHeight, newWidth, newX, newY;
|
let newHeight, newWidth, newX, newY;
|
||||||
newX = -xOffset * 2 * this.adjustedWidth / 100;
|
newX = -xOffset * 2 * this.adjustedWidth / 100;
|
||||||
newY = -yOffset * 2 * this.adjustedHeight / 100;
|
newY = -yOffset * 2 * this.adjustedHeight / 100;
|
||||||
@ -204,6 +266,7 @@ Meteor.WhiteboardPaperModel = (function() {
|
|||||||
return this.adjustedHeight = height;
|
return this.adjustedHeight = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the dimensions of the container.
|
||||||
_updateContainerDimensions() {
|
_updateContainerDimensions() {
|
||||||
let $container, containerDimensions, ref, ref1;
|
let $container, containerDimensions, ref, ref1;
|
||||||
$container = $('#whiteboard-paper');
|
$container = $('#whiteboard-paper');
|
||||||
@ -229,6 +292,10 @@ Meteor.WhiteboardPaperModel = (function() {
|
|||||||
return this.heightRatio = currentSlideDoc != null ? currentSlideDoc.slide.height_ratio : void 0;
|
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) {
|
_getImageFromPaper(url) {
|
||||||
let id;
|
let id;
|
||||||
if(this.slides[url]) {
|
if(this.slides[url]) {
|
||||||
@ -264,6 +331,7 @@ Meteor.WhiteboardPaperModel = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wrapper method to create a tool for the whiteboard
|
||||||
_createTool(type) {
|
_createTool(type) {
|
||||||
let height, model, ref, ref1, ref2, slideHeight, slideWidth, tool, width, xOffset, yOffset;
|
let height, model, ref, ref1, ref2, slideHeight, slideWidth, tool, width, xOffset, yOffset;
|
||||||
switch(type) {
|
switch(type) {
|
||||||
@ -294,6 +362,7 @@ Meteor.WhiteboardPaperModel = (function() {
|
|||||||
ref1 = this._currentSlideOffsets(), xOffset = ref1[0], yOffset = ref1[1];
|
ref1 = this._currentSlideOffsets(), xOffset = ref1[0], yOffset = ref1[1];
|
||||||
ref2 = this._currentSlideDimensions(), width = ref2[0], height = ref2[1];
|
ref2 = this._currentSlideDimensions(), width = ref2[0], height = ref2[1];
|
||||||
tool = new model(this.raphaelObj);
|
tool = new model(this.raphaelObj);
|
||||||
|
// TODO: why are the parameters inverted and it works?
|
||||||
tool.setPaperSize(slideHeight, slideWidth);
|
tool.setPaperSize(slideHeight, slideWidth);
|
||||||
tool.setOffsets(xOffset, yOffset);
|
tool.setOffsets(xOffset, yOffset);
|
||||||
tool.setPaperDimensions(width, height);
|
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) {
|
_slideUrl(url) {
|
||||||
if(url != null ? url.match(/http[s]?:/) : void 0) {
|
if(url != null ? url.match(/http[s]?:/) : void 0) {
|
||||||
return url;
|
return url;
|
||||||
} else {
|
} else {
|
||||||
return console.log(`The url '${url}'' did not match the expected format of: http/s`);
|
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) {
|
_displayPage(data, originalWidth, originalHeight) {
|
||||||
let _this, boardHeight, boardWidth, currentPresentation, currentSlide, currentSlideCursor, presentationId, ref;
|
let _this, boardHeight, boardWidth, currentPresentation, currentSlide, currentSlideCursor, presentationId, ref;
|
||||||
this.removeAllImagesFromPaper();
|
this.removeAllImagesFromPaper();
|
||||||
@ -330,7 +403,7 @@ Meteor.WhiteboardPaperModel = (function() {
|
|||||||
this.zoomObserver.stop();
|
this.zoomObserver.stop();
|
||||||
}
|
}
|
||||||
_this = this;
|
_this = this;
|
||||||
this.zoomObserver = currentSlideCursor.observe({
|
this.zoomObserver = currentSlideCursor.observe({ // watching the current slide changes
|
||||||
changed(newDoc, oldDoc) {
|
changed(newDoc, oldDoc) {
|
||||||
let newRatio, oldRatio, ref1, ref2;
|
let newRatio, oldRatio, ref1, ref2;
|
||||||
if(originalWidth <= originalHeight) {
|
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);
|
return _this.cursor.setRadius(0.65 * newDoc.slide.width_ratio / 100);
|
||||||
} else {
|
} else {
|
||||||
return _this.cursor.setRadius(6 * newDoc.slide.width_ratio / 100);
|
return _this.cursor.setRadius(6 * newDoc.slide.width_ratio / 100);
|
||||||
@ -365,6 +438,7 @@ Meteor.WhiteboardPaperModel = (function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
if(originalWidth <= originalHeight) {
|
if(originalWidth <= originalHeight) {
|
||||||
|
// square => boardHeight is the shortest side
|
||||||
this.adjustedWidth = boardHeight * originalWidth / originalHeight;
|
this.adjustedWidth = boardHeight * originalWidth / originalHeight;
|
||||||
$('#whiteboard-paper').width(this.adjustedWidth);
|
$('#whiteboard-paper').width(this.adjustedWidth);
|
||||||
this.addImageToPaper(data, this.adjustedWidth, boardHeight);
|
this.addImageToPaper(data, this.adjustedWidth, boardHeight);
|
||||||
|
@ -5,17 +5,30 @@ let bind = function(fn, me) {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// A poll in the whiteboard
|
||||||
this.WhiteboardPollModel = (function() {
|
this.WhiteboardPollModel = (function() {
|
||||||
class WhiteboardPollModel extends WhiteboardToolModel {
|
class WhiteboardPollModel extends WhiteboardToolModel {
|
||||||
constructor(paper1) {
|
constructor(paper1) {
|
||||||
super(paper1);
|
super(paper1);
|
||||||
this.paper = paper1;
|
this.paper = paper1;
|
||||||
this.make = bind(this.make, this);
|
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"];
|
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) {
|
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;
|
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];
|
x1 = startingData.points[0];
|
||||||
y1 = startingData.points[1];
|
y1 = startingData.points[1];
|
||||||
x2 = startingData.points[2] + startingData.points[0] - 0.001;
|
x2 = startingData.points[2] + startingData.points[0] - 0.001;
|
||||||
@ -28,7 +41,10 @@ this.WhiteboardPollModel = (function() {
|
|||||||
votesTotal = 0;
|
votesTotal = 0;
|
||||||
maxNumVotes = 0;
|
maxNumVotes = 0;
|
||||||
textArray = [];
|
textArray = [];
|
||||||
|
|
||||||
|
//creating an array of text objects for the labels, percentages and number inside line bars
|
||||||
if(startingData.result != null) {
|
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) {
|
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;
|
votesTotal += startingData.result[i].num_votes;
|
||||||
if(maxNumVotes < startingData.result[i].num_votes) {
|
if(maxNumVotes < startingData.result[i].num_votes) {
|
||||||
@ -36,6 +52,7 @@ this.WhiteboardPollModel = (function() {
|
|||||||
}
|
}
|
||||||
textArray[i] = [];
|
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) {
|
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}`);
|
textArray[i].push(startingData.result[i].key, `${startingData.result[i].num_votes}`);
|
||||||
if(votesTotal === 0) {
|
if(votesTotal === 0) {
|
||||||
@ -46,16 +63,26 @@ this.WhiteboardPollModel = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//if coordinates are reversed - change them back
|
||||||
if(x2 < x1) {
|
if(x2 < x1) {
|
||||||
ref2 = [x2, x1], x1 = ref2[0], x2 = ref2[1];
|
ref2 = [x2, x1], x1 = ref2[0], x2 = ref2[1];
|
||||||
}
|
}
|
||||||
if(y2 < y1) {
|
if(y2 < y1) {
|
||||||
ref3 = [y2, y1], y1 = ref3[0], y2 = ref3[1];
|
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;
|
x = x1 * this.gw + this.xOffset;
|
||||||
y = y1 * this.gh + this.yOffset;
|
y = y1 * this.gh + this.yOffset;
|
||||||
width = (x2 * this.gw + this.xOffset) - x;
|
width = (x2 * this.gw + this.xOffset) - x;
|
||||||
height = (y2 * this.gh + this.yOffset) - y;
|
height = (y2 * this.gh + this.yOffset) - y;
|
||||||
|
|
||||||
|
//creating a base outer rectangle
|
||||||
this.obj = this.paper.rect(x, y, width, height, 0);
|
this.obj = this.paper.rect(x, y, width, height, 0);
|
||||||
this.obj.attr("fill", backgroundColor);
|
this.obj.attr("fill", backgroundColor);
|
||||||
this.obj.attr("stroke-width", 0);
|
this.obj.attr("stroke-width", 0);
|
||||||
@ -63,10 +90,14 @@ this.WhiteboardPollModel = (function() {
|
|||||||
shape: "poll_result",
|
shape: "poll_result",
|
||||||
data: [x1, y1, x2, y2, this.obj.attrs["stroke"], this.obj.attrs["stroke-width"], this.obj.attrs["fill"]]
|
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;
|
width = width * 0.95;
|
||||||
height = height - width * 0.05;
|
height = height - width * 0.05;
|
||||||
x = x + width * 0.025;
|
x = x + width * 0.025;
|
||||||
y = y + 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 = this.paper.rect(x, y, width, height, 0);
|
||||||
this.obj1.attr("stroke", "#333333");
|
this.obj1.attr("stroke", "#333333");
|
||||||
this.obj1.attr("fill", backgroundColor);
|
this.obj1.attr("fill", backgroundColor);
|
||||||
@ -75,6 +106,8 @@ this.WhiteboardPollModel = (function() {
|
|||||||
shape: "poll_result",
|
shape: "poll_result",
|
||||||
data: [x1, y1, x2, y2, this.obj.attrs["stroke"], this.obj1.attrs["stroke-width"], this.obj1.attrs["fill"]]
|
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);
|
calculatedData = calculateFontAndWidth(textArray, calcFontSize, width, height, x, y);
|
||||||
calcFontSize = calculatedData[0];
|
calcFontSize = calculatedData[0];
|
||||||
maxLeftWidth = calculatedData[1];
|
maxLeftWidth = calculatedData[1];
|
||||||
@ -84,6 +117,8 @@ this.WhiteboardPollModel = (function() {
|
|||||||
maxBarWidth = width * 0.9 - maxLeftWidth - maxRightWidth;
|
maxBarWidth = width * 0.9 - maxLeftWidth - maxRightWidth;
|
||||||
barHeight = height * 0.75 / textArray.length;
|
barHeight = height * 0.75 / textArray.length;
|
||||||
svgNSi = "http://www.w3.org/2000/svg";
|
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 = this.paper.text(x, y, "");
|
||||||
this.obj2.attr({
|
this.obj2.attr({
|
||||||
"fill": "#333333",
|
"fill": "#333333",
|
||||||
@ -96,6 +131,8 @@ this.WhiteboardPollModel = (function() {
|
|||||||
while((leftCell != null) && leftCell.hasChildNodes()) {
|
while((leftCell != null) && leftCell.hasChildNodes()) {
|
||||||
leftCell.removeChild(leftCell.firstChild);
|
leftCell.removeChild(leftCell.firstChild);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Initializing a text element for the right column of percentages
|
||||||
this.obj3 = this.paper.text(x, y, "");
|
this.obj3 = this.paper.text(x, y, "");
|
||||||
this.obj3.attr({
|
this.obj3.attr({
|
||||||
"fill": "#333333",
|
"fill": "#333333",
|
||||||
@ -108,19 +145,39 @@ this.WhiteboardPollModel = (function() {
|
|||||||
while((rightCell != null) && rightCell.hasChildNodes()) {
|
while((rightCell != null) && rightCell.hasChildNodes()) {
|
||||||
rightCell.removeChild(rightCell.firstChild);
|
rightCell.removeChild(rightCell.firstChild);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//setting a font size for the text elements on the left and on the right
|
||||||
leftCell.style['font-size'] = calcFontSize;
|
leftCell.style['font-size'] = calcFontSize;
|
||||||
rightCell.style['font-size'] = calcFontSize;
|
rightCell.style['font-size'] = calcFontSize;
|
||||||
|
//Horizontal padding
|
||||||
horizontalPadding = width * 0.1 / 4;
|
horizontalPadding = width * 0.1 / 4;
|
||||||
|
//Vertical padding
|
||||||
verticalPadding = height * 0.25 / (textArray.length + 1);
|
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;
|
magicNumber = 3.5;
|
||||||
|
//*****************************************************************************************************
|
||||||
|
|
||||||
|
//Initial coordinates of the key column
|
||||||
yLeft = y + verticalPadding + barHeight / 2 - magicNumber;
|
yLeft = y + verticalPadding + barHeight / 2 - magicNumber;
|
||||||
xLeft = x + horizontalPadding + 1;
|
xLeft = x + horizontalPadding + 1;
|
||||||
|
//Initial coordinates of the line bar column
|
||||||
xBar = x + maxLeftWidth + horizontalPadding * 2;
|
xBar = x + maxLeftWidth + horizontalPadding * 2;
|
||||||
yBar = y + verticalPadding;
|
yBar = y + verticalPadding;
|
||||||
|
//Initial coordinates of the percentage column
|
||||||
yRight = y + verticalPadding + barHeight / 2 - magicNumber;
|
yRight = y + verticalPadding + barHeight / 2 - magicNumber;
|
||||||
xRight = x + horizontalPadding * 3 + maxLeftWidth + maxRightWidth + maxBarWidth + 1;
|
xRight = x + horizontalPadding * 3 + maxLeftWidth + maxRightWidth + maxBarWidth + 1;
|
||||||
objects = [this.obj, this.obj1, this.obj2, this.obj3];
|
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) {
|
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 = document.createElementNS(svgNSi, "tspan");
|
||||||
tempSpanEl.setAttributeNS(null, "x", xLeft);
|
tempSpanEl.setAttributeNS(null, "x", xLeft);
|
||||||
tempSpanEl.setAttributeNS(null, "y", yLeft);
|
tempSpanEl.setAttributeNS(null, "y", yLeft);
|
||||||
@ -128,6 +185,8 @@ this.WhiteboardPollModel = (function() {
|
|||||||
tempTextNode = document.createTextNode(textArray[i][0]);
|
tempTextNode = document.createTextNode(textArray[i][0]);
|
||||||
tempSpanEl.appendChild(tempTextNode);
|
tempSpanEl.appendChild(tempTextNode);
|
||||||
leftCell.appendChild(tempSpanEl);
|
leftCell.appendChild(tempSpanEl);
|
||||||
|
|
||||||
|
//drawing a black graph bar
|
||||||
if(maxNumVotes === 0 || startingData.result[i].num_votes === 0) {
|
if(maxNumVotes === 0 || startingData.result[i].num_votes === 0) {
|
||||||
barWidth = 2;
|
barWidth = 2;
|
||||||
} else {
|
} else {
|
||||||
@ -138,6 +197,8 @@ this.WhiteboardPollModel = (function() {
|
|||||||
this.obj4.attr("fill", "#333333");
|
this.obj4.attr("fill", "#333333");
|
||||||
this.obj4.attr("stroke-width", zoomStroke(formatThickness(0)));
|
this.obj4.attr("stroke-width", zoomStroke(formatThickness(0)));
|
||||||
objects.push(this.obj4);
|
objects.push(this.obj4);
|
||||||
|
|
||||||
|
//Adding an element to the right column
|
||||||
tempSpanEl = document.createElementNS(svgNSi, "tspan");
|
tempSpanEl = document.createElementNS(svgNSi, "tspan");
|
||||||
tempSpanEl.setAttributeNS(null, "x", xRight);
|
tempSpanEl.setAttributeNS(null, "x", xRight);
|
||||||
tempSpanEl.setAttributeNS(null, "y", yRight);
|
tempSpanEl.setAttributeNS(null, "y", yRight);
|
||||||
@ -145,10 +206,14 @@ this.WhiteboardPollModel = (function() {
|
|||||||
tempTextNode = document.createTextNode(textArray[i][2]);
|
tempTextNode = document.createTextNode(textArray[i][2]);
|
||||||
tempSpanEl.appendChild(tempTextNode);
|
tempSpanEl.appendChild(tempTextNode);
|
||||||
rightCell.appendChild(tempSpanEl);
|
rightCell.appendChild(tempSpanEl);
|
||||||
|
|
||||||
|
//changing the Y coordinate for all the objects
|
||||||
yBar = yBar + barHeight + verticalPadding;
|
yBar = yBar + barHeight + verticalPadding;
|
||||||
yLeft = yLeft + barHeight + verticalPadding;
|
yLeft = yLeft + barHeight + verticalPadding;
|
||||||
yRight = yRight + 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 = this.paper.text(x, y, "");
|
||||||
this.obj5.attr({
|
this.obj5.attr({
|
||||||
"fill": "#333333",
|
"fill": "#333333",
|
||||||
@ -159,10 +224,14 @@ this.WhiteboardPollModel = (function() {
|
|||||||
while((centerCell != null) && centerCell.hasChildNodes()) {
|
while((centerCell != null) && centerCell.hasChildNodes()) {
|
||||||
centerCell.removeChild(centerCell.firstChild);
|
centerCell.removeChild(centerCell.firstChild);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Initial coordinates of the text inside the bar column
|
||||||
xNumVotesDefault = x + maxLeftWidth + horizontalPadding * 2;
|
xNumVotesDefault = x + maxLeftWidth + horizontalPadding * 2;
|
||||||
xNumVotesMovedRight = xNumVotesDefault + barWidth / 2 + horizontalPadding + maxDigitWidth / 2;
|
xNumVotesMovedRight = xNumVotesDefault + barWidth / 2 + horizontalPadding + maxDigitWidth / 2;
|
||||||
yNumVotes = y + verticalPadding - magicNumber;
|
yNumVotes = y + verticalPadding - magicNumber;
|
||||||
color = "white";
|
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) {
|
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) {
|
if(maxNumVotes === 0 || startingData.result[i].num_votes === 0) {
|
||||||
barWidth = 2;
|
barWidth = 2;
|
||||||
@ -190,6 +259,7 @@ this.WhiteboardPollModel = (function() {
|
|||||||
return objects;
|
return objects;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the poll dimensions. Does nothing.
|
||||||
update(startingData) {}
|
update(startingData) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,8 +269,12 @@ this.WhiteboardPollModel = (function() {
|
|||||||
calculateFontAndWidth = function(textArray, calcFontSize, width, height, x, y) {
|
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;
|
let calculatedData, flag, i, j, k, l, len, line, m, maxDigitWidth, maxLeftWidth, maxLineHeight, maxLineWidth, maxRightWidth, ref, ref1, spanHeight, spanWidth, test;
|
||||||
calculatedData = [];
|
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;
|
maxLineWidth = width / 3;
|
||||||
maxLineHeight = height * 0.75 / (textArray != null ? textArray.length : void 0);
|
maxLineHeight = height * 0.75 / (textArray != null ? textArray.length : void 0);
|
||||||
|
|
||||||
|
//calculating a proper font-size
|
||||||
flag = true;
|
flag = true;
|
||||||
while(flag) {
|
while(flag) {
|
||||||
flag = false;
|
flag = false;
|
||||||
@ -217,6 +291,8 @@ calculateFontAndWidth = function(textArray, calcFontSize, width, height, x, y) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
calculatedData.push(calcFontSize);
|
calculatedData.push(calcFontSize);
|
||||||
|
|
||||||
|
//looking for a maximum width and height of the left and right text elements
|
||||||
maxLeftWidth = 0;
|
maxLeftWidth = 0;
|
||||||
maxRightWidth = 0;
|
maxRightWidth = 0;
|
||||||
maxLineHeight = 0;
|
maxLineHeight = 0;
|
||||||
|
@ -4,15 +4,24 @@ const bind = function(fn, me) {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// A rectangle in the whiteboard
|
||||||
this.WhiteboardRectModel = (function() {
|
this.WhiteboardRectModel = (function() {
|
||||||
class WhiteboardRectModel extends WhiteboardToolModel{
|
class WhiteboardRectModel extends WhiteboardToolModel{
|
||||||
constructor(paper) {
|
constructor(paper) {
|
||||||
super(paper);
|
super(paper);
|
||||||
this.paper = paper;
|
this.paper = paper;
|
||||||
this.make = bind(this.make, this);
|
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"];
|
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) {
|
make(startingData) {
|
||||||
let color, thickness, x, y;
|
let color, thickness, x, y;
|
||||||
x = startingData.points[0];
|
x = startingData.points[0];
|
||||||
@ -29,6 +38,12 @@ this.WhiteboardRectModel = (function() {
|
|||||||
return this.obj;
|
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) {
|
update(startingData) {
|
||||||
let height, ref, ref1, reversed, square, width, x, x1, x2, y, y1, y2;
|
let height, ref, ref1, reversed, square, width, x, x1, x2, y, y1, y2;
|
||||||
x1 = startingData.points[0];
|
x1 = startingData.points[0];
|
||||||
@ -45,7 +60,7 @@ this.WhiteboardRectModel = (function() {
|
|||||||
reversed = true;
|
reversed = true;
|
||||||
}
|
}
|
||||||
if(square) {
|
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;
|
y1 = y2 - (x2 - x1) * this.gw / this.gh;
|
||||||
} else {
|
} else {
|
||||||
y2 = y1 + (x2 - x1) * this.gw / this.gh;
|
y2 = y1 + (x2 - x1) * this.gw / this.gh;
|
||||||
@ -55,6 +70,7 @@ this.WhiteboardRectModel = (function() {
|
|||||||
y = y1 * this.gh + this.yOffset;
|
y = y1 * this.gh + this.yOffset;
|
||||||
width = (x2 * this.gw + this.xOffset) - x;
|
width = (x2 * this.gw + this.xOffset) - x;
|
||||||
height = (y2 * this.gh + this.yOffset) - y;
|
height = (y2 * this.gh + this.yOffset) - y;
|
||||||
|
//if !square
|
||||||
this.obj.attr({
|
this.obj.attr({
|
||||||
x: x,
|
x: x,
|
||||||
y: y,
|
y: y,
|
||||||
@ -69,6 +85,8 @@ this.WhiteboardRectModel = (function() {
|
|||||||
width: width
|
width: width
|
||||||
height: 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[0] = x1;
|
||||||
this.definition.data[1] = y1;
|
this.definition.data[1] = y1;
|
||||||
this.definition.data[2] = x2;
|
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) {
|
draw(x1, y1, x2, y2, colour, thickness) {
|
||||||
let r, ref, ref1, x, y;
|
let r, ref, ref1, x, y;
|
||||||
if(x2 < x1) {
|
if(x2 < x1) {
|
||||||
@ -91,9 +116,58 @@ this.WhiteboardRectModel = (function() {
|
|||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
dragOnStart(x, y) {}
|
// Creating a rectangle has started
|
||||||
dragOnMove(dx, dy, x, y, e) {}
|
// @param {number} x the x value of cursor at the time in relation to the left side of the browser
|
||||||
dragOnEnd(e) {}
|
// @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;
|
return WhiteboardRectModel;
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
// A slide in the whiteboard
|
||||||
this.WhiteboardSlideModel = (function() {
|
this.WhiteboardSlideModel = (function() {
|
||||||
class WhiteboardSlideModel {
|
class WhiteboardSlideModel {
|
||||||
|
|
||||||
|
// TODO: check if we really need original and display width and heights separate or if they can be the same
|
||||||
constructor(
|
constructor(
|
||||||
id,
|
id,
|
||||||
url,
|
url,
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
// A text in the whiteboard
|
||||||
this.WhiteboardTextModel = (function() {
|
this.WhiteboardTextModel = (function() {
|
||||||
let checkDashPosition, checkWidth;
|
let checkDashPosition, checkWidth;
|
||||||
|
|
||||||
@ -5,10 +6,15 @@ this.WhiteboardTextModel = (function() {
|
|||||||
constructor(paper) {
|
constructor(paper) {
|
||||||
super(paper);
|
super(paper);
|
||||||
this.paper = 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, ""];
|
this.definition = [0, 0, 0, 0, "#000", 0, 0, ""];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make a text on the whiteboard
|
||||||
make(startingData) {
|
make(startingData) {
|
||||||
|
//console.log "making text:" + JSON.stringify startingData
|
||||||
let calcFontSize, colour, fontSize, height, text, width, x, y;
|
let calcFontSize, colour, fontSize, height, text, width, x, y;
|
||||||
x = startingData.x;
|
x = startingData.x;
|
||||||
y = startingData.y;
|
y = startingData.y;
|
||||||
@ -22,21 +28,26 @@ this.WhiteboardTextModel = (function() {
|
|||||||
shape: "text",
|
shape: "text",
|
||||||
data: [x, y, width, height, colour, fontSize, calcFontSize, text]
|
data: [x, y, width, height, colour, fontSize, calcFontSize, text]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//calcFontSize = (calcFontSize/100 * @gh)
|
||||||
x = (x * this.gw) + this.xOffset;
|
x = (x * this.gw) + this.xOffset;
|
||||||
y = (y * this.gh) + this.yOffset + calcFontSize;
|
y = (y * this.gh) + this.yOffset + calcFontSize;
|
||||||
width = width / 100 * this.gw;
|
width = width / 100 * this.gw;
|
||||||
this.obj = this.paper.text(x / 100, y / 100, "");
|
this.obj = this.paper.text(x / 100, y / 100, "");
|
||||||
this.obj.attr({
|
this.obj.attr({
|
||||||
"fill": colour,
|
"fill": colour,
|
||||||
"font-family": "Arial",
|
"font-family": "Arial", // TODO: make dynamic
|
||||||
"font-size": calcFontSize
|
"font-size": calcFontSize
|
||||||
});
|
});
|
||||||
this.obj.node.style["text-anchor"] = "start";
|
this.obj.node.style["text-anchor"] = "start"; // force left align
|
||||||
this.obj.node.style["textAnchor"] = "start";
|
this.obj.node.style["textAnchor"] = "start"; // for firefox, 'cause they like to be different
|
||||||
return this.obj;
|
return this.obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update text shape drawn
|
||||||
|
// @param {object} the object containing the shape info
|
||||||
update(startingData) {
|
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;
|
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;
|
x = startingData.x;
|
||||||
y = startingData.y;
|
y = startingData.y;
|
||||||
@ -53,13 +64,18 @@ this.WhiteboardTextModel = (function() {
|
|||||||
maxWidth = maxWidth / 100 * this.gw;
|
maxWidth = maxWidth / 100 * this.gw;
|
||||||
this.obj.attr({
|
this.obj.attr({
|
||||||
"fill": colour,
|
"fill": colour,
|
||||||
"font-family": "Arial",
|
"font-family": "Arial", // TODO: make dynamic
|
||||||
"font-size": calcFontSize
|
"font-size": calcFontSize
|
||||||
});
|
});
|
||||||
cell = this.obj.node;
|
cell = this.obj.node;
|
||||||
while((cell != null) && cell.hasChildNodes()) {
|
while((cell != null) && cell.hasChildNodes()) {
|
||||||
cell.removeChild(cell.firstChild);
|
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();
|
dashArray = new Array();
|
||||||
dashFound = true;
|
dashFound = true;
|
||||||
indexPos = 0;
|
indexPos = 0;
|
||||||
@ -68,12 +84,14 @@ this.WhiteboardTextModel = (function() {
|
|||||||
while(dashFound === true) {
|
while(dashFound === true) {
|
||||||
result = myText.indexOf("-", indexPos);
|
result = myText.indexOf("-", indexPos);
|
||||||
if(result === -1) {
|
if(result === -1) {
|
||||||
|
//could not find a dash
|
||||||
dashFound = false;
|
dashFound = false;
|
||||||
} else {
|
} else {
|
||||||
dashArray.push(result);
|
dashArray.push(result);
|
||||||
indexPos = result + 1;
|
indexPos = result + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//split the text at all spaces and dashes
|
||||||
words = myText.split(/[\s-]/);
|
words = myText.split(/[\s-]/);
|
||||||
line = "";
|
line = "";
|
||||||
dy = 0;
|
dy = 0;
|
||||||
@ -82,6 +100,8 @@ this.WhiteboardTextModel = (function() {
|
|||||||
myTextNode = void 0;
|
myTextNode = void 0;
|
||||||
tspanEl = void 0;
|
tspanEl = void 0;
|
||||||
i = 0;
|
i = 0;
|
||||||
|
|
||||||
|
//checking if any of the words exceed the width of a textBox
|
||||||
words = checkWidth(words, maxWidth, x, dy, cell);
|
words = checkWidth(words, maxWidth, x, dy, cell);
|
||||||
while(i < words.length) {
|
while(i < words.length) {
|
||||||
word = words[i];
|
word = words[i];
|
||||||
@ -89,13 +109,15 @@ this.WhiteboardTextModel = (function() {
|
|||||||
if(computedTextLength > maxWidth || i === 0) {
|
if(computedTextLength > maxWidth || i === 0) {
|
||||||
if(computedTextLength > maxWidth) {
|
if(computedTextLength > maxWidth) {
|
||||||
tempText = tspanEl.firstChild.nodeValue;
|
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;
|
tspanEl.firstChild.nodeValue = tempText;
|
||||||
}
|
}
|
||||||
|
//setting up coordinates for the first line of text
|
||||||
if(i === 0) {
|
if(i === 0) {
|
||||||
dy = calcFontSize;
|
dy = calcFontSize;
|
||||||
cumulY += dy;
|
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 = document.createElementNS(svgNS, "tspan");
|
||||||
tspanEl.setAttributeNS(null, "x", x);
|
tspanEl.setAttributeNS(null, "x", x);
|
||||||
tspanEl.setAttributeNS(null, "dy", dy);
|
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) {
|
checkDashPosition = function(dashArray, pos) {
|
||||||
let i, result;
|
let i, result;
|
||||||
result = false;
|
result = false;
|
||||||
@ -153,6 +176,8 @@ this.WhiteboardTextModel = (function() {
|
|||||||
return result;
|
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) {
|
checkWidth = function(words, maxWidth, x, dy, cell) {
|
||||||
let count, num, partWord, start, str, svgNSi, temp, temp3, tempArray, tempSpanEl, tempTextNode, tempWord;
|
let count, num, partWord, start, str, svgNSi, temp, temp3, tempArray, tempSpanEl, tempTextNode, tempWord;
|
||||||
count = 0;
|
count = 0;
|
||||||
@ -166,16 +191,21 @@ this.WhiteboardTextModel = (function() {
|
|||||||
tempTextNode = document.createTextNode(str);
|
tempTextNode = document.createTextNode(str);
|
||||||
tempSpanEl.appendChild(tempTextNode);
|
tempSpanEl.appendChild(tempTextNode);
|
||||||
num = 0;
|
num = 0;
|
||||||
|
//creating a textNode and adding it to the cell to check the width
|
||||||
while(num < temp.length) {
|
while(num < temp.length) {
|
||||||
tempSpanEl.firstChild.nodeValue = temp[num];
|
tempSpanEl.firstChild.nodeValue = temp[num];
|
||||||
cell.appendChild(tempSpanEl);
|
cell.appendChild(tempSpanEl);
|
||||||
|
//if width is bigger than maxWidth + whitespace between textBox borders and a word
|
||||||
if(tempSpanEl.getComputedTextLength() + 10 > maxWidth) {
|
if(tempSpanEl.getComputedTextLength() + 10 > maxWidth) {
|
||||||
tempWord = temp[num];
|
tempWord = temp[num];
|
||||||
cell.removeChild(cell.firstChild);
|
cell.removeChild(cell.firstChild);
|
||||||
|
|
||||||
|
//initializing temp variables
|
||||||
count = 1;
|
count = 1;
|
||||||
start = 0;
|
start = 0;
|
||||||
partWord = `${tempWord[0]}`;
|
partWord = `${tempWord[0]}`;
|
||||||
tempArray = [];
|
tempArray = [];
|
||||||
|
//check the width by increasing the word character by character
|
||||||
while(count < tempWord.length) {
|
while(count < tempWord.length) {
|
||||||
partWord += tempWord[count];
|
partWord += tempWord[count];
|
||||||
tempSpanEl.firstChild.nodeValue = partWord;
|
tempSpanEl.firstChild.nodeValue = partWord;
|
||||||
|
@ -1,11 +1,20 @@
|
|||||||
|
// A triangle in the whiteboard
|
||||||
this.WhiteboardTriangleModel = (function() {
|
this.WhiteboardTriangleModel = (function() {
|
||||||
class WhiteboardTriangleModel extends WhiteboardToolModel {
|
class WhiteboardTriangleModel extends WhiteboardToolModel {
|
||||||
constructor(paper) {
|
constructor(paper) {
|
||||||
super(paper);
|
super(paper);
|
||||||
this.paper = 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"];
|
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) {
|
make(info) {
|
||||||
let color, path, thickness, x, y;
|
let color, path, thickness, x, y;
|
||||||
if((info != null ? info.points : void 0) != null) {
|
if((info != null ? info.points : void 0) != null) {
|
||||||
@ -25,6 +34,11 @@ this.WhiteboardTriangleModel = (function() {
|
|||||||
return this.obj;
|
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) {
|
update(info) {
|
||||||
let path, ref, x1, x2, xBottomLeft, xBottomRight, xTop, y1, y2, yBottomLeft, yBottomRight, yTop;
|
let path, ref, x1, x2, xBottomLeft, xBottomRight, xTop, y1, y2, yBottomLeft, yBottomRight, yTop;
|
||||||
if((info != null ? info.points : void 0) != null) {
|
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) {
|
draw(x1, y1, x2, y2, colour, thickness) {
|
||||||
let path, ref, triangle, xBottomLeft, xBottomRight, xTop, yBottomLeft, yBottomRight, yTop;
|
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];
|
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`;
|
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) {
|
_scaleTrianglePath(string, w, h, xOffset, yOffset) {
|
||||||
let j, len, path, points;
|
let j, len, path, points;
|
||||||
if(xOffset == null) {
|
if(xOffset == null) {
|
||||||
@ -86,6 +111,8 @@ this.WhiteboardTriangleModel = (function() {
|
|||||||
points = string.match(/(\d+[.]?\d*)/g);
|
points = string.match(/(\d+[.]?\d*)/g);
|
||||||
len = points.length;
|
len = points.length;
|
||||||
j = 0;
|
j = 0;
|
||||||
|
|
||||||
|
// go through each point and multiply it by the new height and width
|
||||||
path = "M";
|
path = "M";
|
||||||
while(j < len) {
|
while(j < len) {
|
||||||
if(j !== 0) {
|
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;
|
let config, file, ref, transports, winston;
|
||||||
|
|
||||||
config = {};
|
config = {};
|
||||||
|
|
||||||
|
// Default global variables
|
||||||
|
|
||||||
config.appName = 'BigBlueButton HTML5 Client';
|
config.appName = 'BigBlueButton HTML5 Client';
|
||||||
|
|
||||||
config.bbbServerVersion = '1.0-beta';
|
config.bbbServerVersion = '1.0-beta';
|
||||||
@ -20,20 +25,30 @@ config.maxChatLength = 140;
|
|||||||
|
|
||||||
config.lockOnJoin = true;
|
config.lockOnJoin = true;
|
||||||
|
|
||||||
|
//// Application configurations
|
||||||
|
|
||||||
config.app = {};
|
config.app = {};
|
||||||
|
|
||||||
|
//default font sizes for mobile / desktop
|
||||||
|
|
||||||
config.app.mobileFont = 16;
|
config.app.mobileFont = 16;
|
||||||
|
|
||||||
config.app.desktopFont = 14;
|
config.app.desktopFont = 14;
|
||||||
|
|
||||||
|
// Will offer the user to join the audio when entering the meeting
|
||||||
|
|
||||||
config.app.autoJoinAudio = false;
|
config.app.autoJoinAudio = false;
|
||||||
|
|
||||||
config.app.listenOnly = false;
|
config.app.listenOnly = false;
|
||||||
|
|
||||||
config.app.skipCheck = 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;
|
config.app.WebRTCHangupRetryInterval = 2000;
|
||||||
|
|
||||||
|
// Configs for redis
|
||||||
|
|
||||||
config.redis = {};
|
config.redis = {};
|
||||||
|
|
||||||
config.redis.host = "127.0.0.1";
|
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";
|
config.redis.channels.toBBBApps.polling = "bigbluebutton:to-bbb-apps:polling";
|
||||||
|
|
||||||
|
// Logging
|
||||||
|
|
||||||
config.log = {};
|
config.log = {};
|
||||||
|
|
||||||
if(Meteor.isServer) {
|
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`;
|
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;
|
file = config.log.path;
|
||||||
transports = [
|
transports = [
|
||||||
new winston.transports.Console(), new winston.transports.File({
|
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/>';
|
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';
|
this.CARRIAGE_RETURN = '\r';
|
||||||
|
|
||||||
|
// handle this the same as carriage return, in case text copied has this
|
||||||
this.NEW_LINE = '\n';
|
this.NEW_LINE = '\n';
|
||||||
|
@ -3,6 +3,7 @@ this.Router.configure({
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.Router.map(function() {
|
this.Router.map(function() {
|
||||||
|
// this is how we handle login attempts
|
||||||
this.route("main", {
|
this.route("main", {
|
||||||
path: "/html5client/:meeting_id/:user_id/:auth_token",
|
path: "/html5client/:meeting_id/:user_id/:auth_token",
|
||||||
where: "client",
|
where: "client",
|
||||||
@ -12,7 +13,10 @@ this.Router.map(function() {
|
|||||||
userId = this.params.user_id;
|
userId = this.params.user_id;
|
||||||
authToken = this.params.auth_token;
|
authToken = this.params.auth_token;
|
||||||
setInSession("loginUrl", this.originalUrl);
|
setInSession("loginUrl", this.originalUrl);
|
||||||
|
|
||||||
|
// catch if any of the user's meeting data is invalid
|
||||||
if ((authToken == null) || (meetingId == null) || (userId == null)) {
|
if ((authToken == null) || (meetingId == null) || (userId == null)) {
|
||||||
|
// if their data is invalid, redirect the user to the logout page
|
||||||
document.location = getInSession('logoutURL');
|
document.location = getInSession('logoutURL');
|
||||||
} else {
|
} else {
|
||||||
Meteor.call("validateAuthToken", meetingId, userId, authToken);
|
Meteor.call("validateAuthToken", meetingId, userId, authToken);
|
||||||
@ -27,6 +31,8 @@ this.Router.map(function() {
|
|||||||
return this.next();
|
return this.next();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// the user successfully logged in
|
||||||
this.route("signedin", {
|
this.route("signedin", {
|
||||||
path: "/html5client",
|
path: "/html5client",
|
||||||
where: "client",
|
where: "client",
|
||||||
@ -37,8 +43,13 @@ this.Router.map(function() {
|
|||||||
authToken = getInSession("authToken");
|
authToken = getInSession("authToken");
|
||||||
onErrorFunction = function(error, result) {
|
onErrorFunction = function(error, result) {
|
||||||
console.log("ONERRORFUNCTION");
|
console.log("ONERRORFUNCTION");
|
||||||
|
|
||||||
|
//make sure the user is not let through
|
||||||
Meteor.call("userLogout", meetingId, userId, authToken);
|
Meteor.call("userLogout", meetingId, userId, authToken);
|
||||||
|
|
||||||
clearSessionVar();
|
clearSessionVar();
|
||||||
|
|
||||||
|
// Attempt to log back in
|
||||||
if (error == null) {
|
if (error == null) {
|
||||||
window.location.href = getInSession('loginUrl') || getInSession('logoutURL');
|
window.location.href = getInSession('loginUrl') || getInSession('logoutURL');
|
||||||
}
|
}
|
||||||
@ -65,21 +76,24 @@ this.Router.map(function() {
|
|||||||
return Meteor.subscribe('bbb_cursor', meetingId, {
|
return Meteor.subscribe('bbb_cursor', meetingId, {
|
||||||
onReady: function() {
|
onReady: function() {
|
||||||
let a, handleLogourUrlError;
|
let a, handleLogourUrlError;
|
||||||
|
// done subscribing, start rendering the client and set default settings
|
||||||
_this.render('main');
|
_this.render('main');
|
||||||
onLoadComplete();
|
onLoadComplete();
|
||||||
handleLogourUrlError = function() {
|
handleLogourUrlError = function() {
|
||||||
alert("Error: could not find the logoutURL");
|
alert("Error: could not find the logoutURL");
|
||||||
setInSession("logoutURL", document.location.hostname);
|
setInSession("logoutURL", document.location.hostname);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// obtain the logoutURL
|
||||||
a = $.ajax({
|
a = $.ajax({
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
url: '/bigbluebutton/api/enter'
|
url: '/bigbluebutton/api/enter'
|
||||||
});
|
});
|
||||||
a.done(data => {
|
a.done(data => {
|
||||||
if (data.response.logoutURL != null) {
|
if (data.response.logoutURL != null) { // for a meeting with 0 users
|
||||||
setInSession("logoutURL", data.response.logoutURL);
|
setInSession("logoutURL", data.response.logoutURL);
|
||||||
} else {
|
} else {
|
||||||
if (data.response.logoutUrl != null) {
|
if (data.response.logoutUrl != null) { // for a running meeting
|
||||||
setInSession("logoutURL", data.response.logoutUrl);
|
setInSession("logoutURL", data.response.logoutUrl);
|
||||||
} else {
|
} else {
|
||||||
return handleLogourUrlError();
|
return handleLogourUrlError();
|
||||||
@ -111,6 +125,8 @@ this.Router.map(function() {
|
|||||||
return this.render('loading');
|
return this.render('loading');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// endpoint - is the html5client running (ready to handle a user)
|
||||||
this.route('meteorEndpoint', {
|
this.route('meteorEndpoint', {
|
||||||
path: '/check',
|
path: '/check',
|
||||||
where: 'server',
|
where: 'server',
|
||||||
@ -118,6 +134,8 @@ this.Router.map(function() {
|
|||||||
this.response.writeHead(200, {
|
this.response.writeHead(200, {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// reply that the html5client is running
|
||||||
this.response.end(JSON.stringify({
|
this.response.end(JSON.stringify({
|
||||||
"html5clientStatus": "running"
|
"html5clientStatus": "running"
|
||||||
}));
|
}));
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
Meteor.methods({
|
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) {
|
sendChatMessagetoServer(meetingId, chatObject, requesterUserId, requesterToken) {
|
||||||
let action, chatType, eventName, message, recipient;
|
let action, chatType, eventName, message, recipient;
|
||||||
chatType = chatObject.chat_type;
|
chatType = chatObject.chat_type;
|
||||||
@ -11,7 +15,7 @@ Meteor.methods({
|
|||||||
} else {
|
} else {
|
||||||
eventName = "send_private_chat_message";
|
eventName = "send_private_chat_message";
|
||||||
if(recipient === requesterUserId) {
|
if(recipient === requesterUserId) {
|
||||||
return 'chatSelf';
|
return 'chatSelf'; //not allowed
|
||||||
} else {
|
} else {
|
||||||
return 'chatPrivate';
|
return 'chatPrivate';
|
||||||
}
|
}
|
||||||
@ -35,6 +39,7 @@ Meteor.methods({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
deletePrivateChatMessages(userId, contact_id) {
|
deletePrivateChatMessages(userId, contact_id) {
|
||||||
|
// if authorized pass through
|
||||||
let contact, requester;
|
let contact, requester;
|
||||||
requester = Meteor.Users.findOne({
|
requester = Meteor.Users.findOne({
|
||||||
userId: userId
|
userId: userId
|
||||||
@ -46,8 +51,13 @@ Meteor.methods({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------------
|
||||||
|
// Private methods on server
|
||||||
|
// --------------------------------------------------------------------------------------------
|
||||||
this.addChatToCollection = function(meetingId, messageObject) {
|
this.addChatToCollection = function(meetingId, messageObject) {
|
||||||
let id;
|
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];
|
messageObject.from_time = messageObject.from_time.toString().split('.').join("").split("E")[0];
|
||||||
if((messageObject.from_userid != null) && (messageObject.to_userid != null)) {
|
if((messageObject.from_userid != null) && (messageObject.to_userid != null)) {
|
||||||
messageObject.message = translateFlashToHTML5(messageObject.message);
|
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) {
|
this.clearChatCollection = function(meetingId) {
|
||||||
if(meetingId != null) {
|
if(meetingId != null) {
|
||||||
return Meteor.Chat.remove({
|
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) {
|
this.translateHTML5ToFlash = function(message) {
|
||||||
let result;
|
let result;
|
||||||
result = message;
|
result = message;
|
||||||
@ -99,6 +115,7 @@ this.translateHTML5ToFlash = function(message) {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// translate '<br/>' breakline character to '\r' carriage return character for HTML5
|
||||||
this.translateFlashToHTML5 = function(message) {
|
this.translateFlashToHTML5 = function(message) {
|
||||||
let result;
|
let result;
|
||||||
result = message;
|
result = message;
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
// --------------------------------------------------------------------------------------------
|
||||||
|
// Private methods on server
|
||||||
|
// --------------------------------------------------------------------------------------------
|
||||||
this.initializeCursor = function(meetingId) {
|
this.initializeCursor = function(meetingId) {
|
||||||
return Meteor.Cursor.upsert({
|
return Meteor.Cursor.upsert({
|
||||||
meetingId: meetingId
|
meetingId: meetingId
|
||||||
@ -9,7 +12,7 @@ this.initializeCursor = function(meetingId) {
|
|||||||
if(err) {
|
if(err) {
|
||||||
return Meteor.log.error(`err upserting cursor for ${meetingId}`);
|
return Meteor.log.error(`err upserting cursor for ${meetingId}`);
|
||||||
} else {
|
} else {
|
||||||
|
// Meteor.log.info "ok upserting cursor for #{meetingId}"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -26,11 +29,12 @@ this.updateCursorLocation = function(meetingId, cursorObject) {
|
|||||||
if(err != null) {
|
if(err != null) {
|
||||||
return Meteor.log.error(`_unsucc update of cursor for ${meetingId} ${JSON.stringify(cursorObject)} err=${JSON.stringify(err)}`);
|
return Meteor.log.error(`_unsucc update of cursor for ${meetingId} ${JSON.stringify(cursorObject)} err=${JSON.stringify(err)}`);
|
||||||
} else {
|
} else {
|
||||||
|
// Meteor.log.info "updated cursor for #{meetingId} #{JSON.stringify cursorObject}"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// called on server start and meeting end
|
||||||
this.clearCursorCollection = function(meetingId) {
|
this.clearCursorCollection = function(meetingId) {
|
||||||
if(meetingId != null) {
|
if(meetingId != null) {
|
||||||
return Meteor.Cursor.remove({
|
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) {
|
this.addMeetingToCollection = function(meetingId, name, intendedForRecording, voiceConf, duration, callback) {
|
||||||
|
//check if the meeting is already in the collection
|
||||||
|
|
||||||
Meteor.Meetings.upsert({
|
Meteor.Meetings.upsert({
|
||||||
meetingId: meetingId
|
meetingId: meetingId
|
||||||
}, {
|
}, {
|
||||||
@ -9,13 +15,14 @@ this.addMeetingToCollection = function(meetingId, name, intendedForRecording, vo
|
|||||||
voiceConf: voiceConf,
|
voiceConf: voiceConf,
|
||||||
duration: duration,
|
duration: duration,
|
||||||
roomLockSettings: {
|
roomLockSettings: {
|
||||||
|
// by default the lock settings will be disabled on meeting create
|
||||||
disablePrivateChat: false,
|
disablePrivateChat: false,
|
||||||
disableCam: false,
|
disableCam: false,
|
||||||
disableMic: false,
|
disableMic: false,
|
||||||
lockOnJoin: Meteor.config.lockOnJoin,
|
lockOnJoin: Meteor.config.lockOnJoin,
|
||||||
lockedLayout: false,
|
lockedLayout: false,
|
||||||
disablePublicChat: false,
|
disablePublicChat: false,
|
||||||
lockOnJoinConfigurable: false
|
lockOnJoinConfigurable: false // TODO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, (_this => {
|
}, (_this => {
|
||||||
@ -33,6 +40,8 @@ this.addMeetingToCollection = function(meetingId, name, intendedForRecording, vo
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
})(this));
|
})(this));
|
||||||
|
|
||||||
|
// initialize the cursor in the meeting
|
||||||
return initializeCursor(meetingId);
|
return initializeCursor(meetingId);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -46,18 +55,33 @@ this.clearMeetingsCollection = function(meetingId) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//clean up upon a meeting's end
|
||||||
this.removeMeetingFromCollection = function(meetingId, callback) {
|
this.removeMeetingFromCollection = function(meetingId, callback) {
|
||||||
let funct;
|
let funct;
|
||||||
if(Meteor.Meetings.findOne({
|
if(Meteor.Meetings.findOne({
|
||||||
meetingId: meetingId
|
meetingId: meetingId
|
||||||
}) != null) {
|
}) != null) {
|
||||||
Meteor.log.info(`end of meeting ${meetingId}. Clear the meeting data from all collections`);
|
Meteor.log.info(`end of meeting ${meetingId}. Clear the meeting data from all collections`);
|
||||||
|
|
||||||
|
// delete all users in the meeting
|
||||||
clearUsersCollection(meetingId);
|
clearUsersCollection(meetingId);
|
||||||
|
|
||||||
|
// delete all slides in the meeting
|
||||||
clearSlidesCollection(meetingId);
|
clearSlidesCollection(meetingId);
|
||||||
|
|
||||||
|
// delete all shapes in the meeting
|
||||||
clearShapesCollection(meetingId);
|
clearShapesCollection(meetingId);
|
||||||
|
|
||||||
|
// delete all presentations in the meeting
|
||||||
clearPresentationsCollection(meetingId);
|
clearPresentationsCollection(meetingId);
|
||||||
|
|
||||||
|
// delete all chat messages in the meeting
|
||||||
clearChatCollection(meetingId);
|
clearChatCollection(meetingId);
|
||||||
|
|
||||||
|
// delete the meeting
|
||||||
clearMeetingsCollection(meetingId);
|
clearMeetingsCollection(meetingId);
|
||||||
|
|
||||||
|
// delete the cursor for the meeting
|
||||||
clearCursorCollection(meetingId);
|
clearCursorCollection(meetingId);
|
||||||
return callback();
|
return callback();
|
||||||
} else {
|
} else {
|
||||||
@ -68,3 +92,9 @@ this.removeMeetingFromCollection = function(meetingId, callback) {
|
|||||||
return funct(callback);
|
return funct(callback);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------------
|
||||||
|
// end Private methods on server
|
||||||
|
// --------------------------------------------------------------------------------------------
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
// --------------------------------------------------------------------------------------------
|
||||||
|
// Public methods on server
|
||||||
|
// --------------------------------------------------------------------------------------------
|
||||||
Meteor.methods({
|
Meteor.methods({
|
||||||
publishVoteMessage(meetingId, pollAnswerId, requesterUserId, requesterToken) {
|
publishVoteMessage(meetingId, pollAnswerId, requesterUserId, requesterToken) {
|
||||||
let _poll_id, eventName, message, result;
|
let _poll_id, eventName, message, result;
|
||||||
@ -44,20 +47,29 @@ Meteor.methods({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------------
|
||||||
|
// Private methods on server
|
||||||
|
// --------------------------------------------------------------------------------------------
|
||||||
this.addPollToCollection = function(poll, requester_id, users, meetingId) {
|
this.addPollToCollection = function(poll, requester_id, users, meetingId) {
|
||||||
let _users, answer, entry, i, j, len, len1, ref, user;
|
let _users, answer, entry, i, j, len, len1, ref, user;
|
||||||
|
//copying all the userids into an array
|
||||||
_users = [];
|
_users = [];
|
||||||
for (i = 0, len = users.length; i < len; i++) {
|
for (i = 0, len = users.length; i < len; i++) {
|
||||||
user = users[i];
|
user = users[i];
|
||||||
_users.push(user.user.userid);
|
_users.push(user.user.userid);
|
||||||
}
|
}
|
||||||
|
//adding the initial number of votes for each answer
|
||||||
ref = poll.answers;
|
ref = poll.answers;
|
||||||
for (j = 0, len1 = ref.length; j < len1; j++) {
|
for (j = 0, len1 = ref.length; j < len1; j++) {
|
||||||
answer = ref[j];
|
answer = ref[j];
|
||||||
answer.num_votes = 0;
|
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_responders = -1;
|
||||||
poll.num_respondents = -1;
|
poll.num_respondents = -1;
|
||||||
|
|
||||||
|
//adding all together and inserting into the Polls collection
|
||||||
entry = {
|
entry = {
|
||||||
poll_info: {
|
poll_info: {
|
||||||
"meetingId": meetingId,
|
"meetingId": meetingId,
|
||||||
|
@ -59,8 +59,12 @@ Meteor.methods({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------------
|
||||||
|
// Private methods on server
|
||||||
|
// --------------------------------------------------------------------------------------------
|
||||||
this.addPresentationToCollection = function(meetingId, presentationObject) {
|
this.addPresentationToCollection = function(meetingId, presentationObject) {
|
||||||
let entry, id;
|
let entry, id;
|
||||||
|
//check if the presentation is already in the collection
|
||||||
if(Meteor.Presentations.findOne({
|
if(Meteor.Presentations.findOne({
|
||||||
meetingId: meetingId,
|
meetingId: meetingId,
|
||||||
'presentation.id': presentationObject.id
|
'presentation.id': presentationObject.id
|
||||||
@ -74,6 +78,7 @@ this.addPresentationToCollection = function(meetingId, presentationObject) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
return id = Meteor.Presentations.insert(entry);
|
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) {
|
this.clearPresentationsCollection = function(meetingId) {
|
||||||
if(meetingId != null) {
|
if(meetingId != null) {
|
||||||
return Meteor.Presentations.remove({
|
return Meteor.Presentations.remove({
|
||||||
@ -106,3 +112,7 @@ this.clearPresentationsCollection = function(meetingId) {
|
|||||||
return Meteor.Presentations.remove({}, Meteor.log.info("cleared Presentations Collection (all meetings)!"));
|
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) {
|
this.addShapeToCollection = function(meetingId, whiteboardId, shapeObject) {
|
||||||
let entry, id, removeTempTextShape;
|
let entry, id, removeTempTextShape;
|
||||||
if((shapeObject != null ? shapeObject.shape_type : void 0) === "text") {
|
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") {
|
if(shapeObject.status === "textEdited" || shapeObject.status === "textPublished") {
|
||||||
|
// only keep the final version of the text shape
|
||||||
removeTempTextShape = function(callback) {
|
removeTempTextShape = function(callback) {
|
||||||
Meteor.Shapes.remove({
|
Meteor.Shapes.remove({
|
||||||
'shape.id': shapeObject.shape.id
|
'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 callback();
|
||||||
};
|
};
|
||||||
return removeTempTextShape(() => {
|
return removeTempTextShape(() => {
|
||||||
|
// display as the prestenter is typing
|
||||||
let id;
|
let id;
|
||||||
id = Meteor.Shapes.insert(entry);
|
id = Meteor.Shapes.insert(entry);
|
||||||
return Meteor.log.info(`${shapeObject.status} substituting the temp shapes with the newer one`);
|
return Meteor.log.info(`${shapeObject.status} substituting the temp shapes with the newer one`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} 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")) {
|
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 = {
|
entry = {
|
||||||
meetingId: meetingId,
|
meetingId: meetingId,
|
||||||
@ -78,6 +87,8 @@ this.removeAllShapesFromSlide = function(meetingId, whiteboardId) {
|
|||||||
whiteboardId: whiteboardId
|
whiteboardId: whiteboardId
|
||||||
}, () => {
|
}, () => {
|
||||||
Meteor.log.info("clearing all shapes from slide");
|
Meteor.log.info("clearing all shapes from slide");
|
||||||
|
|
||||||
|
// After shapes are cleared, wait 1 second and set cleaning off
|
||||||
return Meteor.setTimeout(() => {
|
return Meteor.setTimeout(() => {
|
||||||
return Meteor.WhiteboardCleanStatus.update({
|
return Meteor.WhiteboardCleanStatus.update({
|
||||||
meetingId: meetingId
|
meetingId: meetingId
|
||||||
@ -108,6 +119,7 @@ whiteboardId: whiteboardId
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// called on server start and meeting end
|
||||||
this.clearShapesCollection = function(meetingId) {
|
this.clearShapesCollection = function(meetingId) {
|
||||||
if(meetingId != null) {
|
if(meetingId != null) {
|
||||||
return Meteor.Shapes.remove({
|
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) {
|
this.displayThisSlide = function(meetingId, newSlideId, slideObject) {
|
||||||
let presentationId;
|
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({
|
Meteor.Slides.update({
|
||||||
presentationId: presentationId,
|
presentationId: presentationId,
|
||||||
"slide.current": true
|
"slide.current": true
|
||||||
@ -9,7 +13,10 @@ this.displayThisSlide = function(meetingId, newSlideId, slideObject) {
|
|||||||
"slide.current": false
|
"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);
|
removeSlideFromCollection(meetingId, newSlideId);
|
||||||
|
// add the new slide to the collection
|
||||||
return addSlideToCollection(meetingId, presentationId, slideObject);
|
return addSlideToCollection(meetingId, presentationId, slideObject);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -37,6 +44,7 @@ this.addSlideToCollection = function(meetingId, presentationId, slideObject) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
return id = Meteor.Slides.insert(entry);
|
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) {
|
this.clearSlidesCollection = function(meetingId) {
|
||||||
if(meetingId != null) {
|
if(meetingId != null) {
|
||||||
return Meteor.Slides.remove({
|
return Meteor.Slides.remove({
|
||||||
@ -66,3 +75,7 @@ this.clearSlidesCollection = function(meetingId) {
|
|||||||
return Meteor.Slides.remove({}, Meteor.log.info("cleared Slides Collection (all meetings)!"));
|
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({
|
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) {
|
listenOnlyRequestToggle(meetingId, userId, authToken, isJoining) {
|
||||||
let message, ref, ref1, username, voiceConf;
|
let message, ref, ref1, username, voiceConf;
|
||||||
voiceConf = (ref = Meteor.Meetings.findOne({
|
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) {
|
muteUser(meetingId, toMuteUserId, requesterUserId, requesterToken) {
|
||||||
let action, message;
|
let action, message;
|
||||||
action = function() {
|
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) {
|
unmuteUser(meetingId, toMuteUserId, requesterUserId, requesterToken) {
|
||||||
let action, message;
|
let action, message;
|
||||||
action = function() {
|
action = function() {
|
||||||
@ -125,15 +146,26 @@ Meteor.methods({
|
|||||||
version: "0.0.1"
|
version: "0.0.1"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// publish to pubsub
|
||||||
publish(Meteor.config.redis.channels.toBBBApps.users, message);
|
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) {
|
userLogout(meetingId, userId, authToken) {
|
||||||
if(isAllowedTo('logoutSelf', meetingId, userId, authToken)) {
|
if(isAllowedTo('logoutSelf', meetingId, userId, authToken)) {
|
||||||
Meteor.log.info(`a user is logging out from ${meetingId}:${userId}`);
|
Meteor.log.info(`a user is logging out from ${meetingId}:${userId}`);
|
||||||
return requestUserLeaving(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) {
|
kickUser(meetingId, toKickUserId, requesterUserId, authToken) {
|
||||||
let message;
|
let message;
|
||||||
if(isAllowedTo('kickUser', meetingId, requesterUserId, authToken)) {
|
if(isAllowedTo('kickUser', meetingId, requesterUserId, authToken)) {
|
||||||
@ -150,6 +182,12 @@ Meteor.methods({
|
|||||||
return publish(Meteor.config.redis.channels.toBBBApps.users, message);
|
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(
|
setUserPresenter(
|
||||||
meetingId,
|
meetingId,
|
||||||
newPresenterId,
|
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) {
|
this.markUserOffline = function(meetingId, userId, callback) {
|
||||||
|
// mark the user as offline. remove from the collection on meeting_end #TODO
|
||||||
let user;
|
let user;
|
||||||
user = Meteor.Users.findOne({
|
user = Meteor.Users.findOne({
|
||||||
meetingId: meetingId,
|
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) {
|
this.requestUserLeaving = function(meetingId, userId) {
|
||||||
let listenOnlyMessage, message, ref, userObject, voiceConf;
|
let listenOnlyMessage, message, ref, userObject, voiceConf;
|
||||||
userObject = Meteor.Users.findOne({
|
userObject = Meteor.Users.findOne({
|
||||||
@ -237,6 +289,8 @@ this.requestUserLeaving = function(meetingId, userId) {
|
|||||||
meetingId: meetingId
|
meetingId: meetingId
|
||||||
})) != null ? ref.voiceConf : void 0;
|
})) != null ? ref.voiceConf : void 0;
|
||||||
if((userObject != null) && (voiceConf != null) && (userId != null) && (meetingId != null)) {
|
if((userObject != null) && (voiceConf != null) && (userId != null) && (meetingId != null)) {
|
||||||
|
|
||||||
|
// end listenOnly audio for the departing user
|
||||||
if(userObject.user.listenOnly) {
|
if(userObject.user.listenOnly) {
|
||||||
listenOnlyMessage = {
|
listenOnlyMessage = {
|
||||||
payload: {
|
payload: {
|
||||||
@ -252,6 +306,8 @@ this.requestUserLeaving = function(meetingId, userId) {
|
|||||||
};
|
};
|
||||||
publish(Meteor.config.redis.channels.toBBBApps.meeting, listenOnlyMessage);
|
publish(Meteor.config.redis.channels.toBBBApps.meeting, listenOnlyMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// remove user from meeting
|
||||||
message = {
|
message = {
|
||||||
payload: {
|
payload: {
|
||||||
meeting_id: meetingId,
|
meeting_id: meetingId,
|
||||||
@ -269,6 +325,7 @@ this.requestUserLeaving = function(meetingId, userId) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//update a voiceUser - a helper method
|
||||||
this.updateVoiceUser = function(meetingId, voiceUserObject, callback) {
|
this.updateVoiceUser = function(meetingId, voiceUserObject, callback) {
|
||||||
let u;
|
let u;
|
||||||
u = Meteor.Users.findOne({
|
u = Meteor.Users.findOne({
|
||||||
@ -289,7 +346,7 @@ this.updateVoiceUser = function(meetingId, voiceUserObject, callback) {
|
|||||||
}
|
}
|
||||||
return callback();
|
return callback();
|
||||||
});
|
});
|
||||||
}
|
} // talking
|
||||||
if(voiceUserObject.joined != null) {
|
if(voiceUserObject.joined != null) {
|
||||||
Meteor.Users.update({
|
Meteor.Users.update({
|
||||||
meetingId: meetingId,
|
meetingId: meetingId,
|
||||||
@ -306,7 +363,7 @@ this.updateVoiceUser = function(meetingId, voiceUserObject, callback) {
|
|||||||
}
|
}
|
||||||
return callback();
|
return callback();
|
||||||
});
|
});
|
||||||
}
|
} // joined
|
||||||
if(voiceUserObject.locked != null) {
|
if(voiceUserObject.locked != null) {
|
||||||
Meteor.Users.update({
|
Meteor.Users.update({
|
||||||
meetingId: meetingId,
|
meetingId: meetingId,
|
||||||
@ -321,7 +378,7 @@ this.updateVoiceUser = function(meetingId, voiceUserObject, callback) {
|
|||||||
}
|
}
|
||||||
return callback();
|
return callback();
|
||||||
});
|
});
|
||||||
}
|
} // locked
|
||||||
if(voiceUserObject.muted != null) {
|
if(voiceUserObject.muted != null) {
|
||||||
Meteor.Users.update({
|
Meteor.Users.update({
|
||||||
meetingId: meetingId,
|
meetingId: meetingId,
|
||||||
@ -336,7 +393,7 @@ this.updateVoiceUser = function(meetingId, voiceUserObject, callback) {
|
|||||||
}
|
}
|
||||||
return callback();
|
return callback();
|
||||||
});
|
});
|
||||||
}
|
} // muted
|
||||||
if(voiceUserObject.listen_only != null) {
|
if(voiceUserObject.listen_only != null) {
|
||||||
return Meteor.Users.update({
|
return Meteor.Users.update({
|
||||||
meetingId: meetingId,
|
meetingId: meetingId,
|
||||||
@ -351,7 +408,7 @@ this.updateVoiceUser = function(meetingId, voiceUserObject, callback) {
|
|||||||
}
|
}
|
||||||
return callback();
|
return callback();
|
||||||
});
|
});
|
||||||
}
|
} // listenOnly
|
||||||
} else {
|
} else {
|
||||||
Meteor.log.error("ERROR! did not find such voiceUser!");
|
Meteor.log.error("ERROR! did not find such voiceUser!");
|
||||||
return callback();
|
return callback();
|
||||||
@ -365,6 +422,10 @@ this.userJoined = function(meetingId, user, callback) {
|
|||||||
userId: user.userid,
|
userId: user.userid,
|
||||||
meetingId: meetingId
|
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)) {
|
if((u != null) && (u.authToken != null)) {
|
||||||
Meteor.Users.update({
|
Meteor.Users.update({
|
||||||
userId: user.userid,
|
userId: user.userid,
|
||||||
@ -385,7 +446,7 @@ this.userJoined = function(meetingId, user, callback) {
|
|||||||
extern_userid: user.extern_userid,
|
extern_userid: user.extern_userid,
|
||||||
locked: user.locked,
|
locked: user.locked,
|
||||||
time_of_joining: user.timeOfJoining,
|
time_of_joining: user.timeOfJoining,
|
||||||
connection_status: "online",
|
connection_status: "online", // TODO consider other default value
|
||||||
voiceUser: {
|
voiceUser: {
|
||||||
web_userid: user.voiceUser.web_userid,
|
web_userid: user.voiceUser.web_userid,
|
||||||
callernum: user.voiceUser.callernum,
|
callernum: user.voiceUser.callernum,
|
||||||
@ -416,6 +477,7 @@ this.userJoined = function(meetingId, user, callback) {
|
|||||||
meetingId: meetingId
|
meetingId: meetingId
|
||||||
})) != null ? ref.meetingName : void 0);
|
})) != null ? ref.meetingName : void 0);
|
||||||
welcomeMessage = welcomeMessage + Meteor.config.defaultWelcomeMessageFooter;
|
welcomeMessage = welcomeMessage + Meteor.config.defaultWelcomeMessageFooter;
|
||||||
|
// add the welcome message if it's not there already OR update time_of_joining
|
||||||
return Meteor.Chat.upsert({
|
return Meteor.Chat.upsert({
|
||||||
meetingId: meetingId,
|
meetingId: meetingId,
|
||||||
userId: userId,
|
userId: userId,
|
||||||
@ -439,8 +501,12 @@ this.userJoined = function(meetingId, user, callback) {
|
|||||||
} else {
|
} else {
|
||||||
return Meteor.log.info(`_added/updated a system message in chat for user ${userId}`);
|
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 {
|
} else {
|
||||||
|
// Meteor.log.info "NOTE: got user_joined_message #{user.name} #{user.userid}"
|
||||||
return Meteor.Users.upsert({
|
return Meteor.Users.upsert({
|
||||||
meetingId: meetingId,
|
meetingId: meetingId,
|
||||||
userId: userId
|
userId: userId
|
||||||
@ -506,7 +572,7 @@ this.createDummyUser = function(meetingId, userId, authToken) {
|
|||||||
userId: userId,
|
userId: userId,
|
||||||
authToken: authToken,
|
authToken: authToken,
|
||||||
clientType: "HTML5",
|
clientType: "HTML5",
|
||||||
validated: false
|
validated: false //will be validated on validate_auth_token_reply
|
||||||
}, (err, id) => {
|
}, (err, id) => {
|
||||||
return Meteor.log.info(`_added a dummy html5 user with: userId=[${userId}] Users.size is now ${Meteor.Users.find({
|
return Meteor.log.info(`_added a dummy html5 user with: userId=[${userId}] Users.size is now ${Meteor.Users.find({
|
||||||
meetingId: meetingId
|
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) {
|
this.handleLockingMic = function(meetingId, newSettings) {
|
||||||
|
// send mute requests for the viewer users joined with mic
|
||||||
let i, len, ref, ref1, results, u;
|
let i, len, ref, ref1, results, u;
|
||||||
ref1 = (ref = Meteor.Users.find({
|
ref1 = (ref = Meteor.Users.find({
|
||||||
meetingId: meetingId,
|
meetingId: meetingId,
|
||||||
@ -528,11 +597,13 @@ this.handleLockingMic = function(meetingId, newSettings) {
|
|||||||
results = [];
|
results = [];
|
||||||
for(i = 0, len = ref1.length; i < len; i++) {
|
for(i = 0, len = ref1.length; i < len; i++) {
|
||||||
u = ref1[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;
|
return results;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// change the locked status of a user (lock settings)
|
||||||
this.setUserLockedStatus = function(meetingId, userId, isLocked) {
|
this.setUserLockedStatus = function(meetingId, userId, isLocked) {
|
||||||
let u;
|
let u;
|
||||||
u = Meteor.Users.findOne({
|
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}`);
|
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) {
|
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 {
|
} else {
|
||||||
return Meteor.log.error(`(unsuccessful-no such user) setting user locked status for userid:[${userId}] from [${meetingId}] locked=${isLocked}`);
|
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) {
|
this.clearUsersCollection = function(meetingId) {
|
||||||
if(meetingId != null) {
|
if(meetingId != null) {
|
||||||
return Meteor.Users.remove({
|
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) {
|
Meteor.publish('users', function(meetingId, userid, authToken) {
|
||||||
let ref, ref1, u, username;
|
let ref, ref1, u, username;
|
||||||
Meteor.log.info(`attempt publishing users for ${meetingId}, ${userid}, ${authToken}`);
|
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)) {
|
if(isAllowedTo('subscribeUsers', meetingId, userid, authToken)) {
|
||||||
Meteor.log.info(`${userid} was allowed to subscribe to 'users'`);
|
Meteor.log.info(`${userid} was allowed to subscribe to 'users'`);
|
||||||
username = (u != null ? (ref = u.user) != null ? ref.name : void 0 : void 0) || "UNKNOWN";
|
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') {
|
if(((ref1 = u.user) != null ? ref1.connection_status : void 0) !== 'online') {
|
||||||
Meteor.call("validateAuthToken", meetingId, userid, authToken);
|
Meteor.call("validateAuthToken", meetingId, userid, authToken);
|
||||||
}
|
}
|
||||||
@ -37,6 +41,8 @@ Meteor.publish('users', function(meetingId, userid, authToken) {
|
|||||||
return requestUserLeaving(meetingId, userid);
|
return requestUserLeaving(meetingId, userid);
|
||||||
};
|
};
|
||||||
})(this)));
|
})(this)));
|
||||||
|
|
||||||
|
//publish the users which are not offline
|
||||||
return Meteor.Users.find({
|
return Meteor.Users.find({
|
||||||
meetingId: meetingId,
|
meetingId: meetingId,
|
||||||
'user.connection_status': {
|
'user.connection_status': {
|
||||||
@ -51,7 +57,7 @@ Meteor.publish('users', function(meetingId, userid, authToken) {
|
|||||||
Meteor.log.warn("was not authorized to subscribe to 'users'");
|
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'"));
|
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.call("validateAuthToken", meetingId, userid, authToken);
|
||||||
Meteor.log.error(`there was no such user ${userid} in ${meetingId}`);
|
Meteor.log.error(`there was no such user ${userid} in ${meetingId}`);
|
||||||
return Meteor.Users.find({
|
return Meteor.Users.find({
|
||||||
@ -90,7 +96,9 @@ Meteor.publish('chat', function(meetingId, userid, authToken) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Meteor.publish('bbb_poll', 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)) {
|
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)) {
|
if(isAllowedTo('subscribeAnswers', meetingId, userid, authToken)) {
|
||||||
Meteor.log.info("publishing Poll for presenter: " + meetingId + " " + userid + " " + authToken);
|
Meteor.log.info("publishing Poll for presenter: " + meetingId + " " + userid + " " + authToken);
|
||||||
return Meteor.Polls.find({
|
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; };
|
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({
|
Meteor.methods({
|
||||||
|
// Construct and send a message to bbb-web to validate the user
|
||||||
validateAuthToken(meetingId, userId, authToken) {
|
validateAuthToken(meetingId, userId, authToken) {
|
||||||
let message;
|
let message;
|
||||||
Meteor.log.info("sending a validate_auth_token with", {
|
Meteor.log.info("sending a validate_auth_token with", {
|
||||||
@ -47,11 +48,13 @@ Meteor.RedisPubSub = (function() {
|
|||||||
_onSubscribe(channel, count) {
|
_onSubscribe(channel, count) {
|
||||||
let message;
|
let message;
|
||||||
Meteor.log.info(`Subscribed to ${channel}`);
|
Meteor.log.info(`Subscribed to ${channel}`);
|
||||||
|
|
||||||
|
//grab data about all active meetings on the server
|
||||||
message = {
|
message = {
|
||||||
"header": {
|
"header": {
|
||||||
"name": "get_all_meetings_request"
|
"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);
|
return publish(Meteor.config.redis.channels.toBBBApps.meeting, message);
|
||||||
}
|
}
|
||||||
@ -75,6 +78,11 @@ Meteor.RedisPubSub = (function() {
|
|||||||
return RedisPubSub;
|
return RedisPubSub;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------------
|
||||||
|
// Private methods on server
|
||||||
|
// --------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// message should be an object
|
||||||
this.publish = function(channel, message) {
|
this.publish = function(channel, message) {
|
||||||
Meteor.log.info(`redis outgoing message ${message.header.name}`, {
|
Meteor.log.info(`redis outgoing message ${message.header.name}`, {
|
||||||
channel: channel,
|
channel: channel,
|
||||||
|
@ -2,6 +2,8 @@ const indexOf = [].indexOf || function(item) { for(let i = 0, l = this.length; i
|
|||||||
|
|
||||||
Meteor.startup(() => {
|
Meteor.startup(() => {
|
||||||
Meteor.log.info("server start");
|
Meteor.log.info("server start");
|
||||||
|
|
||||||
|
//remove all data
|
||||||
Meteor.WhiteboardCleanStatus.remove({});
|
Meteor.WhiteboardCleanStatus.remove({});
|
||||||
clearUsersCollection();
|
clearUsersCollection();
|
||||||
clearChatCollection();
|
clearChatCollection();
|
||||||
@ -11,10 +13,15 @@ Meteor.startup(() => {
|
|||||||
clearPresentationsCollection();
|
clearPresentationsCollection();
|
||||||
clearPollCollection();
|
clearPollCollection();
|
||||||
clearCursorCollection();
|
clearCursorCollection();
|
||||||
|
|
||||||
|
// create create a PubSub connection, start listening
|
||||||
Meteor.redisPubSub = new Meteor.RedisPubSub(function() {
|
Meteor.redisPubSub = new Meteor.RedisPubSub(function() {
|
||||||
return Meteor.log.info("created pubsub");
|
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) {
|
Meteor.myQueue.taskHandler = function(data, next, failures) {
|
||||||
let eventName, ref;
|
let eventName, ref;
|
||||||
eventName = (ref = JSON.parse(data.jsonMsg)) != null ? ref.header.name : void 0;
|
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) {
|
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;
|
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);
|
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;
|
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;
|
eventName = message.header.name;
|
||||||
meetingId = (ref1 = message.payload) != null ? ref1.meeting_id : void 0;
|
meetingId = (ref1 = message.payload) != null ? ref1.meeting_id : void 0;
|
||||||
if(!(((message != null ? message.header : void 0) != null) && (message.payload != null))) {
|
if(!(((message != null ? message.header : void 0) != null) && (message.payload != null))) {
|
||||||
@ -52,13 +93,18 @@ Meteor.startup(() => {
|
|||||||
message: data.jsonMsg
|
message: data.jsonMsg
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// we currently disregard the pattern and channel
|
||||||
if(((message != null ? message.header : void 0) != null) && (message.payload != null)) {
|
if(((message != null ? message.header : void 0) != null) && (message.payload != null)) {
|
||||||
if(eventName === 'meeting_created_message') {
|
if(eventName === 'meeting_created_message') {
|
||||||
|
// Meteor.log.error JSON.stringify message
|
||||||
meetingName = message.payload.name;
|
meetingName = message.payload.name;
|
||||||
intendedForRecording = message.payload.recorded;
|
intendedForRecording = message.payload.recorded;
|
||||||
voiceConf = message.payload.voice_conf;
|
voiceConf = message.payload.voice_conf;
|
||||||
duration = message.payload.duration;
|
duration = message.payload.duration;
|
||||||
return addMeetingToCollection(meetingId, meetingName, intendedForRecording, voiceConf, duration, callback);
|
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')) {
|
} 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 = {
|
voiceUserObj = {
|
||||||
'web_userid': message.payload.user.voiceUser.web_userid,
|
'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("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));
|
Meteor.log.info(JSON.stringify(message));
|
||||||
listOfMeetings = message.payload.meetings;
|
listOfMeetings = message.payload.meetings;
|
||||||
|
|
||||||
|
// Processing the meetings recursively with a callback to notify us,
|
||||||
|
// ensuring that we update the meeting collection serially
|
||||||
processMeeting = function() {
|
processMeeting = function() {
|
||||||
let meeting;
|
let meeting;
|
||||||
meeting = listOfMeetings.pop();
|
meeting = listOfMeetings.pop();
|
||||||
if(meeting != null) {
|
if(meeting != null) {
|
||||||
return addMeetingToCollection(meeting.meetingID, meeting.meetingName, meeting.recorded, meeting.voiceBridge, meeting.duration, processMeeting);
|
return addMeetingToCollection(meeting.meetingID, meeting.meetingName, meeting.recorded, meeting.voiceBridge, meeting.duration, processMeeting);
|
||||||
} else {
|
} else {
|
||||||
return callback();
|
return callback(); // all meeting arrays (if any) have been processed
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return processMeeting();
|
return processMeeting();
|
||||||
@ -95,11 +144,16 @@ Meteor.startup(() => {
|
|||||||
userId: userObj.userid,
|
userId: userObj.userid,
|
||||||
meetingId: message.payload.meeting_id
|
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)) {
|
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");
|
Meteor.log.error("offline AND phone user");
|
||||||
return callback();
|
return callback(); //return without joining the user
|
||||||
} else {
|
} 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;
|
status = dbUser != null ? dbUser.validated : void 0;
|
||||||
Meteor.log.info(`in user_joined_message the validStatus of the user was ${status}`);
|
Meteor.log.info(`in user_joined_message the validStatus of the user was ${status}`);
|
||||||
userObj.timeOfJoining = message.header.current_time;
|
userObj.timeOfJoining = message.header.current_time;
|
||||||
@ -108,8 +162,17 @@ Meteor.startup(() => {
|
|||||||
return userJoined(meetingId, userObj, callback);
|
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') {
|
} else if(eventName === 'get_users_reply' && message.payload.requester_id === 'nodeJSapp') {
|
||||||
users = message.payload.users;
|
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() {
|
processUser = function() {
|
||||||
let user;
|
let user;
|
||||||
user = users.pop();
|
user = users.pop();
|
||||||
@ -120,10 +183,11 @@ Meteor.startup(() => {
|
|||||||
user.set_emoji_time = new Date();
|
user.set_emoji_time = new Date();
|
||||||
return userJoined(meetingId, user, processUser);
|
return userJoined(meetingId, user, processUser);
|
||||||
} else {
|
} else {
|
||||||
|
// console.error("this is not supposed to happen")
|
||||||
return userJoined(meetingId, user, processUser);
|
return userJoined(meetingId, user, processUser);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return callback();
|
return callback(); // all meeting arrays (if any) have been processed
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return processUser();
|
return processUser();
|
||||||
@ -134,7 +198,10 @@ Meteor.startup(() => {
|
|||||||
meetingId: meetingId
|
meetingId: meetingId
|
||||||
});
|
});
|
||||||
validStatus = message.payload.valid;
|
validStatus = message.payload.valid;
|
||||||
|
|
||||||
|
// if the user already exists in the db
|
||||||
if((user != null ? user.clientType : void 0) === "HTML5") {
|
if((user != null ? user.clientType : void 0) === "HTML5") {
|
||||||
|
//if the html5 client user was validated successfully, add a flag
|
||||||
return Meteor.Users.update({
|
return Meteor.Users.update({
|
||||||
userId: userId,
|
userId: userId,
|
||||||
meetingId: message.payload.meeting_id
|
meetingId: message.payload.meeting_id
|
||||||
@ -168,11 +235,14 @@ Meteor.startup(() => {
|
|||||||
if((userId != null) && (meetingId != null)) {
|
if((userId != null) && (meetingId != null)) {
|
||||||
return markUserOffline(meetingId, userId, callback);
|
return markUserOffline(meetingId, userId, callback);
|
||||||
} else {
|
} 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') {
|
} else if(eventName === 'presenter_assigned_message') {
|
||||||
newPresenterId = message.payload.new_presenter_id;
|
newPresenterId = message.payload.new_presenter_id;
|
||||||
if(newPresenterId != null) {
|
if(newPresenterId != null) {
|
||||||
|
// reset the previous presenter
|
||||||
Meteor.Users.update({
|
Meteor.Users.update({
|
||||||
"user.presenter": true,
|
"user.presenter": true,
|
||||||
meetingId: meetingId
|
meetingId: meetingId
|
||||||
@ -183,6 +253,7 @@ Meteor.startup(() => {
|
|||||||
}, (err, numUpdated) => {
|
}, (err, numUpdated) => {
|
||||||
return Meteor.log.info(` Updating old presenter numUpdated=${numUpdated}, err=${err}`);
|
return Meteor.log.info(` Updating old presenter numUpdated=${numUpdated}, err=${err}`);
|
||||||
});
|
});
|
||||||
|
// set the new presenter
|
||||||
Meteor.Users.update({
|
Meteor.Users.update({
|
||||||
"user.userid": newPresenterId,
|
"user.userid": newPresenterId,
|
||||||
meetingId: meetingId
|
meetingId: meetingId
|
||||||
@ -195,6 +266,8 @@ Meteor.startup(() => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
return callback();
|
return callback();
|
||||||
|
|
||||||
|
// for now not handling this serially #TODO
|
||||||
} else if(eventName === 'user_emoji_status_message') {
|
} else if(eventName === 'user_emoji_status_message') {
|
||||||
userId = message.payload.userid;
|
userId = message.payload.userid;
|
||||||
meetingId = message.payload.meeting_id;
|
meetingId = message.payload.meeting_id;
|
||||||
@ -213,11 +286,15 @@ Meteor.startup(() => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
return callback();
|
return callback();
|
||||||
|
|
||||||
|
// for now not handling this serially #TODO
|
||||||
} else if(eventName === 'user_locked_message' || eventName === 'user_unlocked_message') {
|
} else if(eventName === 'user_locked_message' || eventName === 'user_unlocked_message') {
|
||||||
userId = message.payload.userid;
|
userId = message.payload.userid;
|
||||||
isLocked = message.payload.locked;
|
isLocked = message.payload.locked;
|
||||||
setUserLockedStatus(meetingId, userId, isLocked);
|
setUserLockedStatus(meetingId, userId, isLocked);
|
||||||
return callback();
|
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") {
|
} 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}`);
|
Meteor.log.info(`DESTROYING MEETING ${meetingId}`);
|
||||||
return removeMeetingFromCollection(meetingId, callback);
|
return removeMeetingFromCollection(meetingId, callback);
|
||||||
@ -232,6 +309,8 @@ Meteor.startup(() => {
|
|||||||
unless eventName is "disconnect_all_users_message"
|
unless eventName is "disconnect_all_users_message"
|
||||||
removeMeetingFromCollection meetingId
|
removeMeetingFromCollection meetingId
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// for now not handling this serially #TODO
|
||||||
} else if(eventName === "get_chat_history_reply" && message.payload.requester_id === "nodeJSapp") {
|
} else if(eventName === "get_chat_history_reply" && message.payload.requester_id === "nodeJSapp") {
|
||||||
if(Meteor.Meetings.findOne({
|
if(Meteor.Meetings.findOne({
|
||||||
MeetingId: message.payload.meeting_id
|
MeetingId: message.payload.meeting_id
|
||||||
@ -243,13 +322,19 @@ Meteor.startup(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return callback();
|
return callback();
|
||||||
|
|
||||||
|
// for now not handling this serially #TODO
|
||||||
} else if(eventName === "send_public_chat_message" || eventName === "send_private_chat_message") {
|
} else if(eventName === "send_public_chat_message" || eventName === "send_private_chat_message") {
|
||||||
messageObject = message.payload.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;
|
messageObject.from_time = message.header.current_time;
|
||||||
addChatToCollection(meetingId, messageObject);
|
addChatToCollection(meetingId, messageObject);
|
||||||
return callback();
|
return callback();
|
||||||
|
|
||||||
|
// for now not handling this serially #TODO
|
||||||
} else if(eventName === "presentation_shared_message") {
|
} else if(eventName === "presentation_shared_message") {
|
||||||
presentationId = (ref7 = message.payload.presentation) != null ? ref7.id : void 0;
|
presentationId = (ref7 = message.payload.presentation) != null ? ref7.id : void 0;
|
||||||
|
// change the currently displayed presentation to presentation.current = false
|
||||||
Meteor.Presentations.update({
|
Meteor.Presentations.update({
|
||||||
"presentation.current": true,
|
"presentation.current": true,
|
||||||
meetingId: meetingId
|
meetingId: meetingId
|
||||||
@ -258,6 +343,8 @@ Meteor.startup(() => {
|
|||||||
"presentation.current": false
|
"presentation.current": false
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//update(if already present) entirely the presentation with the fresh data
|
||||||
removePresentationFromCollection(meetingId, presentationId);
|
removePresentationFromCollection(meetingId, presentationId);
|
||||||
addPresentationToCollection(meetingId, message.payload.presentation);
|
addPresentationToCollection(meetingId, message.payload.presentation);
|
||||||
ref9 = (ref8 = message.payload.presentation) != null ? ref8.pages : void 0;
|
ref9 = (ref8 = message.payload.presentation) != null ? ref8.pages : void 0;
|
||||||
@ -273,6 +360,8 @@ Meteor.startup(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return callback();
|
return callback();
|
||||||
|
|
||||||
|
// for now not handling this serially #TODO
|
||||||
} else if(eventName === "get_presentation_info_reply" && message.payload.requester_id === "nodeJSapp") {
|
} else if(eventName === "get_presentation_info_reply" && message.payload.requester_id === "nodeJSapp") {
|
||||||
ref11 = message.payload.presentations;
|
ref11 = message.payload.presentations;
|
||||||
for(k = 0, len2 = ref11.length; k < len2; k++) {
|
for(k = 0, len2 = ref11.length; k < len2; k++) {
|
||||||
@ -281,8 +370,14 @@ Meteor.startup(() => {
|
|||||||
ref12 = presentation.pages;
|
ref12 = presentation.pages;
|
||||||
for(l = 0, len3 = ref12.length; l < len3; l++) {
|
for(l = 0, len3 = ref12.length; l < len3; l++) {
|
||||||
page = ref12[l];
|
page = ref12[l];
|
||||||
|
|
||||||
|
//add the slide to the collection
|
||||||
addSlideToCollection(meetingId, presentation.id, page);
|
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`;
|
replyTo = `${meetingId}/nodeJSapp`;
|
||||||
message = {
|
message = {
|
||||||
"payload": {
|
"payload": {
|
||||||
@ -304,16 +399,23 @@ Meteor.startup(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return callback();
|
return callback();
|
||||||
|
|
||||||
|
// for now not handling this serially #TODO
|
||||||
} else if(eventName === "presentation_page_changed_message") {
|
} else if(eventName === "presentation_page_changed_message") {
|
||||||
newSlide = message.payload.page;
|
newSlide = message.payload.page;
|
||||||
displayThisSlide(meetingId, newSlide != null ? newSlide.id : void 0, newSlide);
|
displayThisSlide(meetingId, newSlide != null ? newSlide.id : void 0, newSlide);
|
||||||
return callback();
|
return callback();
|
||||||
|
|
||||||
|
// for now not handling this serially #TODO
|
||||||
} else if(eventName === "presentation_removed_message") {
|
} else if(eventName === "presentation_removed_message") {
|
||||||
presentationId = message.payload.presentation_id;
|
presentationId = message.payload.presentation_id;
|
||||||
meetingId = message.payload.meeting_id;
|
meetingId = message.payload.meeting_id;
|
||||||
removePresentationFromCollection(meetingId, presentationId);
|
removePresentationFromCollection(meetingId, presentationId);
|
||||||
return callback();
|
return callback();
|
||||||
|
|
||||||
|
// for now not handling this serially #TODO
|
||||||
} else if(eventName === "get_whiteboard_shapes_reply" && message.payload.requester_id === "nodeJSapp") {
|
} 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({
|
if(Meteor.WhiteboardCleanStatus.findOne({
|
||||||
meetingId: meetingId
|
meetingId: meetingId
|
||||||
}) == null) {
|
}) == null) {
|
||||||
@ -329,7 +431,11 @@ Meteor.startup(() => {
|
|||||||
addShapeToCollection(meetingId, whiteboardId, shape);
|
addShapeToCollection(meetingId, whiteboardId, shape);
|
||||||
}
|
}
|
||||||
return callback();
|
return callback();
|
||||||
|
|
||||||
|
// for now not handling this serially #TODO
|
||||||
} else if(eventName === "send_whiteboard_shape_message") {
|
} 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') {
|
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);
|
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;
|
whiteboardId = shape != null ? shape.wb_id : void 0;
|
||||||
addShapeToCollection(meetingId, whiteboardId, shape);
|
addShapeToCollection(meetingId, whiteboardId, shape);
|
||||||
return callback();
|
return callback();
|
||||||
|
|
||||||
|
// for now not handling this serially #TODO
|
||||||
} else if(eventName === "presentation_cursor_updated_message") {
|
} else if(eventName === "presentation_cursor_updated_message") {
|
||||||
cursor = {
|
cursor = {
|
||||||
x: message.payload.x_percent,
|
x: message.payload.x_percent,
|
||||||
y: message.payload.y_percent
|
y: message.payload.y_percent
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// update the location of the cursor on the whiteboard
|
||||||
updateCursorLocation(meetingId, cursor);
|
updateCursorLocation(meetingId, cursor);
|
||||||
return callback();
|
return callback();
|
||||||
|
|
||||||
|
// for now not handling this serially #TODO
|
||||||
} else if(eventName === "whiteboard_cleared_message") {
|
} else if(eventName === "whiteboard_cleared_message") {
|
||||||
whiteboardId = message.payload.whiteboard_id;
|
whiteboardId = message.payload.whiteboard_id;
|
||||||
Meteor.WhiteboardCleanStatus.update({
|
Meteor.WhiteboardCleanStatus.update({
|
||||||
@ -355,11 +467,15 @@ Meteor.startup(() => {
|
|||||||
});
|
});
|
||||||
removeAllShapesFromSlide(meetingId, whiteboardId);
|
removeAllShapesFromSlide(meetingId, whiteboardId);
|
||||||
return callback();
|
return callback();
|
||||||
|
|
||||||
|
// for now not handling this serially #TODO
|
||||||
} else if(eventName === "undo_whiteboard_request") {
|
} else if(eventName === "undo_whiteboard_request") {
|
||||||
whiteboardId = message.payload.whiteboard_id;
|
whiteboardId = message.payload.whiteboard_id;
|
||||||
shapeId = message.payload.shape_id;
|
shapeId = message.payload.shape_id;
|
||||||
removeShapeFromSlide(meetingId, whiteboardId, shapeId);
|
removeShapeFromSlide(meetingId, whiteboardId, shapeId);
|
||||||
return callback();
|
return callback();
|
||||||
|
|
||||||
|
// for now not handling this serially #TODO
|
||||||
} else if(eventName === "presentation_page_resized_message") {
|
} else if(eventName === "presentation_page_resized_message") {
|
||||||
slideId = (ref14 = message.payload.page) != null ? ref14.id : void 0;
|
slideId = (ref14 = message.payload.page) != null ? ref14.id : void 0;
|
||||||
heightRatio = (ref15 = message.payload.page) != null ? ref15.height_ratio : void 0;
|
heightRatio = (ref15 = message.payload.page) != null ? ref15.height_ratio : void 0;
|
||||||
@ -379,6 +495,8 @@ Meteor.startup(() => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
return callback();
|
return callback();
|
||||||
|
|
||||||
|
// for now not handling this serially #TODO
|
||||||
} else if(eventName === "recording_status_changed_message") {
|
} else if(eventName === "recording_status_changed_message") {
|
||||||
intendedForRecording = message.payload.recorded;
|
intendedForRecording = message.payload.recorded;
|
||||||
currentlyBeingRecorded = message.payload.recording;
|
currentlyBeingRecorded = message.payload.recording;
|
||||||
@ -391,16 +509,26 @@ Meteor.startup(() => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
return callback();
|
return callback();
|
||||||
|
|
||||||
|
// --------------------------------------------------
|
||||||
|
// lock settings ------------------------------------
|
||||||
|
// for now not handling this serially #TODO
|
||||||
} else if(eventName === "eject_voice_user_message") {
|
} else if(eventName === "eject_voice_user_message") {
|
||||||
return callback();
|
return callback();
|
||||||
|
|
||||||
|
// for now not handling this serially #TODO
|
||||||
} else if(eventName === "new_permission_settings") {
|
} else if(eventName === "new_permission_settings") {
|
||||||
oldSettings = (ref19 = Meteor.Meetings.findOne({
|
oldSettings = (ref19 = Meteor.Meetings.findOne({
|
||||||
meetingId: meetingId
|
meetingId: meetingId
|
||||||
})) != null ? ref19.roomLockSettings : void 0;
|
})) != null ? ref19.roomLockSettings : void 0;
|
||||||
newSettings = (ref20 = message.payload) != null ? ref20.permissions : 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) {
|
if(!(oldSettings != null ? oldSettings.disableMic : void 0) && newSettings.disableMic) {
|
||||||
handleLockingMic(meetingId, newSettings);
|
handleLockingMic(meetingId, newSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// substitute with the new lock settings
|
||||||
Meteor.Meetings.update({
|
Meteor.Meetings.update({
|
||||||
meetingId: meetingId
|
meetingId: meetingId
|
||||||
}, {
|
}, {
|
||||||
@ -415,6 +543,8 @@ Meteor.startup(() => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
return callback();
|
return callback();
|
||||||
|
|
||||||
|
// for now not handling this serially #TODO
|
||||||
} else if(eventName === "poll_started_message") {
|
} else if(eventName === "poll_started_message") {
|
||||||
if((message.payload.meeting_id != null) && (message.payload.requester_id != null) && (message.payload.poll != null)) {
|
if((message.payload.meeting_id != null) && (message.payload.requester_id != null) && (message.payload.poll != null)) {
|
||||||
if(Meteor.Meetings.findOne({
|
if(Meteor.Meetings.findOne({
|
||||||
@ -437,11 +567,15 @@ Meteor.startup(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return callback();
|
return callback();
|
||||||
|
|
||||||
|
// for now not handling this serially #TODO
|
||||||
} else if(eventName === "poll_stopped_message") {
|
} else if(eventName === "poll_stopped_message") {
|
||||||
meetingId = message.payload.meeting_id;
|
meetingId = message.payload.meeting_id;
|
||||||
poll_id = message.payload.poll_id;
|
poll_id = message.payload.poll_id;
|
||||||
clearPollCollection(meetingId, poll_id);
|
clearPollCollection(meetingId, poll_id);
|
||||||
return callback();
|
return callback();
|
||||||
|
|
||||||
|
// for now not handling this serially #TODO
|
||||||
} else if(eventName === "user_voted_poll_message") {
|
} 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)) {
|
if((((ref21 = message.payload) != null ? ref21.poll : void 0) != null) && (message.payload.meeting_id != null) && (message.payload.presenter_id != null)) {
|
||||||
pollObj = message.payload.poll;
|
pollObj = message.payload.poll;
|
||||||
@ -450,6 +584,8 @@ Meteor.startup(() => {
|
|||||||
updatePollCollection(pollObj, meetingId, requesterId);
|
updatePollCollection(pollObj, meetingId, requesterId);
|
||||||
return callback();
|
return callback();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for now not handling this serially #TODO
|
||||||
} else if(eventName === "poll_show_result_message") {
|
} else if(eventName === "poll_show_result_message") {
|
||||||
if((message.payload.poll.id != null) && (message.payload.meeting_id != null)) {
|
if((message.payload.poll.id != null) && (message.payload.meeting_id != null)) {
|
||||||
poll_id = message.payload.poll.id;
|
poll_id = message.payload.poll.id;
|
||||||
@ -457,7 +593,7 @@ Meteor.startup(() => {
|
|||||||
clearPollCollection(meetingId, poll_id);
|
clearPollCollection(meetingId, poll_id);
|
||||||
}
|
}
|
||||||
return callback();
|
return callback();
|
||||||
} else {
|
} else { // keep moving in the queue
|
||||||
if(indexOf.call(notLoggedEventTypes, eventName) < 0) {
|
if(indexOf.call(notLoggedEventTypes, eventName) < 0) {
|
||||||
Meteor.log.info(`WARNING!!! THE JSON MESSAGE WAS NOT OF TYPE SUPPORTED BY THIS APPLICATION
|
Meteor.log.info(`WARNING!!! THE JSON MESSAGE WAS NOT OF TYPE SUPPORTED BY THIS APPLICATION
|
||||||
${eventName} {JSON.stringify message}`);
|
${eventName} {JSON.stringify message}`);
|
||||||
|
@ -2,37 +2,71 @@ let moderator, presenter, viewer;
|
|||||||
|
|
||||||
presenter = {
|
presenter = {
|
||||||
switchSlide: true,
|
switchSlide: true,
|
||||||
|
|
||||||
|
//poll
|
||||||
subscribePoll: true,
|
subscribePoll: true,
|
||||||
subscribeAnswers: 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 = {
|
moderator = {
|
||||||
|
// audio listen only
|
||||||
joinListenOnly: true,
|
joinListenOnly: true,
|
||||||
leaveListenOnly: 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,
|
raiseOwnHand: true,
|
||||||
lowerOwnHand: true,
|
lowerOwnHand: true,
|
||||||
|
|
||||||
|
// muting
|
||||||
muteSelf: true,
|
muteSelf: true,
|
||||||
unmuteSelf: true,
|
unmuteSelf: true,
|
||||||
|
|
||||||
logoutSelf: true,
|
logoutSelf: true,
|
||||||
|
|
||||||
|
//subscribing
|
||||||
subscribeUsers: true,
|
subscribeUsers: true,
|
||||||
subscribeChat: true,
|
subscribeChat: true,
|
||||||
|
|
||||||
|
//chat
|
||||||
chatPublic: true,
|
chatPublic: true,
|
||||||
chatPrivate: true,
|
chatPrivate: true,
|
||||||
|
|
||||||
|
//poll
|
||||||
subscribePoll: true,
|
subscribePoll: true,
|
||||||
subscribeAnswers: false,
|
subscribeAnswers: false,
|
||||||
|
|
||||||
|
//emojis
|
||||||
setEmojiStatus: true,
|
setEmojiStatus: true,
|
||||||
clearEmojiStatus: true,
|
clearEmojiStatus: true,
|
||||||
|
|
||||||
|
//user control
|
||||||
kickUser: true,
|
kickUser: true,
|
||||||
setPresenter: 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) {
|
viewer = function(meetingId, userId) {
|
||||||
let ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7;
|
let ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7;
|
||||||
return {
|
return {
|
||||||
|
|
||||||
|
// listen only
|
||||||
joinListenOnly: true,
|
joinListenOnly: true,
|
||||||
leaveListenOnly: 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,
|
raiseOwnHand: true,
|
||||||
lowerOwnHand: true,
|
lowerOwnHand: true,
|
||||||
|
|
||||||
|
// muting
|
||||||
muteSelf: true,
|
muteSelf: true,
|
||||||
unmuteSelf: !((ref = Meteor.Meetings.findOne({
|
unmuteSelf: !((ref = Meteor.Meetings.findOne({
|
||||||
meetingId: meetingId
|
meetingId: meetingId
|
||||||
@ -40,9 +74,14 @@ viewer = function(meetingId, userId) {
|
|||||||
meetingId: meetingId,
|
meetingId: meetingId,
|
||||||
userId: userId
|
userId: userId
|
||||||
})) != null ? ref1.user.locked : void 0),
|
})) != null ? ref1.user.locked : void 0),
|
||||||
|
|
||||||
logoutSelf: true,
|
logoutSelf: true,
|
||||||
|
|
||||||
|
//subscribing
|
||||||
subscribeUsers: true,
|
subscribeUsers: true,
|
||||||
subscribeChat: true,
|
subscribeChat: true,
|
||||||
|
|
||||||
|
//chat
|
||||||
chatPublic: !((ref2 = Meteor.Meetings.findOne({
|
chatPublic: !((ref2 = Meteor.Meetings.findOne({
|
||||||
meetingId: meetingId
|
meetingId: meetingId
|
||||||
})) != null ? ref2.roomLockSettings.disablePublicChat : void 0) || !((ref3 = Meteor.Users.findOne({
|
})) != null ? ref2.roomLockSettings.disablePublicChat : void 0) || !((ref3 = Meteor.Users.findOne({
|
||||||
@ -61,13 +100,19 @@ viewer = function(meetingId, userId) {
|
|||||||
meetingId: meetingId,
|
meetingId: meetingId,
|
||||||
userId: userId
|
userId: userId
|
||||||
})) != null ? ref7.user.presenter : void 0),
|
})) != null ? ref7.user.presenter : void 0),
|
||||||
|
|
||||||
|
//poll
|
||||||
subscribePoll: true,
|
subscribePoll: true,
|
||||||
subscribeAnswers: false,
|
subscribeAnswers: false,
|
||||||
|
|
||||||
|
//emojis
|
||||||
setEmojiStatus: true,
|
setEmojiStatus: true,
|
||||||
clearEmojiStatus: 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) {
|
this.isAllowedTo = function(action, meetingId, userId, authToken) {
|
||||||
let ref, ref1, ref2, ref3, user, validated;
|
let ref, ref1, ref2, ref3, user, validated;
|
||||||
validated = (ref = Meteor.Users.findOne({
|
validated = (ref = Meteor.Users.findOne({
|
||||||
@ -79,14 +124,22 @@ this.isAllowedTo = function(action, meetingId, userId, authToken) {
|
|||||||
meetingId: meetingId,
|
meetingId: meetingId,
|
||||||
userId: userId
|
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") {
|
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) {
|
if((ref1 = user.user) != null ? ref1.presenter : void 0) {
|
||||||
Meteor.log.info("user permissions presenter case");
|
Meteor.log.info("user permissions presenter case");
|
||||||
return presenter[action] || viewer(meetingId, userId)[action] || false;
|
return presenter[action] || viewer(meetingId, userId)[action] || false;
|
||||||
|
|
||||||
|
// VIEWER
|
||||||
} else if(((ref2 = user.user) != null ? ref2.role : void 0) === 'VIEWER') {
|
} else if(((ref2 = user.user) != null ? ref2.role : void 0) === 'VIEWER') {
|
||||||
Meteor.log.info("user permissions viewer case");
|
Meteor.log.info("user permissions viewer case");
|
||||||
return viewer(meetingId, userId)[action] || false;
|
return viewer(meetingId, userId)[action] || false;
|
||||||
|
|
||||||
|
// MODERATOR
|
||||||
} else if(((ref3 = user.user) != null ? ref3.role : void 0) === 'MODERATOR') {
|
} else if(((ref3 = user.user) != null ? ref3.role : void 0) === 'MODERATOR') {
|
||||||
Meteor.log.info("user permissions moderator case");
|
Meteor.log.info("user permissions moderator case");
|
||||||
return moderator[action] || false;
|
return moderator[action] || false;
|
||||||
@ -95,7 +148,9 @@ this.isAllowedTo = function(action, meetingId, userId, authToken) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// user was not validated
|
||||||
if(action === "logoutSelf") {
|
if(action === "logoutSelf") {
|
||||||
|
// on unsuccessful sign-in
|
||||||
Meteor.log.warn("a user was successfully removed from the meeting following an unsuccessful login");
|
Meteor.log.warn("a user was successfully removed from the meeting following an unsuccessful login");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user