Merge branch 'develop' into hotfix/feed_icon_fix
This commit is contained in:
commit
b17dafd94f
|
|
@ -65,10 +65,11 @@ gem 'logging-rails', :require => 'logging/rails'
|
|||
gem 'pg_migrate'
|
||||
gem 'ruby-protocol-buffers', '1.2.2'
|
||||
|
||||
gem 'sendgrid', '1.1.0'
|
||||
gem 'sendgrid', '1.2.0'
|
||||
|
||||
gem 'geokit-rails'
|
||||
gem 'postgres_ext', '1.0.0'
|
||||
gem 'resque_mailer'
|
||||
|
||||
group :libv8 do
|
||||
gem 'libv8', "~> 3.11.8"
|
||||
|
|
@ -106,6 +107,7 @@ group :development, :test do
|
|||
gem 'factory_girl_rails', '4.1.0'
|
||||
gem 'database_cleaner', '0.7.0'
|
||||
gem 'launchy'
|
||||
gem 'faker'
|
||||
end
|
||||
|
||||
group :test do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,106 @@
|
|||
ActiveAdmin.register JamRuby::EmailBatch, :as => 'Batch Emails' do
|
||||
|
||||
menu :label => 'Emails'
|
||||
|
||||
config.sort_order = 'updated_at DESC'
|
||||
config.batch_actions = false
|
||||
# config.clear_action_items!
|
||||
config.filters = false
|
||||
|
||||
form :partial => 'form'
|
||||
|
||||
index do
|
||||
column 'Subject' do |pp| pp.subject end
|
||||
column 'Updated' do |pp| pp.updated_at end
|
||||
column 'From' do |pp| pp.from_email end
|
||||
column 'Status' do |pp| pp.aasm_state end
|
||||
column 'Test Emails' do |pp| pp.test_emails end
|
||||
column 'Email Count' do |pp| pp.qualified_count end
|
||||
column 'Send Count' do |pp| pp.sent_count end
|
||||
column 'Started' do |pp| pp.started_at end
|
||||
column 'Completed' do |pp| pp.completed_at end
|
||||
column 'Send Test' do |pp|
|
||||
link_to("Test Batch (#{pp.test_count})",
|
||||
batch_test_admin_batch_email_path(pp.id),
|
||||
:confirm => "Run test batch with #{pp.test_count} emails?")
|
||||
end
|
||||
column 'Send Live' do |pp|
|
||||
link_to("Live Batch (#{User.email_opt_in.count})",
|
||||
batch_send_admin_batch_email_path(pp.id),
|
||||
:confirm => "Run LIVE batch with #{User.email_opt_in.count} emails?")
|
||||
end
|
||||
|
||||
default_actions
|
||||
end
|
||||
|
||||
action_item :only => :show do
|
||||
link_to("Send Test Batch (#{resource.test_count})",
|
||||
batch_test_admin_batch_email_path(resource.id),
|
||||
:confirm => "Run test batch with #{resource.test_count} emails?")
|
||||
end
|
||||
|
||||
action_item :only => :show do
|
||||
link_to("Send Live Batch (#{User.email_opt_in.count})",
|
||||
batch_send_admin_batch_email_path(resource.id),
|
||||
:confirm => "Run LIVE batch with #{User.email_opt_in.count} emails?")
|
||||
end
|
||||
|
||||
show :title => 'Batch Email' do |obj|
|
||||
panel 'Email Contents' do
|
||||
attributes_table_for obj do
|
||||
row 'From' do |obj| obj.from_email end
|
||||
row 'Test Emails' do |obj| obj.test_emails end
|
||||
row 'Subject' do |obj| obj.subject end
|
||||
row 'Body' do |obj| obj.body end
|
||||
end
|
||||
end
|
||||
columns do
|
||||
column do
|
||||
panel 'Sending Parameters' do
|
||||
attributes_table_for obj do
|
||||
row 'State' do |obj| obj.aasm_state end
|
||||
row 'User Count' do |obj|
|
||||
obj.qualified_count ? obj.qualified_count : User.email_opt_in.count
|
||||
end
|
||||
row 'Sent Count' do |obj| obj.sent_count end
|
||||
row 'Started' do |obj| obj.started_at end
|
||||
row 'Completed' do |obj| obj.completed_at end
|
||||
row 'Updated' do |obj| obj.updated_at end
|
||||
end
|
||||
end
|
||||
end
|
||||
column do
|
||||
panel 'Send Results' do
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
controller do
|
||||
|
||||
def create
|
||||
batch = EmailBatch.create_with_params(params[:jam_ruby_email_batch])
|
||||
redirect_to admin_batch_email_path(batch.id)
|
||||
# redirect_to admin_batch_emails_path
|
||||
end
|
||||
|
||||
def update
|
||||
resource.update_with_conflict_validation(params[:jam_ruby_email_batch])
|
||||
redirect_to admin_batch_email_path(resource.id)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
member_action :batch_test, :method => :get do
|
||||
batch = EmailBatch.find(params[:id])
|
||||
batch.send_test_batch
|
||||
redirect_to admin_batch_email_path(batch.id)
|
||||
end
|
||||
|
||||
member_action :batch_send, :method => :get do
|
||||
batch = EmailBatch.find(params[:id])
|
||||
batch.deliver_batch
|
||||
redirect_to admin_batch_email_path(batch.id)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<%= semantic_form_for([:admin, resource], :url => resource.new_record? ? admin_batch_emails_path : "/admin/batch_emails/#{resource.id}") do |f| %>
|
||||
<%= f.inputs do %>
|
||||
<%= f.input(:from_email, :label => "From Email", :input_html => {:maxlength => 64}) %>
|
||||
<%= f.input(:subject, :label => "Subject", :input_html => {:maxlength => 128}) %>
|
||||
<%= f.input(:test_emails, :label => "Test Emails", :input_html => {:maxlength => 1024, :size => '3x3'}) %>
|
||||
<%= f.input(:body, :label => "Body", :input_html => {:maxlength => 3096, :size => '10x20'}) %>
|
||||
<% end %>
|
||||
<%= f.actions %>
|
||||
<% end %>
|
||||
|
|
@ -119,5 +119,7 @@ module JamAdmin
|
|||
config.twitter_app_secret = ENV['TWITTER_APP_SECRET'] || 'Azcy3QqfzYzn2fsojFPYXcn72yfwa0vG6wWDrZ3KT8'
|
||||
|
||||
config.ffmpeg_path = ENV['FFMPEG_PATH'] || (File.exist?('/usr/local/bin/ffmpeg') ? '/usr/local/bin/ffmpeg' : '/usr/bin/ffmpeg')
|
||||
|
||||
config.max_audio_downloads = 100
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -137,4 +137,6 @@ cascading_delete_constraints_for_release.sql
|
|||
events_social_description.sql
|
||||
fix_broken_cities.sql
|
||||
notifications_with_text.sql
|
||||
|
||||
notification_seen_at.sql
|
||||
order_event_session.sql
|
||||
emails.sql
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
CREATE TABLE email_batches (
|
||||
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
subject VARCHAR(256) NOT NULL,
|
||||
body TEXT NOT NULL,
|
||||
from_email VARCHAR(64) NOT NULL default 'support@jamkazam.com',
|
||||
|
||||
aasm_state VARCHAR(32) NOT NULL default 'pending',
|
||||
|
||||
test_emails TEXT NOT NULL default '',
|
||||
|
||||
qualified_count INTEGER NOT NULL default 0,
|
||||
sent_count INTEGER NOT NULL default 0,
|
||||
|
||||
lock_version INTEGER,
|
||||
|
||||
started_at TIMESTAMP,
|
||||
completed_at TIMESTAMP,
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- CREATE TABLE email_batch_results (
|
||||
-- id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
-- email_batch_id VARCHAR(64) REFERENCES email_batches(id) ON DELETE CASCADE,
|
||||
-- user_id VARCHAR(64) REFERENCES users(id) ON DELETE CASCADE,
|
||||
|
||||
-- error_type VARCHAR(32),
|
||||
-- email_address VARCHAR(256),
|
||||
|
||||
-- created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
-- updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
-- );
|
||||
|
||||
-- ALTER TABLE email_batch_results ADD CONSTRAINT email_batch_uniqkey UNIQUE (email_batch_id);
|
||||
-- ALTER TABLE email_batch_results ADD CONSTRAINT email_user_uniqkey UNIQUE (user_id);
|
||||
|
||||
ALTER TABLE users ALTER COLUMN subscribe_email SET DEFAULT true;
|
||||
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE users ADD COLUMN notification_seen_at TIMESTAMP;
|
||||
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE event_sessions ADD COLUMN ordinal INTEGER;
|
||||
|
|
@ -472,7 +472,8 @@ message TestClientMessage {
|
|||
// sent from client to server periodically to let server track if the client is truly alive and avoid TCP timeout scenarios
|
||||
// the server will send a HeartbeatAck in response to this
|
||||
message Heartbeat {
|
||||
|
||||
optional string notification_seen = 1;
|
||||
optional string notification_seen_at = 2;
|
||||
}
|
||||
|
||||
// target: client
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ gem 'eventmachine', '1.0.3'
|
|||
gem 'amqp', '1.0.2'
|
||||
gem 'will_paginate'
|
||||
gem 'actionmailer', '3.2.13'
|
||||
gem 'sendgrid'
|
||||
gem 'sendgrid', '1.2.0'
|
||||
gem 'aws-sdk', '1.29.1'
|
||||
gem 'carrierwave'
|
||||
gem 'aasm', '3.0.16'
|
||||
|
|
@ -40,6 +40,7 @@ gem 'resque'
|
|||
gem 'resque-retry'
|
||||
gem 'resque-failed-job-mailer' #, :path => "/Users/seth/workspace/resque_failed_job_mailer"
|
||||
gem 'resque-lonely_job', '~> 1.0.0'
|
||||
gem 'resque_mailer'
|
||||
gem 'oj'
|
||||
gem 'builder'
|
||||
gem 'fog'
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ require "geokit-rails"
|
|||
require "postgres_ext"
|
||||
require 'builder'
|
||||
require 'cgi'
|
||||
require 'resque_mailer'
|
||||
|
||||
require "jam_ruby/constants/limits"
|
||||
require "jam_ruby/constants/notification_types"
|
||||
|
|
@ -132,6 +133,9 @@ require "jam_ruby/models/playable_play"
|
|||
require "jam_ruby/models/country"
|
||||
require "jam_ruby/models/region"
|
||||
require "jam_ruby/models/city"
|
||||
require "jam_ruby/models/email_batch"
|
||||
require "jam_ruby/app/mailers/async_mailer"
|
||||
require "jam_ruby/app/mailers/batch_mailer"
|
||||
|
||||
include Jampb
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
require 'resque_mailer'
|
||||
|
||||
module JamRuby
|
||||
class AsyncMailer < ActionMailer::Base
|
||||
include SendGrid
|
||||
include Resque::Mailer
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
module JamRuby
|
||||
class BatchMailer < JamRuby::AsyncMailer
|
||||
layout "user_mailer"
|
||||
|
||||
sendgrid_category :use_subject_lines
|
||||
sendgrid_unique_args :env => Environment.mode
|
||||
|
||||
def _send_batch(batch, users)
|
||||
@batch_body = batch.body
|
||||
emails = users.map(&:email)
|
||||
|
||||
sendgrid_recipients(emails)
|
||||
sendgrid_substitute(EmailBatch::VAR_FIRST_NAME, users.map(&:first_name))
|
||||
|
||||
batch.did_send(emails)
|
||||
|
||||
mail(:to => emails,
|
||||
:from => batch.from_email,
|
||||
:subject => batch.subject) do |format|
|
||||
format.text
|
||||
format.html
|
||||
end
|
||||
end
|
||||
|
||||
def send_batch_email(batch_id, user_ids)
|
||||
users = User.find_all_by_id(user_ids)
|
||||
batch = EmailBatch.where(:id => batch_id).limit(1).first
|
||||
self._send_batch(batch, users)
|
||||
end
|
||||
|
||||
def send_batch_email_test(batch_id)
|
||||
batch = EmailBatch.where(:id => batch_id).limit(1).first
|
||||
users = batch.test_users
|
||||
self._send_batch(batch, users)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
<%= @body %>
|
||||
|
|
@ -0,0 +1 @@
|
|||
<%= @body %>
|
||||
|
|
@ -0,0 +1 @@
|
|||
send_batch_email.html.erb
|
||||
|
|
@ -0,0 +1 @@
|
|||
send_batch_email.text.erb
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
<% provide(:title, 'Jamkazam Password Changed') %>
|
||||
|
||||
You just changed your password at Jamkazam.
|
||||
You just changed your password at JamKazam.
|
||||
|
|
|
|||
|
|
@ -24,13 +24,11 @@
|
|||
|
||||
<tr>
|
||||
<td align="left"><h1 style="font-size:22px;font-weight:normal;margin-top:0px"><font color="#F34E1C" face="Arial, Helvetica, sans-serif"><%= yield(:title) %></font></h1>
|
||||
<p><font size="3" color="#AAAAAA" face="Arial, Helvetica, sans-serif"><%= yield %></font></p>
|
||||
<p><font size="3" color="#AAAAAA" face="Arial, Helvetica, sans-serif"><%= @batch_body ? @batch_body.html_safe : yield %></font></p>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
|
||||
<% unless @suppress_user_has_account_footer == true %>
|
||||
<tr>
|
||||
<td>
|
||||
|
|
@ -39,8 +37,8 @@
|
|||
<td align="left">
|
||||
|
||||
<!-- CALL OUT BOX -->
|
||||
<p style="margin-top:0px"><font size="2" color="#7FACBA" face="Arial, Helvetica, sans-serif">This email was sent to you because you have an account at <a style="color: #588C98;" href="http://www.jamkazam.com">JamKazam</a>. Click <a style="color: #588C98;" href="http://www.jamkazam.com/client#/account/profile">here to unsubscribe</a> and update your profile settings.
|
||||
</font></p>
|
||||
<p style="margin-top:0px"><font size="2" color="#7FACBA" face="Arial, Helvetica, sans-serif">This email was sent to you because you have an account at <a style="color: #588C98;" href="http://www.jamkazam.com">JamKazam</a>.
|
||||
</td></tr></table>
|
||||
|
||||
</td>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
<%= yield %>
|
||||
|
||||
<% if @batch_body %>
|
||||
<%= Nokogiri::HTML(@batch_body).text %>
|
||||
<% else %>
|
||||
<%= yield %>
|
||||
<% end %>
|
||||
|
||||
<% unless @suppress_user_has_account_footer == true %>
|
||||
This email was sent to you because you have an account at JamKazam / http://www.jamkazam.com.
|
||||
This email was sent to you because you have an account at JamKazam / http://www.jamkazam.com. Visit your profile page to unsubscribe: http://www.jamkazam.com/client#/account/profile.
|
||||
<% end %>
|
||||
|
||||
Copyright <%= Time.now.year %> JamKazam, Inc. All rights reserved.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,142 @@
|
|||
module JamRuby
|
||||
class EmailBatch < ActiveRecord::Base
|
||||
self.table_name = "email_batches"
|
||||
|
||||
attr_accessible :from_email, :subject, :test_emails, :body
|
||||
attr_accessible :lock_version, :qualified_count, :sent_count, :started_at, :completed_at
|
||||
|
||||
VAR_FIRST_NAME = '@FIRSTNAME'
|
||||
VAR_LAST_NAME = '@LASTNAME'
|
||||
|
||||
DEFAULT_SENDER = "support@jamkazam.com"
|
||||
|
||||
include AASM
|
||||
aasm do
|
||||
state :pending, :initial => true
|
||||
state :testing
|
||||
state :tested
|
||||
state :batching
|
||||
state :batched
|
||||
state :disabled
|
||||
|
||||
event :enable do
|
||||
transitions :from => :disabled, :to => :pending
|
||||
end
|
||||
event :reset do
|
||||
transitions :from => [:disabled, :testing, :tested, :batching, :batched, :pending], :to => :pending
|
||||
end
|
||||
event :do_test_run, :before => :running_tests do
|
||||
transitions :from => [:pending, :tested, :batched], :to => :testing
|
||||
end
|
||||
event :did_test_run, :after => :ran_tests do
|
||||
transitions :from => :testing, :to => :tested
|
||||
end
|
||||
event :do_batch_run, :before => :running_batch do
|
||||
transitions :from => [:tested, :pending, :batched], :to => :batching
|
||||
end
|
||||
event :did_batch_run, :after => :ran_batch do
|
||||
transitions :from => :batching, :to => :batched
|
||||
end
|
||||
event :disable do
|
||||
transitions :from => [:pending, :tested, :batched], :to => :disabled
|
||||
end
|
||||
end
|
||||
|
||||
# has_many :email_batch_results, :class_name => 'JamRuby::EmailBatchResult'
|
||||
|
||||
def self.create_with_params(params)
|
||||
obj = self.new
|
||||
params.each { |kk,vv| vv.strip! }
|
||||
obj.update_with_conflict_validation(params)
|
||||
obj
|
||||
end
|
||||
|
||||
def deliver_batch
|
||||
self.perform_event('do_batch_run!')
|
||||
User.email_opt_in.find_in_batches(batch_size: 1000) do |users|
|
||||
BatchMailer.send_batch_email(self.id, users.map(&:id)).deliver
|
||||
end
|
||||
end
|
||||
|
||||
def test_count
|
||||
self.test_emails.split(',').count
|
||||
end
|
||||
|
||||
def test_users
|
||||
self.test_emails.split(',').collect do |ee|
|
||||
ee.strip!
|
||||
uu = User.new
|
||||
uu.email = ee
|
||||
uu.first_name = ee.match(/^(.*)@/)[1].to_s
|
||||
uu.last_name = 'Test'
|
||||
uu
|
||||
end
|
||||
end
|
||||
|
||||
def send_test_batch
|
||||
self.perform_event('do_test_run!')
|
||||
BatchMailer.send_batch_email_test(self.id).deliver
|
||||
end
|
||||
|
||||
def merged_body(user)
|
||||
body.gsub(VAR_FIRST_NAME, user.first_name).gsub(VAR_LAST_NAME, user.last_name)
|
||||
end
|
||||
|
||||
def did_send(emails)
|
||||
self.update_with_conflict_validation({ :sent_count => self.sent_count + emails.size })
|
||||
|
||||
if self.sent_count >= self.qualified_count
|
||||
if batching?
|
||||
self.perform_event('did_batch_run!')
|
||||
elsif testing?
|
||||
self.perform_event('did_test_run!')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def perform_event(event_name)
|
||||
num_try = 0
|
||||
self.send(event_name)
|
||||
rescue ActiveRecord::StaleObjectError
|
||||
num_try += 1
|
||||
if 5 > num_try
|
||||
self.reload
|
||||
retry
|
||||
end
|
||||
end
|
||||
|
||||
def update_with_conflict_validation(*args)
|
||||
num_try = 0
|
||||
update_attributes(*args)
|
||||
rescue ActiveRecord::StaleObjectError
|
||||
num_try += 1
|
||||
if 5 > num_try
|
||||
self.reload
|
||||
retry
|
||||
end
|
||||
end
|
||||
|
||||
def running_batch
|
||||
self.update_with_conflict_validation({:qualified_count => User.email_opt_in.count,
|
||||
:sent_count => 0,
|
||||
:started_at => Time.now
|
||||
})
|
||||
end
|
||||
|
||||
def running_tests
|
||||
self.update_with_conflict_validation({:qualified_count => self.test_count,
|
||||
:sent_count => 0,
|
||||
:started_at => Time.now
|
||||
})
|
||||
end
|
||||
|
||||
def ran_tests
|
||||
self.update_with_conflict_validation({ :completed_at => Time.now })
|
||||
end
|
||||
|
||||
def ran_batch
|
||||
self.update_with_conflict_validation({ :completed_at => Time.now })
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
class JamRuby::EventSession < ActiveRecord::Base
|
||||
|
||||
attr_accessible :event_id, :user_id, :band_id, :starts_at, :ends_at, :pinned_state, :position, :img_url, :img_width, :img_height, as: :admin
|
||||
attr_accessible :event_id, :user_id, :band_id, :starts_at, :ends_at, :pinned_state, :position, :img_url, :img_width, :img_height, :ordinal, as: :admin
|
||||
|
||||
belongs_to :user, class_name: 'JamRuby::User'
|
||||
belongs_to :band, class_name: 'JamRuby::Band'
|
||||
|
|
@ -13,6 +13,48 @@ class JamRuby::EventSession < ActiveRecord::Base
|
|||
|
||||
before_validation :sanitize_active_admin
|
||||
|
||||
def has_public_mixed_recordings?
|
||||
public_mixed_recordings.length > 0
|
||||
end
|
||||
|
||||
def public_mixed_recordings
|
||||
recordings.select { |recording| recording if recording.has_mix? && recording.is_public? }
|
||||
end
|
||||
|
||||
def recordings
|
||||
recordings=[]
|
||||
|
||||
sessions.each do |session_history|
|
||||
recordings = recordings + session_history.recordings
|
||||
end
|
||||
|
||||
recordings.sort! do |x, y|
|
||||
x.candidate_claimed_recording.name <=> y.candidate_claimed_recording.name
|
||||
end
|
||||
|
||||
recordings
|
||||
end
|
||||
|
||||
# ideally this is based on some proper association with the event, not such a slushy time grab
|
||||
def sessions
|
||||
if ready_display
|
||||
query = MusicSessionHistory.where(fan_access: true).where(created_at: (self.starts_at - 12.hours)..(self.ends_at + 12.hours))
|
||||
if self.user_id
|
||||
query = query.where(user_id: self.user_id)
|
||||
elsif self.band_id
|
||||
query = query.where(band_id: self.band_id)
|
||||
else
|
||||
raise 'invalid state in event_session_button'
|
||||
end
|
||||
query
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
def ready_display
|
||||
self.starts_at && self.ends_at && (self.user_id || self.band_id)
|
||||
end
|
||||
def sanitize_active_admin
|
||||
self.img_url = nil if self.img_url == ''
|
||||
self.user_id = nil if self.user_id == ''
|
||||
|
|
|
|||
|
|
@ -157,6 +157,10 @@ module JamRuby
|
|||
music_session && music_session.mount
|
||||
end
|
||||
|
||||
def recordings
|
||||
Recording.where(music_session_id: self.id)
|
||||
end
|
||||
|
||||
def end_history
|
||||
self.update_attribute(:session_removed_at, Time.now)
|
||||
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ module JamRuby
|
|||
end
|
||||
|
||||
def has_access?(user)
|
||||
return users.exists?(user)
|
||||
users.exists?(user)
|
||||
end
|
||||
|
||||
# Start recording a session.
|
||||
|
|
@ -342,6 +342,9 @@ module JamRuby
|
|||
save
|
||||
end
|
||||
|
||||
def is_public?
|
||||
claimed_recordings.where(is_public: true).length > 0
|
||||
end
|
||||
|
||||
# meant to be used as a way to 'pluck' a claimed_recording appropriate for user.
|
||||
def candidate_claimed_recording
|
||||
|
|
|
|||
|
|
@ -5,8 +5,9 @@ module JamRuby
|
|||
|
||||
#devise: for later: :trackable
|
||||
|
||||
devise :database_authenticatable,
|
||||
:recoverable, :rememberable
|
||||
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
|
||||
|
||||
devise :database_authenticatable, :recoverable, :rememberable
|
||||
|
||||
include Geokit::ActsAsMappable::Glue unless defined?(acts_as_mappable)
|
||||
acts_as_mappable
|
||||
|
|
@ -107,8 +108,7 @@ module JamRuby
|
|||
validates :first_name, presence: true, length: {maximum: 50}, no_profanity: true
|
||||
validates :last_name, presence: true, length: {maximum: 50}, no_profanity: true
|
||||
validates :biography, length: {maximum: 4000}, no_profanity: true
|
||||
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
|
||||
validates :email, presence: true, format: {with: VALID_EMAIL_REGEX}
|
||||
validates :email, presence: true, format: {with: VALID_EMAIL_REGEX}
|
||||
validates :update_email, presence: true, format: {with: VALID_EMAIL_REGEX}, :if => :updating_email
|
||||
|
||||
validates_length_of :password, minimum: 6, maximum: 100, :if => :should_validate_password?
|
||||
|
|
@ -132,6 +132,7 @@ module JamRuby
|
|||
scope :fans, where(:musician => false)
|
||||
scope :geocoded_users, where(['lat IS NOT NULL AND lng IS NOT NULL'])
|
||||
scope :musicians_geocoded, musicians.geocoded_users
|
||||
scope :email_opt_in, where(:subscribe_email => true)
|
||||
|
||||
def user_progression_fields
|
||||
@user_progression_fields ||= Set.new ["first_downloaded_client_at", "first_ran_client_at", "first_music_session_at", "first_real_music_session_at", "first_good_music_session_at", "first_certified_gear_at", "first_invited_at", "first_friended_at", "first_recording_at", "first_social_promoted_at" ]
|
||||
|
|
@ -291,7 +292,33 @@ module JamRuby
|
|||
recordings.concat(msh)
|
||||
recordings.sort! {|a,b| b.created_at <=> a.created_at}.first(5)
|
||||
end
|
||||
|
||||
|
||||
# returns the # of new notifications
|
||||
def new_notifications
|
||||
search = Notification.select('id').where(target_user_id: self.id)
|
||||
search = search.where('created_at > ?', self.notification_seen_at) if self.notification_seen_at
|
||||
search.count
|
||||
end
|
||||
|
||||
# the user can pass in a timestamp string, or the keyword 'LATEST'
|
||||
# if LATEST is specified, we'll use the latest_notification as the timestamp
|
||||
# if not, just use seen as-is
|
||||
def update_notification_seen_at seen
|
||||
new_latest_seen = nil
|
||||
if seen == 'LATEST'
|
||||
latest = self.latest_notification
|
||||
new_latest_seen = latest.created_at if latest
|
||||
else
|
||||
new_latest_seen = seen
|
||||
end
|
||||
|
||||
self.notification_seen_at = new_latest_seen
|
||||
end
|
||||
|
||||
def latest_notification
|
||||
Notification.select('created_at').where(target_user_id: id).limit(1).order('created_at DESC').first
|
||||
end
|
||||
|
||||
def confirm_email!
|
||||
self.email_confirmed = true
|
||||
end
|
||||
|
|
@ -378,7 +405,7 @@ module JamRuby
|
|||
|
||||
def self.reset_password(email, base_uri)
|
||||
user = User.where("email ILIKE ?", email).first
|
||||
raise JamRuby::JamArgumentError if user.nil?
|
||||
raise JamRuby::JamArgumentError.new('unknown email', :email) if user.nil?
|
||||
|
||||
user.reset_password_token = SecureRandom.urlsafe_base64
|
||||
user.reset_password_token_created = Time.now
|
||||
|
|
|
|||
|
|
@ -429,6 +429,12 @@ FactoryGirl.define do
|
|||
factory :event_session, :class => JamRuby::EventSession do
|
||||
end
|
||||
|
||||
factory :email_batch, :class => JamRuby::EmailBatch do
|
||||
subject Faker::Lorem.sentence
|
||||
body "#{JamRuby::EmailBatch::VAR_FIRST_NAME} " + Faker::Lorem.paragraphs(3).join("\n")
|
||||
test_emails 4.times.collect { Faker::Internet.safe_email }.join(',')
|
||||
end
|
||||
|
||||
factory :notification, :class => JamRuby::Notification do
|
||||
|
||||
factory :notification_text_message do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe EmailBatch do
|
||||
let (:email_batch) { FactoryGirl.create(:email_batch) }
|
||||
|
||||
before(:each) do
|
||||
BatchMailer.deliveries.clear
|
||||
end
|
||||
|
||||
it 'has test emails setup' do
|
||||
expect(email_batch.test_emails.present?).to be true
|
||||
expect(email_batch.pending?).to be true
|
||||
|
||||
users = email_batch.test_users
|
||||
expect(email_batch.test_count).to eq(users.count)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -116,8 +116,8 @@ describe Track do
|
|||
tracks = Track.sync(connection.client_id, [{:id => track.id, :client_track_id => track.client_track_id, :sound => track.sound, :instrument_id => track.instrument_id}])
|
||||
tracks.length.should == 1
|
||||
found = tracks[0]
|
||||
found.id.should == track.id
|
||||
found.updated_at.should == track.updated_at
|
||||
expect(found.id).to eq track.id
|
||||
expect(found.updated_at.to_i).to eq track.updated_at.to_i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -195,7 +195,7 @@ describe User do
|
|||
end
|
||||
|
||||
it "fails if the provided email address is unrecognized" do
|
||||
expect { User.reset_password("invalidemail@invalid.com", RESET_PASSWORD_URL) }.to raise_error
|
||||
expect { User.reset_password("invalidemail@invalid.com", RESET_PASSWORD_URL) }.to raise_error(JamRuby::JamArgumentError)
|
||||
end
|
||||
|
||||
it "assigns a reset_token and reset_token_created on reset" do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
require "spec_helper"
|
||||
|
||||
describe BatchMailer do
|
||||
|
||||
describe "should send test emails" do
|
||||
BatchMailer.deliveries.clear
|
||||
|
||||
let (:mail) { BatchMailer.deliveries[0] }
|
||||
|
||||
batch = FactoryGirl.create(:email_batch)
|
||||
batch.update_attribute(:test_emails, "jonathan@jamkazam.com")
|
||||
batch.send_test_batch
|
||||
|
||||
it { BatchMailer.deliveries.length.should == 1 }
|
||||
|
||||
it { mail['from'].to_s.should == EmailBatch::DEFAULT_SENDER }
|
||||
it { mail['to'].to_s.split(',')[0].should == batch.test_emails.split(',')[0] }
|
||||
it { mail.subject.should == batch.subject }
|
||||
|
||||
it { mail.multipart?.should == true } # because we send plain + html
|
||||
it { mail.text_part.decode_body.should match(/#{Regexp.escape(batch.body)}/) }
|
||||
|
||||
it { batch.testing?.should == true }
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -45,7 +45,7 @@ gem 'omniauth-twitter'
|
|||
gem 'omniauth-google-oauth2', '0.2.1'
|
||||
gem 'twitter'
|
||||
gem 'fb_graph', '2.5.9'
|
||||
gem 'sendgrid', '1.1.0'
|
||||
gem 'sendgrid', '1.2.0'
|
||||
gem 'recaptcha', '0.3.4'
|
||||
gem 'filepicker-rails', '0.1.0'
|
||||
gem 'aws-sdk', '1.29.1'
|
||||
|
|
@ -67,6 +67,8 @@ gem 'resque-retry'
|
|||
gem 'resque-failed-job-mailer'
|
||||
gem 'resque-dynamic-queues'
|
||||
gem 'resque-lonely_job', '~> 1.0.0'
|
||||
gem 'resque_mailer'
|
||||
|
||||
gem 'quiet_assets', :group => :development
|
||||
gem 'bugsnag'
|
||||
gem 'multi_json', '1.9.0'
|
||||
|
|
|
|||
|
|
@ -99,8 +99,10 @@
|
|||
};
|
||||
|
||||
// Heartbeat message
|
||||
factory.heartbeat = function() {
|
||||
factory.heartbeat = function(lastNotificationSeen, lastNotificationSeenAt) {
|
||||
var data = {};
|
||||
data.notification_seen = lastNotificationSeen;
|
||||
data.notification_seen_at = lastNotificationSeenAt;
|
||||
return client_container(msg.HEARTBEAT, route_to.SERVER, data);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,9 @@
|
|||
//= require jquery.infinitescroll
|
||||
//= require jquery.hoverIntent
|
||||
//= require jquery.dotdotdot
|
||||
//= require jquery.pulse
|
||||
//= require jquery.browser
|
||||
//= require jquery.custom-protocol
|
||||
//= require AAA_Log
|
||||
//= require globals
|
||||
//= require AAB_message_factory
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@
|
|||
|
||||
function afterShow(data) {
|
||||
inviteMusiciansUtil.loadFriends();
|
||||
|
||||
context.JK.guardAgainstBrowser(app);
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
|
|
@ -117,11 +119,15 @@
|
|||
function submitForm(evt) {
|
||||
evt.preventDefault();
|
||||
|
||||
if(!gon.isNativeClient) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If user hasn't completed FTUE - do so now.
|
||||
if (!(context.JK.hasOneConfiguredDevice())) {
|
||||
app.afterFtue = function() { submitForm(evt); };
|
||||
app.layout.startNewFtue();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// if for some reason there are 0 tracks, show FTUE
|
||||
|
|
@ -131,7 +137,7 @@
|
|||
// If user hasn't completed FTUE - do so now.
|
||||
app.afterFtue = function() { submitForm(evt); };
|
||||
app.layout.startNewFtue();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
var isValid = validateForm();
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
var claimedRecordingId = $parentElement.attr('data-claimed-recording-id');
|
||||
var recordingId = $parentElement.attr('id');
|
||||
var mode = $parentElement.attr('data-mode');
|
||||
|
||||
var $feedItem = $parentElement;
|
||||
var $name = $('.name', $feedItem);
|
||||
|
|
|
|||
|
|
@ -346,6 +346,7 @@
|
|||
buildQuery();
|
||||
refreshDisplay();
|
||||
loadSessions();
|
||||
context.JK.guardAgainstBrowser(app);
|
||||
}
|
||||
|
||||
function clearResults() {
|
||||
|
|
|
|||
|
|
@ -934,6 +934,7 @@
|
|||
}
|
||||
|
||||
function getNotifications(options) {
|
||||
if(!options) options = {};
|
||||
var id = getId(options);
|
||||
return $.ajax({
|
||||
type: "GET",
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@
|
|||
var lastHeartbeatFound = false;
|
||||
var heartbeatAckCheckInterval = null;
|
||||
var userDeferred = null;
|
||||
var notificationLastSeenAt = undefined;
|
||||
var notificationLastSeen = undefined;
|
||||
|
||||
var opts = {
|
||||
inClient: true, // specify false if you want the app object but none of the client-oriented features
|
||||
|
|
@ -91,8 +93,9 @@
|
|||
|
||||
function _heartbeat() {
|
||||
if (app.heartbeatActive) {
|
||||
|
||||
var message = context.JK.MessageFactory.heartbeat();
|
||||
var message = context.JK.MessageFactory.heartbeat(notificationLastSeen, notificationLastSeenAt);
|
||||
notificationLastSeenAt = undefined;
|
||||
notificationLastSeen = undefined;
|
||||
context.JK.JamServer.send(message);
|
||||
lastHeartbeatFound = false;
|
||||
}
|
||||
|
|
@ -384,6 +387,28 @@
|
|||
return userDeferred;
|
||||
}
|
||||
|
||||
this.updateNotificationSeen = function(notificationId, notificationCreatedAt) {
|
||||
var time = new Date(notificationCreatedAt);
|
||||
|
||||
if(!notificationCreatedAt) {
|
||||
throw 'invalid value passed to updateNotificationSeen'
|
||||
}
|
||||
|
||||
if(!notificationLastSeenAt) {
|
||||
notificationLastSeenAt = notificationCreatedAt;
|
||||
notificationLastSeen = notificationId;
|
||||
logger.debug("updated notificationLastSeenAt with: " + notificationCreatedAt);
|
||||
}
|
||||
else if(time.getTime() > new Date(notificationLastSeenAt).getTime()) {
|
||||
notificationLastSeenAt = notificationCreatedAt;
|
||||
notificationLastSeen = notificationId;
|
||||
logger.debug("updated notificationLastSeenAt with: " + notificationCreatedAt);
|
||||
}
|
||||
else {
|
||||
logger.debug("ignored notificationLastSeenAt for: " + notificationCreatedAt);
|
||||
}
|
||||
}
|
||||
|
||||
this.unloadFunction = function () {
|
||||
logger.debug("window.unload function called.");
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,139 @@
|
|||
/*!
|
||||
* jQuery Custom Protocol Launcher v0.0.1
|
||||
* https://github.com/sethcall/jquery-custom-protocol
|
||||
*
|
||||
* Taken and modified from:
|
||||
* https://gist.github.com/rajeshsegu/3716941
|
||||
* http://stackoverflow.com/a/22055638/834644
|
||||
*
|
||||
* Depends on:
|
||||
* https://github.com/gabceb/jquery-browser-plugin
|
||||
*
|
||||
* Modifications Copyright 2014 Seth Call
|
||||
* https://github.com/sethcall
|
||||
*
|
||||
* Released under the MIT license
|
||||
*/
|
||||
|
||||
(function (jQuery, window, undefined) {
|
||||
"use strict";
|
||||
|
||||
function launchCustomProtocol(elem, url, options) {
|
||||
var myWindow, success = false;
|
||||
|
||||
if (!url) {
|
||||
throw "attribute 'href' must be specified on the element, or specified in options"
|
||||
}
|
||||
if (!options.callback) {
|
||||
throw "Specify 'callback' as an option to $.customProtocol";
|
||||
}
|
||||
|
||||
var settings = $.extend({}, options);
|
||||
|
||||
var callback = settings.callback;
|
||||
|
||||
if ($.browser.msie) {
|
||||
return ieTest(elem, url, callback);
|
||||
}
|
||||
else if ($.browser.mozilla) {
|
||||
return iframeTest(elem, url, callback);
|
||||
}
|
||||
else if ($.browser.chrome) {
|
||||
return blurTest(elem, url, callback);
|
||||
}
|
||||
}
|
||||
|
||||
function blurTest(elem, url, callback) {
|
||||
var timeout = null;
|
||||
// If focus is taken, assume a launch dialog was shown
|
||||
elem.css({"outline": 0});
|
||||
elem.attr("tabindex", "1");
|
||||
elem.focus();
|
||||
|
||||
function cleanup() {
|
||||
elem.off('blur');
|
||||
elem.removeAttr("tabindex");
|
||||
if(timeout) {
|
||||
clearTimeout(timeout)
|
||||
timeout = null;
|
||||
}
|
||||
}
|
||||
elem.blur(function () {
|
||||
cleanup();
|
||||
callback(true);
|
||||
});
|
||||
|
||||
location.replace(url);
|
||||
|
||||
timeout = setTimeout(function () {
|
||||
timeout = null;
|
||||
cleanup();
|
||||
callback(false);
|
||||
}, 1000);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function iframeTest(elem, url, callback) {
|
||||
var iframe, success = false;
|
||||
|
||||
try {
|
||||
iframe = $("<iframe />");
|
||||
iframe.css({"display": "none"});
|
||||
iframe.appendTo("body");
|
||||
iframe[0].contentWindow.location.href = url;
|
||||
success = true;
|
||||
} catch (ex) {
|
||||
success = false;
|
||||
}
|
||||
|
||||
if(iframe) iframe.remove();
|
||||
|
||||
callback(success);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function ieTest(elem, url, callback) {
|
||||
var success = false;
|
||||
|
||||
if (navigator.msLaunchUri) {
|
||||
// use msLaunchUri if available. IE10+
|
||||
navigator.msLaunchUri(
|
||||
url,
|
||||
function () {
|
||||
callback(true);
|
||||
},
|
||||
function () {
|
||||
callback(false);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function supportedBrowser() {
|
||||
return $.browser.desktop && (navigator.msLaunchUri || $.browser.chrome || $.browser.mozilla);
|
||||
}
|
||||
|
||||
$.fn.customProtocol = function (options) {
|
||||
this.each(function () {
|
||||
var $elem = $(this);
|
||||
|
||||
if(supportedBrowser()) {
|
||||
$elem.click(function (e) {
|
||||
return launchCustomProtocol($elem, options.href || $elem.attr('href'), options);
|
||||
});
|
||||
}
|
||||
else {
|
||||
if(options && options.fallback) {
|
||||
options.fallback.call($elem)
|
||||
return;
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
})(jQuery, window);
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
(function(context,$) {
|
||||
|
||||
"use strict";
|
||||
context.JK = context.JK || {};
|
||||
context.JK.LaunchAppDialog = function(app) {
|
||||
var logger = context.JK.logger;
|
||||
var rest = context.JK.Rest();
|
||||
var $dialog = null;
|
||||
var $contentHolder = null;
|
||||
var $templateUnsupportedLaunch = null;
|
||||
var $templateAttemptLaunch = null;
|
||||
var $templateLaunchSuccessful = null;
|
||||
var $templateLaunchUnsuccessful = null;
|
||||
|
||||
function renderAttemptLaunch() {
|
||||
var $template = $(context._.template($templateAttemptLaunch.html(), buildOptions(), { variable: 'data' }));
|
||||
$template.find('.btn-cancel').click(handleBack);
|
||||
$contentHolder.empty().append($template);
|
||||
|
||||
|
||||
var options = {
|
||||
callback:function(success) {
|
||||
if(success) {
|
||||
renderLaunchSuccess();
|
||||
}
|
||||
else {
|
||||
renderLaunchUnsuccessful();
|
||||
}
|
||||
},
|
||||
fallback:function() {
|
||||
renderUnsupportedLaunch();
|
||||
}
|
||||
};
|
||||
|
||||
$template.find('.btn-launch-app').customProtocol(options);
|
||||
}
|
||||
|
||||
function renderLaunchSuccess() {
|
||||
var $template = $(context._.template($templateLaunchSuccessful.html(), buildOptions(), { variable: 'data' }));
|
||||
$template.find('.btn-done').click(handleBack);
|
||||
$template.find('.not-actually-launched').click(function() {
|
||||
$dialog.find('.dont-see-it').show();
|
||||
return false;
|
||||
});
|
||||
$contentHolder.empty().append($template);
|
||||
}
|
||||
|
||||
function renderLaunchUnsuccessful() {
|
||||
var $template = $(context._.template($templateLaunchUnsuccessful.html(), buildOptions(), { variable: 'data' }));
|
||||
$template.find('.btn-done').click(handleBack);
|
||||
$contentHolder.empty().append($template);
|
||||
}
|
||||
|
||||
function renderUnsupportedLaunch() {
|
||||
var $template = $(context._.template($templateUnsupportedLaunch.html(), buildOptions(), { variable: 'data' }));
|
||||
$template.find('.btn-cancel').click(handleBack);
|
||||
$contentHolder.empty().append($template);
|
||||
}
|
||||
|
||||
function handleBack() {
|
||||
app.layout.closeDialog('launch-app-dialog');
|
||||
window.location = '/client#/home';
|
||||
return false;
|
||||
}
|
||||
|
||||
function buildOptions() {
|
||||
var osSpecificSystemTray = 'taskbar';
|
||||
if($.browser.mac) {
|
||||
osSpecificSystemTray = 'dock or status menu';
|
||||
}
|
||||
|
||||
var osSpecificSystemTrayLink = '';
|
||||
|
||||
if($.browser.win) {
|
||||
osSpecificSystemTrayLink = 'http://windows.microsoft.com/en-us/windows/taskbar-overview#1TC=windows-7';
|
||||
}
|
||||
else if($.browser.mac) {
|
||||
osSpecificSystemTrayLink = 'http://support.apple.com/kb/ht3737';
|
||||
}
|
||||
|
||||
return { osSpecificSystemTray: osSpecificSystemTray, osSpecificSystemTrayLink: osSpecificSystemTrayLink, launchUrl: 'jamkazam:'};
|
||||
}
|
||||
|
||||
function reset() {
|
||||
renderAttemptLaunch();
|
||||
}
|
||||
|
||||
function beforeShow() {
|
||||
reset();
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
var dialogBindings = {
|
||||
'beforeShow' : beforeShow
|
||||
};
|
||||
|
||||
app.bindDialog('launch-app-dialog', dialogBindings);
|
||||
|
||||
$dialog = $('#launch-app-dialog');
|
||||
$contentHolder = $dialog.find('.dialog-inner');
|
||||
$templateAttemptLaunch = $('#template-attempt-launch');
|
||||
$templateUnsupportedLaunch = $('#template-unsupported-launch');
|
||||
$templateLaunchSuccessful = $('#template-launch-successful');
|
||||
$templateLaunchUnsuccessful = $('#template-launch-unsuccessful');
|
||||
}
|
||||
|
||||
this.initialize = initialize;
|
||||
}
|
||||
|
||||
return this;
|
||||
})(window,jQuery);
|
||||
|
|
@ -251,7 +251,8 @@
|
|||
if (!sidebarVisible) {
|
||||
return;
|
||||
}
|
||||
var $expandedPanelContents = $('[layout-id="' + expandedPanel + '"] [layout-panel="contents"]');
|
||||
var $expandedPanel = $('[layout-id="' + expandedPanel + '"]');
|
||||
var $expandedPanelContents = $expandedPanel.find('[layout-panel="contents"]');
|
||||
var combinedHeaderHeight = $('[layout-panel="contents"]').length * opts.panelHeaderHeight;
|
||||
var searchHeight = $('.sidebar .search').first().height();
|
||||
var expanderHeight = $('[layout-sidebar-expander]').height();
|
||||
|
|
@ -259,6 +260,7 @@
|
|||
$('[layout-panel="contents"]').hide();
|
||||
$('[layout-panel="contents"]').css({"height": "1px"});
|
||||
$expandedPanelContents.show();
|
||||
$expandedPanel.triggerHandler('open')
|
||||
$expandedPanelContents.animate({"height": expandedPanelHeight + "px"}, opts.animationDuration);
|
||||
}
|
||||
|
||||
|
|
@ -425,6 +427,7 @@
|
|||
unstackDialogs($overlay);
|
||||
$dialog.hide();
|
||||
dialogEvent(dialog, 'afterHide');
|
||||
$(me).triggerHandler('dialog_closed', {dialogCount: openDialogs.length})
|
||||
}
|
||||
|
||||
function screenEvent(screen, evtName, data) {
|
||||
|
|
@ -526,6 +529,29 @@
|
|||
}
|
||||
}
|
||||
|
||||
function isDialogShowing() {
|
||||
return openDialogs.length > 0;
|
||||
}
|
||||
|
||||
function currentDialog() {
|
||||
if(openDialogs.length == 0) return null;
|
||||
|
||||
return openDialogs[openDialogs.length - 1];
|
||||
}
|
||||
|
||||
// payload is a notification event from websocket gateway
|
||||
function dialogObscuredNotification(payload) {
|
||||
var openDialog = currentDialog();
|
||||
if(!openDialog) return false;
|
||||
|
||||
if(typeof openDialog.handledNotification === 'function') {
|
||||
return !openDialog.handledNotification(payload);
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Responsible for keeping N dialogs in correct stacked order,
|
||||
* also moves the .dialog-overlay such that it hides/obscures all dialogs except the highest one
|
||||
|
|
@ -849,6 +875,14 @@
|
|||
showDialog(dialog, options);
|
||||
};
|
||||
|
||||
this.dialogObscuredNotification = function() {
|
||||
return dialogObscuredNotification();
|
||||
}
|
||||
|
||||
this.isDialogShowing = function() {
|
||||
return isDialogShowing();
|
||||
}
|
||||
|
||||
this.close = function (evt) {
|
||||
close(evt);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,905 @@
|
|||
(function(context,$) {
|
||||
|
||||
"use strict";
|
||||
|
||||
context.JK = context.JK || {};
|
||||
context.JK.NotificationPanel = function(app) {
|
||||
var logger = context.JK.logger;
|
||||
var friends = [];
|
||||
var rest = context.JK.Rest();
|
||||
var missedNotificationsWhileAway = false;
|
||||
var $panel = null;
|
||||
var $expanded = null;
|
||||
var $contents = null;
|
||||
var $count = null;
|
||||
var $list = null;
|
||||
var $notificationTemplate = null;
|
||||
var sidebar = null;
|
||||
var darkenedColor = '#0D7B89';
|
||||
var highlightedColor = 'white'
|
||||
var textMessageDialog = null;
|
||||
var queuedNotification = null;
|
||||
var queuedNotificationCreatedAt = null;
|
||||
|
||||
function isNotificationsPanelVisible() {
|
||||
return $contents.is(':visible')
|
||||
}
|
||||
|
||||
function incrementNotificationCount() {
|
||||
var count = parseInt($count.text());
|
||||
setCount(count + 1);
|
||||
}
|
||||
|
||||
// set the element to white, and pulse it down to the un-highlighted value 2x, then set
|
||||
function pulseToDark() {
|
||||
logger.debug("pulsing notification badge")
|
||||
lowlightCount();
|
||||
$count.pulse({'background-color' : highlightedColor}, {pulses: 2}, function() {
|
||||
$count.removeAttr('style')
|
||||
setCount(0);
|
||||
})
|
||||
}
|
||||
|
||||
function setCount(count) {
|
||||
$count.text(count);
|
||||
}
|
||||
|
||||
function lowlightCount() {
|
||||
$count.removeClass('highlighted');
|
||||
}
|
||||
|
||||
function highlightCount() {
|
||||
$count.addClass('highlighted');
|
||||
}
|
||||
|
||||
function queueNotificationSeen(notificationId, notificationCreatedAt) {
|
||||
|
||||
var time = new Date(notificationCreatedAt);
|
||||
|
||||
if(!notificationCreatedAt) {
|
||||
throw 'invalid value passed to queuedNotificationCreatedAt'
|
||||
}
|
||||
|
||||
if(!queuedNotificationCreatedAt) {
|
||||
queuedNotification = notificationId;
|
||||
queuedNotificationCreatedAt = notificationCreatedAt;
|
||||
logger.debug("updated queuedNotificationCreatedAt with: " + notificationCreatedAt);
|
||||
}
|
||||
else if(time.getTime() > new Date(queuedNotificationCreatedAt).getTime()) {
|
||||
queuedNotification = notificationId;
|
||||
queuedNotificationCreatedAt = notificationCreatedAt;
|
||||
logger.debug("updated queuedNotificationCreatedAt with: " + notificationCreatedAt);
|
||||
}
|
||||
else {
|
||||
logger.debug("ignored queuedNotificationCreatedAt for: " + notificationCreatedAt);
|
||||
}
|
||||
}
|
||||
|
||||
function onNotificationOccurred(payload) {
|
||||
if(userCanSeeNotifications(payload)) {
|
||||
app.updateNotificationSeen(payload.notification_id, payload.created_at);
|
||||
}
|
||||
else {
|
||||
queueNotificationSeen(payload.notification_id, payload.created_at);
|
||||
highlightCount();
|
||||
incrementNotificationCount();
|
||||
missedNotificationsWhileAway = true;
|
||||
}
|
||||
}
|
||||
|
||||
function userCameBack() {
|
||||
if(isNotificationsPanelVisible()) {
|
||||
if(missedNotificationsWhileAway) {
|
||||
// catch user's eye, then put count to 0
|
||||
pulseToDark();
|
||||
if(queuedNotificationCreatedAt) {
|
||||
app.updateNotificationSeen(queuedNotification, queuedNotificationCreatedAt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
queuedNotification = null;
|
||||
queuedNotificationCreatedAt = null;
|
||||
missedNotificationsWhileAway = false;
|
||||
}
|
||||
|
||||
function opened() {
|
||||
queuedNotification = null;
|
||||
queuedNotificationCreatedAt = null;
|
||||
rest.updateUser({notification_seen_at: 'LATEST'})
|
||||
.done(function(response) {
|
||||
lowlightCount();
|
||||
setCount(0);
|
||||
})
|
||||
.fail(app.ajaxError)
|
||||
}
|
||||
|
||||
function windowBlurred() {
|
||||
|
||||
}
|
||||
|
||||
function events() {
|
||||
$(app.layout).on('dialog_closed', function(e, data) {if(data.dialogCount == 0) userCameBack(); });
|
||||
$(window).focus(userCameBack);
|
||||
$(window).blur(windowBlurred);
|
||||
app.user()
|
||||
.done(function(user) {
|
||||
setCount(user.new_notifications);
|
||||
if(user.new_notifications > 0) {
|
||||
highlightCount();
|
||||
}
|
||||
});
|
||||
|
||||
$panel.on('open', opened);
|
||||
|
||||
// friend notifications
|
||||
registerFriendRequest();
|
||||
registerFriendRequestAccepted();
|
||||
registerNewUserFollower();
|
||||
registerNewBandFollower();
|
||||
|
||||
// session notifications
|
||||
registerSessionInvitation();
|
||||
registerSessionEnded();
|
||||
registerJoinRequest();
|
||||
registerJoinRequestApproved();
|
||||
registerJoinRequestRejected();
|
||||
registerMusicianSessionJoin();
|
||||
registerBandSessionJoin();
|
||||
|
||||
// recording notifications
|
||||
registerMusicianRecordingSaved();
|
||||
registerBandRecordingSaved();
|
||||
registerRecordingMasterMixComplete();
|
||||
|
||||
// band notifications
|
||||
registerBandInvitation();
|
||||
registerBandInvitationAccepted();
|
||||
|
||||
// register text messages
|
||||
registerTextMessage();
|
||||
}
|
||||
|
||||
function populate() {
|
||||
// retrieve pending notifications for this user
|
||||
rest.getNotifications()
|
||||
.done(function(response) {
|
||||
updateNotificationList(response);
|
||||
})
|
||||
.fail(app.ajaxError)
|
||||
}
|
||||
|
||||
function updateNotificationList(response) {
|
||||
$list.empty();
|
||||
|
||||
$.each(response, function(index, val) {
|
||||
|
||||
if(val.description == 'TEXT_MESSAGE') {
|
||||
val.formatted_msg = textMessageDialog.formatTextMessage(val.message.substring(0, 200), val.source_user_id, val.source_user.name, val.message.length > 200).html();
|
||||
}
|
||||
|
||||
// fill in template for Connect pre-click
|
||||
var template = $notificationTemplate.html();
|
||||
var notificationHtml = context.JK.fillTemplate(template, {
|
||||
notificationId: val.notification_id,
|
||||
sessionId: val.session_id,
|
||||
avatar_url: context.JK.resolveAvatarUrl(val.photo_url),
|
||||
text: val.formatted_msg,
|
||||
date: $.timeago(val.created_at)
|
||||
});
|
||||
|
||||
$list.append(notificationHtml);
|
||||
|
||||
// val.description contains the notification record's description value from the DB (i.e., type)
|
||||
initializeActions(val, val.description);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function initializeActions(payload, type) {
|
||||
|
||||
var $notification = $('li[notification-id=' + payload.notification_id + ']');
|
||||
var $btnNotificationAction = '#btn-notification-action';
|
||||
|
||||
// wire up "x" button to delete notification
|
||||
$notification.find('#img-delete-notification').click(deleteNotificationHandler);
|
||||
|
||||
// customize action buttons based on notification type
|
||||
if (type === context.JK.MessageType.FRIEND_REQUEST) {
|
||||
var $action_btn = $notification.find($btnNotificationAction);
|
||||
$action_btn.text('ACCEPT');
|
||||
$action_btn.click(function() {
|
||||
acceptFriendRequest(payload);
|
||||
});
|
||||
}
|
||||
|
||||
else if (type === context.JK.MessageType.FRIEND_REQUEST_ACCEPTED) {
|
||||
$notification.find('#div-actions').hide();
|
||||
}
|
||||
|
||||
else if (type === context.JK.MessageType.NEW_USER_FOLLOWER || type === context.JK.MessageType.NEW_BAND_FOLLOWER) {
|
||||
$notification.find('#div-actions').hide();
|
||||
}
|
||||
|
||||
else if (type === context.JK.MessageType.SESSION_INVITATION) {
|
||||
var $action_btn = $notification.find($btnNotificationAction);
|
||||
$action_btn.text('JOIN');
|
||||
$action_btn.click(function() {
|
||||
openTerms(payload);
|
||||
});
|
||||
}
|
||||
|
||||
else if (type === context.JK.MessageType.JOIN_REQUEST) {
|
||||
var $action_btn = $notification.find($btnNotificationAction);
|
||||
$action_btn.text('APPROVE');
|
||||
$action_btn.click(function() {
|
||||
approveJoinRequest(payload);
|
||||
});
|
||||
}
|
||||
|
||||
else if (type === context.JK.MessageType.JOIN_REQUEST_APPROVED) {
|
||||
var $action_btn = $notification.find($btnNotificationAction);
|
||||
$action_btn.text('JOIN');
|
||||
$action_btn.click(function() {
|
||||
openTerms(payload);
|
||||
});
|
||||
}
|
||||
|
||||
else if (type === context.JK.MessageType.JOIN_REQUEST_REJECTED) {
|
||||
$notification.find('#div-actions').hide();
|
||||
}
|
||||
|
||||
else if (type === context.JK.MessageType.MUSICIAN_SESSION_JOIN || type === context.JK.MessageType.BAND_SESSION_JOIN) {
|
||||
|
||||
var actionText = '';
|
||||
var callback;
|
||||
if (context.JK.currentUserMusician) {
|
||||
// user is MUSICIAN; musician_access = TRUE
|
||||
if (payload.musician_access) {
|
||||
actionText = "JOIN";
|
||||
callback = joinSession;
|
||||
}
|
||||
// user is MUSICIAN; fan_access = TRUE
|
||||
else if (payload.fan_access) {
|
||||
actionText = "LISTEN";
|
||||
callback = listenToSession;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// user is FAN; fan_access = TRUE
|
||||
if (payload.fan_access) {
|
||||
actionText = "LISTEN";
|
||||
callback = listenToSession;
|
||||
}
|
||||
}
|
||||
|
||||
var $action_btn = $notification.find($btnNotificationAction);
|
||||
$action_btn.text(actionText);
|
||||
$action_btn.click(function() {
|
||||
callback(payload);
|
||||
});
|
||||
}
|
||||
|
||||
else if (type === context.JK.MessageType.MUSICIAN_RECORDING_SAVED || type === context.JK.MessageType.BAND_RECORDING_SAVED) {
|
||||
var $action_btn = $notification.find($btnNotificationAction);
|
||||
$action_btn.text('LISTEN');
|
||||
$action_btn.click(function() {
|
||||
listenToRecording(payload);
|
||||
});
|
||||
}
|
||||
|
||||
else if (type === context.JK.MessageType.RECORDING_MASTER_MIX_COMPLETE) {
|
||||
$notification.find('#div-actions').hide();
|
||||
context.jamClient.OnDownloadAvailable(); // poke backend, letting it know a download is available
|
||||
}
|
||||
|
||||
else if (type === context.JK.MessageType.BAND_INVITATION) {
|
||||
var $action_btn = $notification.find($btnNotificationAction);
|
||||
$action_btn.text('ACCEPT');
|
||||
$action_btn.click(function() {
|
||||
acceptBandInvitation(payload);
|
||||
});
|
||||
}
|
||||
else if (type === context.JK.MessageType.BAND_INVITATION_ACCEPTED) {
|
||||
$notification.find('#div-actions').hide();
|
||||
}
|
||||
else if (type === context.JK.MessageType.TEXT_MESSAGE) {
|
||||
var $action_btn = $notification.find($btnNotificationAction);
|
||||
$action_btn.text('REPLY');
|
||||
$action_btn.click(function() {
|
||||
var userId = $notification.find('.more-text-available').attr('data-sender-id');
|
||||
app.layout.showDialog('text-message', { d1: userId });
|
||||
});
|
||||
|
||||
var moreTextLink = $notification.find('.more-text-available');
|
||||
var textMessage = $notification.find('.text-message');
|
||||
var clipped_msg = textMessage.attr('data-is-clipped') === 'true';
|
||||
|
||||
if(clipped_msg) {
|
||||
moreTextLink.text('more').show();
|
||||
moreTextLink.click(function(e) {
|
||||
var userId = $(this).attr('data-sender-id');
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
else {
|
||||
moreTextLink.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function acceptBandInvitation(args) {
|
||||
rest.updateBandInvitation(
|
||||
args.band_id,
|
||||
args.band_invitation_id,
|
||||
true
|
||||
).done(function(response) {
|
||||
deleteNotification(args.notification_id); // delete notification corresponding to this friend request
|
||||
}).error(app.ajaxError);
|
||||
}
|
||||
|
||||
|
||||
function deleteNotification(notificationId) {
|
||||
var url = "/api/users/" + context.JK.currentUserId + "/notifications/" + notificationId;
|
||||
$.ajax({
|
||||
type: "DELETE",
|
||||
dataType: "json",
|
||||
contentType: 'application/json',
|
||||
url: url,
|
||||
processData: false,
|
||||
success: function(response) {
|
||||
$('li[notification-id=' + notificationId + ']').hide();
|
||||
//decrementNotificationCount();
|
||||
},
|
||||
error: app.ajaxError
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function listenToSession(args) {
|
||||
deleteNotification(args.notification_id);
|
||||
context.JK.popExternalLink('/sessions/' + args.session_id);
|
||||
}
|
||||
|
||||
/*********** TODO: THE NEXT 3 FUNCTIONS ARE COPIED FROM sessionList.js. REFACTOR TO COMMON PLACE. *************/
|
||||
function joinSession(args) {
|
||||
// NOTE: invited musicians get their own notification, so no need to check if user has invitation here
|
||||
// like other places because an invited user would never get this notification
|
||||
if (args.musician_access) {
|
||||
if (args.approval_required) {
|
||||
openAlert(args.session_id);
|
||||
}
|
||||
else {
|
||||
openTerms(args);
|
||||
}
|
||||
}
|
||||
deleteNotification(args.notification_id);
|
||||
}
|
||||
|
||||
|
||||
function registerJoinRequestApproved() {
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.JOIN_REQUEST_APPROVED, function(header, payload) {
|
||||
logger.debug("Handling JOIN_REQUEST_APPROVED msg " + JSON.stringify(payload));
|
||||
|
||||
handleNotification(payload, header.type);
|
||||
|
||||
app.notify({
|
||||
"title": "Join Request Approved",
|
||||
"text": payload.msg,
|
||||
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
|
||||
}, {
|
||||
"ok_text": "JOIN SESSION",
|
||||
"ok_callback": openTerms,
|
||||
"ok_callback_args": { "session_id": payload.session_id, "notification_id": payload.notification_id }
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function registerJoinRequestRejected() {
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.JOIN_REQUEST_REJECTED, function(header, payload) {
|
||||
logger.debug("Handling JOIN_REQUEST_REJECTED msg " + JSON.stringify(payload));
|
||||
|
||||
handleNotification(payload, header.type);
|
||||
|
||||
app.notify({
|
||||
"title": "Join Request Rejected",
|
||||
"text": payload.msg,
|
||||
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function registerJoinRequest() {
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.JOIN_REQUEST, function(header, payload) {
|
||||
logger.debug("Handling JOIN_REQUEST msg " + JSON.stringify(payload));
|
||||
|
||||
handleNotification(payload, header.type);
|
||||
|
||||
app.notify({
|
||||
"title": "New Join Request",
|
||||
"text": payload.msg,
|
||||
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
|
||||
}, {
|
||||
"ok_text": "APPROVE",
|
||||
"ok_callback": approveJoinRequest,
|
||||
"ok_callback_args": { "join_request_id": payload.join_request_id, "notification_id": payload.notification_id },
|
||||
"cancel_text": "REJECT",
|
||||
"cancel_callback": rejectJoinRequest,
|
||||
"cancel_callback_args": { "join_request_id": payload.join_request_id, "notification_id": payload.notification_id }
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function registerFriendRequestAccepted() {
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.FRIEND_REQUEST_ACCEPTED, function(header, payload) {
|
||||
logger.debug("Handling FRIEND_REQUEST_ACCEPTED msg " + JSON.stringify(payload));
|
||||
|
||||
handleNotification(payload, header.type);
|
||||
|
||||
sidebar.refreshFriends();
|
||||
|
||||
app.notify({
|
||||
"title": "Friend Request Accepted",
|
||||
"text": payload.msg,
|
||||
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function registerNewUserFollower() {
|
||||
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.NEW_USER_FOLLOWER, function(header, payload) {
|
||||
logger.debug("Handling NEW_USER_FOLLOWER msg " + JSON.stringify(payload));
|
||||
|
||||
handleNotification(payload, header.type);
|
||||
|
||||
app.notify({
|
||||
"title": "New Follower",
|
||||
"text": payload.msg,
|
||||
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function registerNewBandFollower() {
|
||||
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.NEW_BAND_FOLLOWER, function(header, payload) {
|
||||
logger.debug("Handling NEW_BAND_FOLLOWER msg " + JSON.stringify(payload));
|
||||
|
||||
handleNotification(payload, header.type);
|
||||
|
||||
app.notify({
|
||||
"title": "New Band Follower",
|
||||
"text": payload.msg,
|
||||
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function registerFriendRequest() {
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.FRIEND_REQUEST, function(header, payload) {
|
||||
logger.debug("Handling FRIEND_REQUEST msg " + JSON.stringify(payload));
|
||||
|
||||
handleNotification(payload, header.type);
|
||||
|
||||
app.notify({
|
||||
"title": "New Friend Request",
|
||||
"text": payload.msg,
|
||||
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
|
||||
}, {
|
||||
"ok_text": "ACCEPT",
|
||||
"ok_callback": acceptFriendRequest,
|
||||
"ok_callback_args": { "friend_request_id": payload.friend_request_id, "notification_id": payload.notification_id }
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function acceptFriendRequest(args) {
|
||||
|
||||
rest.acceptFriendRequest({
|
||||
status: 'accept',
|
||||
friend_request_id: args.friend_request_id
|
||||
}).done(function(response) {
|
||||
deleteNotification(args.notification_id); // delete notification corresponding to this friend request
|
||||
sidebar.refreshFriends(); // refresh friends panel when request is accepted
|
||||
}).error(app.ajaxError);
|
||||
}
|
||||
|
||||
function registerSessionEnded() {
|
||||
// TODO: this should clean up all notifications related to this session
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SESSION_ENDED, function(header, payload) {
|
||||
logger.debug("Handling SESSION_ENDED msg " + JSON.stringify(payload));
|
||||
deleteSessionNotifications(payload.session_id);
|
||||
});
|
||||
}
|
||||
|
||||
// remove all notifications for this session
|
||||
function deleteSessionNotifications(sessionId) {
|
||||
$('li[session-id=' + sessionId + ']').hide();
|
||||
//decrementNotificationCount();
|
||||
}
|
||||
|
||||
function registerSessionInvitation() {
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SESSION_INVITATION, function(header, payload) {
|
||||
logger.debug("Handling SESSION_INVITATION msg " + JSON.stringify(payload));
|
||||
|
||||
handleNotification(payload, header.type);
|
||||
|
||||
var participants = [];
|
||||
rest.getSession(payload.session_id).done(function(response) {
|
||||
$.each(response.participants, function(index, val) {
|
||||
participants.push({"photo_url": context.JK.resolveAvatarUrl(val.user.photo_url), "name": val.user.name});
|
||||
});
|
||||
|
||||
var participantHtml = "You have been invited to join a session with: <br/><br/>";
|
||||
participantHtml += "<table><tbody>";
|
||||
|
||||
$.each(participants, function(index, val) {
|
||||
if (index < 4) {
|
||||
participantHtml += "<tr><td><img class='avatar-small' src='" + context.JK.resolveAvatarUrl(val.photo_url) + "' /></td><td>" + val.name + "</td></tr>";
|
||||
}
|
||||
});
|
||||
|
||||
participantHtml += "</tbody></table>";
|
||||
|
||||
app.notify({
|
||||
"title": "Session Invitation",
|
||||
"text": participantHtml
|
||||
}, {
|
||||
"ok_text": "JOIN SESSION",
|
||||
"ok_callback": openTerms,
|
||||
"ok_callback_args": { "session_id": payload.session_id, "notification_id": payload.notification_id }
|
||||
});
|
||||
}).error(app.ajaxError);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function registerMusicianSessionJoin() {
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_JOIN, function(header, payload) {
|
||||
logger.debug("Handling MUSICIAN_SESSION_JOIN msg " + JSON.stringify(payload));
|
||||
|
||||
var okText = '';
|
||||
var showNotification = false;
|
||||
var callback;
|
||||
if (context.JK.currentUserMusician) {
|
||||
// user is MUSICIAN; musician_access = TRUE
|
||||
if (payload.musician_access) {
|
||||
showNotification = true;
|
||||
okText = "JOIN";
|
||||
callback = joinSession;
|
||||
}
|
||||
// user is MUSICIAN; fan_access = TRUE
|
||||
else if (payload.fan_access) {
|
||||
showNotification = true;
|
||||
okText = "LISTEN";
|
||||
callback = listenToSession;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// user is FAN; fan_access = TRUE
|
||||
if (payload.fan_access) {
|
||||
showNotification = true;
|
||||
okText = "LISTEN";
|
||||
callback = listenToSession;
|
||||
}
|
||||
}
|
||||
|
||||
if (showNotification) {
|
||||
handleNotification(payload, header.type);
|
||||
|
||||
app.notify({
|
||||
"title": "Musician Joined Session",
|
||||
"text": payload.msg,
|
||||
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
|
||||
}, {
|
||||
"ok_text": okText,
|
||||
"ok_callback": callback,
|
||||
"ok_callback_args": {
|
||||
"session_id": payload.session_id,
|
||||
"fan_access": payload.fan_access,
|
||||
"musician_access": payload.musician_access,
|
||||
"approval_required": payload.approval_required,
|
||||
"notification_id": payload.notification_id
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function registerBandSessionJoin() {
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.BAND_SESSION_JOIN, function(header, payload) {
|
||||
logger.debug("Handling BAND_SESSION_JOIN msg " + JSON.stringify(payload));
|
||||
|
||||
var okText = '';
|
||||
var showNotification = false;
|
||||
var callback;
|
||||
if (context.JK.currentUserMusician) {
|
||||
// user is MUSICIAN; musician_access = TRUE
|
||||
if (payload.musician_access) {
|
||||
showNotification = true;
|
||||
okText = "JOIN";
|
||||
callback = joinSession;
|
||||
}
|
||||
// user is MUSICIAN; fan_access = TRUE
|
||||
else if (payload.fan_access) {
|
||||
showNotification = true;
|
||||
okText = "LISTEN";
|
||||
callback = listenToSession;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// user is FAN; fan_access = TRUE
|
||||
if (payload.fan_access) {
|
||||
showNotification = true;
|
||||
okText = "LISTEN";
|
||||
callback = listenToSession;
|
||||
}
|
||||
}
|
||||
|
||||
if (showNotification) {
|
||||
handleNotification(payload, header.type);
|
||||
|
||||
app.notify({
|
||||
"title": "Band Joined Session",
|
||||
"text": payload.msg,
|
||||
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
|
||||
}, {
|
||||
"ok_text": "LISTEN",
|
||||
"ok_callback": callback,
|
||||
"ok_callback_args": {
|
||||
"session_id": payload.session_id,
|
||||
"fan_access": payload.fan_access,
|
||||
"musician_access": payload.musician_access,
|
||||
"approval_required": payload.approval_required,
|
||||
"notification_id": payload.notification_id
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function registerMusicianRecordingSaved() {
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.MUSICIAN_RECORDING_SAVED, function(header, payload) {
|
||||
logger.debug("Handling MUSICIAN_RECORDING_SAVED msg " + JSON.stringify(payload));
|
||||
|
||||
handleNotification(payload, header.type);
|
||||
|
||||
app.notify({
|
||||
"title": "Musician Recording Saved",
|
||||
"text": payload.msg,
|
||||
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
|
||||
}, {
|
||||
"ok_text": "LISTEN",
|
||||
"ok_callback": listenToRecording,
|
||||
"ok_callback_args": {
|
||||
"recording_id": payload.recording_id,
|
||||
"notification_id": payload.notification_id
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function registerBandRecordingSaved() {
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.BAND_RECORDING_SAVED, function(header, payload) {
|
||||
logger.debug("Handling BAND_RECORDING_SAVED msg " + JSON.stringify(payload));
|
||||
|
||||
handleNotification(payload, header.type);
|
||||
|
||||
app.notify({
|
||||
"title": "Band Recording Saved",
|
||||
"text": payload.msg,
|
||||
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
|
||||
}, {
|
||||
"ok_text": "LISTEN",
|
||||
"ok_callback": listenToRecording,
|
||||
"ok_callback_args": {
|
||||
"recording_id": payload.recording_id,
|
||||
"notification_id": payload.notification_id
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function registerRecordingMasterMixComplete() {
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.RECORDING_MASTER_MIX_COMPLETE, function(header, payload) {
|
||||
logger.debug("Handling RECORDING_MASTER_MIX_COMPLETE msg " + JSON.stringify(payload));
|
||||
|
||||
handleNotification(payload, header.type);
|
||||
|
||||
app.notify({
|
||||
"title": "Recording Master Mix Complete",
|
||||
"text": payload.msg,
|
||||
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
|
||||
}, {
|
||||
"ok_text": "SHARE",
|
||||
"ok_callback": shareRecording,
|
||||
"ok_callback_args": {
|
||||
"recording_id": payload.recording_id
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function shareRecording(args) {
|
||||
var recordingId = args.recording_id;
|
||||
}
|
||||
|
||||
function registerBandInvitation() {
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.BAND_INVITATION, function(header, payload) {
|
||||
logger.debug("Handling BAND_INVITATION msg " + JSON.stringify(payload));
|
||||
|
||||
handleNotification(payload, header.type);
|
||||
|
||||
app.notify({
|
||||
"title": "Band Invitation",
|
||||
"text": payload.msg,
|
||||
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
|
||||
}, {
|
||||
"ok_text": "ACCEPT",
|
||||
"ok_callback": acceptBandInvitation,
|
||||
"ok_callback_args": {
|
||||
"band_invitation_id": payload.band_invitation_id,
|
||||
"band_id": payload.band_id,
|
||||
"notification_id": payload.notification_id
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function registerTextMessage() {
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.TEXT_MESSAGE, function(header, payload) {
|
||||
logger.debug("Handling TEXT_MESSAGE msg " + JSON.stringify(payload));
|
||||
|
||||
textMessageDialog.messageReceived(payload);
|
||||
|
||||
handleNotification(payload, header.type);
|
||||
});
|
||||
}
|
||||
|
||||
function registerBandInvitationAccepted() {
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.BAND_INVITATION_ACCEPTED, function(header, payload) {
|
||||
logger.debug("Handling BAND_INVITATION_ACCEPTED msg " + JSON.stringify(payload));
|
||||
|
||||
handleNotification(payload, header.type);
|
||||
|
||||
app.notify({
|
||||
"title": "Band Invitation Accepted",
|
||||
"text": payload.msg,
|
||||
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// one important limitation; if the user is focused on an iframe, this will be false
|
||||
// however, if they are doing something with Facebook or the photo picker, this may actually still be desirable
|
||||
function userCanSeeNotifications(payload) {
|
||||
return document.hasFocus() && !app.layout.dialogObscuredNotification(payload);
|
||||
}
|
||||
|
||||
// default handler for incoming notification
|
||||
function handleNotification(payload, type) {
|
||||
|
||||
// on a load of notifications, it is possible to load a very new notification,
|
||||
// and get a websocket notification right after for that same notification,
|
||||
// so we need to protect against such duplicates
|
||||
if($list.find('li[notification-id="' + payload.notification_id + '"]').length > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// add notification to sidebar
|
||||
var template = $notificationTemplate.html();
|
||||
var notificationHtml = context.JK.fillTemplate(template, {
|
||||
notificationId: payload.notification_id,
|
||||
sessionId: payload.session_id,
|
||||
avatar_url: context.JK.resolveAvatarUrl(payload.photo_url),
|
||||
text: payload.msg instanceof jQuery ? payload.msg.html() : payload.msg ,
|
||||
date: $.timeago(payload.created_at)
|
||||
});
|
||||
|
||||
$list.prepend(notificationHtml);
|
||||
|
||||
onNotificationOccurred(payload);
|
||||
|
||||
initializeActions(payload, type);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
function onCreateJoinRequest(sessionId) {
|
||||
var joinRequest = {};
|
||||
joinRequest.music_session = sessionId;
|
||||
joinRequest.user = context.JK.currentUserId;
|
||||
rest.createJoinRequest(joinRequest)
|
||||
.done(function(response) {
|
||||
|
||||
}).error(context.JK.app.ajaxError);
|
||||
|
||||
context.JK.app.layout.closeDialog('alert');
|
||||
}
|
||||
|
||||
function approveJoinRequest(args) {
|
||||
rest.updateJoinRequest(args.join_request_id, true)
|
||||
.done(function(response) {
|
||||
deleteNotification(args.notification_id);
|
||||
}).error(app.ajaxError);
|
||||
}
|
||||
|
||||
function rejectJoinRequest(args) {
|
||||
rest.updateJoinRequest(args.join_request_id, false)
|
||||
.done(function(response) {
|
||||
deleteNotification(args.notification_id);
|
||||
}).error(app.ajaxError);
|
||||
}
|
||||
|
||||
function openTerms(args) {
|
||||
var termsDialog = new context.JK.TermsDialog(app, args, onTermsAccepted);
|
||||
termsDialog.initialize();
|
||||
app.layout.showDialog('terms');
|
||||
}
|
||||
|
||||
function onTermsAccepted(args) {
|
||||
deleteNotification(args.notification_id);
|
||||
context.location = '/client#/session/' + args.session_id;
|
||||
}
|
||||
|
||||
|
||||
function openAlert(sessionId) {
|
||||
var alertDialog = new context.JK.AlertDialog(context.JK.app, "YES",
|
||||
"You must be approved to join this session. Would you like to send a request to join?",
|
||||
sessionId, onCreateJoinRequest);
|
||||
|
||||
alertDialog.initialize();
|
||||
context.JK.app.layout.showDialog('alert');
|
||||
}
|
||||
|
||||
function listenToRecording(args) {
|
||||
deleteNotification(args.notification_id);
|
||||
context.JK.popExternalLink('/recordings/' + args.recording_id);
|
||||
}
|
||||
|
||||
function deleteNotificationHandler(evt) {
|
||||
evt.stopPropagation();
|
||||
var notificationId = $(this).attr('notification-id');
|
||||
deleteNotification(notificationId);
|
||||
}
|
||||
|
||||
function initialize(sidebarInstance, textMessageDialogInstance) {
|
||||
sidebar = sidebarInstance;
|
||||
textMessageDialog = textMessageDialogInstance;
|
||||
$panel = $('[layout-id="panelNotifications"]');
|
||||
$expanded = $panel.find('.panel.expanded');
|
||||
$contents = $panel.find('.panelcontents');
|
||||
$count = $panel.find('#sidebar-notification-count');
|
||||
$list = $panel.find('#sidebar-notification-list');
|
||||
$notificationTemplate = $('#template-notification-panel');
|
||||
if($panel.length == 0) throw "notifications panel not found"
|
||||
if($expanded.length == 0) throw "notifications expanded content not found"
|
||||
if($contents.length == 0) throw "notifications contents not found"
|
||||
if($count.length == 0) throw "notifications count element not found";
|
||||
if($list.length == 0) throw "notification list element not found";
|
||||
if($notificationTemplate.length == 0) throw "notification template not found";
|
||||
|
||||
events();
|
||||
|
||||
populate();
|
||||
};
|
||||
|
||||
this.initialize = initialize;
|
||||
this.onNotificationOccurred = onNotificationOccurred;
|
||||
};
|
||||
})(window, jQuery);
|
||||
|
|
@ -111,9 +111,7 @@
|
|||
// time, for that entire duration you'll still be sending voice data to the other users.
|
||||
// this may be bad if someone decides to badmouth others in the left-session during this time
|
||||
logger.debug("calling jamClient.LeaveSession for clientId=" + clientId);
|
||||
console.time('jamClient.LeaveSession');
|
||||
client.LeaveSession({ sessionID: currentSessionId });
|
||||
console.timeEnd('jamClient.LeaveSession');
|
||||
leaveSessionRest(currentSessionId)
|
||||
.done(function() {
|
||||
sessionChanged();
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@
|
|||
var rest = context.JK.Rest();
|
||||
var invitationDialog = null;
|
||||
var textMessageDialog = null;
|
||||
var notificationPanel = null;
|
||||
var me = null;
|
||||
|
||||
function initializeSearchPanel() {
|
||||
$('#search_text_type').change(function() {
|
||||
|
|
@ -88,204 +90,8 @@
|
|||
}
|
||||
|
||||
function initializeNotificationsPanel() {
|
||||
// retrieve pending notifications for this user
|
||||
var url = "/api/users/" + context.JK.currentUserId + "/notifications"
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
contentType: 'application/json',
|
||||
url: url,
|
||||
processData: false,
|
||||
success: function(response) {
|
||||
|
||||
updateNotificationList(response);
|
||||
|
||||
// set notification count
|
||||
$('#sidebar-notification-count').html(response.length);
|
||||
},
|
||||
error: app.ajaxError
|
||||
});
|
||||
}
|
||||
|
||||
function updateNotificationList(response) {
|
||||
$('#sidebar-notification-list').empty();
|
||||
|
||||
$.each(response, function(index, val) {
|
||||
|
||||
if(val.description == 'TEXT_MESSAGE') {
|
||||
val.formatted_msg = textMessageDialog.formatTextMessage(val.message.substring(0, 200), val.source_user_id, val.source_user.name, val.message.length > 200).html();
|
||||
}
|
||||
|
||||
// fill in template for Connect pre-click
|
||||
var template = $('#template-notification-panel').html();
|
||||
var notificationHtml = context.JK.fillTemplate(template, {
|
||||
notificationId: val.notification_id,
|
||||
sessionId: val.session_id,
|
||||
avatar_url: context.JK.resolveAvatarUrl(val.photo_url),
|
||||
text: val.formatted_msg,
|
||||
date: $.timeago(val.created_at)
|
||||
});
|
||||
|
||||
$('#sidebar-notification-list').append(notificationHtml);
|
||||
|
||||
// val.description contains the notification record's description value from the DB (i.e., type)
|
||||
initializeActions(val, val.description);
|
||||
});
|
||||
}
|
||||
|
||||
function initializeActions(payload, type) {
|
||||
|
||||
var $notification = $('li[notification-id=' + payload.notification_id + ']');
|
||||
var $btnNotificationAction = '#btn-notification-action';
|
||||
|
||||
// wire up "x" button to delete notification
|
||||
$notification.find('#img-delete-notification').click(deleteNotificationHandler);
|
||||
|
||||
// customize action buttons based on notification type
|
||||
if (type === context.JK.MessageType.FRIEND_REQUEST) {
|
||||
var $action_btn = $notification.find($btnNotificationAction);
|
||||
$action_btn.text('ACCEPT');
|
||||
$action_btn.click(function() {
|
||||
acceptFriendRequest(payload);
|
||||
});
|
||||
}
|
||||
|
||||
else if (type === context.JK.MessageType.FRIEND_REQUEST_ACCEPTED) {
|
||||
$notification.find('#div-actions').hide();
|
||||
}
|
||||
|
||||
else if (type === context.JK.MessageType.NEW_USER_FOLLOWER || type === context.JK.MessageType.NEW_BAND_FOLLOWER) {
|
||||
$notification.find('#div-actions').hide();
|
||||
}
|
||||
|
||||
else if (type === context.JK.MessageType.SESSION_INVITATION) {
|
||||
var $action_btn = $notification.find($btnNotificationAction);
|
||||
$action_btn.text('JOIN');
|
||||
$action_btn.click(function() {
|
||||
openTerms(payload);
|
||||
});
|
||||
}
|
||||
|
||||
else if (type === context.JK.MessageType.JOIN_REQUEST) {
|
||||
var $action_btn = $notification.find($btnNotificationAction);
|
||||
$action_btn.text('APPROVE');
|
||||
$action_btn.click(function() {
|
||||
approveJoinRequest(payload);
|
||||
});
|
||||
}
|
||||
|
||||
else if (type === context.JK.MessageType.JOIN_REQUEST_APPROVED) {
|
||||
var $action_btn = $notification.find($btnNotificationAction);
|
||||
$action_btn.text('JOIN');
|
||||
$action_btn.click(function() {
|
||||
openTerms(payload);
|
||||
});
|
||||
}
|
||||
|
||||
else if (type === context.JK.MessageType.JOIN_REQUEST_REJECTED) {
|
||||
$notification.find('#div-actions').hide();
|
||||
}
|
||||
|
||||
else if (type === context.JK.MessageType.MUSICIAN_SESSION_JOIN || type === context.JK.MessageType.BAND_SESSION_JOIN) {
|
||||
|
||||
var actionText = '';
|
||||
var callback;
|
||||
if (context.JK.currentUserMusician) {
|
||||
// user is MUSICIAN; musician_access = TRUE
|
||||
if (payload.musician_access) {
|
||||
actionText = "JOIN";
|
||||
callback = joinSession;
|
||||
}
|
||||
// user is MUSICIAN; fan_access = TRUE
|
||||
else if (payload.fan_access) {
|
||||
actionText = "LISTEN";
|
||||
callback = listenToSession;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// user is FAN; fan_access = TRUE
|
||||
if (payload.fan_access) {
|
||||
actionText = "LISTEN";
|
||||
callback = listenToSession;
|
||||
}
|
||||
}
|
||||
|
||||
var $action_btn = $notification.find($btnNotificationAction);
|
||||
$action_btn.text(actionText);
|
||||
$action_btn.click(function() {
|
||||
callback(payload);
|
||||
});
|
||||
}
|
||||
|
||||
else if (type === context.JK.MessageType.MUSICIAN_RECORDING_SAVED || type === context.JK.MessageType.BAND_RECORDING_SAVED) {
|
||||
var $action_btn = $notification.find($btnNotificationAction);
|
||||
$action_btn.text('LISTEN');
|
||||
$action_btn.click(function() {
|
||||
listenToRecording(payload);
|
||||
});
|
||||
}
|
||||
|
||||
else if (type === context.JK.MessageType.RECORDING_MASTER_MIX_COMPLETE) {
|
||||
$notification.find('#div-actions').hide();
|
||||
context.jamClient.OnDownloadAvailable(); // poke backend, letting it know a download is available
|
||||
}
|
||||
|
||||
else if (type === context.JK.MessageType.BAND_INVITATION) {
|
||||
var $action_btn = $notification.find($btnNotificationAction);
|
||||
$action_btn.text('ACCEPT');
|
||||
$action_btn.click(function() {
|
||||
acceptBandInvitation(payload);
|
||||
});
|
||||
}
|
||||
else if (type === context.JK.MessageType.BAND_INVITATION_ACCEPTED) {
|
||||
$notification.find('#div-actions').hide();
|
||||
}
|
||||
else if (type === context.JK.MessageType.TEXT_MESSAGE) {
|
||||
var $action_btn = $notification.find($btnNotificationAction);
|
||||
$action_btn.text('REPLY');
|
||||
$action_btn.click(function() {
|
||||
var userId = $notification.find('.more-text-available').attr('data-sender-id');
|
||||
app.layout.showDialog('text-message', { d1: userId });
|
||||
});
|
||||
|
||||
var moreTextLink = $notification.find('.more-text-available');
|
||||
var textMessage = $notification.find('.text-message');
|
||||
var clipped_msg = textMessage.attr('data-is-clipped') === 'true';
|
||||
|
||||
if(clipped_msg) {
|
||||
moreTextLink.text('more').show();
|
||||
moreTextLink.click(function(e) {
|
||||
var userId = $(this).attr('data-sender-id');
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
else {
|
||||
moreTextLink.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function deleteNotificationHandler(evt) {
|
||||
evt.stopPropagation();
|
||||
var notificationId = $(this).attr('notification-id');
|
||||
deleteNotification(notificationId);
|
||||
}
|
||||
|
||||
function deleteNotification(notificationId) {
|
||||
var url = "/api/users/" + context.JK.currentUserId + "/notifications/" + notificationId;
|
||||
$.ajax({
|
||||
type: "DELETE",
|
||||
dataType: "json",
|
||||
contentType: 'application/json',
|
||||
url: url,
|
||||
processData: false,
|
||||
success: function(response) {
|
||||
$('li[notification-id=' + notificationId + ']').hide();
|
||||
decrementNotificationCount();
|
||||
},
|
||||
error: app.ajaxError
|
||||
});
|
||||
notificationPanel = new context.JK.NotificationPanel(app);
|
||||
notificationPanel.initialize(me, textMessageDialog);
|
||||
}
|
||||
|
||||
function initializeChatPanel() {
|
||||
|
|
@ -334,51 +140,6 @@
|
|||
$('#sidebar-search-results').height('0px');
|
||||
}
|
||||
|
||||
function incrementNotificationCount() {
|
||||
var count = parseInt($('#sidebar-notification-count').html());
|
||||
$('#sidebar-notification-count').html(count + 1);
|
||||
}
|
||||
|
||||
function decrementNotificationCount() {
|
||||
var count = parseInt($('#sidebar-notification-count').html());
|
||||
if (count === 0) {
|
||||
$('#sidebar-notification-count').html(0);
|
||||
}
|
||||
else {
|
||||
$('#sidebar-notification-count').html(count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// default handler for incoming notification
|
||||
function handleNotification(payload, type) {
|
||||
|
||||
// on a load of notifications, it is possible to load a very new notification,
|
||||
// and get a websocket notification right after for that same notification,
|
||||
// so we need to protect against such duplicates
|
||||
if($('#sidebar-notification-list').find('li[notification-id="' + payload.notification_id + '"]').length > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// increment displayed notification count
|
||||
incrementNotificationCount();
|
||||
|
||||
// add notification to sidebar
|
||||
var template = $("#template-notification-panel").html();
|
||||
var notificationHtml = context.JK.fillTemplate(template, {
|
||||
notificationId: payload.notification_id,
|
||||
sessionId: payload.session_id,
|
||||
avatar_url: context.JK.resolveAvatarUrl(payload.photo_url),
|
||||
text: payload.msg instanceof jQuery ? payload.msg.html() : payload.msg ,
|
||||
date: $.timeago(payload.created_at)
|
||||
});
|
||||
|
||||
$('#sidebar-notification-list').prepend(notificationHtml);
|
||||
|
||||
initializeActions(payload, type);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
var delay = (function(){
|
||||
var timer = 0;
|
||||
return function(callback, ms) {
|
||||
|
|
@ -436,32 +197,14 @@
|
|||
|
||||
// friend notifications
|
||||
registerFriendUpdate();
|
||||
registerFriendRequest();
|
||||
registerFriendRequestAccepted();
|
||||
registerNewUserFollower();
|
||||
registerNewBandFollower();
|
||||
|
||||
// session invitations
|
||||
registerSessionInvitation();
|
||||
registerSessionEnded();
|
||||
registerJoinRequest();
|
||||
registerJoinRequestApproved();
|
||||
registerJoinRequestRejected();
|
||||
registerSessionJoin();
|
||||
registerSessionDepart();
|
||||
registerMusicianSessionJoin();
|
||||
registerBandSessionJoin();
|
||||
|
||||
// recording notifications
|
||||
registerMusicianRecordingSaved();
|
||||
registerBandRecordingSaved();
|
||||
registerRecordingStarted();
|
||||
registerRecordingEnded();
|
||||
registerRecordingMasterMixComplete();
|
||||
|
||||
// band notifications
|
||||
registerBandInvitation();
|
||||
registerBandInvitationAccepted();
|
||||
|
||||
// broadcast notifications
|
||||
registerSourceUpRequested();
|
||||
|
|
@ -469,9 +212,6 @@
|
|||
registerSourceUp();
|
||||
registerSourceDown();
|
||||
|
||||
// register text messages
|
||||
registerTextMessage();
|
||||
|
||||
// watch for Invite More Users events
|
||||
$('#sidebar-div .btn-email-invitation').click(function() {
|
||||
invitationDialog.showEmailDialog();
|
||||
|
|
@ -505,210 +245,6 @@
|
|||
});
|
||||
}
|
||||
|
||||
function registerFriendRequest() {
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.FRIEND_REQUEST, function(header, payload) {
|
||||
logger.debug("Handling FRIEND_REQUEST msg " + JSON.stringify(payload));
|
||||
|
||||
handleNotification(payload, header.type);
|
||||
|
||||
app.notify({
|
||||
"title": "New Friend Request",
|
||||
"text": payload.msg,
|
||||
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
|
||||
}, {
|
||||
"ok_text": "ACCEPT",
|
||||
"ok_callback": acceptFriendRequest,
|
||||
"ok_callback_args": { "friend_request_id": payload.friend_request_id, "notification_id": payload.notification_id }
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function acceptFriendRequest(args) {
|
||||
|
||||
rest.acceptFriendRequest({
|
||||
status: 'accept',
|
||||
friend_request_id: args.friend_request_id
|
||||
}).done(function(response) {
|
||||
deleteNotification(args.notification_id); // delete notification corresponding to this friend request
|
||||
initializeFriendsPanel(); // refresh friends panel when request is accepted
|
||||
}).error(app.ajaxError);
|
||||
}
|
||||
|
||||
function registerFriendRequestAccepted() {
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.FRIEND_REQUEST_ACCEPTED, function(header, payload) {
|
||||
logger.debug("Handling FRIEND_REQUEST_ACCEPTED msg " + JSON.stringify(payload));
|
||||
|
||||
handleNotification(payload, header.type);
|
||||
|
||||
initializeFriendsPanel();
|
||||
|
||||
app.notify({
|
||||
"title": "Friend Request Accepted",
|
||||
"text": payload.msg,
|
||||
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function registerNewUserFollower() {
|
||||
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.NEW_USER_FOLLOWER, function(header, payload) {
|
||||
logger.debug("Handling NEW_USER_FOLLOWER msg " + JSON.stringify(payload));
|
||||
|
||||
handleNotification(payload, header.type);
|
||||
|
||||
app.notify({
|
||||
"title": "New Follower",
|
||||
"text": payload.msg,
|
||||
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function registerNewBandFollower() {
|
||||
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.NEW_BAND_FOLLOWER, function(header, payload) {
|
||||
logger.debug("Handling NEW_BAND_FOLLOWER msg " + JSON.stringify(payload));
|
||||
|
||||
handleNotification(payload, header.type);
|
||||
|
||||
app.notify({
|
||||
"title": "New Band Follower",
|
||||
"text": payload.msg,
|
||||
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function registerSessionInvitation() {
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SESSION_INVITATION, function(header, payload) {
|
||||
logger.debug("Handling SESSION_INVITATION msg " + JSON.stringify(payload));
|
||||
|
||||
handleNotification(payload, header.type);
|
||||
|
||||
var participants = [];
|
||||
rest.getSession(payload.session_id).done(function(response) {
|
||||
$.each(response.participants, function(index, val) {
|
||||
participants.push({"photo_url": context.JK.resolveAvatarUrl(val.user.photo_url), "name": val.user.name});
|
||||
});
|
||||
|
||||
var participantHtml = "You have been invited to join a session with: <br/><br/>";
|
||||
participantHtml += "<table><tbody>";
|
||||
|
||||
$.each(participants, function(index, val) {
|
||||
if (index < 4) {
|
||||
participantHtml += "<tr><td><img class='avatar-small' src='" + context.JK.resolveAvatarUrl(val.photo_url) + "' /></td><td>" + val.name + "</td></tr>";
|
||||
}
|
||||
});
|
||||
|
||||
participantHtml += "</tbody></table>";
|
||||
|
||||
app.notify({
|
||||
"title": "Session Invitation",
|
||||
"text": participantHtml
|
||||
}, {
|
||||
"ok_text": "JOIN SESSION",
|
||||
"ok_callback": openTerms,
|
||||
"ok_callback_args": { "session_id": payload.session_id, "notification_id": payload.notification_id }
|
||||
});
|
||||
}).error(app.ajaxError);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function openTerms(args) {
|
||||
var termsDialog = new context.JK.TermsDialog(app, args, onTermsAccepted);
|
||||
termsDialog.initialize();
|
||||
app.layout.showDialog('terms');
|
||||
}
|
||||
|
||||
function onTermsAccepted(args) {
|
||||
deleteNotification(args.notification_id);
|
||||
context.location = '/client#/session/' + args.session_id;
|
||||
}
|
||||
|
||||
function registerSessionEnded() {
|
||||
// TODO: this should clean up all notifications related to this session
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SESSION_ENDED, function(header, payload) {
|
||||
logger.debug("Handling SESSION_ENDED msg " + JSON.stringify(payload));
|
||||
deleteSessionNotifications(payload.session_id);
|
||||
});
|
||||
}
|
||||
|
||||
// remove all notifications for this session
|
||||
function deleteSessionNotifications(sessionId) {
|
||||
$('li[session-id=' + sessionId + ']').hide();
|
||||
decrementNotificationCount();
|
||||
}
|
||||
|
||||
function registerJoinRequest() {
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.JOIN_REQUEST, function(header, payload) {
|
||||
logger.debug("Handling JOIN_REQUEST msg " + JSON.stringify(payload));
|
||||
|
||||
handleNotification(payload, header.type);
|
||||
|
||||
app.notify({
|
||||
"title": "New Join Request",
|
||||
"text": payload.msg,
|
||||
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
|
||||
}, {
|
||||
"ok_text": "APPROVE",
|
||||
"ok_callback": approveJoinRequest,
|
||||
"ok_callback_args": { "join_request_id": payload.join_request_id, "notification_id": payload.notification_id },
|
||||
"cancel_text": "REJECT",
|
||||
"cancel_callback": rejectJoinRequest,
|
||||
"cancel_callback_args": { "join_request_id": payload.join_request_id, "notification_id": payload.notification_id }
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function approveJoinRequest(args) {
|
||||
rest.updateJoinRequest(args.join_request_id, true)
|
||||
.done(function(response) {
|
||||
deleteNotification(args.notification_id);
|
||||
}).error(app.ajaxError);
|
||||
}
|
||||
|
||||
function rejectJoinRequest(args) {
|
||||
rest.updateJoinRequest(args.join_request_id, false)
|
||||
.done(function(response) {
|
||||
deleteNotification(args.notification_id);
|
||||
}).error(app.ajaxError);
|
||||
}
|
||||
|
||||
function registerJoinRequestApproved() {
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.JOIN_REQUEST_APPROVED, function(header, payload) {
|
||||
logger.debug("Handling JOIN_REQUEST_APPROVED msg " + JSON.stringify(payload));
|
||||
|
||||
handleNotification(payload, header.type);
|
||||
|
||||
app.notify({
|
||||
"title": "Join Request Approved",
|
||||
"text": payload.msg,
|
||||
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
|
||||
}, {
|
||||
"ok_text": "JOIN SESSION",
|
||||
"ok_callback": openTerms,
|
||||
"ok_callback_args": { "session_id": payload.session_id, "notification_id": payload.notification_id }
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function registerJoinRequestRejected() {
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.JOIN_REQUEST_REJECTED, function(header, payload) {
|
||||
logger.debug("Handling JOIN_REQUEST_REJECTED msg " + JSON.stringify(payload));
|
||||
|
||||
handleNotification(payload, header.type);
|
||||
|
||||
app.notify({
|
||||
"title": "Join Request Rejected",
|
||||
"text": payload.msg,
|
||||
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function registerSessionJoin() {
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SESSION_JOIN, function(header, payload) {
|
||||
logger.debug("Handling SESSION_JOIN msg " + JSON.stringify(payload));
|
||||
|
|
@ -741,201 +277,6 @@
|
|||
});
|
||||
}
|
||||
|
||||
function registerMusicianSessionJoin() {
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_JOIN, function(header, payload) {
|
||||
logger.debug("Handling MUSICIAN_SESSION_JOIN msg " + JSON.stringify(payload));
|
||||
|
||||
var okText = '';
|
||||
var showNotification = false;
|
||||
var callback;
|
||||
if (context.JK.currentUserMusician) {
|
||||
// user is MUSICIAN; musician_access = TRUE
|
||||
if (payload.musician_access) {
|
||||
showNotification = true;
|
||||
okText = "JOIN";
|
||||
callback = joinSession;
|
||||
}
|
||||
// user is MUSICIAN; fan_access = TRUE
|
||||
else if (payload.fan_access) {
|
||||
showNotification = true;
|
||||
okText = "LISTEN";
|
||||
callback = listenToSession;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// user is FAN; fan_access = TRUE
|
||||
if (payload.fan_access) {
|
||||
showNotification = true;
|
||||
okText = "LISTEN";
|
||||
callback = listenToSession;
|
||||
}
|
||||
}
|
||||
|
||||
if (showNotification) {
|
||||
handleNotification(payload, header.type);
|
||||
|
||||
app.notify({
|
||||
"title": "Musician Joined Session",
|
||||
"text": payload.msg,
|
||||
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
|
||||
}, {
|
||||
"ok_text": okText,
|
||||
"ok_callback": callback,
|
||||
"ok_callback_args": {
|
||||
"session_id": payload.session_id,
|
||||
"fan_access": payload.fan_access,
|
||||
"musician_access": payload.musician_access,
|
||||
"approval_required": payload.approval_required,
|
||||
"notification_id": payload.notification_id
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function registerBandSessionJoin() {
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.BAND_SESSION_JOIN, function(header, payload) {
|
||||
logger.debug("Handling BAND_SESSION_JOIN msg " + JSON.stringify(payload));
|
||||
|
||||
var okText = '';
|
||||
var showNotification = false;
|
||||
var callback;
|
||||
if (context.JK.currentUserMusician) {
|
||||
// user is MUSICIAN; musician_access = TRUE
|
||||
if (payload.musician_access) {
|
||||
showNotification = true;
|
||||
okText = "JOIN";
|
||||
callback = joinSession;
|
||||
}
|
||||
// user is MUSICIAN; fan_access = TRUE
|
||||
else if (payload.fan_access) {
|
||||
showNotification = true;
|
||||
okText = "LISTEN";
|
||||
callback = listenToSession;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// user is FAN; fan_access = TRUE
|
||||
if (payload.fan_access) {
|
||||
showNotification = true;
|
||||
okText = "LISTEN";
|
||||
callback = listenToSession;
|
||||
}
|
||||
}
|
||||
|
||||
if (showNotification) {
|
||||
handleNotification(payload, header.type);
|
||||
|
||||
app.notify({
|
||||
"title": "Band Joined Session",
|
||||
"text": payload.msg,
|
||||
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
|
||||
}, {
|
||||
"ok_text": "LISTEN",
|
||||
"ok_callback": callback,
|
||||
"ok_callback_args": {
|
||||
"session_id": payload.session_id,
|
||||
"fan_access": payload.fan_access,
|
||||
"musician_access": payload.musician_access,
|
||||
"approval_required": payload.approval_required,
|
||||
"notification_id": payload.notification_id
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function listenToSession(args) {
|
||||
deleteNotification(args.notification_id);
|
||||
context.JK.popExternalLink('/sessions/' + args.session_id);
|
||||
}
|
||||
|
||||
/*********** TODO: THE NEXT 3 FUNCTIONS ARE COPIED FROM sessionList.js. REFACTOR TO COMMON PLACE. *************/
|
||||
function joinSession(args) {
|
||||
// NOTE: invited musicians get their own notification, so no need to check if user has invitation here
|
||||
// like other places because an invited user would never get this notification
|
||||
if (args.musician_access) {
|
||||
if (args.approval_required) {
|
||||
openAlert(args.session_id);
|
||||
}
|
||||
else {
|
||||
openTerms(args);
|
||||
}
|
||||
}
|
||||
deleteNotification(args.notification_id);
|
||||
}
|
||||
|
||||
function openAlert(sessionId) {
|
||||
var alertDialog = new context.JK.AlertDialog(context.JK.app, "YES",
|
||||
"You must be approved to join this session. Would you like to send a request to join?",
|
||||
sessionId, onCreateJoinRequest);
|
||||
|
||||
alertDialog.initialize();
|
||||
context.JK.app.layout.showDialog('alert');
|
||||
}
|
||||
|
||||
function onCreateJoinRequest(sessionId) {
|
||||
var joinRequest = {};
|
||||
joinRequest.music_session = sessionId;
|
||||
joinRequest.user = context.JK.currentUserId;
|
||||
rest.createJoinRequest(joinRequest)
|
||||
.done(function(response) {
|
||||
|
||||
}).error(context.JK.app.ajaxError);
|
||||
|
||||
context.JK.app.layout.closeDialog('alert');
|
||||
}
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function registerMusicianRecordingSaved() {
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.MUSICIAN_RECORDING_SAVED, function(header, payload) {
|
||||
logger.debug("Handling MUSICIAN_RECORDING_SAVED msg " + JSON.stringify(payload));
|
||||
|
||||
handleNotification(payload, header.type);
|
||||
|
||||
app.notify({
|
||||
"title": "Musician Recording Saved",
|
||||
"text": payload.msg,
|
||||
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
|
||||
}, {
|
||||
"ok_text": "LISTEN",
|
||||
"ok_callback": listenToRecording,
|
||||
"ok_callback_args": {
|
||||
"recording_id": payload.recording_id,
|
||||
"notification_id": payload.notification_id
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function registerBandRecordingSaved() {
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.BAND_RECORDING_SAVED, function(header, payload) {
|
||||
logger.debug("Handling BAND_RECORDING_SAVED msg " + JSON.stringify(payload));
|
||||
|
||||
handleNotification(payload, header.type);
|
||||
|
||||
app.notify({
|
||||
"title": "Band Recording Saved",
|
||||
"text": payload.msg,
|
||||
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
|
||||
}, {
|
||||
"ok_text": "LISTEN",
|
||||
"ok_callback": listenToRecording,
|
||||
"ok_callback_args": {
|
||||
"recording_id": payload.recording_id,
|
||||
"notification_id": payload.notification_id
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function listenToRecording(args) {
|
||||
deleteNotification(args.notification_id);
|
||||
context.JK.popExternalLink('/recordings/' + args.recording_id);
|
||||
}
|
||||
|
||||
function registerRecordingStarted() {
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.RECORDING_STARTED, function(header, payload) {
|
||||
logger.debug("Handling RECORDING_STARTED msg " + JSON.stringify(payload));
|
||||
|
|
@ -960,86 +301,6 @@
|
|||
});
|
||||
}
|
||||
|
||||
function registerRecordingMasterMixComplete() {
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.RECORDING_MASTER_MIX_COMPLETE, function(header, payload) {
|
||||
logger.debug("Handling RECORDING_MASTER_MIX_COMPLETE msg " + JSON.stringify(payload));
|
||||
|
||||
handleNotification(payload, header.type);
|
||||
|
||||
app.notify({
|
||||
"title": "Recording Master Mix Complete",
|
||||
"text": payload.msg,
|
||||
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
|
||||
}, {
|
||||
"ok_text": "SHARE",
|
||||
"ok_callback": shareRecording,
|
||||
"ok_callback_args": {
|
||||
"recording_id": payload.recording_id
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function shareRecording(args) {
|
||||
var recordingId = args.recording_id;
|
||||
}
|
||||
|
||||
function registerBandInvitation() {
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.BAND_INVITATION, function(header, payload) {
|
||||
logger.debug("Handling BAND_INVITATION msg " + JSON.stringify(payload));
|
||||
|
||||
handleNotification(payload, header.type);
|
||||
|
||||
app.notify({
|
||||
"title": "Band Invitation",
|
||||
"text": payload.msg,
|
||||
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
|
||||
}, {
|
||||
"ok_text": "ACCEPT",
|
||||
"ok_callback": acceptBandInvitation,
|
||||
"ok_callback_args": {
|
||||
"band_invitation_id": payload.band_invitation_id,
|
||||
"band_id": payload.band_id,
|
||||
"notification_id": payload.notification_id
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function acceptBandInvitation(args) {
|
||||
rest.updateBandInvitation(
|
||||
args.band_id,
|
||||
args.band_invitation_id,
|
||||
true
|
||||
).done(function(response) {
|
||||
deleteNotification(args.notification_id); // delete notification corresponding to this friend request
|
||||
}).error(app.ajaxError);
|
||||
}
|
||||
|
||||
function registerTextMessage() {
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.TEXT_MESSAGE, function(header, payload) {
|
||||
logger.debug("Handling TEXT_MESSAGE msg " + JSON.stringify(payload));
|
||||
|
||||
textMessageDialog.messageReceived(payload);
|
||||
|
||||
handleNotification(payload, header.type);
|
||||
});
|
||||
}
|
||||
|
||||
function registerBandInvitationAccepted() {
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.BAND_INVITATION_ACCEPTED, function(header, payload) {
|
||||
logger.debug("Handling BAND_INVITATION_ACCEPTED msg " + JSON.stringify(payload));
|
||||
|
||||
handleNotification(payload, header.type);
|
||||
|
||||
app.notify({
|
||||
"title": "Band Invitation Accepted",
|
||||
"text": payload.msg,
|
||||
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function registerSourceUpRequested() {
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SOURCE_UP_REQUESTED, function(header, payload) {
|
||||
|
||||
|
|
@ -1146,13 +407,14 @@
|
|||
}
|
||||
|
||||
this.initialize = function(invitationDialogInstance, textMessageDialogInstance) {
|
||||
me = this;
|
||||
invitationDialog = invitationDialogInstance;
|
||||
textMessageDialog = textMessageDialogInstance;
|
||||
events();
|
||||
initializeSearchPanel();
|
||||
initializeFriendsPanel();
|
||||
initializeChatPanel();
|
||||
initializeNotificationsPanel();
|
||||
invitationDialog = invitationDialogInstance;
|
||||
textMessageDialog = textMessageDialogInstance;
|
||||
};
|
||||
|
||||
this.refreshFriends = refreshFriends;
|
||||
|
|
|
|||
|
|
@ -47,8 +47,7 @@
|
|||
return message;
|
||||
}
|
||||
|
||||
function sendMessage(e) {
|
||||
|
||||
function sendMessage() {
|
||||
var msg = $textBox.val();
|
||||
if(!msg || msg == '') {
|
||||
// don't bother the server with empty messages
|
||||
|
|
@ -124,6 +123,15 @@
|
|||
return markedUpMsg;
|
||||
}
|
||||
|
||||
// we handled the notification, meaning the dialog showed this message as a chat message
|
||||
function handledNotification(payload) {
|
||||
return showing && payload.description == "TEXT_MESSAGE" && payload.sender_id == otherId;
|
||||
}
|
||||
|
||||
function afterShow(args) {
|
||||
$textBox.focus();
|
||||
}
|
||||
|
||||
function beforeShow(args) {
|
||||
|
||||
app.layout.closeDialog('text-message') // ensure no others are showing. this is a singleton dialog
|
||||
|
|
@ -175,13 +183,38 @@
|
|||
reset();
|
||||
}
|
||||
|
||||
function postMessage(e) {
|
||||
function pasteIntoInput(el, text) {
|
||||
el.focus();
|
||||
if (typeof el.selectionStart == "number"
|
||||
&& typeof el.selectionEnd == "number") {
|
||||
var val = el.value;
|
||||
var selStart = el.selectionStart;
|
||||
el.value = val.slice(0, selStart) + text + val.slice(el.selectionEnd);
|
||||
el.selectionEnd = el.selectionStart = selStart + text.length;
|
||||
} else if (typeof document.selection != "undefined") {
|
||||
var textRange = document.selection.createRange();
|
||||
textRange.text = text;
|
||||
textRange.collapse(false);
|
||||
textRange.select();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
function handleEnter(evt) {
|
||||
if (evt.keyCode == 13 && evt.shiftKey) {
|
||||
pasteIntoInput(this, "\n");
|
||||
evt.preventDefault();
|
||||
}
|
||||
else if(evt.keyCode == 13 && !evt.shiftKey){
|
||||
sendMessage();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function events() {
|
||||
$form.submit(postMessage)
|
||||
$form.submit(sendMessage)
|
||||
|
||||
// http://stackoverflow.com/questions/6014702/how-do-i-detect-shiftenter-and-generate-a-new-line-in-textarea
|
||||
$textBox.keydown(handleEnter);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -234,6 +267,7 @@
|
|||
function initialize() {
|
||||
var dialogBindings = {
|
||||
'beforeShow' : beforeShow,
|
||||
'afterShow' : afterShow,
|
||||
'afterHide': afterHide
|
||||
};
|
||||
|
||||
|
|
@ -253,6 +287,7 @@
|
|||
this.initialize = initialize;
|
||||
this.messageReceived = messageReceived;
|
||||
this.formatTextMessage = formatTextMessage;
|
||||
this.handledNotification = handledNotification;
|
||||
}
|
||||
|
||||
return this;
|
||||
|
|
|
|||
|
|
@ -153,9 +153,9 @@
|
|||
$element.bt(text, options);
|
||||
}
|
||||
|
||||
context.JK.bindHoverEvents = function ($parent) {
|
||||
context.JK.bindHoverEvents = function ($parent) {
|
||||
|
||||
if(!$parent) {
|
||||
if (!$parent) {
|
||||
$parent = $('body');
|
||||
}
|
||||
|
||||
|
|
@ -328,9 +328,9 @@
|
|||
}
|
||||
|
||||
// creates an array with entries like [{ id: "drums", description: "Drums"}, ]
|
||||
context.JK.listInstruments = function() {
|
||||
context.JK.listInstruments = function () {
|
||||
var instrumentArray = [];
|
||||
$.each(context.JK.server_to_client_instrument_map, function(key, val) {
|
||||
$.each(context.JK.server_to_client_instrument_map, function (key, val) {
|
||||
instrumentArray.push({"id": context.JK.server_to_client_instrument_map[key].client_id, "description": key});
|
||||
});
|
||||
return instrumentArray;
|
||||
|
|
@ -652,22 +652,22 @@
|
|||
return hasFlash;
|
||||
}
|
||||
|
||||
context.JK.hasOneConfiguredDevice = function() {
|
||||
context.JK.hasOneConfiguredDevice = function () {
|
||||
var result = context.jamClient.FTUEGetGoodConfigurationList();
|
||||
logger.debug("hasOneConfiguredDevice: ", result);
|
||||
return result.length > 0;
|
||||
};
|
||||
|
||||
context.JK.getGoodAudioConfigs = function() {
|
||||
context.JK.getGoodAudioConfigs = function () {
|
||||
var result = context.jamClient.FTUEGetGoodAudioConfigurations();
|
||||
logger.debug("goodAudioConfigs=%o", result);
|
||||
return result;
|
||||
};
|
||||
|
||||
context.JK.getGoodConfigMap = function() {
|
||||
context.JK.getGoodConfigMap = function () {
|
||||
var goodConfigMap = [];
|
||||
var goodConfigs = context.JK.getGoodAudioConfigs();
|
||||
$.each(goodConfigs, function(index, profileKey) {
|
||||
$.each(goodConfigs, function (index, profileKey) {
|
||||
var friendlyName = context.jamClient.FTUEGetConfigurationDevice(profileKey);
|
||||
goodConfigMap.push({key: profileKey, name: friendlyName});
|
||||
});
|
||||
|
|
@ -675,12 +675,12 @@
|
|||
return goodConfigMap;
|
||||
}
|
||||
|
||||
context.JK.getBadAudioConfigs = function() {
|
||||
context.JK.getBadAudioConfigs = function () {
|
||||
var badAudioConfigs = [];
|
||||
var allAudioConfigs = context.jamClient.FTUEGetAllAudioConfigurations();
|
||||
var goodAudioConfigs = context.JK.getGoodAudioConfigs();
|
||||
|
||||
for (var i=0; i < allAudioConfigs.length; i++) {
|
||||
for (var i = 0; i < allAudioConfigs.length; i++) {
|
||||
if ($.inArray(allAudioConfigs[i], goodAudioConfigs) === -1) {
|
||||
badAudioConfigs.push(allAudioConfigs[i]);
|
||||
}
|
||||
|
|
@ -689,10 +689,10 @@
|
|||
return badAudioConfigs;
|
||||
};
|
||||
|
||||
context.JK.getBadConfigMap = function() {
|
||||
context.JK.getBadConfigMap = function () {
|
||||
var badConfigMap = [];
|
||||
var badConfigs = context.JK.getBadAudioConfigs();
|
||||
$.each(badConfigs, function(index, profileKey) {
|
||||
$.each(badConfigs, function (index, profileKey) {
|
||||
var friendlyName = context.jamClient.FTUEGetConfigurationDevice(profileKey);
|
||||
badConfigMap.push({key: profileKey, name: friendlyName});
|
||||
});
|
||||
|
|
@ -700,7 +700,7 @@
|
|||
return badConfigMap;
|
||||
}
|
||||
|
||||
context.JK.getFirstGoodDevice = function(preferredDeviceId) {
|
||||
context.JK.getFirstGoodDevice = function (preferredDeviceId) {
|
||||
var badConfigs = context.JK.getBadAudioConfigs();
|
||||
|
||||
function getGoodDevice() {
|
||||
|
|
@ -713,7 +713,7 @@
|
|||
}
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
|
||||
var deviceId = null;
|
||||
|
||||
if (preferredDeviceId) {
|
||||
|
|
@ -724,7 +724,7 @@
|
|||
}
|
||||
else {
|
||||
deviceId = getGoodDevice();
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
deviceId = getGoodDevice();
|
||||
|
|
@ -733,15 +733,22 @@
|
|||
}
|
||||
|
||||
// returns /client#/home for http://www.jamkazam.com/client#/home
|
||||
context.JK.locationPath = function() {
|
||||
context.JK.locationPath = function () {
|
||||
var bits = context.location.href.split('/');
|
||||
return '/' + bits.slice(3).join('/');
|
||||
}
|
||||
|
||||
context.JK.nowUTC = function() {
|
||||
context.JK.nowUTC = function () {
|
||||
var d = new Date();
|
||||
return new Date( d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds() );
|
||||
return new Date(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds());
|
||||
}
|
||||
|
||||
context.JK.guardAgainstBrowser = function(app) {
|
||||
if(!gon.isNativeClient) {
|
||||
app.layout.showDialog('launch-app-dialog')
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
|
||||
* Digest Algorithm, as defined in RFC 1321.
|
||||
|
|
|
|||
|
|
@ -78,8 +78,8 @@
|
|||
var clicked = $(this);
|
||||
var href = clicked.attr('href');
|
||||
if(href != "#") {
|
||||
context.JK.GA.trackDownload(clicked.attr('data-platform'));
|
||||
rest.userDownloadedClient().always(function() {
|
||||
context.JK.GA.trackDownload(clicked.attr('data-platform'));
|
||||
$('body').append('<iframe class="downloading" src="' + clicked.attr('href') + '" style="display:none"/>')
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
//= require jquery.dotdotdot
|
||||
//= require jquery.listenbroadcast
|
||||
//= require jquery.listenRecording
|
||||
//= require jquery.browser
|
||||
//= require jquery.custom-protocol
|
||||
//= require jquery.ba-bbq
|
||||
//= require AAA_Log
|
||||
//= require AAC_underscore
|
||||
|
|
|
|||
|
|
@ -61,17 +61,6 @@
|
|||
});
|
||||
$('.carousel').show()
|
||||
|
||||
|
||||
$.each($('.feed-entry'), function (index, feedEntry) {
|
||||
var $feedEntry = $(this);
|
||||
if ($feedEntry.is('.recording-entry')) {
|
||||
new context.JK.FeedItemRecording($feedEntry);
|
||||
}
|
||||
else {
|
||||
new context.JK.FeedItemSession($feedEntry);
|
||||
}
|
||||
})
|
||||
|
||||
context.JK.TickDuration('.feed-entry.music-session-history-entry .inprogress .tick-duration');
|
||||
|
||||
if ($.QueryString['showVideo']) {
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@
|
|||
*= require ./leaveSessionWarning
|
||||
*= require ./textMessageDialog
|
||||
*= require ./acceptFriendRequestDialog
|
||||
*= require ./launchAppDialog
|
||||
*= require ./terms
|
||||
*= require ./createSession
|
||||
*= require ./feed
|
||||
|
|
|
|||
|
|
@ -8,6 +8,11 @@
|
|||
min-width: 400px;
|
||||
min-height: 350px;
|
||||
z-index: 100;
|
||||
|
||||
h2 {
|
||||
font-size:20px;
|
||||
font-weight:300;
|
||||
}
|
||||
}
|
||||
|
||||
.thin-dialog {
|
||||
|
|
|
|||
|
|
@ -604,7 +604,7 @@ table.audiogeartable {
|
|||
left:10px;
|
||||
width:150px;
|
||||
height:18px;
|
||||
background-image:url(../images/content/bkg_slider_gain_horiz.png);
|
||||
background-image:url(/assets/content/bkg_slider_gain_horiz.png);
|
||||
background-repeat:repeat-x;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,76 +1,77 @@
|
|||
.bubble {
|
||||
width:350px;
|
||||
min-height:200px;
|
||||
background-color:#242323;
|
||||
border:solid 1px #ed3618;
|
||||
position:absolute;
|
||||
z-index:999;
|
||||
}
|
||||
width:350px;
|
||||
min-height:200px;
|
||||
background-color:#242323;
|
||||
border:solid 1px #ed3618;
|
||||
position:absolute;
|
||||
z-index:999;
|
||||
|
||||
.bubble.musician-bubble {
|
||||
width:410px;
|
||||
}
|
||||
|
||||
.bubble h2 {
|
||||
padding:6px 0px;
|
||||
text-align:center;
|
||||
font-size:15px;
|
||||
font-weight:200;
|
||||
width:100%;
|
||||
background-color:#ed3618;
|
||||
}
|
||||
&.musician-bubble {
|
||||
width:410px;
|
||||
}
|
||||
|
||||
.bubble h3 {
|
||||
font-weight:400;
|
||||
font-size:16px;
|
||||
color:#fff;
|
||||
}
|
||||
|
||||
.bubble-inner {
|
||||
padding:10px;
|
||||
color:#ccc;
|
||||
}
|
||||
|
||||
.bubble-inner div.mb {
|
||||
margin-bottom:5px;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight:600 !important;
|
||||
}
|
||||
|
||||
.musicians {
|
||||
margin-top:-3px;
|
||||
font-size:11px;
|
||||
}
|
||||
|
||||
.musicians td {
|
||||
border-right:none;
|
||||
border-top:none;
|
||||
padding:3px;
|
||||
vertical-align:middle;
|
||||
}
|
||||
|
||||
.musicians a {
|
||||
color:#fff;
|
||||
text-decoration:none;
|
||||
}
|
||||
|
||||
.avatar-tiny {
|
||||
float:left;
|
||||
padding:1px;
|
||||
width:24px;
|
||||
height:24px;
|
||||
h2 {
|
||||
padding:6px 0px;
|
||||
text-align:center;
|
||||
font-size:15px;
|
||||
font-weight:200;
|
||||
width:100%;
|
||||
background-color:#ed3618;
|
||||
-webkit-border-radius:12px;
|
||||
-moz-border-radius:12px;
|
||||
border-radius:12px;
|
||||
}
|
||||
}
|
||||
|
||||
.avatar-tiny img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
-webkit-border-radius:12px;
|
||||
-moz-border-radius:12px;
|
||||
border-radius:12px;
|
||||
h3 {
|
||||
font-weight:400;
|
||||
font-size:16px;
|
||||
color:#fff;
|
||||
}
|
||||
|
||||
.bubble-inner {
|
||||
padding:10px;
|
||||
color:#ccc;
|
||||
}
|
||||
|
||||
.bubble-inner div.mb {
|
||||
margin-bottom:5px;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight:600 !important;
|
||||
}
|
||||
|
||||
.musicians {
|
||||
margin-top:-3px;
|
||||
font-size:11px;
|
||||
}
|
||||
|
||||
.musicians td {
|
||||
border-right:none;
|
||||
border-top:none;
|
||||
padding:3px;
|
||||
vertical-align:middle;
|
||||
}
|
||||
|
||||
.musicians a {
|
||||
color:#fff;
|
||||
text-decoration:none;
|
||||
}
|
||||
|
||||
.avatar-tiny {
|
||||
float:left;
|
||||
padding:1px;
|
||||
width:24px;
|
||||
height:24px;
|
||||
background-color:#ed3618;
|
||||
-webkit-border-radius:12px;
|
||||
-moz-border-radius:12px;
|
||||
border-radius:12px;
|
||||
}
|
||||
|
||||
.avatar-tiny img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
-webkit-border-radius:12px;
|
||||
-moz-border-radius:12px;
|
||||
border-radius:12px;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
#launch-app-dialog {
|
||||
min-height:150px;
|
||||
|
||||
.dont-see-it {
|
||||
display:none;
|
||||
}
|
||||
|
||||
p {
|
||||
margin:10px 0;
|
||||
line-height:1em;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
margin:20px 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -219,7 +219,7 @@
|
|||
|
||||
.share-message {
|
||||
width: 100%;
|
||||
padding:0;
|
||||
padding:4px;
|
||||
}
|
||||
|
||||
.error-msg {
|
||||
|
|
|
|||
|
|
@ -42,6 +42,10 @@
|
|||
-webkit-border-radius:50%;
|
||||
-moz-border-radius:50%;
|
||||
border-radius:50%;
|
||||
|
||||
&.highlighted {
|
||||
background-color:white;
|
||||
}
|
||||
}
|
||||
|
||||
.expander {
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
|
||||
.previous-message-text {
|
||||
line-height:18px;
|
||||
white-space:pre-line;
|
||||
}
|
||||
|
||||
.previous-message-timestamp {
|
||||
|
|
@ -52,4 +53,9 @@
|
|||
width:100%;
|
||||
height:40px;
|
||||
}
|
||||
|
||||
.btn-send-text-message {
|
||||
text-align:center;
|
||||
width:50px;
|
||||
}
|
||||
}
|
||||
|
|
@ -109,13 +109,43 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
.feed-entry {
|
||||
position:relative;
|
||||
display:block;
|
||||
white-space:nowrap;
|
||||
min-width:700px;
|
||||
border-bottom:solid 1px #666;
|
||||
max-height:74px;
|
||||
overflow:hidden;
|
||||
margin-top:20px;
|
||||
|
||||
&:nth-child(1) {
|
||||
margin-top:0;
|
||||
}
|
||||
|
||||
&[data-mode="minimal"] {
|
||||
.avatar-small {
|
||||
display:none;
|
||||
}
|
||||
.feed-type-title {
|
||||
display:none;
|
||||
}
|
||||
.recording-controls-holder {
|
||||
width: 65%;
|
||||
float:left;
|
||||
}
|
||||
.name-and-description {
|
||||
margin-left:0;
|
||||
}
|
||||
|
||||
min-width:300px;
|
||||
border-bottom-width:0;
|
||||
}
|
||||
|
||||
.session-controls, .recording-controls {
|
||||
display:inline-block;
|
||||
&.ended {
|
||||
background-color: #471f18;
|
||||
|
||||
}
|
||||
|
||||
&.inprogress {
|
||||
|
|
@ -129,6 +159,16 @@
|
|||
}
|
||||
}
|
||||
|
||||
.recording-controls-holder {
|
||||
float:right;
|
||||
width:40%;
|
||||
}
|
||||
|
||||
.name-and-description {
|
||||
float:left;
|
||||
width:30%;
|
||||
margin-left:20px;
|
||||
}
|
||||
.recording-controls {
|
||||
.recording-position {
|
||||
width:70%;
|
||||
|
|
@ -147,22 +187,6 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.feed-entry {
|
||||
position:relative;
|
||||
display:block;
|
||||
white-space:nowrap;
|
||||
min-width:700px;
|
||||
border-bottom:solid 1px #666;
|
||||
max-height:74px;
|
||||
overflow:hidden;
|
||||
margin-top:20px;
|
||||
|
||||
&:nth-child(1) {
|
||||
margin-top:0;
|
||||
}
|
||||
|
||||
/**
|
||||
&.animate-down {
|
||||
-webkit-transition: max-height height 2s;
|
||||
|
|
|
|||
|
|
@ -242,7 +242,7 @@ Version: 1.1
|
|||
|
||||
.carousel .slides .spinner
|
||||
{
|
||||
background : #000 url(loading.gif) no-repeat center; /* video preloader */
|
||||
background : #000 /*url(loading.gif)*/ no-repeat center; /* video preloader */
|
||||
}
|
||||
|
||||
/* _____________________________ *
|
||||
|
|
|
|||
|
|
@ -44,6 +44,12 @@ class ApiUsersController < ApiController
|
|||
@user.show_whats_next = params[:show_whats_next] if params.has_key?(:show_whats_next)
|
||||
@user.subscribe_email = params[:subscribe_email] if params.has_key?(:subscribe_email)
|
||||
@user.biography = params[:biography] if params.has_key?(:biography)
|
||||
|
||||
# allow keyword of 'LATEST' to mean set the notification_seen_at to the most recent notification for this user
|
||||
if params.has_key?(:notification_seen_at)
|
||||
@user.update_notification_seen_at params[:notification_seen_at]
|
||||
end
|
||||
|
||||
@user.save
|
||||
|
||||
if @user.errors.any?
|
||||
|
|
|
|||
|
|
@ -21,4 +21,7 @@ class SpikesController < ApplicationController
|
|||
render :layout => 'web'
|
||||
end
|
||||
|
||||
def launch_app
|
||||
render :layout => 'web'
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -173,12 +173,9 @@ class UsersController < ApplicationController
|
|||
else
|
||||
sign_in @user
|
||||
|
||||
if @user.musician
|
||||
redirect_to :action => :congratulations_musician, :type => 'Native'
|
||||
else
|
||||
redirect_to :action => :congratulations_fan, :type => 'Native'
|
||||
end
|
||||
destination = @user.musician ? :congratulations_musician : :congratulations_fan
|
||||
|
||||
redirect_to :action => destination, :type => @user.user_authorization('facebook') ? 'Facebook' : 'Native'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -215,8 +212,8 @@ class UsersController < ApplicationController
|
|||
end
|
||||
|
||||
# temporary--will go away soon
|
||||
@jamfest_2014 = Event.find_by_id('80bb6acf-3ddc-4305-9442-75e6ec047c27')
|
||||
@jamfest_2014 = Event.find_by_id('a2dfbd26-9b17-4446-8c61-b67a542ea6ee') unless @jamfest_2014
|
||||
@jamfest_2014 = Event.find_by_id('80bb6acf-3ddc-4305-9442-75e6ec047c27') # production ID
|
||||
@jamfest_2014 = Event.find_by_id('a2dfbd26-9b17-4446-8c61-b67a542ea6ee') unless @jamfest_2014 # development ID
|
||||
# temporary--end
|
||||
|
||||
@welcome_page = true
|
||||
|
|
@ -271,8 +268,16 @@ class UsersController < ApplicationController
|
|||
begin
|
||||
@reset_password_email = params[:jam_ruby_user][:email]
|
||||
|
||||
if @reset_password_email.empty?
|
||||
@reset_password_error = "Please enter an email address above"
|
||||
if @reset_password_email.blank?
|
||||
@reset_password_error = "Please enter an email address"
|
||||
render 'request_reset_password', :layout => 'landing'
|
||||
return
|
||||
end
|
||||
|
||||
@reset_password_email.strip!
|
||||
|
||||
unless User::VALID_EMAIL_REGEX.match(@reset_password_email)
|
||||
@reset_password_error = "Please enter a valid email address"
|
||||
render 'request_reset_password', :layout => 'landing'
|
||||
return
|
||||
end
|
||||
|
|
@ -281,7 +286,6 @@ class UsersController < ApplicationController
|
|||
render 'sent_reset_password', :layout => 'landing'
|
||||
rescue JamRuby::JamArgumentError
|
||||
# Dont tell the user if this error occurred to prevent scraping email addresses.
|
||||
#@reset_password_error = "Email address not found"
|
||||
render 'sent_reset_password', :layout => 'landing'
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
object @music_session
|
||||
object @music_session
|
||||
|
||||
if !current_user
|
||||
# there should be more data returned, but we need to think very carefully about what data is public for a music session
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ end
|
|||
|
||||
# give back more info if the user being fetched is yourself
|
||||
if @user == current_user
|
||||
attributes :email, :original_fpfile, :cropped_fpfile, :crop_selection, :session_settings, :show_whats_next, :subscribe_email, :auth_twitter
|
||||
attributes :email, :original_fpfile, :cropped_fpfile, :crop_selection, :session_settings, :show_whats_next, :subscribe_email, :auth_twitter, :new_notifications
|
||||
|
||||
|
||||
elsif current_user
|
||||
node :is_friend do |uu|
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@
|
|||
<div class="right mt10">
|
||||
<div>
|
||||
<a href="#" layout-link="home" class="button-grey">CANCEL</a>
|
||||
<a href="#" id="btn-create-session" class="button-orange">JAM!</a>
|
||||
<a href="#" id="btn-create-session" class="button-orange <%= @nativeClient ? '' : 'button-disabled' %>">JAM!</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -46,39 +46,50 @@
|
|||
<div class="homebox-info"><!-- free service level --></div>
|
||||
</div>
|
||||
</div>
|
||||
<% else %>
|
||||
<div layout-grid="2x4" class="grid">
|
||||
<div layout-grid-position="0,0" layout-grid-rows="1" layout-grid-columns="1"
|
||||
layout-link="feed" class="homecard feed">
|
||||
<h2>feed</h2>
|
||||
<% else %><div layout-grid="2x12" class="grid">
|
||||
<div type="createSession" layout-grid-position="0,0" layout-grid-rows="1" layout-grid-columns="4"
|
||||
layout-link="createSession" class="homecard createsession">
|
||||
<h2>create session</h2>
|
||||
|
||||
<div class="homebox-info"><!-- 4 friends online, 2 currently in sessions --></div>
|
||||
</div>
|
||||
<div layout-grid-position="1,0" layout-grid-rows="1" layout-grid-columns="2"
|
||||
layout-link="bands" class="homecard bands">
|
||||
<h2>bands</h2>
|
||||
<div class="homebox-info"><!-- 4 friends online, 2 currently in sessions --></div>
|
||||
</div>
|
||||
<div type="findSession" layout-grid-position="4,0" layout-grid-rows="1" layout-grid-columns="4"
|
||||
layout-link="findSession" class="homecard findsession">
|
||||
<h2>find session</h2>
|
||||
|
||||
<div class="homebox-info"><!-- 1 session invitation, 19 public sessions active --></div>
|
||||
</div>
|
||||
<div layout-grid-position="3,0" layout-grid-rows="1" layout-grid-columns="1"
|
||||
layout-link="musicians" class="homecard musicians">
|
||||
<h2>musicians</h2>
|
||||
<div class="homebox-info"><!-- 1 session invitation, 19 public sessions active --></div>
|
||||
</div>
|
||||
<div layout-grid-position="8,0" layout-grid-rows="1" layout-grid-columns="4"
|
||||
layout-link="feed" class="homecard feed">
|
||||
<h2>feed</h2>
|
||||
|
||||
<div class="homebox-info"><!-- 5 followers, 3 following --></div>
|
||||
</div>
|
||||
<div layout-grid-position="0,1" layout-grid-rows="1" layout-grid-columns="2"
|
||||
class="homecard profile">
|
||||
<h2>profile</h2>
|
||||
<div class="homebox-info"><!-- 4 friends online, 2 currently in sessions --></div>
|
||||
</div>
|
||||
<div layout-grid-position="0,1" layout-grid-rows="1" layout-grid-columns="3"
|
||||
layout-link="musicians" class="homecard musicians">
|
||||
<h2>musicians</h2>
|
||||
|
||||
<div class="homebox-info"><!-- 38 new recordings, 19 sessions open to fans --></div>
|
||||
</div>
|
||||
<div layout-grid-position="2,1" layout-grid-rows="1" layout-grid-columns="2"
|
||||
layout-link="account" class="homecard account">
|
||||
<h2>account</h2>
|
||||
<div class="homebox-info"><!-- 5 followers, 3 following --></div>
|
||||
</div>
|
||||
<div layout-grid-position="3,1" layout-grid-rows="1" layout-grid-columns="3"
|
||||
layout-link="bands" class="homecard bands">
|
||||
<h2>bands</h2>
|
||||
|
||||
<div class="homebox-info"><!-- free service level --></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="homebox-info"><!-- 1 session invitation, 19 public sessions active --></div>
|
||||
</div>
|
||||
<div layout-grid-position="6,1" layout-grid-rows="1" layout-grid-columns="3"
|
||||
class="homecard profile">
|
||||
<h2>profile</h2>
|
||||
|
||||
<div class="homebox-info"><!-- 5 followers, 3 following --></div>
|
||||
</div>
|
||||
<div layout-grid-position="9,1" layout-grid-rows="1" layout-grid-columns="3"
|
||||
layout-link="account" class="homecard account">
|
||||
<h2>account</h2>
|
||||
|
||||
<div class="homebox-info"><!-- free service level --></div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
.dialog.dialog-overlay-sm{ layout: 'dialog', 'layout-id' => 'launch-app-dialog', id: 'launch-app-dialog'}
|
||||
.content-head
|
||||
= image_tag "content/icon_alert.png", {:width => 24, :height => 24, :class => 'content-icon' }
|
||||
%h1 Application Notice
|
||||
.dialog-inner
|
||||
|
||||
%script{type: 'text/template', id: 'template-attempt-launch'}
|
||||
%p To create or find and join a session, you must use the JamKazam application.
|
||||
.right.buttons
|
||||
%a.button-grey.btn-cancel{href:'#', 'layout-action' => 'close'} CANCEL
|
||||
%a.button-orange.btn-launch-app{href:'{{data.launchUrl}}'} LAUNCH APP
|
||||
|
||||
%script{type: 'text/template', id: 'template-unsupported-launch'}
|
||||
%p To create or find and join a session, you must use the JamKazam application. Please download and install the application if you have not done so already.
|
||||
.right.buttons
|
||||
%a.button-grey.btn-cancel{href:'#', 'layout-action' => 'close'} CANCEL
|
||||
%a.button-orange.btn-go-to-download-page{href:'/downloads'} GO TO APP DOWNLOAD PAGE
|
||||
|
||||
%script{type: 'text/template', id: 'template-launch-successful'}
|
||||
%p
|
||||
The JamKazam application was launched successfully.
|
||||
%a.not-actually-launched{href: '#'} Don't see it?
|
||||
.dont-see-it
|
||||
%p
|
||||
If you do not see the application, it may be minimized. Double-check the
|
||||
%a{href:'{{data.osSpecificSystemTrayLink}}', rel: 'external'} {{data.osSpecificSystemTray}}
|
||||
to see if it's running.
|
||||
%p
|
||||
If the application is not running, then please
|
||||
%a.download-application{href: '/downloads'} download
|
||||
and install the application if you have not done so already, and then start it manually rather than using this web launcher.
|
||||
.right.buttons
|
||||
%a.button-grey.btn-done{href:'#', 'layout-action' => 'close'} DONE
|
||||
|
||||
%script{type: 'text/template', id: 'template-launch-unsuccessful'}
|
||||
%h2 The JamKazam application could not be launched.
|
||||
%p
|
||||
If you do not see the application, it may be minimized. Double-check the
|
||||
%a{href:'{{data.osSpecificSystemTrayLink}}', rel: 'external'} {{data.osSpecificSystemTray}}
|
||||
to see if it's running.
|
||||
%p
|
||||
If the application is not running, then please
|
||||
%a.download-application{href: '/downloads'} download
|
||||
and install the application if you have not done so already, and then start it manually rather than using this web launcher.
|
||||
.right.buttons
|
||||
%a.button-grey.btn-done{href:'#', 'layout-action' => 'close'} CLOSE
|
||||
%a.button-orange.btn-go-to-download-page{href:'/downloads'} GO TO APP DOWNLOAD PAGE
|
||||
|
|
@ -51,6 +51,7 @@
|
|||
<%= render "showServerErrorDialog" %>
|
||||
<%= render "textMessageDialog" %>
|
||||
<%= render "acceptFriendRequestDialog" %>
|
||||
<%= render "launchAppDialog" %>
|
||||
<%= render "notify" %>
|
||||
<%= render "client_update" %>
|
||||
<%= render "banner" %>
|
||||
|
|
@ -145,6 +146,9 @@
|
|||
var friendSelectorDialog = new JK.FriendSelectorDialog(JK.app);
|
||||
friendSelectorDialog.initialize();
|
||||
|
||||
var launchAppDialog = new JK.LaunchAppDialog(JK.app);
|
||||
launchAppDialog.initialize();
|
||||
|
||||
var userDropdown = new JK.UserDropdown(JK.app);
|
||||
JK.UserDropdown = userDropdown;
|
||||
userDropdown.initialize(invitationDialog);
|
||||
|
|
|
|||
|
|
@ -6,15 +6,19 @@
|
|||
%span.event-title= event_session_title(event_session)
|
||||
|
||||
.landing-details.event
|
||||
.left.f20.teal.time
|
||||
%strong
|
||||
= event_session_start_hour(event_session)
|
||||
.right.session-button
|
||||
= event_session_button(event_session)
|
||||
- unless event_session.has_public_mixed_recordings?
|
||||
.left.f20.teal.time
|
||||
%strong
|
||||
= event_session_start_hour(event_session)
|
||||
.right.session-button
|
||||
= event_session_button(event_session)
|
||||
%br{ clear:'all' }
|
||||
|
||||
.left.bio
|
||||
= event_session_description(event_session)
|
||||
.left
|
||||
- event_session.public_mixed_recordings.each do |recording|
|
||||
= render :partial => "users/feed_recording", locals: { feed_item: recording, mode: 'minimal'}
|
||||
%br
|
||||
%br
|
||||
%br{ clear:'all' }
|
||||
|
|
@ -23,7 +23,7 @@
|
|||
%h2 ARTIST LINEUP
|
||||
%br
|
||||
|
||||
= render :partial => "event_session", :collection => @event.event_sessions.order('starts_at')
|
||||
= render :partial => "event_session", :collection => @event.event_sessions.order('ordinal, starts_at')
|
||||
|
||||
%br{clear:'all'}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
<%= render "layouts/social_meta" %>
|
||||
<% end %>
|
||||
</head>
|
||||
<body class="jam">
|
||||
<body class="jam" data-client-type="<%= @nativeClient ? 'client' : 'browser' %>">
|
||||
<%= yield %>
|
||||
<%= render "shared/ga" %>
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
- provide(:title, 'Launch App')
|
||||
|
||||
|
||||
.content-wrapper
|
||||
%h2 Launch App Test
|
||||
|
||||
%a#try{href:'jamkazam:abc'} Click Here For Mumble
|
||||
|
||||
%br
|
||||
|
||||
%a#try_bad{href:'bumble:abc'} Click Here For Bumble
|
||||
|
||||
%a#hiddenLink{style: 'display:none'} Hidden Link
|
||||
|
||||
#result Result will show here
|
||||
|
||||
|
||||
:javascript
|
||||
$(function () {
|
||||
|
||||
var options = {
|
||||
callback:function(success) {$('#result').text('success=' + success); },
|
||||
fallback:function() {console.log("not supported"); }
|
||||
};
|
||||
|
||||
$('#try').customProtocol(options);
|
||||
$('#try_bad').customProtocol(options);
|
||||
});
|
||||
|
|
@ -68,4 +68,8 @@
|
|||
|
||||
|
||||
%br{:clear => "all"}/
|
||||
%br/
|
||||
%br/
|
||||
:javascript
|
||||
$(function () {
|
||||
new window.JK.FeedItemSession($('.feed-entry[data-music-session="#{feed_item.id}"]'));
|
||||
})
|
||||
|
|
@ -1,21 +1,24 @@
|
|||
.feed-entry.recording-entry{:id => feed_item.id, :'data-claimed-recording-id' => feed_item.candidate_claimed_recording.id}
|
||||
/ default values for template
|
||||
- mode ||= local_assigns[:mode] = local_assigns.fetch(:mode, 'full')
|
||||
|
||||
.feed-entry.recording-entry{:id => feed_item.id, :'data-claimed-recording-id' => feed_item.candidate_claimed_recording.id, :'data-mode' => mode}
|
||||
/ avatar
|
||||
.avatar-small.ib
|
||||
= recording_avatar(feed_item)
|
||||
/ type and artist
|
||||
.left.ml20.w15
|
||||
.left.ml20.w15.feed-type-title
|
||||
.title{hoveraction: 'recording', :'recording-id' => feed_item.candidate_claimed_recording.id } RECORDING
|
||||
.artist
|
||||
= recording_artist_name(feed_item)
|
||||
= timeago(feed_item.created_at, class: 'small created_at')
|
||||
/ name and description
|
||||
.left.ml20.w30
|
||||
.name-and-description
|
||||
.name.dotdotdot
|
||||
= recording_name(feed_item)
|
||||
.description.dotdotdot
|
||||
= recording_description(feed_item)
|
||||
/ timeline and controls
|
||||
.right.w40
|
||||
.recording-controls-holder
|
||||
/ recording play controls
|
||||
.recording-controls{ class: feed_item.candidate_claimed_recording.has_mix? ? 'has-mix' : 'no-mix'}
|
||||
/ play button
|
||||
|
|
@ -83,3 +86,7 @@
|
|||
|
||||
%br{:clear => "all"}/
|
||||
%br/
|
||||
:javascript
|
||||
$(function () {
|
||||
new window.JK.FeedItemRecording($('.feed-entry[data-claimed-recording-id="#{feed_item.candidate_claimed_recording.id}"]'));
|
||||
})
|
||||
|
|
|
|||
|
|
@ -10,14 +10,15 @@
|
|||
<!-- inner wrapper -->
|
||||
<div class="ftue-inner">
|
||||
|
||||
Password successfully reset for email: <%= params[:jam_ruby_user][:email] %>
|
||||
<span class="please-check">Please check your email at <b><%= params[:jam_ruby_user][:email] %></b> and click the link in the email to set a new password.</span>
|
||||
|
||||
<br clear="all" />
|
||||
<br/>
|
||||
|
||||
Please check your email and click the URL to set a new password.
|
||||
<span class="wrong-email-notice">If no email arrives at <b><%= params[:jam_ruby_user][:email] %></b>, you may not be entering the email address that you used when signing up with the service.</span>
|
||||
|
||||
<br clear="all" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@
|
|||
- content_for :after_black_bar do
|
||||
- if @jamfest_2014
|
||||
.jamfest{style: 'top:-70px;position:relative'}
|
||||
%a{ href: event_path(@jamfest_2014.slug), style: 'font-size:24px' }
|
||||
Join us online March 12 for Virtual Jam Fest!
|
||||
%a{ href: event_path(@jamfest_2014.slug), style: 'font-size:20px;margin-top:11px' }
|
||||
Listen to the terrific band performances from Virtual Jam Fest 2014!
|
||||
%div{style: "padding-top:20px;"}
|
||||
.right
|
||||
= render :partial => "buzz"
|
||||
|
|
|
|||
|
|
@ -68,8 +68,8 @@ SampleApp::Application.configure do
|
|||
# it's nice to have even admin accounts (which all the default ones are) generate GA data for testing
|
||||
config.ga_suppress_admin = false
|
||||
|
||||
config.websocket_gateway_connect_time_stale = 12 # 12 matches production
|
||||
config.websocket_gateway_connect_time_expire = 20 # 20 matches production
|
||||
config.websocket_gateway_connect_time_stale = 12
|
||||
config.websocket_gateway_connect_time_expire = 20
|
||||
|
||||
config.audiomixer_path = ENV['AUDIOMIXER_PATH'] || audiomixer_workspace_path || "/var/lib/audiomixer/audiomixer/audiomixerapp"
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
Resque::Mailer.excluded_environments = [:test, :cucumber]
|
||||
|
|
@ -75,6 +75,8 @@ SampleApp::Application.routes.draw do
|
|||
|
||||
# route to spike controller (proof-of-concepts)
|
||||
match '/facebook_invite', to: 'spikes#facebook_invite'
|
||||
match '/launch_app', to: 'spikes#launch_app'
|
||||
|
||||
|
||||
# junk pages
|
||||
match '/help', to: 'static_pages#help'
|
||||
|
|
|
|||
|
|
@ -115,15 +115,44 @@ describe "Events", :js => true, :type => :feature, :capybara_feature => true, :s
|
|||
@event_session2.starts_at = 4.hours.ago
|
||||
@event_session2.save!
|
||||
visit "/events/so_latency"
|
||||
expect(page).to have_css(".landing-band.event[data-event-session=\"#{@event_session2.id}\"]")
|
||||
first(".landing-band.event[data-event-session='#{@event_session2.id}']:nth-child(1)")
|
||||
|
||||
# test that it sorts correctly by putting this later event second
|
||||
@event_session2.starts_at = 4.hours.from_now
|
||||
@event_session2.save!
|
||||
visit "/events/so_latency"
|
||||
expect(page).to have_css(".landing-band.event[data-event-session=\"#{@event_session.id}\"]")
|
||||
first(".landing-band.event[data-event-session='#{@event_session.id}']:nth-child(1)")
|
||||
|
||||
# test that it sorts correctly by putting this later event first, because ordinal is specified
|
||||
@event_session2.ordinal = 0
|
||||
@event_session2.save!
|
||||
visit "/events/so_latency"
|
||||
first(".landing-band.event[data-event-session='#{@event_session2.id}']:nth-child(1)")
|
||||
|
||||
# associate a recording, and verify that the display changes to have no description, but has a recording widget
|
||||
mix = FactoryGirl.create(:mix)
|
||||
mix.recording.music_session_id = music_session_history.id
|
||||
mix.recording.save!
|
||||
visit "/events/so_latency"
|
||||
find(".feed-entry.recording-entry[data-claimed-recording-id='#{mix.recording.candidate_claimed_recording.id}']")
|
||||
|
||||
# associate a second recording, and verify it's ordered by name
|
||||
mix2 = FactoryGirl.create(:mix)
|
||||
mix2.recording.music_session_id = music_session_history.id
|
||||
mix2.recording.save!
|
||||
mix2.recording.claimed_recordings.length.should == 1
|
||||
mix2.recording.claimed_recordings[0].name = '____AAA'
|
||||
mix2.recording.claimed_recordings[0].save!
|
||||
visit "/events/so_latency"
|
||||
find(".feed-entry.recording-entry[data-claimed-recording-id='#{mix2.recording.candidate_claimed_recording.id}']:nth-child(1)")
|
||||
find(".feed-entry.recording-entry[data-claimed-recording-id='#{mix.recording.candidate_claimed_recording.id}']:nth-child(3)")
|
||||
|
||||
# and do a re-order test
|
||||
mix2.recording.claimed_recordings[0].name = 'zzzzz'
|
||||
mix2.recording.claimed_recordings[0].save!
|
||||
visit "/events/so_latency"
|
||||
find('.feed-entry.recording-entry .name', text:'zzzzz')
|
||||
find(".feed-entry.recording-entry[data-claimed-recording-id='#{mix.recording.candidate_claimed_recording.id}']:nth-child(1)")
|
||||
find(".feed-entry.recording-entry[data-claimed-recording-id='#{mix2.recording.candidate_claimed_recording.id}']:nth-child(3)")
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ describe "Home Screen", :js => true, :type => :feature, :capybara_feature => tru
|
|||
Capybara.javascript_driver = :poltergeist
|
||||
Capybara.current_driver = Capybara.javascript_driver
|
||||
Capybara.default_wait_time = 10
|
||||
MusicSession.delete_all
|
||||
end
|
||||
|
||||
let(:user) { FactoryGirl.create(:user) }
|
||||
|
|
@ -137,6 +138,8 @@ describe "Home Screen", :js => true, :type => :feature, :capybara_feature => tru
|
|||
end
|
||||
|
||||
it_behaves_like :has_footer
|
||||
it_behaves_like :create_session_homecard
|
||||
it_behaves_like :find_session_homecard
|
||||
it_behaves_like :feed_homecard
|
||||
it_behaves_like :musicians_homecard
|
||||
it_behaves_like :bands_homecard
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe "Reset Password", :js => true, :type => :feature, :capybara_feature => true do
|
||||
|
||||
subject { page }
|
||||
|
||||
let(:user) { FactoryGirl.create(:user) }
|
||||
|
||||
share_examples_for :launch_not_supported do |options|
|
||||
it "should indicate not supported" do
|
||||
sign_in_poltergeist user
|
||||
visit options[:screen_path]
|
||||
should have_selector('h1', text: 'Application Notice')
|
||||
should have_selector('p', text: 'To create or find and join a session, you must use the JamKazam application. Please download and install the application if you have not done so already.')
|
||||
find('a.btn-go-to-download-page').trigger(:click)
|
||||
find('h3', text: 'SYSTEM REQUIREMENTS:')
|
||||
end
|
||||
end
|
||||
|
||||
share_examples_for :launch_supported do |options|
|
||||
it "should indicate supported" do
|
||||
sign_in_poltergeist user
|
||||
visit options[:screen_path]
|
||||
should have_selector('h1', text: 'Application Notice')
|
||||
should have_selector('p', text: 'To create or find and join a session, you must use the JamKazam application.')
|
||||
should have_selector('.btn-launch-app')
|
||||
find('.btn-launch-app')['href'].start_with?('jamkazam:')
|
||||
end
|
||||
end
|
||||
|
||||
describe "unsupported" do
|
||||
before do
|
||||
# emulate mac safari
|
||||
page.driver.headers = { 'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.74.9 (KHTML, like Gecko) Version/7.0.2 Safari/537.74.9'}
|
||||
end
|
||||
it_behaves_like :launch_not_supported, screen_path: '/client#/createSession'
|
||||
it_behaves_like :launch_not_supported, screen_path: '/client#/findSession'
|
||||
end
|
||||
|
||||
describe "supported" do
|
||||
before do
|
||||
# emulate chrome
|
||||
page.driver.headers = { 'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.152 Safari/537.36'}
|
||||
end
|
||||
it_behaves_like :launch_supported, screen_path: '/client#/createSession'
|
||||
it_behaves_like :launch_supported, screen_path: '/client#/findSession'
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,168 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe "Notification Highlighter", :js => true, :type => :feature, :capybara_feature => true do
|
||||
|
||||
subject { page }
|
||||
|
||||
before(:all) do
|
||||
Capybara.javascript_driver = :poltergeist
|
||||
Capybara.current_driver = Capybara.javascript_driver
|
||||
Capybara.default_wait_time = 10
|
||||
end
|
||||
|
||||
let(:user) { FactoryGirl.create(:user) }
|
||||
let(:user2) { FactoryGirl.create(:user) }
|
||||
|
||||
|
||||
shared_examples_for :notification_badge do |options|
|
||||
it "in correct state" do
|
||||
sign_in_poltergeist(user) unless page.has_selector?('h2', 'musicians')
|
||||
badge = find("#{NOTIFICATION_PANEL} .badge", text:options[:count])
|
||||
badge['class'].include?('highlighted').should == options[:highlighted]
|
||||
|
||||
if options[:action] == :click
|
||||
badge.trigger(:click)
|
||||
badge = find("#{NOTIFICATION_PANEL} .badge", text:0)
|
||||
badge['class'].include?('highlighted').should == false
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "user with no notifications" do
|
||||
|
||||
it_behaves_like :notification_badge, highlighted: false, count:0
|
||||
|
||||
describe "and realtime notifications with sidebar closed" do
|
||||
before(:each) do
|
||||
sign_in_poltergeist(user)
|
||||
document_focus
|
||||
user.reload
|
||||
end
|
||||
|
||||
it_behaves_like :notification_badge, highlighted: false, count:0
|
||||
|
||||
describe "sees notification" do
|
||||
before(:each) do
|
||||
notification = Notification.send_text_message("text message", user2, user)
|
||||
notification.errors.any?.should be_false
|
||||
end
|
||||
|
||||
it_behaves_like :notification_badge, highlighted: false, count:0, action: :click
|
||||
end
|
||||
|
||||
describe "document out of focus" do
|
||||
before(:each) do
|
||||
document_blur
|
||||
notification = Notification.send_text_message("text message", user2, user)
|
||||
notification.errors.any?.should be_false
|
||||
end
|
||||
|
||||
it_behaves_like :notification_badge, highlighted: true, count:1, action: :click
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "and realtime notifications with sidebar open" do
|
||||
before(:each) do
|
||||
# generate one message so that count = 1 to start
|
||||
notification = Notification.send_text_message("text message", user2, user)
|
||||
notification.errors.any?.should be_false
|
||||
sign_in_poltergeist(user)
|
||||
document_focus
|
||||
open_notifications
|
||||
badge = find("#{NOTIFICATION_PANEL} .badge", text:'0') # wait for the opening of the sidebar to bring count to 0
|
||||
user.reload
|
||||
end
|
||||
|
||||
it_behaves_like :notification_badge, highlighted: false, count:0
|
||||
|
||||
describe "sees notification" do
|
||||
before(:each) do
|
||||
notification = Notification.send_text_message("text message", user2, user)
|
||||
notification.errors.any?.should be_false
|
||||
find('#notification #ok-button') # wait for notification to show, so that we know the sidebar had a chance to update
|
||||
end
|
||||
|
||||
it_behaves_like :notification_badge, highlighted: false, count:0
|
||||
end
|
||||
|
||||
describe "document out of focus" do
|
||||
before(:each) do
|
||||
document_blur
|
||||
notification = Notification.send_text_message("text message 2", user2, user)
|
||||
notification.errors.any?.should be_false
|
||||
find('#notification #ok-button')
|
||||
end
|
||||
|
||||
it_behaves_like :notification_badge, highlighted: true, count:1
|
||||
|
||||
describe "user comes back" do
|
||||
before(:each) do
|
||||
window_focus
|
||||
|
||||
it_behaves_like :notification_badge, highlighted: false, count:1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "user with new notifications" do
|
||||
before(:each) do
|
||||
# create a notification
|
||||
notification = Notification.send_text_message("text message", user2, user)
|
||||
notification.errors.any?.should be_false
|
||||
end
|
||||
|
||||
it_behaves_like :notification_badge, highlighted:true, count:1, action: :click
|
||||
|
||||
describe "user has previously seen notifications" do
|
||||
before(:each) do
|
||||
user.update_notification_seen_at 'LATEST'
|
||||
user.save!
|
||||
end
|
||||
|
||||
it_behaves_like :notification_badge, highlighted: false, count:0, action: :click
|
||||
|
||||
describe "user again has unseen notifications" do
|
||||
before(:each) do
|
||||
# create a notification
|
||||
notification = Notification.send_text_message("text message", user2, user)
|
||||
notification.errors.any?.should be_false
|
||||
end
|
||||
|
||||
it_behaves_like :notification_badge, highlighted:true, count:1, action: :click
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "user no unseen notifications" do
|
||||
describe "notification occurs in realtime" do
|
||||
|
||||
describe "sidebar is open" do
|
||||
describe "user can see notifications" do
|
||||
it "stays deactivated" do
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
describe "user can not see notifications" do
|
||||
describe "with dialog open" do
|
||||
it "temporarily activates" do
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
describe "with document blurred" do
|
||||
it "temporarily activates" do
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe "Reset Password", :js => true, :type => :feature, :capybara_feature => true do
|
||||
|
||||
subject { page }
|
||||
|
||||
before do
|
||||
UserMailer.deliveries.clear
|
||||
visit request_reset_password_path
|
||||
end
|
||||
|
||||
it "shows specific error for empty password" do
|
||||
click_button 'RESET'
|
||||
find('.login-error-msg', text: 'Please enter an email address')
|
||||
UserMailer.deliveries.length.should == 0
|
||||
end
|
||||
|
||||
it "shows specific error for invalid email address" do
|
||||
fill_in "jam_ruby_user_email", with: 'snoozeday'
|
||||
click_button 'RESET'
|
||||
find('.login-error-msg', text: 'Please enter a valid email address')
|
||||
UserMailer.deliveries.length.should == 0
|
||||
end
|
||||
|
||||
it "acts as if success when email of no one in the service" do
|
||||
fill_in "jam_ruby_user_email", with: 'noone_exists_with_this@blah.com'
|
||||
click_button 'RESET'
|
||||
find('span.please-check', text: 'Please check your email at noone_exists_with_this@blah.com and click the link in the email to set a new password.')
|
||||
UserMailer.deliveries.length.should == 0
|
||||
end
|
||||
|
||||
it "acts as if success when email is valid" do
|
||||
user = FactoryGirl.create(:user)
|
||||
fill_in "jam_ruby_user_email", with: user.email
|
||||
click_button 'RESET'
|
||||
find('span.please-check', text: "Please check your email at #{user.email} and click the link in the email to set a new password.")
|
||||
UserMailer.deliveries.length.should == 1
|
||||
end
|
||||
|
||||
it "acts as if success when email is valid (but with extra whitespace)" do
|
||||
user = FactoryGirl.create(:user)
|
||||
fill_in "jam_ruby_user_email", with: user.email + ' '
|
||||
click_button 'RESET'
|
||||
find('span.please-check', text: "Please check your email at #{user.email} and click the link in the email to set a new password.")
|
||||
UserMailer.deliveries.length.should == 1
|
||||
end
|
||||
end
|
||||
|
|
@ -162,6 +162,30 @@ describe "Signup", :js => true, :type => :feature, :capybara_feature => true do
|
|||
|
||||
end
|
||||
|
||||
describe "signup facebook user" do
|
||||
before do
|
||||
@fb_signup = FactoryGirl.create(:facebook_signup)
|
||||
visit "#{signup_path}?facebook_signup=#{@fb_signup.lookup_id}"
|
||||
find('#jam_ruby_user_first_name')
|
||||
sleep 1 # if I don't do this, first_name and/or last name intermittently fail to fill out
|
||||
|
||||
fill_in "jam_ruby_user[first_name]", with: "Mike"
|
||||
fill_in "jam_ruby_user[last_name]", with: "Jones"
|
||||
fill_in "jam_ruby_user[email]", with: "newuser_fb@jamkazam.com"
|
||||
fill_in "jam_ruby_user[password]", with: "jam123"
|
||||
fill_in "jam_ruby_user[password_confirmation]", with: "jam123"
|
||||
check("jam_ruby_user[instruments][drums][selected]")
|
||||
check("jam_ruby_user[terms_of_service]")
|
||||
click_button "CREATE ACCOUNT"
|
||||
end
|
||||
|
||||
it "success" do
|
||||
page.should have_title("JamKazam")
|
||||
should have_selector('div.tagline', text: "Congratulations!")
|
||||
uri = URI.parse(current_url)
|
||||
"#{uri.path}?#{uri.query}".should == congratulations_musician_path(:type => 'Facebook')
|
||||
end
|
||||
end
|
||||
def signup_invited_user
|
||||
visit "#{signup_path}?invitation_code=#{@invited_user.invitation_code}"
|
||||
find('#jam_ruby_user_first_name')
|
||||
|
|
@ -239,7 +263,6 @@ describe "Signup", :js => true, :type => :feature, :capybara_feature => true do
|
|||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ describe "Text Message", :js => true, :type => :feature, :capybara_feature => tr
|
|||
notification = Notification.send_text_message("bibbity bobbity boo", @user2, @user1)
|
||||
notification.errors.any?.should be_false
|
||||
|
||||
open_sidebar
|
||||
open_notifications
|
||||
|
||||
# find the notification and click REPLY
|
||||
find("[layout-id='panelNotifications'] [notification-id='#{notification.id}'] .button-orange", text:'REPLY').trigger(:click)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
|
||||
# methods here all assume you are in /client
|
||||
|
||||
NOTIFICATION_PANEL = '[layout-id="panelNotifications"]'
|
||||
|
||||
# enters text into the search sidebar
|
||||
def site_search(text, options = {})
|
||||
within('#searchForm') do
|
||||
|
|
@ -56,11 +58,41 @@ def send_text_message(msg, options={})
|
|||
end
|
||||
end
|
||||
|
||||
def open_sidebar
|
||||
find('[layout-id="panelNotifications"] .panel-header').trigger(:click)
|
||||
def open_notifications
|
||||
find("#{NOTIFICATION_PANEL} .panel-header").trigger(:click)
|
||||
end
|
||||
|
||||
def hover_intent(element)
|
||||
element.hover
|
||||
element.hover
|
||||
end
|
||||
|
||||
# forces document.hasFocus() to return false
|
||||
def document_blur
|
||||
page.evaluate_script(%{(function() {
|
||||
// save original
|
||||
if(!window.documentFocus) { window.documentFocus = window.document.hasFocus; }
|
||||
|
||||
window.document.hasFocus = function() {
|
||||
console.log("document.hasFocus() returns false");
|
||||
return false;
|
||||
}
|
||||
})()})
|
||||
end
|
||||
|
||||
def document_focus
|
||||
page.evaluate_script(%{(function() {
|
||||
// save original
|
||||
if(!window.documentFocus) { window.documentFocus = window.document.hasFocus; }
|
||||
|
||||
window.document.hasFocus = function() {
|
||||
console.log("document.hasFocus() returns true");
|
||||
return true;
|
||||
}
|
||||
})()})
|
||||
end
|
||||
|
||||
# simulates focus event on window
|
||||
def window_focus
|
||||
page.evaluate_script(%{window.jQuery(window).trigger('focus');})
|
||||
end
|
||||
|
|
@ -194,6 +194,7 @@ def create_session(creator = FactoryGirl.create(:user), unique_session_desc = ni
|
|||
# create session in one client
|
||||
in_client(creator) do
|
||||
page.driver.resize(1500, 800) # makes sure all the elements are visible
|
||||
page.driver.headers = { 'User-Agent' => ' JamKazam ' }
|
||||
sign_in_poltergeist creator
|
||||
wait_until_curtain_gone
|
||||
visit "/client#/createSession"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,112 @@
|
|||
/*!
|
||||
* jQuery Browser Plugin v0.0.6
|
||||
* https://github.com/gabceb/jquery-browser-plugin
|
||||
*
|
||||
* Original jquery-browser code Copyright 2005, 2013 jQuery Foundation, Inc. and other contributors
|
||||
* http://jquery.org/license
|
||||
*
|
||||
* Modifications Copyright 2013 Gabriel Cebrian
|
||||
* https://github.com/gabceb
|
||||
*
|
||||
* Released under the MIT license
|
||||
*
|
||||
* Date: 2013-07-29T17:23:27-07:00
|
||||
*/
|
||||
|
||||
(function( jQuery, window, undefined ) {
|
||||
"use strict";
|
||||
|
||||
var matched, browser;
|
||||
|
||||
jQuery.uaMatch = function( ua ) {
|
||||
ua = ua.toLowerCase();
|
||||
|
||||
var match = /(opr)[\/]([\w.]+)/.exec( ua ) ||
|
||||
/(chrome)[ \/]([\w.]+)/.exec( ua ) ||
|
||||
/(version)[ \/]([\w.]+).*(safari)[ \/]([\w.]+)/.exec( ua ) ||
|
||||
/(webkit)[ \/]([\w.]+)/.exec( ua ) ||
|
||||
/(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) ||
|
||||
/(msie) ([\w.]+)/.exec( ua ) ||
|
||||
ua.indexOf("trident") >= 0 && /(rv)(?::| )([\w.]+)/.exec( ua ) ||
|
||||
ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) ||
|
||||
[];
|
||||
|
||||
var platform_match = /(ipad)/.exec( ua ) ||
|
||||
/(iphone)/.exec( ua ) ||
|
||||
/(android)/.exec( ua ) ||
|
||||
/(windows phone)/.exec( ua ) ||
|
||||
/(win)/.exec( ua ) ||
|
||||
/(mac)/.exec( ua ) ||
|
||||
/(linux)/.exec( ua ) ||
|
||||
/(cros)/i.exec( ua ) ||
|
||||
[];
|
||||
|
||||
return {
|
||||
browser: match[ 3 ] || match[ 1 ] || "",
|
||||
version: match[ 2 ] || "0",
|
||||
platform: platform_match[ 0 ] || ""
|
||||
};
|
||||
};
|
||||
|
||||
matched = jQuery.uaMatch( window.navigator.userAgent );
|
||||
browser = {};
|
||||
|
||||
if ( matched.browser ) {
|
||||
browser[ matched.browser ] = true;
|
||||
browser.version = matched.version;
|
||||
browser.versionNumber = parseInt(matched.version);
|
||||
}
|
||||
|
||||
if ( matched.platform ) {
|
||||
browser[ matched.platform ] = true;
|
||||
}
|
||||
|
||||
// These are all considered mobile platforms, meaning they run a mobile browser
|
||||
if ( browser.android || browser.ipad || browser.iphone || browser[ "windows phone" ] ) {
|
||||
browser.mobile = true;
|
||||
}
|
||||
|
||||
// These are all considered desktop platforms, meaning they run a desktop browser
|
||||
if ( browser.cros || browser.mac || browser.linux || browser.win ) {
|
||||
browser.desktop = true;
|
||||
}
|
||||
|
||||
// Chrome, Opera 15+ and Safari are webkit based browsers
|
||||
if ( browser.chrome || browser.opr || browser.safari ) {
|
||||
browser.webkit = true;
|
||||
}
|
||||
|
||||
// IE11 has a new token so we will assign it msie to avoid breaking changes
|
||||
if ( browser.rv )
|
||||
{
|
||||
var ie = "msie";
|
||||
|
||||
matched.browser = ie;
|
||||
browser[ie] = true;
|
||||
}
|
||||
|
||||
// Opera 15+ are identified as opr
|
||||
if ( browser.opr )
|
||||
{
|
||||
var opera = "opera";
|
||||
|
||||
matched.browser = opera;
|
||||
browser[opera] = true;
|
||||
}
|
||||
|
||||
// Stock Android browsers are marked as Safari on Android.
|
||||
if ( browser.safari && browser.android )
|
||||
{
|
||||
var android = "android";
|
||||
|
||||
matched.browser = android;
|
||||
browser[android] = true;
|
||||
}
|
||||
|
||||
// Assign the name and platform variable
|
||||
browser.name = matched.browser;
|
||||
browser.platform = matched.platform;
|
||||
|
||||
|
||||
jQuery.browser = browser;
|
||||
})( jQuery, window );
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
/*global jQuery*/
|
||||
/*jshint curly:false*/
|
||||
|
||||
;(function ( $, window) {
|
||||
"use strict";
|
||||
|
||||
var defaults = {
|
||||
pulses : 1,
|
||||
interval : 0,
|
||||
returnDelay : 0,
|
||||
duration : 500
|
||||
};
|
||||
|
||||
$.fn.pulse = function(properties, options, callback) {
|
||||
// $(...).pulse('destroy');
|
||||
var stop = properties === 'destroy';
|
||||
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
options = $.extend({}, defaults, options);
|
||||
|
||||
if (!(options.interval >= 0)) options.interval = 0;
|
||||
if (!(options.returnDelay >= 0)) options.returnDelay = 0;
|
||||
if (!(options.duration >= 0)) options.duration = 500;
|
||||
if (!(options.pulses >= -1)) options.pulses = 1;
|
||||
if (typeof callback !== 'function') callback = function(){};
|
||||
|
||||
return this.each(function () {
|
||||
var el = $(this),
|
||||
property,
|
||||
original = {};
|
||||
|
||||
var data = el.data('pulse') || {};
|
||||
data.stop = stop;
|
||||
el.data('pulse', data);
|
||||
|
||||
for (property in properties) {
|
||||
if (properties.hasOwnProperty(property)) original[property] = el.css(property);
|
||||
}
|
||||
|
||||
var timesPulsed = 0;
|
||||
|
||||
function animate() {
|
||||
if (typeof el.data('pulse') === 'undefined') return;
|
||||
if (el.data('pulse').stop) return;
|
||||
if (options.pulses > -1 && ++timesPulsed > options.pulses) return callback.apply(el);
|
||||
el.animate(
|
||||
properties,
|
||||
{
|
||||
duration : options.duration / 2,
|
||||
complete : function(){
|
||||
window.setTimeout(function(){
|
||||
el.animate(original, {
|
||||
duration : options.duration / 2,
|
||||
complete : function() {
|
||||
window.setTimeout(animate, options.interval);
|
||||
}
|
||||
});
|
||||
},options.returnDelay);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
animate();
|
||||
});
|
||||
};
|
||||
|
||||
})( jQuery, window, document );
|
||||
|
|
@ -565,9 +565,19 @@ module JamWebsockets
|
|||
raise SessionError, 'connection state is gone. please reconnect.'
|
||||
else
|
||||
Connection.transaction do
|
||||
music_session = MusicSession.select(:track_changes_counter).find_by_id(connection.music_session_id) if connection.music_session_id
|
||||
track_changes_counter = music_session.track_changes_counter if music_session
|
||||
# send back track_changes_counter if in a session
|
||||
if connection.music_session_id
|
||||
music_session = MusicSession.select(:track_changes_counter).find_by_id(connection.music_session_id)
|
||||
track_changes_counter = music_session.track_changes_counter if music_session
|
||||
end
|
||||
|
||||
# update connection updated_at
|
||||
connection.touch
|
||||
|
||||
# update user's notification_seen_at field if the heartbeat indicates it saw one
|
||||
# first we try to use the notification id, which should usually exist.
|
||||
# if not, then fallback to notification_seen_at, which is approximately the last time we saw a notification
|
||||
update_notification_seen_at(connection, context, heartbeat)
|
||||
end
|
||||
|
||||
ConnectionManager.active_record_transaction do |connection_manager|
|
||||
|
|
@ -593,6 +603,34 @@ module JamWebsockets
|
|||
end
|
||||
end
|
||||
|
||||
def update_notification_seen_at(connection, context, heartbeat)
|
||||
notification_id_field = heartbeat.notification_seen if heartbeat.value_for_tag(1)
|
||||
if notification_id_field
|
||||
notification = Notification.find_by_id(notification_id_field)
|
||||
if notification
|
||||
connection.user.notification_seen_at = notification.created_at
|
||||
unless connection.user.save(validate: false)
|
||||
@log.error "unable to update notification_seen_at via id field for client #{context}. errors: #{connection.user.errors.inspect}"
|
||||
end
|
||||
else
|
||||
notification_seen_at_parsed = nil
|
||||
notification_seen_at = heartbeat.notification_seen_at if heartbeat.value_for_tag(2)
|
||||
begin
|
||||
notification_seen_at_parsed = Time.parse(notification_seen_at) if notification_seen_at && notification_seen_at.length > 0
|
||||
rescue Exception => e
|
||||
@log.error "unable to parse notification_seen_at in heartbeat from #{context}. notification_seen_at: #{notification_seen_at}"
|
||||
end
|
||||
|
||||
if notification_seen_at_parsed
|
||||
connection.user.notification_seen_at = notification_seen_at
|
||||
unless connection.user.save(validate: false)
|
||||
@log.error "unable to update notification_seen_at via time field for client #{context}. errors: #{connection.user.errors.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def valid_login(username, password, token, client_id)
|
||||
|
||||
if !token.nil? && token != ''
|
||||
|
|
|
|||
Loading…
Reference in New Issue