Merge branch 'merging-html5-with-master' of https://github.com/antobinary/bigbluebutton
This commit is contained in:
commit
c9ad7035b1
@ -14,6 +14,7 @@ import org.bigbluebutton.conference.service.messaging.RegisterUserMessage;
|
||||
import org.bigbluebutton.conference.service.messaging.UserConnectedToGlobalAudio;
|
||||
import org.bigbluebutton.conference.service.messaging.UserDisconnectedFromGlobalAudio;
|
||||
import org.bigbluebutton.conference.service.messaging.ValidateAuthTokenMessage;
|
||||
import org.bigbluebutton.conference.service.messaging.GetAllMeetingsRequest;
|
||||
import org.bigbluebutton.conference.service.messaging.redis.MessageHandler;
|
||||
import org.bigbluebutton.core.api.IBigBlueButtonInGW;
|
||||
|
||||
@ -54,13 +55,18 @@ public class MeetingMessageHandler implements MessageHandler {
|
||||
bbbGW.validateAuthToken(emm.meetingId, emm.userId, emm.token, emm.replyTo);
|
||||
} else if (msg instanceof UserConnectedToGlobalAudio) {
|
||||
UserConnectedToGlobalAudio emm = (UserConnectedToGlobalAudio) msg;
|
||||
log.debug("Received UserConnectedToGlobalAudio toekn request. user id [{}]", emm.name);
|
||||
log.info("Received UserConnectedToGlobalAudio token request. user id [{}]", emm.name);
|
||||
bbbGW.userConnectedToGlobalAudio(emm.voiceConf, emm.userid, emm.name);
|
||||
} else if (msg instanceof UserDisconnectedFromGlobalAudio) {
|
||||
UserDisconnectedFromGlobalAudio emm = (UserDisconnectedFromGlobalAudio) msg;
|
||||
log.debug("Received UserDisconnectedFromGlobalAudio toekn request. Meeting id [{}]", emm.name);
|
||||
log.debug("Received UserDisconnectedFromGlobalAudio token request. Meeting id [{}]", emm.name);
|
||||
bbbGW.userDisconnectedFromGlobalAudio(emm.voiceConf, emm.userid, emm.name);
|
||||
}
|
||||
else if (msg instanceof GetAllMeetingsRequest) {
|
||||
GetAllMeetingsRequest emm = (GetAllMeetingsRequest) msg;
|
||||
log.info("Received GetAllMeetingsRequest");
|
||||
bbbGW.getAllMeetings("no_need_of_a_meeting_id");
|
||||
}
|
||||
}
|
||||
} else if (channel.equalsIgnoreCase(MessagingConstants.TO_SYSTEM_CHANNEL)) {
|
||||
IMessage msg = MessageFromJsonConverter.convert(message);
|
||||
|
@ -31,52 +31,41 @@ public class ChatMessageListener implements MessageHandler{
|
||||
String eventName = headerObject.get("name").toString();
|
||||
eventName = eventName.replace("\"", "");
|
||||
|
||||
if (eventName.equalsIgnoreCase("public_chat_message_event") ||
|
||||
eventName.equalsIgnoreCase("send_public_chat_message") || //identical
|
||||
eventName.equalsIgnoreCase("private_chat_message_event") ||
|
||||
eventName.equalsIgnoreCase("send_private_chat_message") ||//identical
|
||||
eventName.equalsIgnoreCase("get_chat_history")){
|
||||
if (eventName.equalsIgnoreCase(MessagingConstants.SEND_PUBLIC_CHAT_MESSAGE_REQUEST) ||
|
||||
eventName.equalsIgnoreCase(MessagingConstants.SEND_PRIVATE_CHAT_MESSAGE_REQUEST)){
|
||||
|
||||
String meetingID = payloadObject.get("meeting_id").toString().replace("\"", "");
|
||||
String requesterID = payloadObject.get("requester_id").toString().replace("\"", "");
|
||||
|
||||
//case getChatHistory
|
||||
if(eventName.equalsIgnoreCase("get_chat_history")) {
|
||||
String replyTo = meetingID + "/" + requesterID;
|
||||
bbbGW.getChatHistory(meetingID, requesterID, replyTo);
|
||||
}
|
||||
else {
|
||||
String chatType = messageObject.get("chat_type").toString().replace("\"", "");
|
||||
String fromUserID = messageObject.get("from_userid").toString().replace("\"", "");
|
||||
String fromUsername = messageObject.get("from_username").toString().replace("\"", "");
|
||||
String fromColor = messageObject.get("from_color").toString().replace("\"", "");
|
||||
String fromTime = messageObject.get("from_time").toString().replace("\"", "");
|
||||
String fromTimezoneOffset = messageObject.get("from_tz_offset").toString().replace("\"", "");
|
||||
String fromLang = messageObject.get("from_lang").toString().replace("\"", "");
|
||||
String toUserID = messageObject.get("to_userid").toString().replace("\"", "");
|
||||
String toUsername = messageObject.get("to_username").toString().replace("\"", "");
|
||||
String chatText = messageObject.get("message").toString().replace("\"", "");
|
||||
String chatType = messageObject.get("chat_type").toString().replace("\"", "");
|
||||
String fromUserID = messageObject.get("from_userid").toString().replace("\"", "");
|
||||
String fromUsername = messageObject.get("from_username").toString().replace("\"", "");
|
||||
String fromColor = messageObject.get("from_color").toString().replace("\"", "");
|
||||
String fromTime = messageObject.get("from_time").toString().replace("\"", "");
|
||||
String fromTimezoneOffset = messageObject.get("from_tz_offset").toString().replace("\"", "");
|
||||
String fromLang = messageObject.get("from_lang").toString().replace("\"", "");
|
||||
String toUserID = messageObject.get("to_userid").toString().replace("\"", "");
|
||||
String toUsername = messageObject.get("to_username").toString().replace("\"", "");
|
||||
String chatText = messageObject.get("message").toString().replace("\"", "");
|
||||
|
||||
Map<String, String> map = new HashMap<String, String>();
|
||||
map.put(ChatKeyUtil.CHAT_TYPE, chatType);
|
||||
map.put(ChatKeyUtil.FROM_USERID, fromUserID);
|
||||
map.put(ChatKeyUtil.FROM_USERNAME, fromUsername);
|
||||
map.put(ChatKeyUtil.FROM_COLOR, fromColor);
|
||||
map.put(ChatKeyUtil.FROM_TIME, fromTime);
|
||||
map.put(ChatKeyUtil.FROM_TZ_OFFSET, fromTimezoneOffset);
|
||||
map.put(ChatKeyUtil.FROM_LANG, fromLang);
|
||||
map.put(ChatKeyUtil.TO_USERID, toUserID);
|
||||
map.put(ChatKeyUtil.TO_USERNAME, toUsername);
|
||||
map.put(ChatKeyUtil.MESSAGE, chatText);
|
||||
Map<String, String> map = new HashMap<String, String>();
|
||||
map.put(ChatKeyUtil.CHAT_TYPE, chatType);
|
||||
map.put(ChatKeyUtil.FROM_USERID, fromUserID);
|
||||
map.put(ChatKeyUtil.FROM_USERNAME, fromUsername);
|
||||
map.put(ChatKeyUtil.FROM_COLOR, fromColor);
|
||||
map.put(ChatKeyUtil.FROM_TIME, fromTime);
|
||||
map.put(ChatKeyUtil.FROM_TZ_OFFSET, fromTimezoneOffset);
|
||||
map.put(ChatKeyUtil.FROM_LANG, fromLang);
|
||||
map.put(ChatKeyUtil.TO_USERID, toUserID);
|
||||
map.put(ChatKeyUtil.TO_USERNAME, toUsername);
|
||||
map.put(ChatKeyUtil.MESSAGE, chatText);
|
||||
|
||||
//public message
|
||||
if(eventName.equalsIgnoreCase("public_chat_message_event")
|
||||
|| eventName.equalsIgnoreCase("send_public_chat_message")) {
|
||||
bbbGW.sendPublicMessage(meetingID, requesterID, map);
|
||||
} else if(eventName.equalsIgnoreCase("private_chat_message_event")
|
||||
|| eventName.equalsIgnoreCase("send_private_chat_message")) {
|
||||
bbbGW.sendPrivateMessage(meetingID, requesterID, map);
|
||||
}
|
||||
if(eventName.equalsIgnoreCase("public_chat_message_event")
|
||||
|| eventName.equalsIgnoreCase("send_public_chat_message")) {
|
||||
bbbGW.sendPublicMessage(meetingID, requesterID, map);
|
||||
} else if(eventName.equalsIgnoreCase("private_chat_message_event")
|
||||
|| eventName.equalsIgnoreCase("send_private_chat_message")) {
|
||||
bbbGW.sendPrivateMessage(meetingID, requesterID, map);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,12 @@
|
||||
package org.bigbluebutton.conference.service.messaging;
|
||||
|
||||
public class GetAllMeetingsRequest implements IMessage {
|
||||
public static final String GET_ALL_MEETINGS_REQUEST_EVENT = "get_all_meetings_request";
|
||||
public static final String VERSION = "0.0.1";
|
||||
|
||||
public final String meetingId;
|
||||
|
||||
public GetAllMeetingsRequest(String meetingId) {
|
||||
this.meetingId = meetingId;
|
||||
}
|
||||
}
|
@ -33,6 +33,8 @@ public class MessageFromJsonConverter {
|
||||
return UserConnectedToGlobalAudio.fromJson(message);
|
||||
case UserDisconnectedFromGlobalAudio.USER_DISCONNECTED_FROM_GLOBAL_AUDIO:
|
||||
return UserDisconnectedFromGlobalAudio.fromJson(message);
|
||||
case GetAllMeetingsRequest.GET_ALL_MEETINGS_REQUEST_EVENT:
|
||||
return new GetAllMeetingsRequest("the_string_is_not_used_anywhere");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -72,4 +74,6 @@ public class MessageFromJsonConverter {
|
||||
String id = payload.get(Constants.KEEP_ALIVE_ID).getAsString();
|
||||
return new KeepAliveMessage(id);
|
||||
}
|
||||
|
||||
//private static IMessage processGetAllMeetings(JsonObject)
|
||||
}
|
||||
|
@ -37,8 +37,9 @@ public class MessagingConstants {
|
||||
public static final String TO_PRESENTATION_CHANNEL = TO_BBB_APPS_CHANNEL + ":presentation";
|
||||
public static final String TO_POLLING_CHANNEL = TO_BBB_APPS_CHANNEL + ":polling";
|
||||
public static final String TO_USERS_CHANNEL = TO_BBB_APPS_CHANNEL + ":users";
|
||||
public static final String TO_CHAT_CHANNEL = TO_BBB_APPS_CHANNEL + ":chat";
|
||||
|
||||
public static final String TO_CHAT_CHANNEL = TO_BBB_APPS_CHANNEL + ":chat";
|
||||
public static final String TO_VOICE_CHANNEL = TO_BBB_APPS_CHANNEL + ":voice";
|
||||
public static final String TO_WHITEBOARD_CHANNEL = TO_BBB_APPS_CHANNEL + ":whiteboard";
|
||||
|
||||
public static final String DESTROY_MEETING_REQUEST_EVENT = "DestroyMeetingRequestEvent";
|
||||
public static final String CREATE_MEETING_REQUEST_EVENT = "CreateMeetingRequestEvent";
|
||||
@ -51,4 +52,7 @@ public class MessagingConstants {
|
||||
public static final String USER_STATUS_CHANGE_EVENT = "UserStatusChangeEvent";
|
||||
public static final String SEND_POLLS_EVENT = "SendPollsEvent";
|
||||
public static final String RECORD_STATUS_EVENT = "RecordStatusEvent";
|
||||
public static final String SEND_PUBLIC_CHAT_MESSAGE_REQUEST = "send_public_chat_message_request";
|
||||
public static final String SEND_PRIVATE_CHAT_MESSAGE_REQUEST = "send_private_chat_message_request";
|
||||
public static final String MUTE_USER_REQUEST = "mute_user_request";
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ package org.bigbluebutton.conference.service.participants;
|
||||
|
||||
import org.bigbluebutton.conference.service.messaging.MessagingConstants;
|
||||
import org.bigbluebutton.conference.service.messaging.redis.MessageHandler;
|
||||
//import org.bigbluebutton.core.api.*;
|
||||
|
||||
import org.bigbluebutton.core.api.IBigBlueButtonInGW;
|
||||
import com.google.gson.JsonParser;
|
||||
@ -20,7 +21,6 @@ public class ParticipantsListener implements MessageHandler{
|
||||
@Override
|
||||
public void handleMessage(String pattern, String channel, String message) {
|
||||
if (channel.equalsIgnoreCase(MessagingConstants.TO_USERS_CHANNEL)) {
|
||||
System.out.println("AntonChannel=(participants)" + channel);
|
||||
|
||||
JsonParser parser = new JsonParser();
|
||||
JsonObject obj = (JsonObject) parser.parse(message);
|
||||
@ -29,46 +29,22 @@ public class ParticipantsListener implements MessageHandler{
|
||||
|
||||
String eventName = headerObject.get("name").toString().replace("\"", "");
|
||||
|
||||
if(eventName.equalsIgnoreCase("register_user_request") ||
|
||||
eventName.equalsIgnoreCase("user_left_event") ||
|
||||
eventName.equalsIgnoreCase("user_joined_event") ||
|
||||
eventName.equalsIgnoreCase("get_users_request") ||
|
||||
eventName.equalsIgnoreCase("raise_user_hand_request")){
|
||||
if(eventName.equalsIgnoreCase("user_leaving_request") ||
|
||||
eventName.equalsIgnoreCase("user_raised_hand_message") ||
|
||||
eventName.equalsIgnoreCase("user_lowered_hand_message")){
|
||||
|
||||
String roomName = payloadObject.get("meeting_id").toString().replace("\"", "");
|
||||
String userID = payloadObject.get("userid").toString().replace("\"", "");
|
||||
|
||||
if(eventName.equalsIgnoreCase("register_user_request")){
|
||||
String userID = payloadObject.get("user_id").toString().replace("\"", "");
|
||||
String username = payloadObject.get("name").toString().replace("\"", "");
|
||||
String role = payloadObject.get("role").toString().replace("\"", "");
|
||||
String externUserID = payloadObject.get("external_user_id").toString().replace("\"", "");
|
||||
|
||||
}
|
||||
else if(eventName.equalsIgnoreCase("user_left_event")){
|
||||
String userID = payloadObject.get("user_id").toString().replace("\"", "");
|
||||
|
||||
if(eventName.equalsIgnoreCase("user_leaving_request")){
|
||||
bbbInGW.userLeft(roomName, userID);
|
||||
}
|
||||
else if(eventName.equalsIgnoreCase("user_joined_event")){
|
||||
String userID = payloadObject.get("user_id").toString().replace("\"", "");
|
||||
|
||||
bbbInGW.userJoin(roomName, userID);
|
||||
else if(eventName.equalsIgnoreCase("user_raised_hand_message")){
|
||||
bbbInGW.userRaiseHand(roomName, userID);
|
||||
}
|
||||
else if(eventName.equalsIgnoreCase("get_users_request")){
|
||||
String requesterID = payloadObject.get("requester_id").toString().replace("\"", "");
|
||||
bbbInGW.getUsers(roomName, requesterID);
|
||||
}
|
||||
else if(eventName.equalsIgnoreCase("raise_user_hand_request")){
|
||||
String userID = payloadObject.get("user_id").toString().replace("\"", "");
|
||||
boolean raise = Boolean.parseBoolean(payloadObject.get("raise").toString().replace("\"", ""));
|
||||
|
||||
if(raise){
|
||||
bbbInGW.userRaiseHand(roomName, userID);
|
||||
}
|
||||
else {
|
||||
String requesterID = payloadObject.get("requester_id").toString().replace("\"", "");
|
||||
bbbInGW.lowerHand(roomName, userID, requesterID);
|
||||
}
|
||||
else if(eventName.equalsIgnoreCase("user_lowered_hand_message")){
|
||||
String requesterID = payloadObject.get("lowered_by").toString().replace("\"", "");
|
||||
bbbInGW.lowerHand(roomName, userID, requesterID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,6 @@
|
||||
package org.bigbluebutton.conference.service.voice;
|
||||
|
||||
public class VoiceKeyUtil {
|
||||
public static final String MUTE = "mute";
|
||||
public static final String USERID = "userId";
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package org.bigbluebutton.conference.service.voice;
|
||||
|
||||
import org.bigbluebutton.conference.service.messaging.MessagingConstants;
|
||||
import org.bigbluebutton.conference.service.messaging.redis.MessageHandler;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.bigbluebutton.core.api.IBigBlueButtonInGW;
|
||||
|
||||
public class VoiceMessageListener implements MessageHandler{
|
||||
|
||||
private IBigBlueButtonInGW bbbGW;
|
||||
|
||||
public void setBigBlueButtonInGW(IBigBlueButtonInGW bbbGW) {
|
||||
this.bbbGW = bbbGW;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(String pattern, String channel, String message) {
|
||||
if (channel.equalsIgnoreCase(MessagingConstants.TO_VOICE_CHANNEL)) {
|
||||
|
||||
JsonParser parser = new JsonParser();
|
||||
JsonObject obj = (JsonObject) parser.parse(message);
|
||||
JsonObject headerObject = (JsonObject) obj.get("header");
|
||||
JsonObject payloadObject = (JsonObject) obj.get("payload");
|
||||
|
||||
String eventName = headerObject.get("name").toString().replace("\"", "");
|
||||
|
||||
if (eventName.equalsIgnoreCase(MessagingConstants.MUTE_USER_REQUEST)){
|
||||
|
||||
String meetingID = payloadObject.get("meeting_id").toString().replace("\"", "");
|
||||
String requesterID = payloadObject.get("requester_id").toString().replace("\"", "");
|
||||
String userID = payloadObject.get("userid").toString().replace("\"", "");
|
||||
String muteString = payloadObject.get(VoiceKeyUtil.MUTE).toString().replace("\"", "");
|
||||
Boolean mute = Boolean.valueOf(muteString);
|
||||
|
||||
System.out.println("handling mute_user_request");
|
||||
bbbGW.muteUser(meetingID, requesterID, userID, mute);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -17,11 +17,15 @@
|
||||
*
|
||||
*/
|
||||
package org.bigbluebutton.conference.service.voice;
|
||||
import org.slf4j.Logger;
import org.red5.server.api.Red5;
import org.bigbluebutton.conference.BigBlueButtonSession;
import org.bigbluebutton.conference.Constants;
import org.bigbluebutton.core.api.IBigBlueButtonInGW;
|
||||
import org.slf4j.Logger;
|
||||
import org.red5.server.api.Red5;
|
||||
import org.bigbluebutton.conference.BigBlueButtonSession;
|
||||
import org.bigbluebutton.conference.Constants;
|
||||
import org.bigbluebutton.core.api.IBigBlueButtonInGW;
|
||||
import org.red5.logging.Red5LoggerFactory;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
public class VoiceService {
|
||||
|
||||
private static Logger log = Red5LoggerFactory.getLogger( VoiceService.class, "bigbluebutton" );
|
||||
@ -70,16 +74,17 @@ public class VoiceService {
|
||||
String requesterID = getBbbSession().getInternalUserID();
|
||||
bbbInGW.isMeetingMuted(meetingID, requesterID);
|
||||
}
|
||||
|
||||
|
||||
// not sure if this is used
|
||||
public void muteUnmuteUser(Map<String, Object> msg) {
|
||||
Boolean mute = (Boolean) msg.get("mute");
|
||||
String userid = (String) msg.get("userId");
|
||||
Boolean mute = (Boolean) msg.get(VoiceKeyUtil.MUTE);
|
||||
String userid = (String) msg.get(VoiceKeyUtil.USERID);
|
||||
|
||||
String meetingID = Red5.getConnectionLocal().getScope().getName();
|
||||
String requesterID = getBbbSession().getInternalUserID();
|
||||
String requesterID = getBbbSession().getInternalUserID();
|
||||
bbbInGW.muteUser(meetingID, requesterID, userid, mute);
|
||||
}
|
||||
|
||||
|
||||
public void lockMuteUser(Map<String, Object> msg) {
|
||||
Boolean lock = (Boolean) msg.get("lock");
|
||||
String userid = (String) msg.get("userId");
|
||||
@ -89,7 +94,7 @@ public class VoiceService {
|
||||
bbbInGW.lockUser(meetingID, requesterID, userid, lock);
|
||||
}
|
||||
|
||||
public void kickUSer(Map<String, Object> msg) {
|
||||
public void kickUSer(Map<String, Object> msg) { //change to kickUser
|
||||
String userid = (String) msg.get("userId");
|
||||
String meetingID = Red5.getConnectionLocal().getScope().getName();
|
||||
String requesterID = getBbbSession().getInternalUserID();
|
||||
|
@ -0,0 +1,52 @@
|
||||
|
||||
package org.bigbluebutton.conference.service.whiteboard;
|
||||
|
||||
|
||||
import org.bigbluebutton.conference.service.messaging.MessagingConstants;
|
||||
import org.bigbluebutton.conference.service.messaging.redis.MessageHandler;
|
||||
|
||||
import org.bigbluebutton.core.api.IBigBlueButtonInGW;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
public class WhiteboardListener implements MessageHandler{
|
||||
|
||||
private IBigBlueButtonInGW bbbInGW;
|
||||
|
||||
public void setBigBlueButtonInGW(IBigBlueButtonInGW bbbInGW) {
|
||||
this.bbbInGW = bbbInGW;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(String pattern, String channel, String message) {
|
||||
if (channel.equalsIgnoreCase(MessagingConstants.TO_WHITEBOARD_CHANNEL)) {
|
||||
System.out.println("AntonChannel=(whiteboard)" + channel);
|
||||
|
||||
JsonParser parser = new JsonParser();
|
||||
JsonObject obj = (JsonObject) parser.parse(message);
|
||||
JsonObject headerObject = (JsonObject) obj.get("header");
|
||||
JsonObject payloadObject = (JsonObject) obj.get("payload");
|
||||
|
||||
String eventName = headerObject.get("name").toString().replace("\"", "");
|
||||
|
||||
if(eventName.equalsIgnoreCase("get_whiteboard_shapes_request")){
|
||||
//more cases to follow
|
||||
|
||||
String roomName = payloadObject.get("meeting_id").toString().replace("\"", "");
|
||||
|
||||
if(eventName.equalsIgnoreCase("get_whiteboard_shapes_request")){
|
||||
String requesterID = payloadObject.get("requester_id").toString().replace("\"", "");
|
||||
if(payloadObject.get("whiteboard_id") != null){
|
||||
String whiteboardID = payloadObject.get("whiteboard_id").toString().replace("\"", "");
|
||||
System.out.println("\n FOUND A whiteboardID:" + whiteboardID + "\n");
|
||||
bbbInGW.requestWhiteboardAnnotationHistory(roomName, requesterID, whiteboardID, requesterID);
|
||||
}
|
||||
else {
|
||||
System.out.println("\n DID NOT FIND A whiteboardID \n");
|
||||
}
|
||||
System.out.println("\n\n\n user<" + requesterID + "> requested the shapes.\n\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@ public interface IBigBlueButtonInGW {
|
||||
void endAllMeetings();
|
||||
void createMeeting2(String meetingID, String meetingName, boolean recorded, String voiceBridge, long duration);
|
||||
void destroyMeeting(String meetingID);
|
||||
void getAllMeetings(String meetingID);
|
||||
void lockSettings(String meetingID, Boolean locked, Map<String, Boolean> lockSettigs);
|
||||
|
||||
|
||||
|
@ -16,6 +16,7 @@ class BigBlueButtonActor(outGW: MessageOutGateway) extends Actor {
|
||||
case msg: CreateMeeting => handleCreateMeeting(msg)
|
||||
case msg: DestroyMeeting => handleDestroyMeeting(msg)
|
||||
case msg: KeepAliveMessage => handleKeepAliveMessage(msg)
|
||||
case msg: GetAllMeetingsRequest => handleGetAllMeetingsRequest(msg)
|
||||
case msg: InMessage => handleMeetingMessage(msg)
|
||||
case _ => // do nothing
|
||||
}
|
||||
@ -96,5 +97,40 @@ class BigBlueButtonActor(outGW: MessageOutGateway) extends Actor {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private def handleGetAllMeetingsRequest(msg: GetAllMeetingsRequest) {
|
||||
var len = meetings.keys.size
|
||||
println("meetings.size=" + meetings.size)
|
||||
println("len_=" + len)
|
||||
|
||||
val set = meetings.keySet
|
||||
val arr : Array[String] = new Array[String](len)
|
||||
set.copyToArray(arr)
|
||||
val resultArray : Array[MeetingInfo] = new Array[MeetingInfo](len)
|
||||
|
||||
for(i <- 0 until arr.length) {
|
||||
val id = arr(i)
|
||||
val name = meetings.get(arr(i)).head.getMeetingName()
|
||||
val recorded = meetings.get(arr(i)).head.getRecordedStatus()
|
||||
|
||||
var info = new MeetingInfo(id, name, recorded)
|
||||
resultArray(i) = info
|
||||
|
||||
//remove later
|
||||
println("for a meeting:" + id)
|
||||
println("Meeting Name = " + meetings.get(id).head.getMeetingName())
|
||||
println("isRecorded = " + meetings.get(id).head.getRecordedStatus())
|
||||
|
||||
//send the users
|
||||
this ! (new GetUsers(id, "nodeJSapp"))
|
||||
|
||||
//send the presentation
|
||||
this ! (new GetPresentationInfo(id, "nodeJSapp", "nodeJSapp"))
|
||||
|
||||
//send chat history
|
||||
this ! (new GetChatHistoryRequest(id, "nodeJSapp", "nodeJSapp"))
|
||||
}
|
||||
|
||||
outGW.send(new GetAllMeetingsReply(resultArray))
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,12 @@ class BigBlueButtonInGW(bbbGW: BigBlueButtonGateway, presUtil: PreuploadedPresen
|
||||
// println("******************** DESTROY MEETING [" + meetingID + "] ***************************** ")
|
||||
bbbGW.accept(new DestroyMeeting(meetingID))
|
||||
}
|
||||
|
||||
|
||||
def getAllMeetings(meetingID: String) {
|
||||
println("******************** GET ALL MEETINGS ***************************** ")
|
||||
bbbGW.accept(new GetAllMeetingsRequest("meetingId"))
|
||||
}
|
||||
|
||||
def isAliveAudit(aliveId:String) {
|
||||
bbbGW.acceptKeepAlive(new KeepAliveMessage(aliveId));
|
||||
}
|
||||
@ -231,9 +236,10 @@ class BigBlueButtonInGW(bbbGW: BigBlueButtonGateway, presUtil: PreuploadedPresen
|
||||
val thumbnail = presBaseUrl + "/thumbnail/" + i
|
||||
val swfUri = presBaseUrl + "/slide/" + i
|
||||
val txtUri = presBaseUrl + "/textfiles/slide-" + i + ".txt"
|
||||
|
||||
val pngUri = presBaseUrl + "/png/" + i
|
||||
|
||||
val p = new Page(id=id, num=num, thumbUri=thumbnail, swfUri=swfUri,
|
||||
txtUri=txtUri, pngUri=thumbnail,
|
||||
txtUri=txtUri, pngUri=pngUri,
|
||||
current=current)
|
||||
pages += (p.id -> p)
|
||||
}
|
||||
|
@ -98,6 +98,7 @@ class CollectorActor(dispatcher: IDispatcher) extends Actor {
|
||||
case msg: UndoWhiteboardRequest => handleUndoWhiteboardRequest(msg)
|
||||
case msg: EnableWhiteboardRequest => handleEnableWhiteboardRequest(msg)
|
||||
case msg: IsWhiteboardEnabledRequest => handleIsWhiteboardEnabledRequest(msg)
|
||||
case msg: GetAllMeetingsRequest => handleGetAllMeetingsRequest(msg)
|
||||
|
||||
//OUT MESSAGES
|
||||
case msg: MeetingCreated => handleMeetingCreated(msg)
|
||||
@ -177,6 +178,7 @@ class CollectorActor(dispatcher: IDispatcher) extends Actor {
|
||||
case msg: UndoWhiteboardEvent => handleUndoWhiteboardEvent(msg)
|
||||
case msg: WhiteboardEnabledEvent => handleWhiteboardEnabledEvent(msg)
|
||||
case msg: IsWhiteboardEnabledReply => handleIsWhiteboardEnabledReply(msg)
|
||||
case msg: GetAllMeetingsReply => handleGetAllMeetingsReply(msg)
|
||||
|
||||
case _ => // do nothing
|
||||
}
|
||||
@ -1300,6 +1302,12 @@ class CollectorActor(dispatcher: IDispatcher) extends Actor {
|
||||
dispatcher.dispatch(buildJson(header, payload))
|
||||
}
|
||||
|
||||
private def handleGetAllMeetingsRequest(msg: GetAllMeetingsRequest) {
|
||||
println("***** DISPATCHING GET ALL MEETINGS REQUEST *****************")
|
||||
}
|
||||
|
||||
|
||||
|
||||
// OUT MESSAGES
|
||||
private def handleMeetingCreated(msg: MeetingCreated) {
|
||||
val json = MeetingMessageToJsonConverter.meetingCreatedToJson(msg)
|
||||
@ -2079,4 +2087,9 @@ class CollectorActor(dispatcher: IDispatcher) extends Actor {
|
||||
val json = WhiteboardMessageToJsonConverter.isWhiteboardEnabledReplyToJson(msg)
|
||||
dispatcher.dispatch(json)
|
||||
}
|
||||
private def handleGetAllMeetingsReply(msg: GetAllMeetingsReply) {
|
||||
val json = MeetingMessageToJsonConverter.getAllMeetingsReplyToJson(msg)
|
||||
println("***** DISPATCHING GET ALL MEETINGS REPLY OUTMSG *****************")
|
||||
dispatcher.dispatch(json)
|
||||
}
|
||||
}
|
||||
|
@ -27,11 +27,19 @@ class MeetingActor(val meetingID: String, meetingName: String, val recorded: Boo
|
||||
var recording = false;
|
||||
var muted = false;
|
||||
var meetingEnded = false
|
||||
|
||||
def getMeetingName():String = {
|
||||
meetingName
|
||||
}
|
||||
|
||||
def getRecordedStatus():Boolean = {
|
||||
recorded
|
||||
}
|
||||
|
||||
val TIMER_INTERVAL = 30000
|
||||
var hasLastWebUserLeft = false
|
||||
var lastWebUserLeftOn:Long = 0
|
||||
|
||||
|
||||
class TimerActor(val timeout: Long, val who: Actor, val reply: String) extends Actor {
|
||||
def act {
|
||||
reactWithin(timeout) {
|
||||
@ -41,83 +49,83 @@ class MeetingActor(val meetingID: String, meetingName: String, val recorded: Boo
|
||||
}
|
||||
|
||||
def act() = {
|
||||
loop {
|
||||
react {
|
||||
case "StartTimer" => handleStartTimer
|
||||
case "Hello" => handleHello
|
||||
case "MonitorNumberOfWebUsers" => handleMonitorNumberOfWebUsers()
|
||||
case msg: ValidateAuthToken => handleValidateAuthToken(msg)
|
||||
case msg: RegisterUser => handleRegisterUser(msg)
|
||||
case msg: VoiceUserJoined => handleVoiceUserJoined(msg)
|
||||
case msg: VoiceUserLeft => handleVoiceUserLeft(msg)
|
||||
case msg: VoiceUserMuted => handleVoiceUserMuted(msg)
|
||||
case msg: VoiceUserTalking => handleVoiceUserTalking(msg)
|
||||
case msg: UserJoining => handleUserJoin(msg)
|
||||
case msg: UserLeaving => handleUserLeft(msg)
|
||||
case msg: AssignPresenter => handleAssignPresenter(msg)
|
||||
case msg: GetUsers => handleGetUsers(msg)
|
||||
case msg: ChangeUserStatus => handleChangeUserStatus(msg)
|
||||
case msg: UserRaiseHand => handleUserRaiseHand(msg)
|
||||
case msg: UserLowerHand => handleUserLowerHand(msg)
|
||||
case msg: UserShareWebcam => handleUserShareWebcam(msg)
|
||||
case msg: UserUnshareWebcam => handleUserunshareWebcam(msg)
|
||||
case msg: MuteMeetingRequest => handleMuteMeetingRequest(msg)
|
||||
case msg: MuteAllExceptPresenterRequest => handleMuteAllExceptPresenterRequest(msg)
|
||||
case msg: IsMeetingMutedRequest => handleIsMeetingMutedRequest(msg)
|
||||
case msg: MuteUserRequest => handleMuteUserRequest(msg)
|
||||
case msg: EjectUserRequest => handleEjectUserRequest(msg)
|
||||
case msg: SetLockSettings => handleSetLockSettings(msg)
|
||||
case msg: InitLockSettings => handleInitLockSettings(msg)
|
||||
case msg: GetChatHistoryRequest => handleGetChatHistoryRequest(msg)
|
||||
case msg: SendPublicMessageRequest => handleSendPublicMessageRequest(msg)
|
||||
case msg: SendPrivateMessageRequest => handleSendPrivateMessageRequest(msg)
|
||||
case msg: UserConnectedToGlobalAudio => handleUserConnectedToGlobalAudio(msg)
|
||||
case msg: UserDisconnectedFromGlobalAudio => handleUserDisconnectedFromGlobalAudio(msg)
|
||||
case msg: GetCurrentLayoutRequest => handleGetCurrentLayoutRequest(msg)
|
||||
case msg: BroadcastLayoutRequest => handleBroadcastLayoutRequest(msg)
|
||||
case msg: InitializeMeeting => handleInitializeMeeting(msg)
|
||||
case msg: ClearPresentation => handleClearPresentation(msg)
|
||||
case msg: PresentationConversionUpdate => handlePresentationConversionUpdate(msg)
|
||||
case msg: PresentationPageCountError => handlePresentationPageCountError(msg)
|
||||
case msg: PresentationSlideGenerated => handlePresentationSlideGenerated(msg)
|
||||
case msg: PresentationConversionCompleted => handlePresentationConversionCompleted(msg)
|
||||
case msg: RemovePresentation => handleRemovePresentation(msg)
|
||||
case msg: GetPresentationInfo => handleGetPresentationInfo(msg)
|
||||
case msg: SendCursorUpdate => handleSendCursorUpdate(msg)
|
||||
case msg: ResizeAndMoveSlide => handleResizeAndMoveSlide(msg)
|
||||
case msg: GotoSlide => handleGotoSlide(msg)
|
||||
case msg: SharePresentation => handleSharePresentation(msg)
|
||||
case msg: GetSlideInfo => handleGetSlideInfo(msg)
|
||||
case msg: PreuploadedPresentations => handlePreuploadedPresentations(msg)
|
||||
case msg: PreCreatedPoll => handlePreCreatedPoll(msg)
|
||||
case msg: CreatePoll => handleCreatePoll(msg)
|
||||
case msg: UpdatePoll => handleUpdatePoll(msg)
|
||||
case msg: DestroyPoll => handleDestroyPoll(msg)
|
||||
case msg: RemovePoll => handleRemovePoll(msg)
|
||||
case msg: SharePoll => handleSharePoll(msg)
|
||||
case msg: StopPoll => handleStopPoll(msg)
|
||||
case msg: StartPoll => handleStartPoll(msg)
|
||||
case msg: ClearPoll => handleClearPoll(msg)
|
||||
case msg: GetPolls => handleGetPolls(msg)
|
||||
case msg: RespondToPoll => handleRespondToPoll(msg)
|
||||
case msg: HidePollResult => handleHidePollResult(msg)
|
||||
case msg: ShowPollResult => handleShowPollResult(msg)
|
||||
case msg: SendWhiteboardAnnotationRequest => handleSendWhiteboardAnnotationRequest(msg)
|
||||
case msg: GetWhiteboardShapesRequest => handleGetWhiteboardShapesRequest(msg)
|
||||
case msg: ClearWhiteboardRequest => handleClearWhiteboardRequest(msg)
|
||||
case msg: UndoWhiteboardRequest => handleUndoWhiteboardRequest(msg)
|
||||
case msg: EnableWhiteboardRequest => handleEnableWhiteboardRequest(msg)
|
||||
case msg: IsWhiteboardEnabledRequest => handleIsWhiteboardEnabledRequest(msg)
|
||||
case msg: SetRecordingStatus => handleSetRecordingStatus(msg)
|
||||
case msg: GetRecordingStatus => handleGetRecordingStatus(msg)
|
||||
case msg: VoiceRecording => handleVoiceRecording(msg)
|
||||
|
||||
case msg: EndMeeting => handleEndMeeting(msg)
|
||||
case StopMeetingActor => exit
|
||||
case _ => // do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
loop {
|
||||
react {
|
||||
case "StartTimer" => handleStartTimer
|
||||
case "Hello" => handleHello
|
||||
case "MonitorNumberOfWebUsers" => handleMonitorNumberOfWebUsers()
|
||||
case msg: ValidateAuthToken => handleValidateAuthToken(msg)
|
||||
case msg: RegisterUser => handleRegisterUser(msg)
|
||||
case msg: VoiceUserJoined => handleVoiceUserJoined(msg)
|
||||
case msg: VoiceUserLeft => handleVoiceUserLeft(msg)
|
||||
case msg: VoiceUserMuted => handleVoiceUserMuted(msg)
|
||||
case msg: VoiceUserTalking => handleVoiceUserTalking(msg)
|
||||
case msg: UserJoining => handleUserJoin(msg)
|
||||
case msg: UserLeaving => handleUserLeft(msg)
|
||||
case msg: AssignPresenter => handleAssignPresenter(msg)
|
||||
case msg: GetUsers => handleGetUsers(msg)
|
||||
case msg: ChangeUserStatus => handleChangeUserStatus(msg)
|
||||
case msg: UserRaiseHand => handleUserRaiseHand(msg)
|
||||
case msg: UserLowerHand => handleUserLowerHand(msg)
|
||||
case msg: UserShareWebcam => handleUserShareWebcam(msg)
|
||||
case msg: UserUnshareWebcam => handleUserunshareWebcam(msg)
|
||||
case msg: MuteMeetingRequest => handleMuteMeetingRequest(msg)
|
||||
case msg: MuteAllExceptPresenterRequest => handleMuteAllExceptPresenterRequest(msg)
|
||||
case msg: IsMeetingMutedRequest => handleIsMeetingMutedRequest(msg)
|
||||
case msg: MuteUserRequest => handleMuteUserRequest(msg)
|
||||
case msg: EjectUserRequest => handleEjectUserRequest(msg)
|
||||
case msg: SetLockSettings => handleSetLockSettings(msg)
|
||||
case msg: InitLockSettings => handleInitLockSettings(msg)
|
||||
case msg: GetChatHistoryRequest => handleGetChatHistoryRequest(msg)
|
||||
case msg: SendPublicMessageRequest => handleSendPublicMessageRequest(msg)
|
||||
case msg: SendPrivateMessageRequest => handleSendPrivateMessageRequest(msg)
|
||||
case msg: UserConnectedToGlobalAudio => handleUserConnectedToGlobalAudio(msg)
|
||||
case msg: UserDisconnectedFromGlobalAudio => handleUserDisconnectedFromGlobalAudio(msg)
|
||||
case msg: GetCurrentLayoutRequest => handleGetCurrentLayoutRequest(msg)
|
||||
case msg: BroadcastLayoutRequest => handleBroadcastLayoutRequest(msg)
|
||||
case msg: InitializeMeeting => handleInitializeMeeting(msg)
|
||||
case msg: ClearPresentation => handleClearPresentation(msg)
|
||||
case msg: PresentationConversionUpdate => handlePresentationConversionUpdate(msg)
|
||||
case msg: PresentationPageCountError => handlePresentationPageCountError(msg)
|
||||
case msg: PresentationSlideGenerated => handlePresentationSlideGenerated(msg)
|
||||
case msg: PresentationConversionCompleted => handlePresentationConversionCompleted(msg)
|
||||
case msg: RemovePresentation => handleRemovePresentation(msg)
|
||||
case msg: GetPresentationInfo => handleGetPresentationInfo(msg)
|
||||
case msg: SendCursorUpdate => handleSendCursorUpdate(msg)
|
||||
case msg: ResizeAndMoveSlide => handleResizeAndMoveSlide(msg)
|
||||
case msg: GotoSlide => handleGotoSlide(msg)
|
||||
case msg: SharePresentation => handleSharePresentation(msg)
|
||||
case msg: GetSlideInfo => handleGetSlideInfo(msg)
|
||||
case msg: PreuploadedPresentations => handlePreuploadedPresentations(msg)
|
||||
case msg: PreCreatedPoll => handlePreCreatedPoll(msg)
|
||||
case msg: CreatePoll => handleCreatePoll(msg)
|
||||
case msg: UpdatePoll => handleUpdatePoll(msg)
|
||||
case msg: DestroyPoll => handleDestroyPoll(msg)
|
||||
case msg: RemovePoll => handleRemovePoll(msg)
|
||||
case msg: SharePoll => handleSharePoll(msg)
|
||||
case msg: StopPoll => handleStopPoll(msg)
|
||||
case msg: StartPoll => handleStartPoll(msg)
|
||||
case msg: ClearPoll => handleClearPoll(msg)
|
||||
case msg: GetPolls => handleGetPolls(msg)
|
||||
case msg: RespondToPoll => handleRespondToPoll(msg)
|
||||
case msg: HidePollResult => handleHidePollResult(msg)
|
||||
case msg: ShowPollResult => handleShowPollResult(msg)
|
||||
case msg: SendWhiteboardAnnotationRequest => handleSendWhiteboardAnnotationRequest(msg)
|
||||
case msg: GetWhiteboardShapesRequest => handleGetWhiteboardShapesRequest(msg)
|
||||
case msg: ClearWhiteboardRequest => handleClearWhiteboardRequest(msg)
|
||||
case msg: UndoWhiteboardRequest => handleUndoWhiteboardRequest(msg)
|
||||
case msg: EnableWhiteboardRequest => handleEnableWhiteboardRequest(msg)
|
||||
case msg: IsWhiteboardEnabledRequest => handleIsWhiteboardEnabledRequest(msg)
|
||||
case msg: SetRecordingStatus => handleSetRecordingStatus(msg)
|
||||
case msg: GetRecordingStatus => handleGetRecordingStatus(msg)
|
||||
case msg: VoiceRecording => handleVoiceRecording(msg)
|
||||
|
||||
case msg: EndMeeting => handleEndMeeting(msg)
|
||||
case StopMeetingActor => exit
|
||||
case _ => // do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def hasMeetingEnded():Boolean = {
|
||||
meetingEnded
|
||||
@ -139,15 +147,15 @@ class MeetingActor(val meetingID: String, meetingName: String, val recorded: Boo
|
||||
def webUserJoined() {
|
||||
if (users.numWebUsers > 0) {
|
||||
lastWebUserLeftOn = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def startCheckingIfWeNeedToEndVoiceConf() {
|
||||
if (users.numWebUsers == 0) {
|
||||
lastWebUserLeftOn = timeNowInMinutes
|
||||
println("*************** MonitorNumberOfWebUsers started ******************")
|
||||
println("*************** MonitorNumberOfWebUsers started ******************")
|
||||
scheduleEndVoiceConference()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def handleMonitorNumberOfWebUsers() {
|
||||
|
@ -498,3 +498,7 @@ case class IsWhiteboardEnabledRequest(
|
||||
requesterID: String,
|
||||
replyTo: String
|
||||
) extends InMessage
|
||||
|
||||
case class GetAllMeetingsRequest(
|
||||
meetingID: String /** Not used. Just to satisfy trait **/
|
||||
) extends InMessage
|
||||
|
@ -78,6 +78,7 @@ object MessageNames {
|
||||
val UNDO_WHITEBOARD = "undo_whiteboard_request"
|
||||
val ENABLE_WHITEBOARD = "enable_whiteboard_request"
|
||||
val IS_WHITEBOARD_ENABLED = "is_whiteboard_enabled_request"
|
||||
val GET_ALL_MEETINGS_REQUEST = "get_all_meetings_request"
|
||||
|
||||
// OUT MESSAGES
|
||||
val MEETING_CREATED = "meeting_created_message"
|
||||
@ -158,5 +159,5 @@ object MessageNames {
|
||||
val MEETING_DESTROYED_EVENT = "meeting_destroyed_event"
|
||||
val KEEP_ALIVE_REPLY = "keep_alive_reply"
|
||||
val USER_LISTEN_ONLY = "user_listening_only"
|
||||
|
||||
val GET_ALL_MEETINGS_REPLY = "get_all_meetings_reply"
|
||||
}
|
@ -642,7 +642,11 @@ case class IsWhiteboardEnabledReply(
|
||||
replyTo: String,
|
||||
version:String = Versions.V_0_0_1
|
||||
) extends IOutMessage
|
||||
|
||||
|
||||
case class GetAllMeetingsReply(
|
||||
meetings: Array[MeetingInfo],
|
||||
version:String = Versions.V_0_0_1
|
||||
) extends IOutMessage
|
||||
|
||||
// Value Objects
|
||||
case class MeetingVO(
|
||||
|
@ -5,7 +5,7 @@ object Role extends Enumeration {
|
||||
val MODERATOR = Value("MODERATOR")
|
||||
val VIEWER = Value("VIEWER")
|
||||
}
|
||||
|
||||
|
||||
case class Presenter(
|
||||
presenterID: String,
|
||||
presenterName: String,
|
||||
@ -102,4 +102,6 @@ case class VoiceConfig(telVoice: String, webVoice: String, dialNumber: String)
|
||||
case class MeetingPasswords(moderatorPass: String, viewerPass: String)
|
||||
|
||||
case class MeetingDuration(duration: Int = 0, createdTime: Long = 0,
|
||||
startTime: Long = 0, endTime: Long = 0)
|
||||
startTime: Long = 0, endTime: Long = 0)
|
||||
|
||||
case class MeetingInfo(meetingID: String, meetingName: String, recorded: Boolean)
|
||||
|
@ -52,6 +52,7 @@ trait UsersApp {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def handleMuteAllExceptPresenterRequest(msg: MuteAllExceptPresenterRequest) {
|
||||
meetingMuted = msg.mute
|
||||
outGW.send(new MeetingMuted(meetingID, recorded, meetingMuted))
|
||||
@ -60,7 +61,7 @@ trait UsersApp {
|
||||
outGW.send(new MuteVoiceUser(meetingID, recorded, msg.requesterID, u.userID, msg.mute))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def handleMuteMeetingRequest(msg: MuteMeetingRequest) {
|
||||
meetingMuted = msg.mute
|
||||
outGW.send(new MeetingMuted(meetingID, recorded, meetingMuted))
|
||||
@ -72,9 +73,27 @@ trait UsersApp {
|
||||
def handleValidateAuthToken(msg: ValidateAuthToken) {
|
||||
// println("*************** Got ValidateAuthToken message ********************" )
|
||||
regUsers.get (msg.userId) match {
|
||||
case Some(u) => outGW.send(new ValidateAuthTokenReply(meetingID, msg.userId, msg.token, true, msg.correlationId))
|
||||
case Some(u) =>
|
||||
{
|
||||
val replyTo = meetingID + '/' + msg.userId
|
||||
|
||||
//send the reply
|
||||
outGW.send(new ValidateAuthTokenReply(meetingID, msg.userId, msg.token, true, msg.correlationId))
|
||||
|
||||
//send the list of users in the meeting
|
||||
outGW.send(new GetUsersReply(meetingID, msg.userId, users.getUsers))
|
||||
|
||||
//send chat history
|
||||
this ! (new GetChatHistoryRequest(meetingID, msg.userId, replyTo))
|
||||
|
||||
//join the user
|
||||
handleUserJoin(new UserJoining(meetingID, msg.userId))
|
||||
|
||||
//send the presentation
|
||||
this ! (new GetPresentationInfo(meetingID, msg.userId, replyTo))
|
||||
}
|
||||
case None => outGW.send(new ValidateAuthTokenReply(meetingID, msg.userId, msg.token, false, msg.correlationId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def handleRegisterUser(msg: RegisterUser) {
|
||||
@ -113,8 +132,23 @@ trait UsersApp {
|
||||
case None => // do nothing
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
def handleLockUser(msg: LockUser) {
|
||||
|
||||
}
|
||||
|
||||
def handleLockAllUsers(msg: LockAllUsers) {
|
||||
|
||||
}
|
||||
|
||||
def handleGetLockSettings(msg: GetLockSettings) {
|
||||
|
||||
}
|
||||
|
||||
def handleIsMeetingLocked(msg: IsMeetingLocked) {
|
||||
|
||||
}
|
||||
|
||||
def handleSetLockSettings(msg: SetLockSettings) {
|
||||
// println("*************** Received new lock settings ********************")
|
||||
if (!permissionsEqual(msg.settings)) {
|
||||
@ -194,7 +228,7 @@ trait UsersApp {
|
||||
outGW.send(new UserUnsharedWebcam(meetingID, recorded, uvo.userID, stream))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def handleChangeUserStatus(msg: ChangeUserStatus):Unit = {
|
||||
if (users.hasUser(msg.userID)) {
|
||||
outGW.send(new UserStatusChange(meetingID, recorded, msg.userID, msg.status, msg.value))
|
||||
|
@ -27,21 +27,21 @@ trait WhiteboardApp {
|
||||
wbModel.addAnnotation(wbId, shape)
|
||||
} else if ((WhiteboardKeyUtil.PENCIL_TYPE == shapeType)
|
||||
&& (WhiteboardKeyUtil.DRAW_START_STATUS == status)) {
|
||||
// println("Received pencil draw start status")
|
||||
wbModel.addAnnotation(wbId, shape)
|
||||
println("Received pencil draw start status")
|
||||
wbModel.addAnnotation(wbId, shape)
|
||||
} else if ((WhiteboardKeyUtil.DRAW_END_STATUS == status)
|
||||
&& ((WhiteboardKeyUtil.RECTANGLE_TYPE == shapeType)
|
||||
|| (WhiteboardKeyUtil.ELLIPSE_TYPE == shapeType)
|
||||
|| (WhiteboardKeyUtil.TRIANGLE_TYPE == shapeType)
|
||||
|| (WhiteboardKeyUtil.LINE_TYPE == shapeType))) {
|
||||
// println("Received [" + shapeType +"] draw end status")
|
||||
wbModel.addAnnotation(wbId, shape)
|
||||
|| (WhiteboardKeyUtil.TRIANGLE_TYPE == shapeType)
|
||||
|| (WhiteboardKeyUtil.LINE_TYPE == shapeType))) {
|
||||
println("Received [" + shapeType +"] draw end status")
|
||||
wbModel.addAnnotation(wbId, shape)
|
||||
} else if (WhiteboardKeyUtil.TEXT_TYPE == shapeType) {
|
||||
// println("Received [" + shapeType +"] modify text status")
|
||||
wbModel.modifyText(wbId, shape)
|
||||
} else {
|
||||
// println("Received UNKNOWN whiteboard shape!!!!. status=[" + status + "], shapeType=[" + shapeType + "]")
|
||||
}
|
||||
println("Received [" + shapeType +"] modify text status")
|
||||
wbModel.modifyText(wbId, shape)
|
||||
} else {
|
||||
println("Received UNKNOWN whiteboard shape!!!!. status=[" + status + "], shapeType=[" + shapeType + "]")
|
||||
}
|
||||
|
||||
wbModel.getWhiteboard(wbId) foreach {wb =>
|
||||
// println("WhiteboardApp::handleSendWhiteboardAnnotationRequest - num shapes [" + wb.shapes.length + "]")
|
||||
@ -52,20 +52,20 @@ trait WhiteboardApp {
|
||||
}
|
||||
|
||||
def handleGetWhiteboardShapesRequest(msg: GetWhiteboardShapesRequest) {
|
||||
// println("WB: Received page history [" + msg.whiteboardId + "]")
|
||||
//println("WB: Received page history [" + msg.whiteboardId + "]")
|
||||
wbModel.history(msg.whiteboardId) foreach {wb =>
|
||||
outGW.send(new GetWhiteboardShapesReply(meetingID, recorded,
|
||||
msg.requesterID, wb.id, wb.shapes.toArray, msg.replyTo))
|
||||
msg.requesterID, wb.id, wb.shapes.toArray, msg.replyTo))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def handleClearWhiteboardRequest(msg: ClearWhiteboardRequest) {
|
||||
// println("WB: Received clear whiteboard")
|
||||
//println("WB: Received clear whiteboard")
|
||||
wbModel.clearWhiteboard(msg.whiteboardId)
|
||||
wbModel.getWhiteboard(msg.whiteboardId) foreach {wb =>
|
||||
outGW.send(new ClearWhiteboardEvent(meetingID, recorded,
|
||||
msg.requesterID, wb.id))
|
||||
}
|
||||
msg.requesterID, wb.id))
|
||||
}
|
||||
}
|
||||
|
||||
def handleUndoWhiteboardRequest(msg: UndoWhiteboardRequest) {
|
||||
|
@ -23,7 +23,8 @@ class MeetingEventRedisPublisher(service: MessageSender) extends OutMessageListe
|
||||
case msg: MeetingDestroyed => handleMeetingDestroyed(msg)
|
||||
case msg: KeepAliveMessageReply => handleKeepAliveMessageReply(msg)
|
||||
case msg: StartRecording => handleStartRecording(msg)
|
||||
case msg: StopRecording => handleStopRecording(msg)
|
||||
case msg: StopRecording => handleStopRecording(msg)
|
||||
case msg: GetAllMeetingsReply => handleGetAllMeetingsReply(msg)
|
||||
case _ => //println("Unhandled message in MeetingEventRedisPublisher")
|
||||
}
|
||||
}
|
||||
@ -84,5 +85,10 @@ class MeetingEventRedisPublisher(service: MessageSender) extends OutMessageListe
|
||||
private def handleMeetingHasEnded(msg: MeetingHasEnded) {
|
||||
val json = MeetingMessageToJsonConverter.meetingHasEndedToJson(msg)
|
||||
service.send(MessagingConstants.FROM_MEETING_CHANNEL, json)
|
||||
}
|
||||
}
|
||||
|
||||
private def handleGetAllMeetingsReply(msg: GetAllMeetingsReply) {
|
||||
val json = MeetingMessageToJsonConverter.getAllMeetingsReplyToJson(msg)
|
||||
service.send(MessagingConstants.FROM_MEETING_CHANNEL, json)
|
||||
}
|
||||
}
|
@ -103,7 +103,7 @@ object MeetingMessageToJsonConverter {
|
||||
Util.buildJson(header, payload)
|
||||
}
|
||||
|
||||
def stopRecordingToJson(msg: StopRecording):String = {
|
||||
def stopRecordingToJson(msg: StopRecording):String = {
|
||||
val payload = new java.util.HashMap[String, Any]()
|
||||
payload.put(Constants.MEETING_ID, msg.meetingID)
|
||||
payload.put(Constants.RECORDED, msg.recorded)
|
||||
@ -112,5 +112,12 @@ object MeetingMessageToJsonConverter {
|
||||
val header = Util.buildHeader(MessageNames.STOP_RECORDING, msg.version, None)
|
||||
Util.buildJson(header, payload)
|
||||
}
|
||||
|
||||
|
||||
def getAllMeetingsReplyToJson(msg: GetAllMeetingsReply):String = {
|
||||
val payload = new java.util.HashMap[String, Any]()
|
||||
payload.put("meetings", msg.meetings)
|
||||
|
||||
val header = Util.buildHeader(MessageNames.GET_ALL_MEETINGS_REPLY, msg.version, None)
|
||||
Util.buildJson(header, payload)
|
||||
}
|
||||
}
|
@ -39,5 +39,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
<bean id="chat.service" class="org.bigbluebutton.conference.service.chat.ChatService">
|
||||
<property name="chatApplication"> <ref local="chatApplication"/></property>
|
||||
</bean>
|
||||
|
||||
|
||||
<bean id="chatMessageListener" class="org.bigbluebutton.conference.service.chat.ChatMessageListener">
|
||||
<property name="bigBlueButtonInGW" ref="bbbInGW" />
|
||||
</bean>
|
||||
</beans>
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||
|
||||
Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
|
||||
Copyright (c) 2014 BigBlueButton Inc. and by respective authors (see below).
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU Lesser General Public License as published by the Free Software
|
||||
@ -45,12 +45,4 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
<bean id="presentationMessageListener" class="org.bigbluebutton.conference.service.presentation.PresentationMessageListener">
|
||||
<property name="conversionUpdatesProcessor" ref="conversionUpdatesProcessor" />
|
||||
</bean>
|
||||
|
||||
<bean id="chatMessageListener" class="org.bigbluebutton.conference.service.chat.ChatMessageListener">
|
||||
<property name="bigBlueButtonInGW" ref="bbbInGW" />
|
||||
</bean>
|
||||
|
||||
<bean id="participantsListener" class="org.bigbluebutton.conference.service.participants.ParticipantsListener">
|
||||
<property name="bigBlueButtonInGW" ref="bbbInGW" />
|
||||
</bean>
|
||||
</beans>
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||
|
||||
Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
|
||||
Copyright (c) 2014 BigBlueButton Inc. and by respective authors (see below).
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU Lesser General Public License as published by the Free Software
|
||||
@ -26,7 +26,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
http://www.springframework.org/schema/util
|
||||
http://www.springframework.org/schema/util/spring-util-2.0.xsd
|
||||
">
|
||||
|
||||
|
||||
<bean id="participantsHandler" class="org.bigbluebutton.conference.service.participants.ParticipantsHandler">
|
||||
<property name="participantsApplication"> <ref local="participantsApplication"/></property>
|
||||
</bean>
|
||||
@ -38,5 +38,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
<bean id="participants.service" class="org.bigbluebutton.conference.service.participants.ParticipantsService">
|
||||
<property name="participantsApplication"> <ref local="participantsApplication"/></property>
|
||||
</bean>
|
||||
|
||||
|
||||
<bean id="participantsListener" class="org.bigbluebutton.conference.service.participants.ParticipantsListener">
|
||||
<property name="bigBlueButtonInGW" ref="bbbInGW" />
|
||||
</bean>
|
||||
</beans>
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||
|
||||
Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
|
||||
Copyright (c) 2014 BigBlueButton Inc. and by respective authors (see below).
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU Lesser General Public License as published by the Free Software
|
||||
@ -26,13 +26,17 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
http://www.springframework.org/schema/util
|
||||
http://www.springframework.org/schema/util/spring-util-2.0.xsd
|
||||
">
|
||||
|
||||
|
||||
<bean id="whiteboardApplication" class="org.bigbluebutton.conference.service.whiteboard.WhiteboardApplication">
|
||||
<property name="bigBlueButtonInGW"> <ref bean="bbbInGW"/></property>
|
||||
<property name="bigBlueButtonInGW"> <ref bean="bbbInGW"/></property>
|
||||
</bean>
|
||||
|
||||
|
||||
<bean id="whiteboard.service" class="org.bigbluebutton.conference.service.whiteboard.WhiteboardService">
|
||||
<property name="whiteboardApplication"> <ref local="whiteboardApplication"/></property>
|
||||
</bean>
|
||||
|
||||
|
||||
<bean id="whiteboardListener" class="org.bigbluebutton.conference.service.whiteboard.WhiteboardListener">
|
||||
<property name="bigBlueButtonInGW"> <ref bean="bbbInGW"/></property>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||
|
||||
Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
|
||||
Copyright (c) 2014 BigBlueButton Inc. and by respective authors (see below).
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU Lesser General Public License as published by the Free Software
|
||||
@ -26,35 +26,36 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
http://www.springframework.org/schema/util
|
||||
http://www.springframework.org/schema/util/spring-util-2.0.xsd
|
||||
">
|
||||
|
||||
|
||||
|
||||
<bean id="redisMessageSender" class="org.bigbluebutton.conference.service.messaging.redis.MessageSender"
|
||||
init-method="start" destroy-method="stop">
|
||||
<property name="redisPool"> <ref bean="redisPool"/></property>
|
||||
</bean>
|
||||
<property name="redisPool"> <ref bean="redisPool"/></property>
|
||||
</bean>
|
||||
|
||||
<bean id="redisMessageReceiver" class="org.bigbluebutton.conference.service.messaging.redis.MessageReceiver"
|
||||
init-method="start" destroy-method="stop">
|
||||
<property name="redisPool"> <ref bean="redisPool"/></property>
|
||||
<property name="messageHandler"> <ref local="redisMessageHandler"/> </property>
|
||||
</bean>
|
||||
<property name="redisPool"> <ref bean="redisPool"/></property>
|
||||
<property name="messageHandler"> <ref local="redisMessageHandler"/> </property>
|
||||
</bean>
|
||||
|
||||
<bean id="redisMessageHandler" class="org.bigbluebutton.conference.service.messaging.redis.ReceivedMessageHandler"
|
||||
init-method="start" destroy-method="stop">
|
||||
<property name="messageDistributor"><ref bean="redisMessageDistributor" /></property>
|
||||
</bean>
|
||||
|
||||
<bean id="redisMessageDistributor" class="org.bigbluebutton.conference.service.messaging.redis.MessageDistributor">
|
||||
<property name="messageHandler"> <ref local="redisMessageHandler"/> </property>
|
||||
<property name="messageListeners">
|
||||
<set>
|
||||
<ref bean="presentationMessageListener" />
|
||||
<ref bean="chatMessageListener" />
|
||||
<ref bean="meetingMessageHandler" />
|
||||
<ref bean="pollMessageHandler" />
|
||||
<ref bean="participantsListener" />
|
||||
</set>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
|
||||
<bean id="redisMessageDistributor" class="org.bigbluebutton.conference.service.messaging.redis.MessageDistributor">
|
||||
<property name="messageHandler"> <ref local="redisMessageHandler"/> </property>
|
||||
<property name="messageListeners">
|
||||
<set>
|
||||
<ref bean="presentationMessageListener" />
|
||||
<ref bean="chatMessageListener" />
|
||||
<ref bean="meetingMessageHandler" />
|
||||
<ref bean="pollMessageHandler" />
|
||||
<ref bean="participantsListener" />
|
||||
<ref bean="voiceMessageListener" />
|
||||
<ref bean="whiteboardListener" />
|
||||
</set>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||
|
||||
Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
|
||||
Copyright (c) 2014 BigBlueButton Inc. and by respective authors (see below).
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU Lesser General Public License as published by the Free Software
|
||||
@ -26,13 +26,17 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
<beans:bean id="voiceHandler" class="org.bigbluebutton.conference.service.voice.VoiceHandler">
|
||||
</beans:bean>
|
||||
|
||||
|
||||
<beans:bean id="voiceEventRecorder" class="org.bigbluebutton.webconference.voice.VoiceEventRecorder">
|
||||
<beans:property name="recorderApplication" ref="recorderApplication"/>
|
||||
</beans:bean>
|
||||
|
||||
|
||||
<beans:bean id="voice.service" class="org.bigbluebutton.conference.service.voice.VoiceService">
|
||||
<beans:property name="bigBlueButtonInGW" ref="bbbInGW"/>
|
||||
</beans:bean>
|
||||
|
||||
|
||||
<beans:bean id="voiceMessageListener" class="org.bigbluebutton.conference.service.voice.VoiceMessageListener">
|
||||
<beans:property name="bigBlueButtonInGW" ref="bbbInGW" />
|
||||
</beans:bean>
|
||||
|
||||
</beans:beans>
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||
|
||||
Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
|
||||
Copyright (c) 2014 BigBlueButton Inc. and by respective authors (see below).
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU Lesser General Public License as published by the Free Software
|
||||
|
@ -24,6 +24,14 @@ class UrlMappings {
|
||||
"/presentation/$conference/$room/$presentation_name/thumbnail/$id"(controller:"presentation") {
|
||||
action = [GET:'showThumbnail']
|
||||
}
|
||||
|
||||
"/presentation/$conference/$room/$presentation_name/pngs"(controller:"presentation") {
|
||||
action = [GET:'numberOfPngs']
|
||||
}
|
||||
|
||||
"/presentation/$conference/$room/$presentation_name/png/$id"(controller:"presentation") {
|
||||
action = [GET:'showPngImage']
|
||||
}
|
||||
|
||||
"/presentation/$conference/$room/$presentation_name/textfiles"(controller:"presentation") {
|
||||
action = [GET:'numberOfTextfiles']
|
||||
|
@ -297,7 +297,28 @@ class PresentationController {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def numberOfPngs = {
|
||||
def filename = params.presentation_name
|
||||
def f = confInfo()
|
||||
def numPngs = presentationService.numberOfPngs(f.conference, f.room, filename)
|
||||
withFormat {
|
||||
xml {
|
||||
render(contentType:"text/xml") {
|
||||
conference(id:f.conference, room:f.room) {
|
||||
presentation(name:filename) {
|
||||
pngs(count:numPngs) {
|
||||
for (def i=0;i<numPngs;i++) {
|
||||
png(name:"pngs/${i}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def numberOfTextfiles = {
|
||||
def filename = params.presentation_name
|
||||
def f = confInfo()
|
||||
|
@ -1,45 +1,45 @@
|
||||
/**
|
||||
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||
*
|
||||
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free Software
|
||||
* Foundation; either version 3.0 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License along
|
||||
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||
*
|
||||
* Copyright (c) 2014 BigBlueButton Inc. and by respective authors (see below).
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free Software
|
||||
* Foundation; either version 3.0 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License along
|
||||
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package org.bigbluebutton.web.services
|
||||
|
||||
import java.util.concurrent.*;
|
||||
import java.lang.InterruptedException
|
||||
import org.bigbluebutton.presentation.DocumentConversionService
|
||||
import org.bigbluebutton.presentation.UploadedPresentation
|
||||
import java.lang.InterruptedException
|
||||
import org.bigbluebutton.presentation.DocumentConversionService
|
||||
import org.bigbluebutton.presentation.UploadedPresentation
|
||||
|
||||
class PresentationService {
|
||||
|
||||
static transactional = false
|
||||
DocumentConversionService documentConversionService
|
||||
def presentationDir
|
||||
def testConferenceMock
|
||||
def testRoomMock
|
||||
def testPresentationName
|
||||
def testUploadedPresentation
|
||||
def defaultUploadedPresentation
|
||||
def presentationBaseUrl
|
||||
|
||||
static transactional = false
|
||||
DocumentConversionService documentConversionService
|
||||
def presentationDir
|
||||
def testConferenceMock
|
||||
def testRoomMock
|
||||
def testPresentationName
|
||||
def testUploadedPresentation
|
||||
def defaultUploadedPresentation
|
||||
def presentationBaseUrl
|
||||
|
||||
def deletePresentation = {conf, room, filename ->
|
||||
def directory = new File(roomDirectory(conf, room).absolutePath + File.separatorChar + filename)
|
||||
deleteDirectory(directory)
|
||||
deleteDirectory(directory)
|
||||
}
|
||||
|
||||
|
||||
def deleteDirectory = {directory ->
|
||||
log.debug "delete = ${directory}"
|
||||
/**
|
||||
@ -47,7 +47,7 @@ class PresentationService {
|
||||
* We need to delete files inside a directory before a
|
||||
* directory can be deleted.
|
||||
**/
|
||||
File[] files = directory.listFiles();
|
||||
File[] files = directory.listFiles();
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
if (files[i].isDirectory()) {
|
||||
deleteDirectory(files[i])
|
||||
@ -56,9 +56,9 @@ class PresentationService {
|
||||
}
|
||||
}
|
||||
// Now that the directory is empty. Delete it.
|
||||
directory.delete()
|
||||
directory.delete()
|
||||
}
|
||||
|
||||
|
||||
def listPresentations = {conf, room ->
|
||||
def presentationsList = []
|
||||
def directory = roomDirectory(conf, room)
|
||||
@ -69,90 +69,94 @@ class PresentationService {
|
||||
if( file.isDirectory() )
|
||||
presentationsList.add( file.name )
|
||||
}
|
||||
}
|
||||
}
|
||||
return presentationsList
|
||||
}
|
||||
|
||||
def getPresentationDir = {
|
||||
return presentationDir
|
||||
|
||||
def getPresentationDir = {
|
||||
return presentationDir
|
||||
}
|
||||
|
||||
def processUploadedPresentation = {uploadedPres ->
|
||||
|
||||
def processUploadedPresentation = {uploadedPres ->
|
||||
// Run conversion on another thread.
|
||||
new Timer().runAfter(1000)
|
||||
new Timer().runAfter(1000)
|
||||
{
|
||||
documentConversionService.processDocument(uploadedPres)
|
||||
documentConversionService.processDocument(uploadedPres)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def showSlide(String conf, String room, String presentationName, String id) {
|
||||
new File(roomDirectory(conf, room).absolutePath + File.separatorChar + presentationName + File.separatorChar + "slide-${id}.swf")
|
||||
}
|
||||
|
||||
def showPngImage(String conf, String room, String presentationName, String id) {
|
||||
new File(roomDirectory(conf, room).absolutePath + File.separatorChar + presentationName + File.separatorChar + "pngs" + File.separatorChar + id)
|
||||
}
|
||||
|
||||
|
||||
def showPngImage(String conf, String room, String presentationName, String id) {
|
||||
new File(roomDirectory(conf, room).absolutePath + File.separatorChar + presentationName + File.separatorChar + "pngs" + File.separatorChar + "slide${id}.png")
|
||||
}
|
||||
|
||||
def showPresentation = {conf, room, filename ->
|
||||
new File(roomDirectory(conf, room).absolutePath + File.separatorChar + filename + File.separatorChar + "slides.swf")
|
||||
}
|
||||
|
||||
def showThumbnail = {conf, room, presentationName, thumb ->
|
||||
|
||||
def showThumbnail = {conf, room, presentationName, thumb ->
|
||||
println "Show thumbnails request for $presentationName $thumb"
|
||||
def thumbFile = roomDirectory(conf, room).absolutePath + File.separatorChar + presentationName + File.separatorChar +
|
||||
"thumbnails" + File.separatorChar + "thumb-${thumb}.png"
|
||||
log.debug "showing $thumbFile"
|
||||
|
||||
|
||||
new File(thumbFile)
|
||||
}
|
||||
|
||||
def showTextfile = {conf, room, presentationName, textfile ->
|
||||
println "Show textfiles request for $presentationName $textfile"
|
||||
def txt = roomDirectory(conf, room).absolutePath + File.separatorChar + presentationName + File.separatorChar +
|
||||
"textfiles" + File.separatorChar + "slide-${textfile}.txt"
|
||||
log.debug "showing $txt"
|
||||
|
||||
new File(txt)
|
||||
}
|
||||
|
||||
|
||||
def showTextfile = {conf, room, presentationName, textfile ->
|
||||
println "Show textfiles request for $presentationName $textfile"
|
||||
def txt = roomDirectory(conf, room).absolutePath + File.separatorChar + presentationName + File.separatorChar +
|
||||
"textfiles" + File.separatorChar + "slide-${textfile}.txt"
|
||||
log.debug "showing $txt"
|
||||
|
||||
new File(txt)
|
||||
}
|
||||
|
||||
def numberOfThumbnails = {conf, room, name ->
|
||||
def thumbDir = new File(roomDirectory(conf, room).absolutePath + File.separatorChar + name + File.separatorChar + "thumbnails")
|
||||
thumbDir.listFiles().length
|
||||
}
|
||||
|
||||
def numberOfTextfiles = {conf, room, name ->
|
||||
log.debug roomDirectory(conf, room).absolutePath + File.separatorChar + name + File.separatorChar + "textfiles"
|
||||
def textfilesDir = new File(roomDirectory(conf, room).absolutePath + File.separatorChar + name + File.separatorChar + "textfiles")
|
||||
textfilesDir.listFiles().length
|
||||
}
|
||||
|
||||
def roomDirectory = {conf, room ->
|
||||
return new File(presentationDir + File.separatorChar + conf + File.separatorChar + room)
|
||||
}
|
||||
|
||||
def testConversionProcess() {
|
||||
File presDir = new File(roomDirectory(testConferenceMock, testRoomMock).absolutePath + File.separatorChar + testPresentationName)
|
||||
|
||||
if (presDir.exists()) {
|
||||
File pres = new File(presDir.getAbsolutePath() + File.separatorChar + testUploadedPresentation)
|
||||
if (pres.exists()) {
|
||||
UploadedPresentation uploadedPres = new UploadedPresentation(testConferenceMock, testRoomMock, testPresentationName);
|
||||
uploadedPres.setUploadedFile(pres);
|
||||
// Run conversion on another thread.
|
||||
new Timer().runAfter(1000)
|
||||
{
|
||||
documentConversionService.processDocument(uploadedPres)
|
||||
}
|
||||
} else {
|
||||
log.error "${pres.absolutePath} does NOT exist"
|
||||
}
|
||||
} else {
|
||||
log.error "${presDir.absolutePath} does NOT exist."
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def numberOfPngs = {conf, room, name ->
|
||||
def PngsDir = new File(roomDirectory(conf, room).absolutePath + File.separatorChar + name + File.separatorChar + "pngs")
|
||||
PngsDir.listFiles().length
|
||||
}
|
||||
|
||||
def numberOfTextfiles = {conf, room, name ->
|
||||
log.debug roomDirectory(conf, room).absolutePath + File.separatorChar + name + File.separatorChar + "textfiles"
|
||||
def textfilesDir = new File(roomDirectory(conf, room).absolutePath + File.separatorChar + name + File.separatorChar + "textfiles")
|
||||
textfilesDir.listFiles().length
|
||||
}
|
||||
|
||||
def roomDirectory = {conf, room ->
|
||||
return new File(presentationDir + File.separatorChar + conf + File.separatorChar + room)
|
||||
}
|
||||
|
||||
def testConversionProcess() {
|
||||
File presDir = new File(roomDirectory(testConferenceMock, testRoomMock).absolutePath + File.separatorChar + testPresentationName)
|
||||
|
||||
if (presDir.exists()) {
|
||||
File pres = new File(presDir.getAbsolutePath() + File.separatorChar + testUploadedPresentation)
|
||||
if (pres.exists()) {
|
||||
UploadedPresentation uploadedPres = new UploadedPresentation(testConferenceMock, testRoomMock, testPresentationName);
|
||||
uploadedPres.setUploadedFile(pres);
|
||||
// Run conversion on another thread.
|
||||
new Timer().runAfter(1000)
|
||||
{
|
||||
documentConversionService.processDocument(uploadedPres)
|
||||
}
|
||||
} else {
|
||||
log.error "${pres.absolutePath} does NOT exist"
|
||||
}
|
||||
} else {
|
||||
log.error "${presDir.absolutePath} does NOT exist."
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/*** Helper classes **/
|
||||
import java.io.FilenameFilter;
|
||||
@ -161,4 +165,4 @@ class PngFilter implements FilenameFilter {
|
||||
public boolean accept(File dir, String name) {
|
||||
return (name.endsWith(".png"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ public class PdfToSwfSlidesGenerationService {
|
||||
log.info("Determined number of pages. MeetingId=[" + pres.getMeetingId() + "], presId=[" + pres.getId() + "], name=[" + pres.getName() + "], numPages=[" + pres.getNumberOfPages() + "]");
|
||||
if (pres.getNumberOfPages() > 0) {
|
||||
convertPdfToSwf(pres);
|
||||
// createPngImages(pres);
|
||||
createPngImages(pres);
|
||||
createTextFiles(pres);
|
||||
createThumbnails(pres);
|
||||
notifier.sendConversionCompletedMessage(pres);
|
||||
|
@ -66,7 +66,8 @@ public class PngImageCreatorImp implements PngImageCreator {
|
||||
for(int i=1; i<=pres.getNumberOfPages(); i++){
|
||||
File tmp = new File(imagePresentationDir.getAbsolutePath() + File.separatorChar + "tmp" + File.separatorChar + "slide" + i + ".pdf");
|
||||
File destpng = new File(imagePresentationDir.getAbsolutePath() + File.separatorChar + "slide" + i + ".png");
|
||||
COMMAND = IMAGEMAGICK_DIR + "/convert -density 300x300 -quality 90 +dither -depth 8 -colors 256 " + File.separatorChar + tmp.getAbsolutePath() + " " + destpng.getAbsolutePath();
|
||||
COMMAND = IMAGEMAGICK_DIR + "/convert -density 300x300 -quality 90 +dither -depth 8 -colors 256 " + File.separatorChar + tmp.getAbsolutePath() + " " + destpng.getAbsolutePath();
|
||||
|
||||
done = new ExternalProcessExecutor().exec(COMMAND, 60000);
|
||||
if(!done){
|
||||
break;
|
||||
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||
*
|
||||
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
|
||||
*
|
||||
* Copyright (c) 2014 BigBlueButton Inc. and by respective authors (see below).
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free Software
|
||||
@ -69,7 +69,6 @@ public class ThumbnailCreatorImp implements ThumbnailCreator {
|
||||
String source = pres.getUploadedFile().getAbsolutePath();
|
||||
String dest;
|
||||
String COMMAND = "";
|
||||
|
||||
if(SupportedFileTypes.isImageFile(pres.getFileType())){
|
||||
dest = thumbsDir.getAbsolutePath() + File.separator + TEMP_THUMB_NAME + ".png";
|
||||
COMMAND = IMAGEMAGICK_DIR + "/convert -thumbnail 150x150 " + source + " " + dest;
|
||||
@ -77,22 +76,22 @@ public class ThumbnailCreatorImp implements ThumbnailCreator {
|
||||
dest = thumbsDir.getAbsolutePath() + File.separator + "thumb-";
|
||||
COMMAND = IMAGEMAGICK_DIR + "/gs -q -sDEVICE=pngalpha -dBATCH -dNOPAUSE -dNOPROMPT -dDOINTERPOLATE -dPDFFitPage -r16 -sOutputFile=" + dest +"%d.png " + source;
|
||||
}
|
||||
|
||||
|
||||
boolean done = new ExternalProcessExecutor().exec(COMMAND, 60000);
|
||||
|
||||
|
||||
if (done) {
|
||||
return true;
|
||||
} else {
|
||||
log.warn("Failed to create thumbnails: " + COMMAND);
|
||||
} else {
|
||||
log.warn("Failed to create thumbnails: " + COMMAND);
|
||||
}
|
||||
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private File determineThumbnailDirectory(File presentationFile) {
|
||||
return new File(presentationFile.getParent() + File.separatorChar + "thumbnails");
|
||||
}
|
||||
|
||||
|
||||
private void renameThumbnails(File dir) {
|
||||
/*
|
||||
* If more than 1 file, filename like 'temp-thumb-X.png' else filename is 'temp-thumb.png'
|
||||
@ -137,17 +136,17 @@ public class ThumbnailCreatorImp implements ThumbnailCreator {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void copyBlankThumbnail(File thumb) {
|
||||
try {
|
||||
FileUtils.copyFile(new File(BLANK_THUMBNAIL), thumb);
|
||||
} catch (IOException e) {
|
||||
log.error("IOException while copying blank thumbnail.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanDirectory(File directory) {
|
||||
File[] files = directory.listFiles();
|
||||
|
||||
private void cleanDirectory(File directory) {
|
||||
File[] files = directory.listFiles();
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
files[i].delete();
|
||||
}
|
||||
|
2
client/bbb-html5-client/assets/css/navbar.less
Normal file → Executable file
2
client/bbb-html5-client/assets/css/navbar.less
Normal file → Executable file
@ -69,7 +69,7 @@
|
||||
}
|
||||
|
||||
.navbar-btn-group-right {
|
||||
dispaly:block;
|
||||
display:block;
|
||||
float:right;
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ config.redis.channels.toBBBApps.pattern = "bigbluebutton:to-bbb-apps:*"
|
||||
config.redis.channels.toBBBApps.chat = "bigbluebutton:to-bbb-apps:chat"
|
||||
config.redis.channels.toBBBApps.meeting = "bigbluebutton:to-bbb-apps:meeting"
|
||||
config.redis.channels.toBBBApps.users = "bigbluebutton:to-bbb-apps:users"
|
||||
config.redis.channels.toBBBApps.whiteboard = "bigbluebutton:to-bbb-apps:whiteboard"
|
||||
config.redis.internalChannels = {}
|
||||
config.redis.internalChannels.receive = "html5-receive"
|
||||
config.redis.internalChannels.reply = "html5-reply"
|
||||
|
@ -20,7 +20,7 @@ module.exports = class ClientProxy
|
||||
@io.sockets.on 'connection', (socket) =>
|
||||
log.debug({ client: socket.id }, "Client has connected.")
|
||||
socket.on 'message', (jsonMsg) =>
|
||||
log.debug({ message: jsonMsg }, "Received message") # TODO to check whether only 'message' works or 'djhkwa' too
|
||||
log.debug({ message: jsonMsg }, "Received message")
|
||||
@_handleMessage(socket, jsonMsg)
|
||||
socket.on 'disconnect', =>
|
||||
@_handleClientDisconnected socket
|
||||
@ -49,8 +49,10 @@ module.exports = class ClientProxy
|
||||
callback?()
|
||||
|
||||
_handleClientDisconnected: (socket) ->
|
||||
if socket.userId?
|
||||
log.info("User [#{socket.userId}] has disconnected.")
|
||||
console.log "\ntrying to disconnect"
|
||||
|
||||
#if socket.userId?
|
||||
# log.info("User [#{socket.userId}] has disconnected.")
|
||||
|
||||
_handleMessage: (socket, message) ->
|
||||
if message.header?.name?
|
||||
@ -64,6 +66,8 @@ module.exports = class ClientProxy
|
||||
@_handleLoginMessage socket, message
|
||||
when 'send_public_chat_message'
|
||||
@controller.sendingChat message
|
||||
when 'user_leaving_request'
|
||||
@controller.sendingUsersMessage message
|
||||
else
|
||||
log.error({ message: message }, 'Unknown message name.')
|
||||
|
||||
|
@ -35,4 +35,8 @@ module.exports = class Controller
|
||||
# @clientProxy.endMeeting()
|
||||
|
||||
sendingChat: (data) =>
|
||||
@messageBus.sendingToRedis(config.redis.channels.toBBBApps.chat, data)
|
||||
@messageBus.sendingToRedis(config.redis.channels.toBBBApps.chat, data)
|
||||
|
||||
|
||||
sendingUsersMessage: (data) =>
|
||||
@messageBus.sendingToRedis(config.redis.channels.toBBBApps.users, data)
|
||||
|
@ -1,8 +1,8 @@
|
||||
postal = require('postal')
|
||||
crypto = require 'crypto'
|
||||
postal = require 'postal'
|
||||
|
||||
config = require '../config'
|
||||
log = require './bbblogger'
|
||||
log = require './bbblogger'
|
||||
|
||||
moduleDeps = ["RedisPubSub"]
|
||||
|
||||
@ -40,4 +40,4 @@ module.exports = class MessageBus
|
||||
data: data
|
||||
|
||||
sendingToRedis: (channel, message) =>
|
||||
@pubSub.publishing(channel, message)
|
||||
@pubSub.publishing(channel, message)
|
||||
|
@ -58,7 +58,7 @@ module.exports = class RedisPubSub
|
||||
# put the entry in the hash so we can match the response later
|
||||
@pendingRequests[correlationId] = entry
|
||||
message.header.reply_to = correlationId
|
||||
console.log("\n\nmessage=" + JSON.stringify(message) + "\n\n")
|
||||
console.log "\n Waiting for a reply on:" + JSON.stringify(message)
|
||||
log.info({ message: message, channel: config.redis.channels.toBBBApps.meeting}, "Publishing a message")
|
||||
@pubClient.publish(config.redis.channels.toBBBApps.meeting, JSON.stringify(message))
|
||||
|
||||
@ -73,16 +73,16 @@ module.exports = class RedisPubSub
|
||||
# TODO: this has to be in a try/catch block, otherwise the server will
|
||||
# crash if the message has a bad format
|
||||
message = JSON.parse(jsonMsg)
|
||||
correlationId = message.payload?.reply_to or message.header?.reply_to
|
||||
|
||||
unless message.header?.name is "keep_alive_reply" #temporarily stop logging the keep_alive_reply message
|
||||
unless message.header?.name is "keep_alive_reply"
|
||||
console.log "\nchannel=" + channel
|
||||
console.log "correlationId=" + correlationId if correlationId?
|
||||
console.log "eventType=" + message.header?.name + "\n"
|
||||
log.debug({ pattern: pattern, channel: channel, message: message}, "Received a message from redis")
|
||||
#console.log "=="+JSON.stringify message
|
||||
|
||||
# retrieve the request entry
|
||||
|
||||
#correlationId = message.header?.reply_to
|
||||
correlationId = message.payload?.reply_to or message.header?.reply_to
|
||||
console.log "\ncorrelation_id=" + correlationId
|
||||
if correlationId? and @pendingRequests?[correlationId]?
|
||||
entry = @pendingRequests[correlationId]
|
||||
# make sure the message in the timeout isn't triggered by clearing it
|
||||
@ -94,76 +94,63 @@ module.exports = class RedisPubSub
|
||||
topic: entry.replyTo.topic
|
||||
data: message
|
||||
else
|
||||
#sendToController(message)
|
||||
if message.header?.name is 'get_presentation_info_reply'
|
||||
#filter for the current=true page on the server-side
|
||||
currentPage = null
|
||||
numCurrentPage = null
|
||||
presentations = message.payload?.presentations
|
||||
|
||||
if message.header?.name is 'validate_auth_token_reply'
|
||||
if message.payload?.valid is "true"
|
||||
for presentation in presentations
|
||||
pages = presentation.pages
|
||||
|
||||
#TODO use the message library for these messages. Perhaps put it in Modules?!
|
||||
for page in pages
|
||||
if page.current is true
|
||||
currentPage = page
|
||||
numCurrentPage = page.num
|
||||
|
||||
joinMeetingMessage = {
|
||||
console.log "\n\n\n\n the message is: " + JSON.stringify message
|
||||
console.log "\n" + message.payload?.presentations[0]?.id + "/" + numCurrentPage + "\n\n"
|
||||
#request the whiteboard information
|
||||
requestMessage = {
|
||||
"payload": {
|
||||
"meeting_id": message.payload.meeting_id
|
||||
"user_id": message.payload.userid
|
||||
"meeting_id": message.payload?.meeting_id
|
||||
"requester_id": message.payload?.requester_id
|
||||
"whiteboard_id": message.payload?.presentations[0]?.id + "/" + numCurrentPage #not sure if always [0]
|
||||
},
|
||||
"header": {
|
||||
"timestamp": new Date().getTime()
|
||||
"reply_to": message.payload.meeting_id + "/" + message.payload.userid
|
||||
"name": "user_joined_event"
|
||||
"name": "get_whiteboard_shapes_request"
|
||||
}
|
||||
}
|
||||
# the user joins the meeting
|
||||
@publishing(config.redis.channels.toBBBApps.whiteboard, requestMessage)
|
||||
|
||||
@pubClient.publish(config.redis.channels.toBBBApps.users, JSON.stringify(joinMeetingMessage))
|
||||
console.log "just published the joinMeetingMessage in RedisPubSub"
|
||||
#strip off excess data, leaving only the current slide information
|
||||
message.payload.currentPage = currentPage
|
||||
message.payload.presentations = null
|
||||
message.header.name = "presentation_page"
|
||||
|
||||
#get the list of users in the meeting
|
||||
getUsersMessage = {
|
||||
"payload": {
|
||||
"meeting_id": message.payload.meeting_id
|
||||
"requester_id": message.payload.userid
|
||||
},
|
||||
"header": {
|
||||
"timestamp": new Date().getTime()
|
||||
"reply_to": message.payload.meeting_id + "/" + message.payload.userid
|
||||
"name": "get_users_request"
|
||||
}
|
||||
}
|
||||
else if message.header?.name is 'presentation_shared_message'
|
||||
currentPage = null
|
||||
presentation = message.payload?.presentation
|
||||
for page in presentation.pages
|
||||
if page.current is true
|
||||
currentPage = page
|
||||
|
||||
@pubClient.publish(config.redis.channels.toBBBApps.users, JSON.stringify(getUsersMessage))
|
||||
console.log "just published the getUsersMessage in RedisPubSub"
|
||||
#strip off excess data, leaving only the current slide information
|
||||
message.payload.currentPage = currentPage
|
||||
message.payload.presentation = null
|
||||
message.header.name = "presentation_page"
|
||||
|
||||
#get the chat history
|
||||
getChatHistory = {
|
||||
"payload": {
|
||||
"meeting_id": message.payload.meeting_id
|
||||
"requester_id": message.payload.userid
|
||||
},
|
||||
"header": {
|
||||
"timestamp": new Date().getTime()
|
||||
"reply_to": message.payload.meeting_id + "/" + message.payload.userid
|
||||
"name": "get_chat_history"
|
||||
}
|
||||
}
|
||||
else if message.header?.name is 'presentation_page_changed_message'
|
||||
message.payload.currentPage = message.payload?.page
|
||||
message.payload?.page = null
|
||||
message.header.name = "presentation_page"
|
||||
|
||||
@pubClient.publish(config.redis.channels.toBBBApps.chat, JSON.stringify(getChatHistory))
|
||||
console.log "just published the getChatHistory in RedisPubSub"
|
||||
|
||||
|
||||
else if message.header?.name is 'get_users_reply'
|
||||
console.log 'got a reply from bbb-apps for get users'
|
||||
console.log " Sending to Controller (In):" + message.header?.name
|
||||
sendToController(message)
|
||||
|
||||
else if message.header?.name is 'get_chat_history_reply'
|
||||
console.log 'got a reply from bbb-apps for chat history'
|
||||
sendToController(message)
|
||||
|
||||
else if message.header?.name is 'send_public_chat_message'
|
||||
console.log "just got a public chat message :" + JSON.stringify message
|
||||
sendToController (message)
|
||||
|
||||
publishing: (channel, message) =>
|
||||
console.log '\n Publishing\n'
|
||||
console.log "Publishing #{message.header?.name}"
|
||||
@pubClient.publish(channel, JSON.stringify(message))
|
||||
|
||||
sendToController = (message) ->
|
||||
@ -171,4 +158,3 @@ sendToController = (message) ->
|
||||
channel: config.redis.internalChannels.receive
|
||||
topic: "broadcast"
|
||||
data: message
|
||||
|
||||
|
@ -5,7 +5,6 @@ define [
|
||||
'cs!models/user'
|
||||
], (_, Backbone, globals, UserModel) ->
|
||||
|
||||
# TODO: this class should actually store UserModel's, for now it is only trigerring events
|
||||
UsersCollection = Backbone.Collection.extend
|
||||
model: UserModel
|
||||
|
||||
@ -23,38 +22,41 @@ define [
|
||||
|
||||
_registerEvents: ->
|
||||
|
||||
globals.events.on "connection:user_list_change", (users) =>
|
||||
globals.events.trigger("users:user_list_change", users)
|
||||
|
||||
globals.events.on "connection:load_users", (users) =>
|
||||
#alert "load users"
|
||||
for userBlock in users
|
||||
@add [
|
||||
id : userBlock.id
|
||||
userid: userBlock.id
|
||||
username: userBlock.name
|
||||
new UserModel {id: userBlock.id, userid: userBlock.id, username: userBlock.name}
|
||||
]
|
||||
globals.events.trigger("users:load_users", users)
|
||||
|
||||
globals.events.on "connection:user_join", (userid, username) =>
|
||||
console.log "users.coffee: on(connection:user_join)" + username
|
||||
@add [
|
||||
id : userid
|
||||
userid: userid
|
||||
username: username
|
||||
]
|
||||
globals.events.trigger("users:user_join", userid, username)
|
||||
#globals.events.on "getUsers", =>
|
||||
#users = @toJSON()
|
||||
#globals.events.trigger("receiveUsers", users)
|
||||
|
||||
globals.events.on "connection:user_leave", (userid) =>
|
||||
toDel = @get(userid)
|
||||
@remove(toDel)
|
||||
globals.events.trigger("users:user_leave", userid)
|
||||
globals.events.on "connection:user_join", (newUserid, newUsername) =>
|
||||
unless @get(newUserid)? #check if the user is already present
|
||||
#newUser = new UserModel {id: newUserid, userid: newUserid, username: newUsername}
|
||||
newUser = new UserModel()
|
||||
newUser.id = newUserid
|
||||
newUser.userid = newUserid
|
||||
newUser.username = newUsername
|
||||
|
||||
@add [
|
||||
newUser
|
||||
]
|
||||
globals.events.trigger("user:add_new_user", newUser)
|
||||
|
||||
globals.events.on "connection:user_left", (userid) =>
|
||||
toDel = @get(userid)
|
||||
@remove(toDel)
|
||||
globals.events.trigger("users:user_left", userid)
|
||||
if toDel? # only remove if the user model was found
|
||||
@remove(toDel)
|
||||
globals.events.trigger("users:user_left", userid)
|
||||
|
||||
globals.events.on "connection:setPresenter", (userid) =>
|
||||
globals.events.trigger("users:setPresenter", userid)
|
||||
|
||||
render: ->
|
||||
alert "user collection rendering"
|
||||
|
||||
UsersCollection
|
||||
|
@ -17,13 +17,10 @@ define [
|
||||
@userId = @getUrlVars()["user_id"]
|
||||
@meetingId = @getUrlVars()["meeting_id"]
|
||||
@username = @getUrlVars()["username"]
|
||||
globals.meetingName = decodeURI(@getUrlVars()["meetingName"])
|
||||
|
||||
disconnect: ->
|
||||
if @socket?
|
||||
console.log "disconnecting from", @host
|
||||
@socket.disconnect()
|
||||
else
|
||||
console.log "tried to disconnect but it's not connected"
|
||||
alert( " i go through disconnect") # not used right now
|
||||
|
||||
connect: ->
|
||||
console.log("user_id=" + @userId + " auth_token=" + @authToken + " meeting_id=" + @meetingId)
|
||||
@ -57,12 +54,10 @@ define [
|
||||
console.log "socket.io received: data"
|
||||
globals.events.trigger("message", data)
|
||||
|
||||
|
||||
# Immediately say we are connected
|
||||
@socket.on "connect", =>
|
||||
console.log "socket on: connect"
|
||||
globals.events.trigger("connection:connected")
|
||||
#@socket.emit "user connect" # tell the server we have a new user
|
||||
|
||||
message = {
|
||||
"payload": {
|
||||
@ -80,17 +75,22 @@ define [
|
||||
if @authToken? and @userId? and @meetingId?
|
||||
@socket.emit "message", message
|
||||
|
||||
# Received a list of users from bbb-apps
|
||||
# param {object} message object
|
||||
@socket.on "get_users_reply", (message) =>
|
||||
users = []
|
||||
for user in message.payload?.users
|
||||
users.push user
|
||||
|
||||
globals.events.trigger("connection:load_users", users)
|
||||
|
||||
@socket.on "get_chat_history_reply", (message) =>
|
||||
requesterId = message.payload?.requester_id
|
||||
|
||||
#console.log("my_id=" + @userId + ", while requester_id=" + requesterId)
|
||||
if(requesterId is @userId)
|
||||
users = []
|
||||
for user in message.payload?.users
|
||||
users.push user
|
||||
|
||||
globals.events.trigger("connection:load_users", users)
|
||||
|
||||
# Received a the chat history for a meeting
|
||||
# @param {object} message object
|
||||
@socket.on "get_chat_history_reply", (message) =>
|
||||
requesterId = message.payload?.requester_id
|
||||
if(requesterId is @userId)
|
||||
globals.events.trigger("connection:all_messages", message.payload?.chat_history)
|
||||
|
||||
@ -102,12 +102,6 @@ define [
|
||||
text = message.payload.message.message
|
||||
globals.events.trigger("connection:msg", username, text)
|
||||
|
||||
# Received event to logout yourself
|
||||
@socket.on "logout", ->
|
||||
console.log "socket on: logout"
|
||||
Utils.postToUrl "logout"
|
||||
window.location.replace "./"
|
||||
|
||||
# If the server disconnects from the client or vice-versa
|
||||
@socket.on "disconnect", ->
|
||||
console.log "socket on: disconnect"
|
||||
@ -115,83 +109,91 @@ define [
|
||||
globals.events.trigger("connection:disconnected")
|
||||
@socket = null
|
||||
|
||||
@socket.on "reconnect", ->
|
||||
console.log "socket on: reconnect"
|
||||
globals.events.trigger("connection:reconnect")
|
||||
#@socket.on "reconnect", ->
|
||||
# console.log "socket on: reconnect"
|
||||
# globals.events.trigger("connection:reconnect")
|
||||
|
||||
@socket.on "reconnecting", ->
|
||||
console.log "socket on: reconnecting"
|
||||
globals.events.trigger("connection:reconnecting")
|
||||
#@socket.on "reconnecting", ->
|
||||
# console.log "socket on: reconnecting"
|
||||
# globals.events.trigger("connection:reconnecting")
|
||||
|
||||
@socket.on "reconnect_failed", ->
|
||||
console.log "socket on: reconnect_failed"
|
||||
globals.events.trigger("connection:reconnect_failed")
|
||||
#@socket.on "reconnect_failed", ->
|
||||
# console.log "socket on: reconnect_failed"
|
||||
# globals.events.trigger("connection:reconnect_failed")
|
||||
|
||||
# If an error occurs while not connected
|
||||
# @param {string} reason Reason for the error.
|
||||
@socket.on "error", (reason) ->
|
||||
console.error "unable to connect socket.io", reason
|
||||
#@socket.on "error", (reason) -> #TODO
|
||||
# console.error "unable to connect socket.io", reason
|
||||
|
||||
# Received event to update all the slide images
|
||||
# @param {Array} urls list of URLs to be added to the paper (after old images are removed)
|
||||
@socket.on "all_slides", (allSlidesEventObject) =>
|
||||
console.log "socket on: all_slides"
|
||||
console.log "allSlidesEventObject: " + allSlidesEventObject
|
||||
globals.events.trigger("connection:all_slides", allSlidesEventObject);
|
||||
#@socket.on "all_slides", (allSlidesEventObject) =>
|
||||
# console.log "socket on: all_slides"
|
||||
# console.log "allSlidesEventObject: " + allSlidesEventObject
|
||||
# globals.events.trigger("connection:all_slides", allSlidesEventObject);
|
||||
|
||||
# Received event to clear the whiteboard shapes
|
||||
@socket.on "clrPaper",=>
|
||||
console.log "socket on: clrPaper"
|
||||
globals.events.trigger("connection:clrPaper")
|
||||
#@socket.on "clrPaper",=>
|
||||
# console.log "socket on: clrPaper"
|
||||
# globals.events.trigger("connection:clrPaper")
|
||||
|
||||
# Received event to update all the shapes in the whiteboard
|
||||
# @param {Array} shapes Array of shapes to be drawn
|
||||
@socket.on "allShapes", (allShapesEventObject) =>
|
||||
console.log "socket on: all_shapes" + allShapesEventObject
|
||||
globals.events.trigger("connection:all_shapes", allShapesEventObject)
|
||||
#@socket.on "allShapes", (allShapesEventObject) =>
|
||||
# # check for the requester_id
|
||||
# console.log "socket on: all_shapes" + allShapesEventObject
|
||||
# globals.events.trigger("connection:all_shapes", allShapesEventObject)
|
||||
|
||||
# Received event to update all the shapes in the whiteboard
|
||||
# @param {Array} shapes Array of shapes to be drawn
|
||||
#@socket.on "get_whiteboard_shapes_reply", (object) =>
|
||||
# if @userId is object.payload?.requester_id
|
||||
# #alert("I am getting some shapes reply" + JSON.stringify object)
|
||||
# for shape in object.payload?.shapes
|
||||
# #alert("for a shape:")
|
||||
# shape_type = shape.shape_type
|
||||
# globals.events.trigger("connection:whiteboard_draw_event", shape_type, shape.shape) # TODO to change the name
|
||||
|
||||
# globals.events.trigger("connection:updShape", shape_type, shape.shape)
|
||||
|
||||
# Received event to update a shape being created
|
||||
# @param {string} shape type of shape being updated
|
||||
# @param {Array} data all information to update the shape
|
||||
@socket.on "whiteboard_update_event", (data) =>
|
||||
console.log "socket on: whiteboard_update_event"
|
||||
shape = data.payload.shape_type
|
||||
@socket.on "send_whiteboard_shape_message", (data) =>
|
||||
alert "send_whiteboard_shape_message" + JSON.stringify data
|
||||
shape = data.payload.shape.shape_type
|
||||
for point in data.payload.shape.shape.points
|
||||
point = point/100 #early attempt to scale down
|
||||
globals.events.trigger("connection:whiteboard_draw_event", shape, data)
|
||||
globals.events.trigger("connection:updShape", shape, data)
|
||||
|
||||
# Received event to create a shape on the whiteboard
|
||||
# @param {string} shape type of shape being made
|
||||
# @param {Array} data all information to make the shape
|
||||
@socket.on "whiteboard_draw_event", (data) =>
|
||||
console.log "socket on: whiteboard_draw_event"
|
||||
shape = data.payload.shape_type
|
||||
globals.events.trigger("connection:whiteboard_draw_event", shape, data)
|
||||
|
||||
# Pencil drawings are received as points from the server and painted as lines.
|
||||
@socket.on "whiteboardDrawPen", (data) =>
|
||||
console.log "socket on: whiteboardDrawPen"+ data
|
||||
globals.events.trigger("connection:whiteboardDrawPen", data)
|
||||
#@socket.on "whiteboardDrawPen", (data) =>
|
||||
# console.log "socket on: whiteboardDrawPen"+ data
|
||||
# globals.events.trigger("connection:whiteboardDrawPen", data)
|
||||
|
||||
# Received event to update the cursor coordinates
|
||||
# @param {number} x x-coord of the cursor as a percentage of page width
|
||||
# @param {number} y y-coord of the cursor as a percentage of page height
|
||||
@socket.on "mvCur", (data) =>
|
||||
x = data.cursor.x #TODO change to new json structure
|
||||
y = data.cursor.y #TODO change to new json structure
|
||||
console.log "socket on: mvCur"
|
||||
globals.events.trigger("connection:mvCur", x, y)
|
||||
#@socket.on "mvCur", (data) =>
|
||||
# x = data.cursor.x #TODO change to new json structure
|
||||
# y = data.cursor.y #TODO change to new json structure
|
||||
# console.log "socket on: mvCur"
|
||||
# globals.events.trigger("connection:mvCur", x, y)
|
||||
|
||||
# Received event to update the zoom or move the slide
|
||||
# @param {number} x x-coord of the cursor as a percentage of page width
|
||||
# @param {number} y y-coord of the cursor as a percentage of page height
|
||||
@socket.on "move_and_zoom", (xOffset, yOffset, widthRatio, heightRatio) =>
|
||||
console.log "socket on: move_and_zoom"
|
||||
globals.events.trigger("connection:move_and_zoom", xOffset, yOffset, widthRatio, heightRatio)
|
||||
#@socket.on "move_and_zoom", (xOffset, yOffset, widthRatio, heightRatio) =>
|
||||
# console.log "socket on: move_and_zoom"
|
||||
# globals.events.trigger("connection:move_and_zoom", xOffset, yOffset, widthRatio, heightRatio)
|
||||
|
||||
# Received event to update the slide image
|
||||
# @param {string} url URL of image to show
|
||||
@socket.on "changeslide", (url) =>
|
||||
console.log "socket on: changeslide"
|
||||
globals.events.trigger("connection:changeslide", url)
|
||||
#@socket.on "changeslide", (url) =>
|
||||
# console.log "socket on: changeslide"
|
||||
# globals.events.trigger("connection:changeslide", url)
|
||||
|
||||
# Received event to update the viewBox value
|
||||
# @param {string} xperc Percentage of x-offset from top left corner
|
||||
@ -199,69 +201,67 @@ define [
|
||||
# @param {string} wperc Percentage of full width of image to be displayed
|
||||
# @param {string} hperc Percentage of full height of image to be displayed
|
||||
# TODO: not tested yet
|
||||
@socket.on "viewBox", (xperc, yperc, wperc, hperc) =>
|
||||
console.log "socket on: viewBox"
|
||||
globals.events.trigger("connection:viewBox", xperc, yperc, wperc, hperc)
|
||||
#@socket.on "viewBox", (xperc, yperc, wperc, hperc) =>
|
||||
# console.log "socket on: viewBox"
|
||||
# globals.events.trigger("connection:viewBox", xperc, yperc, wperc, hperc)
|
||||
|
||||
|
||||
# Received event to update the zoom level of the whiteboard.
|
||||
# @param {number} delta amount of change in scroll wheel
|
||||
@socket.on "zoom", (delta) ->
|
||||
console.log "socket on: zoom"
|
||||
globals.events.trigger("connection:zoom", delta)
|
||||
#@socket.on "zoom", (delta) ->
|
||||
# console.log "socket on: zoom"
|
||||
# globals.events.trigger("connection:zoom", delta)
|
||||
|
||||
# Received event to update the whiteboard size and position
|
||||
# @param {number} cx x-offset from top left corner as percentage of original width of paper
|
||||
# @param {number} cy y-offset from top left corner as percentage of original height of paper
|
||||
# @param {number} sw slide width as percentage of original width of paper
|
||||
# @param {number} sh slide height as a percentage of original height of paper
|
||||
@socket.on "paper", (cx, cy, sw, sh) ->
|
||||
console.log "socket on: paper"
|
||||
globals.events.trigger("connection:paper", cx, cy, sw, sh)
|
||||
#@socket.on "paper", (cx, cy, sw, sh) ->
|
||||
# console.log "socket on: paper"
|
||||
# globals.events.trigger("connection:paper", cx, cy, sw, sh)
|
||||
|
||||
# Received event when the panning action finishes
|
||||
@socket.on "panStop", ->
|
||||
console.log "socket on: panStop"
|
||||
globals.events.trigger("connection:panStop")
|
||||
#@socket.on "panStop", ->
|
||||
# console.log "socket on: panStop"
|
||||
# globals.events.trigger("connection:panStop")
|
||||
|
||||
|
||||
# Received event to denote when the text has been created
|
||||
@socket.on "textDone", ->
|
||||
console.log "socket on: textDone"
|
||||
globals.events.trigger("connection:textDone")
|
||||
#@socket.on "textDone", ->
|
||||
# console.log "socket on: textDone"
|
||||
# globals.events.trigger("connection:textDone")
|
||||
|
||||
# Received event to update the status of the upload progress
|
||||
# @param {string} message update message of status of upload progress
|
||||
# @param {boolean} fade true if you wish the message to automatically disappear after 3 seconds
|
||||
@socket.on "uploadStatus", (message, fade) =>
|
||||
console.log "socket on: uploadStatus"
|
||||
globals.events.trigger("connection:uploadStatus", message, fade)
|
||||
#@socket.on "uploadStatus", (message, fade) =>
|
||||
# console.log "socket on: uploadStatus"
|
||||
# globals.events.trigger("connection:uploadStatus", message, fade)
|
||||
|
||||
# Received event for a user list change
|
||||
# @param {Array} users Array of names and publicIDs of connected users
|
||||
# TODO: event name with spaces is bad
|
||||
@socket.on "user list change", (users) =>
|
||||
console.log "socket on: user list change"
|
||||
globals.events.trigger("connection:user_list_change", users)
|
||||
#@socket.on "user list change", (users) =>
|
||||
# console.log "socket on: user list change"
|
||||
# globals.events.trigger("connection:user_list_change", users)
|
||||
|
||||
# Received event for a new user
|
||||
@socket.on "user_joined_event", (message) =>
|
||||
console.log "message: " + message
|
||||
userid = message.payload.user.id
|
||||
@socket.on "user_joined_message", (message) =>
|
||||
userid = message.payload.user.userid
|
||||
username = message.payload.user.name
|
||||
globals.events.trigger("connection:user_join", userid, username) #should it be user_joined?! #TODO
|
||||
globals.events.trigger("connection:user_join", userid, username)
|
||||
|
||||
# Received event when a user leaves
|
||||
@socket.on "user_left_event", (message) =>
|
||||
console.log "message: " + message
|
||||
userid = message.payload.user.id
|
||||
@socket.on "user_left_message", (message) ->
|
||||
userid = message.payload.user.userid
|
||||
globals.events.trigger("connection:user_left", userid)
|
||||
|
||||
# Received event to set the presenter to a user
|
||||
# @param {string} userID publicID of the user that is being set as the current presenter
|
||||
@socket.on "setPresenter", (userid) =>
|
||||
console.log "socket on: setPresenter"
|
||||
globals.events.trigger("connection:setPresenter", userid)
|
||||
#@socket.on "setPresenter", (userid) =>
|
||||
# console.log "socket on: setPresenter"
|
||||
# globals.events.trigger("connection:setPresenter", userid)
|
||||
|
||||
# Received event to update all the messages in the chat box
|
||||
# @param {Array} messages Array of messages in public chat box
|
||||
@ -269,10 +269,11 @@ define [
|
||||
# console.log "socket on: all_messages" + allMessagesEventObject
|
||||
# globals.events.trigger("connection:all_messages", allMessagesEventObject)
|
||||
|
||||
@socket.on "share_presentation_event", (data) =>
|
||||
console.log "socket on: share_presentation_event"
|
||||
globals.events.trigger("connection:share_presentation_event", data)
|
||||
|
||||
# Change the current slide/page [if any] with the one
|
||||
# contained in the message
|
||||
@socket.on "presentation_page", (message) ->
|
||||
console.log "socket on: presentation_page"
|
||||
globals.events.trigger("connection:display_page", message)
|
||||
|
||||
# Emit an update to move the cursor around the canvas
|
||||
# @param {number} x x-coord of the cursor as a percentage of page width
|
||||
@ -281,16 +282,12 @@ define [
|
||||
@socket.emit "mvCur", x, y
|
||||
|
||||
# Requests the shapes from the server.
|
||||
emitAllShapes: ->
|
||||
@socket.emit "all_shapes"
|
||||
#emitAllShapes: ->
|
||||
# @socket.emit "all_shapes"
|
||||
|
||||
|
||||
# Emit a message to the server
|
||||
# @param {string} the message
|
||||
# Emit a chat message to the server
|
||||
# @param {string} the chat message
|
||||
emitMsg: (msg) ->
|
||||
|
||||
console.log "emitting message: " + msg
|
||||
|
||||
object = {
|
||||
"header": {
|
||||
"name": "send_public_chat_message"
|
||||
@ -314,33 +311,31 @@ define [
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@socket.emit "message", object
|
||||
|
||||
|
||||
# Emit the finish of a text shape
|
||||
emitTextDone: ->
|
||||
@socket.emit "textDone"
|
||||
#emitTextDone: ->
|
||||
# @socket.emit "textDone"
|
||||
|
||||
# Emit the creation of a shape
|
||||
# @param {string} shape type of shape
|
||||
# @param {Array} data all the data required to draw the shape on the client whiteboard
|
||||
emitMakeShape: (shape, data) ->
|
||||
@socket.emit "makeShape", shape, data
|
||||
#emitMakeShape: (shape, data) ->
|
||||
# @socket.emit "makeShape", shape, data
|
||||
|
||||
# Emit the update of a shape
|
||||
# @param {string} shape type of shape
|
||||
# @param {Array} data all the data required to update the shape on the client whiteboard
|
||||
emitUpdateShape: (shape, data) ->
|
||||
@socket.emit "updShape", shape, data
|
||||
#emitUpdateShape: (shape, data) ->
|
||||
# @socket.emit "updShape", shape, data
|
||||
|
||||
# Emit an update in the whiteboard position/size values
|
||||
# @param {number} cx x-offset from top left corner as percentage of original width of paper
|
||||
# @param {number} cy y-offset from top left corner as percentage of original height of paper
|
||||
# @param {number} sw slide width as percentage of original width of paper
|
||||
# @param {number} sh slide height as a percentage of original height of paper
|
||||
emitPaperUpdate: (cx, cy, sw, sh) ->
|
||||
@socket.emit "paper", cx, cy, sw, sh
|
||||
#emitPaperUpdate: (cx, cy, sw, sh) ->
|
||||
# @socket.emit "paper", cx, cy, sw, sh
|
||||
|
||||
# Update the zoom level for the clients
|
||||
# @param {number} delta amount of change in scroll wheel
|
||||
@ -348,43 +343,58 @@ define [
|
||||
@socket.emit "zoom", delta
|
||||
|
||||
# Request the next slide
|
||||
emitNextSlide: ->
|
||||
@socket.emit "nextslide"
|
||||
#emitNextSlide: ->
|
||||
# @socket.emit "nextslide"
|
||||
|
||||
# Request the previous slide
|
||||
emitPreviousSlide: ->
|
||||
@socket.emit "prevslide"
|
||||
#emitPreviousSlide: ->
|
||||
# @socket.emit "prevslide"
|
||||
|
||||
# Logout of the meeting
|
||||
emitLogout: ->
|
||||
@socket.emit "logout"
|
||||
message = {
|
||||
"payload": {
|
||||
"meeting_id": @meetingId
|
||||
"userid": @userId
|
||||
},
|
||||
"header": {
|
||||
"timestamp": new Date().getTime()
|
||||
"name": "user_leaving_request"
|
||||
"version": "0.0.1"
|
||||
}
|
||||
}
|
||||
@socket.emit "message", message
|
||||
@socket.disconnect()
|
||||
|
||||
#Utils.postToUrl "logout"
|
||||
#window.location.replace "./"
|
||||
|
||||
# Emit panning has stopped
|
||||
emitPanStop: ->
|
||||
@socket.emit "panStop"
|
||||
#emitPanStop: ->
|
||||
# @socket.emit "panStop"
|
||||
|
||||
# Publish a shape to the server to be saved
|
||||
# @param {string} shape type of shape to be saved
|
||||
# @param {Array} data information about shape so that it can be recreated later
|
||||
emitPublishShape: (shape, data) ->
|
||||
@socket.emit "saveShape", shape, JSON.stringify(data)
|
||||
#emitPublishShape: (shape, data) ->
|
||||
# @socket.emit "saveShape", shape, JSON.stringify(data)
|
||||
|
||||
# Emit a change in the current tool
|
||||
# @param {string} tool [description]
|
||||
emitChangeTool: (tool) ->
|
||||
@socket.emit "changeTool", tool
|
||||
#emitChangeTool: (tool) ->
|
||||
# @socket.emit "changeTool", tool
|
||||
|
||||
# Tell the server to undo the last shape
|
||||
emitUndo: ->
|
||||
@socket.emit "undo"
|
||||
#emitUndo: ->
|
||||
# @socket.emit "undo"
|
||||
|
||||
# Emit a change in the presenter
|
||||
emitSetPresenter: (id) ->
|
||||
@socket.emit "setPresenter", id
|
||||
#emitSetPresenter: (id) ->
|
||||
# @socket.emit "setPresenter", id
|
||||
|
||||
# Emit signal to clear the canvas
|
||||
emitClearCanvas: (id) ->
|
||||
@socket.emit "clrPaper", id
|
||||
#emitClearCanvas: (id) ->
|
||||
# @socket.emit "clrPaper", id
|
||||
|
||||
# Helper method to get the meeting_id, user_id and auth_token from the url
|
||||
getUrlVars: ->
|
||||
|
22
client/bbb-html5-client/public/js/models/user.coffee
Normal file → Executable file
22
client/bbb-html5-client/public/js/models/user.coffee
Normal file → Executable file
@ -2,10 +2,26 @@ define [
|
||||
'underscore',
|
||||
'backbone',
|
||||
'globals'
|
||||
], (_, Backbone, globals) ->
|
||||
'text!templates/user.html'
|
||||
], (_, Backbone, globals, userTemplate) ->
|
||||
|
||||
UserModel = Backbone.Model.extend
|
||||
|
||||
initialize: ->
|
||||
defaults:
|
||||
id : null
|
||||
userid: null
|
||||
username: null
|
||||
|
||||
initialize: ->
|
||||
#alert("iiiiiinitialize"+newUserid+" "+newUsername)
|
||||
console.log "creation"
|
||||
|
||||
isValid: ->
|
||||
console.log "inside is valid- id: #{@id} userid: #{@userid} username: #{@username}"
|
||||
value = @id? and @userid? and @username?
|
||||
|
||||
UserModel
|
||||
render: ->
|
||||
_.template(userTemplate, {userID: @userid, username: @username})
|
||||
|
||||
|
||||
UserModel
|
@ -23,6 +23,7 @@ define [
|
||||
|
||||
# Container must be a DOM element
|
||||
initialize: (@container) ->
|
||||
alert("initializing the paper model")
|
||||
# a WhiteboardCursorModel
|
||||
@cursor = null
|
||||
|
||||
@ -152,8 +153,7 @@ define [
|
||||
# @return {Raphael.image} the image object added to the whiteboard
|
||||
addImageToPaper: (url, width, height) ->
|
||||
@_updateContainerDimensions()
|
||||
|
||||
alert "addImageToPaper url=#{url} \n #{width}x#{height}"
|
||||
|
||||
if @fitToPage
|
||||
# solve for the ratio of what length is going to fit more than the other
|
||||
max = Math.max(width / @containerWidth, height / @containerHeight)
|
||||
@ -169,7 +169,7 @@ define [
|
||||
originalHeight = height
|
||||
else
|
||||
# fit to width
|
||||
alert "no fit"
|
||||
console.log "ERROR! The slide did not fit"
|
||||
# assume it will fit width ways
|
||||
sw = width / wr
|
||||
sh = height / wr
|
||||
@ -279,7 +279,7 @@ define [
|
||||
@cursor.undrag()
|
||||
@currentLine = @_createTool(tool)
|
||||
@cursor.drag(@currentLine.dragOnMove, @currentLine.dragOnStart, @currentLine.dragOnEnd)
|
||||
when "rect"
|
||||
when "rectangle"
|
||||
@cursor.undrag()
|
||||
@currentRect = @_createTool(tool)
|
||||
@cursor.drag(@currentRect.dragOnMove, @currentRect.dragOnStart, @currentRect.dragOnEnd)
|
||||
@ -352,6 +352,7 @@ define [
|
||||
# Draws an array of shapes to the paper.
|
||||
# @param {array} shapes the array of shapes to draw
|
||||
drawListOfShapes: (shapes) ->
|
||||
alert("drawListOfShapes" + shapes.length)
|
||||
@currentShapesDefinitions = shapes
|
||||
@currentShapes = @raphaelObj.set()
|
||||
for shape in shapes
|
||||
@ -365,11 +366,6 @@ define [
|
||||
# make sure the cursor is still on top
|
||||
@cursor.toFront()
|
||||
|
||||
#Changes the currently displayed presentation (if any) with this one
|
||||
#@param {object} containing the "presentation" object -id,name,pages[]
|
||||
sharePresentation: (data) ->
|
||||
globals.events.trigger("connection:all_slides", data.payload)
|
||||
|
||||
# Clear all shapes from this paper.
|
||||
clearShapes: ->
|
||||
if @currentShapes?
|
||||
@ -398,6 +394,7 @@ define [
|
||||
|
||||
# Make a shape `shape` with the data in `data`.
|
||||
makeShape: (shape, data) ->
|
||||
console.log("shape=" + shape + " data=" + JSON.stringify data)
|
||||
tool = null
|
||||
switch shape
|
||||
when "path", "line"
|
||||
@ -415,6 +412,7 @@ define [
|
||||
when "triangle"
|
||||
@currentTriangle = @_createTool(shape)
|
||||
toolModel = @currentTriangle
|
||||
toolModel.draw(tool, data)
|
||||
tool = @currentTriangle.make(data)
|
||||
when "text"
|
||||
@currentText = @_createTool(shape)
|
||||
@ -423,7 +421,12 @@ define [
|
||||
else
|
||||
console.log "shape not recognized at makeShape", shape
|
||||
if tool?
|
||||
@currentShapes.push(tool)
|
||||
alert("in currentShapes")
|
||||
if @currentShapes? #rewrite TODO
|
||||
@currentShapes.push(tool)
|
||||
else
|
||||
@currentShapes = []
|
||||
@currentShapes.push(tool)
|
||||
@currentShapesDefinitions.push(toolModel.getDefinition())
|
||||
|
||||
# Update the cursor position on screen
|
||||
@ -516,47 +519,12 @@ define [
|
||||
|
||||
# Registers listeners for events in the gloval event bus
|
||||
_registerEvents: ->
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
globals.events.on "connection:all_slides", (data) =>
|
||||
@removeAllImagesFromPaper()
|
||||
###
|
||||
urls = data.slides
|
||||
for url in urls
|
||||
@addImageToPaper(url[0], url[1], url[2])
|
||||
#alert "registerEvents url[0]=" + url[0]
|
||||
###
|
||||
|
||||
urls = data.presentation.pages
|
||||
for url in urls
|
||||
@addImageToPaper(url.png , 200, 200)
|
||||
#alert "registerEvents url[0]=" + url[0]
|
||||
globals.events.trigger("whiteboard:paper:all_slides", urls)
|
||||
|
||||
|
||||
globals.events.on "connection:clrPaper", =>
|
||||
@clearShapes()
|
||||
|
||||
globals.events.on "connection:allShapes", (allShapesEventObject) =>
|
||||
# TODO: a hackish trick for making compatible the shapes from redis with the node.js
|
||||
alert("on connection:allShapes:" + JSON.stringify allShapesEventObject)
|
||||
shapes = allShapesEventObject.shapes
|
||||
for shape in shapes
|
||||
properties = JSON.parse(shape.data)
|
||||
@ -585,8 +553,9 @@ define [
|
||||
globals.events.on "connection:whiteboard_draw_event", (shape, data) =>
|
||||
@makeShape shape, data
|
||||
|
||||
globals.events.on "connection:share_presentation_event", (data) =>
|
||||
@sharePresentation data
|
||||
globals.events.on "connection:display_page", (data) =>
|
||||
console.log ("connection:display_page in whiteboard_paper.coffee")
|
||||
@_displayPage data
|
||||
|
||||
globals.events.on "connection:whiteboardDrawPen", (startingData) =>
|
||||
type = startingData.payload.shape_type
|
||||
@ -876,4 +845,11 @@ define [
|
||||
else
|
||||
globals.presentationServer + url
|
||||
|
||||
#Changes the currently displayed page/slide (if any) with this one
|
||||
#@param {data} message object containing the "presentation" object
|
||||
_displayPage: (data) ->
|
||||
@removeAllImagesFromPaper()
|
||||
page = data.payload.currentPage
|
||||
@addImageToPaper(page.png_uri, 400, 400) #the dimensions should be modified
|
||||
|
||||
WhiteboardPaperModel
|
||||
|
@ -24,16 +24,16 @@ define [
|
||||
# @param {string} colour the colour of the object
|
||||
# @param {number} thickness the thickness of the object's line(s)
|
||||
make: (startingData) ->
|
||||
console.log "make startingData"+ startingData
|
||||
x = startingData.payload.data.coordinate.first_x
|
||||
y = startingData.payload.data.coordinate.first_y
|
||||
color = startingData.payload.data.line.color
|
||||
thickness = startingData.payload.data.line.weight
|
||||
console.log "make startingData"#+ JSON.stringify startingData
|
||||
x = startingData.payload.shape.shape.points[0]
|
||||
y = startingData.payload.shape.shape.points[1]
|
||||
color = startingData.payload.shape.shape.color
|
||||
thickness = startingData.payload.shape.shape.thickness
|
||||
|
||||
@obj = @paper.rect(x * @gw + @xOffset, y * @gh + @yOffset, 0, 0, 1)
|
||||
@obj.attr Utils.strokeAndThickness(color, thickness)
|
||||
@definition =
|
||||
shape: "rect"
|
||||
shape: "rectangle"
|
||||
data: [x, y, 0, 0, @obj.attrs["stroke"], @obj.attrs["stroke-width"]]
|
||||
@obj
|
||||
|
||||
@ -44,11 +44,11 @@ define [
|
||||
# @param {number} y2 the y value of the bottom right corner
|
||||
# @param {boolean} square (draw a square or not)
|
||||
update: (startingData) ->
|
||||
x1 = startingData.payload.data.coordinate.first_x
|
||||
y1 = startingData.payload.data.coordinate.first_y
|
||||
x2 = startingData.payload.data.coordinate.last_x
|
||||
y2 = startingData.payload.data.coordinate.last_y
|
||||
square = startingData.payload.data.square
|
||||
x1 = startingData.payload.shape.shape.points[0]
|
||||
y1 = startingData.payload.shape.shape.points[1]
|
||||
x2 = startingData.payload.shape.shape.points[2]
|
||||
y2 = startingData.payload.shape.shape.points[3]
|
||||
square = startingData.payload.shape.shape.square
|
||||
if @obj?
|
||||
[x1, x2] = [x2, x1] if x2 < x1
|
||||
[x1, x2] = [x2, x1] if x2 < x1
|
||||
|
@ -8,10 +8,11 @@ define [
|
||||
'cs!views/session_navbar_hidden',
|
||||
'cs!views/session_chat',
|
||||
'cs!views/session_users',
|
||||
'cs!views/SingleUserView',
|
||||
'cs!views/session_video'
|
||||
'cs!views/session_whiteboard'
|
||||
], ($, _, Backbone, globals, sessionTemplate, SessionNavbarView, SessionNavbarHiddenView,
|
||||
SessionChatView, SessionUsersView, SessionVideoView, SessionWhiteboardView) ->
|
||||
SessionChatView, SessionUsersView, SingleUserView, SessionVideoView, SessionWhiteboardView) ->
|
||||
|
||||
SessionView = Backbone.View.extend
|
||||
tagName: 'section'
|
||||
@ -23,6 +24,7 @@ define [
|
||||
@navbarHiddenView = new SessionNavbarHiddenView()
|
||||
@navbarHiddenView.$parentEl = @$el
|
||||
@chatView = new SessionChatView()
|
||||
@singleUserView = new SingleUserView()
|
||||
@usersView = new SessionUsersView()
|
||||
@videoView = new SessionVideoView()
|
||||
@whiteboardView = new SessionWhiteboardView()
|
||||
|
@ -63,11 +63,8 @@ define [
|
||||
#TODO check if public or private message, etc...
|
||||
@_scrollToBottom()
|
||||
|
||||
globals.events.on "users:user_leave", (userid) =>
|
||||
@_removeUserFromChatList(userid, username)
|
||||
|
||||
globals.events.on "users:user_left", (userid) =>
|
||||
@_removeUserFromChatList(userid) #do we need username or userid is sufficient?
|
||||
@_removeUserFromChatList(userid)
|
||||
|
||||
globals.events.on "users:user_join", (userid, username) =>
|
||||
console.log "session_chat - user_join for user:#{username}"
|
||||
@ -132,9 +129,7 @@ define [
|
||||
# @param userid [string] the name of the user
|
||||
_addUserToChatList: (userid, username) ->
|
||||
# only add the new element if it doesn't exist yet
|
||||
console.log("_addUserToChatList ", userid, " ", username)
|
||||
console.log "chat-user-#{userid}.length =" + $("#chat-user-#{userid}").length
|
||||
unless $("#chat-user-#{userid}").length > 0
|
||||
if $("#chat-user-#{userid}").length is 0
|
||||
data =
|
||||
userid: userid
|
||||
username: username
|
||||
@ -143,8 +138,7 @@ define [
|
||||
|
||||
# Removes a user from the list of users in the chat
|
||||
# @param userid [string] the ID of the user
|
||||
# @param userid [string] the name of the user
|
||||
_removeUserFromChatList: (userid, username) ->
|
||||
_removeUserFromChatList: (userid) ->
|
||||
$("#chat-user-#{userid}").remove()
|
||||
|
||||
# When a user clicks to start a private chat with a user
|
||||
@ -208,7 +202,7 @@ define [
|
||||
|
||||
# Adds a default welcome message to the chat
|
||||
_addWelcomeMessage: ->
|
||||
msg = "You are now connected to the meeting '#{globals.currentAuth?.get('meetingID')}'"
|
||||
msg = "You are now connected to the meeting '#{globals.meetingName}'"
|
||||
@_addChatMessage("System", msg)
|
||||
|
||||
SessionChatView
|
||||
|
@ -22,6 +22,7 @@ define [
|
||||
|
||||
initialize: ->
|
||||
@$parentEl = null
|
||||
@usersShown = true # Whether the user's pane is displayed, it is displayed be default
|
||||
|
||||
render: ->
|
||||
compiledTemplate = _.template(sessionNavbarTemplate)
|
||||
@ -41,9 +42,15 @@ define [
|
||||
@$parentEl.toggleClass('chat-on')
|
||||
@_setToggleButtonsStatus()
|
||||
|
||||
# Toggle the visibility of the users panel
|
||||
# Toggle the visibility of the user's pane
|
||||
_toggleUsers: ->
|
||||
@$parentEl.toggleClass('users-on')
|
||||
if @usersShown # If the user's pane is displayed, hide it and mark flag as hidden
|
||||
$("#users").hide()
|
||||
@usersShown=false
|
||||
else # Display the pane
|
||||
$("#users").show()
|
||||
@usersShown=true
|
||||
#@$parentEl.toggleClass('users-on')
|
||||
@_setToggleButtonsStatus()
|
||||
|
||||
_toggleVideo: ->
|
||||
@ -65,6 +72,7 @@ define [
|
||||
_scheduleResize: (id) ->
|
||||
attempts = 0
|
||||
before = $(id).is(':visible')
|
||||
console.log "isvisible: "+before
|
||||
interval = setInterval( ->
|
||||
if $(id).is(':visible') != before or attempts > 20
|
||||
attempts += 1
|
||||
@ -77,6 +85,6 @@ define [
|
||||
# Log out of the session
|
||||
_logout: ->
|
||||
globals.connection.emitLogout()
|
||||
globals.currentAuth = null
|
||||
#globals.currentAuth = null
|
||||
|
||||
SessionNavbarView
|
||||
|
@ -13,15 +13,16 @@ define [
|
||||
# manage the events in the users.
|
||||
SessionUsersView = Backbone.View.extend
|
||||
model: new UserCollection()
|
||||
|
||||
|
||||
events:
|
||||
"click #switch-presenter": "_switchPresenter"
|
||||
"click .user": "_userClicked"
|
||||
|
||||
initialize: ->
|
||||
@userListID = "#user-list"
|
||||
userListID = "#user-list"
|
||||
@model.start()
|
||||
|
||||
@users = null
|
||||
|
||||
# Bind to the event triggered when the client connects to the server
|
||||
if globals.connection.isConnected()
|
||||
@_registerEvents()
|
||||
@ -30,9 +31,10 @@ define [
|
||||
@_registerEvents()
|
||||
|
||||
render: ->
|
||||
# this renders to unordered list where users will be appended to
|
||||
compiledTemplate = _.template(sessionUsersTemplate)
|
||||
@$el.html compiledTemplate
|
||||
|
||||
|
||||
# Registers listeners for events in the event bus.
|
||||
# TODO: bind to backbone events in UserCollection such as 'user added', 'user removed'
|
||||
_registerEvents: ->
|
||||
@ -40,18 +42,22 @@ define [
|
||||
globals.events.on "users:user_list_change", (users) =>
|
||||
@_removeAllUsers()
|
||||
for userBlock in users
|
||||
console.log("on user_list_change; adding user:" + JSON.stringify userBlock)
|
||||
@_addUser(userBlock.id, userBlock.name)
|
||||
|
||||
#globals.events.on "receiveUsers", (data) =>
|
||||
#@users = data
|
||||
|
||||
globals.events.on "users:load_users", (users) =>
|
||||
@_removeAllUsers()
|
||||
for userBlock in users
|
||||
@_addUser(userBlock.id, userBlock.name)
|
||||
#@_addUser(userBlock.userid, userBlock.name)
|
||||
globals.events.trigger "user:add_new_user", {id: userBlock.userid, userid: userBlock.userid, username: userBlock.name}
|
||||
|
||||
globals.events.on "users:user_join", (userid, username) =>
|
||||
@_addUser(userid, username)
|
||||
|
||||
globals.events.on "users:user_leave", (userid) =>
|
||||
@_removeUserByID(userid)
|
||||
#@_addUser(userid, username)
|
||||
console.log "fffffffffffffff"
|
||||
globals.events.trigger "user:add_new_user", {id: userid, userid: userid, username: username}
|
||||
|
||||
globals.events.on "users:user_left", (userid) =>
|
||||
@_removeUserByID(userid)
|
||||
@ -67,14 +73,6 @@ define [
|
||||
_removeUserByID: (userID)->
|
||||
@$("#user-"+userID).remove()
|
||||
|
||||
# Add a user to the screen.
|
||||
_addUser: (userID, username) ->
|
||||
data =
|
||||
username: username
|
||||
userID: userID
|
||||
compiledTemplate = _.template(userTemplate, data)
|
||||
@$el.children("ul").append compiledTemplate
|
||||
|
||||
# Marks a user as selected when clicked.
|
||||
_userClicked: (e) ->
|
||||
@$('.user.selected').removeClass('selected')
|
||||
|
@ -1,10 +1,10 @@
|
||||
<li class="user-wrapper">
|
||||
<div class="row">
|
||||
<div class="user-role col-md-1" id="user-<%= userID %>"><i class="icon fa fa-user"></i></div>
|
||||
<div class="user-name col-md-5"><%= username %></div>
|
||||
<div class="user-video col-md-1"><i class="icon fa fa-video-camera"></i></div>
|
||||
<div class="user-audio col-md-1"><i class="icon fa fa-microphone"></i></div>
|
||||
<div class="user-kick col-md-1"><i class="icon fa fa-sign-out"></i></div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="user-wrapper" id="user-<%= userID %>">
|
||||
<div class="row">
|
||||
<div class="user-role col-md-1"><i class="icon fa fa-user"></i></div>
|
||||
<div class="user-name col-md-5"><%= username %></div>
|
||||
<div class="user-video col-md-1"><i class="icon fa fa-video-camera"></i></div>
|
||||
<div class="user-audio col-md-1"><i class="icon fa fa-microphone"></i></div>
|
||||
<div class="user-kick col-md-1"><i class="icon fa fa-sign-out"></i></div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</li>
|
1
labs/demos/.gitignore
vendored
Normal file
1
labs/demos/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
node_modules
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"settings": {
|
||||
"IP": "http://192.168.0.203",
|
||||
"IP": "http://192.168.0.232",
|
||||
"PORT": "4000",
|
||||
"salt": "74a91f30f165423067bf3039722e33e0"
|
||||
"salt": "c7faeb82a786bd71134b61833b0ec4af"
|
||||
}
|
||||
}
|
||||
}
|
9
labs/demos/lib/handlers.coffee
Normal file → Executable file
9
labs/demos/lib/handlers.coffee
Normal file → Executable file
@ -2,6 +2,7 @@ xml2js = require 'xml2js'
|
||||
|
||||
bbbapi = require './bbbapi'
|
||||
testapi = require './testapi'
|
||||
configJson = require './../config.json'
|
||||
|
||||
index = (request, response) ->
|
||||
response.sendfile('./views/index.html')
|
||||
@ -19,6 +20,8 @@ login = (req, resp) ->
|
||||
#calling createapi
|
||||
bbbapi.create(createParams, serverAndSecret, {}, (errorOuter, responseOuter, bodyOuter) ->
|
||||
#console.log JSON.stringify(response)
|
||||
console.log "\n\nouterXML=" + responseOuter.body
|
||||
console.log "\nerrorOuter=" + JSON.stringify errorOuter
|
||||
bbbapi.join(joinParams, serverAndSecret, {}, (error, response, body) ->
|
||||
if error
|
||||
console.log error
|
||||
@ -38,8 +41,8 @@ login = (req, resp) ->
|
||||
"\nuser_id = " + user_id +
|
||||
"\nauth_token = " + auth_token
|
||||
|
||||
url = "http://192.168.0.203:3000/html5.client?meeting_id=" + meeting_id + "&user_id=" +
|
||||
user_id + "&auth_token=" + auth_token + "&username=" + joinParams.fullName
|
||||
url = "#{configJson.settings.IP}:3000/meeting_id=" + meeting_id + "&user_id=" +
|
||||
user_id + "&auth_token=" + auth_token
|
||||
|
||||
json =
|
||||
resp.json({
|
||||
@ -49,7 +52,7 @@ login = (req, resp) ->
|
||||
failure: {
|
||||
message: "Something went terribly wrong"
|
||||
}
|
||||
})
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -11,19 +11,19 @@ console.log "will be creating a meeting on server: " + bbbServer
|
||||
str = "name=Demo+Meeting&meetingID=Demo+Meeting&voiceBridge=70827&attendeePW=ap&moderatorPW=mp&record=false"
|
||||
|
||||
console.log(sha1("create" + str + sharedSecret))
|
||||
|
||||
tempName = "Demo Meeting"
|
||||
createParams = {}
|
||||
createParams.attendeePW = "ap"
|
||||
createParams.moderatorPW = "mp"
|
||||
createParams.record = false
|
||||
createParams.voiceBridge = 70827
|
||||
createParams.name = "Demo Meeting"
|
||||
createParams.meetingID = "Demo Meeting"
|
||||
createParams.name = tempName
|
||||
createParams.meetingID = tempName
|
||||
|
||||
joinParams = {}
|
||||
joinParams.password = "mp"
|
||||
joinParams.password = "ap"
|
||||
joinParams.fullName = "Richard"
|
||||
joinParams.meetingID = "Demo Meeting"
|
||||
joinParams.meetingID = tempName
|
||||
joinParams.redirect = false
|
||||
|
||||
serverAndSecret = {server: bbbServer, secret: sharedSecret}
|
||||
|
2
labs/demos/views/index.html
Normal file → Executable file
2
labs/demos/views/index.html
Normal file → Executable file
@ -1,7 +1,7 @@
|
||||
<!doctype html>
|
||||
<html ng-app="landingPage">
|
||||
<head>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.7/angular.min.js"></script>
|
||||
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.4/angular.min.js"></script>
|
||||
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
|
||||
<script src="../public/js/app.js"></script>
|
||||
</head>
|
||||
|
6
labs/meteor-client/.gitignore
vendored
Executable file
6
labs/meteor-client/.gitignore
vendored
Executable file
@ -0,0 +1,6 @@
|
||||
packages
|
||||
build/
|
||||
npm-debug.log
|
||||
../node_modules/
|
||||
../node_modules/hiredis/
|
||||
../node_modules/redis/
|
1
labs/meteor-client/.meteor/.gitignore
vendored
Normal file
1
labs/meteor-client/.meteor/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
local
|
15
labs/meteor-client/.meteor/packages
Normal file
15
labs/meteor-client/.meteor/packages
Normal file
@ -0,0 +1,15 @@
|
||||
# Meteor packages used by this project, one per line.
|
||||
#
|
||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||
# but you can also edit it by hand.
|
||||
|
||||
standard-app-packages
|
||||
coffeescript
|
||||
redis
|
||||
npm
|
||||
less
|
||||
bootstrap-3
|
||||
iron-router
|
||||
underscore
|
||||
amplify
|
||||
raphaeljs-package
|
1
labs/meteor-client/.meteor/release
Normal file
1
labs/meteor-client/.meteor/release
Normal file
@ -0,0 +1 @@
|
||||
0.8.1.3
|
347
labs/meteor-client/client/compatibility/3rd-party.js
Executable file
347
labs/meteor-client/client/compatibility/3rd-party.js
Executable file
@ -0,0 +1,347 @@
|
||||
|
||||
var presenterUserID = "";
|
||||
|
||||
var registerListeners = function() {
|
||||
console.log("Listening for events.");
|
||||
BBB.listen("QueryPresentationsReplyEvent", function(bbbEvent) {
|
||||
console.log("Number of presentations [" + bbbEvent.presentations.length + "]. First presentation [" + bbbEvent.presentations[0] + "].");
|
||||
});
|
||||
BBB.listen("OpenExternalFileUploadWindowEvent", function(bbbEvent) {
|
||||
console.log("Open file upload dialog. Max file size is [" + bbbEvent.maxFileSize + "].");
|
||||
});
|
||||
BBB.listen("UserKickedOutEvent", function(bbbEvent) {
|
||||
console.log("User has been kicked [" + bbbEvent.userID + "].");
|
||||
});
|
||||
BBB.listen("SwitchedLayoutEvent", function(bbbEvent) {
|
||||
console.log("New Layout [" + bbbEvent.layoutID + "].");
|
||||
});
|
||||
BBB.listen("NewRoleEvent", function(bbbEvent) {
|
||||
console.log("New Role Event [amIPresenter=" + bbbEvent.amIPresenter + ",role=" + bbbEvent.role + ",newPresenterUserID=" + bbbEvent.newPresenterUserID + "].");
|
||||
});
|
||||
BBB.listen("SwitchedPresenterEvent", function(bbbEvent) {
|
||||
console.log("Switched Presenter [amIPresenter=" + bbbEvent.amIPresenter + ",role=" + bbbEvent.role + ",newPresenterUserID=" + bbbEvent.newPresenterUserID + "].");
|
||||
|
||||
presenterUserID = bbbEvent.newPresenterUserID;
|
||||
|
||||
if (bbbEvent.amIPresenter) {
|
||||
console.log("*** I am presenter. Am I publishing webcam?");
|
||||
BBB.listen("AmISharingCamQueryResponse", function(bbbEvent2) {
|
||||
console.log("AmISharingCamQueryResponse [isPublishing=" + bbbEvent2.isPublishing + ",camIndex=" + bbbEvent2.camIndex + "]");
|
||||
});
|
||||
BBB.amISharingWebcam();
|
||||
BBB.amISharingWebcam(function(bbbEvent3) {
|
||||
console.log("amISharingWebcam [isPublishing=" + bbbEvent3.isPublishing
|
||||
+ ",camIndex=" + bbbEvent3.camIndex
|
||||
+ ",camWidth=" + bbbEvent3.camWidth
|
||||
+ ",camHeight=" + bbbEvent3.camHeight
|
||||
+ ",camKeyFrameInterval=" + bbbEvent3.camKeyFrameInterval
|
||||
+ ",camModeFps=" + bbbEvent3.camModeFps
|
||||
+ ",camQualityBandwidth=" + bbbEvent3.camQualityBandwidth
|
||||
+ ",camQualityPicture=" + bbbEvent3.camQualityPicture
|
||||
+ "]");
|
||||
if (bbbEvent3.isPublishing) {
|
||||
CAM_PREVIEW.stopPreviewCamera(bbbEvent3.avatarURL);
|
||||
CAM_PREVIEW.previewCamera(bbbEvent3.camIndex, bbbEvent3.camWidth, bbbEvent3.camHeight, bbbEvent3.camKeyFrameInterval,
|
||||
bbbEvent3.camModeFps, bbbEvent3.camQualityBandwidth, bbbEvent3.camQualityPicture, bbbEvent3.avatarURL);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log("*** I am NOT presenter. Is new presenter publishing webcam?");
|
||||
BBB.listen("IsUserPublishingCamResponse", function(bbbEvent4) {
|
||||
console.log("IsUserPublishingCamResponse [isUserPublishing=" + bbbEvent4.isUserPublishing
|
||||
+ ",uri=" + bbbEvent4.uri
|
||||
+ ",streamName=" + bbbEvent4.streamName + "]");
|
||||
});
|
||||
BBB.isUserSharingWebcam(bbbEvent.newPresenterUserID);
|
||||
BBB.isUserSharingWebcam(bbbEvent.newPresenterUserID, function(bbbEvent5) {
|
||||
console.log("isUserSharingWebcam [isUserPublishing=" + bbbEvent5.isUserPublishing
|
||||
+ ",uri=" + bbbEvent5.uri
|
||||
+ ",streamName=" + bbbEvent5.streamName + "]");
|
||||
if (presenterUserID == bbbEvent.userID) {
|
||||
CAM_VIEW.stopViewWebcamStream(bbbEvent.avatarURL);
|
||||
CAM_VIEW.viewWebcamStream(bbbEvent.uri, bbbEvent.streamName, bbbEvent5.avatarURL);
|
||||
}
|
||||
});
|
||||
CAM_PREVIEW.stopPreviewCamera(bbbEvent.avatarURL);
|
||||
}
|
||||
});
|
||||
BBB.listen("UserLeftEvent", function(bbbEvent) {
|
||||
console.log("User [" + bbbEvent.userID + "] has left.");
|
||||
});
|
||||
BBB.listen("UserJoinedEvent", function(bbbEvent) {
|
||||
console.log("User [" + bbbEvent.userID + ", [" + bbbEvent.userName + "] has joined.");
|
||||
});
|
||||
BBB.listen("NewPublicChatEvent", function(bbbEvent) {
|
||||
console.log("Received NewPublicChatEvent [" + bbbEvent.message + "]");
|
||||
});
|
||||
BBB.listen("NewPrivateChatEvent", function(bbbEvent) {
|
||||
console.log("Received NewPrivateChatEvent event");
|
||||
});
|
||||
BBB.listen("UserJoinedVoiceEvent", function(bbbEvent) {
|
||||
console.log("User [" + bbbEvent.userID + "] had joined the voice conference.");
|
||||
});
|
||||
BBB.listen("UserLeftVoiceEvent", function(bbbEvent) {
|
||||
console.log("User [" + bbbEvent.userID + "has left the voice conference.");
|
||||
});
|
||||
BBB.listen("UserVoiceMutedEvent", function(bbbEvent) {
|
||||
console.log("User [" + bbbEvent.userID + "] is muted [" + bbbEvent.muted + "]");
|
||||
});
|
||||
BBB.listen("UserLockedVoiceEvent", function(bbbEvent) {
|
||||
console.log("User [" + bbbEvent.userID + "] is locked [" + bbbEvent.locked + "]");
|
||||
});
|
||||
BBB.listen("CamStreamSharedEvent", function(bbbEvent) {
|
||||
console.log("User CamStreamSharedEvent [" + bbbEvent.uri + "," + bbbEvent.streamName + "]");
|
||||
if (presenterUserID == bbbEvent.userID) {
|
||||
CAM_VIEW.stopViewWebcamStream(bbbEvent.avatarURL);
|
||||
CAM_VIEW.viewWebcamStream(bbbEvent.uri, bbbEvent.streamName, bbbEvent.avatarURL);
|
||||
}
|
||||
});
|
||||
BBB.listen("BroadcastingCameraStartedEvent", function(bbbEvent) {
|
||||
console.log("User BroadcastingCameraStartedEvent [" + bbbEvent.camIndex + "] [" + bbbEvent.camWidth + "]");
|
||||
if (bbbEvent.isPresenter) {
|
||||
CAM_PREVIEW.stopPreviewCamera(bbbEvent.avatarURL);
|
||||
CAM_PREVIEW.previewCamera(bbbEvent.camIndex, bbbEvent.camWidth, bbbEvent.camHeight, bbbEvent.camKeyFrameInterval,
|
||||
bbbEvent.camModeFps, bbbEvent.camQualityBandwidth, bbbEvent.camQualityPicture, bbbEvent.avatarURL);
|
||||
}
|
||||
});
|
||||
BBB.listen("BroadcastingCameraStoppedEvent", function(bbbEvent) {
|
||||
console.log("User BroadcastingCameraStoppedEvent ]");
|
||||
CAM_PREVIEW.stopPreviewCamera(bbbEvent.avatarURL);
|
||||
});
|
||||
|
||||
console.log("Listen Presentation Updates");
|
||||
BBB.listen("OfficeDocConversionSuccessEvent", function(bbbEvent) {
|
||||
console.log("Successfully converted Office document. : " + JSON.stringify(bbbEvent));
|
||||
});
|
||||
|
||||
BBB.listen("OfficeDocConversionFailedEvent", function(bbbEvent) {
|
||||
console.log("Failed to convert Office document. : " + JSON.stringify(bbbEvent));
|
||||
});
|
||||
|
||||
BBB.listen("SupportedDocEvent", function(bbbEvent) {
|
||||
console.log("Uploaded presentation file type is supported. : " + JSON.stringify(bbbEvent));
|
||||
});
|
||||
|
||||
BBB.listen("UnsupportedDocEvent", function(bbbEvent) {
|
||||
console.log("Uploaded presentation file type is unsupported. : " + JSON.stringify(bbbEvent));
|
||||
});
|
||||
|
||||
BBB.listen("PageCountFailedEvent", function(bbbEvent) {
|
||||
console.log("Failed to determine number of pages for the uploaded presentation. : " + JSON.stringify(bbbEvent));
|
||||
});
|
||||
|
||||
BBB.listen("ThumbnailsUpdateEvent", function(bbbEvent) {
|
||||
console.log("Generating thumbnails for uploaded presentation. : " + JSON.stringify(bbbEvent));
|
||||
});
|
||||
|
||||
BBB.listen("PageCountExceededEvent", function(bbbEvent) {
|
||||
console.log("Uploaded presentation had exceeded max number of pages. : " + JSON.stringify(bbbEvent));
|
||||
});
|
||||
|
||||
BBB.listen("ConversionSuccessEvent", function(bbbEvent) {
|
||||
console.log("Successfully converted uploaded presentation. : " + JSON.stringify(bbbEvent));
|
||||
});
|
||||
|
||||
BBB.listen("ConversionProgressEvent", function(bbbEvent) {
|
||||
console.log("Progress update on conversion process. : " + JSON.stringify(bbbEvent));
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
var leaveVoiceConference2 = function () {
|
||||
BBB.leaveVoiceConference();
|
||||
}
|
||||
|
||||
var joinVoiceConference2 = function () {
|
||||
BBB.joinVoiceConference();
|
||||
}
|
||||
|
||||
var amIPresenterAsync = function() {
|
||||
BBB.listen("AmIPresenterQueryResponse", function(bbbEvent) {
|
||||
console.log("Received AmIPresenterQueryResponse event [" + bbbEvent.amIPresenter + "]");
|
||||
});
|
||||
|
||||
BBB.amIPresenter();
|
||||
}
|
||||
|
||||
var amIPresenterSync = function() {
|
||||
BBB.amIPresenter(function(amIPresenter) {
|
||||
console.log("Am I Presenter = " + amIPresenter);
|
||||
});
|
||||
}
|
||||
|
||||
var getMyUserInfoAsynch = function() {
|
||||
BBB.listen("GetMyUserInfoResponse", function(bbbEvent) {
|
||||
console.log("User info response [myUserID=" + bbbEvent.myUserID
|
||||
+ ",myUsername=" + bbbEvent.myUsername + ",myAvatarURL=" + bbbEvent.myAvatarURL
|
||||
+ ",myRole=" + bbbEvent.myRole + ",amIPresenter=" + bbbEvent.amIPresenter
|
||||
+ ",dialNumber=" + bbbEvent.dialNumber + ",voiceBridge=" + bbbEvent.voiceBridge + "].");
|
||||
|
||||
for(var key in bbbEvent.customdata){
|
||||
console.log(key + " " + bbbEvent.customdata[key]);
|
||||
}
|
||||
});
|
||||
|
||||
BBB.getMyUserInfo();
|
||||
}
|
||||
|
||||
var getMyUserInfoSynch = function() {
|
||||
BBB.getMyUserInfo(function(userInfo) {
|
||||
console.log("User info callback [myUserID=" + userInfo.myUserID
|
||||
+ ",myUsername=" + userInfo.myUsername + ",myAvatarURL=" + userInfo.myAvatarURL
|
||||
+ ",myRole=" + userInfo.myRole + ",amIPresenter=" + userInfo.amIPresenter
|
||||
+ ",dialNumber=" + userInfo.dialNumber + ",voiceBridge=" + userInfo.voiceBridge + "].");
|
||||
|
||||
for(var key in userInfo.customdata){
|
||||
console.log(key + " " + userInfo.customdata[key]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
var getMyRoleAsynch = function() {
|
||||
BBB.listen("GetMyRoleResponse", function(bbbEvent) {
|
||||
console.log("Received GetMyRoleResponse event [" + bbbEvent.myRole + "]");
|
||||
});
|
||||
|
||||
BBB.getMyRole();
|
||||
}
|
||||
|
||||
var getMyRoleSynch = function() {
|
||||
BBB.getMyRole(function(myRole) {
|
||||
console.log("My role = " + myRole);
|
||||
});
|
||||
}
|
||||
|
||||
var getMyUserID = function() {
|
||||
BBB.getMyUserID(function(userID) {
|
||||
console.log("My user ID = [" + userID + "]");
|
||||
});
|
||||
}
|
||||
|
||||
var getMeetingID = function() {
|
||||
BBB.getMeetingID(function(meetingID) {
|
||||
console.log("Meeting ID = [" + meetingID + "]");
|
||||
});
|
||||
}
|
||||
|
||||
var raiseHand = function(raiseHand) {
|
||||
BBB.raiseHand(raiseHand);
|
||||
}
|
||||
|
||||
var muteMe = function() {
|
||||
BBB.muteMe();
|
||||
}
|
||||
|
||||
var unmuteMe = function() {
|
||||
BBB.unmuteMe();
|
||||
}
|
||||
|
||||
var muteAll = function() {
|
||||
BBB.muteAll();
|
||||
}
|
||||
|
||||
var unmuteAll = function() {
|
||||
BBB.unmuteAll();
|
||||
}
|
||||
|
||||
var switchLayout = function(newLayout) {
|
||||
BBB.switchLayout(newLayout);
|
||||
}
|
||||
|
||||
var lockLayout = function(lock) {
|
||||
BBB.lockLayout(lock);
|
||||
}
|
||||
|
||||
var queryListOfPresentations = function() {
|
||||
BBB.queryListOfPresentations();
|
||||
}
|
||||
|
||||
var displayPresentation = function(presentationID) {
|
||||
BBB.displayPresentation(presentationID);
|
||||
}
|
||||
|
||||
var deletePresentation = function(presentationID) {
|
||||
BBB.deletePresentation(presentationID);
|
||||
}
|
||||
|
||||
var sendPublicChat = function () {
|
||||
var message = "Hello from the Javascript API";
|
||||
BBB.sendPublicChatMessage('0x7A7A7A', "en", message);
|
||||
}
|
||||
|
||||
var sendPrivateChat = function () {
|
||||
var message = "ECHO: " + bbbEvent.message;
|
||||
BBB.sendPrivateChatMessage(bbbEvent.fromColor, bbbEvent.fromLang, message, bbbEvent.fromUserID);
|
||||
}
|
||||
|
||||
var webcamViewStandaloneAppReady = function() {
|
||||
console.log("WebcamViewStandalone App is ready.");
|
||||
BBB.getPresenterUserID(function(puid) {
|
||||
if (puid == "") {
|
||||
console.log("There is no presenter in the meeting");
|
||||
} else {
|
||||
console.log("The presenter user id is [" + puid + "]");
|
||||
// Is presenter sharing webcam? If so, get the webcam stream and display.
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var webcamPreviewStandaloneAppReady = function() {
|
||||
console.log("WebcamPreviewStandalone App is ready.");
|
||||
BBB.getPresenterUserID(function(puid) {
|
||||
if (puid == "") {
|
||||
console.log("There is no presenter in the meeting");
|
||||
} else {
|
||||
console.log("The presenter user id is [" + puid + "]");
|
||||
}
|
||||
});
|
||||
// Am I presenter? If so, am I publishing my camera? If so, display my camera.
|
||||
|
||||
}
|
||||
|
||||
var uploadPresentation = function() {
|
||||
|
||||
console.log("uploadPresentation");
|
||||
|
||||
BBB.getInternalMeetingID(function(meetingID) {
|
||||
var formData = new FormData($('form')[0]);
|
||||
formData.append("presentation_name", document.getElementById('fileUpload').value.split(/(\\|\/)/g).pop());
|
||||
formData.append("conference", meetingID);
|
||||
formData.append("room", meetingID);
|
||||
|
||||
$.ajax({
|
||||
url: '/bigbluebutton/presentation/upload', //server script to process data
|
||||
type: 'POST',
|
||||
xhr: function() { // custom xhr
|
||||
myXhr = $.ajaxSettings.xhr();
|
||||
if(myXhr.upload){ // check if upload property exists
|
||||
myXhr.upload.addEventListener('progress',progressHandlingFunction, false); // for handling the progress of the upload
|
||||
}
|
||||
return myXhr;
|
||||
},
|
||||
//Ajax events
|
||||
success: completeHandler,
|
||||
error: errorHandler,
|
||||
// Form data
|
||||
data: formData,
|
||||
//Options to tell JQuery not to process data or worry about content-type
|
||||
cache: false,
|
||||
contentType: false,
|
||||
processData: false
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function progressHandlingFunction(e){
|
||||
if(e.lengthComputable){
|
||||
console.log("progress: loaded " + e.loaded + " total:" + e.total);
|
||||
}
|
||||
}
|
||||
function completeHandler(e){
|
||||
$('form')[0].reset();
|
||||
console.log("you file has been uploaded!");
|
||||
}
|
||||
function errorHandler(e){
|
||||
console.log("There was an error uploading your file.");
|
||||
}
|
117
labs/meteor-client/client/compatibility/bbb_webrtc_bridge_jssip.js
Executable file
117
labs/meteor-client/client/compatibility/bbb_webrtc_bridge_jssip.js
Executable file
@ -0,0 +1,117 @@
|
||||
|
||||
var bbbAudioConference;
|
||||
var currentSession;
|
||||
|
||||
// Hang Up webrtc call
|
||||
function webrtc_hangup(callback) {
|
||||
console.log("Terminating current session");
|
||||
currentSession.terminate(); // allows calling multiple times
|
||||
callback();
|
||||
}
|
||||
|
||||
// Call
|
||||
function webrtc_call(username, voiceBridge, server, callback) {
|
||||
var sayswho = navigator.sayswho,
|
||||
browser = sayswho[0],
|
||||
version = +(sayswho[1].split('.')[0]);
|
||||
|
||||
console.log("Browser: " + browser + ", version: " + version);
|
||||
if ( !( (browser == "Chrome" && version >= 28) || (browser == "Firefox" && version >= 26) ) ) {
|
||||
callback({'status': 'browserError', message: "Browser version not supported"});
|
||||
return;
|
||||
}
|
||||
|
||||
server = server || window.document.location.host.split(':')[0]
|
||||
console.log("user " + username + " calling to " + voiceBridge);
|
||||
|
||||
var configuration = {
|
||||
uri: 'sip:' + escape(username) + '@' + server,
|
||||
// password: freeswitchPassword,
|
||||
// ws_servers: 'wss://' + server + ':7443',
|
||||
ws_servers: 'ws://' + server + ':5066',
|
||||
display_name: username,
|
||||
// authorization_user: freeswitchUser,
|
||||
register: false,
|
||||
// register_expires: null,
|
||||
// no_answer_timeout: null,
|
||||
trace_sip: true,
|
||||
stun_servers: "stun:74.125.134.127:19302",
|
||||
// turn_servers: null,
|
||||
// use_preloaded_route: null,
|
||||
// connection_recovery_min_interval: null,
|
||||
// connection_recovery_max_interval: null,
|
||||
// hack_via_tcp: null,
|
||||
// hack_ip_in_contact: null
|
||||
};
|
||||
|
||||
|
||||
bbbAudioConference = new JsSIP.UA(configuration);
|
||||
|
||||
bbbAudioConference.on('newRTCSession', function(e) {
|
||||
console.log("New Webrtc session created!");
|
||||
currentSession = e.data.session;
|
||||
});
|
||||
|
||||
bbbAudioConference.start();
|
||||
// Make an audio/video call:
|
||||
// HTML5 <video> elements in which local and remote video will be shown
|
||||
var selfView = document.getElementById('local-media');
|
||||
var remoteView = document.getElementById('remote-media');
|
||||
|
||||
console.log("Registering callbacks to desired call events..");
|
||||
var eventHandlers = {
|
||||
'progress': function(e){
|
||||
console.log('call is in progress: ' + e.data);
|
||||
callback({'status':'progress', 'message': e.data});
|
||||
},
|
||||
'failed': function(e){
|
||||
console.log('call failed with cause: '+ e.data.cause);
|
||||
callback({'status':'failed', 'cause': e.data.cause});
|
||||
},
|
||||
'ended': function(e){
|
||||
console.log('call ended with cause: '+ e.data.cause);
|
||||
callback({'status':'ended', 'cause': e.data.cause});
|
||||
},
|
||||
'started': function(e){
|
||||
var rtcSession = e.sender;
|
||||
var localStream = false;
|
||||
var remoteStream = false;
|
||||
|
||||
console.log('BigBlueButton call started');
|
||||
|
||||
// Attach local stream to selfView
|
||||
if (rtcSession.getLocalStreams().length > 0) {
|
||||
console.log("Got local stream");
|
||||
localStream = true;
|
||||
}
|
||||
|
||||
// Attach remote stream to remoteView
|
||||
if (rtcSession.getRemoteStreams().length > 0) {
|
||||
console.log("Got remote stream");
|
||||
remoteView.src = window.URL.createObjectURL(rtcSession.getRemoteStreams()[0]);
|
||||
remoteStream = true;
|
||||
}
|
||||
callback({'status':'started', 'localStream': localStream, 'remoteStream': remoteStream});
|
||||
}
|
||||
};
|
||||
|
||||
console.log("Setting options.. ");
|
||||
var options = {
|
||||
'eventHandlers': eventHandlers,
|
||||
'mediaConstraints': {'audio': true, 'video': false}
|
||||
};
|
||||
|
||||
console.log("Calling to " + voiceBridge + "....");
|
||||
bbbAudioConference.call('sip:' + voiceBridge + '@' + server, options);
|
||||
}
|
||||
|
||||
// http://stackoverflow.com/questions/5916900/detect-version-of-browser
|
||||
navigator.sayswho= (function(){
|
||||
var ua= navigator.userAgent,
|
||||
N= navigator.appName,
|
||||
tem,
|
||||
M= ua.match(/(opera|chrome|safari|firefox|msie|trident)\/?\s*([\d\.]+)/i) || [];
|
||||
M= M[2]? [M[1], M[2]]:[N, navigator.appVersion, '-?'];
|
||||
if(M && (tem= ua.match(/version\/([\.\d]+)/i))!= null) M[2]= tem[1];
|
||||
return M;
|
||||
})();
|
19285
labs/meteor-client/client/compatibility/jssip-devel.js
Executable file
19285
labs/meteor-client/client/compatibility/jssip-devel.js
Executable file
File diff suppressed because it is too large
Load Diff
214
labs/meteor-client/client/globals.coffee
Executable file
214
labs/meteor-client/client/globals.coffee
Executable file
@ -0,0 +1,214 @@
|
||||
# retrieve account for selected user
|
||||
@getCurrentUserFromSession = ->
|
||||
Meteor.Users.findOne("userId": getInSession("userId"))
|
||||
|
||||
@getInSession = (k) -> SessionAmplify.get k
|
||||
|
||||
@getMeetingName = ->
|
||||
meetName = getInSession("meetingName") # check if we actually have one in the session
|
||||
if meetName? then meetName # great return it, no database query
|
||||
else # we need it from the database
|
||||
meet = Meteor.Meetings.findOne({})
|
||||
if meet?.meetingName
|
||||
setInSession "meetingName", meet?.meetingName # store in session for fast access next time
|
||||
meet?.meetingName
|
||||
else null
|
||||
|
||||
# Finds the names of all people the current user is in a private conversation with
|
||||
# Removes yourself and duplicates if they exist
|
||||
@getPrivateChatees = ->
|
||||
me = getInSession("userId")
|
||||
users = Meteor.Users.find().fetch()
|
||||
people = Meteor.Chat.find({$or: [{'message.from_userid': me, 'message.chat_type': 'PRIVATE_CHAT'},{'message.to_userid': me, 'message.chat_type': 'PRIVATE_CHAT'}] }).fetch()
|
||||
formattedUsers = null
|
||||
formattedUsers = (u for u in users when (do ->
|
||||
return false if u.userId is me
|
||||
found = false
|
||||
for chatter in people
|
||||
if u.userId is chatter.message.to_userid or u.userId is chatter.message.from_userid
|
||||
found = true
|
||||
found
|
||||
)
|
||||
)
|
||||
if formattedUsers? then formattedUsers else []
|
||||
|
||||
@getTime = -> # returns epoch in ms
|
||||
(new Date).valueOf()
|
||||
|
||||
@getUsersName = ->
|
||||
name = getInSession("userName") # check if we actually have one in the session
|
||||
if name? then name # great return it, no database query
|
||||
else # we need it from the database
|
||||
user = Meteor.Users.findOne({'userId': getInSession("userId")})
|
||||
if user?.user?.name
|
||||
setInSession "userName", user.user.name # store in session for fast access next time
|
||||
user.user.name
|
||||
else null
|
||||
|
||||
Handlebars.registerHelper 'equals', (a, b) -> # equals operator was dropped in Meteor's migration from Handlebars to Spacebars
|
||||
a is b
|
||||
|
||||
Handlebars.registerHelper "getCurrentSlide", ->
|
||||
currentPresentation = Meteor.Presentations.findOne({"presentation.current": true})
|
||||
presentationId = currentPresentation?.presentation?.id
|
||||
Meteor.Slides.find({"presentationId": presentationId, "slide.current": true})
|
||||
|
||||
# retrieve account for selected user
|
||||
Handlebars.registerHelper "getCurrentUser", =>
|
||||
@window.getCurrentUserFromSession()
|
||||
|
||||
# Allow access through all templates
|
||||
Handlebars.registerHelper "getInSession", (k) -> SessionAmplify.get k
|
||||
|
||||
Handlebars.registerHelper "getMeetingName", ->
|
||||
window.getMeetingName()
|
||||
|
||||
Handlebars.registerHelper "getShapesForSlide", ->
|
||||
currentPresentation = Meteor.Presentations.findOne({"presentation.current": true})
|
||||
presentationId = currentPresentation?.presentation?.id
|
||||
currentSlide = Meteor.Slides.findOne({"presentationId": presentationId, "slide.current": true})
|
||||
# try to reuse the lines above
|
||||
Meteor.Shapes.find({whiteboardId: currentSlide?.slide?.id})
|
||||
|
||||
# retrieves all users in the meeting
|
||||
Handlebars.registerHelper "getUsersInMeeting", ->
|
||||
Meteor.Users.find({})
|
||||
|
||||
Handlebars.registerHelper "isCurrentUser", (id) ->
|
||||
id is getInSession("userId")
|
||||
|
||||
Handlebars.registerHelper "meetingIsRecording", ->
|
||||
Meteor.Meetings.findOne()?.recorded # Should only ever have one meeting, so we dont need any filter and can trust result #1
|
||||
|
||||
Handlebars.registerHelper "isUserSharingAudio", (u) ->
|
||||
if u?
|
||||
user = Meteor.Users.findOne({userId:u.userid})
|
||||
user.user.voiceUser?.joined
|
||||
else return false
|
||||
|
||||
Handlebars.registerHelper "isUserSharingVideo", (u) ->
|
||||
u.webcam_stream.length isnt 0
|
||||
|
||||
Handlebars.registerHelper "isUserTalking", (u) ->
|
||||
if u?
|
||||
user = Meteor.Users.findOne({userId:u.userid})
|
||||
user.user.voiceUser?.talking
|
||||
else return false
|
||||
|
||||
Handlebars.registerHelper "isUserMuted", (u) ->
|
||||
if u?
|
||||
user = Meteor.Users.findOne({userId:u.userid})
|
||||
user.user.voiceUser?.muted
|
||||
else return false
|
||||
|
||||
Handlebars.registerHelper "messageFontSize", ->
|
||||
style: "font-size: #{getInSession("messageFontSize")}px;"
|
||||
|
||||
Handlebars.registerHelper "pointerLocation", ->
|
||||
currentPresentation = Meteor.Presentations.findOne({"presentation.current": true})
|
||||
currentPresentation.pointer
|
||||
|
||||
Handlebars.registerHelper "setInSession", (k, v) -> SessionAmplify.set k, v
|
||||
|
||||
Handlebars.registerHelper "visibility", (section) ->
|
||||
if getInSession "display_#{section}"
|
||||
style: 'display:block'
|
||||
else
|
||||
style: 'display:none'
|
||||
|
||||
# transform plain text links into HTML tags compatible with Flash client
|
||||
@linkify = (str) ->
|
||||
www = /(^|[^\/])(www\.[\S]+($|\b))/img
|
||||
http = /\b(https?:\/\/[0-9a-z+|.,:;\/&?_~%#=@!-]*[0-9a-z+|\/&_~%#=@-])/img
|
||||
str = str.replace http, "<a href='event:$1'><u>$1</u></a>"
|
||||
str = str.replace www, "$1<a href='event:http://$2'><u>$2</u></a>"
|
||||
|
||||
# Creates a 'tab' object for each person in chat with
|
||||
# adds public and options tabs to the menu
|
||||
@makeTabs = ->
|
||||
privTabs = getPrivateChatees().map (u, index) ->
|
||||
newObj = {
|
||||
userId: u.userId
|
||||
name: u.user.name
|
||||
class: "privateChatTab"
|
||||
}
|
||||
tabs = [
|
||||
{userId: "PUBLIC_CHAT", name: "Public", class: "publicChatTab"},
|
||||
{userId: "OPTIONS", name: "Options", class: "optionsChatTab"}
|
||||
].concat privTabs
|
||||
|
||||
@setInSession = (k, v) -> SessionAmplify.set k, v
|
||||
|
||||
Meteor.methods
|
||||
sendMeetingInfoToClient: (meetingId, userId) ->
|
||||
setInSession("userId", userId)
|
||||
setInSession("meetingId", meetingId)
|
||||
setInSession("currentChatId", meetingId)
|
||||
setInSession("meetingName", null)
|
||||
setInSession("bbbServerVersion", "0.90")
|
||||
setInSession("userName", null)
|
||||
|
||||
@toggleCam = (event) ->
|
||||
# Meteor.Users.update {_id: context._id} , {$set:{"user.sharingVideo": !context.sharingVideo}}
|
||||
# Meteor.call('userToggleCam', context._id, !context.sharingVideo)
|
||||
|
||||
@toggleChatbar = ->
|
||||
setInSession "display_chatbar", !getInSession "display_chatbar"
|
||||
|
||||
@toggleMic = (event) ->
|
||||
if getInSession "isSharingAudio" # only allow muting/unmuting if they are in the call
|
||||
u = Meteor.Users.findOne({userId:getInSession("userId")})
|
||||
if u?
|
||||
# format: meetingId, userId, requesterId, mutedBoolean
|
||||
# TODO: insert the requesterId - the user who requested the muting of userId (might be a moderator)
|
||||
Meteor.call('publishMuteRequest', u.meetingId, u.userId, u.userId, not u.user.voiceUser.muted)
|
||||
|
||||
@toggleNavbar = ->
|
||||
setInSession "display_navbar", !getInSession "display_navbar"
|
||||
|
||||
# toggle state of session variable
|
||||
@toggleUsersList = ->
|
||||
setInSession "display_usersList", !getInSession "display_usersList"
|
||||
|
||||
@toggleVoiceCall = (event) ->
|
||||
if getInSession "isSharingAudio"
|
||||
hangupCallback = ->
|
||||
console.log "left voice conference"
|
||||
# sometimes we can hangup before the message that the user stopped talking is received so lets set it manually, otherwise they might leave the audio call but still be registered as talking
|
||||
Meteor.call("userStopAudio", getInSession("meetingId"),getInSession("userId"))
|
||||
setInSession "isSharingAudio", false # update to no longer sharing
|
||||
webrtc_hangup hangupCallback # sign out of call
|
||||
else
|
||||
# create voice call params
|
||||
username = "#{getInSession("userId")}-bbbID-#{getUsersName()}"
|
||||
# voiceBridge = "70827"
|
||||
voiceBridge = "70827"
|
||||
server = null
|
||||
joinCallback = (message) ->
|
||||
console.log JSON.stringify message
|
||||
Meteor.call("userShareAudio", getInSession("meetingId"),getInSession("userId"))
|
||||
console.log "joined audio call"
|
||||
console.log Meteor.Users.findOne(userId:getInSession("userId"))
|
||||
setInSession "isSharingAudio", true
|
||||
webrtc_call(username, voiceBridge, server, joinCallback) # make the call
|
||||
return false
|
||||
|
||||
@toggleWhiteBoard = ->
|
||||
setInSession "display_whiteboard", !getInSession "display_whiteboard"
|
||||
|
||||
# Starts the entire logout procedure.
|
||||
# meeting: the meeting the user is in
|
||||
# the user's userId
|
||||
@userLogout = (meeting, user) ->
|
||||
Meteor.call("userLogout", meeting, user)
|
||||
|
||||
# Clear the local user session and redirect them away
|
||||
setInSession("userId", null)
|
||||
setInSession("meetingId", null)
|
||||
setInSession("currentChatId", null)
|
||||
setInSession("meetingName", null)
|
||||
setInSession("bbbServerVersion", null)
|
||||
setInSession("userName", null)
|
||||
setInSession "display_navbar", false # needed to hide navbar when the layout template renders
|
||||
|
||||
Router.go('logout') # navigate to logout
|
102
labs/meteor-client/client/lib/scale.raphael.js
Normal file
102
labs/meteor-client/client/lib/scale.raphael.js
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* ScaleRaphael 0.8 by Zevan Rosser 2010
|
||||
* For use with Raphael library : www.raphaeljs.com
|
||||
* Licensed under the MIT license.
|
||||
*
|
||||
* www.shapevent.com/scaleraphael/
|
||||
*/
|
||||
(function(){
|
||||
window.ScaleRaphael = function(container, width, height){
|
||||
var wrapper = document.getElementById(container);
|
||||
if (!wrapper.style.position) wrapper.style.position = "relative";
|
||||
wrapper.style.width = width + "px";
|
||||
wrapper.style.height = height + "px";
|
||||
wrapper.style.overflow = "hidden";
|
||||
var nestedWrapper;
|
||||
|
||||
if (Raphael.type == "VML"){
|
||||
wrapper.innerHTML = "<rvml:group style='position : absolute; width: 1000px; height: 1000px; top: 0px; left: 0px' coordsize='1000,1000' class='rvml' id='vmlgroup'><\/rvml:group>";
|
||||
nestedWrapper = document.getElementById("vmlgroup");
|
||||
}else{
|
||||
wrapper.innerHTML = "<div id='svggroup'><\/div>";
|
||||
nestedWrapper = document.getElementById("svggroup");
|
||||
}
|
||||
|
||||
var paper = new Raphael(nestedWrapper, width, height);
|
||||
var vmlDiv;
|
||||
|
||||
if (Raphael.type == "SVG"){
|
||||
paper.canvas.setAttribute("viewBox", "0 0 "+width+" "+height);
|
||||
}else{
|
||||
vmlDiv = wrapper.getElementsByTagName("div")[0];
|
||||
}
|
||||
|
||||
paper.changeSize = function(w, h, center, clipping){
|
||||
clipping = !clipping;
|
||||
|
||||
var ratioW = w / width;
|
||||
var ratioH = h / height;
|
||||
var scale = ratioW < ratioH ? ratioW : ratioH;
|
||||
|
||||
var newHeight = parseInt(height * scale);
|
||||
var newWidth = parseInt(width * scale);
|
||||
|
||||
if (Raphael.type == "VML"){
|
||||
// scale the textpaths
|
||||
var txt = document.getElementsByTagName("textpath");
|
||||
for (var i in txt){
|
||||
var curr = txt[i];
|
||||
if (curr.style){
|
||||
if(!curr._fontSize){
|
||||
var mod = curr.style.font.split("px");
|
||||
curr._fontSize = parseInt(mod[0]);
|
||||
curr._font = mod[1];
|
||||
}
|
||||
curr.style.font = curr._fontSize * scale + "px" + curr._font;
|
||||
}
|
||||
}
|
||||
var newSize;
|
||||
|
||||
if (newWidth < newHeight){
|
||||
newSize = newWidth * 1000 / width;
|
||||
}else{
|
||||
newSize = newHeight * 1000 / height;
|
||||
}
|
||||
newSize = parseInt(newSize);
|
||||
nestedWrapper.style.width = newSize + "px";
|
||||
nestedWrapper.style.height = newSize + "px";
|
||||
if (clipping){
|
||||
nestedWrapper.style.left = parseInt((w - newWidth) / 2) + "px";
|
||||
nestedWrapper.style.top = parseInt((h - newHeight) / 2) + "px";
|
||||
}
|
||||
vmlDiv.style.overflow = "visible";
|
||||
}
|
||||
|
||||
if (clipping){
|
||||
newWidth = w;
|
||||
newHeight = h;
|
||||
}
|
||||
|
||||
wrapper.style.width = newWidth + "px";
|
||||
wrapper.style.height = newHeight + "px";
|
||||
paper.setSize(newWidth, newHeight);
|
||||
|
||||
if (center){
|
||||
wrapper.style.position = "absolute";
|
||||
wrapper.style.left = parseInt((w - newWidth) / 2) + "px";
|
||||
wrapper.style.top = parseInt((h - newHeight) / 2) + "px";
|
||||
}
|
||||
}
|
||||
|
||||
paper.scaleAll = function(amount){
|
||||
paper.changeSize(width * amount, height * amount);
|
||||
}
|
||||
|
||||
paper.changeSize(width, height);
|
||||
|
||||
paper.w = width;
|
||||
paper.h = height;
|
||||
|
||||
return paper;
|
||||
}
|
||||
})();
|
72
labs/meteor-client/client/main.coffee
Executable file
72
labs/meteor-client/client/main.coffee
Executable file
@ -0,0 +1,72 @@
|
||||
Template.footer.helpers
|
||||
getFooterString: ->
|
||||
# info = Meteor.call('getServerInfo')
|
||||
year = "YEAR" #info.getBuildYear()
|
||||
month = "MONTH" #info.getBuildMonth()
|
||||
day = "DAY" #info.getBuildDay()
|
||||
version = "VERSION_XXXX" #info.getBuildVersion()
|
||||
copyrightYear = (new Date()).getFullYear()
|
||||
link = "<a href='http://bigbluebutton.org/' target='_blank'>http://bigbluebutton.org</a>"
|
||||
foot = "(c) #{copyrightYear} BigBlueButton Inc. [build #{version}-#{year}-#{month}-#{day}] - For more information visit #{link}"
|
||||
|
||||
Template.header.events
|
||||
"click .usersListIcon": (event) ->
|
||||
toggleUsersList()
|
||||
"click .chatBarIcon": (event) ->
|
||||
toggleChatbar()
|
||||
"click .videoFeedIcon": (event) ->
|
||||
toggleCam @
|
||||
"click .audioFeedIcon": (event) ->
|
||||
toggleVoiceCall @
|
||||
"click .muteIcon": (event) ->
|
||||
toggleMic @
|
||||
"click .signOutIcon": (event) ->
|
||||
userLogout getInSession("meetingId"), getInSession("userId"), true
|
||||
"click .hideNavbarIcon": (event) ->
|
||||
toggleNavbar()
|
||||
"click .settingsIcon": (event) ->
|
||||
alert "settings"
|
||||
"click .raiseHand": (event) ->
|
||||
Meteor.call('userRaiseHand', getInSession("meetingId"), @id)
|
||||
"click .lowerHand": (event) ->
|
||||
# loweredBy = @id # TODO! this must be the userid of the person lowering the hand - instructor/student
|
||||
loweredBy = getInSession("userId")
|
||||
Meteor.call('userLowerHand', getInSession("meetingId"), @id, loweredBy)
|
||||
"click .whiteboardIcon": (event) ->
|
||||
toggleWhiteBoard()
|
||||
|
||||
Template.recordingStatus.rendered = ->
|
||||
$('button[rel=tooltip]').tooltip()
|
||||
|
||||
Template.makeButton.rendered = ->
|
||||
$('button[rel=tooltip]').tooltip()
|
||||
|
||||
# Gets called last in main template, just an easy place to print stuff out
|
||||
Handlebars.registerHelper "doFinalStuff", ->
|
||||
console.log "-----Doing Final Stuff-----"
|
||||
|
||||
# These settings can just be stored locally in session, created at start up
|
||||
Meteor.startup ->
|
||||
@SessionAmplify = _.extend({}, Session,
|
||||
keys: _.object(_.map(amplify.store(), (value, key) ->
|
||||
[
|
||||
key
|
||||
JSON.stringify(value)
|
||||
]
|
||||
))
|
||||
set: (key, value) ->
|
||||
Session.set.apply this, arguments
|
||||
amplify.store key, value
|
||||
return
|
||||
)
|
||||
|
||||
setInSession "display_usersList", true
|
||||
setInSession "display_navbar", true
|
||||
setInSession "display_chatbar", true
|
||||
setInSession "display_whiteboard", true
|
||||
setInSession "display_chatPane", true
|
||||
setInSession 'inChatWith', "PUBLIC_CHAT"
|
||||
setInSession "joinedAt", getTime()
|
||||
setInSession "isSharingAudio", false
|
||||
setInSession "inChatWith", 'PUBLIC_CHAT'
|
||||
setInSession "messageFontSize", 12
|
107
labs/meteor-client/client/main.html
Executable file
107
labs/meteor-client/client/main.html
Executable file
@ -0,0 +1,107 @@
|
||||
<template name="footer">
|
||||
<div class="myFooter gradientBar navbar navbar-default navbar-fixed-bottom" role="navigation">
|
||||
{{{getFooterString}}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="header">
|
||||
{{#if getInSession "display_navbar"}}
|
||||
<div id="navbar" class="myNavbar gradientBar navbar navbar-default navbar-fixed-top" role="navigation">
|
||||
{{#with getCurrentUser}}
|
||||
<div class="navbarUserButtons navbarSection">
|
||||
<!-- display/hide users list toggle -->
|
||||
{{#if getInSession "display_usersList"}}
|
||||
{{> makeButton id=userId btn_class="navbarIconToggleActive usersListIcon navbarButton" i_class="user" _id=_id rel="tooltip" data_placement="bottom" title="Hide List of Users"}}
|
||||
{{else}}
|
||||
{{> makeButton id=userId btn_class="usersListIcon navbarButton" i_class="user" _id=_id rel="tooltip" data_placement="bottom" title="Show List of Users"}}
|
||||
{{/if}}
|
||||
|
||||
<!-- display/hide whiteboard toggle -->
|
||||
{{#if getInSession "display_whiteboard"}}
|
||||
{{> makeButton btn_class="navbarIconToggleActive whiteboardIcon navbarButton" i_class="pencil" _id=_id rel="tooltip" data_placement="bottom" title="Hide Whiteboard"}}
|
||||
{{else}}
|
||||
{{> makeButton id=userId btn_class="whiteboardIcon navbarButton" i_class="pencil" _id=_id rel="tooltip" data_placement="bottom" title="Show Whiteboard"}}
|
||||
{{/if}}
|
||||
|
||||
<!-- display/hide chat bar toggle -->
|
||||
{{#if getInSession "display_chatbar"}}
|
||||
{{> makeButton id=userId btn_class="navbarIconToggleActive chatBarIcon navbarButton" i_class="comment" _id=_id rel="tooltip" data_placement="bottom" title="Hide Message Pane"}}
|
||||
{{else}}
|
||||
{{> makeButton id=userId btn_class="chatBarIcon navbarButton" i_class="comment" _id=_id rel="tooltip" data_placement="bottom" title="Show Message Pane"}}
|
||||
{{/if}}
|
||||
|
||||
<!-- display/hide webcam streams toggle -->
|
||||
{{#if isUserSharingVideo user}}
|
||||
{{> makeButton id=userId btn_class="navbarIconToggleActive videoFeedIcon navbarButton" i_class="stop" sharingVideo=true _id=_id rel="tooltip" data_placement="bottom" title="Hide Webcams"}}
|
||||
{{else}}
|
||||
{{> makeButton id=userId btn_class="videoFeedIcon navbarButton" i_class="facetime-video" sharingVideo=false _id=_id rel="tooltip" data_placement="bottom" title="Show Webcams"}}
|
||||
{{/if}}
|
||||
|
||||
<!-- Join/hang up audio call -->
|
||||
{{#if isUserSharingAudio user}}
|
||||
{{> makeButton id=userId btn_class="navbarIconToggleActive audioFeedIcon navbarButton" i_class="volume-off" sharingAudio=true _id=_id rel="tooltip" data_placement="bottom" title="Leave Audio Call"}}
|
||||
{{else}}
|
||||
{{> makeButton id=userId btn_class="audioFeedIcon navbarButton" i_class="headphones" sharingAudio=false _id=_id rel="tooltip" data_placement="bottom" title="Join Audio Call"}}
|
||||
{{/if}}
|
||||
|
||||
<!-- Mute/unmute user -->
|
||||
{{#if isUserSharingAudio user}}
|
||||
<!-- Should use much more noticeable icons than just bootstraps volume-up & volume-down to differentiate talking but it is good for now -->
|
||||
{{#if isUserMuted user}}
|
||||
{{> makeButton id=userid btn_class="muteIcon navbarButton" i_class="volume-off" sharingAudio=true _id=_id rel="tooltip" data_placement="bottom" title="Unute"}}
|
||||
{{else}}
|
||||
{{#if isUserTalking user}}
|
||||
{{> makeButton id=userid btn_class="navbarIconToggleActive muteIcon navbarButton" i_class="volume-up" sharingAudio=true _id=_id rel="tooltip" data_placement="bottom" title="Mute"}}
|
||||
{{else}}
|
||||
{{> makeButton id=userId btn_class="navbarIconToggleActive muteIcon navbarButton" i_class="volume-down" sharingAudio=true _id=_id rel="tooltip" data_placement="bottom" title="Mute"}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if user.raise_hand}}
|
||||
{{> makeButton id=userId btn_class="lowerHand navbarIconToggleActive navbarButton" i_class="hand-up" rel="tooltip" data_placement="bottom" title="Lower your hand"}}
|
||||
{{else}}
|
||||
{{> makeButton id=userId btn_class="raiseHand navbarButton" i_class="hand-up" rel="tooltip" data_placement="bottom" title="Raise your hand"}}
|
||||
{{/if}}
|
||||
|
||||
{{> recordingStatus}}
|
||||
</div>
|
||||
<div class="navbarTitle navbarSection"><span>{{getMeetingName}}</span></div>
|
||||
<div class="navbarSettingsButtons navbarSection">
|
||||
{{> makeButton id="userId" btn_class="settingsIcon navbarButton" i_class="cog" rel="tooltip" data_placement="bottom" title="Settings"}}
|
||||
{{> makeButton id="userId" btn_class="signOutIcon navbarButton" i_class="log-out" rel="tooltip" data_placement="bottom" title="Logout"}}
|
||||
{{> makeButton id="" btn_class="hideNavbarIcon navbarButton" i_class="chevron-up" rel="tooltip" data_placement="bottom" title="Hide Navbar"}}
|
||||
</div>
|
||||
{{/with}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div id="navbar" class="myNavbarMinimized navbar-default navbar-static-top" role="navigation">
|
||||
<!-- User wants to hide navbar. The button for bringing the navbar back needs to still be available. Perhaps it should appear/disappear in the future on hover? Something to add later. -->
|
||||
{{> makeButton btn_class="hideNavbarIcon navbarMinimizedButton" i_class="chevron-down" rel="tooltip" data_placement="bottom" title="Display Navbar"}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</template>
|
||||
|
||||
<template name="main">
|
||||
<head><title>BigBlueButton Meteor</title></head>
|
||||
<body>
|
||||
<div id="main" class="mainContainer row-fluid">
|
||||
<div>{{> header}}</div>
|
||||
{{> usersList id="users" name="usersList"}}
|
||||
{{> whiteboard id="whiteboard" title="Whiteboard" name="whiteboard"}}
|
||||
{{> chatbar id="chat" title="Chat" name="chatbar"}}
|
||||
<audio id="remote-media" autoplay="autoplay"></audio>
|
||||
{{> footer}}
|
||||
{{doFinalStuff}}
|
||||
</div>
|
||||
</body>
|
||||
</template>
|
||||
|
||||
<template name="recordingStatus">
|
||||
<!-- Recording status of the meeting -->
|
||||
{{#if meetingIsRecording}}
|
||||
<button class="recordingStatus recordingStatusTrue" rel="tooltip" data-placement="bottom" title="This Meeting is Being Recorded"><span class="glyphicon glyphicon-record"></span> Recording</button>
|
||||
{{else}}
|
||||
<button class="recordingStatus recordingStatusFalse" rel="tooltip" data-placement="bottom" title="This Meeting is Not Being Recorded"><span class="glyphicon glyphicon-record"></span></button>
|
||||
{{/if}}
|
||||
</template>
|
93
labs/meteor-client/client/stylesheets/chat.less
Executable file
93
labs/meteor-client/client/stylesheets/chat.less
Executable file
@ -0,0 +1,93 @@
|
||||
.chat{
|
||||
list-style: none;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
#chat {
|
||||
background-color: #f5f5f5;
|
||||
border: 1px solid #ccc;
|
||||
float: left;
|
||||
min-height:500px;
|
||||
min-width:300px;
|
||||
overflow: hidden;
|
||||
width: 25%;
|
||||
}
|
||||
.chat li{
|
||||
border-bottom: 1px dotted #B3A9A9;
|
||||
margin: 0px;
|
||||
padding-bottom: 5px;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
padding-top: 5px;
|
||||
word-wrap:break-word;
|
||||
}
|
||||
.chat li:nth-child(even) {
|
||||
background-color: rgb(245,245,245);
|
||||
}
|
||||
#chatbar-contents{
|
||||
background-color: #fff;
|
||||
height:90%;
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
width:100%;
|
||||
}
|
||||
#chatbody{
|
||||
height:80%;
|
||||
overflow-y: scroll;
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
}
|
||||
.chatGreeting {
|
||||
color: blue;
|
||||
margin-top:5px;
|
||||
}
|
||||
.chat-input-wrapper {
|
||||
margin-left:20px;
|
||||
}
|
||||
#chat-options-bar {
|
||||
border-bottom:1px solid #ccc;
|
||||
position:relative;
|
||||
width:100%;
|
||||
}
|
||||
#chat-user-list {
|
||||
padding:5px;
|
||||
}
|
||||
#newMessageInput {
|
||||
height:40px;
|
||||
width: 80%;
|
||||
margin-top:6px;
|
||||
padding:5px;
|
||||
}
|
||||
.optionsBar{
|
||||
padding-top:15px;
|
||||
padding-left: 15px;
|
||||
height:100%;
|
||||
}
|
||||
.panel-footer{
|
||||
padding-top:0px;
|
||||
position:relative;
|
||||
bottom:0px;
|
||||
}
|
||||
.privateChatTab{}
|
||||
.publicChatTab{}
|
||||
.private-chat-user-box {
|
||||
border: solid 1px grey;
|
||||
padding: 5px;
|
||||
width: 90%;
|
||||
height:60%;
|
||||
}
|
||||
.private-chat-user-entry {}
|
||||
.private-chat-user-list {
|
||||
font-size:12px;
|
||||
}
|
||||
.private-chat-user-list :hover {
|
||||
background: #0099FF;
|
||||
font-size:14px;
|
||||
font-style:italic;
|
||||
}
|
||||
#sendMessageButton{
|
||||
height:40px;
|
||||
margin-left: 10px;
|
||||
background-color: #D7D7D7
|
||||
}
|
||||
.tab{}
|
123
labs/meteor-client/client/stylesheets/style.css
Executable file
123
labs/meteor-client/client/stylesheets/style.css
Executable file
@ -0,0 +1,123 @@
|
||||
.audioFeedIcon{}
|
||||
body {
|
||||
background: #eee;
|
||||
bottom:0;
|
||||
color: #666666;
|
||||
height:100%;
|
||||
left:0;
|
||||
position:absolute;
|
||||
right:0;
|
||||
top:0;
|
||||
}
|
||||
.btn{
|
||||
background-color:#F5F5F5;
|
||||
}
|
||||
.chatBarIcon{}
|
||||
.component{
|
||||
background: #fff;
|
||||
border: 1px solid #ccc;
|
||||
float: left;
|
||||
height:90%;
|
||||
}
|
||||
.gradientBar{
|
||||
background: -webkit-linear-gradient(rgb(255,255,255), rgb(182,181,181)); /* For Safari 5.1 to 6.0 */
|
||||
background: -o-linear-gradient( rgb(255,255,255), rgb(182,181,181)); /* For Opera 11.1 to 12.0 */
|
||||
background: -moz-linear-gradient( rgb(255,255,255), rgb(182,181,181)); /* For Firefox 3.6 to 15 */
|
||||
background: linear-gradient( rgb(255,255,255), rgb(182,181,181)); /* Standard syntax (must be last) */
|
||||
}
|
||||
.hideNavbarIcon{}
|
||||
.lowerHand{}
|
||||
.mainContainer{
|
||||
height:100%;
|
||||
padding-top:60px;
|
||||
}
|
||||
.myFooter{
|
||||
color:black;
|
||||
font-size: 12px;
|
||||
min-height: 0px;
|
||||
max-height: 30px;
|
||||
}
|
||||
.myNavbar{
|
||||
margin-bottom:0.5%;
|
||||
min-width:900px;
|
||||
padding-top:5px;
|
||||
}
|
||||
.myNavbarMinimized{
|
||||
background: #eee;
|
||||
height:20px;
|
||||
margin-bottom:0.2%;
|
||||
margin-top:0px;
|
||||
min-width:900px;
|
||||
padding-top:0px;
|
||||
text-align:right;
|
||||
}
|
||||
.navbarButton {
|
||||
height:40px;
|
||||
margin-left: 2px;
|
||||
margin-right: 2px;
|
||||
width:40px;
|
||||
}
|
||||
.navbarIconToggleActive{
|
||||
border-bottom: 4px solid lightblue;
|
||||
}
|
||||
.navbarMinimizedButton{
|
||||
height:20px;
|
||||
margin-bottom:0px;
|
||||
margin-left: 2px;
|
||||
margin-right: 20px;
|
||||
margin-top:0px;
|
||||
width:40px;
|
||||
}
|
||||
.navbarSection{
|
||||
float:left;
|
||||
min-width:300px;
|
||||
width:33%;
|
||||
}
|
||||
.navbarSettingsButtons{
|
||||
text-align: right;
|
||||
}
|
||||
.navbarTitle{
|
||||
font-size: 16px;
|
||||
font-weight:bold;
|
||||
padding-top:15px;
|
||||
text-align:center;
|
||||
}
|
||||
.navbarUserButtons{
|
||||
padding-left:0.5%;
|
||||
}
|
||||
.raiseHand {}
|
||||
.recordingStatus{
|
||||
background:none!important;
|
||||
border:none;
|
||||
padding:0!important;
|
||||
margin-left: 10px;
|
||||
}
|
||||
.recordingStatusTrue{
|
||||
color:green;
|
||||
}
|
||||
.recordingStatusFalse{
|
||||
color:maroon;
|
||||
}
|
||||
.ScrollableWindow {
|
||||
height: 100%;
|
||||
overflow-y: scroll;
|
||||
padding-right:5px;
|
||||
}
|
||||
.settingsIcon{}
|
||||
.signOutIcon{}
|
||||
.tab{
|
||||
height:40px;
|
||||
}
|
||||
.title {
|
||||
border-bottom: 1px solid #d7d7d7;
|
||||
color: #666;
|
||||
font-size:14px;
|
||||
font-weight: bold;
|
||||
line-height: 2em;
|
||||
margin:0;
|
||||
padding-bottom: 5px;
|
||||
padding-left: 10px;
|
||||
padding-top: 5px;
|
||||
}
|
||||
.usersListIcon{}
|
||||
.videoFeedIcon{}
|
25
labs/meteor-client/client/stylesheets/users.less
Executable file
25
labs/meteor-client/client/stylesheets/users.less
Executable file
@ -0,0 +1,25 @@
|
||||
#users {
|
||||
margin-left: 0.5%;
|
||||
min-width:230px;
|
||||
padding-bottom: 10px;
|
||||
width: 12%;
|
||||
}
|
||||
#user-contents {
|
||||
padding-bottom: 10px;
|
||||
height:95%;
|
||||
}
|
||||
.userNameEntry{
|
||||
height:20px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width: 90px;
|
||||
}
|
||||
.user-entry tr:nth-child(even) {background-color: rgb(245,245,245);}
|
||||
.user-entry tr:hover {font-weight: bold;}
|
||||
.userListSettingIcon {}
|
||||
|
||||
.userNameContainer{
|
||||
border-right: 1px solid #ccc;
|
||||
padding-right:0px;
|
||||
}
|
11
labs/meteor-client/client/stylesheets/whiteboard.less
Executable file
11
labs/meteor-client/client/stylesheets/whiteboard.less
Executable file
@ -0,0 +1,11 @@
|
||||
#whiteboard {
|
||||
margin-left: 0.5%;
|
||||
margin-right: 0.5%;
|
||||
padding: 5px;
|
||||
width:61%;
|
||||
}
|
||||
|
||||
#whiteboard-paper {
|
||||
background-color:grey;
|
||||
width: 100%;
|
||||
}
|
197
labs/meteor-client/client/views/chat/chat_bar.coffee
Executable file
197
labs/meteor-client/client/views/chat/chat_bar.coffee
Executable file
@ -0,0 +1,197 @@
|
||||
@sendMessage = ->
|
||||
message = linkify $('#newMessageInput').val() # get the message from the input box
|
||||
unless (message?.length > 0 and (/\S/.test(message))) # check the message has content and it is not whitespace
|
||||
return # do nothing if invalid message
|
||||
|
||||
chattingWith = getInSession('inChatWith')
|
||||
|
||||
if chattingWith isnt "PUBLIC_CHAT"
|
||||
dest = Meteor.Users.findOne("userId": chattingWith)
|
||||
|
||||
messageForServer = { # construct message for server
|
||||
"message": message
|
||||
"chat_type": if chattingWith is "PUBLIC_CHAT" then "PUBLIC_CHAT" else "PRIVATE_CHAT"
|
||||
"from_userid": getInSession("userId")
|
||||
"from_username": getUsersName()
|
||||
"from_tz_offset": "240"
|
||||
"to_username": if chattingWith is "PUBLIC_CHAT" then "public_chat_username" else dest.user.name
|
||||
"to_userid": if chattingWith is "PUBLIC_CHAT" then "public_chat_userid" else chattingWith
|
||||
"from_lang": "en"
|
||||
"from_time": getTime()
|
||||
"from_color": "0x000000"
|
||||
# "from_color": "0x#{getInSession("messageColor")}"
|
||||
}
|
||||
|
||||
Meteor.call "sendChatMessagetoServer", getInSession("meetingId"), messageForServer
|
||||
$('#newMessageInput').val '' # Clear message box
|
||||
|
||||
Template.chatInput.events
|
||||
'click #sendMessageButton': (event) ->
|
||||
sendMessage()
|
||||
'keypress #newMessageInput': (event) -> # user pressed a button inside the chatbox
|
||||
if event.which is 13 # Check for pressing enter to submit message
|
||||
sendMessage()
|
||||
|
||||
Template.chatInput.rendered = ->
|
||||
$('input[rel=tooltip]').tooltip()
|
||||
$('button[rel=tooltip]').tooltip()
|
||||
|
||||
Template.chatbar.helpers
|
||||
getChatGreeting: ->
|
||||
greeting = "Welcome to #{getMeetingName()}!\n\n
|
||||
For help on using BigBlueButton see these (short) <a href='http://www.bigbluebutton.org/videos/' target='_blank'>tutorial videos</a>.\n\n
|
||||
To join the audio bridge click the headset icon (upper-left hand corner). Use a headset to avoid causing background noise for others.\n\n\n
|
||||
This server is running BigBlueButton #{getInSession 'bbbServerVersion'}."
|
||||
|
||||
# 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
|
||||
getFormattedMessagesForChat: () ->
|
||||
friend = chattingWith = getInSession('inChatWith') # the recipient(s) of the messages
|
||||
after = before = greeting = []
|
||||
|
||||
if chattingWith is 'PUBLIC_CHAT' # find all public messages
|
||||
before = Meteor.Chat.find({'message.chat_type': chattingWith, 'message.from_time': {$lt: String(getInSession("joinedAt"))}}).fetch()
|
||||
after = Meteor.Chat.find({'message.chat_type': chattingWith, 'message.from_time': {$gt: String(getInSession("joinedAt"))}}).fetch()
|
||||
|
||||
greeting = [
|
||||
'message':
|
||||
'message': Template.chatbar.getChatGreeting(),
|
||||
'from_username': 'System',
|
||||
'from_time': getTime()
|
||||
'from_color': '0x3399FF' # A nice blue in hex
|
||||
]
|
||||
else
|
||||
me = getInSession("userId")
|
||||
after = Meteor.Chat.find({ # find all messages between current user and recipient
|
||||
'message.chat_type': 'PRIVATE_CHAT',
|
||||
$or: [{'message.from_userid': me, 'message.to_userid': friend},{'message.from_userid': friend, 'message.to_userid': me}]
|
||||
}).fetch()
|
||||
|
||||
messages = (before.concat greeting).concat after
|
||||
|
||||
getCombinedMessagesForChat: ->
|
||||
msgs = Template.chatbar.getFormattedMessagesForChat()
|
||||
prev_time = msgs[0].message.from_time
|
||||
prev_userid = msgs[0].message.from_userid
|
||||
for i in [0...msgs.length]
|
||||
if i != 0
|
||||
if prev_userid is msgs[i].message.from_userid
|
||||
msgs[i].message.from_username = ''
|
||||
if Template.message.toClockTime(msgs[i].message.from_time) is Template.message.toClockTime(prev_time)
|
||||
prev_time = msgs[i].message.from_time
|
||||
msgs[i].message.from_time = null
|
||||
else
|
||||
prev_time = msgs[i].message.from_time
|
||||
else
|
||||
prev_time = msgs[i].message.from_time
|
||||
prev_userid = msgs[i].message.from_userid
|
||||
msgs
|
||||
|
||||
Template.message.rendered = -> # When a message has been added and finished rendering, scroll to the bottom of the chat
|
||||
$('#chatbody').scrollTop($('#chatbody')[0].scrollHeight)
|
||||
|
||||
Template.optionsBar.events
|
||||
'click .private-chat-user-entry': (event) -> # clicked a user's name to begin private chat
|
||||
setInSession 'display_chatPane', true
|
||||
setInSession "inChatWith", @userId
|
||||
me = getInSession("userId")
|
||||
|
||||
if Meteor.Chat.find({'message.chat_type': 'PRIVATE_CHAT', $or: [{'message.from_userid': me, 'message.to_userid': @userId},{'message.from_userid': @userId, 'message.to_userid': me}]}).fetch().length is 0
|
||||
messageForServer =
|
||||
"message": "#{getUsersName()} has joined private chat with #{@user.name}."
|
||||
"chat_type": "PRIVATE_CHAT"
|
||||
"from_userid": me
|
||||
"from_username": getUsersName()
|
||||
"from_tz_offset": "240"
|
||||
"to_username": @user.name
|
||||
"to_userid": @userId
|
||||
"from_lang": "en"
|
||||
"from_time": getTime()
|
||||
"from_color": "0"
|
||||
Meteor.call "sendChatMessagetoServer", getInSession("meetingId"), messageForServer
|
||||
|
||||
Template.optionsBar.rendered = ->
|
||||
$('div[rel=tooltip]').tooltip()
|
||||
|
||||
Template.optionsFontSize.events
|
||||
"click .fontSizeSelector": (event) ->
|
||||
selectedFontSize = parseInt(event.target.id)
|
||||
if selectedFontSize
|
||||
setInSession "messageFontSize", selectedFontSize
|
||||
else setInSession "messageFontSize", 12
|
||||
|
||||
Template.tabButtons.events
|
||||
'click .close': (event) -> # user closes private chat
|
||||
setInSession 'inChatWith', 'PUBLIC_CHAT'
|
||||
setInSession 'display_chatPane', true
|
||||
Meteor.call("deletePrivateChatMessages", getInSession("userId"), @userId)
|
||||
return false # stops propogation/prevents default
|
||||
|
||||
'click .optionsChatTab': (event) ->
|
||||
setInSession 'display_chatPane', false
|
||||
|
||||
'click .privateChatTab': (event) ->
|
||||
setInSession 'display_chatPane', true
|
||||
console.log ".private"
|
||||
|
||||
'click .publicChatTab': (event) ->
|
||||
setInSession 'display_chatPane', true
|
||||
|
||||
'click .tab': (event) ->
|
||||
setInSession "inChatWith", @userId
|
||||
|
||||
Template.tabButtons.helpers
|
||||
getChatbarTabs: ->
|
||||
tabs = makeTabs()
|
||||
|
||||
makeTabButton: -> # create tab button for private chat or other such as options
|
||||
button = '<li '
|
||||
button += 'class="'
|
||||
button += 'active ' if getInSession("inChatWith") is @userId
|
||||
button += "tab #{@class}\"><a href=\"#\" data-toggle=\"tab\">#{@name}"
|
||||
button += ' <button class="close closeTab" type="button" >×</button>' if @class is 'privateChatTab'
|
||||
button += '</a></li>'
|
||||
button
|
||||
|
||||
Template.message.helpers
|
||||
activateBreakLines: (str) ->
|
||||
res = str
|
||||
res = res.replace /\n/gim, '<br/>'
|
||||
res = res.replace /\r/gim, '<br/>'
|
||||
|
||||
getHexColor: (c) ->
|
||||
if parseInt(c).toString(16).length is 4
|
||||
"#00#{parseInt(c).toString(16)}"
|
||||
else
|
||||
"##{parseInt(c).toString(16)}"
|
||||
|
||||
# make links received from Flash client clickable in HTML
|
||||
toClickable: (str) ->
|
||||
res = str
|
||||
# res = str.replace /<a href='event:/gim, "<a target='_blank' href='"
|
||||
# res = res.replace /<a>/gim, '</a>'
|
||||
|
||||
# res = res.replace /<u>/gim, '<u>'
|
||||
# res = res.replace /<\/u>/gim, '</u>'
|
||||
res
|
||||
|
||||
toClockTime: (epochTime) ->
|
||||
if epochTime is null
|
||||
return ""
|
||||
local = new Date()
|
||||
offset = local.getTimezoneOffset()
|
||||
epochTime = epochTime - offset * 60000 # 1 min = 60 s = 60,000 ms
|
||||
dateObj = new Date(epochTime)
|
||||
hours = dateObj.getUTCHours()
|
||||
minutes = dateObj.getUTCMinutes()
|
||||
if minutes < 10
|
||||
minutes = "0" + minutes
|
||||
hours + ":" + minutes
|
||||
|
||||
sanitizeAndFormat: (str) ->
|
||||
res = str
|
||||
# First, replace replace all tags with the ascii equivalent
|
||||
res = res.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
|
||||
res = Template.message.toClickable(res)
|
||||
res = Template.message.activateBreakLines(res)
|
107
labs/meteor-client/client/views/chat/chat_bar.html
Executable file
107
labs/meteor-client/client/views/chat/chat_bar.html
Executable file
@ -0,0 +1,107 @@
|
||||
<template name="chatbar">
|
||||
<div id="{{id}}" {{visibility name}} class="component">
|
||||
<div id="chatbar-contents">
|
||||
<h3 class="title gradientBar"><span class="glyphicon glyphicon-comment"></span> {{title}}</h3>
|
||||
<div style="overflow-y: scroll; height:40px">{{>tabButtons}}</div>
|
||||
{{#if getInSession "display_chatPane"}}
|
||||
<div id="chatbody">
|
||||
<ul class="chat">
|
||||
{{#each getCombinedMessagesForChat}}
|
||||
<li {{messageFontSize}}>{{> message}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="panel-footer">{{> chatInput}}</div>
|
||||
{{else}}
|
||||
{{> optionsBar}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="chatInput">
|
||||
<div class="chat-input-wrapper">
|
||||
<input type="text" id="newMessageInput" placeholder="Write a message..." rel="tooltip" data-placement="top" title="Write a new message" />
|
||||
<button type="submit" id="sendMessageButton" class="btn" rel="tooltip" data-placement="top" title="Click to send your message">
|
||||
Send
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="chatOptions">
|
||||
<p>Chat Options:</p>
|
||||
{{> optionsFontSize}}
|
||||
</template>
|
||||
|
||||
<!-- Displays and styles an individual message in the chat -->
|
||||
<template name="message">
|
||||
<table style="width:100%">
|
||||
<tr>
|
||||
<td>
|
||||
{{#if message.from_username}}
|
||||
<div class="userNameEntry" rel="tooltip" data-placement="bottom" title="{{message.from_username}}">
|
||||
<strong>{{message.from_username}}</strong>
|
||||
</div>
|
||||
{{/if}}
|
||||
</td>
|
||||
<td style="text-align:right">
|
||||
{{#if message.from_time}}<small class="pull-right">{{toClockTime message.from_time}} <span class="glyphicon glyphicon-time"></span></small>{{/if}}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div style="color:{{getHexColor message.from_color}}">{{{sanitizeAndFormat message.message}}}</div> <!-- Messages must be safely filtered and stripped -->
|
||||
</template>
|
||||
|
||||
<!-- Displays the list of options available -->
|
||||
<template name="optionsBar">
|
||||
<div class="optionsBar">
|
||||
<p>Select a person to chat with privately</p>
|
||||
<div class="private-chat-user-box" rel="tooltip" data-placement="top" title="Select a participant to open a private chat">
|
||||
{{#Layout template="scrollWindow" id="privateChatUserScrollWindow"}}
|
||||
{{#contentFor region="scrollContents"}}
|
||||
<table class="table table-hover">
|
||||
<tbody class="private-chat-user-list">
|
||||
{{#each getUsersInMeeting}}
|
||||
<tr class="private-chat-user-entry">
|
||||
{{#unless isCurrentUser userId}}
|
||||
{{user.name}}
|
||||
{{/unless}}
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{/contentFor}}
|
||||
{{/Layout}}
|
||||
</div>
|
||||
<br/>
|
||||
{{> chatOptions}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="optionsFontSize">
|
||||
<div class="dropdown" style="float:left">
|
||||
<span {{messageFontSize}}>Chat Message Font Size: </span>
|
||||
<button class="btn btn-default dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown">
|
||||
Font Size ({{getInSession "messageFontSize"}})
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
|
||||
<ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu1" style="height:80px; overflow-y:scroll; right:0px;">
|
||||
<li role="presentation"><a class="fontSizeSelector" id="8" role="menuitem" tabindex="-1" href="#">8</a></li>
|
||||
<li role="presentation"><a class="fontSizeSelector" id="10" role="menuitem" tabindex="-1" href="#">10</a></li>
|
||||
<li role="presentation"><a class="fontSizeSelector" id="12" role="menuitem" tabindex="-1" href="#">12</a></li>
|
||||
<li role="presentation"><a class="fontSizeSelector" id="14" role="menuitem" tabindex="-1" href="#">14</a></li>
|
||||
<li role="presentation"><a class="fontSizeSelector" id="16" role="menuitem" tabindex="-1" href="#">16</a></li>
|
||||
<li role="presentation"><a class="fontSizeSelector" id="18" role="menuitem" tabindex="-1" href="#">18</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Display buttons on the chat tab, public, options, and all the private chat tabs -->
|
||||
<template name="tabButtons">
|
||||
<ul id="tabButtonContainer" class="nav nav-tabs">
|
||||
{{#each getChatbarTabs}}
|
||||
{{{makeTabButton}}}
|
||||
{{/each}}
|
||||
</ul>
|
||||
</template>
|
3
labs/meteor-client/client/views/layout.html
Executable file
3
labs/meteor-client/client/views/layout.html
Executable file
@ -0,0 +1,3 @@
|
||||
<template name="layout">
|
||||
{{> yield}}
|
||||
</template>
|
10
labs/meteor-client/client/views/logout/logout.html
Executable file
10
labs/meteor-client/client/views/logout/logout.html
Executable file
@ -0,0 +1,10 @@
|
||||
<template name="logout">
|
||||
<head>
|
||||
<title>BigBlueButton</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="main" class="row-fluid">
|
||||
<h2>You have been logged out.</h2>
|
||||
</div>
|
||||
</body>
|
||||
</template>
|
21
labs/meteor-client/client/views/sharedTemplates.html
Executable file
21
labs/meteor-client/client/views/sharedTemplates.html
Executable file
@ -0,0 +1,21 @@
|
||||
<template name="scrollWindow">
|
||||
{{> yield}}
|
||||
<div class="ScrollableWindow" id="{{id}}">
|
||||
{{> yield region="scrollContents"}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="makeButton">
|
||||
<button type="submit" id="{{id}}" class="{{btn_class}} btn" {{isDisabled}} rel="{{rel}}" data-placement="{{data_placement}}" title="{{title}}">
|
||||
<i class="glyphicon glyphicon-{{i_class}}"></i>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<template name="moduleTable">
|
||||
{{> yield}}
|
||||
<table class="{{tableClass}}">
|
||||
<tbody class="{{tbodyClass}}">
|
||||
{{> yield region="Contents"}}
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
0
labs/meteor-client/client/views/users/user_item.coffee
Executable file
0
labs/meteor-client/client/views/users/user_item.coffee
Executable file
41
labs/meteor-client/client/views/users/user_item.html
Executable file
41
labs/meteor-client/client/views/users/user_item.html
Executable file
@ -0,0 +1,41 @@
|
||||
<template name="displayUserIcons">
|
||||
<td>{{#if isUserSharingVideo user}}
|
||||
<span class="userListSettingIcon glyphicon glyphicon-facetime-video"></span>
|
||||
{{/if}}</td>
|
||||
|
||||
<!-- Should use much more noticeable icons than just bootstrap's volume-up & volume-down to differentiate talking but it is good for now -->
|
||||
<td>{{#if isUserSharingAudio user}}
|
||||
{{#if isUserMuted user}}
|
||||
<span class="userListSettingIcon glyphicon glyphicon-volume-off"></span>
|
||||
{{else}}
|
||||
{{#if isUserTalking user}}
|
||||
<span class="userListSettingIcon glyphicon glyphicon-volume-up"></span>
|
||||
{{else}}
|
||||
<span class="userListSettingIcon glyphicon glyphicon-volume-down"></span>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}</td>
|
||||
|
||||
<td>{{#if user.presenter}}<span class="userListSettingIcon glyphicon glyphicon-picture"></span>{{/if}}</td>
|
||||
|
||||
<td>{{#if user.raise_hand}}<span class="userListSettingIcon glyphicon glyphicon-hand-up"></span>{{/if}}</td>
|
||||
</template>
|
||||
|
||||
<template name="usernameEntry"> <!-- A template now because more information be added/styled -->
|
||||
<td class="userNameContainer">
|
||||
{{#if isCurrentUser userId}}
|
||||
<p class="userNameEntry" rel="tooltip" data-placement="bottom" title="{{user.name}} (you)">
|
||||
<strong>{{user.name}} (you)</strong>
|
||||
</p>
|
||||
{{else}}
|
||||
<p class="userNameEntry" rel="tooltip" data-placement="bottom" title="{{user.name}}">
|
||||
{{user.name}}
|
||||
</p>
|
||||
{{/if}}
|
||||
</td>
|
||||
</template>
|
||||
|
||||
<template name="userItem">
|
||||
{{> usernameEntry}}
|
||||
{{> displayUserIcons}}
|
||||
</template>
|
3
labs/meteor-client/client/views/users/user_list.coffee
Executable file
3
labs/meteor-client/client/views/users/user_list.coffee
Executable file
@ -0,0 +1,3 @@
|
||||
Template.usersList.helpers
|
||||
getMeetingSize: -> # Retreieve the number of users in the chat, or "error" string
|
||||
Meteor.Users.find().count()
|
18
labs/meteor-client/client/views/users/users_list.html
Executable file
18
labs/meteor-client/client/views/users/users_list.html
Executable file
@ -0,0 +1,18 @@
|
||||
<template name="usersList">
|
||||
<div id="{{id}}" {{visibility name}} class="component">
|
||||
<h3 class="title gradientBar"><span class="glyphicon glyphicon-user"></span> Participants: {{getMeetingSize}} Users</h3>
|
||||
<div id="user-contents">
|
||||
{{#Layout template="scrollWindow" id="publicUserScrollWindow"}}
|
||||
{{#contentFor region="scrollContents"}}
|
||||
{{#Layout template="moduleTable" tableClass="table table-hover" tbodyClass="user-entry"}}
|
||||
{{#contentFor region="Contents"}}
|
||||
{{#each getUsersInMeeting}}
|
||||
<tr>{{>userItem}}</tr>
|
||||
{{/each}}
|
||||
{{/contentFor}}
|
||||
{{/Layout}}
|
||||
{{/contentFor}}
|
||||
{{/Layout}}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
66
labs/meteor-client/client/views/whiteboard/slide.coffee
Executable file
66
labs/meteor-client/client/views/whiteboard/slide.coffee
Executable file
@ -0,0 +1,66 @@
|
||||
Template.slide.rendered = ->
|
||||
height = $('#whiteboard').height()
|
||||
console.log "height = #{height}"
|
||||
$('#whiteboard-paper').height((height-$("#whiteboard-navbar").height()-10)+'px')
|
||||
|
||||
currentPresentation = Meteor.Presentations.findOne({"presentation.current": true})
|
||||
presentationId = currentPresentation?.presentation?.id
|
||||
currentSlide = Meteor.Slides.findOne({"presentationId": presentationId, "slide.current": true})
|
||||
if currentSlide?.slide?.png_uri?
|
||||
Template.slide.createWhiteboardPaper (wpm) ->
|
||||
Template.slide.displaySlide wpm
|
||||
|
||||
Template.slide.helpers
|
||||
createWhiteboardPaper: (callback) ->
|
||||
Template.slide.whiteboardPaperModel = new WhiteboardPaperModel('whiteboard-paper')
|
||||
callback(Template.slide.whiteboardPaperModel)
|
||||
|
||||
displaySlide: (wpm) ->
|
||||
currentPresentation = Meteor.Presentations.findOne({"presentation.current": true})
|
||||
presentationId = currentPresentation?.presentation?.id
|
||||
currentSlide = Meteor.Slides.findOne({"presentationId": presentationId, "slide.current": true})
|
||||
|
||||
wpm.create()
|
||||
wpm._displayPage(currentSlide?.slide?.png_uri)
|
||||
|
||||
updatePointerLocation: (pointer) ->
|
||||
wpm = Template.slide.whiteboardPaperModel
|
||||
wpm?.moveCursor(pointer.x, pointer.y)
|
||||
|
||||
manuallyDisplayShapes: ->
|
||||
currentPresentation = Meteor.Presentations.findOne({"presentation.current": true})
|
||||
presentationId = currentPresentation?.presentation?.id
|
||||
currentSlide = Meteor.Slides.findOne({"presentationId": presentationId, "slide.current": true})
|
||||
wpm = Template.slide.whiteboardPaperModel
|
||||
|
||||
shapes = Meteor.Shapes.find({whiteboardId: currentSlide?.slide?.id}).fetch()
|
||||
for s in shapes
|
||||
shapeInfo = s.shape?.shape or s?.shape
|
||||
shapeType = shapeInfo?.type
|
||||
|
||||
if shapeType isnt "text"
|
||||
for num in [0..3] # the coordinates must be in the range 0 to 1
|
||||
shapeInfo?.points[num] = shapeInfo?.points[num] / 100
|
||||
wpm.makeShape(shapeType, shapeInfo)
|
||||
wpm.updateShape(shapeType, shapeInfo)
|
||||
|
||||
|
||||
#### SHAPE ####
|
||||
Template.shape.rendered = ->
|
||||
# @data is the shape object coming from the {{#each}} in the html file
|
||||
shapeInfo = @data.shape?.shape or @data.shape
|
||||
shapeType = shapeInfo?.type
|
||||
|
||||
if shapeType isnt "text"
|
||||
for num in [0..3] # the coordinates must be in the range 0 to 1
|
||||
shapeInfo.points[num] = shapeInfo.points[num] / 100
|
||||
|
||||
wpm = Template.slide.whiteboardPaperModel
|
||||
wpm.makeShape(shapeType, shapeInfo)
|
||||
wpm.updateShape(shapeType, shapeInfo)
|
||||
|
||||
Template.shape.destroyed = ->
|
||||
wpm = Template.slide.whiteboardPaperModel
|
||||
wpm.clearShapes()
|
||||
Template.slide.displaySlide(wpm)
|
||||
Template.slide.manuallyDisplayShapes()
|
9
labs/meteor-client/client/views/whiteboard/slide.html
Executable file
9
labs/meteor-client/client/views/whiteboard/slide.html
Executable file
@ -0,0 +1,9 @@
|
||||
<template name="slide">
|
||||
{{#each getShapesForSlide}}
|
||||
{{> shape}}
|
||||
{{/each}}
|
||||
{{updatePointerLocation pointerLocation}}
|
||||
</template>
|
||||
|
||||
<template name="shape">
|
||||
</template>
|
16
labs/meteor-client/client/views/whiteboard/whiteboard.coffee
Executable file
16
labs/meteor-client/client/views/whiteboard/whiteboard.coffee
Executable file
@ -0,0 +1,16 @@
|
||||
Template.whiteboard.rendered = ->
|
||||
$(window).resize( ->
|
||||
height = $('#whiteboard').height()
|
||||
console.log "height = #{height}"
|
||||
$('#whiteboard-paper').height((height-$("#whiteboard-navbar").height()-10)+'px')
|
||||
|
||||
# $('#svggroup').html('')
|
||||
|
||||
Template.slide.whiteboardPaperModel._updateContainerDimensions()
|
||||
|
||||
wpm = Template.slide.whiteboardPaperModel
|
||||
wpm.clearShapes()
|
||||
wpm = Template.slide.whiteboardPaperModel
|
||||
Template.slide.displaySlide(wpm)
|
||||
Template.slide.manuallyDisplayShapes()
|
||||
);
|
10
labs/meteor-client/client/views/whiteboard/whiteboard.html
Executable file
10
labs/meteor-client/client/views/whiteboard/whiteboard.html
Executable file
@ -0,0 +1,10 @@
|
||||
<template name="whiteboard">
|
||||
<div id="{{id}}" {{visibility name}} class="component">
|
||||
<h3 id="whiteboard-navbar" class="title gradientBar"><span class="glyphicon glyphicon-pencil"></span> {{title}}</h3>
|
||||
<div id="whiteboard-paper">
|
||||
{{#each getCurrentSlide}}
|
||||
{{> slide}}
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
@ -0,0 +1,26 @@
|
||||
# A base class for whiteboard tools
|
||||
class @WhiteboardToolModel
|
||||
|
||||
initialize: (@paper) ->
|
||||
console.log "paper:" + @paper
|
||||
@gh = 0
|
||||
@gw = 0
|
||||
@obj = 0
|
||||
# the defintion of this shape, kept so we can redraw the shape whenever needed
|
||||
@definition = []
|
||||
|
||||
#set the size of the paper
|
||||
# @param {number} @gh gh parameter
|
||||
# @param {number} @gw gw parameter
|
||||
setPaperSize: (@gh, @gw) ->
|
||||
|
||||
setOffsets: (@xOffset, @yOffset) ->
|
||||
|
||||
setPaperDimensions: (@paperWidth, @paperHeight) ->
|
||||
# TODO: can't we simply take the width and the height from `@paper`?
|
||||
|
||||
getDefinition: () ->
|
||||
@definition
|
||||
|
||||
hide: () ->
|
||||
@obj.hide() if @obj?
|
42
labs/meteor-client/client/whiteboard_models/utils.coffee
Normal file
42
labs/meteor-client/client/whiteboard_models/utils.coffee
Normal file
@ -0,0 +1,42 @@
|
||||
# General utility methods
|
||||
|
||||
Meteor.methods
|
||||
# POST request using javascript
|
||||
# @param {string} path path of submission
|
||||
# @param {string} params parameters to submit
|
||||
# @param {string} method method of submission ("post" is default)
|
||||
# @return {undefined}
|
||||
postToUrl: (path, params, method) ->
|
||||
method = method or "post"
|
||||
form = $("<form></form>")
|
||||
form.attr
|
||||
"method" : method,
|
||||
"action" : path
|
||||
for key of params
|
||||
if params.hasOwnProperty(key)
|
||||
$hiddenField = $ "input"
|
||||
$hiddenField.attr
|
||||
"type" : "hidden",
|
||||
"name" : key,
|
||||
"value" : params[key]
|
||||
form.append $hiddenField
|
||||
|
||||
$('body').append form
|
||||
form.submit()
|
||||
|
||||
# @param {string,int} stroke stroke color, can be a number (a hex converted to int) or a
|
||||
# string (e.g. "#ffff00")
|
||||
# @param {string,ing} thickness thickness as a number or string (e.g. "2" or "2px")
|
||||
strokeAndThickness: (stroke, thickness) ->
|
||||
stroke ?= "0"
|
||||
thickness ?= "1"
|
||||
r =
|
||||
stroke: if stroke.toString().match(/\#.*/) then stroke else colourToHex(stroke)
|
||||
"stroke-width": if thickness.toString().match(/.*px$/) then thickness else "#{thickness}px"
|
||||
r
|
||||
|
||||
# Convert a color `value` as integer to a hex color (e.g. 255 to #0000ff)
|
||||
colourToHex = (value) ->
|
||||
hex = value.toString(16)
|
||||
hex = "0" + hex while hex.length < 6
|
||||
"##{hex}"
|
@ -0,0 +1,40 @@
|
||||
# The cursor/pointer in the whiteboard
|
||||
class @WhiteboardCursorModel
|
||||
|
||||
constructor: (@paper, @radius=null, @color=null) ->
|
||||
@radius ?= 6
|
||||
@color ?= "#ff6666" # a pinkish red
|
||||
@cursor = null
|
||||
@paper
|
||||
|
||||
draw: () =>
|
||||
@cursor = @paper.circle(0, 0, @radius)
|
||||
@cursor.attr
|
||||
"fill": @color
|
||||
"stroke": @color
|
||||
"opacity": "0.8"
|
||||
$(@cursor.node).on "mousewheel", _.bind(@_onMouseWheel, @)
|
||||
|
||||
toFront: () ->
|
||||
@cursor.toFront() if @cursor?
|
||||
|
||||
setRadius: (value) ->
|
||||
if @cursor?
|
||||
@cursor.attr r: value
|
||||
|
||||
setPosition: (x, y) ->
|
||||
if @cursor?
|
||||
@cursor.attr
|
||||
cx: x
|
||||
cy: y
|
||||
|
||||
undrag: () ->
|
||||
@cursor.undrag() if @cursor?
|
||||
|
||||
drag: (onMove, onStart, onEnd, target=null) ->
|
||||
if @cursor?
|
||||
target or= @
|
||||
@cursor.drag _.bind(onMove, target), _.bind(onStart, target), _.bind(onEnd, target)
|
||||
|
||||
_onMouseWheel: (e, delta) ->
|
||||
@trigger("cursor:mousewheel", e, delta)
|
150
labs/meteor-client/client/whiteboard_models/whiteboard_ellipse.coffee
Executable file
150
labs/meteor-client/client/whiteboard_models/whiteboard_ellipse.coffee
Executable file
@ -0,0 +1,150 @@
|
||||
# An ellipse in the whiteboard
|
||||
class @WhiteboardEllipseModel extends WhiteboardToolModel
|
||||
|
||||
constructor: (@paper) ->
|
||||
super @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
|
||||
@definition = [0, 0, 0, 0, "#000", "0px"]
|
||||
|
||||
# Make an ellipse on the whiteboard
|
||||
# @param {[type]} x the x value of the top left corner
|
||||
# @param {[type]} y the y value of the top left corner
|
||||
# @param {string} colour the colour of the object
|
||||
# @param {number} thickness the thickness of the object's line(s)
|
||||
make: (info) ->
|
||||
console.log "Whiteboard - Making ellipse: "
|
||||
console.log info
|
||||
if info?.points?
|
||||
x = info.points[0]
|
||||
y = info.points[1]
|
||||
color = info.color
|
||||
thickness = info.thickness
|
||||
|
||||
@obj = @paper.ellipse(x * @gw + @xOffset, y * @gh + @yOffset, 0, 0)
|
||||
@obj.attr Meteor.call("strokeAndThickness", color, thickness)
|
||||
@definition = [x, y, y, x, @obj.attrs["stroke"], @obj.attrs["stroke-width"]]
|
||||
|
||||
@obj
|
||||
|
||||
# Update ellipse drawn
|
||||
# @param {number} x1 the x value of the top left corner in percent of current slide size
|
||||
# @param {number} y1 the y value of the top left corner in percent of current slide size
|
||||
# @param {number} x2 the x value of the bottom right corner in percent of current slide size
|
||||
# @param {number} y2 the y value of the bottom right corner in percent of current slide size
|
||||
# @param {boolean} square (draw a circle or not
|
||||
update: (info) ->
|
||||
console.log info
|
||||
if info?.points?
|
||||
x1 = info.points[0]
|
||||
y1 = info.points[1]
|
||||
x2 = info.points[2]
|
||||
y2 = info.points[3]
|
||||
|
||||
circle = !info.square
|
||||
|
||||
if @obj?
|
||||
[x1, x2] = [x2, x1] if x2 < x1
|
||||
|
||||
if y2 < y1
|
||||
[y1, y2] = [y2, y1]
|
||||
reversed = true
|
||||
|
||||
if circle
|
||||
if reversed # if reveresed, the y1 coordinate gets updated, not the y2 coordinate
|
||||
y1 = y2 - (x2 - x1) * @gw / @gh
|
||||
else
|
||||
y2 = y1 + (x2 - x1) * @gw / @gh
|
||||
|
||||
#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
|
||||
coords =
|
||||
x1: x1
|
||||
x2: x2
|
||||
y1: y1
|
||||
y2: y2
|
||||
|
||||
console.log(coords)
|
||||
|
||||
rx = (x2 - x1) / 2
|
||||
ry = (y2 - y1) / 2
|
||||
|
||||
r =
|
||||
rx: rx * @gw
|
||||
ry: ry * @gh
|
||||
cx: (rx + x1) * @gw + @xOffset
|
||||
cy: (ry + y1) * @gh + @yOffset
|
||||
|
||||
@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
|
||||
@definition[0] = x1
|
||||
@definition[1] = y1
|
||||
@definition[2] = x2
|
||||
@definition[3] = y2
|
||||
|
||||
# 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) ->
|
||||
[x1, x2] = [x2, x1] if x2 < x1
|
||||
[y1, y2] = [y2, y1] if y2 < y1
|
||||
|
||||
rx = (x2 - x1) / 2
|
||||
ry = (y2 - y1) / 2
|
||||
x = (rx + x1) * @gw + @xOffset
|
||||
y = (ry + y1) * @gh + @yOffset
|
||||
elip = @paper.ellipse(x, y, rx * @gw, ry * @gh)
|
||||
elip.attr Utils.strokeAndThickness(colour, thickness)
|
||||
elip
|
||||
|
||||
# When first starting drawing the ellipse
|
||||
# @param {number} x the x value of cursor at the time in relation to the left side of the browser
|
||||
# @param {number} y the y value of cursor at the time in relation to the top of the browser
|
||||
# TODO: moved here but not finished
|
||||
dragOnStart: (x, y) ->
|
||||
# sx = (@paperWidth - @gw) / 2
|
||||
# sy = (@paperHeight - @gh) / 2
|
||||
# # find the x and y values in relation to the whiteboard
|
||||
# @ellipseX = (x - @containerOffsetLeft - sx + @xOffset)
|
||||
# @ellipseY = (y - @containerOffsetTop - sy + @yOffset)
|
||||
# globals.connection.emitMakeShape "ellipse",
|
||||
# [ @ellipseX / @paperWidth, @ellipseY / @paperHeight, @currentColour, @currentThickness ]
|
||||
|
||||
# When first starting to draw an ellipse
|
||||
# @param {number} dx the difference in the x value at the start as opposed to the x value now
|
||||
# @param {number} dy the difference in the y value at the start as opposed to the y value now
|
||||
# @param {number} x the x value of cursor at the time in relation to the left side of the browser
|
||||
# @param {number} y the y value of cursor at the time in relation to the top of the browser
|
||||
# @param {Event} e the mouse event
|
||||
# TODO: moved here but not finished
|
||||
dragOnMove: (dx, dy, x, y, e) ->
|
||||
# # if shift is pressed, draw a circle instead of ellipse
|
||||
# dy = dx if @shiftPressed
|
||||
# dx = dx / 2
|
||||
# dy = dy / 2
|
||||
# # adjust for negative values as well
|
||||
# x = @ellipseX + dx
|
||||
# y = @ellipseY + dy
|
||||
# dx = (if dx < 0 then -dx else dx)
|
||||
# dy = (if dy < 0 then -dy else dy)
|
||||
# globals.connection.emitUpdateShape "ellipse",
|
||||
# [ x / @paperWidth, y / @paperHeight, dx / @paperWidth, dy / @paperHeight ]
|
||||
|
||||
# When releasing the mouse after drawing the ellipse
|
||||
# @param {Event} e the mouse event
|
||||
# TODO: moved here but not finished
|
||||
dragOnStop: (e) ->
|
||||
# attrs = undefined
|
||||
# attrs = @currentEllipse.attrs if @currentEllipse?
|
||||
# if attrs?
|
||||
# globals.connection.emitPublishShape "ellipse",
|
||||
# [ attrs.cx / @gw, attrs.cy / @gh, attrs.rx / @gw, attrs.ry / @gh,
|
||||
# @currentColour, @currentThickness ]
|
||||
# @currentEllipse = null # late updates will be blocked by this
|
206
labs/meteor-client/client/whiteboard_models/whiteboard_line.coffee
Executable file
206
labs/meteor-client/client/whiteboard_models/whiteboard_line.coffee
Executable file
@ -0,0 +1,206 @@
|
||||
MAX_PATHS_IN_SEQUENCE = 30
|
||||
|
||||
# A line in the whiteboard
|
||||
# Note: is used to draw lines from the pencil tool and from the line tool, this is why some
|
||||
# methods can receive different set of parameters.
|
||||
# TODO: Maybe this should be split in WhiteboardPathModel for the pencil and
|
||||
# WhiteboardLineModel for the line tool
|
||||
class @WhiteboardLineModel extends WhiteboardToolModel
|
||||
|
||||
constructor: (@paper) ->
|
||||
super @paper
|
||||
|
||||
# the defintion of this shape, kept so we can redraw the shape whenever needed
|
||||
# format: svg path, stroke color, thickness
|
||||
@definition = ["", "#000", "0px"]
|
||||
|
||||
# @lineX = null
|
||||
# @lineY = null
|
||||
|
||||
# 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) ->
|
||||
|
||||
if info?.points?
|
||||
x = info.points[0]
|
||||
y = info.points[1]
|
||||
color = info.color
|
||||
thickness = info.thickness
|
||||
|
||||
x1 = x * @gw + @xOffset
|
||||
y1 = y * @gh + @yOffset
|
||||
path = "M" + x1 + " " + y1 + " L" + x1 + " " + y1
|
||||
pathPercent = "M" + x + " " + y + " L" + x + " " + y
|
||||
@obj = @paper.path(path)
|
||||
@obj.attr Meteor.call("strokeAndThickness", color, thickness)
|
||||
@obj.attr({"stroke-linejoin": "round"})
|
||||
|
||||
@definition = [pathPercent, @obj.attrs["stroke"], @obj.attrs["stroke-width"]]
|
||||
|
||||
@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) ->
|
||||
|
||||
if info?.points?
|
||||
x1 = info.points[0]
|
||||
y1 = info.points[1]
|
||||
x2 = info.points[2]
|
||||
y2 = info.points[3]
|
||||
|
||||
if @obj?
|
||||
|
||||
# if adding points from the pencil
|
||||
if _.isBoolean(info.adding)
|
||||
add = info.adding
|
||||
|
||||
pathPercent = "L" + x1 + " " + y1
|
||||
@definition.data[0] += pathPercent
|
||||
|
||||
x1 = x1 * @gw + @xOffset
|
||||
y1 = y1 * @gh + @yOffset
|
||||
|
||||
# if adding to the line
|
||||
if add
|
||||
path = @obj.attrs.path + "L" + x1 + " " + y1
|
||||
@obj.attr path: path
|
||||
|
||||
# if simply updating the last portion (for drawing a straight line)
|
||||
else
|
||||
@obj.attrs.path.pop()
|
||||
path = @obj.attrs.path.join(" ")
|
||||
path = path + "L" + x1 + " " + y1
|
||||
@obj.attr path: path
|
||||
|
||||
# adding lines from the line tool
|
||||
else
|
||||
path = @_buildPath(x1, y1, x2, y2)
|
||||
@definition[0] = path
|
||||
|
||||
path = @_scaleLinePath(path, @gw, @gh, @xOffset, @yOffset)
|
||||
@obj.attr path: path
|
||||
|
||||
# Draw a line on the paper
|
||||
# @param {number,string} x1 1) the x value of the first point
|
||||
# 2) the string path
|
||||
# @param {number,string} y1 1) the y value of the first point
|
||||
# 2) the colour
|
||||
# @param {number} x2 1) the x value of the second point
|
||||
# 2) the thickness
|
||||
# @param {number} y2 1) the y value of the second point
|
||||
# 2) undefined
|
||||
# @param {string} colour 1) the colour of the shape to be drawn
|
||||
# 2) undefined
|
||||
# @param {number} thickness 1) the thickness of the line to be drawn
|
||||
# 2) undefined
|
||||
draw: (x1, y1, x2, y2, colour, thickness) ->
|
||||
|
||||
# if the drawing is from the pencil tool, it comes as a path first
|
||||
if _.isString(x1)
|
||||
colour = y1
|
||||
thickness = x2
|
||||
path = x1
|
||||
|
||||
# if the drawing is from the line tool, it comes with two points
|
||||
else
|
||||
path = @_buildPath(x1, y1, x2, y2)
|
||||
|
||||
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: (x1, y1, x2, y2) ->
|
||||
"M#{x1} #{y1}L#{x2} #{y2}"
|
||||
|
||||
# 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=0, yOffset=0) ->
|
||||
path = null
|
||||
points = string.match(/(\d+[.]?\d*)/g)
|
||||
len = points.length
|
||||
j = 0
|
||||
|
||||
# go through each point and multiply it by the new height and width
|
||||
while j < len
|
||||
if j isnt 0
|
||||
path += "L" + (points[j] * w + xOffset) + "," + (points[j + 1] * h + yOffset)
|
||||
else
|
||||
path = "M" + (points[j] * w + xOffset) + "," + (points[j + 1] * h + yOffset)
|
||||
j += 2
|
||||
path
|
828
labs/meteor-client/client/whiteboard_models/whiteboard_paper.coffee
Executable file
828
labs/meteor-client/client/whiteboard_models/whiteboard_paper.coffee
Executable file
@ -0,0 +1,828 @@
|
||||
# "Paper" which is the Raphael term for the entire SVG object on the webpage.
|
||||
# This class deals with this SVG component only.
|
||||
class @WhiteboardPaperModel
|
||||
|
||||
# Container must be a DOM element
|
||||
constructor: (@container) ->
|
||||
# a WhiteboardCursorModel
|
||||
@cursor = null
|
||||
|
||||
# all slides in the presentation indexed by url
|
||||
@slides = {}
|
||||
# the slide being shown
|
||||
@currentSlide = null
|
||||
|
||||
@fitToPage = true
|
||||
@panX = null
|
||||
@panY = null
|
||||
|
||||
# a raphaeljs set with all the shapes in the current slide
|
||||
@currentShapes = 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)
|
||||
@currentShapesDefinitions = []
|
||||
# pointers to the current shapes being drawn
|
||||
@currentLine = null
|
||||
@currentRect = null
|
||||
@currentEllipse = null
|
||||
@currentTriangle = null
|
||||
@currentText = null
|
||||
|
||||
@zoomLevel = 1
|
||||
@shiftPressed = false
|
||||
@currentPathCount = 0
|
||||
|
||||
# $container = $('#whiteboard-paper')
|
||||
# @containerWidth = $container.innerWidth()
|
||||
# @containerHeight = $container.innerHeight()
|
||||
@_updateContainerDimensions()
|
||||
|
||||
# $(window).on "resize.whiteboard_paper", _.bind(@_onWindowResize, @)
|
||||
# $(document).on "keydown.whiteboard_paper", _.bind(@_onKeyDown, @)
|
||||
# $(document).on "keyup.whiteboard_paper", _.bind(@_onKeyUp, @)
|
||||
|
||||
# Bind to the event triggered when the client connects to the server
|
||||
# if globals.connection.isConnected()
|
||||
# @_registerEvents()
|
||||
# else
|
||||
# globals.events.on "connection:connected", =>
|
||||
# @_registerEvents()
|
||||
|
||||
# Override the close() to unbind events.
|
||||
unbindEvents: ->
|
||||
$(window).off "resize.whiteboard_paper"
|
||||
$(document).off "keydown.whiteboard_paper"
|
||||
$(document).off "keyup.whiteboard_paper"
|
||||
# TODO: other events are being used in the code and should be off() here
|
||||
|
||||
# Initializes the paper in the page.
|
||||
# Can't do these things in initialize() because by then some elements
|
||||
# are not yet created in the page.
|
||||
create: ->
|
||||
# paper is embedded within the div#slide of the page.
|
||||
# @raphaelObj ?= ScaleRaphael(@container, "900", "500")
|
||||
|
||||
h = $("#"+@container).height()
|
||||
w = $("#"+@container).width()
|
||||
console.log "h: #{h}"
|
||||
console.log "w: #{w}"
|
||||
|
||||
# @raphaelObj ?= ScaleRaphael(@container, "900", "500")
|
||||
@raphaelObj ?= ScaleRaphael(@container, w, h)
|
||||
|
||||
# $container = $('#whiteboard-contents')
|
||||
@raphaelObj ?= ScaleRaphael(@container, $container.innerHeight(), $container.innerWidth())
|
||||
|
||||
@raphaelObj.canvas.setAttribute "preserveAspectRatio", "xMinYMin slice"
|
||||
|
||||
@cursor = new WhiteboardCursorModel(@raphaelObj)
|
||||
@cursor.draw()
|
||||
#@cursor.on "cursor:mousewheel", _.bind(@_zoomSlide, @)
|
||||
|
||||
if @slides
|
||||
@rebuild()
|
||||
else
|
||||
@slides = {} # if previously loaded
|
||||
unless navigator.userAgent.indexOf("Firefox") is -1
|
||||
@raphaelObj.renderfix()
|
||||
|
||||
# initializing border around slide to cover up areas which shouldnt show
|
||||
@borders = {}
|
||||
for border in ['left', 'right', 'top', 'bottom']
|
||||
@borders[border] = @raphaelObj.rect(0, 0, 0, 0)
|
||||
@borders[border].attr("fill", "#ababab")
|
||||
@borders[border].attr( {stroke:"#ababab"} )
|
||||
|
||||
@raphaelObj
|
||||
|
||||
# Re-add the images to the paper that are found
|
||||
# in the slides array (an object of urls and dimensions).
|
||||
rebuild: ->
|
||||
@currentSlide = null
|
||||
for url of @slides
|
||||
if @slides.hasOwnProperty(url)
|
||||
@addImageToPaper url, @slides[url].getWidth(), @slides[url].getHeight()
|
||||
|
||||
# A wrapper around ScaleRaphael's `changeSize()` method, more details at:
|
||||
# http://www.shapevent.com/scaleraphael/
|
||||
# Also makes sure that the images are redraw in the canvas so they are actually resized.
|
||||
changeSize: (windowWidth, windowHeight, center=true, clipping=false) ->
|
||||
if @raphaelObj?
|
||||
@raphaelObj.changeSize(windowWidth, windowHeight, center, clipping)
|
||||
|
||||
# TODO: we can scale the slides and drawings instead of re-adding them, but the logic
|
||||
# will change quite a bit
|
||||
# slides
|
||||
slidesTmp = _.clone(@slides)
|
||||
urlTmp = @currentSlide
|
||||
@removeAllImagesFromPaper()
|
||||
@slides = slidesTmp
|
||||
@rebuild()
|
||||
@showImageFromPaper(urlTmp?.url)
|
||||
# drawings
|
||||
tmp = _.clone(@currentShapesDefinitions)
|
||||
@clearShapes()
|
||||
@drawListOfShapes(tmp)
|
||||
|
||||
# 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) ->
|
||||
@_updateContainerDimensions()
|
||||
|
||||
if @fitToPage
|
||||
# solve for the ratio of what length is going to fit more than the other
|
||||
max = Math.max(width / @containerWidth, height / @containerHeight)
|
||||
# fit it all in appropriately
|
||||
# TODO: temporary solution
|
||||
url = @_slideUrl(url)
|
||||
sw = width / max
|
||||
sh = height / max
|
||||
cx = (@containerWidth / 2) - (width / 2)
|
||||
cy = (@containerHeight / 2) - (height / 2)
|
||||
|
||||
img = @raphaelObj.image(url, cx, cy, width, height)
|
||||
originalWidth = width
|
||||
originalHeight = height
|
||||
else
|
||||
# fit to width
|
||||
alert "no fit"
|
||||
# assume it will fit width ways
|
||||
sw = width / wr
|
||||
sh = height / wr
|
||||
wr = width / @containerWidth
|
||||
originalWidth = sw
|
||||
originalHeight = sh
|
||||
sw = width / wr
|
||||
sh = height / wr
|
||||
img = @raphaelObj.image(url, cx = 0, cy = 0, sw, sh)
|
||||
|
||||
# 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
|
||||
@slides[url] = new WhiteboardSlideModel(img.id, url, img, originalWidth, originalHeight, sw, sh, cx, cy)
|
||||
|
||||
unless @currentSlide?
|
||||
img.toBack()
|
||||
@currentSlide = @slides[url]
|
||||
else if @currentSlide.url is url
|
||||
img.toBack()
|
||||
else
|
||||
img.hide()
|
||||
$(@container).on "mousemove", _.bind(@_onMouseMove, @)
|
||||
$(@container).on "mousewheel", _.bind(@_zoomSlide, @)
|
||||
# TODO $(img.node).bind "mousewheel", zoomSlide
|
||||
#@trigger('paper:image:added', img)
|
||||
|
||||
# TODO: other places might also required an update in these dimensions
|
||||
@_updateContainerDimensions()
|
||||
|
||||
img
|
||||
|
||||
# Removes all the images from the Raphael paper.
|
||||
removeAllImagesFromPaper: ->
|
||||
for url of @slides
|
||||
if @slides.hasOwnProperty(url)
|
||||
@raphaelObj.getById(@slides[url]?.getId())?.remove()
|
||||
#@trigger('paper:image:removed', @slides[url].getId()) # TODO do we need this?
|
||||
@slides = {}
|
||||
@currentSlide = null
|
||||
|
||||
# Shows an image from the paper.
|
||||
# The url must be in the slides array.
|
||||
# @param {string} url the url of the image (must be in slides array)
|
||||
showImageFromPaper: (url) ->
|
||||
# TODO: temporary solution
|
||||
url = @_slideUrl(url)
|
||||
if not @currentSlide? or (@slides[url]? and @currentSlide.url isnt url)
|
||||
@_hideImageFromPaper(@currentSlide.url) if @currentSlide?
|
||||
next = @_getImageFromPaper(url)
|
||||
if next
|
||||
next.show()
|
||||
next.toFront()
|
||||
@currentShapes.forEach (element) ->
|
||||
element.toFront()
|
||||
@cursor.toFront()
|
||||
@currentSlide = @slides[url]
|
||||
|
||||
# Updates the paper from the server values.
|
||||
# @param {number} cx_ the x-offset value as a percentage of the original width
|
||||
# @param {number} cy_ the y-offset value as a percentage of the original height
|
||||
# @param {number} sw_ the slide width value as a percentage of the original width
|
||||
# @param {number} sh_ the slide height value as a percentage of the original height
|
||||
# TODO: not working as it should
|
||||
updatePaperFromServer: (cx_, cy_, sw_, sh_) ->
|
||||
# # if updating the slide size (zooming!)
|
||||
# [slideWidth, slideHeight] = @_currentSlideOriginalDimensions()
|
||||
# if sw_ and sh_
|
||||
# @raphaelObj.setViewBox cx_ * slideWidth, cy_ * slideHeight, sw_ * slideWidth, sh_ * slideHeight,
|
||||
# sw = slideWidth / sw_
|
||||
# sh = slideHeight / sh_
|
||||
# # just panning, so use old slide size values
|
||||
# else
|
||||
# [sw, sh] = @_currentSlideDimensions()
|
||||
# @raphaelObj.setViewBox cx_ * slideWidth, cy_ * slideHeight, @raphaelObj._viewBox[2], @raphaelObj._viewBox[3]
|
||||
|
||||
# # update corners
|
||||
# cx = cx_ * sw
|
||||
# cy = cy_ * sh
|
||||
# # update position of svg object in the window
|
||||
# sx = (@containerWidth - slideWidth) / 2
|
||||
# sy = (@containerHeight - slideHeight) / 2
|
||||
# sy = 0 if sy < 0
|
||||
# @raphaelObj.canvas.style.left = sx + "px"
|
||||
# @raphaelObj.canvas.style.top = sy + "px"
|
||||
# @raphaelObj.setSize slideWidth - 2, slideHeight - 2
|
||||
|
||||
# # update zoom level and cursor position
|
||||
# z = @raphaelObj._viewBox[2] / slideWidth
|
||||
# @zoomLevel = z
|
||||
# dcr = 1
|
||||
# @cursor.setRadius(dcr * z)
|
||||
|
||||
# # force the slice attribute despite Raphael changing it
|
||||
# @raphaelObj.canvas.setAttribute "preserveAspectRatio", "xMinYMin slice"
|
||||
|
||||
# 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) ->
|
||||
@currentTool = tool
|
||||
console.log "setting current tool to", tool
|
||||
switch tool
|
||||
when "path", "line"
|
||||
@cursor.undrag()
|
||||
@currentLine = @_createTool(tool)
|
||||
@cursor.drag(@currentLine.dragOnMove, @currentLine.dragOnStart, @currentLine.dragOnEnd)
|
||||
when "rect"
|
||||
@cursor.undrag()
|
||||
@currentRect = @_createTool(tool)
|
||||
@cursor.drag(@currentRect.dragOnMove, @currentRect.dragOnStart, @currentRect.dragOnEnd)
|
||||
|
||||
# TODO: the shapes below are still in the old format
|
||||
# when "panzoom"
|
||||
# @cursor.undrag()
|
||||
# @cursor.drag _.bind(@_panDragging, @),
|
||||
# _.bind(@_panGo, @), _.bind(@_panStop, @)
|
||||
# when "ellipse"
|
||||
# @cursor.undrag()
|
||||
# @cursor.drag _.bind(@_ellipseDragging, @),
|
||||
# _.bind(@_ellipseDragStart, @), _.bind(@_ellipseDragStop, @)
|
||||
# when "text"
|
||||
# @cursor.undrag()
|
||||
# @cursor.drag _.bind(@_rectDragging, @),
|
||||
# _.bind(@_textStart, @), _.bind(@_textStop, @)
|
||||
else
|
||||
console.log "ERROR: Cannot set invalid tool:", tool
|
||||
|
||||
# Sets the fit to page.
|
||||
# @param {boolean} value If true fit to page. If false fit to width.
|
||||
# TODO: not really working as it should be
|
||||
setFitToPage: (value) ->
|
||||
@fitToPage = value
|
||||
|
||||
# TODO: we can scale the slides and drawings instead of re-adding them, but the logic
|
||||
# will change quite a bit
|
||||
temp = @slides
|
||||
@removeAllImagesFromPaper()
|
||||
@slides = temp
|
||||
# re-add all the images as they should fit differently
|
||||
@rebuild()
|
||||
|
||||
# set to default zoom level
|
||||
#globals.connection.emitPaperUpdate 0, 0, 1, 1
|
||||
# get the shapes to reprocess
|
||||
#globals.connection.emitAllShapes()
|
||||
|
||||
# Socket response - Update zoom variables and viewbox
|
||||
# @param {number} d the delta value from the scroll event
|
||||
# @return {undefined}
|
||||
setZoom: (d) ->
|
||||
step = 0.05 # step size
|
||||
if d < 0
|
||||
@zoomLevel += step # zooming out
|
||||
else
|
||||
@zoomLevel -= step # zooming in
|
||||
|
||||
[sw, sh] = @_currentSlideDimensions()
|
||||
[cx, cy] = @_currentSlideOffsets()
|
||||
x = cx / sw
|
||||
y = cy / sh
|
||||
# cannot zoom out further than 100%
|
||||
z = (if @zoomLevel > 1 then 1 else @zoomLevel)
|
||||
# cannot zoom in further than 400% (1/4)
|
||||
z = (if z < 0.25 then 0.25 else z)
|
||||
# cannot zoom to make corner less than (x,y) = (0,0)
|
||||
x = (if x < 0 then 0 else x)
|
||||
y = (if y < 0 then 0 else y)
|
||||
# cannot view more than the bottom corners
|
||||
zz = 1 - z
|
||||
x = (if x > zz then zz else x)
|
||||
y = (if y > zz then zz else y)
|
||||
#globals.connection.emitPaperUpdate x, y, z, z # send update to all clients
|
||||
|
||||
stopPanning: ->
|
||||
# nothing to do
|
||||
|
||||
# Draws an array of shapes to the paper.
|
||||
# @param {array} shapes the array of shapes to draw
|
||||
drawListOfShapes: (shapes) ->
|
||||
@currentShapesDefinitions = shapes
|
||||
@currentShapes = @raphaelObj.set()
|
||||
for shape in shapes
|
||||
shapeType = shape?.shape?.shape_type
|
||||
dataBlock = shape?.shape?.shape
|
||||
data = if _.isString(dataBlock) then JSON.parse(dataBlock) else dataBlock
|
||||
tool = @_createTool(shapeType)
|
||||
if tool?
|
||||
@currentShapes.push tool.draw.apply(tool, data)
|
||||
else
|
||||
console.log "shape not recognized at drawListOfShapes", shape
|
||||
|
||||
# make sure the cursor is still on top
|
||||
@cursor.toFront()
|
||||
|
||||
#Changes the currently displayed presentation (if any) with this one
|
||||
#@param {object} containing the "presentation" object -id,name,pages[]
|
||||
sharePresentation: (data) ->
|
||||
#globals.events.trigger("connection:all_slides", data.payload)
|
||||
|
||||
# Clear all shapes from this paper.
|
||||
clearShapes: ->
|
||||
if @currentShapes?
|
||||
@currentShapes.forEach (element) ->
|
||||
element.remove()
|
||||
@currentShapes = []
|
||||
@currentShapesDefinitions = []
|
||||
|
||||
# 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) ->
|
||||
switch shape
|
||||
when "line"
|
||||
@currentLine.update(data)
|
||||
when "rectangle"
|
||||
@currentRect.update(data)
|
||||
when "ellipse"
|
||||
@currentEllipse.update(data)
|
||||
when "triangle"
|
||||
@currentTriangle.update(data)
|
||||
when "text"
|
||||
#@currentText.update.apply(@currentText, data)
|
||||
@currentText.update(data)
|
||||
else
|
||||
console.log "shape not recognized at updateShape", shape
|
||||
|
||||
# Make a shape `shape` with the data in `data`.
|
||||
makeShape: (shape, data) ->
|
||||
tool = null
|
||||
switch shape
|
||||
when "path", "line"
|
||||
@currentLine = @_createTool(shape)
|
||||
toolModel = @currentLine
|
||||
tool = @currentLine.make(data)
|
||||
when "rectangle"
|
||||
@currentRect = @_createTool(shape)
|
||||
toolModel = @currentRect
|
||||
tool = @currentRect.make(data)
|
||||
when "ellipse"
|
||||
@currentEllipse = @_createTool(shape)
|
||||
toolModel = @currentEllipse
|
||||
tool = @currentEllipse.make(data)
|
||||
when "triangle"
|
||||
@currentTriangle = @_createTool(shape)
|
||||
toolModel = @currentTriangle
|
||||
tool = @currentTriangle.make(data)
|
||||
when "text"
|
||||
@currentText = @_createTool(shape)
|
||||
toolModel = @currentText
|
||||
#tool = @currentText.make.apply(@currentText, data)
|
||||
tool = @currentText.make(data)
|
||||
else
|
||||
console.log "shape not recognized at makeShape", shape
|
||||
if tool?
|
||||
@currentShapes ?= @raphaelObj.set()
|
||||
@currentShapes.push(tool)
|
||||
@currentShapesDefinitions.push(toolModel.getDefinition())
|
||||
|
||||
# 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) ->
|
||||
[cx, cy] = @_currentSlideOffsets()
|
||||
[slideWidth, slideHeight] = @_currentSlideOriginalDimensions()
|
||||
@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 @viewBoxXpos? && @viewBoxYPos? && @viewBoxWidth? && @viewBoxHeight?
|
||||
@cursor.setPosition( @viewBoxXpos + x * @viewBoxWidth, @viewBoxYPos + y * @viewBoxHeight )
|
||||
|
||||
# Update the slide to move and zoom
|
||||
# @param {number} xOffset the x value of offset
|
||||
# @param {number} yOffset the y value of offset
|
||||
# @param {number} widthRatio the ratio of the previous width
|
||||
# @param {number} heightRatio the ratio of the previous height
|
||||
moveAndZoom: (xOffset, yOffset, widthRatio, heightRatio) ->
|
||||
@globalxOffset = xOffset
|
||||
@globalyOffset = yOffset
|
||||
@globalwidthRatio = widthRatio
|
||||
@globalheightRatio = heightRatio
|
||||
|
||||
[slideWidth, slideHeight] = @_currentSlideOriginalDimensions()
|
||||
#console.log("xOffset: " + xOffset + ", yOffset: " + yOffset);
|
||||
#console.log("@containerWidth: " + @containerWidth + " @containerHeight: " + @containerHeight);
|
||||
#console.log("slideWidth: " + slideWidth + " slideHeight: " + slideHeight);
|
||||
baseWidth = (@containerWidth - slideWidth) / 2
|
||||
baseHeight = (@containerHeight - slideHeight) / 2
|
||||
|
||||
|
||||
#get the actual size of the slide, depending on the limiting factor (container width or container height)
|
||||
|
||||
actualWidth = @currentSlide.displayWidth
|
||||
actualHeight = @currentSlide.displayHeight
|
||||
#console.log("actualWidth:" + actualWidth + " actualHeight: " + actualHeight)
|
||||
|
||||
#calculate parameters to pass
|
||||
newXPos = baseWidth - 2* xOffset * actualWidth / 100
|
||||
newyPos = baseHeight - 2* yOffset * actualHeight / 100
|
||||
newWidth = actualWidth / 100 * widthRatio
|
||||
newHeight = actualHeight / 100 * heightRatio
|
||||
|
||||
@viewBoxXpos = newXPos
|
||||
@viewBoxYPos = newyPos
|
||||
@viewBoxWidth = newWidth
|
||||
@viewBoxHeight = newHeight
|
||||
|
||||
#console.log("newXPos: " + newXPos + " newyPos: " + newyPos + " newWidth: " + newWidth + " newHeight: " + newHeight)
|
||||
|
||||
#set parameters to raphael viewbox
|
||||
@raphaelObj.setViewBox(newXPos , newyPos, newWidth , newHeight , true)
|
||||
|
||||
|
||||
# update the rectangle elements which create the border when page is zoomed
|
||||
@borders.left.attr( {width:newXPos, height: @containerHeight} )
|
||||
|
||||
@borders.right.attr(
|
||||
x: newXPos + newWidth
|
||||
y: 0
|
||||
width:newXPos
|
||||
height:@containerHeight
|
||||
)
|
||||
|
||||
@borders.top.attr(
|
||||
width: @containerWidth
|
||||
height: newyPos
|
||||
)
|
||||
|
||||
@borders.bottom.attr(
|
||||
y: newyPos + newHeight
|
||||
width: @containerWidth
|
||||
height: @containerHeight
|
||||
)
|
||||
|
||||
# borders should appear infront of every other element (i.e. shapes)
|
||||
for _, border of @borders
|
||||
border.toFront()
|
||||
|
||||
#update cursor to appear the same size even when page is zoomed in
|
||||
@cursor.setRadius( 3 * widthRatio / 100 )
|
||||
|
||||
# Registers listeners for events in the gloval event bus
|
||||
_registerEvents: ->
|
||||
|
||||
# globals.events.on "connection:whiteboardDrawPen", (startingData) =>
|
||||
# type = startingData.payload.shape_type
|
||||
# color = startingData.payload.data.line.color
|
||||
# thickness = startingData.payload.data.line.weight
|
||||
# points = startingData.shape.points
|
||||
# if type is "line"
|
||||
# for i in [0..points.length - 1]
|
||||
# if i is 0
|
||||
# #make these compatible with a line
|
||||
# console.log "points[i]: " + points[i]
|
||||
# lineObject = {
|
||||
# shape: {
|
||||
# type: "line",
|
||||
# coordinate: {
|
||||
# firstX : points[i].x/100,
|
||||
# firstY : points[i].y/100
|
||||
# },
|
||||
# color: startingData.payload.data.line.color,
|
||||
# thickness : startingData.payload.data.line.weight
|
||||
# }
|
||||
# adding : false #tell the line object that we are NOT adding points but creating a new line
|
||||
# }
|
||||
# console.log "lineObject: " + lineObject
|
||||
# @makeShape type, lineObject
|
||||
# else
|
||||
# console.log "points[i]: "+ points[i]
|
||||
# lineObject = {
|
||||
# shape: {
|
||||
# type: "line",
|
||||
# coordinate: {
|
||||
# firstX : points[i].x/100,
|
||||
# firstY : points[i].y/100
|
||||
# },
|
||||
# color: startingData.payload.data.line.color,
|
||||
# thickness : startingData.payload.data.line.weight
|
||||
# }
|
||||
# adding : true #tell the line object that we ARE adding points and NOT creating a new line
|
||||
# }
|
||||
# console.log "lineObject: " + lineObject
|
||||
# @updateShape type, lineObject
|
||||
|
||||
|
||||
# globals.events.on "connection:move_and_zoom", (xOffset, yOffset, widthRatio, heightRatio) =>
|
||||
# @moveAndZoom(xOffset, yOffset, widthRatio, heightRatio)
|
||||
|
||||
# globals.events.on "connection:changeslide", (url) =>
|
||||
# @showImageFromPaper(url)
|
||||
|
||||
# globals.events.on "connection:viewBox", (xperc, yperc, wperc, hperc) =>
|
||||
# xperc = parseFloat(xperc, 10)
|
||||
# yperc = parseFloat(yperc, 10)
|
||||
# wperc = parseFloat(wperc, 10)
|
||||
# hperc = parseFloat(hperc, 10)
|
||||
# @updatePaperFromServer(xperc, yperc, wperc, hperc)
|
||||
|
||||
# globals.events.on "connection:fitToPage", (value) =>
|
||||
# @setFitToPage(value)
|
||||
|
||||
# globals.events.on "connection:zoom", (delta) =>
|
||||
# @setZoom(delta)
|
||||
|
||||
# globals.events.on "connection:paper", (cx, cy, sw, sh) =>
|
||||
# @updatePaperFromServer(cx, cy, sw, sh)
|
||||
|
||||
# globals.events.on "connection:panStop", =>
|
||||
# @stopPanning()
|
||||
|
||||
# globals.events.on "connection:toolChanged", (tool) =>
|
||||
# @setCurrentTool(tool)
|
||||
|
||||
# globals.events.on "connection:textDone", =>
|
||||
# @textDone()
|
||||
|
||||
# globals.events.on "connection:uploadStatus", (message, fade) =>
|
||||
# globals.events.trigger("whiteboard:paper:uploadStatus", message, fade)
|
||||
|
||||
|
||||
# Update the dimensions of the container.
|
||||
_updateContainerDimensions: ->
|
||||
console.log "update Container Dimensions"
|
||||
|
||||
$container = $('#whiteboard-paper')
|
||||
@containerWidth = $container.innerWidth()
|
||||
@containerHeight = $container.innerHeight()
|
||||
|
||||
@containerOffsetLeft = $container.offset()?.left
|
||||
@containerOffsetTop = $container.offset()?.top
|
||||
|
||||
|
||||
# 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) ->
|
||||
if @slides[url]
|
||||
id = @slides[url].getId()
|
||||
return @raphaelObj.getById(id) if id?
|
||||
null
|
||||
|
||||
# Hides an image from the paper given the URL.
|
||||
# The url must be in the slides array.
|
||||
# @param {string} url the url of the image (must be in slides array)
|
||||
_hideImageFromPaper: (url) ->
|
||||
img = @_getImageFromPaper(url)
|
||||
img.hide() if img?
|
||||
|
||||
# Update zoom variables on all clients
|
||||
# @param {Event} e the event that occurs when scrolling
|
||||
# @param {number} delta the speed/direction at which the scroll occurred
|
||||
_zoomSlide: (e, delta) ->
|
||||
#globals.connection.emitZoom delta
|
||||
|
||||
# Called when the cursor is moved over the presentation.
|
||||
# Sends cursor moving event to server.
|
||||
# @param {Event} e the mouse event
|
||||
# @param {number} x the x value of cursor at the time in relation to the left side of the browser
|
||||
# @param {number} y the y value of cursor at the time in relation to the top of the browser
|
||||
# TODO: this should only be done if the user is the presenter
|
||||
_onMouseMove: (e, x, y) ->
|
||||
[sw, sh] = @_currentSlideDimensions()
|
||||
xLocal = (e.pageX - @containerOffsetLeft) / sw
|
||||
yLocal = (e.pageY - @containerOffsetTop) / sh
|
||||
#globals.connection.emitMoveCursor xLocal, yLocal
|
||||
|
||||
# When the user is dragging the cursor (click + move)
|
||||
# @param {number} dx the difference between the x value from panGo and now
|
||||
# @param {number} dy the difference between the y value from panGo and now
|
||||
_panDragging: (dx, dy) ->
|
||||
[slideWidth, slideHeight] = @_currentSlideOriginalDimensions()
|
||||
sx = (@containerWidth - slideWidth) / 2
|
||||
sy = (@containerHeight - slideHeight) / 2
|
||||
[sw, sh] = @_currentSlideDimensions()
|
||||
|
||||
# ensuring that we cannot pan outside of the boundaries
|
||||
x = (@panX - dx)
|
||||
# cannot pan past the left edge of the page
|
||||
x = (if x < 0 then 0 else x)
|
||||
y = (@panY - dy)
|
||||
# cannot pan past the top of the page
|
||||
y = (if y < 0 then 0 else y)
|
||||
if @fitToPage
|
||||
x2 = slideWidth + x
|
||||
else
|
||||
x2 = @containerWidth + x
|
||||
# cannot pan past the width
|
||||
x = (if x2 > sw then sw - (@containerWidth - sx * 2) else x)
|
||||
if @fitToPage
|
||||
y2 = slideHeight + y
|
||||
else
|
||||
# height of image could be greater (or less) than the box it fits in
|
||||
y2 = @containerHeight + y
|
||||
# cannot pan below the height
|
||||
y = (if y2 > sh then sh - (@containerHeight - sy * 2) else y)
|
||||
#globals.connection.emitPaperUpdate x / sw, y / sh, null, null
|
||||
|
||||
# When panning starts
|
||||
# @param {number} x the x value of the cursor
|
||||
# @param {number} y the y value of the cursor
|
||||
_panGo: (x, y) ->
|
||||
[cx, cy] = @_currentSlideOffsets()
|
||||
@panX = cx
|
||||
@panY = cy
|
||||
|
||||
# When panning finishes
|
||||
# @param {Event} e the mouse event
|
||||
_panStop: (e) ->
|
||||
@stopPanning()
|
||||
|
||||
# Called when the application window is resized.
|
||||
_onWindowResize: ->
|
||||
@_updateContainerDimensions()
|
||||
console.log "_onWindowResize"
|
||||
|
||||
#TODO: temporary hacked away fix so that the buttons resize correctly when the window resizes
|
||||
$("#users-btn").click();
|
||||
$("#users-btn").click();
|
||||
|
||||
|
||||
#TODO: maybe find solution besides these global values..no conflicts however
|
||||
|
||||
[slideWidth, slideHeight] = @_currentSlideOriginalDimensions()
|
||||
#console.log("xOffset: " + xOffset + ", yOffset: " + yOffset);
|
||||
#console.log("@containerWidth: " + @containerWidth + " @containerHeight: " + @containerHeight);
|
||||
#console.log("slideWidth: " + slideWidth + " slideHeight: " + slideHeight);
|
||||
baseWidth = (@containerWidth - slideWidth) / 2
|
||||
baseHeight = (@containerHeight - slideHeight) / 2
|
||||
|
||||
|
||||
#get the actual size of the slide, depending on the limiting factor (container width or container height)
|
||||
if(@containerWidth - slideWidth < @containerHeight - slideHeight)
|
||||
actualHeight = @containerWidth * slideHeight / slideWidth
|
||||
actualWidth = @containerWidth
|
||||
else
|
||||
actualWidth = @containerHeight * slideWidth / slideHeight
|
||||
actualHeight = @containerHeight
|
||||
|
||||
#console.log("actualWidth:" + actualWidth + " actualHeight: " + actualHeight)
|
||||
|
||||
#calculate parameters to pass
|
||||
newXPos = baseWidth
|
||||
newyPos = baseHeight
|
||||
newWidth = actualWidth
|
||||
newHeight = actualHeight
|
||||
|
||||
#now the zooming will still be correct when the window is resized
|
||||
#and hopefully when rotated on a mobile device
|
||||
if @globalxOffset? && @globalyOffset? && @globalwidthRatio? && @globalheightRatio?
|
||||
console.log "has zoomed in"
|
||||
@moveAndZoom(@globalxOffset, @globalyOffset, @globalwidthRatio, @globalheightRatio)
|
||||
|
||||
else
|
||||
obj =
|
||||
globalxOffset : @globalxOffset
|
||||
globalyOffset : @globalyOffset
|
||||
globalwidthRatio : @globalwidthRatio
|
||||
globalheightRatio : @globalheightRatio
|
||||
|
||||
console.log obj
|
||||
console.log "not zoomed"
|
||||
@raphaelObj.setViewBox(newXPos, newyPos, newWidth, newHeight,true)
|
||||
|
||||
|
||||
# when pressing down on a key at anytime
|
||||
_onKeyDown: (event) ->
|
||||
unless event
|
||||
keyCode = window.event.keyCode
|
||||
else
|
||||
keyCode = event.keyCode
|
||||
switch keyCode
|
||||
when 16 # shift key
|
||||
@shiftPressed = true
|
||||
|
||||
# when releasing any key at any time
|
||||
_onKeyUp: ->
|
||||
unless event
|
||||
keyCode = window.event.keyCode
|
||||
else
|
||||
keyCode = event.keyCode
|
||||
switch keyCode
|
||||
when 16 # shift key
|
||||
@shiftPressed = false
|
||||
|
||||
_currentSlideDimensions: ->
|
||||
if @currentSlide? then @currentSlide.getDimensions() else [0, 0]
|
||||
|
||||
_currentSlideOriginalDimensions: ->
|
||||
if @currentSlide? then @currentSlide.getOriginalDimensions() else [0, 0]
|
||||
|
||||
_currentSlideOffsets: ->
|
||||
if @currentSlide? then @currentSlide.getOffsets() else [0, 0]
|
||||
|
||||
# Wrapper method to create a tool for the whiteboard
|
||||
_createTool: (type) ->
|
||||
switch type
|
||||
when "path", "line"
|
||||
model = WhiteboardLineModel
|
||||
when "rectangle"
|
||||
model = WhiteboardRectModel
|
||||
when "ellipse"
|
||||
model = WhiteboardEllipseModel
|
||||
when "triangle"
|
||||
model = WhiteboardTriangleModel
|
||||
when "text"
|
||||
model = WhiteboardTextModel
|
||||
|
||||
if model?
|
||||
[slideWidth, slideHeight] = @_currentSlideOriginalDimensions()
|
||||
[xOffset, yOffset] = @_currentSlideOffsets()
|
||||
[width, height] = @_currentSlideDimensions()
|
||||
|
||||
tool = new model(@raphaelObj)
|
||||
# TODO: why are the parameters inverted and it works?
|
||||
tool.setPaperSize(slideHeight, slideWidth)
|
||||
tool.setOffsets(xOffset, yOffset)
|
||||
tool.setPaperDimensions(width,height)
|
||||
tool
|
||||
else
|
||||
null
|
||||
|
||||
# Adds the base url (the protocol+server part) to `url` if needed.
|
||||
_slideUrl: (url) ->
|
||||
if url?.match(/http[s]?:/)
|
||||
url
|
||||
else
|
||||
console.log "the url did not match the expected format"
|
||||
#globals.presentationServer + url
|
||||
|
||||
#Changes the currently displayed page/slide (if any) with this one
|
||||
#@param {data} message object containing the "presentation" object
|
||||
_displayPage: (data) ->
|
||||
@removeAllImagesFromPaper()
|
||||
|
||||
# get dimensions for available whiteboard space
|
||||
# get where to start from the left -> either the end of the user's list or the left edge of the screen
|
||||
# if getInSession "display_usersList" then xBegin = $("#userListContainer").width()
|
||||
# else xBegin = 0
|
||||
# # get where to start from the right -> either the beginning of the chat bar or the right edge of the screen
|
||||
# if getInSession "display_chatbar" then xEnd = $("#chat").position().left
|
||||
# else xEnd = $( document ).width();
|
||||
|
||||
# # find the height to start the top of the image at
|
||||
# if getInSession "display_navbar" then yBegin = $("#navbar").height()
|
||||
# else yBegin = 0
|
||||
# yEnd = $( document ).height();
|
||||
|
||||
# # TODO: add some form of padding to the left, right, top, and bottom boundaries
|
||||
# #
|
||||
# boardWidth = xEnd - xBegin
|
||||
# boardHeight = yEnd - yBegin
|
||||
|
||||
boardWidth = @containerWidth
|
||||
boardHeight = @containerHeight
|
||||
|
||||
currentPresentation = Meteor.Presentations.findOne({"presentation.current": true})
|
||||
presentationId = currentPresentation?.presentation?.id
|
||||
currentSlide = Meteor.Slides.findOne({"presentationId": presentationId, "slide.current": true})
|
||||
|
||||
# TODO currentSlide undefined in some cases - will check later why
|
||||
imageWidth = boardWidth * (currentSlide?.slide.width_ratio/100) or boardWidth
|
||||
imageHeight = boardHeight * (currentSlide?.slide.height_ratio/100) or boardHeight
|
||||
|
||||
# console.log "xBegin: #{xBegin}"
|
||||
# console.log "xEnd: #{xEnd}"
|
||||
# console.log "yBegin: #{yBegin}"
|
||||
# console.log "yEnd: #{yEnd}"
|
||||
# console.log "boardWidth: #{boardWidth}"
|
||||
# console.log "boardHeight: #{boardHeight}"
|
||||
console.log "imageWidth: #{imageWidth}"
|
||||
console.log "imageHeight: #{imageHeight}"
|
||||
|
||||
# @addImageToPaper(data, imageWidth, imageHeight) # TODO the dimensions should be modified
|
||||
@addImageToPaper(data, imageWidth, imageHeight)
|
@ -0,0 +1,145 @@
|
||||
# A rectangle in the whiteboard
|
||||
class @WhiteboardRectModel extends WhiteboardToolModel
|
||||
constructor: (@paper) ->
|
||||
super @paper
|
||||
|
||||
# the defintion of this shape, kept so we can redraw the shape whenever needed
|
||||
# format: x1, y1, x2, y2, stroke color, thickness
|
||||
@definition = [0, 0, 0, 0, "#000", "0px"]
|
||||
@paper
|
||||
|
||||
# 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) =>
|
||||
x = startingData.points[0]
|
||||
y = startingData.points[1]
|
||||
color = startingData.color
|
||||
thickness = startingData.thickness
|
||||
|
||||
@obj = @paper.rect(x * @gw + @xOffset, y * @gh + @yOffset, 0, 0, 1)
|
||||
@obj.attr Meteor.call("strokeAndThickness",color, thickness)
|
||||
@definition =
|
||||
shape: "rect"
|
||||
data: [x, y, 0, 0, @obj.attrs["stroke"], @obj.attrs["stroke-width"]]
|
||||
@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) ->
|
||||
|
||||
x1 = startingData.points[0]
|
||||
y1 = startingData.points[1]
|
||||
x2 = startingData.points[2]
|
||||
y2 = startingData.points[3]
|
||||
|
||||
square = startingData.square
|
||||
if @obj?
|
||||
[x1, x2] = [x2, x1] if x2 < x1
|
||||
[x1, x2] = [x2, x1] if x2 < x1
|
||||
|
||||
if y2 < y1
|
||||
[y1, y2] = [y2, y1]
|
||||
reversed = true
|
||||
|
||||
if square
|
||||
if reversed #if reveresed, the y1 coordinate gets updated, not the y2 coordinate
|
||||
y1 = y2 - (x2 - x1) * @gw / @gh
|
||||
else
|
||||
y2 = y1 + (x2 - x1) * @gw / @gh
|
||||
|
||||
x = x1 * @gw + @xOffset
|
||||
y = y1 * @gh + @yOffset
|
||||
width = (x2 * @gw + @xOffset) - x
|
||||
height = (y2 * @gh + @yOffset) - y
|
||||
#if !square
|
||||
@obj.attr
|
||||
x: x
|
||||
y: y
|
||||
width: width
|
||||
height: height
|
||||
###else
|
||||
@obj.attr
|
||||
x: x
|
||||
y: y
|
||||
width: width
|
||||
height: width###
|
||||
|
||||
# we need to update all these values, specially for when shapes are drawn backwards
|
||||
@definition.data[0] = x1
|
||||
@definition.data[1] = y1
|
||||
@definition.data[2] = x2
|
||||
@definition.data[3] = y2
|
||||
|
||||
# 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) ->
|
||||
[x1, x2] = [x2, x1] if x2 < x1
|
||||
[y1, y2] = [y2, y1] if y2 < y1
|
||||
|
||||
x = x1 * @gw
|
||||
y = y1 * @gh
|
||||
r = @paper.rect(x + @xOffset, y + @yOffset, (x2 * @gw) - x, (y2 * @gh) - y, 1)
|
||||
r.attr Meteor.call("strokeAndThickness", colour, thickness)
|
||||
r
|
||||
|
||||
# Creating a rectangle has started
|
||||
# @param {number} x the x value of cursor at the time in relation to the left side of the browser
|
||||
# @param {number} y the y value of cursor at the time in relation to the top of the browser
|
||||
# TODO: moved here but not finished
|
||||
dragOnStart: (x, y) ->
|
||||
# sx = (@paperWidth - @gw) / 2
|
||||
# sy = (@paperHeight - @gh) / 2
|
||||
# # find the x and y values in relation to the whiteboard
|
||||
# @cx2 = (x - @containerOffsetLeft - sx + @xOffset) / @paperWidth
|
||||
# @cy2 = (y - @containerOffsetTop - sy + @yOffset) / @paperHeight
|
||||
# globals.connection.emitMakeShape "rect",
|
||||
# [ @cx2, @cy2, @currentColour, @currentThickness ]
|
||||
|
||||
# Adjusting rectangle continues
|
||||
# @param {number} dx the difference in the x value at the start as opposed to the x value now
|
||||
# @param {number} dy the difference in the y value at the start as opposed to the y value now
|
||||
# @param {number} x the x value of cursor at the time in relation to the left side of the browser
|
||||
# @param {number} y the y value of cursor at the time in relation to the top of the browser
|
||||
# @param {Event} e the mouse event
|
||||
# TODO: moved here but not finished
|
||||
dragOnMove: (dx, dy, x, y, e) ->
|
||||
# # if shift is pressed, make it a square
|
||||
# dy = dx if @shiftPressed
|
||||
# dx = dx / @paperWidth
|
||||
# dy = dy / @paperHeight
|
||||
# # adjust for negative values as well
|
||||
# if dx >= 0
|
||||
# x1 = @cx2
|
||||
# else
|
||||
# x1 = @cx2 + dx
|
||||
# dx = -dx
|
||||
# if dy >= 0
|
||||
# y1 = @cy2
|
||||
# else
|
||||
# y1 = @cy2 + dy
|
||||
# dy = -dy
|
||||
# globals.connection.emitUpdateShape "rect", [ x1, y1, dx, dy ]
|
||||
|
||||
# When rectangle finished being drawn
|
||||
# @param {Event} e the mouse event
|
||||
# TODO: moved here but not finished
|
||||
dragOnEnd: (e) ->
|
||||
# if @obj?
|
||||
# attrs = @obj.attrs
|
||||
# if attrs?
|
||||
# globals.connection.emitPublishShape "rect",
|
||||
# [ attrs.x / @gw, attrs.y / @gh, attrs.width / @gw, attrs.height / @gh,
|
||||
# @currentColour, @currentThickness ]
|
||||
# @obj = null
|
@ -0,0 +1,21 @@
|
||||
# A slide in the whiteboard
|
||||
class @WhiteboardSlideModel
|
||||
|
||||
# TODO: check if we really need original and display width and heights separate or if they can be the same
|
||||
constructor: (@id, @url, @img, @originalWidth, @originalHeight, @displayWidth, @displayHeight, @xOffset=0, @yOffset=0) ->
|
||||
|
||||
getWidth: -> @displayWidth
|
||||
|
||||
getHeight: -> @displayHeight
|
||||
|
||||
getOriginalWidth: -> @originalWidth
|
||||
|
||||
getOriginalHeight: -> @originalHeight
|
||||
|
||||
getId: -> @id
|
||||
|
||||
getDimensions: -> [@getWidth(), @getHeight()]
|
||||
|
||||
getOriginalDimensions: -> [@getOriginalWidth(), @getOriginalHeight()]
|
||||
|
||||
getOffsets: -> [@xOffset, @yOffset]
|
@ -0,0 +1,250 @@
|
||||
# A text in the whiteboard
|
||||
class @WhiteboardTextModel extends WhiteboardToolModel
|
||||
|
||||
constructor: (@paper) ->
|
||||
super @paper
|
||||
# the defintion of this shape, kept so we can redraw the shape whenever needed
|
||||
# format: x, y, width, height, colour, fontSize, calcFontSize, text
|
||||
@definition = [0, 0, 0, 0, "#000", 0, 0, ""]
|
||||
|
||||
# Make a text on the whiteboard
|
||||
make: (startingData) ->
|
||||
console.log "making a text:" + JSON.stringify startingData
|
||||
|
||||
x = startingData.x
|
||||
y = startingData.y
|
||||
width = startingData.textBoxWidth
|
||||
height = startingData.textBoxHeight
|
||||
colour = startingData.fontColor
|
||||
fontSize = startingData.fontSize
|
||||
calcFontSize = startingData.calcedFontSize
|
||||
text = startingData.text
|
||||
|
||||
@definition =
|
||||
shape: "text"
|
||||
data: [x, y, width, height, colour, fontSize, calcFontSize, text]
|
||||
|
||||
#calcFontSize = (calcFontSize/100 * @gh)
|
||||
x = (x * @gw) + @xOffset
|
||||
y = (y * @gh) + @yOffset + calcFontSize
|
||||
width = width/100 * @gw
|
||||
colour = Meteor.call("strokeAndThickness",colour, false)
|
||||
|
||||
@obj = @paper.text(x/100, y/100, "")
|
||||
@obj.attr
|
||||
fill: Meteor.call("strokeAndThickness",colour, false)
|
||||
"font-family": "Arial" # TODO: make dynamic
|
||||
"font-size": calcFontSize
|
||||
@obj.node.style["text-anchor"] = "start" # force left align
|
||||
@obj.node.style["textAnchor"] = "start" # for firefox, 'cause they like to be different
|
||||
@obj
|
||||
|
||||
# Update text shape drawn
|
||||
# @param {object} the object containing the shape info
|
||||
update: (startingData) ->
|
||||
console.log "updating text" + JSON.stringify startingData
|
||||
|
||||
x = startingData.x
|
||||
y = startingData.y
|
||||
maxWidth = startingData.textBoxWidth
|
||||
height = startingData.textBoxHeight
|
||||
colour = startingData.fontColor
|
||||
fontSize = startingData.fontSize
|
||||
calcFontSize = startingData.calcedFontSize
|
||||
myText = startingData.text
|
||||
|
||||
svgNS = "http://www.w3.org/2000/svg"
|
||||
|
||||
if @obj?
|
||||
@definition.data = [x, y, maxWidth, height, colour, fontSize, calcFontSize, myText]
|
||||
|
||||
calcFontSize = (calcFontSize/100 * @gh)
|
||||
x = (x * @gw)/100 + @xOffset
|
||||
maxWidth = maxWidth/100 * @gw
|
||||
|
||||
@obj.attr
|
||||
fill: "#000" #Meteor.call("strokeAndThickness",colour, false)
|
||||
"font-size": calcFontSize
|
||||
cell = @obj.node
|
||||
while cell? and cell.hasChildNodes()
|
||||
cell.removeChild(cell.firstChild)
|
||||
|
||||
#extract and add line breaks for start
|
||||
dashArray = new Array()
|
||||
dashFound = true
|
||||
indexPos = 0
|
||||
cumulY = 0
|
||||
while dashFound is true
|
||||
result = myText.indexOf("-", indexPos)
|
||||
if result is -1
|
||||
|
||||
#could not find a dash
|
||||
dashFound = false
|
||||
else
|
||||
dashArray.push result
|
||||
indexPos = result + 1
|
||||
|
||||
#split the text at all spaces and dashes
|
||||
words = myText.split(/[\s-]/)
|
||||
line = ""
|
||||
dy = 0
|
||||
curNumChars = 0
|
||||
computedTextLength = 0
|
||||
myTextNode = undefined
|
||||
tspanEl = undefined
|
||||
lastLineBreak = 0
|
||||
i = 0
|
||||
while i < words.length
|
||||
word = words[i]
|
||||
curNumChars += word.length + 1
|
||||
if computedTextLength > maxWidth or i is 0
|
||||
if computedTextLength > maxWidth
|
||||
tempText = tspanEl.firstChild.nodeValue
|
||||
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
|
||||
|
||||
#alternatively one could use textLength and lengthAdjust, however, currently this is not too well supported in SVG UA's
|
||||
tspanEl = document.createElementNS(svgNS, "tspan")
|
||||
tspanEl.setAttributeNS null, "x", x
|
||||
tspanEl.setAttributeNS null, "dy", dy
|
||||
myTextNode = document.createTextNode(line)
|
||||
tspanEl.appendChild myTextNode
|
||||
cell.appendChild tspanEl
|
||||
if checkDashPosition(dashArray, curNumChars - 1)
|
||||
line = word + "-"
|
||||
else
|
||||
line = word + " "
|
||||
line = words[i - 1] + " " + line unless i is 0
|
||||
dy = calcFontSize
|
||||
cumulY += dy
|
||||
else
|
||||
if checkDashPosition(dashArray, curNumChars - 1)
|
||||
line += word + "-"
|
||||
else
|
||||
line += word + " "
|
||||
tspanEl.firstChild.nodeValue = line
|
||||
computedTextLength = tspanEl.getComputedTextLength()
|
||||
if i is words.length - 1
|
||||
if computedTextLength > maxWidth
|
||||
tempText = tspanEl.firstChild.nodeValue
|
||||
tspanEl.firstChild.nodeValue = tempText.slice(0, (tempText.length - words[i].length - 1))
|
||||
tspanEl = document.createElementNS(svgNS, "tspan")
|
||||
tspanEl.setAttributeNS null, "x", x
|
||||
tspanEl.setAttributeNS null, "dy", dy
|
||||
myTextNode = document.createTextNode(words[i])
|
||||
tspanEl.appendChild myTextNode
|
||||
cell.appendChild tspanEl
|
||||
i++
|
||||
cumulY
|
||||
|
||||
|
||||
#this function checks if there should be a dash at the given position, instead of a blank
|
||||
checkDashPosition = (dashArray, pos) ->
|
||||
result = false
|
||||
i = 0
|
||||
|
||||
while i < dashArray.length
|
||||
result = true if dashArray[i] is pos
|
||||
i++
|
||||
result
|
||||
|
||||
|
||||
# Draw a text on the whiteboard
|
||||
# @param {string} colour the colour of the object
|
||||
# @param {number} thickness the thickness of the object's line(s)
|
||||
# draw: (x, y, width, height, colour, fontSize, calcFontSize, text) ->
|
||||
# calcFontSize = (calcFontSize/100 * @gh)
|
||||
# x = x * @gw + @xOffset
|
||||
# y = (y * @gh) + @yOffset + calcFontSize
|
||||
# width = width/100 * @gw
|
||||
# #colour = Utils.strokeAndThickness(colour)["stroke"]
|
||||
|
||||
|
||||
# el = @paper.text(x, y, "")
|
||||
# el.attr
|
||||
# fill: Meteor.call("strokeAndThickness",colour, false)
|
||||
# "font-family": "Arial" # TODO: make dynamic
|
||||
# "font-size": calcFontSize
|
||||
# el.node.style["text-anchor"] = "start" # force left align
|
||||
# el.node.style["textAnchor"] = "start" # for firefox, 'cause they like to be different
|
||||
# Meteor.call("textFlow", text, el.node, width, x, calcFontSize, false)
|
||||
# el
|
||||
|
||||
# When first dragging the mouse to create the textbox size
|
||||
# @param {number} x the x value of cursor at the time in relation to the left side of the browser
|
||||
# @param {number} y the y value of cursor at the time in relation to the top of the browser
|
||||
# TODO: moved here but not finished nor tested
|
||||
# _textStart: (x, y) ->
|
||||
# [sw, sh] = @_currentSlideDimensions()
|
||||
# [cx, cy] = @_currentSlideOffsets()
|
||||
# if @currentText?
|
||||
# globals.connection.emitPublishShape "text",
|
||||
# [ @textbox.value, @currentText.attrs.x / @gw, @currentText.attrs.y / @gh,
|
||||
# @textbox.clientWidth, 16, @currentColour, "Arial", 14 ]
|
||||
# globals.connection.emitTextDone()
|
||||
# @textbox.value = ""
|
||||
# @textbox.style.visibility = "hidden"
|
||||
# @textX = x
|
||||
# @textY = y
|
||||
# sx = (@containerWidth - @gw) / 2
|
||||
# sy = (@containerHeight - @gh) / 2
|
||||
# @cx2 = (x - @containerOffsetLeft - sx + cx) / sw
|
||||
# @cy2 = (y - @containerOffsetTop - sy + cy) / sh
|
||||
# @_makeRect @cx2, @cy2, "#000", 1
|
||||
# globals.connection.emitMakeShape "rect", [ @cx2, @cy2, "#000", 1 ]
|
||||
|
||||
# Finished drawing the rectangle that the text will fit into
|
||||
# @param {Event} e the mouse event
|
||||
# TODO: moved here but not finished nor tested
|
||||
# _textStop: (e) ->
|
||||
# @currentRect.hide() if @currentRect?
|
||||
# [sw, sh] = @_currentSlideDimensions()
|
||||
# [cx, cy] = @_currentSlideOffsets()
|
||||
# tboxw = (e.pageX - @textX)
|
||||
# tboxh = (e.pageY - @textY)
|
||||
# if tboxw >= 14 or tboxh >= 14 # restrict size
|
||||
# @textbox.style.width = tboxw * (@gw / sw) + "px"
|
||||
# @textbox.style.visibility = "visible"
|
||||
# @textbox.style["font-size"] = 14 + "px"
|
||||
# @textbox.style["fontSize"] = 14 + "px" # firefox
|
||||
# @textbox.style.color = @currentColour
|
||||
# @textbox.value = ""
|
||||
# sx = (@containerWidth - @gw) / 2
|
||||
# sy = (@containerHeight - @gh) / 2
|
||||
# x = @textX - @containerOffsetLeft - sx + cx + 1 # 1px random padding
|
||||
# y = @textY - @containerOffsetTop - sy + cy
|
||||
# @textbox.focus()
|
||||
|
||||
# # if you click outside, it will automatically sumbit
|
||||
# @textbox.onblur = (e) =>
|
||||
# if @currentText
|
||||
# globals.connection.emitPublishShape "text",
|
||||
# [ @value, @currentText.attrs.x / @gw, @currentText.attrs.y / @gh,
|
||||
# @textbox.clientWidth, 16, @currentColour, "Arial", 14 ]
|
||||
# globals.connection.emitTextDone()
|
||||
# @textbox.value = ""
|
||||
# @textbox.style.visibility = "hidden"
|
||||
|
||||
# # if user presses enter key, then automatically submit
|
||||
# @textbox.onkeypress = (e) ->
|
||||
# if e.keyCode is "13"
|
||||
# e.preventDefault()
|
||||
# e.stopPropagation()
|
||||
# @onblur()
|
||||
|
||||
# # update everyone with the new text at every change
|
||||
# _paper = @
|
||||
# @textbox.onkeyup = (e) ->
|
||||
# @style.color = _paper.currentColour
|
||||
# @value = @value.replace(/\n{1,}/g, " ").replace(/\s{2,}/g, " ")
|
||||
# globals.connection.emitUpdateShape "text",
|
||||
# [ @value, x / _paper.sw, (y + (14 * (_paper.sh / _paper.gh))) / _paper.sh,
|
||||
# tboxw * (_paper.gw / _paper.sw), 16, _paper.currentColour, "Arial", 14 ]
|
||||
|
||||
# The server has said the text is finished,
|
||||
# so set it to null for the next text object
|
||||
# TODO: moved here but not finished nor tested
|
||||
# textDone: ->
|
||||
# if @currentText?
|
||||
# @currentText = null
|
||||
# @currentRect.hide() if @currentRect?
|
105
labs/meteor-client/client/whiteboard_models/whiteboard_triangle.coffee
Executable file
105
labs/meteor-client/client/whiteboard_models/whiteboard_triangle.coffee
Executable file
@ -0,0 +1,105 @@
|
||||
# A triangle in the whiteboard
|
||||
class @WhiteboardTriangleModel extends WhiteboardToolModel
|
||||
|
||||
constructor: (@paper) ->
|
||||
console.log "Whiteboard - Creating rectangle"
|
||||
super @paper
|
||||
|
||||
# the defintion of this shape, kept so we can redraw the shape whenever needed
|
||||
# format: x1, y1, x2, y2, stroke color, thickness
|
||||
@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) ->
|
||||
if info?.points?
|
||||
x = info.points[0]
|
||||
y = info.points[1]
|
||||
color = info.color
|
||||
thickness = info.thickness
|
||||
|
||||
path = @_buildPath(x, y, x, y, x, y)
|
||||
@obj = @paper.path(path)
|
||||
@obj.attr Meteor.call("strokeAndThickness", color, thickness)
|
||||
@obj.attr({"stroke-linejoin": "round"})
|
||||
|
||||
@definition = [x, y, x, y, @obj.attrs["stroke"], @obj.attrs["stroke-width"]]
|
||||
|
||||
@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) ->
|
||||
console.log "Whiteboard - updating triangle points"
|
||||
if info?.points?
|
||||
x1 = info.points[0]
|
||||
y1 = info.points[1]
|
||||
x2 = info.points[2]
|
||||
y2 = info.points[3]
|
||||
|
||||
if @obj?
|
||||
[xTop, yTop, xBottomLeft, yBottomLeft, xBottomRight, yBottomRight] = @_getCornersFromPoints(x1, y1, x2, y2)
|
||||
|
||||
path = @_buildPath(xTop * @gw + @xOffset, yTop * @gh + @yOffset,
|
||||
xBottomLeft * @gw + @xOffset, yBottomLeft * @gh + @yOffset,
|
||||
xBottomRight * @gw + @xOffset, yBottomRight * @gh + @yOffset)
|
||||
@obj.attr path: path
|
||||
|
||||
@definition[0] = x1
|
||||
@definition[1] = y1
|
||||
@definition[2] = x2
|
||||
@definition[3] = y2
|
||||
|
||||
# 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) ->
|
||||
[xTop, yTop, xBottomLeft, yBottomLeft, xBottomRight, yBottomRight] = @_getCornersFromPoints(x1, y1, x2, y2)
|
||||
path = @_buildPath(xTop, yTop, xBottomLeft, yBottomLeft, xBottomRight, yBottomRight)
|
||||
path = @_scaleTrianglePath(path, @gw, @gh, @xOffset, @yOffset)
|
||||
triangle = @paper.path(path)
|
||||
triangle.attr Utils.strokeAndThickness(colour, thickness)
|
||||
triangle.attr({"stroke-linejoin": "round"})
|
||||
triangle
|
||||
|
||||
_getCornersFromPoints: (x1, y1, x2, y2) ->
|
||||
xTop = (((x2 - x1) / 2) + x1)
|
||||
yTop = y1
|
||||
xBottomLeft = x1
|
||||
yBottomLeft = y2
|
||||
xBottomRight = x2
|
||||
yBottomRight = y2
|
||||
[xTop, yTop, xBottomLeft, yBottomLeft, xBottomRight, yBottomRight]
|
||||
|
||||
_buildPath: (xTop, yTop, xBottomLeft, yBottomLeft, xBottomRight, yBottomRight) ->
|
||||
"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=0, yOffset=0) ->
|
||||
path = null
|
||||
points = string.match(/(\d+[.]?\d*)/g)
|
||||
len = points.length
|
||||
j = 0
|
||||
|
||||
# go through each point and multiply it by the new height and width
|
||||
path = "M"
|
||||
while j < len
|
||||
path += "," unless j is 0
|
||||
path += "" + (points[j] * w + xOffset) + "," + (points[j + 1] * h + yOffset)
|
||||
j += 2
|
||||
path + "z"
|
||||
|
||||
WhiteboardTriangleModel
|
22
labs/meteor-client/collections/chat.coffee
Executable file
22
labs/meteor-client/collections/chat.coffee
Executable file
@ -0,0 +1,22 @@
|
||||
Meteor.methods
|
||||
addChatToCollection: (meetingId, messageObject) ->
|
||||
entry =
|
||||
meetingId: meetingId
|
||||
message:
|
||||
chat_type: messageObject.chat_type
|
||||
message: messageObject.message
|
||||
to_username: messageObject.to_username
|
||||
from_tz_offset: messageObject.from_tz_offset
|
||||
from_color: messageObject.from_color
|
||||
to_userid: messageObject.to_userid
|
||||
from_userid: messageObject.from_userid
|
||||
from_time: messageObject.from_time
|
||||
from_username: messageObject.from_username
|
||||
from_lang: messageObject.from_lang
|
||||
|
||||
id = Meteor.Chat.insert(entry)
|
||||
console.log "added chat id=[#{id}]:#{messageObject.message}. Chat.size is now
|
||||
#{Meteor.Chat.find({meetingId: meetingId}).count()}"
|
||||
|
||||
sendChatMessagetoServer: (meetingId, messageObject) ->
|
||||
Meteor.call "publishChatMessage", meetingId, messageObject
|
6
labs/meteor-client/collections/collections.coffee
Executable file
6
labs/meteor-client/collections/collections.coffee
Executable file
@ -0,0 +1,6 @@
|
||||
Meteor.Users = new Meteor.Collection("bbb_users")
|
||||
Meteor.Chat = new Meteor.Collection("bbb_chat")
|
||||
Meteor.Meetings = new Meteor.Collection("meetings")
|
||||
Meteor.Presentations = new Meteor.Collection("presentations")
|
||||
Meteor.Shapes = new Meteor.Collection("shapes")
|
||||
Meteor.Slides = new Meteor.Collection("slides")
|
19
labs/meteor-client/collections/meetings.coffee
Normal file
19
labs/meteor-client/collections/meetings.coffee
Normal file
@ -0,0 +1,19 @@
|
||||
Meteor.methods
|
||||
addMeetingToCollection: (meetingId, name, recorded) ->
|
||||
console.log "trying to add to Meetings:#{meetingId}|#{name} Meetings.size before:#{Meteor.Meetings.find().count()}"
|
||||
|
||||
#check if the meeting is already in the collection
|
||||
unless Meteor.Meetings.findOne({meetingId: meetingId})?
|
||||
id = Meteor.Meetings.insert(meetingId: meetingId, meetingName: name, recorded: recorded)
|
||||
console.log "added meeting _id=[#{id}]:meetingId=[#{meetingId}]:name=[#{name}].
|
||||
Meetings.size is now #{Meteor.Meetings.find().count()}"
|
||||
|
||||
removeMeetingFromCollection: (meetingId) ->
|
||||
if Meteor.Meetings.findOne({meetingId: meetingId})?
|
||||
if Meteor.Users.find({meetingId: meetingId}).count() isnt 0
|
||||
console.log "\n!!!!!removing a meeting which has active users in it!!!!\n"
|
||||
id = Meteor.Meetings.findOne({meetingId: meetingId})
|
||||
if id?
|
||||
Meteor.Meetings.remove(id._id)
|
||||
console.log "removed from Meetings:#{meetingId} now there are only
|
||||
#{Meteor.Meetings.find().count()} meetings running"
|
26
labs/meteor-client/collections/presentations.coffee
Normal file
26
labs/meteor-client/collections/presentations.coffee
Normal file
@ -0,0 +1,26 @@
|
||||
Meteor.methods
|
||||
addPresentationToCollection: (meetingId, presentationObject) ->
|
||||
#check if the presentation is already in the collection
|
||||
unless Meteor.Presentations.findOne({meetingId: meetingId, 'presentation.id': presentationObject.id})?
|
||||
entry =
|
||||
meetingId: meetingId
|
||||
presentation:
|
||||
id: presentationObject.id
|
||||
name: presentationObject.name
|
||||
current: presentationObject.current
|
||||
|
||||
pointer: #initially we have no data about the cursor
|
||||
x: 0.0
|
||||
y: 0.0
|
||||
|
||||
id = Meteor.Presentations.insert(entry)
|
||||
console.log "added presentation id =[#{id}]:#{presentationObject.id} in #{meetingId}. Presentations.size is now
|
||||
#{Meteor.Presentations.find({meetingId: meetingId}).count()}"
|
||||
|
||||
removePresentationFromCollection: (meetingId, presentationId) ->
|
||||
if meetingId? and presentationId? and Meteor.Presentations.findOne({meetingId: meetingId, "presentation.id": presentationId})?
|
||||
id = Meteor.Presentations.findOne({meetingId: meetingId, "presentation.id": presentationId})
|
||||
if id?
|
||||
Meteor.Presentations.remove(id._id)
|
||||
console.log "----removed presentation[" + presentationId + "] from " + meetingId
|
||||
|
77
labs/meteor-client/collections/shapes.coffee
Normal file
77
labs/meteor-client/collections/shapes.coffee
Normal file
@ -0,0 +1,77 @@
|
||||
Meteor.methods
|
||||
addShapeToCollection: (meetingId, whiteboardId, shapeObject) ->
|
||||
console.log "shapeObject=" + JSON.stringify shapeObject
|
||||
if shapeObject?.shape_type is "text" and shapeObject.status is "textPublished"
|
||||
console.log "we are dealing with a text shape"
|
||||
|
||||
entry =
|
||||
meetingId: meetingId
|
||||
whiteboardId: whiteboardId
|
||||
shape:
|
||||
type: shapeObject.shape.type
|
||||
textBoxHeight: shapeObject.shape.textBoxHeight
|
||||
backgroundColor: shapeObject.shape.backgroundColor
|
||||
fontColor: shapeObject.shape.fontColor
|
||||
status: shapeObject.shape.status
|
||||
dataPoints: shapeObject.shape.dataPoints
|
||||
x: shapeObject.shape.x
|
||||
textBoxWidth: shapeObject.shape.textBoxWidth
|
||||
whiteboardId: shapeObject.shape.whiteboardId
|
||||
fontSize: shapeObject.shape.fontSize
|
||||
id: shapeObject.shape.id
|
||||
y: shapeObject.shape.y
|
||||
calcedFontSize: shapeObject.shape.calcedFontSize
|
||||
text: shapeObject.shape.text
|
||||
background: shapeObject.shape.background
|
||||
|
||||
id = Meteor.Shapes.insert(entry)
|
||||
numShapesOnSlide = Meteor.Shapes.find({meetingId: meetingId, whiteboardId: whiteboardId}).fetch().length
|
||||
console.log "added textShape id =[#{id}]:#{shapeObject.id} in #{meetingId} || now there are #{numShapesOnSlide} shapes on the slide"
|
||||
|
||||
else
|
||||
if shapeObject?.status is "DRAW_END" #the mouse button was released - the drawing is complete
|
||||
entry =
|
||||
meetingId: meetingId
|
||||
whiteboardId: whiteboardId
|
||||
shape:
|
||||
wb_id: shapeObject.wb_id
|
||||
shape_type: shapeObject.shape_type
|
||||
status: shapeObject.status
|
||||
id: shapeObject.id
|
||||
shape:
|
||||
type: shapeObject.shape.type
|
||||
status: shapeObject.shape.status
|
||||
points: shapeObject.shape.points
|
||||
whiteboardId: shapeObject.shape.whiteboardId
|
||||
id: shapeObject.shape.id
|
||||
square: shapeObject.shape.square
|
||||
transparency: shapeObject.shape.transparency
|
||||
thickness: shapeObject.shape.thickness
|
||||
color: shapeObject.shape.color
|
||||
|
||||
id = Meteor.Shapes.insert(entry)
|
||||
numShapesOnSlide = Meteor.Shapes.find({meetingId: meetingId, whiteboardId: whiteboardId}).fetch().length
|
||||
console.log "added shape id =[#{id}]:#{shapeObject.id} in #{meetingId} || now there are #{numShapesOnSlide} shapes on the slide"
|
||||
|
||||
removeAllShapesFromSlide: (meetingId, whiteboardId) ->
|
||||
console.log "removeAllShapesFromSlide__" + whiteboardId
|
||||
if meetingId? and whiteboardId? and Meteor.Shapes.find({meetingId: meetingId, whiteboardId: whiteboardId})?
|
||||
shapesOnSlide = Meteor.Shapes.find({meetingId: meetingId, whiteboardId: whiteboardId}).fetch()
|
||||
console.log "number of shapes:" + shapesOnSlide.length
|
||||
for s in shapesOnSlide
|
||||
console.log "shape=" + s.shape.id
|
||||
id = Meteor.Shapes.findOne({meetingId: meetingId, whiteboardId: whiteboardId, "shape.id": s.shape.id})
|
||||
if id?
|
||||
Meteor.Shapes.remove(id._id)
|
||||
console.log "----removed shape[" + s.shape.id + "] from " + whiteboardId
|
||||
console.log "remaining shapes on the slide:" + Meteor.Shapes.find({meetingId: meetingId, whiteboardId: whiteboardId}).fetch().length
|
||||
|
||||
removeShapeFromSlide: (meetingId, whiteboardId, shapeId) ->
|
||||
shapeToRemove = Meteor.Shapes.findOne({meetingId: meetingId, whiteboardId: whiteboardId, "shape.id": shapeId})
|
||||
if meetingId? and whiteboardId? and shapeId? and shapeToRemove?
|
||||
Meteor.Shapes.remove(shapeToRemove._id)
|
||||
console.log "----removed shape[" + shapeId + "] from " + whiteboardId
|
||||
console.log "remaining shapes on the slide:" + Meteor.Shapes.find({meetingId: meetingId, whiteboardId: whiteboardId}).count()
|
||||
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user