VRFS-1503 implement session chat feature

This commit is contained in:
Bert Owen 2014-05-03 00:33:27 +08:00
commit 7d3ac6fc54
60 changed files with 884 additions and 298 deletions

View File

@ -146,4 +146,7 @@ bands_did_session.sql
email_change_default_sender.sql
affiliate_partners.sql
chat_messages.sql
rename_chat_messages.sql
rename_chat_messages.sql
diagnostics.sql
user_mods.sql
connection_stale_expire.sql

View File

@ -0,0 +1,2 @@
ALTER TABLE connections ADD COLUMN stale_time INTEGER NOT NULL DEFAULT 40;
ALTER TABLE connections ADD COLUMN expire_time INTEGER NOT NULL DEFAULT 60;

11
db/up/diagnostics.sql Normal file
View File

@ -0,0 +1,11 @@
CREATE TABLE diagnostics
(
id VARCHAR(64) NOT NULL DEFAULT uuid_generate_v4(),
user_id VARCHAR(64) NOT NULL REFERENCES users (id) ON DELETE CASCADE,
type VARCHAR(255) NOT NULL,
creator VARCHAR(255) NOT NULL,
data TEXT,
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX diagnostics_type_idx ON diagnostics(type);

1
db/up/user_mods.sql Normal file
View File

@ -0,0 +1 @@
ALTER TABLE users ADD COLUMN mods JSON;

View File

@ -369,23 +369,21 @@ SQL
end
end
# if a blk is passed in, upon success, it will be called and you can issue notifications
# within the connection table lock
def join_music_session(user, client_id, music_session, as_musician, tracks, &blk)
def join_music_session(user, client_id, music_session, as_musician, tracks)
connection = nil
user_id = user.id
music_session_id = music_session.id
ConnectionManager.active_record_transaction do |connection_manager|
db_conn = connection_manager.pg_conn
connection = Connection.find_by_client_id_and_user_id!(client_id, user_id)
connection.music_session_id = music_session_id
connection.as_musician = as_musician
connection.joining_session = true
connection.joined_session_at = Time.now
associate_tracks(connection, tracks)
connection.save
connection = Connection.find_by_client_id_and_user_id!(client_id, user.id)
connection.join_the_session(music_session, as_musician, tracks)
# connection.music_session_id = music_session.id
# connection.as_musician = as_musician
# connection.joining_session = true
# connection.joined_session_at = Time.now
# associate_tracks(connection, tracks)
# connection.save
if connection.errors.any?
raise ActiveRecord::Rollback
@ -419,7 +417,7 @@ SQL
end
# can throw exception if the session is deleted just before this
conn.exec("UPDATE connections SET music_session_id = NULL, joined_session_at = NULL, as_musician = NULL WHERE client_id = $1 AND user_id =$2", [client_id, user_id]) do |result|
conn.exec("UPDATE connections SET music_session_id = NULL, joined_session_at = NULL, as_musician = NULL WHERE client_id = $1 AND user_id = $2 AND music_session_id = $3", [client_id, user_id, music_session_id]) do |result|
if result.cmd_tuples == 1
@log.debug("disassociated music_session with connection for client_id=#{client_id}, user_id=#{user_id}")
@ -442,23 +440,23 @@ SQL
conn.exec("LOCK connections IN EXCLUSIVE MODE").clear
end
def associate_tracks(connection, tracks)
@log.debug "Tracks:"
@log.debug tracks
connection.tracks.clear()
unless tracks.nil?
tracks.each do |track|
instrument = Instrument.find(track["instrument_id"])
t = Track.new
t.instrument = instrument
t.connection = connection
t.sound = track["sound"]
t.client_track_id = track["client_track_id"]
t.save
connection.tracks << t
end
end
end
# def associate_tracks(connection, tracks)
# @log.debug "Tracks:"
# @log.debug tracks
# connection.tracks.clear()
#
# unless tracks.nil?
# tracks.each do |track|
# instrument = Instrument.find(track["instrument_id"])
# t = Track.new
# t.instrument = instrument
# t.connection = connection
# t.sound = track["sound"]
# t.client_track_id = track["client_track_id"]
# t.save
# connection.tracks << t
# end
# end
# end
end
end

View File

@ -12,7 +12,7 @@ require 'jam_ruby'
module JamWebEventMachine
@@log = Logging.logger[JamWebEventMachine]
# starts amqp & eventmachine up first.
# and then calls your block.
# After the supplied block is done,
@ -36,8 +36,9 @@ module JamWebEventMachine
end
def self.run_em(calling_thread)
def self.run_em(calling_thread = nil, semaphore = nil, ran = {})
semaphore.lock if semaphore
EM.run do
# this is global because we need to check elsewhere if we are currently connected to amqp before signalling success with some APIs, such as 'create session'
$amqp_connection_manager = AmqpConnectionManager.new(true, 4, :host => APP_CONFIG.rabbitmq_host, :port => APP_CONFIG.rabbitmq_port)
@ -54,7 +55,9 @@ module JamWebEventMachine
end
end
calling_thread.wakeup
ran[:ran] = true
semaphore.unlock if semaphore
calling_thread.wakeup if calling_thread
end
end
@ -65,12 +68,18 @@ module JamWebEventMachine
end
def self.run
return if defined?(Rails::Console)
ran = {}
semaphore = Mutex.new
current = Thread.current
Thread.new do
run_em(current)
run_em(current, semaphore, ran)
end
Thread.stop
semaphore.synchronize {
unless ran[:ran]
semaphore.sleep(10)
end
}
end
def self.start

View File

@ -68,6 +68,13 @@ module JamRuby
def can_join_music_session
# puts "can_join_music_session: #{music_session_id} was #{music_session_id_was}" if music_session_id_changed?
if music_session_id_changed? and !(music_session_id_was.nil? or music_session_id_was.blank?)
errors.add(:music_session, ValidationMessages::CANT_JOIN_MULTIPLE_SESSIONS)
return false
end
if music_session.nil?
errors.add(:music_session, ValidationMessages::MUSIC_SESSION_MUST_BE_SPECIFIED)
return false
@ -139,7 +146,37 @@ module JamRuby
true
end
def join_the_session(music_session, as_musician, tracks)
self.music_session_id = music_session.id
self.as_musician = as_musician
self.joining_session = true
self.joined_session_at = Time.now
associate_tracks(tracks) unless tracks.nil?
self.save
end
def associate_tracks(tracks)
# @log.debug "Tracks:"
# @log.debug tracks
unless tracks.nil?
self.tracks.clear()
tracks.each do |track|
t = Track.new
t.instrument = Instrument.find(track["instrument_id"])
t.connection = self
t.sound = track["sound"]
t.client_track_id = track["client_track_id"]
t.save # todo what if it fails?
self.tracks << t
end
end
end
private
def require_at_least_one_track_when_in_session
if tracks.count == 0
errors.add(:tracks, ValidationMessages::SELECT_AT_LEAST_ONE)

View File

@ -55,6 +55,7 @@ class MQRouter
# sends a message to a client with no checking of permissions (RAW USAGE)
# this method deliberately has no database interactivity/active_record objects
def publish_to_client(client_id, client_msg, sender = {:client_id => ""})
@@log.error "EM not running in publish_to_client" unless EM.reactor_running?
EM.schedule do
sender_client_id = sender[:client_id]
@ -68,6 +69,7 @@ class MQRouter
# sends a message to a session with no checking of permissions (RAW USAGE)
# this method deliberately has no database interactivity/active_record objects
def publish_to_session(music_session_id, client_ids, client_msg, sender = {:client_id => nil})
@@log.error "EM not running in publish_to_session" unless EM.reactor_running?
EM.schedule do
sender_client_id = sender[:client_id]
@ -84,7 +86,7 @@ class MQRouter
# sends a message to a user with no checking of permissions (RAW USAGE)
# this method deliberately has no database interactivity/active_record objects
def publish_to_user(user_id, user_msg)
@@log.warn "EM not running in publish_to_user" unless EM.reactor_running?
@@log.error "EM not running in publish_to_user" unless EM.reactor_running?
EM.schedule do
@@log.debug "publishing to user:#{user_id} from server"
@ -96,6 +98,8 @@ class MQRouter
# sends a message to a list of friends with no checking of permissions (RAW USAGE)
# this method deliberately has no database interactivity/active_record objects
def publish_to_friends(friend_ids, user_msg, from_user_id)
@@log.error "EM not running in publish_to_friends" unless EM.reactor_running?
EM.schedule do
friend_ids.each do |friend_id|
@@log.debug "publishing to friend:#{friend_id} from user/band #{from_user_id}"

View File

@ -490,34 +490,57 @@ describe ConnectionManager do
end
it "join_music_session fails if user has music_session already active" do
# there are two different problems: user can only be in one active music_session at a time,
# and a connection can only point to one active music_session at a time. this is a test of
# the former but we need a test of the latter, too.
pending
end
it "join_music_session fails if connection has music_session already active" do
# there are two different problems: user can only be in one active music_session at a time,
# and a connection can only point to one active music_session at a time. this is a test of
# the latter but we need a test of the former, too.
user_id = create_user("test", "user11", "user11@jamkazam.com")
user = User.find(user_id)
music_session = MusicSession.find(create_music_session(user_id))
client_id = Faker::Number.number(20)
@connman.create_connection(user_id, client_id, "1.1.1.1", 'client')
connection = @connman.join_music_session(user, client_id, music_session, true, TRACKS)
client_id1 = Faker::Number.number(20)
@connman.create_connection(user_id, client_id1, "1.1.1.1", 'client')
music_session1 = MusicSession.find(create_music_session(user_id))
connection1 = @connman.join_music_session(user, client_id1, music_session1, true, TRACKS)
client_id = Faker::Number.number(20)
@connman.create_connection(user_id, client_id, Faker::Internet.ip_v4_address, 'client')
music_session = MusicSession.find(create_music_session(user_id))
connection = @connman.join_music_session(user, client_id, music_session, true, TRACKS)
connection1.errors.size.should == 0
connection.errors.size.should == 1
connection.errors.get(:music_session).should == [ValidationMessages::CANT_JOIN_MULTIPLE_SESSIONS]
music_session2 = MusicSession.find(create_music_session(user_id))
connection2 = @connman.join_music_session(user, client_id1, music_session2, true, TRACKS)
user.update_attribute(:admin, true)
client_id = Faker::Number.number(20)
@connman.create_connection(user_id, client_id, "1.1.1.1", 'client')
music_session = MusicSession.find(create_music_session(user_id))
connection = @connman.join_music_session(user, client_id, music_session, true, TRACKS)
client_id = Faker::Number.number(20)
@connman.create_connection(user_id, client_id, Faker::Internet.ip_v4_address, 'client')
music_session = MusicSession.find(create_music_session(user_id))
connection = @connman.join_music_session(user, client_id, music_session, true, TRACKS)
connection.errors.size.should == 0
connection2.errors.size.should == 1
connection2.errors.get(:music_session).should == [ValidationMessages::CANT_JOIN_MULTIPLE_SESSIONS]
# client_id2 = Faker::Number.number(20)
# @connman.create_connection(user_id, client_id2, "2.2.2.2", 'client')
# music_session2 = MusicSession.find(create_music_session(user_id))
# connection2 = @connman.join_music_session(user, client_id2, music_session2, true, TRACKS)
#
# connection2.errors.size.should == 1
# connection2.errors.get(:music_session).should == [ValidationMessages::CANT_JOIN_MULTIPLE_SESSIONS]
#
# user.update_attribute(:admin, true)
# client_id = Faker::Number.number(20)
# @connman.create_connection(user_id, client_id, "1.1.1.1", 'client')
# music_session = MusicSession.find(create_music_session(user_id))
# connection = @connman.join_music_session(user, client_id, music_session, true, TRACKS)
# client_id = Faker::Number.number(20)
# @connman.create_connection(user_id, client_id, Faker::Internet.ip_v4_address, 'client')
# music_session = MusicSession.find(create_music_session(user_id))
# connection = @connman.join_music_session(user, client_id, music_session, true, TRACKS)
# connection.errors.size.should == 0
end
end

View File

@ -19,8 +19,9 @@ describe ClaimedRecording do
@instrument = FactoryGirl.create(:instrument, :description => 'a great instrument')
@track = FactoryGirl.create(:track, :connection => @connection, :instrument => @instrument)
@music_session = FactoryGirl.create(:music_session, :creator => @user, :musician_access => true)
@music_session.connections << @connection
# @music_session.connections << @connection
@music_session.save
@connection.join_the_session(@music_session, true, nil)
@recording = Recording.start(@music_session, @user)
@recording.stop
@recording.reload

View File

@ -1,6 +1,6 @@
require 'spec_helper'
describe Connection do
describe JamRuby::Connection do
let(:user) { FactoryGirl.create(:user) }
let (:music_session) { FactoryGirl.create(:music_session, :creator => user) }

View File

@ -8,8 +8,9 @@ describe Mix do
@instrument = FactoryGirl.create(:instrument, :description => 'a great instrument')
@track = FactoryGirl.create(:track, :connection => @connection, :instrument => @instrument)
@music_session = FactoryGirl.create(:music_session, :creator => @user, :musician_access => true)
@music_session.connections << @connection
# @music_session.connections << @connection
@music_session.save
@connection.join_the_session(@music_session, true, nil)
@recording = Recording.start(@music_session, @user)
@recording.stop
@recording.claim(@user, "name", "description", Genre.first, true)

View File

@ -396,8 +396,9 @@ describe MusicSession do
@instrument = FactoryGirl.create(:instrument, :description => 'a great instrument')
@track = FactoryGirl.create(:track, :connection => @connection, :instrument => @instrument)
@music_session = FactoryGirl.create(:music_session, :creator => @user1, :musician_access => true)
@music_session.connections << @connection
@music_session.save
# @music_session.connections << @connection
@music_session.save!
@connection.join_the_session(@music_session, true, nil)
end
describe "not recording" do

View File

@ -113,8 +113,9 @@ describe 'Musician search' do
instrument = FactoryGirl.create(:instrument, :description => 'a great instrument')
track = FactoryGirl.create(:track, :connection => connection, :instrument => instrument)
music_session = FactoryGirl.create(:music_session, :creator => usr, :musician_access => true)
music_session.connections << connection
# music_session.connections << connection
music_session.save
connection.join_the_session(music_session, true, nil)
recording = Recording.start(music_session, usr)
recording.stop
recording.reload
@ -127,8 +128,9 @@ describe 'Musician search' do
def make_session(usr)
connection = FactoryGirl.create(:connection, :user => usr)
music_session = FactoryGirl.create(:music_session, :creator => usr, :musician_access => true)
music_session.connections << connection
# music_session.connections << connection
music_session.save
connection.join_the_session(music_session, true, nil)
end
context 'musician stat counters' do

View File

@ -79,8 +79,9 @@ describe Recording do
@instrument2 = FactoryGirl.create(:instrument, :description => 'a great instrument')
@track2 = FactoryGirl.create(:track, :connection => @connection2, :instrument => @instrument2)
@music_session.connections << @connection2
# @music_session.connections << @connection2
@connection2.join_the_session(@music_session, true, nil)
@recording = Recording.start(@music_session, @user)
@user.recordings.length.should == 0
#@user.recordings.first.should == @recording
@ -176,8 +177,9 @@ describe Recording do
@user2 = FactoryGirl.create(:user)
@connection2 = FactoryGirl.create(:connection, :user => @user2)
@track = FactoryGirl.create(:track, :connection => @connection2, :instrument => @instrument)
@music_session.connections << @connection2
# @music_session.connections << @connection2
@music_session.save
@connection2.join_the_session(@music_session, true, nil)
@recording = Recording.start(@music_session, @user)
@recording.stop
@recording.reload

View File

@ -155,8 +155,9 @@ describe AudioMixer do
@instrument = FactoryGirl.create(:instrument, :description => 'a great instrument')
@track = FactoryGirl.create(:track, :connection => @connection, :instrument => @instrument)
@music_session = FactoryGirl.create(:music_session, :creator => @user, :musician_access => true)
@music_session.connections << @connection
# @music_session.connections << @connection
@music_session.save
@connection.join_the_session(@music_session, true, nil)
@recording = Recording.start(@music_session, @user)
@recording.stop
@recording.claim(@user, "name", "description", Genre.first, true)

View File

@ -0,0 +1,149 @@
(function(context,$) {
"use strict";
context.JK = context.JK || {};
context.JK.CommentDialog = function(app, options) {
var logger = context.JK.logger;
var rest = context.JK.Rest();
var $screen = null;
var $content = null;
var recordingId;
var entityType = options.entity_type;
var sessionId = options.session_id;
var recordingId = options.recording_id;
var claimedRecordingId = options.claimed_recording_id;
function beforeShow(data) {
}
function afterShow(data) {
$("#txtComment", $screen).val('');
renderComments();
}
function afterHide() {
}
function renderComments() {
$content.empty();
var h1Text = $('h1', $screen).html('comment on this ' + entityType);
if (entityType === 'session') {
rest.getSessionHistory(sessionId)
.done(function(response) {
if (response && response.comments) {
$.each(response.comments, function(index, val) {
renderComment(val.comment, val.creator.id, val.creator.name,
context.JK.resolveAvatarUrl(val.creator.photo_url), $.timeago(val.created_at), val.creator.musician, true);
});
context.JK.bindHoverEvents($content);
context.JK.bindProfileClickEvents($content, ['comment-dialog']);
}
})
.fail(function(xhr) {
});
}
else if (entityType === 'recording') {
rest.getClaimedRecording(claimedRecordingId)
.done(function(response) {
if (response.recording && response.recording.comments) {
$.each(response.recording.comments, function(index, val) {
renderComment(val.comment, val.creator.id, val.creator.name,
context.JK.resolveAvatarUrl(val.creator.photo_url), $.timeago(val.created_at), val.creator.musician, true);
});
context.JK.bindHoverEvents($content);
context.JK.bindProfileClickEvents($content, ['comment-dialog']);
}
})
.fail(function(xhr) {
});
}
}
function renderComment(comment, userId, userName, userAvatarUrl, timeago, musician, append) {
var options = {
avatar_url: userAvatarUrl,
user_id: userId,
hoverAction: musician ? "musician" : "fan",
name: userName,
comment: comment,
timeago: timeago
};
var $comment = $(context._.template($('#template-comments').html(), options, {variable: 'data'}));
if (append) {
$content.append($comment);
}
else {
$content.prepend($comment);
}
}
function addComment() {
var comment = $("#txtComment", $screen).val();
if ($.trim(comment).length > 0) {
if (entityType === 'session') {
rest.addSessionComment(sessionId, JK.currentUserId, comment)
.done(function(response) {
// $("#spnCommentCount").html(parseInt($("#spnCommentCount").text()) + 1);
renderComment(comment, context.JK.currentUserId, context.JK.currentUserName,
context.JK.currentUserAvatarUrl, $.timeago(Date.now()), context.JK.currentUserMusician, false);
});
}
else if (entityType === 'recording') {
rest.addRecordingComment(recordingId, JK.currentUserId, comment)
.done(function(response) {
// $("#spnCommentCount", $scope).html(parseInt($("#spnCommentCount").text()) + 1);
renderComment(comment, context.JK.currentUserId, context.JK.currentUserName,
context.JK.currentUserAvatarUrl, $.timeago(Date.now()), context.JK.currentUserMusician, false);
});
}
}
}
function events() {
var $btnSelector = $('#btn-add-comment', $screen);
var $txtComment = $('#txtComment', $screen);
if (!context.JK.currentUserId) {
$txtComment.attr('placeholder', 'You must be logged in to add a comment.');
$btnSelector.removeClass('button-orange');
$btnSelector.addClass('button-grey');
}
else {
$btnSelector.unbind('click');
$btnSelector.click(addComment);
}
}
function showDialog() {
app.layout.showDialog('comment-dialog');
}
function initialize() {
var dialogBindings = {
'beforeShow' : beforeShow,
'afterShow' : afterShow,
'afterHide': afterHide
};
app.bindDialog('comment-dialog', dialogBindings);
$screen = $('[layout-id="comment-dialog"]');
$content = $screen.find('.dialog-comment-scroller');
events();
}
this.initialize = initialize;
this.showDialog = showDialog;
}
return this;
})(window,jQuery);

View File

@ -5,7 +5,8 @@
context.JK.FeedScreen = function(app) {
var logger = context.JK.logger;
var rest = context.JK.Rest();
var rest = new context.JK.Rest();
var ui = new context.JK.UIHelper(JK.app);
var currentQuery = null;
var currentPage = 0;
var LIMIT = 20;
@ -282,24 +283,6 @@
return false;
}
function toggleUserProfile() {
var userId = $(this).attr('user-id');
window.location = '/client#/profile/' + userId;
return false;
}
function toggleBandProfile() {
var bandId = $(this).attr('band-id');
if (bandId == null) {
var userId = $(this).attr('user-id');
window.location = '/client#/profile/' + userId;
}
else {
window.location = '/client#/bandProfile/' + bandId;
}
return false;
}
function renderFeeds(feeds) {
$.each(feeds.entries, function(i, feed) {
@ -319,9 +302,27 @@
$('.details', $feedItem).click(toggleSessionDetails);
$('.details-arrow', $feedItem).click(toggleSessionDetails);
$('.play-button', $feedItem).click(toggleSessionPlay);
$('.avatar-tiny', $feedItem).click(toggleUserProfile);
$('.musician-name', $feedItem).click(toggleUserProfile);
$('.artist', $feedItem).click(toggleBandProfile);
if (!feed.session_removed_at)
{
$('.btn-share', $feedItem).click(function() {
ui.launchShareDialog(feed.id, 'session');
});
}
else {
$('.btn-share', $feedItem).hide();
}
$('.btn-comment', $feedItem).click(function() {
ui.launchCommentDialog({
session_id: feed.id,
entity_type: 'session'
});
});
$('.btn-like', $feedItem).click(function() {
ui.addSessionLike(feed.id, JK.currentUserId, $('.likes', $feedItem), $('.btn-like', $feedItem))
});
// put the feed item on the page
renderFeed($feedItem);
@ -332,6 +333,7 @@
$('.dotdotdot', $feedItem).dotdotdot();
$feedItem.data('original-max-height', $feedItem.css('height'));
context.JK.bindHoverEvents($feedItem);
context.JK.bindProfileClickEvents($feedItem);
}
else if(feed.type == 'recording') {
if(feed.claimed_recordings.length == 0) {
@ -353,6 +355,22 @@
$('.details', $feedItem).click(toggleRecordingDetails);
$('.details-arrow', $feedItem).click(toggleRecordingDetails);
$('.play-button', $feedItem).click(toggleRecordingPlay);
$('.btn-share', $feedItem).click(function() {
ui.launchShareDialog(options.candidate_claimed_recording.id, 'recording');
});
$('.btn-comment', $feedItem).click(function() {
ui.launchCommentDialog({
recording_id: feed.id,
claimed_recording_id: options.candidate_claimed_recording.id,
entity_type: 'recording'
});
});
$('.btn-like', $feedItem).click(function() {
ui.addRecordingLike(feed.id, options.candidate_claimed_recording.id, JK.currentUserId, $('.likes', $feedItem), $('.btn-like', $feedItem));
});
// put the feed item on the page
renderFeed($feedItem);
@ -363,11 +381,14 @@
$('.dotdotdot', $feedItem).dotdotdot();
$feedItem.data('original-max-height', $feedItem.css('height'));
context.JK.bindHoverEvents($feedItem);
context.JK.bindProfileClickEvents($feedItem);
}
else {
logger.warn("skipping feed type: " + feed.type);
}
})
context.JK.bindProfileClickEvents();
});
}
function renderFeed(feed) {

View File

@ -5,6 +5,8 @@
context.JK = context.JK || {};
context.JK.FeedItemRecording = function($parentElement, options){
var ui = new context.JK.UIHelper(JK.app);
var claimedRecordingId = $parentElement.attr('data-claimed-recording-id');
var recordingId = $parentElement.attr('id');
var mode = $parentElement.attr('data-mode');
@ -94,19 +96,39 @@
$('.details', $feedItem).click(toggleDetails);
$('.details-arrow', $feedItem).click(toggleDetails);
$('.play-button', $feedItem).click(togglePlay);
$('.btn-share', $feedItem).click(function() {
ui.launchShareDialog(claimedRecordingId, 'recording');
});
$('.btn-comment', $feedItem).click(function() {
ui.launchCommentDialog({
recording_id: recordingId,
claimed_recording_id: claimedRecordingId,
entity_type: 'recording'
});
});
$('.btn-like', $feedItem).click(function() {
ui.addRecordingLike(recordingId, claimedRecordingId, JK.currentUserId, $('.likes', $feedItem), $('.btn-like', $feedItem));
});
$controls.bind('statechange.listenRecording', stateChange);
}
function initialize() {
$('.timeago', $feedItem).timeago();
$('.dotdotdot', $feedItem).dotdotdot();
$controls.listenRecording({recordingId: recordingId, claimedRecordingId: claimedRecordingId, sliderSelector:'.recording-slider', sliderBarSelector: '.recording-playback', currentTimeSelector:'.recording-current'});
$controls.listenRecording({recordingId: recordingId, claimedRecordingId: claimedRecordingId, sliderSelector:'.recording-slider', sliderBarSelector: '.recording-playback', currentTimeSelector:'.recording-current'});
context.JK.prettyPrintElements($('time.duration', $feedItem));
context.JK.setInstrumentAssetPath($('.instrument-icon', $feedItem));
$feedItem.data('original-max-height', $feedItem.css('height'));
events();
context.JK.bindHoverEvents($feedItem);
//context.JK.bindProfileClickEvents($feedItem);
}
initialize();

View File

@ -6,8 +6,7 @@
context.JK.FeedItemSessionTimer = null;
context.JK.FeedItemSession = function($parentElement, options){
var logger = context.JK.logger;
var rest = new context.JK.Rest();
var ui = new context.JK.UIHelper(JK.app);
var $feedItem = $parentElement;
var $description = $('.description', $feedItem)
@ -84,6 +83,22 @@
$('.details', $feedItem).click(toggleDetails);
$('.details-arrow', $feedItem).click(toggleDetails);
$('.play-button', $feedItem).click(togglePlay);
$('.btn-share', $feedItem).click(function() {
ui.launchShareDialog(musicSessionId, 'session');
});
$('.btn-comment', $feedItem).click(function() {
ui.launchCommentDialog({
session_id: musicSessionId,
entity_type: 'session'
});
});
$('.btn-like', $feedItem).click(function() {
ui.addSessionLike(musicSessionId, JK.currentUserId, $('.likes', $feedItem), $('.btn-like', $feedItem))
});
$controls.bind('statechange.listenBroadcast', stateChange);
}
@ -97,6 +112,9 @@
$feedItem.data('original-max-height', $feedItem.css('height'));
events();
context.JK.bindHoverEvents($feedItem);
//context.JK.bindProfileClickEvents($feedItem);
}
initialize();

View File

@ -3,7 +3,6 @@
"use strict";
context.JK = context.JK || {};
context.JK.BandHoverBubble = function(bandId, x, y) {
var logger = context.JK.logger;
var rest = context.JK.Rest();
var hoverSelector = "#band-hover";
@ -13,9 +12,9 @@
var mouseTop = y < (document.body.clientHeight / 2);
var css = {};
if (mouseLeft)
css.left = x + 10 + 'px';
css.left = x + 20 + 'px';
else
css.left = x - (7 + $(hoverSelector).width()) + 'px';
css.left = x - (25 + $(hoverSelector).width()) + 'px';
if (mouseTop)
css.top = y + 10 + 'px';
else

View File

@ -6,7 +6,6 @@
var logger = context.JK.logger;
var rest = context.JK.Rest();
var instrumentLogoMap = context.JK.getInstrumentIconMap24();
var hoverSelector = "#fan-hover";
this.showBubble = function() {
@ -14,9 +13,9 @@
var mouseTop = y < (document.body.clientHeight / 2);
var css = {};
if (mouseLeft)
css.left = x + 10 + 'px';
css.left = x + 20 + 'px';
else
css.left = x - (7 + $(hoverSelector).width()) + 'px';
css.left = x - (25 + $(hoverSelector).width()) + 'px';
if (mouseTop)
css.top = y + 10 + 'px';
else

View File

@ -9,14 +9,13 @@
var hoverSelector = "#musician-hover";
this.showBubble = function() {
var mouseLeft = x < (document.body.clientWidth / 2);
var mouseTop = y < (document.body.clientHeight / 2);
var css = {};
if (mouseLeft)
css.left = x + 10 + 'px';
css.left = x + 20 + 'px';
else
css.left = x - (7 + $(hoverSelector).width()) + 'px';
css.left = x - (25 + $(hoverSelector).width()) + 'px';
if (mouseTop)
css.top = y + 10 + 'px';
else

View File

@ -42,9 +42,9 @@
var mouseTop = y < (document.body.clientHeight / 2);
var css = {};
if (mouseLeft)
css.left = x + 10 + 'px';
css.left = x + 20 + 'px';
else
css.left = x - (7 + $(hoverSelector).width()) + 'px';
css.left = x - (25 + $(hoverSelector).width()) + 'px';
if (mouseTop)
css.top = y + 10 + 'px';
else

View File

@ -13,9 +13,9 @@
var mouseTop = y < (document.body.clientHeight / 2);
var css = {};
if (mouseLeft)
css.left = x + 10 + 'px';
css.left = x + 20 + 'px';
else
css.left = x - (7 + $(hoverSelector).width()) + 'px';
css.left = x - (25 + $(hoverSelector).width()) + 'px';
if (mouseTop)
css.top = y + 10 + 'px';
else

View File

@ -5,7 +5,9 @@
//= require globals
//= require jamkazam
//= require utils
//= require ui_helper
//= require ga
//= require jam_rest
//= require landing/init
//= require landing/signup
//= require landing/signup
//= require shareDialog

View File

@ -36,8 +36,8 @@
var localResults = context.jamClient.GetLocalRecordingState({recordings: [recording]});
if (localResults['error']) {
logger.error("unable to open recording due to error: %o", localResults);
app.notify({
title: "Unable to Open Recording for Playback",
text: localResults['error'],
@ -47,6 +47,7 @@
else {
var localResult = localResults.recordings[0];
if (localResult.aggregate_state == 'MISSING') {
logger.error("unable to open recording due to all missing tracks: %o", localResults);
app.notify({
title: "Unable to Open Recording for Playback",
text: "All tracks associated with the recording are missing",
@ -54,6 +55,7 @@
});
}
else if (localResult.aggregate_state == 'PARTIALLY_MISSING') {
logger.error("unable to open recording due to some missing tracks: %o", localResults);
app.notify({
title: "Unable to Open Recording for Playback",
text: "Some tracks associated with the recording are missing",

View File

@ -413,7 +413,7 @@
$(dialogId + ' .link-contents').text(entity.share_url)
})
.fail(function(jqXHR) {
app.notifyServerError(jqXHR, "Unable to Fetch Session Data");
app.notifyServerError(jqXHR, "Unable to Fetch Recording Data");
})
}
else {

View File

@ -0,0 +1,46 @@
(function(context,$) {
"use strict";
context.JK = context.JK || {};
context.JK.UIHelper = function(app) {
var logger = context.JK.logger;
var rest = new context.JK.Rest();
function addSessionLike(sessionId, userId, $likeCountSelector, $likeButtonSelector) {
rest.addSessionLike(sessionId, userId)
.done(function(response) {
$likeCountSelector.html(parseInt($likeCountSelector.text()) + 1);
$likeButtonSelector.unbind("click");
});
}
function addRecordingLike(recordingId, claimedRecordingId, userId, $likeCountSelector, $likeButtonSelector) {
rest.addRecordingLike(recordingId, claimedRecordingId, userId)
.done(function(response) {
$likeCountSelector.html(parseInt($likeCountSelector.text()) + 1);
$likeButtonSelector.unbind("click");
});
}
function launchCommentDialog(options) {
var commentDialog = new JK.CommentDialog(JK.app, options);
commentDialog.initialize();
commentDialog.showDialog();
}
function launchShareDialog(entityId, entityType) {
var shareDialog = new JK.ShareDialog(JK.app, entityId, entityType);
shareDialog.initialize(JK.FacebookHelperInstance);
shareDialog.showDialog();
}
this.addSessionLike = addSessionLike;
this.addRecordingLike = addRecordingLike;
this.launchCommentDialog = launchCommentDialog;
this.launchShareDialog = launchShareDialog;
return this;
};
})(window,jQuery);

View File

@ -153,9 +153,39 @@
$element.bt(text, options);
}
context.JK.bindProfileClickEvents = function($parent, dialogsToClose) {
if (!$parent) {
$parent = $('body');
}
function closeDialogs() {
if (dialogsToClose) {
$.each(dialogsToClose, function(index, val) {
JK.app.layout.closeDialog(val);
});
}
}
$("[profileaction='band']", $parent).unbind('click');
$("[profileaction='band']", $parent).click(function(evt) {
closeDialogs();
console.log("navigating to band profile %o", $(this).attr('band-id'));
window.location = "/client#/bandProfile/" + $(this).attr('band-id');
});
$("[profileaction='musician']", $parent).unbind('click');
$("[profileaction='musician']", $parent).click(function(evt) {
closeDialogs();
console.log("navigating to musician profile %o", $(this).attr('user-id'));
window.location = "/client#/profile/" + $(this).attr('user-id');
});
}
context.JK.bindHoverEvents = function ($parent) {
var timeout = 300;
var fadeoutValue = 100;
var sensitivity = 3;
var interval = 500;
if (!$parent) {
$parent = $('body');
@ -194,7 +224,8 @@
out: function () { // this registers for leaving the hoverable element
hideBubble($(this));
},
sensitivity: 1,
sensitivity: sensitivity,
interval: interval,
timeout: timeout
});
@ -207,7 +238,8 @@
out: function () { // this registers for leaving the hoverable element
hideBubble($(this));
},
sensitivity: 1,
sensitivity: sensitivity,
interval: interval,
timeout: timeout
});
@ -220,7 +252,8 @@
out: function () { // this registers for leaving the hoverable element
hideBubble($(this));
},
sensitivity: 1,
sensitivity: sensitivity,
interval: interval,
timeout: timeout
});
@ -233,7 +266,8 @@
out: function () { // this registers for leaving the hoverable element
hideBubble($(this));
},
sensitivity: 1,
sensitivity: sensitivity,
interval: interval,
timeout: timeout
});
@ -246,7 +280,8 @@
out: function () { // this registers for leaving the hoverable element
hideBubble($(this));
},
sensitivity: 1,
sensitivity: sensitivity,
interval: interval,
timeout: timeout
});
}

View File

@ -2,7 +2,8 @@
context.JK.ShowRecording = function(app) {
var logger = context.JK.logger;
var rest = new JK.Rest();
var rest = JK.Rest();
var ui = context.JK.UIHelper();
var recordingId = null;
var claimedRecordingId = null;
var $scope = $(".landing-details");
@ -105,14 +106,11 @@
$controls.bind('statechange.listenRecording', stateChange);
$controls.listenRecording({recordingId: recordingId, claimedRecordingId: claimedRecordingId, sliderSelector:'.recording-slider', sliderBarSelector: '.recording-playback', currentTimeSelector:'.recording-current'});
$("#btnShare", $scope).click(function(e) {
ui.launchShareDialog(claimedRecordingId, "recording");
});
if (JK.currentUserId) {
var shareDialog = new JK.ShareDialog(JK.app, claimedRecordingId, "recording");
shareDialog.initialize(JK.FacebookHelperInstance);
$("#btnShare", $scope).click(function(e) {
shareDialog.showDialog();
});
$("#btnPostComment").click(function(e) {
if ($.trim($("#txtRecordingComment").val()).length > 0) {
addComment();

View File

@ -2,7 +2,8 @@
context.JK.ShowMusicSession = function(app) {
var logger = context.JK.logger;
var rest = new JK.Rest();
var rest = JK.Rest();
var ui = context.JK.UIHelper();
var sessionId = null;
var $scope = $(".landing-details");
var $controls = null;
@ -107,14 +108,11 @@
sessionId = musicSessionId;
$("#btnShare").click(function(e) {
ui.launchShareDialog(sessionId, "session");
});
if (JK.currentUserId) {
var shareDialog = new JK.ShareDialog(context.JK.app, sessionId, "session");
shareDialog.initialize(JK.FacebookHelperInstance);
$("#btnShare").click(function(e) {
shareDialog.showDialog();
});
$("#btnPostComment").click(function(e) {
if ($.trim($("#txtSessionComment").val()).length > 0) {
addComment();

View File

@ -35,10 +35,12 @@
//= require hoverSession
//= require hoverRecording
//= require shareDialog
//= require commentDialog
//= require layout
//= require user_dropdown
//= require jamkazam
//= require utils
//= require ui_helper
//= require custom_controls
//= require ga
//= require jam_rest

View File

@ -22,5 +22,9 @@
.buttons {
margin:0 20px 20px 0;
}
.close-btn {
display:none;
}
}

View File

@ -57,7 +57,7 @@
*= require ./musician
*= require web/audioWidgets
*= require web/recordings
#= require web/sessions
*= require web/sessions
*= require jquery.Jcrop
*= require icheck/minimal/minimal
*/

View File

@ -78,5 +78,10 @@
color:#aaa;
}
.dialog-comment-scroller {
height:210px;
overflow:auto;
}

View File

@ -14,15 +14,15 @@
h2 {
padding:6px 0px;
text-align:center;
font-size:15px;
font-weight:200;
font-size:15px !important;
font-weight:200 !important;
width:100%;
background-color:#ed3618;
}
h3 {
font-weight:400;
font-size:16px;
font-weight:400 !important;
font-size:16px !important;
color:#fff;
}

View File

@ -238,7 +238,7 @@
font-size:12px;
font-weight:bold;
color:#ccc;
margin-bottom:10px;
margin-bottom:5px;
overflow: hidden;
white-space: nowrap;
text-decoration: none;

View File

@ -1,6 +1,6 @@
object @history
attributes :id, :music_session_id, :description, :genres, :like_count, :comment_count, :created_at
attributes :id, :music_session_id, :description, :fan_access, :genres, :like_count, :comment_count, :created_at
node :share_url do |history|
unless history.share_token.nil?

View File

@ -0,0 +1,29 @@
.dialog.dialog-overlay-sm{layout: 'dialog', 'layout-id' => 'comment-dialog', id: 'comment-dialog'}
.content-head
%h1
.dialog-inner
.right
%a.button-orange{id: 'dialog-close-button', 'layout-action' => 'close'} X CLOSE
%h2 Comments:
.avatar-small
= image_tag 'shared/avatar_generic.png', :alt => ""
.left.w80.p10
%textarea.w100.p5.f15{id: 'txtComment', rows: '2', placeholder: 'Enter a comment...'}
%br/
%br/
.right
%a.button-orange{id: 'btn-add-comment'} ADD COMMENT
%br{clear: 'all'}/
.dialog-comment-scroller
%script{type: 'text/template', id: 'template-comments'}
.avatar-small.mr10{'user-id' => '{{data.user_id}}', 'hoveraction' => '{{data.hoverAction}}', 'profileaction' => '{{data.hoverAction}}'}
%a{'href' => '/client#/profile/{{data.user_id}}'}
%img{:'src' => '{{data.avatar_url}}', alt: ''}
.w80.left.p10.lightgrey.mt10.comment-text
%a{'user-id' => '{{data.user_id}}', 'hoveraction' => '{{data.hoverAction}}', 'profileaction' => '{{data.hoverAction}}'} {{data.name}}
&nbsp;{{data.comment}}
%br/
.f12.grey.mt5.comment-timestamp
{{data.timeago}}
%br{clear: 'all'}/

View File

@ -41,6 +41,7 @@
<div class="left"><a id="btnLike" onclick="addRecordingLike('{recordingId}', '{claimedRecordingId}');" class="button-orange">LIKE</a></div>
<div style="display:none;" class="left"><a id="btnComment" class="button-orange">COMMENT</a></div>
<div class="left"><a id="btnShare" layout-link="share-dialog" class="button-orange">SHARE</a></div>
<div class="left"><a href="/recordings/{claimedRecordingId}" class="button-orange" rel="external" target="_blank">WEB PAGE</a></div>
</div>
<br /><br />
</div>

View File

@ -35,6 +35,7 @@
<div class="left"><a id="btnLike" onclick="addSessionLike('{musicSessionId}');" class="button-orange">LIKE</a></div>
<div style="display:none;" class="left"><a id="btnComment" class="button-orange">COMMENT</a></div>
<div class="left"><a id="btnShare" layout-link="share-dialog" class="button-orange">SHARE</a></div>
<div class="left"><a href="/sessions/{musicSessionId}" class="button-orange" rel="external" target="_blank">WEB PAGE</a></div>
</div>
<br /><br />
</div>

View File

@ -16,7 +16,7 @@
<div class="left w50 mr20">
<div class="field w100">
<label for="name">Recording name:</label><br/>
<input type="text" name="name" id="claim-recording-name" class="w100"/>
<input type="text" name="name" id="claim-recording-name" class="recording-name" class="w100"/>
</div>
</div>
<div class="right w40 genre-selector">

View File

@ -63,6 +63,8 @@
<%= render "clients/banners/disconnected" %>
<%= render "overlay_small" %>
<%= render "help" %>
<%= render "commentDialog" %>
<div id="fb-root"></div>
<script type="text/javascript">

View File

@ -17,73 +17,78 @@
<% end %>
<div class="sessions-page">
<div class="landing-band">
<% unless @music_session.band.nil? %>
<div class="landing-avatar">
<% unless @music_session.band.photo_url.blank? %>
<%= image_tag "#{@music_session.band.photo_url}", {:alt => ""} %>
<% if @music_session.fan_access %>
<div class="landing-band">
<% unless @music_session.band.nil? %>
<div class="landing-avatar">
<% unless @music_session.band.photo_url.blank? %>
<%= image_tag "#{@music_session.band.photo_url}", {:alt => ""} %>
<% else %>
<%= image_tag "shared/avatar_generic_band.png", {:alt => ""} %>
<% end %>
</div>
<%= @music_session.band.name %>
<% else %>
<%= image_tag "shared/avatar_generic_band.png", {:alt => ""} %>
<div class="landing-avatar">
<% unless @music_session.user.photo_url.blank? %>
<%= image_tag "#{@music_session.user.photo_url}", {:alt => ""} %>
<% else %>
<%= image_tag "shared/avatar_generic.png", {:alt => ""} %>
<% end %>
</div>
<%= @music_session.user.name %>
<% end %>
</div>
<%= @music_session.band.name %>
<div class="landing-details">
<div class="left f20 teal"><strong>SESSION</strong></div>
<div class="right f14 grey"><%= timeago(@music_session.created_at) %></div>
<br clear="all" /><br />
<div class="left w70"><%= @music_session.description %><br /><br /></div>
<% if @music_session.session_removed_at.blank? %>
<div class="right">
<a id="btnLike"><%= image_tag "content/icon_like.png", {:width => 12, :height => 12, :alt => ""} %>&nbsp;LIKE</a>&nbsp;&nbsp;&nbsp;
<a id="btnShare"><%= image_tag "content/icon_share.png", {:width => 13, :height => 15, :alt => ""} %>&nbsp;SHARE</a>
</div>
<% end %>
<br clear="all" />
<div class="w100">
<div class="recording-controls <%= @music_session.is_over? ? 'ended' : 'inprogress' %>" data-music-session="<%=@music_session.id %>">
<a class="left play-button" href="#">
<%= image_tag 'content/icon_playbutton.png', width:20, height:20, class:'play-icon' %>
<% if @music_session.music_session && @music_session.music_session.mount %>
<audio preload="none">
<source src="<%= @music_session.music_session.mount.url %>" type="<%= @music_session.music_session.mount.resolve_string(:mime_type) %>">
</audio>
<% end %>
</a>
<div class="session-status"><%= @music_session.is_over? ? 'SESSION ENDED' : 'LIVE SESSION IN PROGRESS' %></div>
<%= session_duration(@music_session, class: 'session-duration tick-duration recording-current', 'data-created-at' => @music_session.created_at.to_i) %>
</div>
<div class="left white"><%= @music_session.genres.split('|').first.capitalize unless @music_session.genres.blank? %></div>
<div class="right white">
<span id="spnCommentCount"><%= @music_session.comment_count %></span>
<%= image_tag "content/icon_comment.png", {:width => 13, :height => 12, :align => "absmiddle", :style => "vertical-align:middle", :alt => ""} %>&nbsp;&nbsp;&nbsp;&nbsp;
<span id="spnLikeCount"><%= @music_session.like_count %></span>
<%= image_tag "content/icon_like.png", {:width => 12, :height => 12, :align => "absmiddle", :style => "vertical-align:middle", :alt => ""} %>
</div>
</div>
<br clear="all" /><br />
<%= render :partial => "shared/track_details", :locals => {:tracks => @music_session.grouped_tracks} %>
</div>
<br clear="all" />
<% else %>
<div class="landing-avatar">
<% unless @music_session.user.photo_url.blank? %>
<%= image_tag "#{@music_session.user.photo_url}", {:alt => ""} %>
<% else %>
<%= image_tag "shared/avatar_generic.png", {:alt => ""} %>
<% end %>
</div>
<%= @music_session.user.name %>
<div class="left f20 teal"><strong>SESSION NOT FOUND</strong></div>
<% end %>
</div>
<div class="landing-details">
<div class="left f20 teal"><strong>SESSION</strong></div>
<div class="right f14 grey"><%= timeago(@music_session.created_at) %></div>
<br clear="all" /><br />
<div class="left w70"><%= @music_session.description %><br /><br /></div>
<% if @music_session.session_removed_at.blank? %>
<div class="right">
<a id="btnLike"><%= image_tag "content/icon_like.png", {:width => 12, :height => 12, :alt => ""} %>&nbsp;LIKE</a>&nbsp;&nbsp;&nbsp;
<a id="btnShare"><%= image_tag "content/icon_share.png", {:width => 13, :height => 15, :alt => ""} %>&nbsp;SHARE</a>
</div>
<% end %>
<br clear="all" />
<div class="w100">
<div class="recording-controls <%= @music_session.is_over? ? 'ended' : 'inprogress' %>" data-music-session="<%=@music_session.id %>">
<a class="left play-button" href="#">
<%= image_tag 'content/icon_playbutton.png', width:20, height:20, class:'play-icon' %>
<% if @music_session.music_session && @music_session.music_session.mount %>
<audio preload="none">
<source src="<%= @music_session.music_session.mount.url %>" type="<%= @music_session.music_session.mount.resolve_string(:mime_type) %>">
</audio>
<% end %>
</a>
<div class="session-status"><%= @music_session.is_over? ? 'SESSION ENDED' : 'LIVE SESSION IN PROGRESS' %></div>
<%= session_duration(@music_session, class: 'session-duration tick-duration recording-current', 'data-created-at' => @music_session.created_at.to_i) %>
</div>
<div class="left white"><%= @music_session.genres.split('|').first.capitalize unless @music_session.genres.blank? %></div>
<div class="right white">
<span id="spnCommentCount"><%= @music_session.comment_count %></span>
<%= image_tag "content/icon_comment.png", {:width => 13, :height => 12, :align => "absmiddle", :style => "vertical-align:middle", :alt => ""} %>&nbsp;&nbsp;&nbsp;&nbsp;
<span id="spnLikeCount"><%= @music_session.like_count %></span>
<%= image_tag "content/icon_like.png", {:width => 12, :height => 12, :align => "absmiddle", :style => "vertical-align:middle", :alt => ""} %>
</div>
</div>
<br clear="all" /><br />
<%= render :partial => "shared/track_details", :locals => {:tracks => @music_session.grouped_tracks} %>
</div>
<br clear="all" />
</div>
<% if true %>
<% if true %>
<div class="landing-sidebar" style="z-index:900;"><br />
<br>
<br>
@ -100,33 +105,35 @@
class: 'video-slide', :'data-video-header' => 'JamKazam Overview',
:'data-video-url' => 'http://www.youtube.com/embed/ylYcvTY9CVo?autoplay=1') %>
</div>
</div>
</div>
<% else %>
<% if signed_in? %>
<% unless @music_session.band.nil? %>
<%= render :partial => "shared/landing_sidebar", :locals => {:user => @music_session.band, :recent_history => @music_session.band.recent_history} %>
<% else %>
<%= render :partial => "shared/landing_sidebar", :locals => {:user => @music_session.user, :recent_history => @music_session.user.recent_history} %>
<% end %>
<% else %>
<%= render :partial => "shared/cta_sidebar" %>
<% end %>
<% if signed_in? && @music_session.fan_access %>
<% unless @music_session.band.nil? %>
<%= render :partial => "shared/landing_sidebar", :locals => {:user => @music_session.band, :recent_history => @music_session.band.recent_history} %>
<% else %>
<%= render :partial => "shared/landing_sidebar", :locals => {:user => @music_session.user, :recent_history => @music_session.user.recent_history} %>
<% end %>
<% else %>
<%= render :partial => "shared/cta_sidebar" %>
<% end %>
<% end %>
<% content_for :after_black_bar do %>
<br />
<%= render :partial => "shared/comments", :locals => {:comments => @music_session.comments, :id => "txtSessionComment"} %>
<% end %>
<% if @music_session.fan_access %>
<% content_for :after_black_bar do %>
<br />
<%= render :partial => "shared/comments", :locals => {:comments => @music_session.comments, :id => "txtSessionComment"} %>
<% end %>
<% content_for :extra_dialogs do %>
<%= render :partial => "clients/shareDialog" %>
<% end %>
<% content_for :extra_dialogs do %>
<%= render :partial => "clients/shareDialog" %>
<% end %>
<% content_for :extra_js do %>
<script type="text/javascript">
$(function () {
var showMusicSession = new JK.ShowMusicSession(JK.app);
showMusicSession.initialize("<%= @music_session.id %>");
});
</script>
<% end %>
<% content_for :extra_js do %>
<script type="text/javascript">
$(function () {
var showMusicSession = new JK.ShowMusicSession(JK.app);
showMusicSession.initialize("<%= @music_session.id %>");
});
</script>
<% end %>
<% end %>

View File

@ -18,6 +18,7 @@
<% end %>
<div class="recordings-page">
<% if @claimed_recording.is_public %>
<div class="landing-band">
<% unless @claimed_recording.recording.band.blank? %>
<div class="landing-avatar">
@ -90,32 +91,40 @@
<%= render :partial => "shared/track_details", :locals => {:tracks => @claimed_recording.recording.grouped_tracks} %>
</div>
<br clear="all" />
</div>
<% if signed_in? %>
<% unless @claimed_recording.recording.band.nil? %>
<%= render :partial => "shared/landing_sidebar", :locals => {:user => @claimed_recording.recording.band, :recent_history => @claimed_recording.recording.band.recent_history} %>
<% else %>
<%= render :partial => "shared/landing_sidebar", :locals => {:user => @claimed_recording.recording.owner, :recent_history => @claimed_recording.recording.owner.recent_history} %>
<div class="left f20 orange"><strong>RECORDING NOT FOUND</strong></div>
<% end %>
</div>
<% if @claimed_recording.is_public %>
<% if signed_in? %>
<% unless @claimed_recording.recording.band.nil? %>
<%= render :partial => "shared/landing_sidebar", :locals => {:user => @claimed_recording.recording.band, :recent_history => @claimed_recording.recording.band.recent_history} %>
<% else %>
<%= render :partial => "shared/landing_sidebar", :locals => {:user => @claimed_recording.recording.owner, :recent_history => @claimed_recording.recording.owner.recent_history} %>
<% end %>
<% else %>
<%= render :partial => "shared/cta_sidebar" %>
<% end %>
<% content_for :after_black_bar do %>
<br />
<%= render :partial => "shared/comments", :locals => {:comments => @claimed_recording.recording.comments, :id => "txtRecordingComment"} %>
<% end %>
<% content_for :extra_dialogs do %>
<%= render :partial => "clients/shareDialog" %>
<% end %>
<% content_for :extra_js do %>
<script type="text/javascript">
$(function () {
var showRecording = new JK.ShowRecording(JK.app);
showRecording.initialize("<%= @claimed_recording.id %>", "<%= @claimed_recording.recording_id %>");
});
</script>
<% end %>
<% else %>
<%= render :partial => "shared/cta_sidebar" %>
<% end %>
<% content_for :after_black_bar do %>
<br />
<%= render :partial => "shared/comments", :locals => {:comments => @claimed_recording.recording.comments, :id => "txtRecordingComment"} %>
<% end %>
<% content_for :extra_dialogs do %>
<%= render :partial => "clients/shareDialog" %>
<% end %>
<% content_for :extra_js do %>
<script type="text/javascript">
$(function () {
var showRecording = new JK.ShowRecording(JK.app);
showRecording.initialize("<%= @claimed_recording.id %>", "<%= @claimed_recording.recording_id %>");
});
</script>
<% end %>

View File

@ -4,7 +4,8 @@
= session_avatar(feed_item)
/ type and artist
.left.ml20.w15
.title{hoveraction: 'session', :'session-id' => feed_item.id } SESSION
.title{hoveraction: 'session', :'session-id' => feed_item.id }
%a{:href => "/sessions/#{feed_item.id}", :target => "_blank"} SESSION
.artist
= session_artist_name(feed_item)
= timeago(feed_item.created_at, class: 'small created_at')
@ -31,18 +32,24 @@
.left.small
= session_genre(feed_item)
.right.small.feed-details
- if !feed_item.session_removed_at
%a{title: 'Share', class: 'btn-share'}
= image_tag 'content/icon_share.png', :height => "12", :width => "7"
&nbsp;
%span.play-count
%span.plays
= feed_item.play_count
= image_tag 'content/icon_arrow.png', :height => "12", :width => "7"
= image_tag 'content/icon_arrow.png', :height => "12", :width => "7", :title => 'Play'
%span.comment-count
%span.comments
= feed_item.comment_count
= image_tag 'content/icon_comment.png', :height => "12", :width => "13"
%a{title: 'Comment', class: 'btn-comment'}
= image_tag 'content/icon_comment.png', :height => "12", :width => "13"
%span.like-count
%span.likes
= feed_item.like_count
= image_tag 'content/icon_like.png', :height => "12", :width => "12"
%a{title: 'Like', class: 'btn-like'}
= image_tag 'content/icon_like.png', :height => "12", :width => "12"
%a.details{:href => "#"} Details
%a.details-arrow.arrow-down-orange{:href => "#"}
%br/

View File

@ -2,13 +2,15 @@
.feed-entry.music-session-history-entry{'data-music-session' => '{{data.feed_item.id}}' }
/ avatar
.avatar-small.ib
%img{ src: '{{data.feed_item.helpers.avatar}}' }
%a{:hoveraction => "{{data.feed_item.helpers.artist_hoveraction}}", :profileaction => "{{data.feed_item.helpers.artist_hoveraction}}", :"{{data.feed_item.helpers.artist_datakey}}" => "{{data.feed_item.helpers.artist_id}}"}
%img{ src: '{{data.feed_item.helpers.avatar}}' }
/ type and artist
.left.ml20.w15
.title{hoveraction: 'session', :'session-id' => '{{data.feed_item.id}}' } SESSION
.title{hoveraction: 'session', :'session-id' => '{{data.feed_item.id}}' }
%a{:href => "/sessions/{{data.feed_item.id}}", :rel => "external"} SESSION
.artist
%a.artist{href: "#", :hoveraction => '{{data.feed_item.helpers.artist_hoveraction}}', :'{{data.feed_item.helpers.artist_datakey}}' => '{{data.feed_item.helpers.artist_id}}'}
= '{{data.feed_item.helpers.artist_name}}'
%a.artist{:hoveraction => '{{data.feed_item.helpers.artist_hoveraction}}', :profileaction => "{{data.feed_item.helpers.artist_hoveraction}}", :'{{data.feed_item.helpers.artist_datakey}}' => '{{data.feed_item.helpers.artist_id}}'}
= '{{data.feed_item.helpers.artist_name}}'
%time.small.created_at.timeago{datetime: '{{data.feed_item.helpers.utc_created_at}}'}= '{{data.feed_item.created_at}}'
/ name and description
.left.ml20.w30
@ -34,18 +36,23 @@
/ genre and social
.left.small= '{{data.feed_item.helpers.genre}}'
.right.small.feed-details
%a{title: 'Share', class: 'btn-share'}
= image_tag 'content/icon_share.png', :height => "12", :width => "7"
&nbsp;
%span.play-count
%span.plays
= '{{data.feed_item.play_count}}'
= image_tag 'content/icon_arrow.png', :height => "12", :width => "7"
= image_tag 'content/icon_arrow.png', :height => "12", :width => "7", :title => 'Play'
%span.comment-count
%span.comments
= '{{data.feed_item.comment_count}}'
= image_tag 'content/icon_comment.png', :height => "12", :width => "13"
%a{title: 'Comment', class: 'btn-comment'}
= image_tag 'content/icon_comment.png', :height => "12", :width => "13"
%span.like-count
%span.likes
= '{{data.feed_item.comment_count}}'
= image_tag 'content/icon_like.png', :height => "12", :width => "12"
= '{{data.feed_item.like_count}}'
%a{title: 'Like', class: 'btn-like'}
= image_tag 'content/icon_like.png', :height => "12", :width => "12"
%a.details{:href => "#"} Details
%a.details-arrow.arrow-down-orange{:href => "#"}
%br/
@ -56,10 +63,10 @@
= '{% _.each(data.feed_item.participants, function(user) { %}'
%tr
%td{:width => "24"}
%a.avatar-tiny{:href => "#", :hoveraction => "musician", :'user-id' => '{{user.id}}'}
%a.avatar-tiny{:hoveraction => "musician", :profileaction => "musician", :'user-id' => '{{user.id}}'}
%img{src: '{{user.helpers.avatar}}'}
%td
%a.musician-name{:href => "#", :hoveraction => "musician", :'user-id' => '{{user.id}}'}
%a.musician-name{:hoveraction => "musician", :profileaction => "musician", :'user-id' => '{{user.id}}'}
= '{{user.first_name}} {{user.last_name}}'
%td
.nowrap

View File

@ -7,7 +7,8 @@
= recording_avatar(feed_item)
/ type and artist
.left.ml20.w15.feed-type-title
.title{hoveraction: 'recording', :'recording-id' => feed_item.candidate_claimed_recording.id } RECORDING
.title{hoveraction: 'recording', :'recording-id' => feed_item.candidate_claimed_recording.id }
%a{:href => "/recordings/#{feed_item.candidate_claimed_recording.id}", :target => "_blank"} RECORDING
.artist
= recording_artist_name(feed_item)
= timeago(feed_item.created_at, class: 'small created_at')
@ -52,18 +53,23 @@
.left.small
= recording_genre(feed_item)
.right.small.feed-details
%a{title: 'Share', class: 'btn-share'}
= image_tag 'content/icon_share.png', :height => "12", :width => "7"
&nbsp;
%span.play-count
%span.plays
= feed_item.play_count
= image_tag 'content/icon_arrow.png', :height => "12", :width => "7"
= image_tag 'content/icon_arrow.png', :height => "12", :width => "7", :title => 'Play'
%span.comment-count
%span.comments
= feed_item.comment_count
= image_tag 'content/icon_comment.png', :height => "12", :width => "13"
%a{title: 'Comment', class: 'btn-comment'}
= image_tag 'content/icon_comment.png', :height => "12", :width => "13"
%span.like-count
%span.likes
= feed_item.like_count
= image_tag 'content/icon_like.png', :height => "12", :width => "12"
%a{title: 'Like', class: 'btn-like'}
= image_tag 'content/icon_like.png', :height => "12", :width => "12"
%a.details{:href => "#"} Details
%a.details-arrow.arrow-down-orange{:href => "#"}
%br/

View File

@ -2,13 +2,15 @@
.feed-entry.recording-entry{:'data-claimed-recording-id' => '{{data.candidate_claimed_recording.id}}' }
/ avatar
.avatar-small.ib
%img{ src: '{{data.feed_item.helpers.avatar}}' }
%a{:hoveraction => "{{data.feed_item.helpers.artist_hoveraction}}", :profileaction => "{{data.feed_item.helpers.artist_hoveraction}}", :"{{data.feed_item.helpers.artist_datakey}}" => "{{data.feed_item.helpers.artist_id}}"}
%img{ src: '{{data.feed_item.helpers.avatar}}' }
/ type and artist
.left.ml20.w15
.title{hoveraction: 'recording', :'recording-id' => '{{data.candidate_claimed_recording.id}}' } RECORDING
.title{hoveraction: 'recording', :'recording-id' => '{{data.candidate_claimed_recording.id}}' }
%a{:href => "/recordings/{{data.candidate_claimed_recording.id}}", :rel => "external"} RECORDING
.artist
%a.artist{href: "#", :hoveraction => '{{data.feed_item.helpers.artist_hoveraction}}', :'{{data.feed_item.helpers.artist_datakey}}' => '{{data.feed_item.helpers.artist_id}}'}
= '{{data.feed_item.helpers.artist_name}}'
%a.artist{:hoveraction => '{{data.feed_item.helpers.artist_hoveraction}}', :profileaction => "{{data.feed_item.helpers.artist_hoveraction}}", :'{{data.feed_item.helpers.artist_datakey}}' => '{{data.feed_item.helpers.artist_id}}'}
= '{{data.feed_item.helpers.artist_name}}'
%time.small.created_at.timeago{datetime: '{{data.feed_item.helpers.utc_created_at}}'}
= '{{data.feed_item.created_at}}'
/ name and description
@ -54,18 +56,23 @@
/ genre and social
.left.small= '{{data.feed_item.helpers.genre}}'
.right.small.feed-details
%a{title: 'Share', class: 'btn-share'}
= image_tag 'content/icon_share.png', :height => "12", :width => "7"
&nbsp;
%span.play-count
%span.plays
= '{{data.feed_item.play_count}}'
= image_tag 'content/icon_arrow.png', :height => "12", :width => "7"
= image_tag 'content/icon_arrow.png', :height => "12", :width => "7", :title => 'Play'
%span.comment-count
%span.comments
= '{{data.feed_item.comment_count}}'
= image_tag 'content/icon_comment.png', :height => "12", :width => "13"
%a{title: 'Comment', class: 'btn-comment'}
= image_tag 'content/icon_comment.png', :height => "12", :width => "13"
%span.like-count
%span.likes
= '{{data.feed_item.like_count}}'
= image_tag 'content/icon_like.png', :height => "12", :width => "12"
%a{title: 'Like', class: 'btn-like'}
= image_tag 'content/icon_like.png', :height => "12", :width => "12"
%a.details{:href => "#"} Details
%a.details-arrow.arrow-down-orange{:href => "#"}
%br/
@ -76,10 +83,10 @@
= '{% _.each(data.feed_item.grouped_tracks, function(track) { %}'
%tr
%td{:width => "24"}
%a.avatar-tiny{:href => "#", :hoveraction => "musician", :"user-id" => '{{track.musician.id}}'}
%a.avatar-tiny{:hoveraction => "musician", :profileaction => "musician", :"user-id" => '{{track.musician.id}}'}
%img{src: '{{track.musician.helpers.avatar}}'}
%td
%a.musician-name{:href => "#", :hoveraction => "musician", :"user-id" => '{{track.musician.id}}'}
%a.musician-name{:hoveraction => "musician", :profileaction => "musician", :"user-id" => '{{track.musician.id}}'}
= '{{track.musician.first_name}} {{track.musician.last_name}}'
%td
.nowrap

View File

@ -25,3 +25,7 @@
$(function () {
window.JK.WelcomePage();
})
- content_for :extra_dialogs do
= render :partial => "clients/shareDialog"
= render :partial => "clients/commentDialog"

View File

@ -17,11 +17,11 @@ worker_processes 4
# as root unless it's from system init scripts.
# If running the master process as root and the workers as an unprivileged
# user, do this to switch euid/egid in the workers (also chowns logs):
user "jam-web", "jam-web"
#user "jam-web", "jam-web"
# Help ensure your application will always spawn in the symlinked
# "current" directory that Capistrano sets up.
working_directory "/var/lib/jam-web" # available in 0.94.0+
#working_directory "/var/lib/jam-web" # available in 0.94.0+
# listen on both a Unix domain socket and a TCP port,
# we use a shorter backlog for quicker failover when busy
@ -32,13 +32,13 @@ listen 3100, :tcp_nopush => true
timeout 30
# feel free to point this anywhere accessible on the filesystem
pid "/var/run/jam-web/jam-web.pid"
#pid "/var/run/jam-web/jam-web.pid"
# By default, the Unicorn logger will write to stderr.
# Additionally, ome applications/frameworks log to stderr or stdout,
# so prevent them from going to /dev/null when daemonized here:
stderr_path "/var/lib/jam-web/log/unicorn.stderr.log"
stdout_path "/var/lib/jam-web/log/unicorn.stdout.log"
#stderr_path "/var/lib/jam-web/log/unicorn.stderr.log"
#stdout_path "/var/lib/jam-web/log/unicorn.stdout.log"
# combine Ruby 2.0.0dev or REE with "preload_app true" for memory savings
# http://rubyenterpriseedition.com/faq.html#adapt_apps_for_cow
@ -95,7 +95,11 @@ after_fork do |server, worker|
ActiveRecord::Base.establish_connection
Thread.new do
JamWebEventMachine.run_em
begin
JamWebEventMachine.run_em
rescue Exception => e
puts "unable to start eventmachine in after_fork!!: #{e}"
end
end
# if preload_app is true, then you may also want to check and
# restart any other shared sockets/descriptors such as Memcached,

View File

@ -9,8 +9,9 @@ describe ApiClaimedRecordingsController do
@instrument = FactoryGirl.create(:instrument, :description => 'a great instrument')
@track = FactoryGirl.create(:track, :connection => @connection, :instrument => @instrument)
@music_session = FactoryGirl.create(:music_session, :creator => @user, :musician_access => true)
@music_session.connections << @connection
# @music_session.connections << @connection
@music_session.save
@connection.join_the_session(@music_session, true, nil)
@recording = Recording.start(@music_session, @user)
@recording.stop
@recording.reload

View File

@ -13,19 +13,58 @@ describe "Feed", :js => true, :type => :feature, :capybara_feature => true do
describe "sessions" do
before(:each) do
MusicSessionHistory.delete_all
create_session(creator: user)
formal_leave_by(user)
end
# it "should render avatar" do
# it " and link to profile" do
# end
# it " and render artist hover bubble" do
# end
# end
# it "should render description" do
# it " and link to session landing" do
# end
# it " and render session hover bubble" do
# end
# end
# it "should render stats" do
# it "should render artist name" do
# it " and link to profile" do
# end
# it " and render artist hover bubble"
# end
it "should render stats" do
visit "/client#/feed"
# initial stats
find('span.plays').should have_content('0')
find('span.comments').should have_content('0')
find('span.likes').should have_content('0')
# Comments
find('a.btn-comment').trigger(:click)
comment = 'this sounds great'
fill_in "txtComment", with: comment
find('#btn-add-comment').trigger(:click)
find('div.comment-text', text: comment)
find('#dialog-close-button', '[layout-id="comment-dialog"]').trigger(:click)
find('#btn-refresh-feed').trigger(:click)
find('span.comments').should have_content('1')
# Likes
find('a.btn-like').trigger(:click)
find('span.likes').should have_content('1')
end
it "should render details" do
visit "/client#/feed"
find('.feed-details a.details').trigger(:click)
@ -56,26 +95,78 @@ describe "Feed", :js => true, :type => :feature, :capybara_feature => true do
before(:each) do
MusicSessionHistory.delete_all
Recording.delete_all
start_recording_with(user)
stop_recording
formal_leave_by(user)
end
# it "should render avatar" do
# it " and link to profile" do
# end
# it " and render artist hover bubble" do
# end
# end
# it "should render description" do
# it " and link to recording landing" do
# end
# it " and render recording hover bubble" do
# end
# end
# it "should render stats" do
# it "should render artist name" do
# it " and link to profile" do
# end
# it " and render artist hover bubble"
# end
it "should render stats" do
visit "/client#/feed"
# close recording finished dialog
claim_recording("my recording", "my recording description")
MusicSessionHistory.delete_all
find('#btn-refresh-feed').trigger(:click)
# initial stats
find('span.plays').should have_content('0')
find('span.comments').should have_content('0')
find('span.likes').should have_content('0')
# ensure Share icon exists
find('a.btn-share')
# Comments
find('a.btn-comment').trigger(:click)
comment = 'this sounds great'
fill_in "txtComment", with: comment
find('#btn-add-comment').trigger(:click)
find('div.comment-text', text: comment)
find('#dialog-close-button', '[layout-id="comment-dialog"]').trigger(:click)
find('#btn-refresh-feed').trigger(:click)
find('span.comments').should have_content('1')
# Likes
find('a.btn-like').trigger(:click)
find('span.likes').should have_content('1')
end
it "should render details" do
visit "/client#/feed"
# close recording finished dialog
find('#recording-finished-dialog h1')
find('#discard-session-recording').trigger(:click)
claim_recording("my recording", "my recording description")
MusicSessionHistory.delete_all
find('#btn-refresh-feed').trigger(:click)
find('.feed-details a.details').trigger(:click)
@ -94,7 +185,7 @@ describe "Feed", :js => true, :type => :feature, :capybara_feature => true do
# confirm navigate to user profile page
find(".avatar-tiny[user-id=\"#{user.id}\"][hoveraction=\"musician\"]").trigger(:click)
find("#user-profile h2[id=profile-username]", text: user.name)
find("#user-profile h2[id=profile-username]", text: user.name)
end
# it "should render play widget" do

View File

@ -14,7 +14,7 @@ describe "Home Screen", :js => true, :type => :feature, :capybara_feature => tru
let(:user) { FactoryGirl.create(:user) }
share_examples_for :has_footer do
shared_examples_for :has_footer do
it "should have footer elements" do
should have_selector('#footer-links')
find('#footer-links').should have_content('about')

View File

@ -61,7 +61,13 @@ describe "social metadata" do
let(:connection) { FactoryGirl.create(:connection, :user => user) }
let(:instrument) { FactoryGirl.create(:instrument, :description => 'a great instrument') }
let(:track) { FactoryGirl.create(:track, :connection => connection, :instrument => instrument) }
let(:music_session) { ms = FactoryGirl.create(:music_session, :creator => user, :musician_access => true); ms.connections << connection; ms.save!; ms }
let(:music_session) {
ms = FactoryGirl.create(:music_session, :creator => user, :musician_access => true)
# ms.connections << connection
ms.save!
connection.join_the_session(ms, true, nil)
ms
}
it "renders facebook metadata" do
visit "/sessions/#{music_session.id}"
@ -85,8 +91,9 @@ describe "social metadata" do
@instrument = FactoryGirl.create(:instrument, :description => 'a great instrument')
@track = FactoryGirl.create(:track, :connection => @connection, :instrument => @instrument)
@music_session = FactoryGirl.create(:music_session, :creator => @user, :musician_access => true)
@music_session.connections << @connection
# @music_session.connections << @connection
@music_session.save
@connection.join_the_session(@music_session, true, nil)
@recording = Recording.start(@music_session, @user)
@recording.stop
@recording.reload

View File

@ -8,8 +8,9 @@ describe MusicSessionHelper do
@instrument = FactoryGirl.create(:instrument, :description => 'a great instrument')
@track = FactoryGirl.create(:track, :connection => @connection, :instrument => @instrument)
@music_session = FactoryGirl.create(:music_session, :creator => @user, :musician_access => true)
@music_session.connections << @connection
# @music_session.connections << @connection
@music_session.save
@connection.join_the_session(@music_session, true, nil)
@recording = Recording.start(@music_session, @user)
@recording.stop
@recording.reload

View File

@ -698,8 +698,9 @@ describe "Music Session API ", :type => :api do
connection = FactoryGirl.create(:connection, :user => user)
track = FactoryGirl.create(:track, :connection => connection, :instrument => Instrument.first)
music_session = FactoryGirl.create(:music_session, :creator => user, :musician_access => true)
music_session.connections << connection
# music_session.connections << connection
music_session.save
connection.join_the_session(music_session, true, nil)
recording = Recording.start(music_session, user)
recording.stop
recording.reload

View File

@ -981,7 +981,12 @@ describe "User API", :type => :api do
let(:connection) { FactoryGirl.create(:connection, :user => user) }
let(:instrument) { FactoryGirl.create(:instrument, :description => 'a great instrument') }
let(:track) { FactoryGirl.create(:track, :connection => connection, :instrument => instrument) }
let(:music_session) { ms = FactoryGirl.create(:music_session, :creator => user, :musician_access => true); ms.connections << connection; ms.save!; ms }
let(:music_session) {
ms = FactoryGirl.create(:music_session, :creator => user, :musician_access => true)
# ms.connections << connection
ms.save!
connection.join_the_session(ms, true, nil)
ms }
it "fetches facebook successfully" do
login(user.email, user.password, 200, true)
@ -1115,8 +1120,9 @@ describe "User API", :type => :api do
@instrument = FactoryGirl.create(:instrument, :description => 'a great instrument')
@track = FactoryGirl.create(:track, :connection => @connection, :instrument => @instrument)
@music_session = FactoryGirl.create(:music_session, :creator => user, :musician_access => true)
@music_session.connections << @connection
# @music_session.connections << @connection
@music_session.save
@connection.join_the_session(@music_session, true, nil)
@recording = Recording.start(@music_session, user)
@recording.stop
@recording.reload

View File

@ -322,7 +322,7 @@ def claim_recording(name, description)
fill_in "claim-recording-name", with: name
fill_in "claim-recording-description", with: description
find('#keep-session-recording').trigger(:click)
should have_no_selector('h1', text: 'recording finished')
page.should have_no_selector('h1', text: 'recording finished')
end
def set_session_as_private()