VRFS-925 merge conflict

This commit is contained in:
Jonathan Kolyer 2014-03-10 09:01:33 +00:00
commit d63f090657
52 changed files with 743 additions and 161 deletions

3
admin/app/admin/event.rb Normal file
View File

@ -0,0 +1,3 @@
ActiveAdmin.register JamRuby::Event, :as => 'Event' do
menu :parent => 'Events'
end

View File

@ -0,0 +1,3 @@
ActiveAdmin.register JamRuby::EventSession, :as => 'Event Session' do
menu :parent => 'Events'
end

View File

@ -133,3 +133,4 @@ plays_refactor.sql
fix_max_mind_isp_and_geo.sql
update_get_work_for_client_type.sql
bands_did_session.sql
events.sql

24
db/up/events.sql Normal file
View File

@ -0,0 +1,24 @@
CREATE TABLE events (
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
slug VARCHAR(512) NOT NULL UNIQUE,
title TEXT,
description TEXT,
show_sponser BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE event_sessions (
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
starts_at TIMESTAMP,
ends_at TIMESTAMP,
pinned_state VARCHAR(255),
img_url VARCHAR(1024),
img_width INTEGER,
img_height INTEGER,
event_id VARCHAR(64) REFERENCES events(id) ON DELETE CASCADE,
user_id VARCHAR(64) REFERENCES users(id) ON DELETE SET NULL,
band_id VARCHAR(64) REFERENCES bands(id) ON DELETE SET NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

View File

@ -100,6 +100,8 @@ require "jam_ruby/models/claimed_recording"
require "jam_ruby/models/crash_dump"
require "jam_ruby/models/isp_score_batch"
require "jam_ruby/models/promotional"
require "jam_ruby/models/event"
require "jam_ruby/models/event_session"
require "jam_ruby/models/icecast_admin_authentication"
require "jam_ruby/models/icecast_directory"
require "jam_ruby/models/icecast_limit"

View File

@ -47,6 +47,9 @@ module JamRuby
has_many :music_sessions, :class_name => "JamRuby::MusicSession", :foreign_key => "band_id"
has_many :music_session_history, :class_name => "JamRuby::MusicSessionHistory", :foreign_key => "band_id", :inverse_of => :band
# events
has_many :event_sessions, :class_name => "JamRuby::EventSession"
include Geokit::ActsAsMappable::Glue unless defined?(acts_as_mappable)
acts_as_mappable

View File

@ -0,0 +1,9 @@
class JamRuby::Event < ActiveRecord::Base
attr_accessible :slug, :title, :description, :show_sponser, as: :admin
validates :slug, uniqueness: true, presence: true
validates :show_sponser, :inclusion => {:in => [true, false]}
has_many :event_sessions, class_name: 'JamRuby::EventSession'
end

View File

@ -0,0 +1,28 @@
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
belongs_to :user, class_name: 'JamRuby::User'
belongs_to :band, class_name: 'JamRuby::Band'
belongs_to :event
validates :event, presence: true
validates :pinned_state, :inclusion => {:in => [nil, 'not_started', 'over']}
validate :one_of_user_band
before_validation :sanitize_active_admin
def sanitize_active_admin
self.img_url = nil if self.img_url == ''
self.user_id = nil if self.user_id == ''
self.band_id = nil if self.band_id == ''
self.pinned_state = nil if self.pinned_state == ''
end
def one_of_user_band
if band && user
errors.add(:user, 'specify band, or user. not both')
end
end
end

View File

@ -45,7 +45,7 @@ module JamRuby
# startIpNum,endIpNum,isp
self.transaction do
GeoIpIsp.delete_all
self.connection.execute "delete from #{GEOIPISP_TABLE}"
File.open(file, 'r:ISO-8859-1') do |io|
#s = io.gets.strip # eat the copyright line. gah, why do they have that in their file??
#unless s.eql? 'Copyright (c) 2012 MaxMind LLC. All Rights Reserved.'

View File

@ -94,6 +94,9 @@ module JamRuby
# crash dumps
has_many :crash_dumps, :foreign_key => "user_id", :class_name => "JamRuby::CrashDump"
# events
has_many :event_sessions, :class_name => "JamRuby::EventSession"
# This causes the authenticate method to be generated (among other stuff)
#has_secure_password

View File

@ -415,4 +415,13 @@ FactoryGirl.define do
factory :music_session_like, :class => JamRuby::MusicSessionLiker do
end
factory :event, :class => JamRuby::Event do
sequence(:slug) { |n| "slug-#{n}" }
title 'event title'
description 'event description'
end
factory :event_session, :class => JamRuby::EventSession do
end
end

View File

@ -0,0 +1,26 @@
require 'spec_helper'
describe EventSession do
it "should be creatable" do
event = FactoryGirl.create(:event)
event_session = FactoryGirl.create(:event_session, event: event)
end
it "requires a parent event" do
event_session = FactoryGirl.build(:event_session)
event_session.save.should be_false
event_session.errors[:event].should == ["can't be blank"]
end
it "can't specify both band and user" do
user = FactoryGirl.create(:user)
band = FactoryGirl.create(:band)
event = FactoryGirl.create(:event)
event_session = FactoryGirl.build(:event_session, event: event, user: user, band:band)
event_session.save.should be_false
event_session.errors[:user].should == ["specify band, or user. not both"]
end
end

View File

@ -0,0 +1,25 @@
require 'spec_helper'
describe Event do
it "should be creatable" do
FactoryGirl.create(:event)
end
it "should not have duplicate slugs" do
event1 = FactoryGirl.create(:event)
dup = FactoryGirl.build(:event, slug: event1.slug)
dup.save.should be_false
dup.errors[:slug].should == ["has already been taken"]
end
it "can have associated event session, then destroy it by destroying event" do
event = FactoryGirl.create(:event)
event_session = FactoryGirl.create(:event_session, event: event)
event.reload
event.event_sessions.length.should == 1
event_session.event.should == event
event.destroy
EventSession.find_by_id(event_session.id).should be_nil
end
end

View File

@ -158,8 +158,8 @@ describe Feed do
# creates both recording and history record in feed
claimed_recording1 = FactoryGirl.create(:claimed_recording)
# move the feed entry created for the recording back more than a months ago
claimed_recording1.recording.feed.created_at = 25.hours.ago
# move the feed entry created for the recording back more than a day ago
claimed_recording1.recording.feed.created_at = 48.hours.ago
claimed_recording1.recording.feed.save!
feeds, start = Feed.index(user1, :type => 'recording', time_range: 'today')

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,32 @@
(function(context,$) {
"use strict";
context.JK = context.JK || {};
context.JK.FeedScreen = function(app) {
var logger = context.JK.logger;
var rest = context.JK.Rest();
function beforeShow(data) {
}
function afterShow(data) {
}
function initialize() {
var screenBindings = {
'beforeShow': beforeShow,
'afterShow': afterShow
};
app.bindScreen('feed', screenBindings);
}
this.initialize = initialize;
return this;
}
})(window,jQuery);

View File

@ -106,7 +106,8 @@
description: data.description,
caption: data.caption,
name: data.title,
picture: data.photo_url
picture: data.photo_url,
link: data.url
})
.done(function(response) {
checkShareCheckbox('facebook', false);
@ -163,7 +164,8 @@
description: data.description,
caption: data.caption,
name: data.title,
picture: data.photo_url
picture: data.photo_url,
link: data.url
})
.done(function(response) {
checkShareCheckbox('facebook', false);

View File

@ -8,7 +8,7 @@
$.each($(customSelector ? customSelector : '.inprogress .tick-duration'), function(index, item) {
var $duration = $(item);
var createdAt = new Date(Number($duration.attr('data-created-at')) * 1000)
var millisElapsed = (new Date()).getTime() - createdAt.getTime();
var millisElapsed = (context.JK.nowUTC().getTime() - createdAt.getTime());
$duration.text(context.JK.prettyPrintSeconds( parseInt(millisElapsed / 1000)));
});
}, 333);

View File

@ -718,6 +718,10 @@
return deviceId;
}
context.JK.nowUTC = function() {
var d = new Date();
return new Date( d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds() );
}
/*
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
* Digest Algorithm, as defined in RFC 1321.

View File

@ -9,27 +9,27 @@
var rest = context.JK.Rest();
var dialogId = '#video-dialog';
function videoClick(e) {
var $self = $(this);
if (!context.jamClient || !context.jamClient.IsNativeClient()) {
$('#video-dialog-header').html($self.data('video-header') || $self.attr('data-video-header'));
$('#video-dialog-iframe').attr('src', $self.data('video-url') || $self.atr('data-video-url'));
app.layout.showDialog('video-dialog');
e.stopPropagation();
e.preventDefault();
return false;
}
else {
var videoUrl = $.param.querystring(window.location.href, 'showVideo=' + encodeURIComponent($self.data('video-url')));
console.log("videoUrl: ", videoUrl);
context.jamClient.OpenSystemBrowser(videoUrl);
}
}
function events() {
$('.carousel .slides').on('click', '.slideItem', function (e) {
var $self = $(this);
if (!context.jamClient || !context.jamClient.IsNativeClient()) {
$('#video-dialog-header').html($self.data('video-header'));
$('#video-dialog-iframe').attr('src', $self.data('video-url'));
app.layout.showDialog('video-dialog');
e.stopPropagation();
e.preventDefault();
return false;
}
else {
var videoUrl = $.param.querystring(window.location.href, 'showVideo=' + encodeURIComponent($self.data('video-url')));
console.log("videoUrl: ", videoUrl);
context.jamClient.OpenSystemBrowser(videoUrl);
}
})
$('.carousel .slides').on('click', '.slideItem', videoClick);
$('.video-slide').on('click', videoClick);
$(dialogId + '-close').click(function (e) {
app.layout.closeDialog('video-dialog');

View File

@ -518,3 +518,10 @@ div[layout-id=session], div[layout-id=ftue], .no-selection-range {
margin:0 10px 0 3px;
}
}
hr {
background-color: #999999;
border: 0 none;
height: 1px;
margin: 10px 0;
}

View File

@ -16,8 +16,12 @@
float:left;
}
span.testimonial-user-desc {
font-style: italic !important;
ul.quote {
li {
font-style: italic !important;
color: white !important;
}
}
span.press-date {
@ -113,6 +117,10 @@ body.corporate ul {
padding-left:20px;
margin-bottom:20px;
list-style:disc outside none;
li {
padding-bottom: 10px;
}
}
.wrapper {

View File

@ -0,0 +1,25 @@
.landing-band.event {
}
.landing-details.event {
.time {
margin-bottom:30px;
}
.bio {
line-height:16px;
margin-bottom:20px;
}
.session-button {
img.listen-now-play {
vertical-align: top;
padding-right:7px;
}
}
}
.landing-sidebar {
.sponsor {
margin-bottom:50px;
}
}

View File

@ -52,6 +52,7 @@ body.web {
color:#ed3718;
font-size:26px;
font-weight:300;
line-height:30px;
}
}
@ -68,18 +69,48 @@ body.web {
width:100%;
min-height: 366px;
position:relative;
padding-top:15px;
padding-bottom:30px;
p {
color:#CCC;
line-height:150%;
}
.welcome {
padding-top: 30px;
}
.wrapper h1 {
position:relative;
padding-top:15px;
color:#ed3718 !important;
font-weight:normal;
font-size:40px;
}
.wrapper h2 {
font-weight:300;
}
h2 {
font-size:24px;
color:#ccc;
font-weight:200;
}
.button-orange, .button-grey {
display:inline-block;
height: 20px;
line-height:20px;
margin-bottom:10px;
margin-right:0px;
}
.button-grey:hover {
cursor:default;
background-color:#666;
color:#ccc;
}
}
.landing-sidebar {
@ -217,10 +248,17 @@ body.web {
p, ul {
color:#999;
line-height:160%;
margin-bottom:20px;
width:90%;
white-space:normal;
}
ul {
font-size:16px;
margin-bottom:20px;
}
p {
font-size:14px;
}
h2 {
@ -383,6 +421,14 @@ body.web {
padding-left:40px;
}
}
.congratulations {
padding-top:1px;
}
.recordings-page, .sessions-page {
padding-top:15px;
}
}

View File

@ -18,5 +18,6 @@
*= require web/recordings
*= require web/welcome
#= require web/sessions
*= require web/events
*= require users/signinDialog
*/

View File

@ -81,7 +81,7 @@ Version: 1.1
{
display:none;
position :relative ;
margin :30px auto 0;
margin :15px auto 0;
width :auto;
height :auto;
border :none;

View File

@ -531,6 +531,7 @@ class ApiUsersController < ApiController
description: view_context.description_for_music_session_history(history),
title: view_context.title_for_music_session_history(history, current_user),
photo_url: view_context.facebook_image_for_music_session_history(history),
url: share_token_url(history.share_token.token),
caption: 'www.jamkazam.com'
}, status: 200
@ -557,6 +558,7 @@ class ApiUsersController < ApiController
description: view_context.description_for_claimed_recording(claimed_recording),
title: view_context.title_for_claimed_recording(claimed_recording, current_user),
photo_url: view_context.facebook_image_for_claimed_recording(claimed_recording),
url: share_token_url(claimed_recording.share_token.token),
caption: 'www.jamkazam.com'
}, status: 200

View File

@ -0,0 +1,10 @@
class EventsController < ApplicationController
respond_to :html
def show
@event = Event.find_by_slug!(params[:slug])
render 'event', :layout => "web"
end
end

View File

@ -198,10 +198,10 @@ class UsersController < ApplicationController
def welcome
@slides = [
Slide.new("JamKazam Overview", "web/carousel_musicians.jpg", "http://www.youtube.com/embed/eaYNM7p6Z5s"),
Slide.new("JamKazam Overview", "web/carousel_musicians.jpg", "http://www.youtube.com/embed/ylYcvTY9CVo?autoplay=1"),
Slide.new("Getting Started", "web/carousel_fans.jpg", "http://www.youtube.com/embed/eaYNM7p6Z5s"),
Slide.new("Playing in a Session", "web/carousel_bands.jpg", "http://www.youtube.com/embed/eaYNM7p6Z5s"),
Slide.new("JamKazam Overview", "web/carousel_musicians.jpg", "http://www.youtube.com/embed/eaYNM7p6Z5s"),
Slide.new("JamKazam Overview", "web/carousel_musicians.jpg", "http://www.youtube.com/embed/ylYcvTY9CVo?autoplay=1"),
Slide.new("Getting Started", "web/carousel_fans.jpg", "http://www.youtube.com/embed/eaYNM7p6Z5s"),
Slide.new("Playing in a Session", "web/carousel_bands.jpg", "http://www.youtube.com/embed/eaYNM7p6Z5s")
]

View File

@ -22,11 +22,15 @@ module AvatarHelper
end
end
def resolve_avatarables(*avatarables)
def resolve_avatarables(*avatarables, allow_none: false)
avatarables.each do |avatarable|
return resolve_avatarable(avatarable) if avatarable
end
raise "at least one avatarable must be specified"
if allow_none
nil
else
raise "at least one avatarable must be specified"
end
end
end

View File

@ -0,0 +1,101 @@
module EventSessionHelper
def event_session_img(event_session)
# need to figure out img, width, height
# prefer the session URL if specified; otherwise use the band/user
url = nil
width = nil
height = nil
if event_session.img_url
url = image_path(event_session.img_url)
else
url = resolve_avatarables(event_session.band, event_session.user, allow_none: true)
url = image_path(url) if url
end
if url
width = event_session.img_width
height = event_session.img_height
else
url = image_path('web/logo-256.png')
width = 115
end
content_tag(:img, nil, src: url, width: width, height: height)
end
def event_session_title(event_session)
return event_session.band.name if event_session.band
return event_session.user.name if event_session.user
'TBD'
end
def event_session_start_hour(event_session)
return 'TBD' unless event_session.starts_at
timezone = ActiveSupport::TimeZone.new('Central Time (US & Canada)')
timezone.at(event_session.starts_at.to_i).strftime('%l:%M %P')
end
def fetch_last_session(event_session)
# if no pinned state, then we try to find if there is a session currently on going during the specified time range
# if so, then we are playing.
# if there has been none, we say it's still coming,
# if there has been at least one, and it's over, we say session over
query = MusicSessionHistory.where(fan_access: true).where(created_at: (event_session.starts_at - 12.hours)..(event_session.ends_at + 12.hours))
if event_session.user_id
query = query.where(user_id: event_session.user_id)
elsif event_session.band_id
query = query.where(band_id: event_session.band_id)
else
raise 'invalid state in event_session_button'
end
query.order('created_at DESC').first
end
def event_session_button(event_session)
state = nil # can be :not_started, :over, :playing
state = event_session.pinned_state if event_session.pinned_state
if state
music_session_history = fetch_last_session(event_session)
elsif !state && (event_session.starts_at && event_session.ends_at && (event_session.user_id || event_session.band_id))
music_session_history = fetch_last_session(event_session)
if music_session_history
if music_session_history.session_removed_at
state = 'over'
else
state = 'playing'
end
else
state = 'not_started'
end
end
if state == 'over'
content_tag(:a, 'SESSION ENDED', href: music_session_history.nil? ? '#' : music_session_detail_path(music_session_history.id), class: 'button-grey')
elsif state == 'playing'
content_tag(:a, '', href: music_session_detail_path(music_session_history.id), class: 'button-orange') do
content_tag(:span, image_tag('content/icon_playbutton.png', :width => 20, height: 20, align: 'absmiddle', class:'listen-now-play') + 'LISTEN NOW')
end
elsif state == 'not_started'
nil
else
nil
end
end
def event_session_description(event_session)
return event_session.band.biography if event_session.band
return event_session.user.biography if event_session.user
''
end
end

View File

@ -1,49 +0,0 @@
<!-- Feed Screen -->
<%= content_tag(:div, :layout => 'screen', 'layout-id' => 'feed', :class => "screen secondary") do -%>
<%= content_tag(:div, :class => :content) do -%>
<%= content_tag(:div, :class => 'content-head') do -%>
<%= content_tag(:div, image_tag("content/icon_feed.png", {:height => 19, :width => 19}), :class => 'content-icon') %>
<%= content_tag(:h1, 'feed') %>
<%= render "screen_navigation" %>
<% end -%>
<%= content_tag(:div, :class => 'content-body') do -%>
<%= form_tag('', {:id => 'find-session-form', :class => 'inner-content'}) do -%>
<%= render(:partial => "web_filter", :locals => {:search_type => Search::PARAM_FEED}) %>
<%= content_tag(:div, :class => 'filter-body') do %>
<%= content_tag(:div, :class => 'content-body-scroller') do -%>
<p>This feature not yet implemented</p>
<%= content_tag(:div, content_tag(:div, '', :id => 'session-filter-results', :class => 'filter-results'), :class => 'content-wrapper') %>
<% end -%>
<% end -%>
<% end -%>
<% end -%>
<% end -%>
<% end -%>
<!-- Session Row Template -->
<script type="text/template" id="template-find-session-row">
<div class="profile-band-list-result band-list-result">
<div class="left">
<!-- avatar -->
<div class="avatar-small"><img src="{avatar_url}" /></div>
<!-- name & location -->
<div style="width:220px;" class="result-name">{band_name}<br />
<span class="result-location">{band_location}</span>
<br /><br />
<div id="result_sessions" class="nowrap">{genres}</div>
<br /><br />
{follow_count} <img src="../assets/content/icon_followers.png" width="22" height="12" align="absmiddle" />&nbsp;&nbsp;&nbsp;{recording_count} <img src="../assets/content/icon_recordings.png" width="12" height="13" align="absmiddle" />&nbsp;&nbsp;&nbsp;{session_count} <img src="../assets/content/icon_session_tiny.png" width="12" height="12" align="absmiddle" /><br /><br />
</div>
</div>
<div class="left ml20 f11 whitespace w35"><br />
{biography}<br />
<br />
<div data-band-id={band_id}>{band_action_template}</div>
</div>
<div class="left ml10 w25 band-players">
<table class="musicians" cellpadding="0" cellspacing="5">{band_player_template}</table>
</div>
<br clear="all" />
</div>
</script>

View File

@ -0,0 +1,13 @@
%div{ layout: 'screen', :'layout-id' => 'feed', :class => 'screen secondary'}
.content
.content-head
.content-icon= image_tag("content/icon_feed.png", {:height => 19, :width => 19})
%h1 feed
= render "screen_navigation"
.content-body
= form_tag('', {:id => 'find-session-form', :class => 'inner-content'}) do
= render(:partial => "web_filter", :locals => {:search_type => Search::PARAM_FEED})
.filter-body
.content-body-scroller
%p This feature not yet implemented
.content-wrapper

View File

@ -184,6 +184,9 @@
var bandSetupPhotoScreen = new JK.BandSetupPhotoScreen(JK.app);
bandSetupPhotoScreen.initialize();
var feedScreen = new JK.FeedScreen(JK.app);
feedScreen.initialize();
var findSessionScreen = new JK.FindSessionScreen(JK.app);
var sessionLatency = null;
if ("jamClient" in window) {

View File

@ -3,13 +3,15 @@
<h1>Testimonials</h1>
<br/>
<p><h2>JULIE BONK</h2></p>
<p>
<span class="testimonial-user-desc">Julie is a well-respected and oft-recorded pianist and teacher in jazz and blues improvisation, composition, and theory, and has mentored students including Grammy winner Norah Jones.</span><br/>
<ul class="no-style">
<li>I've been hoping and waiting for something like this for a long time. It's amazing!</li>
<li>I use Skype to give music lessons, but it won't work for rehearsals, and this will really help with preparation.</li>
<li>I'll be using this every week.</li>
<span>Julie is a well-respected and oft-recorded pianist and teacher in jazz and blues improvisation, composition, and theory, and has mentored students including Grammy winner Norah Jones.</span><br/>
<ul class="no-style quote">
<li>&quot;I've been hoping and waiting for something like this for a long time. It's amazing!&quot;</li>
<li>&quot;I use Skype to give music lessons, but it won't work for rehearsals, and this will really help with preparation.&quot;</li>
<li>"I'll be using this every week.&quot;</li>
</ul>
</p>
@ -17,11 +19,11 @@
<p><h2>JUSTIN PIERCE</h2></p>
<p>
<div class="testimonial-user-desc">Justin holds a master's degree in jazz studies, performs regularly with bands in Texas, and has played with John Clayton, The Temptations, Wayne Newton, and others.</div><br/>
<ul class="no-style">
<li>The sound quality is exceptional. It reminds me of recording in a studio.</li>
<li>The service met and exceeded my expectations, and I'm excited for its future.</li>
<li>The sound quality is significantly better than Skype for giving remote lessons.</li>
<span>Justin holds a master's degree in jazz studies, performs regularly with bands in Texas, and has played with John Clayton, The Temptations, Wayne Newton, and others.</span><br/>
<ul class="no-style quote">
<li>&quot;The sound quality is exceptional. It reminds me of recording in a studio.&quot;</li>
<li>&quot;The service met and exceeded my expectations, and I'm excited for its future.&quot;</li>
<li>&quot;The sound quality is significantly better than Skype for giving remote lessons.&quot;</li>
</ul>
</p>
@ -29,10 +31,10 @@
<p><h2>CHRIS BENNETT</h2></p>
<p>
<div class="testimonial-user-desc">Chris has decades of musical experience in touring bands, has been published in Classic Drummer and Not So Modern Drummer, started Bopworks Drumsticks, and teaches at the Austin School of Music.</div><br/>
<ul class="no-style">
<li>Far from being futuristically cold and isolating, playing was way easier than a studio.</li>
<li>Given the fact that I had never met, seen, or played with any of the musicians I played with using JamKazam, the experience was great.</li>
<span>Chris has decades of musical experience in touring bands, has been published in Classic Drummer and Not So Modern Drummer, started Bopworks Drumsticks, and teaches at the Austin School of Music.</span><br/>
<ul class="no-style quote">
<li>&quot;Far from being futuristically cold and isolating, playing was way easier than a studio.&quot;</li>
<li>&quot;Given the fact that I had never met, seen, or played with any of the musicians I played with using JamKazam, the experience was great.&quot;</li>
</ul>
</p>
@ -40,10 +42,10 @@
<p><h2>SARA NELSON</h2></p>
<p>
<div class="testimonial-user-desc">Sara holds a bachelor's degree in music, performs as a cellist for the Austin Lyric Opera, and has also played with the Austin Symphony Orchestra and bands such as David Byrne and Bob Schneider.</div><br/>
<ul class="no-style">
<li>JamKazam is a great tool for musicians who want to rehearse without the hassle of finding a common location to meet.</li>
<li>Being able to play with people in different cities is very exciting!</li>
<span>Sara holds a bachelor's degree in music, performs as a cellist for the Austin Lyric Opera, and has also played with the Austin Symphony Orchestra and bands such as David Byrne and Bob Schneider.</span><br/>
<ul class="no-style quote">
<li>&quot;JamKazam is a great tool for musicians who want to rehearse without the hassle of finding a common location to meet.&quot;</li>
<li>&quot;Being able to play with people in different cities is very exciting!&quot;</li>
</ul>
</p>
@ -51,10 +53,10 @@
<p><h2>GEORGE PRADO</h2></p>
<p>
<div class="testimonial-user-desc">George spent 15 years in LA performing and recording with numerous industry greats including Eartha Kitt and Chuck Berry, founded the legendary Regency Jazz Band, and has played and taught 40+ years.</div><br/>
<ul class="no-style">
<li>An exciting tool with great sound and fidelity, this has unlimited potential for musicians and others in the entertainment industry, not to mention its use for educators. Check it out!</li>
<li>Working for over 50 years as a professional musician, I am excited about this new method of sharing musical ideas. It allows me to connect with friends far away and close by through the means of this technology.</li>
<span>George spent 15 years in LA performing and recording with numerous industry greats including Eartha Kitt and Chuck Berry, founded the legendary Regency Jazz Band, and has played and taught 40+ years.</span><br/>
<ul class="no-style quote">
<li>&quot;An exciting tool with great sound and fidelity, this has unlimited potential for musicians and others in the entertainment industry, not to mention its use for educators. Check it out!&quot;</li>
<li>&quot;Working for over 50 years as a professional musician, I am excited about this new method of sharing musical ideas. It allows me to connect with friends far away and close by through the means of this technology.&quot;</li>
</ul>
</p>
@ -62,10 +64,10 @@
<p><h2>CHRIS MAX</h2></p>
<p>
<div class="testimonial-user-desc">Chris is the lead guitar player and backing vocalist for LC Rocks, which has been playing top Austin clubs since 2002, and also frequently plays solo gigs throughout Central Texas.</div><br/>
<ul class="no-style">
<li>JamKazam, now here's a concept whose time has come!</li>
<li>No loading gear, driving, or rehearsal room rental fees. Just plug into your home computer, interface with your bandmates, and start getting ready for your upcoming shows.</li>
<span>Chris is the lead guitar player and backing vocalist for LC Rocks, which has been playing top Austin clubs since 2002, and also frequently plays solo gigs throughout Central Texas.</span><br/>
<ul class="no-style quote">
<li>&quot;JamKazam, now here's a concept whose time has come!&quot;</li>
<li>&quot;No loading gear, driving, or rehearsal room rental fees. Just plug into your home computer, interface with your bandmates, and start getting ready for your upcoming shows.&quot;</li>
</ul>
</p>

View File

@ -0,0 +1,20 @@
%hr{ class:'w60' }
.landing-band.event
= event_session_img(event_session)
%br
%br
%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)
%br{ clear:'all' }
.left.bio
= event_session_description(event_session)
%br
%br
%br{ clear:'all' }

View File

@ -0,0 +1,31 @@
- provide(:title, @event.title)
.landing-content
%h1= @event.title
%p.w60= raw(@event.description)
%br
%br
%h2 ARTIST LINEUP
%br
= render :partial => "event_session", :collection => @event.event_sessions
%br{clear:'all'}
.landing-sidebar
%br
%br
%div{align:'center'}
- if @event.show_sponser?
.sponsor
%span SPONSORED BY:
%a{href: 'http://www.centurylinktechnology.com/'}
= image_tag 'content/logo_centurylink.png', width:320, height:80, alt:'CenturyLink logo'
%div{align: 'center'} LEARN ABOUT JAMKAZAM</div>
%br
= image_tag 'web/carousel_musicians.jpg', width:350, alt:'JamKazam Overview', class: 'video-slide',
:'data-video-header' => 'JamKazam Overview', :'data-video-url' => 'http://www.youtube.com/embed/ylYcvTY9CVo?autoplay=1'
%br{clear:'all'}

View File

@ -16,7 +16,8 @@
<meta name="twitter:description" content="<%= description_for_music_session_history(@music_session) %>" />
<% end %>
<div class="landing-band">
<div class="sessions-page">
<div class="landing-band">
<% unless @music_session.band.nil? %>
<div class="landing-avatar">
<% unless @music_session.band.photo_url.blank? %>
@ -79,7 +80,28 @@
<%= render :partial => "shared/track_details", :locals => {:tracks => @music_session.grouped_tracks} %>
</div>
<br clear="all" />
</div>
<% if true %>
<div class="landing-sidebar" style="z-index:900;"><br />
<br>
<br>
<div align="center">
<div class="sponsor">
<span>SONSORED BY:</span>
<a href="http://www.centurylinktechnology.com/">
<%= image_tag 'content/logo_centurylink.png', width:320, height:80, alt:'CenturyLink logo'%>
</a>
</div>
<div align="center">LEARN ABOUT JAMKAZAM</div>
<br>
<%= image_tag('web/carousel_musicians.jpg', width:350, alt:'JamKazam Overview',
class: 'video-slide', :'data-video-header' => 'JamKazam Overview',
:'data-video-url' => 'http://www.youtube.com/embed/ylYcvTY9CVo?autoplay=1') %>
</div>
</div>
<% else %>
<% if signed_in? %>
<% unless @music_session.band.nil? %>
<%= render :partial => "shared/landing_sidebar", :locals => {:user => @music_session.band, :recent_history => @music_session.band.recent_history} %>
@ -89,6 +111,7 @@
<% else %>
<%= render :partial => "shared/cta_sidebar" %>
<% end %>
<% end %>
<% content_for :after_black_bar do %>
<br />

View File

@ -17,6 +17,7 @@
<meta name="twitter:description" content="<%= description_for_claimed_recording(@claimed_recording) %>" />
<% end %>
<div class="recordings-page">
<div class="landing-band">
<% unless @claimed_recording.recording.band.blank? %>
<div class="landing-avatar">
@ -89,7 +90,7 @@
<%= render :partial => "shared/track_details", :locals => {:tracks => @claimed_recording.recording.grouped_tracks} %>
</div>
<br clear="all" />
</div>
<% if signed_in? %>
<% unless @claimed_recording.recording.band.nil? %>
<%= render :partial => "shared/landing_sidebar", :locals => {:user => @claimed_recording.recording.band, :recent_history => @claimed_recording.recording.band.recent_history} %>
@ -114,47 +115,6 @@
$(function () {
var showRecording = new JK.ShowRecording(JK.app);
showRecording.initialize("<%= @claimed_recording.id %>", "<%= @claimed_recording.recording_id %>");
$("#recordingDuration").html(formatTime("<%= @claimed_recording.recording.duration %>"));
// remainder of this code is related to playing/pausing the recording
var htmlAudio = $(".recording-controls").find('audio').get(0);
var $imgPlayPauseSelector = $("#imgPlayPause");
var playButtonPath = '/assets/content/icon_playbutton.png';
var pauseButtonPath = '/assets/content/icon_pausebutton.png';
var durationInitialized = false;
function formatTime(time) {
var minutes = Math.floor(time / 60);
var seconds = Math.floor(time % 60);
return minutes.toString() + ":" + (seconds > 9 ? seconds.toString() : '0' + seconds.toString());
}
// this sets the slider to the appropriate position and updates the current play time
$(htmlAudio).on('timeupdate', function() {
var percentComplete = (htmlAudio.currentTime / htmlAudio.duration) * 100;
$(".recording-slider").css({'left': percentComplete + '%'});
$(".recording-current").html(formatTime(htmlAudio.currentTime));
// reset icon to play and slider to far left when done
if (percentComplete === 100) {
$imgPlayPauseSelector.attr('src', playButtonPath);
$(".recording-slider").css({'left': 0 + '%'});
$(".recording-current").html("0:00");
}
});
$("#btnPlayPause").click(function() {
if (htmlAudio.paused) {
htmlAudio.play();
$imgPlayPauseSelector.attr('src', pauseButtonPath);
}
else {
htmlAudio.pause();
$imgPlayPauseSelector.attr('src', playButtonPath);
}
});
});
</script>
<% end %>

View File

@ -4,7 +4,7 @@
<a href="/signin">Already have an account?</a>
</div>
<br /><br />
<%= image_tag "web/carousel_musicians.jpg", {:width => 350, :alt => ""} %><br /><br />
<%= image_tag "web/carousel_musicians.jpg", {:width => 350, :alt => "", :class => 'video-slide', :'data-video-header' => 'JamKazam Overview', :'data-video-url' => 'http://www.youtube.com/embed/ylYcvTY9CVo?autoplay=1'} %><br /><br />
<%= image_tag "web/carousel_fans.jpg", {:width => 350, :alt => ""} %><br /><br />
<%= image_tag "web/carousel_bands.jpg", {:width => 350, :alt => ""} %>
</div>

View File

@ -0,0 +1,72 @@
%script {type: 'text/javascript'}
.feed-entry.music-session-history-entry{'data-music-session' => feed_item.id}
/ avatar
.avatar-small.ib
= session_avatar(feed_item)
/ type and artist
.left.ml20.w15
.title{hoveraction: 'session', :'session-id' => feed_item.id } SESSION
.artist
= session_artist_name(feed_item)
= timeago(feed_item.created_at, class: 'small created_at')
/ name and description
.left.ml20.w30
.description.dotdotdot
= session_description(feed_item)
/ timeline and controls
.right.w40
/ recording play controls
.session-controls{ class: "#{(feed_item.is_over? ? 'ended' : 'inprogress')} #{feed_item.has_mount? ? 'has-mount' : 'no-mount'}", 'data-music-session' => feed_item.id, 'fan-access' => feed_item.fan_access.to_s}
/ session status
%a.left.play-button{href:'#'}
= image_tag 'content/icon_playbutton.png', width:20, height:20, class:'play-icon'
- if feed_item.has_mount?
%audio{preload: 'none'}
%source{src: feed_item.music_session.mount.url, type: feed_item.music_session.mount.resolve_string(:mime_type)}
%span.session-status
= session_text(feed_item)
/ current playback time
= session_duration(feed_item, class: 'session-duration tick-duration recording-current', 'data-created-at' => feed_item.created_at.to_i)
/ end recording play controls
/ genre and social
.left.small
= session_genre(feed_item)
.right.small.feed-details
%span.play-count
%span.plays
= feed_item.play_count
= image_tag 'content/icon_arrow.png', :height => "12", :width => "7"
%span.comment-count
%span.comments
= feed_item.comment_count
= image_tag 'content/icon_comment.png', :height => "12", :width => "13"
%span.like-count
%span.likes
= feed_item.like_count
= image_tag 'content/icon_like.png', :height => "12", :width => "12"
%a.details{:href => "#"} Details
%a.details-arrow.arrow-down-orange{:href => "#"}
%br/
.musician-detail.hidden
/ sub-table of musicians
%table.musicians{:cellpadding => "0", :cellspacing => "5"}
%tbody
- feed_item.unique_user_histories.each do |user|
%tr
%td{:width => "24"}
%a.avatar-tiny{:href => "#"}
= render_avatarable(user)
%td
%a{:href => "#"}
= "#{user.first_name} #{user.last_name}"
%td
.nowrap
- if user.total_instruments
- user.total_instruments.split('|').uniq.each do |instrument_id|
%img.instrument-icon{'instrument-id' =>instrument_id, height:24, width:24}
- else
%img.instrument-icon{'instrument-id' =>'default', height:24, width:24}
%br{:clear => "all"}/
%br/

View File

@ -1,11 +1,13 @@
<% provide(:title, 'Congratulations') %>
<div class="tagline">Congratulations!</div>
<div class="congratulations">
<div class="tagline">Congratulations!</div>
<p>You have successfully registered as a JamKazam fan.</p>
<p>You have successfully registered as a JamKazam fan.</p>
<div class="proceed"><%= link_to "PROCEED TO JAMKAZAM SITE", client_path, :class =>"button-orange m0" %></div>
<div class="proceed"><%= link_to "PROCEED TO JAMKAZAM SITE", client_path, :class =>"button-orange m0" %></div>
</div>
<script type="text/javascript">
window.congratulations.initialize(false, jQuery.QueryString["type"]);
</script>

View File

@ -1,9 +1,11 @@
<% provide(:title, 'Congratulations') %>
<% if @nativeClient %>
<div class="tagline">Congratulations!</div>
<p>You have successfully registered as a musician.</p>
<div class="proceed"><%= link_to "PROCEED TO JAMKAZAM SITE", client_path, :class =>"button-orange m0" %></div>
<div class="congratulations">
<div class="tagline">Congratulations!</div>
<p>You have successfully registered as a musician.</p>
<div class="proceed"><%= link_to "PROCEED TO JAMKAZAM SITE", client_path, :class =>"button-orange m0" %></div>
</div>
<% else %>
<%= render "users/downloads" %>
<% end %>

View File

@ -60,8 +60,9 @@ SampleApp::Application.routes.draw do
match '/gmail_contacts', to: 'gmail#gmail_contacts'
match '/events/:slug', to: 'events#show', :via => :get
# temporarily allow for debugging
# temporarily allow for debugging--only allows admini n
match '/listen_in', to: 'spikes#listen_in'
# embed resque-web if this is development mode

View File

@ -390,4 +390,14 @@ FactoryGirl.define do
mix.recording.claimed_recordings << FactoryGirl.create(:claimed_recording, user: user, recording: mix.recording)
}
end
factory :event, :class => JamRuby::Event do
sequence(:slug) { |n| "slug-#{n}" }
title 'event title'
description 'event description'
end
factory :event_session, :class => JamRuby::EventSession do
end
end

View File

@ -0,0 +1,114 @@
require 'spec_helper'
describe "Events", :js => true, :type => :feature, :capybara_feature => true, :slow => true do
subject { page }
before(:all) do
Capybara.javascript_driver = :poltergeist
Capybara.current_driver = Capybara.javascript_driver
Capybara.default_wait_time = 30 # these tests are SLOOOOOW
end
before(:each) do
UserMailer.deliveries.clear
MusicSession.delete_all
@event = FactoryGirl.create(:event, :slug => 'so_latency', show_sponser:true)
visit "/events/so_latency"
end
it "should not break as page gets more and more well-defined" do
find('h1', text: @event.title)
find('h2', text: 'ARTIST LINEUP')
find('p', text: @event.description)
find('.landing-sidebar')
find('.sponsor span', 'SPONSORED BY:')
# add an event session to the event, with nothing defined
@event_session = FactoryGirl.create(:event_session, event: @event)
visit "/events/so_latency"
find('.landing-band.event img')['src'].should == '/assets/web/logo-256.png'
find('.event-title', text: 'TBD')
find('.time strong', text: 'TBD')
# define the event better by associating with a band
band = FactoryGirl.create(:band)
@event_session.band = band
@event_session.save!
visit "/events/so_latency"
find('.landing-details.event .bio', text: band.biography)
find('.landing-band.event img')['src'].should == '/assets/shared/avatar_generic_band.png'
# update starts at
starts_at = 1.hours.ago
@event_session.starts_at = starts_at
@event_session.save!
visit "/events/so_latency"
timezone = ActiveSupport::TimeZone.new('Central Time (US & Canada)')
find('.time strong', text: timezone.at(@event_session.starts_at.to_i).strftime('%l:%M %P').strip)
# update ends at
ends_at = 1.hours.from_now
@event_session.ends_at = ends_at
@event_session.save!
visit "/events/so_latency"
# UI shouldn't change; as long as it doesn't crash we are OK
# now start a session, and don't sent session_removed_at
music_session = FactoryGirl.create(:music_session, band: band)
music_session_history = music_session.music_session_history
music_session_history.session_removed_at.should be_nil
visit "/events/so_latency"
find('.landing-details .session-button span', text:'LISTEN NOW')
find('.landing-details .session-button a').trigger(:click)
find('.sessions-page .landing-band', text: band.name) # indication of session landing page
find(".recording-controls[data-music-session=\"#{music_session_history.id}\"]")
# force the pinned_state to say 'not_started'
@event_session.pinned_state = 'not_started'
@event_session.save!
visit "/events/so_latency"
expect(page).not_to have_css('.landing-details .session-button a') # no button at all
# force the pinned_state to say 'not_started'
@event_session.pinned_state = 'over'
@event_session.save!
visit "/events/so_latency"
find('.landing-details .session-button a', text:'SESSION ENDED').trigger(:click)
find('.sessions-page .landing-band', text: band.name) # indication of session landing page
find(".recording-controls[data-music-session=\"#{music_session_history.id}\"]")
# unpin
@event_session.pinned_state = nil
@event_session.save!
# turn fan_access = false... this will hide the button
music_session_history.fan_access = false
music_session_history.save!
visit "/events/so_latency"
expect(page).not_to have_css('.landing-details .session-button a') # no button at all
# now start a second session, and don't sent session_removed_at
music_session = FactoryGirl.create(:music_session, band: band)
music_session_history = music_session.music_session_history
music_session_history.session_removed_at.should be_nil
visit "/events/so_latency"
find('.landing-details .session-button span', text:'LISTEN NOW')
find('.landing-details .session-button a').trigger(:click)
find('.sessions-page .landing-band', text: band.name) # indication of session landing page
find(".recording-controls[data-music-session=\"#{music_session_history.id}\"]")
visit "/events/so_latency"
# then end it, and see session_ended
music_session_history = music_session.music_session_history
music_session_history.session_removed_at = Time.now
music_session_history.save!
visit "/events/so_latency"
find('.landing-details .session-button a', text:'SESSION ENDED').trigger(:click)
find('.sessions-page .landing-band', text: band.name) # indication of session landing page
find(".recording-controls[data-music-session=\"#{music_session_history.id}\"]")
end
end