diff --git a/db/manifest b/db/manifest
index 36e365013..5bad3d1e0 100755
--- a/db/manifest
+++ b/db/manifest
@@ -182,3 +182,4 @@ rsvp_slots_prof_level.sql
add_file_name_music_notation.sql
change_scheduled_start_music_session.sql
music_sessions_iso_639_3.sql
+discardable_claimed_recordings.sql
diff --git a/db/up/discardable_claimed_recordings.sql b/db/up/discardable_claimed_recordings.sql
new file mode 100644
index 000000000..d082ff770
--- /dev/null
+++ b/db/up/discardable_claimed_recordings.sql
@@ -0,0 +1 @@
+ALTER TABLE claimed_recordings ADD COLUMN discarded BOOLEAN DEFAULT FALSE;
\ No newline at end of file
diff --git a/ruby/lib/jam_ruby/lib/nav.rb b/ruby/lib/jam_ruby/lib/nav.rb
index 56a7a09af..1bce0f133 100644
--- a/ruby/lib/jam_ruby/lib/nav.rb
+++ b/ruby/lib/jam_ruby/lib/nav.rb
@@ -1,9 +1,18 @@
module JamRuby
+ # To use this, be sure to add this in before block: stub_const("APP_CONFIG", web_config)
class Nav
def self.home(options ={})
- "#{APP_CONFIG.external_root_url}/client#/home#{dialog(options)}"
+ "#{base_url}/home#{dialog(options)}"
+ end
+
+ def self.profile(user)
+ "#{base_url}/profile/#{user.id}"
+ end
+
+ def self.feed
+ "#{base_url}/feed"
end
def self.accept_friend_request_dialog(friend_request_id)
@@ -12,6 +21,10 @@ module JamRuby
private
+ def self.base_url
+ "#{APP_CONFIG.external_root_url}/client#"
+ end
+
def self.dialog(options)
dialog = ''
if options[:dialog]
diff --git a/ruby/lib/jam_ruby/models/claimed_recording.rb b/ruby/lib/jam_ruby/models/claimed_recording.rb
index 573d5a390..d984d6a3c 100644
--- a/ruby/lib/jam_ruby/models/claimed_recording.rb
+++ b/ruby/lib/jam_ruby/models/claimed_recording.rb
@@ -8,9 +8,9 @@ module JamRuby
belongs_to :genre, :class_name => "JamRuby::Genre"
has_many :recorded_tracks, :through => :recording, :class_name => "JamRuby::RecordedTrack"
has_many :playing_sessions, :class_name => "JamRuby::ActiveMusicSession"
- has_many :likes, :class_name => "JamRuby::RecordingLiker", :foreign_key => "claimed_recording_id"
+ has_many :likes, :class_name => "JamRuby::RecordingLiker", :foreign_key => "claimed_recording_id", :dependent => :destroy
has_many :plays, :class_name => "JamRuby::PlayablePlay", :foreign_key => "claimed_recording_id", :dependent => :destroy
- has_one :share_token, :class_name => "JamRuby::ShareToken", :inverse_of => :shareable, :foreign_key => 'shareable_id'
+ has_one :share_token, :class_name => "JamRuby::ShareToken", :inverse_of => :shareable, :foreign_key => 'shareable_id', :dependent => :destroy
validates :name, no_profanity: true, length: {minimum: 3, maximum: 64}, presence: true
validates :description, no_profanity: true, length: {maximum: 8000}
@@ -39,9 +39,9 @@ module JamRuby
raise PermissionError, "user doesn't own claimed_recording"
end
- self.name = params[:name] unless params[:name].nil?
- self.description = params[:description] unless params[:description].nil?
- self.genre = Genre.find(params[:genre]) unless params[:genre].nil?
+ self.name = params[:name]
+ self.description = params[:description]
+ self.genre = Genre.find_by_id(params[:genre]) unless params[:genre].nil?
self.is_public = params[:is_public] unless params[:is_public].nil?
save
end
@@ -50,13 +50,10 @@ module JamRuby
if user != self.user
raise PermissionError, "user doesn't own claimed_recording"
end
-
- # If this is the only copy, destroy the entire recording. Otherwise, just destroy this claimed_recording
- if recording.claimed_recordings.count == 1
- recording.destroy
- else
- self.destroy
- end
+
+ ClaimedRecording.where(:id => id).update_all(:discarded => true )
+
+ recording.discard(user)
end
diff --git a/ruby/lib/jam_ruby/models/feed.rb b/ruby/lib/jam_ruby/models/feed.rb
index 7c118d364..0802e3085 100644
--- a/ruby/lib/jam_ruby/models/feed.rb
+++ b/ruby/lib/jam_ruby/models/feed.rb
@@ -43,6 +43,7 @@ module JamRuby
query = Feed.joins("LEFT OUTER JOIN recordings ON recordings.id = feeds.recording_id")
.joins("LEFT OUTER JOIN music_sessions ON music_sessions.id = feeds.music_session_id")
.limit(limit)
+ .where('recordings is NULL OR recordings.all_discarded = false') # remove any 'all_discarded recordings from the search results'
# handle sort
if sort == 'date'
@@ -72,14 +73,14 @@ module JamRuby
end
- if target_user
+ if target_user
if target_user != user.id
require_public_recordings = "claimed_recordings.is_public = TRUE AND"
require_public_sessions = "music_sessions.fan_access = TRUE AND"
end
- query = query.joins("LEFT OUTER JOIN claimed_recordings ON recordings.id = claimed_recordings.recording_id AND #{require_public_recordings} (claimed_recordings.user_id = '#{target_user}' OR (recordings.band_id IN (SELECT band_id FROM bands_musicians where user_id='#{target_user}')))")
+ query = query.joins("LEFT OUTER JOIN claimed_recordings ON recordings.id = claimed_recordings.recording_id AND claimed_recordings.discarded = FALSE AND #{require_public_recordings} (claimed_recordings.user_id = '#{target_user}' OR (recordings.band_id IN (SELECT band_id FROM bands_musicians where user_id='#{target_user}')))")
query = query.joins("LEFT OUTER JOIN music_sessions_user_history ON music_sessions.id = music_sessions_user_history.music_session_id AND #{require_public_sessions} music_sessions_user_history.user_id = '#{target_user}'")
query = query.group("feeds.id, feeds.recording_id, feeds.music_session_id, feeds.created_at, feeds.updated_at, recordings.id, music_sessions.id")
if sort == 'plays'
@@ -97,7 +98,7 @@ module JamRuby
require_public_sessions = "music_sessions.fan_access = TRUE AND"
end
- query = query.joins("LEFT OUTER JOIN claimed_recordings ON recordings.id = claimed_recordings.recording_id AND #{require_public_recordings} recordings.band_id = '#{target_band}'")
+ query = query.joins("LEFT OUTER JOIN claimed_recordings ON recordings.id = claimed_recordings.recording_id AND claimed_recordings.discarded = FALSE AND #{require_public_recordings} recordings.band_id = '#{target_band}'")
query = query.where("music_sessions IS NULL OR #{require_public_sessions} music_sessions.band_id = '#{target_band}'")
query = query.group("feeds.id, feeds.recording_id, feeds.music_session_id, feeds.created_at, feeds.updated_at, recordings.id, music_sessions.id")
if sort == 'plays'
@@ -108,7 +109,7 @@ module JamRuby
query = query.where('recordings.id is NULL OR claimed_recordings.id IS NOT NULL')
#query = query.where('music_sessions.id is NULL OR music_sessions_user_history.id IS NOT NULL')
else
- query = query.joins('LEFT OUTER JOIN claimed_recordings ON recordings.id = claimed_recordings.recording_id AND claimed_recordings.is_public = TRUE')
+ query = query.joins('LEFT OUTER JOIN claimed_recordings ON recordings.id = claimed_recordings.recording_id AND claimed_recordings.discarded = FALSE AND claimed_recordings.is_public = TRUE')
query = query.joins("LEFT OUTER JOIN music_sessions_user_history ON music_sessions.id = music_sessions_user_history.music_session_id AND music_sessions.fan_access = TRUE")
query = query.group("feeds.id, feeds.recording_id, feeds.music_session_id, feeds.created_at, feeds.updated_at, recordings.id, music_sessions.id")
if sort == 'plays'
diff --git a/ruby/lib/jam_ruby/models/recording.rb b/ruby/lib/jam_ruby/models/recording.rb
index 143aed8f6..c4d5fc08d 100644
--- a/ruby/lib/jam_ruby/models/recording.rb
+++ b/ruby/lib/jam_ruby/models/recording.rb
@@ -5,12 +5,12 @@ module JamRuby
attr_accessible :owner, :owner_id, :band, :band_id, :recorded_tracks_attributes, :mixes_attributes, :claimed_recordings_attributes, :name, :description, :genre, :is_public, :duration, as: :admin
- has_many :claimed_recordings, :class_name => "JamRuby::ClaimedRecording", :inverse_of => :recording, :foreign_key => 'recording_id', :dependent => :destroy
has_many :users, :through => :recorded_tracks, :class_name => "JamRuby::User"
+ has_many :claimed_recordings, :class_name => "JamRuby::ClaimedRecording", :inverse_of => :recording, :foreign_key => 'recording_id', :dependent => :destroy
has_many :mixes, :class_name => "JamRuby::Mix", :inverse_of => :recording, :foreign_key => 'recording_id', :dependent => :destroy
has_many :recorded_tracks, :class_name => "JamRuby::RecordedTrack", :foreign_key => :recording_id, :dependent => :destroy
- has_many :comments, :class_name => "JamRuby::RecordingComment", :foreign_key => "recording_id"
- has_many :likes, :class_name => "JamRuby::RecordingLiker", :foreign_key => "recording_id"
+ has_many :comments, :class_name => "JamRuby::RecordingComment", :foreign_key => "recording_id", :dependent => :destroy
+ has_many :likes, :class_name => "JamRuby::RecordingLiker", :foreign_key => "recording_id", :dependent => :destroy
has_many :plays, :class_name => "JamRuby::PlayablePlay", :as => :playable, :dependent => :destroy
has_one :feed, :class_name => "JamRuby::Feed", :inverse_of => :recording, :foreign_key => 'recording_id', :dependent => :destroy
@@ -128,7 +128,7 @@ module JamRuby
unless self.users.exists?(user)
raise PermissionError, "user was not in this session"
end
- recorded_tracks.where(:user_id=> user.id)
+ recorded_tracks.where(:user_id => user.id)
end
def has_access?(user)
@@ -211,7 +211,7 @@ module JamRuby
# check if all recorded_tracks for this recording are discarded
if recorded_tracks.where('discard = false or discard is NULL').length == 0
- self.all_discarded = true
+ self.all_discarded = true # the feed won't pick this up; also background cleanup will find these and whack them later
self.save(:validate => false)
end
@@ -238,7 +238,7 @@ module JamRuby
.order('recorded_tracks.id')
.where('recorded_tracks.fully_uploaded = TRUE')
.where('recorded_tracks.id > ?', since)
- .where('claimed_recordings.user_id = ?', user).limit(limit).each do |recorded_track|
+ .where('claimed_recordings.user_id = ? AND claimed_recordings.discarded = FALSE', user).limit(limit).each do |recorded_track|
downloads.push(
{
:type => "recorded_track",
@@ -258,7 +258,7 @@ module JamRuby
.order('mixes.id')
.where('mixes.completed_at IS NOT NULL')
.where('mixes.id > ?', since)
- .where('claimed_recordings.user_id = ?', user)
+ .where('claimed_recordings.user_id = ? AND claimed_recordings.discarded = FALSE', user)
.limit(limit).each do |mix|
downloads.push(
{
diff --git a/ruby/spec/jam_ruby/models/recording_spec.rb b/ruby/spec/jam_ruby/models/recording_spec.rb
index d61a1eecd..0bc9702e0 100644
--- a/ruby/spec/jam_ruby/models/recording_spec.rb
+++ b/ruby/spec/jam_ruby/models/recording_spec.rb
@@ -188,8 +188,12 @@ describe Recording do
expect { @claimed_recordign.discard(@user2) }.to raise_error
@claimed_recording = @recording.claim(@user2, "name2", "description2", @genre, true)
@claimed_recording.discard(@user2)
+ @claimed_recording.reload
+ @claimed_recording.discarded.should == true
+ @recording.recorded_tracks_for_user(@user2)[0].discard.should == true
@recording.reload
- @recording.claimed_recordings.length.should == 1
+ @recording.claimed_recordings.length.should == 2
+ @recording.all_discarded.should == false
end
it "should destroy the entire recording if there was only one claimed_recording which is discarded" do
@@ -199,8 +203,10 @@ describe Recording do
@genre = FactoryGirl.create(:genre)
@claimed_recording = @recording.claim(@user, "name", "description", @genre, true)
@claimed_recording.discard(@user)
- expect { Recording.find(@recording.id) }.to raise_error
- expect { ClaimedRecording.find(@claimed_recording.id) }.to raise_error
+ @claimed_recording.reload
+ @claimed_recording.discarded.should == true
+ @claimed_recording.recording.all_discarded.should == true
+ @recording.recorded_tracks_for_user(@user)[0].discard.should == true
end
it "should use the since parameter when restricting uploads" do
diff --git a/web/app/assets/javascripts/banner.js b/web/app/assets/javascripts/banner.js
index 7eda9fa0f..99e0ddfbd 100644
--- a/web/app/assets/javascripts/banner.js
+++ b/web/app/assets/javascripts/banner.js
@@ -9,6 +9,9 @@
var self = this;
var logger = context.JK.logger;
var $banner = null;
+ var $closeBtn = null;
+ var $yesBtn = null;
+ var $noBtn = null;
// you can also do
// * showAlert('title', 'text')
@@ -26,6 +29,19 @@
return show(options);
}
+ function showYesNo(options) {
+ if (typeof options == 'string' || options instanceof String) {
+ if(arguments.length == 2) {
+ options = {title: options, html:arguments[1]}
+ }
+ else {
+ options = {html:options};
+ }
+ }
+ options.type = 'yes_no'
+ return show(options);
+ }
+
// responsible for updating the contents of the update dialog
// as well as registering for any event handlers
function show(options) {
@@ -33,7 +49,12 @@
var html = options.html;
if(!options.title) {
- options.title = 'alert'
+ if(options.type == 'alert') {
+ options.title = 'alert'
+ }
+ else if(options.type == 'yes_no') {
+ options.title = 'please confirm';
+ }
}
var $h1 = $banner.find('h1');
@@ -50,11 +71,18 @@
throw "unable to show banner for empty message";
}
- var $closeBtn = $banner.find('.close-btn');
if((options.type == "alert" && !options.buttons) || options.close) {
- $closeBtn.show().click(function() {
+ var closeButtonText = 'CLOSE';
+ if(options.close !== null && typeof options.close == 'object') {
+ // extra styling options for close button
+ if(options.close.name) {
+ closeButtonText = options.close.name;
+ }
+ }
+
+ $closeBtn.show().text(closeButtonText).unbind('click').click(function() {
hide();
return false;
});
@@ -63,6 +91,27 @@
$closeBtn.hide();
}
+ if(options.type == "yes_no") {
+ $yesBtn.show().unbind('click').click(function() {
+ if(options.yes) {
+ options.yes();
+ }
+ hide();
+ return false;
+ })
+ $noBtn.show().unbind('click').click(function() {
+ if(options.no) {
+ options.no();
+ }
+ hide();
+ return false;
+ })
+ }
+ else {
+ $yesBtn.hide();
+ $noBtn.hide();
+ }
+
if(options.buttons) {
var $buttons = $banner.find('.buttons')
context._.each(options.buttons, function(button) {
@@ -86,6 +135,7 @@
return newContent;
}
+
function hide() {
$banner.hide();
$banner.find('.user-btn').remove();
@@ -96,6 +146,10 @@
function initialize() {
$banner = $('#banner');
+
+ $closeBtn = $banner.find('.close-btn');
+ $yesBtn = $banner.find('.yes-btn');
+ $noBtn = $banner.find('.no-btn');
return self;
}
@@ -104,6 +158,7 @@
initialize: initialize,
show: show,
showAlert: showAlert,
+ showYesNo: showYesNo,// shows Yes and Cancel button (confirmation dialog)
hide: hide
}
diff --git a/web/app/assets/javascripts/commentDialog.js b/web/app/assets/javascripts/commentDialog.js
index 263ea7d6d..794ee141b 100644
--- a/web/app/assets/javascripts/commentDialog.js
+++ b/web/app/assets/javascripts/commentDialog.js
@@ -122,7 +122,7 @@
}
function showDialog() {
- app.layout.showDialog('comment-dialog');
+ return app.layout.showDialog('comment-dialog');
}
function initialize() {
diff --git a/web/app/assets/javascripts/editRecordingDialog.js b/web/app/assets/javascripts/editRecordingDialog.js
new file mode 100644
index 000000000..23c1d4919
--- /dev/null
+++ b/web/app/assets/javascripts/editRecordingDialog.js
@@ -0,0 +1,170 @@
+(function (context, $) {
+
+ "use strict";
+ context.JK = context.JK || {};
+ context.JK.EditRecordingDialog = function (app) {
+ var logger = context.JK.logger;
+ var rest = context.JK.Rest();
+ var claimedRecordingId = null;
+ var $dialog = null;
+ var $form = null;
+ var $name = null;
+ var $description = null;
+ var $genre = null;
+ var $isPublic = null;
+ var $cancelBtn = null;
+ var $saveBtn = null;
+ var $deleteBtn = null;
+
+ var updating = false;
+ var deleting = false;
+
+ function resetForm() {
+
+ // remove all display errors
+ $dialog.find('.error-text').remove()
+ $dialog.find('.error').removeClass("error")
+ }
+
+ function beforeShow(args) {
+
+ claimedRecordingId = args.d1;
+
+ if(!claimedRecordingId) throw "claimedRecordingId must be specified";
+
+ resetForm();
+
+ rest.getClaimedRecording(claimedRecordingId)
+ .done(function(data) {
+ var name = data.name;
+ var description = data.description;
+ var is_public = data.is_public;
+ var genre_id = data.genre_id;
+
+ context.JK.GenreSelectorHelper.setSelectedGenres($genre.parent(), [genre_id]);
+ $name.val(name);
+ $description.val(description);
+ if(is_public) {
+ $isPublic.attr('checked', 'checked').iCheck('check')
+ }
+ else {
+ $isPublic.removeAttr('checked').iCheck('uncheck')
+ }
+ })
+ .fail(app.ajaxError)
+ }
+
+ function afterHide() {
+
+ }
+
+ function attemptUpdate() {
+ if(updating) return;
+
+ updating = true;
+ var name = $name.val();
+ var description = $description.val();
+ var genre = $genre.val();
+ var is_public = $isPublic.is(':checked');
+
+ rest.updateClaimedRecording({id: claimedRecordingId, name: name, description: description, is_public: is_public, genre: genre })
+ .done(function(updated) {
+ resetForm();
+ $dialog.triggerHandler('recording_updated', {id: claimedRecordingId, name: name, description: description, is_public: is_public, genre: genre})
+ app.layout.closeDialog('edit-recording');
+ })
+ .fail(function(jqXHR) {
+
+ if(jqXHR.status = 422) {
+ // highlight fields in error
+
+ resetForm();
+
+ var errors = JSON.parse(jqXHR.responseText);
+
+ var $name_errors = context.JK.format_errors('name', errors);
+ if ($name_errors) $name.closest('div.field').addClass('error').end().after($name_errors);
+
+ var $description_errors = context.JK.format_errors('description', errors);
+ if ($description_errors) $description.closest('div.field').addClass('error').end().after($description_errors);
+
+ var $genre_errors = context.JK.format_errors('genre', errors);
+ if ($genre_errors) $genre.closest('div.field').addClass('error').end().after($genre_errors);
+
+ var $is_public_errors = context.JK.format_errors('is_public', errors);
+ if ($is_public_errors) $isPublic.closest('div.field').addClass('error').end().after($is_public_errors);
+
+ }
+ else {
+ app.ajaxError(arguments);
+ }
+ })
+ .always(function() {
+ updating = false;
+ })
+ }
+
+ function attemptDelete() {
+ if(deleting) return;
+
+ deleting = true;
+ context.JK.Banner.showYesNo({
+ title: "Confirm Deletion",
+ html: "Are you sure you want to delete this recording?",
+ yes: function() {
+ rest.deleteClaimedRecording(claimedRecordingId)
+ .done(function() {
+ $dialog.triggerHandler('recording_deleted', {id: claimedRecordingId});
+ app.layout.closeDialog('edit-recording');
+ })
+ .fail(app.ajaxError)
+ .always(function() {
+ deleting = false;
+ })
+ },
+ no : function() {
+ context.JK.Banner.hide();
+ deleting = false;
+ }
+ })
+ }
+
+ function cancel() {
+ app.layout.closeDialog('edit-recording');
+ }
+
+ function events() {
+ $saveBtn.click(attemptUpdate);
+ $deleteBtn.click(attemptDelete);
+ $cancelBtn.click(cancel)
+ $form.submit(false);
+ }
+
+
+ function initialize() {
+ var dialogBindings = {
+ 'beforeShow': beforeShow,
+ 'afterHide': afterHide
+ };
+
+ app.bindDialog('edit-recording', dialogBindings);
+
+ $dialog = $('#edit-recording-dialog');
+ $form = $dialog.find('form');
+ $cancelBtn = $dialog.find('.cancel-btn');
+ $saveBtn = $dialog.find('.save-btn');
+ $deleteBtn = $dialog.find('.delete-btn');
+ $name = $dialog.find('input[name="name"]');
+ $description = $dialog.find('textarea[name="description"]');
+ $genre = $dialog.find('select[name=genre]');
+ $isPublic = $dialog.find('input[name=is_public]');
+
+ events();
+
+ context.JK.GenreSelectorHelper.render($genre.parent());
+ context.JK.checkbox($isPublic);
+ };
+
+ this.initialize = initialize;
+ }
+})(window, jQuery);
\ No newline at end of file
diff --git a/web/app/assets/javascripts/feed.js b/web/app/assets/javascripts/feed.js
index 486b9e604..41027b898 100644
--- a/web/app/assets/javascripts/feed.js
+++ b/web/app/assets/javascripts/feed.js
@@ -27,7 +27,7 @@
var $scroller = $screen.find('.content-body-scroller');
var $content = $screen.find('.feed-content');
var $noMoreFeeds = $('#end-of-feeds-list');
- var $refresh = $screen.find('#btn-refresh-feed');
+ var $refresh = $screen.find('.btn-refresh-entries');
var $sortFeedBy = $screen.find('#feed_order_by');
var $includeDate = $screen.find('#feed_date');
var $includeType = $screen.find('#feed_show');
diff --git a/web/app/assets/javascripts/feedHelper.js b/web/app/assets/javascripts/feedHelper.js
index 088a8c37e..fd7b3eb73 100644
--- a/web/app/assets/javascripts/feedHelper.js
+++ b/web/app/assets/javascripts/feedHelper.js
@@ -7,7 +7,9 @@
var logger = context.JK.logger;
var rest = new context.JK.Rest();
+ var EVENTS = context.JK.EVENTS;
var ui = new context.JK.UIHelper(JK.app);
+ var userId = null;
var currentQuery = null;
var currentPage = 0;
var LIMIT = 20;
@@ -29,6 +31,10 @@
query.since = next;
}
+ if(userId) {
+ query.user = userId;
+ }
+
return query;
}
@@ -61,6 +67,10 @@
$scroller.infinitescroll('pause');
logger.debug("end of feeds")
+ if(currentPage == 0 && response.entries.length == 0) {
+ $content.append("
This user has no history.
") ;
+ }
+
if(currentPage > 0) {
$noMoreFeeds.show();
// there are bugs with infinitescroll not removing the 'loading'.
@@ -75,6 +85,10 @@
}
}
+ function setUser(_userId) {
+ userId = _userId;
+ }
+
function refresh() {
clearResults();
@@ -221,6 +235,49 @@
return false;
}
+ function isOwner() {
+ return userId == context.JK.currentUserId;
+ }
+
+ function obtainCandidate(recording) {
+ if(isOwner()) {
+ var candidate = null;
+ context._.each(recording.claimed_recordings, function(claimedRecording) {
+ if(claimedRecording.user_id == context.JK.currentUserId) {
+ candidate = claimedRecording;
+ return false;
+ }
+ })
+
+ if(!candidate) throw "unable to find candidate claimed recording, yet we can see this recording. server error..."
+ return candidate;
+ }
+ else {
+ return recording.claimed_recordings[0]
+ }
+ }
+
+ function toggleOpen($feedItem, $name, $description, $musicians) {
+ $description.trigger('destroy.dot');
+ $description.data('original-height', $description.css('height')).css('height', 'auto');
+ $name.trigger('destroy.dot');
+ $name.data('original-height', $name.css('height')).css('height', 'auto');
+ $musicians.show();
+ $feedItem.animate({'max-height': '1000px'});
+ }
+
+ function toggleClose($feedItem, $name, $description, $musicians, immediate) {
+ $feedItem.css('height', $feedItem.height() + 'px')
+ $feedItem.animate({'height': $feedItem.data('original-max-height')}, immediate ? 0 : 400).promise().done(function() {
+ $feedItem.css('height', 'auto').css('max-height', $feedItem.data('original-max-height'));
+
+ $musicians.hide();
+ $description.css('height', $description.data('original-height'));
+ $description.dotdotdot();
+ $name.css('height', $name.data('original-height'));
+ $name.dotdotdot();
+ });
+ }
function stateChangeRecording(e, data) {
var $controls = data.element;
@@ -251,24 +308,10 @@
var toggledOpen = $detailsLink.data('toggledOpen');
if(toggledOpen) {
- $feedItem.css('height', $feedItem.height() + 'px')
- $feedItem.animate({'height': $feedItem.data('original-max-height')}).promise().done(function() {
- $feedItem.css('height', 'auto').css('max-height', $feedItem.data('original-max-height'));
-
- $musicians.hide();
- $description.css('height', $description.data('original-height'));
- $description.dotdotdot();
- $name.css('height', $name.data('original-height'));
- $name.dotdotdot();
- });
+ toggleClose($feedItem, $name, $description, $musicians)
}
else {
- $description.trigger('destroy.dot');
- $description.data('original-height', $description.css('height')).css('height', 'auto');
- $name.trigger('destroy.dot');
- $name.data('original-height', $name.css('height')).css('height', 'auto');
- $musicians.show();
- $feedItem.animate({'max-height': '1000px'});
+ toggleOpen($feedItem, $name, $description, $musicians)
}
toggledOpen = !toggledOpen;
@@ -277,6 +320,64 @@
return false;
}
+ function updateRecordingName($feedEntry, name) {
+ $feedEntry.find('.name-text').text(name);
+ }
+
+ function updateRecordingDescription($feedEntry, description) {
+ $feedEntry.find('.description').text(description);
+ }
+
+ function updateIsPublic($feedEntry, isPublic) {
+ var $isPrivate = $feedEntry.find('.is_private')
+ if(isPublic) {
+ $isPrivate.removeClass('enabled')
+ }
+ else {
+ $isPrivate.addClass('enabled')
+ }
+ }
+
+ function updateComments($feedEntry, comments) {
+ $feedEntry.find('.comments').html(comments)
+ }
+
+ function updatePlays($feedEntry, plays) {
+ $feedEntry.find('.plays').html(plays);
+ }
+
+ function updateLikes($feedEntry, likes) {
+ $feedEntry.find('.likes').html(likes);
+ }
+
+ function updateStats($feedEntry) {
+ if($feedEntry.is('.recording-entry')) {
+ var id = $feedEntry.attr('data-claimed-recording-id');
+ rest.getClaimedRecording(id)
+ .done(function(claimedRecording) {
+ updateComments($feedEntry, claimedRecording.recording.comment_count);
+ updateLikes($feedEntry, claimedRecording.recording.like_count);
+ updatePlays($feedEntry, claimedRecording.recording.play_count);
+
+ })
+ .fail(app.ajaxError)
+ }
+ else {
+ var id = $feedEntry.attr('data-music-session');
+ rest.getSessionHistory(id)
+ .done(function(music_session) {
+ updateComments($feedEntry, music_session.comment_count);
+ updateLikes($feedEntry, music_session.like_count);
+ updatePlays($feedEntry, music_session.play_count);
+ })
+ .fail(app.ajaxError)
+ }
+ }
+
+ function updateGenre($feedEntry, genre) {
+ $feedEntry.find('.genre').text(context.JK.GenreSelectorHelper.getNameForId(genre));
+ }
+
function renderFeeds(feeds) {
$.each(feeds.entries, function(i, feed) {
@@ -308,10 +409,12 @@
}
$('.btn-comment', $feedItem).click(function() {
- ui.launchCommentDialog({
+ var result = ui.launchCommentDialog({
session_id: feed.id,
entity_type: 'session'
- });
+ }).one(EVENTS.DIALOG_CLOSED, function() {
+ updateStats($feedItem);
+ })
});
$('.btn-like', $feedItem).click(function() {
@@ -336,7 +439,7 @@
}
var options = {
feed_item: feed,
- candidate_claimed_recording: feed.claimed_recordings[0],
+ candidate_claimed_recording: obtainCandidate(feed),
mix_class: feed['has_mix?'] ? 'has-mix' : 'no-mix',
}
@@ -349,6 +452,7 @@
$('.details', $feedItem).click(toggleRecordingDetails);
$('.details-arrow', $feedItem).click(toggleRecordingDetails);
$('.play-button', $feedItem).click(toggleRecordingPlay);
+ updateIsPublic($feedItem, options.candidate_claimed_recording.is_public);
$('.btn-share', $feedItem).click(function() {
ui.launchShareDialog(options.candidate_claimed_recording.id, 'recording');
@@ -359,6 +463,9 @@
recording_id: feed.id,
claimed_recording_id: options.candidate_claimed_recording.id,
entity_type: 'recording'
+ })
+ .one(EVENTS.DIALOG_CLOSED, function() {
+ updateStats($feedItem);
});
});
@@ -366,6 +473,47 @@
ui.addRecordingLike(feed.id, options.candidate_claimed_recording.id, JK.currentUserId, $('.likes', $feedItem), $('.btn-like', $feedItem));
});
+ if(isOwner()) {
+ $('.edit-recording-dialog', $feedItem).data('claimed_recording_id', options.candidate_claimed_recording.id).click(function() {
+ app.layout.showDialog('edit-recording', {d1: $(this).data('claimed_recording_id')})
+ .one(EVENTS.DIALOG_CLOSED, function() {
+ $(this).unbind('recording_updated').unbind('recording_deleted');
+ })
+ .one('recording_updated', function(e, data) {
+ // find recording by claimed recording id
+ var $feedEntry = $screen.find('.feed-entry.recording-entry[data-claimed-recording-id="'+ data.id +'"]');
+ var $musicians = $feedEntry.find('.musician-detail');
+ var $description = $feedEntry.find('.description');
+ var $name = $feedEntry.find('.name');
+ var $detailsLink = $feedEntry.find('.details');
+ var toggledOpen = $detailsLink.data('toggledOpen');
+
+ if(toggledOpen) {
+ toggleClose($feedEntry, $name, $description, $musicians, true);
+ }
+
+ $description.trigger('destroy.dot');
+ $name.trigger('destroy.dot');
+
+ updateRecordingName($feedEntry, data.name);
+ updateRecordingDescription($feedEntry, data.description);
+ updateIsPublic($feedEntry, data.is_public);
+ updateGenre($feedEntry, data.genre);
+
+ $name.dotdotdot();
+ $description.dotdotdot();
+ $feedItem.data('original-max-height', $feedEntry.css('height'));
+
+ $detailsLink.data('toggledOpen', false);
+ })
+ .one('recording_deleted', function(e, data) {
+ var $feedEntry = $screen.find('.feed-entry.recording-entry[data-claimed-recording-id="'+ data.id +'"]');
+ $feedEntry.remove();
+ })
+ return false;
+ }).show();
+ }
+
// put the feed item on the page
renderFeed($feedItem);
@@ -431,6 +579,7 @@
this.initialize = initialize;
this.refresh = refresh;
+ this.setUser = setUser;
return this;
}
diff --git a/web/app/assets/javascripts/genreSelector.js b/web/app/assets/javascripts/genreSelector.js
index 7dabdf2b0..42c2ca442 100644
--- a/web/app/assets/javascripts/genreSelector.js
+++ b/web/app/assets/javascripts/genreSelector.js
@@ -7,21 +7,12 @@
"use strict";
context.JK = context.JK || {};
+ context.JK.GenreSelectorDeferred = null;
context.JK.GenreSelectorHelper = (function() {
var logger = context.JK.logger;
var _genres = []; // will be list of structs: [ {label:xxx, value:yyy}, {...}, ... ]
- function loadGenres() {
- var url = "/api/genres";
- $.ajax({
- type: "GET",
- url: url,
- async: false, // do this synchronously so the event handlers in events() can be wired up
- success: genresLoaded
- });
- }
-
function reset(parentSelector, defaultGenre) {
defaultGenre = typeof(defaultGenre) == 'undefined' ? '' : defaultGenre;
$('select', parentSelector).val(defaultGenre);
@@ -38,7 +29,7 @@
function render(parentSelector) {
$('select', parentSelector).empty();
- $('select', parentSelector).append('Any Genre ');
+ $('select', parentSelector).append('Unspecified ');
var template = $('#template-genre-option').html();
$.each(_genres, function(index, value) {
// value will be a dictionary entry from _genres:
@@ -67,6 +58,21 @@
return selectedGenres;
}
+ function getNameForId(genreId) {
+ var name = null;
+ context._.each(_genres, function(genre) {
+ if(genreId == genre.value) {
+ name = genre.label;
+ return false;
+ }
+ });
+
+ if(!name) {
+ logger.warn("no genre found for genreId: " + genreId);
+ }
+ return name;
+ }
+
function setSelectedGenres(parentSelector, genreList) {
if (!genreList) {
return;
@@ -79,18 +85,31 @@
$('select', parentSelector).val(values[0]);
}
- function initialize() {
- loadGenres();
+ function initialize(app) {
+ // XXX; _instruments should be populated in a template, rather than round-trip to server
+ if(!context.JK.GenreSelectorDeferred) {
+ // this dance is to make sure there is only one server request instead of InstrumentSelector instances *
+ context.JK.GenreSelectorDeferred = rest.getGenres()
+ }
+
+ context.JK.GenreSelectorDeferred
+ .done(function(response) {genresLoaded(response)})
+ .fail(app.ajaxError)
+
+ return this;
}
var me = { // This will be our singleton.
initialize: initialize,
getSelectedGenres: getSelectedGenres,
setSelectedGenres: setSelectedGenres,
+ getNameForId : getNameForId,
getSelectedGenresValues: getSelectedGenresValues,
reset: reset,
- render: render,
- loadGenres: loadGenres
+ render: function() {
+ var _args = arguments;
+ context.JK.GenreSelectorDeferred.done(function(){render.apply(self, _args)})
+ }
};
return me;
diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js
index 2d331be18..3bc8556bc 100644
--- a/web/app/assets/javascripts/jam_rest.js
+++ b/web/app/assets/javascripts/jam_rest.js
@@ -948,6 +948,27 @@
});
}
+ function updateClaimedRecording(options) {
+ var claimedRecordingId = options["id"];
+ return $.ajax({
+ type: "PUT",
+ dataType: "json",
+ url: '/api/claimed_recordings/' + claimedRecordingId,
+ contentType: 'application/json',
+ processData: false,
+ data: JSON.stringify(options)
+ });
+ }
+
+ function deleteClaimedRecording(id) {
+ return $.ajax({
+ type: "DELETE",
+ dataType: "json",
+ contentType: 'application/json',
+ url: "/api/claimed_recordings/" + id
+ });
+ }
+
function claimRecording(options) {
var recordingId = options["id"];
@@ -1213,6 +1234,8 @@
this.getRecording = getRecording;
this.getClaimedRecordings = getClaimedRecordings;
this.getClaimedRecording = getClaimedRecording;
+ this.updateClaimedRecording = updateClaimedRecording;
+ this.deleteClaimedRecording = deleteClaimedRecording;
this.claimRecording = claimRecording;
this.startPlayClaimedRecording = startPlayClaimedRecording;
this.stopPlayClaimedRecording = stopPlayClaimedRecording;
diff --git a/web/app/assets/javascripts/layout.js b/web/app/assets/javascripts/layout.js
index ccbff33e1..43aaf0d7b 100644
--- a/web/app/assets/javascripts/layout.js
+++ b/web/app/assets/javascripts/layout.js
@@ -646,6 +646,7 @@
if (dialogEvent(dialog, 'beforeShow', options) === false) {
return null;
}
+ logger.debug("opening dialog: " + dialog)
var $overlay = $('.dialog-overlay')
if (opts.sizeOverlayToContent) {
diff --git a/web/app/assets/javascripts/profile.js b/web/app/assets/javascripts/profile.js
index b8e2a5810..bc6277302 100644
--- a/web/app/assets/javascripts/profile.js
+++ b/web/app/assets/javascripts/profile.js
@@ -13,6 +13,7 @@
var sentFriendRequest = false;
var profileScreen = null;
var textMessageDialog = null;
+ var feed = null;
var instrument_logo_map = context.JK.getInstrumentIconMap24();
@@ -30,6 +31,7 @@
function beforeShow(data) {
userId = data.id;
+ feed.setUser(userId);
}
function afterShow(data) {
@@ -37,6 +39,10 @@
resetForm();
}
+ function beforeHide(data) {
+ feed.setUser(null);
+ }
+
function resetForm() {
$('#profile-instruments').empty();
@@ -571,7 +577,7 @@
}
function bindHistory() {
-
+ feed.refresh();
}
/****************** BANDS TAB *****************/
@@ -752,15 +758,31 @@
function bindFavorites() {
}
+ function initializeFeed() {
+
+ var $scroller = profileScreen.find('.content-body-scroller');
+ var $content = profileScreen.find('.feed-content');
+ var $noMoreFeeds = $('#end-of-feeds-list');
+ var $refresh = profileScreen.find('.btn-refresh-entries');
+ var $sortFeedBy = profileScreen.find('#feed_order_by');
+ var $includeDate = profileScreen.find('#feed_date');
+ var $includeType = profileScreen.find('#feed_show');
+
+ feed = new context.JK.Feed(app);
+ feed.initialize(profileScreen, $scroller, $content, $noMoreFeeds, $refresh, $sortFeedBy, $includeDate, $includeType);
+ }
+
function initialize(textMessageDialogInstance) {
textMessageDialog = textMessageDialogInstance;
var screenBindings = {
'beforeShow': beforeShow,
- 'afterShow': afterShow
+ 'afterShow': afterShow,
+ 'beforeHide' : beforeHide
};
app.bindScreen('profile', screenBindings);
profileScreen = $('#user-profile');
events();
+ initializeFeed();
}
this.initialize = initialize;
diff --git a/web/app/assets/javascripts/recordingFinishedDialog.js b/web/app/assets/javascripts/recordingFinishedDialog.js
index 90061b63e..972badf3e 100644
--- a/web/app/assets/javascripts/recordingFinishedDialog.js
+++ b/web/app/assets/javascripts/recordingFinishedDialog.js
@@ -98,10 +98,10 @@
id: recording.id
})
.done(function () {
- console.error("recording discarded by user. recordingId=%o", recording.id);
+ logger.debug("recording discarded by user. recordingId=%o", recording.id);
})
.fail(function (jqXHR) {
- console.error("recording discard by user failed. recordingId=%o. reason: %o", recording.id, jqXHR.responseText);
+ logger.error("recording discard by user failed. recordingId=%o. reason: %o", recording.id, jqXHR.responseText);
})
.always(function () {
app.layout.closeDialog('recordingFinished')
diff --git a/web/app/assets/javascripts/rsvpCancelDialog.js b/web/app/assets/javascripts/rsvpCancelDialog.js
index a0a610cb5..2156cec2e 100644
--- a/web/app/assets/javascripts/rsvpCancelDialog.js
+++ b/web/app/assets/javascripts/rsvpCancelDialog.js
@@ -34,7 +34,7 @@
}
function showDialog() {
- app.layout.showDialog('rsvp-cancel-dialog');
+ return app.layout.showDialog('rsvp-cancel-dialog');
}
function events() {
diff --git a/web/app/assets/javascripts/rsvpSubmitDialog.js b/web/app/assets/javascripts/rsvpSubmitDialog.js
index 281b8120b..9f7ea2e59 100644
--- a/web/app/assets/javascripts/rsvpSubmitDialog.js
+++ b/web/app/assets/javascripts/rsvpSubmitDialog.js
@@ -55,7 +55,7 @@
}
function showDialog() {
- app.layout.showDialog(dialogId);
+ return app.layout.showDialog(dialogId);
}
function events() {
diff --git a/web/app/assets/javascripts/shareDialog.js b/web/app/assets/javascripts/shareDialog.js
index dc3bca311..0d3fbcfda 100644
--- a/web/app/assets/javascripts/shareDialog.js
+++ b/web/app/assets/javascripts/shareDialog.js
@@ -336,7 +336,7 @@
}
function showDialog() {
- app.layout.showDialog('share-dialog');
+ return app.layout.showDialog('share-dialog');
}
// function initDialog() {
diff --git a/web/app/assets/javascripts/ui_helper.js b/web/app/assets/javascripts/ui_helper.js
index 9bff51a7d..0ed51e8d0 100644
--- a/web/app/assets/javascripts/ui_helper.js
+++ b/web/app/assets/javascripts/ui_helper.js
@@ -26,25 +26,25 @@
function launchCommentDialog(options) {
var commentDialog = new JK.CommentDialog(JK.app, options);
commentDialog.initialize();
- commentDialog.showDialog();
+ return commentDialog.showDialog();
}
function launchShareDialog(entityId, entityType) {
var shareDialog = new JK.ShareDialog(JK.app, entityId, entityType);
shareDialog.initialize(JK.FacebookHelperInstance);
- shareDialog.showDialog();
+ return shareDialog.showDialog();
}
function launchRsvpSubmitDialog(sessionId) {
var rsvpDialog = new JK.RsvpSubmitDialog(JK.app, sessionId);
rsvpDialog.initialize();
- rsvpDialog.showDialog();
+ return rsvpDialog.showDialog();
}
function launchRsvpCancelDialog(sessionId, rsvpRequestId) {
var rsvpDialog = new JK.RsvpCancelDialog(JK.app, sessionId, rsvpRequestId);
rsvpDialog.initialize();
- rsvpDialog.showDialog();
+ return rsvpDialog.showDialog();
}
this.addSessionLike = addSessionLike;
diff --git a/web/app/assets/stylesheets/client/client.css b/web/app/assets/stylesheets/client/client.css
index 7dba0e458..0fe1feba3 100644
--- a/web/app/assets/stylesheets/client/client.css
+++ b/web/app/assets/stylesheets/client/client.css
@@ -54,6 +54,7 @@
*= require ./textMessageDialog
*= require ./acceptFriendRequestDialog
*= require ./launchAppDialog
+ *= require ./editRecordingDialog
*= require ./iconInstrumentSelect
*= require ./terms
*= require ./createSession
diff --git a/web/app/assets/stylesheets/client/editRecordingDialog.css.scss b/web/app/assets/stylesheets/client/editRecordingDialog.css.scss
new file mode 100644
index 000000000..5a38282a4
--- /dev/null
+++ b/web/app/assets/stylesheets/client/editRecordingDialog.css.scss
@@ -0,0 +1,79 @@
+@import "client/common";
+
+#edit-recording-dialog {
+
+ min-height:330px;
+
+ input, textarea {
+ @include border_box_sizing;
+ }
+
+ .field {
+ margin-top:20px;
+
+ &:nth-of-type(1) {
+ margin-top:0;
+ }
+ }
+
+ .buttons {
+ float:right;
+ clear:both;
+ margin-top:20px;
+ }
+
+ label[for="name"] {
+
+ }
+ input[name="name"] {
+ margin-top:5px;
+ width:100%;
+ }
+
+ label[for="description"] {
+
+ }
+ textarea[name="description"] {
+ margin-top:5px;
+ width:100%;
+ }
+
+ label[for="genre"] {
+ display:inline;
+ float:left;
+ line-height:26px;
+ vertical-align:middle;
+ }
+ select[name="genre"] {
+ float:left;
+ margin-left:5px;
+ }
+
+ .genre-selector {
+ float:left;
+ .dropdown-wrapper {
+ margin-left:5px;
+ }
+ }
+
+ label[for="is_public"] {
+ display: inline;
+ float:right;
+ line-height: 26px;
+ padding-right: 5px;
+ vertical-align: middle;
+ }
+ select[name="is_public"] {
+ float:right;
+ }
+ div[purpose="is_public"] {
+ float:right;
+ .icheckbox_minimal {
+ float:right;
+ margin-top:4px;
+ }
+ }
+
+
+
+}
\ No newline at end of file
diff --git a/web/app/assets/stylesheets/client/feed.css.scss b/web/app/assets/stylesheets/client/feed.css.scss
index 4dbd60d4f..a69727e00 100644
--- a/web/app/assets/stylesheets/client/feed.css.scss
+++ b/web/app/assets/stylesheets/client/feed.css.scss
@@ -2,4 +2,9 @@
.recording-current {
position:absolute; // solves a problem with duration wrapping--only in firefox
}
+
+ .btn-refresh-holder {
+ float:right;
+ margin-right:10px;
+ }
}
\ No newline at end of file
diff --git a/web/app/assets/stylesheets/client/jamkazam.css.scss b/web/app/assets/stylesheets/client/jamkazam.css.scss
index e4256229f..16d357c82 100644
--- a/web/app/assets/stylesheets/client/jamkazam.css.scss
+++ b/web/app/assets/stylesheets/client/jamkazam.css.scss
@@ -328,6 +328,7 @@ input[type="text"], input[type="password"]{
textarea {
font-size:15px;
+ padding:3px;
}
diff --git a/web/app/assets/stylesheets/client/profile.css.scss b/web/app/assets/stylesheets/client/profile.css.scss
index 746f783e3..ff45db852 100644
--- a/web/app/assets/stylesheets/client/profile.css.scss
+++ b/web/app/assets/stylesheets/client/profile.css.scss
@@ -317,4 +317,60 @@
#btn-add-friend {
display:none;
+}
+
+#profile-history {
+ padding:0 10px 0 20px;
+ width:100%;
+ position:relative;
+ height:100%;
+ @include border_box_sizing;
+
+ #user-feed-controls {
+ width:100%;
+ @include border_box_sizing;
+ position:relative;
+ display:none;
+ }
+
+ .btn-refresh-holder {
+ left: 95%; // 5 * 19% to right-align 5 user blocks (in conjunction with the margin-left
+ margin-left: -65px;
+ position: absolute;
+ }
+
+ .filter-body {
+ bottom: 0;
+ right: 0;
+ top: 0;
+ left: 20px;
+ position: absolute;
+ width: 95%;
+ padding-top:0;
+ margin-top:10px;
+ height:auto;
+ }
+
+ .profile-wrapper {
+ padding: 10px 0;
+ }
+
+ .feed-entry .feed-details {
+ margin-right:5px;
+ }
+
+ .recording-current {
+ position:absolute; // solves a problem with duration wrapping--only in firefox
+ }
+
+ .content-body-scroller {
+ height: 100%;
+ width: 100%;
+ position: absolute;
+ }
+
+
+ .no-feed-msg {
+ text-align:center
+ }
}
\ No newline at end of file
diff --git a/web/app/assets/stylesheets/web/audioWidgets.css.scss b/web/app/assets/stylesheets/web/audioWidgets.css.scss
index da7a933f1..1fcd93f85 100644
--- a/web/app/assets/stylesheets/web/audioWidgets.css.scss
+++ b/web/app/assets/stylesheets/web/audioWidgets.css.scss
@@ -272,6 +272,20 @@
}
}
+ a.edit-recording-dialog {
+ font-size:12px;
+ display:none;
+ }
+
+ .is_private {
+ display:none;
+ font-size:12px;
+ font-style:italic;
+ &.enabled {
+ display:inline;
+ }
+ }
+
.play-count {
margin-right:10px;
}
diff --git a/web/app/controllers/api_claimed_recordings_controller.rb b/web/app/controllers/api_claimed_recordings_controller.rb
index 61eaa9de2..04cef8d3f 100644
--- a/web/app/controllers/api_claimed_recordings_controller.rb
+++ b/web/app/controllers/api_claimed_recordings_controller.rb
@@ -19,30 +19,20 @@ class ApiClaimedRecordingsController < ApiController
end
def update
-
if @claimed_recording.user_id != current_user.id
raise PermissionError, 'only owner of claimed_recording can update it'
end
- begin
- @claimed_recording.update_fields(current_user, params)
- respond_with responder: ApiResponder, :status => 204
- rescue
- render :json => { :message => "claimed_recording could not be updated" }, :status => 403
- end
+ @claimed_recording.update_fields(current_user, params)
+ respond_with @claimed_recording
end
def delete
if @claimed_recording.user_id != current_user.id
raise PermissionError, 'only owner of claimed_recording can update it'
end
- #begin
- #@claimed_recording.discard(current_user)
- #render :json => {}, :status => 204
-# respond_with responder: ApiResponder, :status => 204
- #rescue
- #render :json => { :message => "claimed_recording could not be deleted" }, :status => 403
- #end
+ @claimed_recording.discard(current_user)
+ render :json => {}, :status => 200
end
def download
diff --git a/web/app/controllers/api_recordings_controller.rb b/web/app/controllers/api_recordings_controller.rb
index 478a3aeee..75e3ea6f4 100644
--- a/web/app/controllers/api_recordings_controller.rb
+++ b/web/app/controllers/api_recordings_controller.rb
@@ -205,6 +205,7 @@ class ApiRecordingsController < ApiController
end
end
+
private
def parse_filename
@recorded_track = RecordedTrack.find_by_recording_id_and_client_track_id!(params[:id], params[:track_id])
diff --git a/web/app/views/api_feeds/show.rabl b/web/app/views/api_feeds/show.rabl
index 03a8ad2c5..140828833 100644
--- a/web/app/views/api_feeds/show.rabl
+++ b/web/app/views/api_feeds/show.rabl
@@ -140,7 +140,7 @@ glue :recording do
child(:claimed_recordings => :claimed_recordings) {
- attributes :id, :name, :description, :is_public, :genre_id, :has_mix?
+ attributes :id, :name, :description, :is_public, :genre_id, :has_mix?, :user_id
child(:user => :creator) {
attributes :id, :first_name, :last_name, :photo_url
diff --git a/web/app/views/api_music_sessions/show_history.rabl b/web/app/views/api_music_sessions/show_history.rabl
index 1472378cb..216428716 100644
--- a/web/app/views/api_music_sessions/show_history.rabl
+++ b/web/app/views/api_music_sessions/show_history.rabl
@@ -17,7 +17,7 @@ if !current_user
else
attributes :id, :music_session_id, :name, :description, :musician_access, :approval_required, :fan_access, :fan_chat,
- :band_id, :user_id, :genre_id, :created_at, :like_count, :comment_count, :scheduled_start, :scheduled_duration,
+ :band_id, :user_id, :genre_id, :created_at, :like_count, :comment_count, :play_count, :scheduled_start, :scheduled_duration,
:language, :recurring_mode, :language_description, :scheduled_start_time, :access_description, :timezone, :timezone_description,
:musician_access_description, :fan_access_description, :session_removed_at, :legal_policy, :open_rsvps
diff --git a/web/app/views/api_recordings/show.rabl b/web/app/views/api_recordings/show.rabl
index 633816ba0..8b75f0384 100644
--- a/web/app/views/api_recordings/show.rabl
+++ b/web/app/views/api_recordings/show.rabl
@@ -28,7 +28,7 @@ child(:comments => :comments) {
child(:claimed_recordings => :claimed_recordings) {
- attributes :id, :name, :description, :is_public, :genre_id
+ attributes :id, :name, :description, :is_public, :genre_id, :has_mix?, :user_id
node :share_url do |claimed_recording|
unless claimed_recording.share_token.nil?
diff --git a/web/app/views/clients/_banner.html.erb b/web/app/views/clients/_banner.html.erb
deleted file mode 100644
index 1bcde7cbd..000000000
--- a/web/app/views/clients/_banner.html.erb
+++ /dev/null
@@ -1,43 +0,0 @@
-
-
-
-
-
-
-
- <%= image_tag("content/icon_alert.png", :height => '24', :width => '24', :class => "content-icon") %>
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/web/app/views/clients/_banner.html.haml b/web/app/views/clients/_banner.html.haml
new file mode 100644
index 000000000..9e53fb0e1
--- /dev/null
+++ b/web/app/views/clients/_banner.html.haml
@@ -0,0 +1,33 @@
+#banner_overlay.overlay
+#banner.dialog-overlay-sm{ 'data-type' => '' }
+ .content-head
+ = image_tag("content/icon_alert.png", :height => '24', :width => '24', :class => "content-icon")
+ %h1
+
+ .dialog-inner
+
+ %br.end-content{ clear: 'all'}
+
+ .right.buttons
+ %a.button-orange.close-btn CLOSE
+ %a.button-orange.yes-btn YES
+ %a.button-grey.no-btn CANCEL
+
+%script{type: 'text/template', id: 'template-app-in-read-only-volume'}
+ .template-app-in-read-only-volume
+ %p The JamKazam application is running in a read-only volume. This stops the automatic update feature from working, and may cause other issues because it is not a supported configuration.
+ %p So let's fix it. Don't worry--it's easy to do--please read on.
+ %p First, here's almost certainly what happened to cause this problem: after JamKazam.dmg was downloaded, it was then double-clicked and a window opened showing the contents of the dmg. The JamKazam application icon was double-clicked inside that opened window. Unfortunately, that isn't OK.
+ %p Instead, do this to move JamKazam to a good location, and run it from there:
+ %ol
+ %li.download-dmg
+ Download the latest mac installer from the
+ %a{href:"/downloads"}Downloads
+ page.
+ %br
+ %em (the download will have a filename ending in .dmg)
+ %li Double-click the downloaded dmg file to open it.
+ %li In the resulting screen, drag the JamKazam icon to the Applications folder. It will show a progress bar as it copies.
+ %li Double-click the Applications folder to go into it.
+ %li If you are still running the JamKazam application, you will need to stop it before executing the last step.
+ %li Find the JamKazam application in the Applications folder, and double-click the icon to launch it!
\ No newline at end of file
diff --git a/web/app/views/clients/_edit_recording_dialog.html.haml b/web/app/views/clients/_edit_recording_dialog.html.haml
new file mode 100644
index 000000000..b1b944c4d
--- /dev/null
+++ b/web/app/views/clients/_edit_recording_dialog.html.haml
@@ -0,0 +1,24 @@
+.dialog.configure-tracks{ layout: 'dialog', 'layout-id' => 'edit-recording', id: 'edit-recording-dialog'}
+ .content-head
+ = image_tag "content/icon_add.png", {:width => 19, :height => 19, :class => 'content-icon' }
+ %h1 Edit Recording
+ .dialog-inner
+ %form
+ .field
+ %label{for: 'name'} Recording name:
+ %input{type: 'text', name: 'name'}
+ .field
+ %label{for: 'description'} Description:
+ %textarea{name: 'description', rows: '4'}
+ .field.genre-selector
+ %label{for: 'genre'} Genre:
+ %select{name:'genre'}
+ .field{purpose: 'is_public'}
+ %input{type: 'checkbox', name: 'is_public'}
+ %label{for: 'is_public'} Public Recording
+
+ .buttons
+ %a.button-grey.cancel-btn CANCEL
+ %a.button-orange.delete-btn DELETE
+ %a.button-orange.save-btn UPDATE
+ %br{clear: 'all'}
\ No newline at end of file
diff --git a/web/app/views/clients/_profile.html.erb b/web/app/views/clients/_profile.html.erb
index 21f06b84d..b4ad60577 100644
--- a/web/app/views/clients/_profile.html.erb
+++ b/web/app/views/clients/_profile.html.erb
@@ -88,9 +88,19 @@
+ <%= form_tag('', {:id => 'user-feed-form', :class => 'inner-content'}) do %>
+ <%= render(:partial => "web_filter", :locals => {:search_type => Search::PARAM_FEED, :id => 'user-feed-controls'}) %>
+
-
+
+
+
+
No more feed entries
+
+
+
+ <% end %>
diff --git a/web/app/views/clients/_web_filter.html.erb b/web/app/views/clients/_web_filter.html.erb
index 5beed3237..6baa9cfc7 100644
--- a/web/app/views/clients/_web_filter.html.erb
+++ b/web/app/views/clients/_web_filter.html.erb
@@ -72,8 +72,8 @@
<% end -%>
<% if :feed == filter_label %>
-
-
REFRESH
+
<% end %>
<% end -%>
diff --git a/web/app/views/clients/index.html.erb b/web/app/views/clients/index.html.erb
index 602b82afb..2ab03c5c5 100644
--- a/web/app/views/clients/index.html.erb
+++ b/web/app/views/clients/index.html.erb
@@ -50,6 +50,7 @@
<%= render "account_audio_profile" %>
<%= render "account_sessions" %>
<%= render "configure_tracks_dialog" %>
+<%= render "edit_recording_dialog" %>
<%= render "invitationDialog" %>
<%= render "inviteMusicians" %>
<%= render "hoverBand" %>
@@ -129,6 +130,9 @@
if (this.didInitAfterConnect) return;
this.didInitAfterConnect = true
+ // This is a helper class with a singleton. No need to instantiate.
+ JK.GenreSelectorHelper.initialize(JK.app);
+
var recordingManager = new JK.RecordingManager();
var facebookHelper = new JK.FacebookHelper(JK.app);
@@ -156,6 +160,8 @@
var launchAppDialog = new JK.LaunchAppDialog(JK.app);
launchAppDialog.initialize();
+ var editRecordingDialog = new JK.EditRecordingDialog(JK.app);
+ editRecordingDialog.initialize();
var userDropdown = new JK.UserDropdown(JK.app);
JK.UserDropdown = userDropdown;
@@ -202,9 +208,6 @@
var searchResultScreen = new JK.SearchResultScreen(JK.app);
searchResultScreen.initialize();
- // This is a helper class with a singleton. No need to instantiate.
- JK.GenreSelectorHelper.initialize();
-
var inviteMusiciansUtil1 = new JK.InviteMusiciansUtil(JK.app);
inviteMusiciansUtil1.initialize(friendSelectorDialog);
// var createSessionScreen = new JK.CreateSessionScreen(JK.app);
diff --git a/web/app/views/users/_feed_recording_ajax.html.haml b/web/app/views/users/_feed_recording_ajax.html.haml
index 25198edeb..6e3eb140d 100644
--- a/web/app/views/users/_feed_recording_ajax.html.haml
+++ b/web/app/views/users/_feed_recording_ajax.html.haml
@@ -1,5 +1,5 @@
%script{type: 'text/template', id: 'template-feed-recording'}
- .feed-entry.recording-entry{:'data-claimed-recording-id' => '{{data.candidate_claimed_recording.id}}' }
+ .feed-entry.recording-entry{:'data-claimed-recording-id' => '{{data.candidate_claimed_recording.id}}', :'data-recording-id' => '{{data.feed_item.id}}' }
/ avatar
.avatar-small.ib
%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}}"}
@@ -8,6 +8,7 @@
.left.ml20.w15
.title{hoveraction: 'recording', :'recording-id' => '{{data.candidate_claimed_recording.id}}' }
%a{:href => "/recordings/{{data.candidate_claimed_recording.id}}", :rel => "external"} RECORDING
+ %a.edit-recording-dialog{href: "#"} (edit)
.artist
%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}}'
@@ -16,7 +17,8 @@
/ name and description
.left.ml20.w30
.name.dotdotdot
- = '{{data.feed_item.helpers.name}}'
+ %span.name-text {{data.feed_item.helpers.name}}
+ %span.is_private (private)
.description.dotdotdot
= '{{data.feed_item.helpers.description}}'
/ timeline and controls
@@ -54,7 +56,7 @@
0:00
/ end recording play controls
/ genre and social
- .left.small= '{{data.feed_item.helpers.genre}}'
+ .left.small.genre= '{{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"
diff --git a/web/spec/features/feed_spec.rb b/web/spec/features/feed_spec.rb
index a177c63b5..4e3bff388 100644
--- a/web/spec/features/feed_spec.rb
+++ b/web/spec/features/feed_spec.rb
@@ -56,7 +56,7 @@ describe "Feed", :js => true, :type => :feature, :capybara_feature => true do
find('div.comment-text', text: comment)
find('#dialog-close-button', '[layout-id="comment-dialog"]').trigger(:click)
- find('#btn-refresh-feed').trigger(:click)
+ find('[layout-id="feed"] .btn-refresh-entries').trigger(:click)
find('span.comments').should have_content('1')
# Likes
@@ -142,7 +142,7 @@ describe "Feed", :js => true, :type => :feature, :capybara_feature => true do
find('div.comment-text', text: comment)
find('#dialog-close-button', '[layout-id="comment-dialog"]').trigger(:click)
- find('#btn-refresh-feed').trigger(:click)
+ find('[layout-id="feed"] .btn-refresh-entries').trigger(:click)
find('span.comments').should have_content('1')
# Likes
@@ -173,12 +173,6 @@ describe "Feed", :js => true, :type => :feature, :capybara_feature => true do
find("#user-profile h2[id=profile-username]", text: user.name)
end
- # it "should render play widget" do
-
- # it " and allow recording playback" do
- # end
- # end
-
end
end
\ No newline at end of file
diff --git a/web/spec/features/profile_history_spec.rb b/web/spec/features/profile_history_spec.rb
new file mode 100644
index 000000000..d9662d2c9
--- /dev/null
+++ b/web/spec/features/profile_history_spec.rb
@@ -0,0 +1,196 @@
+require 'spec_helper'
+
+describe "Profile History", :js => true, :type => :feature, :capybara_feature => true do
+
+ subject { page }
+
+ let(:user) { FactoryGirl.create(:user) }
+
+ before do
+ MusicSession.delete_all
+ Recording.delete_all
+ sign_in_poltergeist user
+ stub_const("APP_CONFIG", web_config)
+ end
+
+ it "loads empty history" do
+ nav_profile_history(user)
+
+ find('.no-feed-msg', text: 'This user has no history.')
+ end
+
+
+ # the same feedHelper instance is used to show any user's feed. so we need to make sure bouncing back and forth
+ # between feeds is safe
+ it "between users" do
+ create_session(creator: user)
+ formal_leave_by(user)
+ user2 = FactoryGirl.create(:user)
+
+ nav_profile_history(user)
+ find('.feed-entry.music-session-history-entry')
+ visit Nav.feed # to get around bug where you can't cause a screen to update if it's the same screen, different params
+ nav_profile_history(user2)
+ should_not have_selector('.feed-entry.music-session-history-entry')
+ find('.no-feed-msg', text: 'This user has no history.')
+ end
+
+ describe "sessions" do
+
+ before(:each) do
+ create_session(creator: user)
+ formal_leave_by(user)
+ end
+
+ it "should render stats" do
+ nav_profile_history(user)
+
+ # 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('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
+ nav_profile_history(user)
+ find('.feed-details a.details').trigger(:click)
+
+ # confirm user avatar exists
+ find("a.avatar-tiny[user-id=\"#{user.id}\"][hoveraction=\"musician\"] img")
+
+ # confirm user name exists
+ find("a.musician-name[user-id=\"#{user.id}\"][hoveraction=\"musician\"]", text: user.name)
+
+ # confirm instrument icons exist
+ find("img[instrument-id=\"electric guitar\"]")
+
+ # confirm hover bubbles show
+ find("a.avatar-tiny[user-id=\"#{user.id}\"][hoveraction=\"musician\"]").hover_intent
+
+ # 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)
+ end
+
+ end
+
+ describe "recordings" do
+
+ before(:each) do
+ start_recording_with(user)
+ stop_recording
+ claim_recording("my recording", "my recording description")
+ formal_leave_by(user)
+ MusicSession.delete_all
+ 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 artist name" do
+ # it " and link to profile" do
+ # end
+
+ # it " and render artist hover bubble"
+ # end
+
+ it "should render stats" do
+ nav_profile_history(user)
+
+ # 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('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
+ nav_profile_history(user)
+
+ find('.feed-details a.details').trigger(:click)
+
+ # confirm user avatar exists
+ find("a.avatar-tiny[user-id=\"#{user.id}\"][hoveraction=\"musician\"] img")
+
+ # confirm user name exists
+ find("a.musician-name[user-id=\"#{user.id}\"][hoveraction=\"musician\"]", text: user.name)
+
+ # confirm instrument icons exist
+ find("img[instrument-id=\"electric guitar\"]")
+
+
+ # confirm hover bubbles show
+ find("a.avatar-tiny[user-id=\"#{user.id}\"][hoveraction=\"musician\"]").hover_intent
+
+ # 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)
+ end
+
+ it "should allow edit and delete" do
+ nav_profile_history(user)
+
+ # edit it's name
+ find('.edit-recording-dialog').trigger(:click)
+ find('h1', text: 'Edit Recording')
+ fill_in 'name', with: 'some crazy name'
+ find('.save-btn').trigger(:click)
+ should_not have_selector('h1', text: 'Edit Recording')
+ find('.name-text', text:'some crazy name')
+
+ # now delete it
+ find('.edit-recording-dialog').trigger(:click)
+ find('h1', text: 'Edit Recording')
+ find('.delete-btn').trigger(:click)
+ # confirm...
+ find('.yes-btn').trigger(:click)
+ should_not have_selector('h1', text: 'Edit Recording')
+ should_not have_selector('.feed-entry.recording-entry')
+
+ ClaimedRecording.first.discarded.should == true
+ end
+
+ end
+end
diff --git a/web/spec/support/app_config.rb b/web/spec/support/app_config.rb
index 5f124e9a9..fcbc05edf 100644
--- a/web/spec/support/app_config.rb
+++ b/web/spec/support/app_config.rb
@@ -17,6 +17,35 @@ def web_config
def external_root_url
"#{external_protocol}#{external_hostname}#{(external_port == 80 || external_port == 443) ? '' : ':' + external_port.to_s}"
end
+
+
+ def aws_bucket
+ JAMKAZAM_TESTING_BUCKET
+ end
+
+ def aws_access_key_id
+ 'AKIAJESQY24TOT542UHQ'
+ end
+
+ def aws_secret_access_key
+ 'h0V0ffr3JOp/UtgaGrRfAk25KHNiO9gm8Pj9m6v3'
+ end
+
+ def aws_region
+ 'us-east-1'
+ end
+
+ def aws_bucket_public
+ 'jamkazam-testing-public'
+ end
+
+ def aws_cache
+ '315576000'
+ end
+
+ def max_audio_downloads
+ 10
+ end
end
klass.new
end
diff --git a/web/spec/support/utilities.rb b/web/spec/support/utilities.rb
index 081cca030..7bc112ad0 100644
--- a/web/spec/support/utilities.rb
+++ b/web/spec/support/utilities.rb
@@ -581,3 +581,8 @@ def garbage length
length.times { output << special_characters.sample }
output.slice(0, length)
end
+
+def nav_profile_history(user)
+ visit Nav.profile(user)
+ find('#profile-history-link').trigger(:click)
+end
\ No newline at end of file