diff --git a/db/manifest b/db/manifest
index 6f303be21..1250059b1 100755
--- a/db/manifest
+++ b/db/manifest
@@ -326,4 +326,5 @@ profile_teacher.sql
populate_languages.sql
populate_subjects.sql
reviews.sql
-download_tracker_fingerprints.sql
\ No newline at end of file
+download_tracker_fingerprints.sql
+connection_active.sql
\ No newline at end of file
diff --git a/db/up/connection_active.sql b/db/up/connection_active.sql
new file mode 100644
index 000000000..085021aaf
--- /dev/null
+++ b/db/up/connection_active.sql
@@ -0,0 +1 @@
+ALTER TABLE connections ADD COLUMN user_active BOOLEAN DEFAULT TRUE;
\ No newline at end of file
diff --git a/pb/src/client_container.proto b/pb/src/client_container.proto
index aa9fe6a12..250bede8e 100644
--- a/pb/src/client_container.proto
+++ b/pb/src/client_container.proto
@@ -21,6 +21,7 @@ message ClientMessage {
UNSUBSCRIBE = 137;
SUBSCRIPTION_MESSAGE = 138;
SUBSCRIBE_BULK = 139;
+ USER_STATUS = 141;
// friend notifications
FRIEND_UPDATE = 140;
@@ -67,6 +68,7 @@ message ClientMessage {
// text message
TEXT_MESSAGE = 236;
CHAT_MESSAGE = 237;
+ SEND_CHAT_MESSAGE = 238;
MUSICIAN_SESSION_FRESH = 240;
MUSICIAN_SESSION_STALE = 245;
@@ -131,6 +133,7 @@ message ClientMessage {
optional Unsubscribe unsubscribe = 137;
optional SubscriptionMessage subscription_message = 138;
optional SubscribeBulk subscribe_bulk = 139;
+ optional UserStatus user_status = 141;
// friend notifications
optional FriendUpdate friend_update = 140; // from server to all friends of user
@@ -178,6 +181,7 @@ message ClientMessage {
// text message
optional TextMessage text_message = 236;
optional ChatMessage chat_message = 237;
+ optional SendChatMessage send_chat_message = 238;
optional MusicianSessionFresh musician_session_fresh = 240;
optional MusicianSessionStale musician_session_stale = 245;
@@ -569,6 +573,12 @@ message ChatMessage {
optional string msg = 3;
optional string msg_id = 4;
optional string created_at = 5;
+ optional string channel = 6;
+}
+
+message SendChatMessage {
+ optional string msg = 1;
+ optional string channel = 2;
}
// route_to: client:
@@ -657,6 +667,11 @@ message SubscribeBulk {
//repeated Subscription subscriptions = 1; # the ruby protocol buffer library chokes on this. so we have to do the above
}
+message UserStatus {
+ optional bool active = 1; // same as heartbeat 'active'... does the user appear present
+ optional string status = 2;
+}
+
// route_to: session
// a test message used by ruby-client currently. just gives way to send out to rest of session
message TestSessionMessage {
@@ -693,6 +708,7 @@ message TestClientMessage {
message Heartbeat {
optional string notification_seen = 1;
optional string notification_seen_at = 2;
+ optional bool active = 3; // is the user active?
}
// target: client
diff --git a/ruby/lib/jam_ruby/jam_track_importer.rb b/ruby/lib/jam_ruby/jam_track_importer.rb
index c6a6549b3..87637ddc8 100644
--- a/ruby/lib/jam_ruby/jam_track_importer.rb
+++ b/ruby/lib/jam_ruby/jam_track_importer.rb
@@ -1650,11 +1650,11 @@ module JamRuby
output = cmd("soxi -r \"#{track.wav_file}\"", "get_sample_rate")
sample_rate = output.to_i
- create_silence(tmp_dir, "padded_silence#{track.id}", amount, sample_rate, channels)
+ padding_file = create_silence(tmp_dir, "padded_silence#{track.id}", amount, sample_rate, channels)
output_file = File.join(tmp_dir, "with_padding_#{track.id}.wav")
- cmd("sox \"#{track.wav_file}\" \"#{output_file}\"", "same_lengthening")
+ cmd("sox \"#{track.wav_file}\" \"#{padding_file}\" \"#{output_file}\"", "same_lengthening")
track.wav_file = output_file
end
diff --git a/ruby/lib/jam_ruby/message_factory.rb b/ruby/lib/jam_ruby/message_factory.rb
index 94e86af4f..dff7e726b 100644
--- a/ruby/lib/jam_ruby/message_factory.rb
+++ b/ruby/lib/jam_ruby/message_factory.rb
@@ -897,19 +897,26 @@ module JamRuby
)
end
- # creates the session chat message
- def chat_message(session_id, sender_name, sender_id, msg, msg_id, created_at)
+ # creates the chat message
+ def chat_message(session_id, sender_name, sender_id, msg, msg_id, created_at, channel)
chat_message = Jampb::ChatMessage.new(
:sender_id => sender_id,
:sender_name => sender_name,
:msg => msg,
:msg_id => msg_id,
- :created_at => created_at
+ :created_at => created_at,
+ :channel => channel
)
+ if session_id
+ route_to = SESSION_TARGET_PREFIX + session_id
+ else
+ route_to = ALL_ACTIVE_CLIENTS
+ end
+
Jampb::ClientMessage.new(
:type => ClientMessage::Type::CHAT_MESSAGE,
- :route_to => SESSION_TARGET_PREFIX + session_id,
+ :route_to => route_to,
:chat_message => chat_message
)
end
diff --git a/ruby/lib/jam_ruby/models/chat_message.rb b/ruby/lib/jam_ruby/models/chat_message.rb
index 52046d7b3..005187415 100644
--- a/ruby/lib/jam_ruby/models/chat_message.rb
+++ b/ruby/lib/jam_ruby/models/chat_message.rb
@@ -50,7 +50,8 @@ module JamRuby
user.id,
chat_msg.message,
chat_msg.id,
- chat_msg.created_at.utc.iso8601
+ chat_msg.created_at.utc.iso8601,
+ 'session'
)
@@mq_router.server_publish_to_session(music_session, msg, sender = {:client_id => client_id})
diff --git a/web/app/assets/javascripts/AAB_message_factory.js b/web/app/assets/javascripts/AAB_message_factory.js
index 2a502f43e..cc75ebbb4 100644
--- a/web/app/assets/javascripts/AAB_message_factory.js
+++ b/web/app/assets/javascripts/AAB_message_factory.js
@@ -20,6 +20,7 @@
UNSUBSCRIBE : "UNSUBSCRIBE",
SUBSCRIPTION_MESSAGE : "SUBSCRIPTION_MESSAGE",
SUBSCRIBE_BULK : "SUBSCRIBE_BULK",
+ USER_STATUS : "USER_STATUS",
// friend notifications
FRIEND_UPDATE : "FRIEND_UPDATE",
@@ -66,6 +67,7 @@
// text message
TEXT_MESSAGE : "TEXT_MESSAGE",
CHAT_MESSAGE : "CHAT_MESSAGE",
+ SEND_CHAT_MESSAGE : "SEND_CHAT_MESSAGE",
// broadcast notifications
SOURCE_UP_REQUESTED : "SOURCE_UP_REQUESTED",
@@ -120,13 +122,30 @@
};
// Heartbeat message
- factory.heartbeat = function(lastNotificationSeen, lastNotificationSeenAt) {
+ factory.heartbeat = function(lastNotificationSeen, lastNotificationSeenAt, active) {
var data = {};
data.notification_seen = lastNotificationSeen;
data.notification_seen_at = lastNotificationSeenAt;
+ data.active = active;
return client_container(msg.HEARTBEAT, route_to.SERVER, data);
};
+ // User Status update message
+ factory.userStatus = function(active, status) {
+ var data = {};
+ data.active = active;
+ data.status = status;
+ return client_container(msg.USER_STATUS, route_to.SERVER, data);
+ };
+
+ factory.chatMessage = function(channel, msg) {
+ var data = {}
+ data.channel = {}
+ data.msg = {}
+
+ return client_container(msg.SEND_CHAT_MESSAGE, route_to.SERVER, data)
+ }
+
// create a login message using user/pass
factory.login_with_user_pass = function(username, password) {
var login = { username : username , password : password };
diff --git a/web/app/assets/javascripts/JamServer.js b/web/app/assets/javascripts/JamServer.js
index b893ef7c7..7b523dbb5 100644
--- a/web/app/assets/javascripts/JamServer.js
+++ b/web/app/assets/javascripts/JamServer.js
@@ -36,6 +36,7 @@
var notificationLastSeen = undefined;
var clientClosedConnection = false;
var initialConnectAttempt = true;
+ var active = true;
// reconnection logic
var connectDeferred = null;
@@ -46,6 +47,7 @@
var reconnectingWaitPeriodStart = null;
var reconnectDueTime = null;
var connectTimeout = null;
+ var activityTimeout;
// elements
var $inSituBanner = null;
@@ -80,7 +82,6 @@
function initiateReconnect(activeElementVotes, in_error) {
var initialConnect = !!activeElementVotes;
- console.log("activeElementVotes", activeElementVotes)
freezeInteraction = activeElementVotes && ((activeElementVotes.dialog && activeElementVotes.dialog.freezeInteraction === true) || (activeElementVotes.screen && activeElementVotes.screen.freezeInteraction === true));
if (in_error) {
@@ -174,7 +175,8 @@
function _heartbeat() {
if (app.heartbeatActive) {
- var message = context.JK.MessageFactory.heartbeat(notificationLastSeen, notificationLastSeenAt);
+ console.log("heartbeat active?: " + active)
+ var message = context.JK.MessageFactory.heartbeat(notificationLastSeen, notificationLastSeenAt, active);
notificationLastSeenAt = undefined;
notificationLastSeen = undefined;
// for debugging purposes, see if the last time we've sent a heartbeat is way off (500ms) of the target interval
@@ -290,6 +292,35 @@
}, 0)
}
+ function markAway() {
+ logger.debug("sleep again!")
+ active = false;
+ }
+
+ function activityCheck() {
+ var timeoutTime = 300000; // 5 * 1000 * 60 , 5 minutes
+ active = true;
+ activityTimeout = setTimeout(markAway, timeoutTime);
+ $(document).ready(function() {
+ $('body').bind('mousedown keydown touchstart focus', function(event) {
+ if (activityTimeout) {
+ clearTimeout(activityTimeout);
+ activityTimeout = null;
+ }
+
+ if (!active) {
+ if(server && server.connected) {
+ logger.debug("awake again!")
+ var userStatus = msg_factory.userStatus(true, null);
+ server.send(userStatus);
+ }
+ }
+ active = true;
+ activityTimeout = setTimeout(markAway, timeoutTime);
+ });
+ });
+ }
+
function heartbeatAck(header, payload) {
lastHeartbeatAckTime = new Date();
}
@@ -771,6 +802,19 @@
//console.timeEnd('sendP2PMessage');
};
+ server.sendChatMessage = function(message) {
+
+ if (server.connected) {
+
+ var chatMsg = msg_factory.chatMessage(channel, message)
+ server.send(chatMsg)
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+
server.updateNotificationSeen = function (notificationId, notificationCreatedAt) {
var time = new Date(notificationCreatedAt);
@@ -816,6 +860,7 @@
registerHeartbeatAck();
registerServerRejection();
registerSocketClosed();
+ activityCheck();
$inSituBanner = $('.server-connection');
$inSituBannerHolder = $('.no-websocket-connection');
diff --git a/web/app/assets/javascripts/application.js b/web/app/assets/javascripts/application.js
index b99487546..019ddfcda 100644
--- a/web/app/assets/javascripts/application.js
+++ b/web/app/assets/javascripts/application.js
@@ -40,6 +40,7 @@
//= require jquery.visible
//= require jquery.jstarbox
//= require fingerprint2.min
+//= require ResizeSensor
//= require classnames
//= require reflux
//= require howler.core.js
diff --git a/web/app/assets/javascripts/dialog/recordingSelectorDialog.js b/web/app/assets/javascripts/dialog/recordingSelectorDialog.js
index 10e76595b..d34a338f5 100644
--- a/web/app/assets/javascripts/dialog/recordingSelectorDialog.js
+++ b/web/app/assets/javascripts/dialog/recordingSelectorDialog.js
@@ -15,9 +15,10 @@
var feedHelper = new context.JK.Feed(app);
var $scroller = $recordings;
var $content = $recordings;
+ var $recordsHolder = $screen.find('.recordings-content');
var $noMoreFeeds = $screen.find('.end-of-list');
var $empty = $();
- feedHelper.initialize($screen, $scroller, $content, $noMoreFeeds, $empty, $empty, $empty, $empty, {sort: 'date', time_range: 'all', type: 'recording', show_checkbox: true, hide_avatar: true});
+ feedHelper.initialize($screen, $scroller, $recordsHolder, $noMoreFeeds, $empty, $empty, $empty, $empty, {sort: 'date', time_range: 'all', type: 'recording', show_checkbox: true, hide_avatar: true});
function beforeShow(data) {
diff --git a/web/app/assets/javascripts/everywhere/everywhere.js b/web/app/assets/javascripts/everywhere/everywhere.js
index 4adfb59d3..d40ad9186 100644
--- a/web/app/assets/javascripts/everywhere/everywhere.js
+++ b/web/app/assets/javascripts/everywhere/everywhere.js
@@ -41,6 +41,7 @@
trackNewUser();
trackScreenChanges();
+
})
$(document).on('JAMKAZAM_READY', function() {
diff --git a/web/app/assets/javascripts/feedHelper.js b/web/app/assets/javascripts/feedHelper.js
index d7ce6765f..ce7196160 100644
--- a/web/app/assets/javascripts/feedHelper.js
+++ b/web/app/assets/javascripts/feedHelper.js
@@ -13,7 +13,7 @@
var userId = null;
var bandId = null;
var currentFeedPage = 0;
- var feedBatchSize = 10;
+ var feedBatchSize = 4;
var $screen = null;
var $scroller = null;
var $content = null;
@@ -454,7 +454,6 @@
mix_class: feed['has_mix?'] ? 'has-mix' : 'no-mix',
}
- console.log("OPTIONS", options)
var $feedItem = $(context._.template($('#template-feed-recording').html(), options, {variable: 'data'}));
var $controls = $feedItem.find('.recording-controls');
diff --git a/web/app/assets/javascripts/layout.js b/web/app/assets/javascripts/layout.js
index fd39f6b9f..3ea51c8be 100644
--- a/web/app/assets/javascripts/layout.js
+++ b/web/app/assets/javascripts/layout.js
@@ -739,6 +739,13 @@
stackDialogs($dialog, $overlay);
addScreenContextToDialog($dialog)
$dialog.show();
+
+ // maintain center (un-attach previous sensor if applicable, then re-add always)
+ window.ResizeSensor.detach($dialog.get(0))
+ new window.ResizeSensor($dialog, function(){
+ centerDialog(dialog);
+ });
+
dialogEvent(dialog, 'afterShow', options);
$.btOffAll(); // add any prod bubbles if you open a dailog
return $dialog;
diff --git a/web/app/assets/javascripts/react-components.js b/web/app/assets/javascripts/react-components.js
index d02622469..9e82ac707 100644
--- a/web/app/assets/javascripts/react-components.js
+++ b/web/app/assets/javascripts/react-components.js
@@ -9,6 +9,7 @@
//= require ./react-components/stores/SubjectStore
//= require ./react-components/stores/ProfileStore
//= require ./react-components/stores/PlatformStore
+//= require ./react-components/stores/ChatStore
//= require ./react-components/stores/BrowserMediaStore
//= require ./react-components/stores/RecordingStore
//= require ./react-components/stores/VideoStore
diff --git a/web/app/assets/javascripts/react-components/ChatWindow.js.jsx.coffee b/web/app/assets/javascripts/react-components/ChatWindow.js.jsx.coffee
new file mode 100644
index 000000000..415eaae52
--- /dev/null
+++ b/web/app/assets/javascripts/react-components/ChatWindow.js.jsx.coffee
@@ -0,0 +1,16 @@
+context = window
+MIX_MODES = context.JK.MIX_MODES
+
+
+@ChatWindow = React.createClass({
+ mixins: [Reflux.listenTo(@AppStore, "onAppInit"), Reflux.listenTo(@UserStore, "onUserChanged"), Reflux.listenTo(@ChatStore, "onChatChanged")]
+
+ getInitialState: () ->
+ {channels:['global', 'session']}
+
+ render: () ->
+ `
`
+})
\ No newline at end of file
diff --git a/web/app/assets/javascripts/react-components/EditableList.js.jsx.coffee b/web/app/assets/javascripts/react-components/EditableList.js.jsx.coffee
index 6b9b00b71..4844fc15b 100644
--- a/web/app/assets/javascripts/react-components/EditableList.js.jsx.coffee
+++ b/web/app/assets/javascripts/react-components/EditableList.js.jsx.coffee
@@ -21,7 +21,6 @@ logger = context.JK.logger
render: () ->
object_options = []
- logger.debug("Rendering EditableList", this.props, this.props.listItems)
if this.props.listItems? && this.props.listItems.length > 0
for object,i in this.props.listItems
diff --git a/web/app/assets/javascripts/react-components/TeacherExperienceEditableList.js.jsx.coffee b/web/app/assets/javascripts/react-components/TeacherExperienceEditableList.js.jsx.coffee
index 3ac17dd47..ebebdd62f 100644
--- a/web/app/assets/javascripts/react-components/TeacherExperienceEditableList.js.jsx.coffee
+++ b/web/app/assets/javascripts/react-components/TeacherExperienceEditableList.js.jsx.coffee
@@ -11,8 +11,14 @@ logger = context.JK.logger
@root.off("submit", ".teacher-experience-teaching-form").on("submit", ".teacher-experience-teaching-form", @addExperience)
formatListItem: (obj) ->
+
+ if obj.end_year?
+ endYear = obj.end_year
+ else
+ endYear = 'Present'
+
t = "#{obj.name}/#{obj.organization} (#{obj.start_year}"
- t += "-#{obj.end_year}" if this.props.showEndDate
+ t += "-#{endYear}" if this.props.showEndDate
t += ")"
getInitialProps: () ->
@@ -24,7 +30,6 @@ logger = context.JK.logger
addExperience: (e) ->
e.preventDefault()
- logger.debug("addExperience", this.props.listItems, this.props)
$form = e.target
start_year = $("[name='start_year']", $form).val()
diff --git a/web/app/assets/javascripts/react-components/TeacherProfile.js.jsx.coffee b/web/app/assets/javascripts/react-components/TeacherProfile.js.jsx.coffee
index c94a7c7ef..90de1c5d5 100644
--- a/web/app/assets/javascripts/react-components/TeacherProfile.js.jsx.coffee
+++ b/web/app/assets/javascripts/react-components/TeacherProfile.js.jsx.coffee
@@ -152,21 +152,23 @@ proficiencyDescriptionMap = {
`
+ getYoutubeId:(url) ->
+ regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/
+ match = url.match(regExp)
+
+ if match && match[2].length >= 9
+ return match[2]
+ else
+ return 'unknown'
+
+
sampleVideo: (user, teacher) ->
- if teacher.introductory_video?
+ if teacher.introductory_video? && teacher.introductory_video.length > 0
videoUrl = teacher.introductory_video
- if videoUrl.indexOf(window.location.protocol) != 0
- console.log("replacing video")
- if window.location.protocol == 'http:'
- console.log("replacing https: " + videoUrl)
- videoUrl = videoUrl.replace('https://', 'http://')
- console.log("replaced : " + videoUrl)
- else
- videoUrl = videoUrl.replace('http://', 'https://')
-
- videoUrl = videoUrl.replace("watch?v=", "v/")
-
+ # http://stackoverflow.com/a/21607897
+ embedId = @getYoutubeId(videoUrl)
+ embedUrl = "//www.youtube.com/embed/#{embedId}"
return `
Intro Video
@@ -174,7 +176,7 @@ proficiencyDescriptionMap = {
@@ -402,7 +404,7 @@ proficiencyDescriptionMap = {
if teachingExperience.end_year && teachingExperience.end_year > 0
years = "#{teachingExperience.start_year} - #{teachingExperience.end_year}"
else
- years = "#{teachingExperience.start_year} - Present"
+ years = "#{teachingExperience.start_year} - Present"
teachingExperiences.push(`
{years}
@@ -505,7 +507,7 @@ proficiencyDescriptionMap = {
if summary.review_count == 1
reviewCount = '1 review'
else
- reviewCount = sumarry.review_count + ' reviews'
+ reviewCount = summary.review_count + ' reviews'
reviews = []
for review in teacher.recent_reviews
diff --git a/web/app/assets/javascripts/react-components/YearSelect.js.jsx.coffee b/web/app/assets/javascripts/react-components/YearSelect.js.jsx.coffee
index 92a3039f7..483eb347e 100644
--- a/web/app/assets/javascripts/react-components/YearSelect.js.jsx.coffee
+++ b/web/app/assets/javascripts/react-components/YearSelect.js.jsx.coffee
@@ -9,10 +9,10 @@ logger = context.JK.logger
options = []
now = new Date().getFullYear()
- for yr in [1901..now]
+ for yr in [now..1916]
options.push `
`
- `