attach notations, audio, and recordings done

This commit is contained in:
Seth Call 2016-05-26 16:25:51 -05:00
parent 5939079a89
commit cc3576f70f
37 changed files with 451 additions and 96 deletions

View File

@ -354,3 +354,4 @@ lesson_booking_success.sql
user_origin.sql
remove_stripe_acct_id.sql
track_user_on_lesson.sql
audio_in_music_notations.sql

View File

@ -0,0 +1,5 @@
ALTER TABLE music_notations ADD COLUMN attachment_type VARCHAR NOT NULL DEFAULT 'notation';
ALTER TABLE chat_messages ADD PRIMARY KEY (id);
ALTER TABLE music_notations ADD PRIMARY KEY (id);
ALTER TABLE chat_messages ADD COLUMN music_notation_id VARCHAR(64) REFERENCES music_notations(id);
ALTER TABLE chat_messages ADD COLUMN claimed_recording_id VARCHAR(64) REFERENCES claimed_recordings(id);

View File

@ -621,6 +621,10 @@ message ChatMessage {
optional string channel = 6;
optional string lesson_session_id = 7;
optional string purpose = 8;
optional string attachment_id = 9;
optional string attachment_type = 10;
optional string attachment_name = 11;
}
message SendChatMessage {

View File

@ -934,7 +934,7 @@ module JamRuby
@lesson_session = lesson_session
email = @student.email
subject = "You have used #{@student.remaining_test_drives} of #{@student.total_test_drives} TestDrive lesson credits"
subject = "You have used #{@student.used_test_drives} of #{@student.total_test_drives} TestDrive lesson credits"
unique_args = {:type => "student_test_drive_success"}
sendgrid_category "Notification"

View File

@ -20,6 +20,6 @@ class MusicNotationUploader < CarrierWave::Uploader::Base
end
def extension_white_list
%w(pdf png jpg jpeg gif xml mxl txt)
%w(pdf png jpg jpeg gif xml mxl txt wav flac ogg aiff aifc au)
end
end

View File

@ -1,4 +1,4 @@
<% provide(:title, "You have used #{@student.remaining_test_drives} of #{@student.total_test_drives} TestDrive lesson credits") %>
<% provide(:title, "You have used #{@student.used_test_drives} of #{@student.total_test_drives} TestDrive lesson credits") %>
<% provide(:photo_url, @teacher.resolved_photo_url) %>
<% content_for :note do %>

View File

@ -1,4 +1,4 @@
You have used <%= @student.remaining_test_drives %> of <%= @student.total_test_drives %> TestDrive lesson credits.
You have used <%= @student.used_test_drives %> of <%= @student.total_test_drives %> TestDrive lesson credits.
<% if @student.has_rated_teacher(@teacher) %>
Also, please rate your teacher at <%= @teacher.ratings_url %> now for todays lesson to help other students in the community find the best instructors.

View File

@ -993,7 +993,8 @@ module JamRuby
end
# creates the chat message
def chat_message(session_id, sender_name, sender_id, msg, msg_id, created_at, channel, lesson_session_id, purpose)
def chat_message(session_id, sender_name, sender_id, msg, msg_id, created_at, channel, lesson_session_id, purpose,
attachment_id, attachment_type, attachment_name)
chat_message = Jampb::ChatMessage.new(
:sender_id => sender_id,
:sender_name => sender_name,
@ -1002,7 +1003,10 @@ module JamRuby
:created_at => created_at,
:channel => channel,
:lesson_session_id => lesson_session_id,
:purpose => purpose
:purpose => purpose,
:attachment_id => attachment_id,
:attachment_type => attachment_type,
:attachment_name => attachment_name
)
if session_id

View File

@ -17,11 +17,13 @@ module JamRuby
belongs_to :music_session
belongs_to :target_user, class_name: "JamRuby::User"
belongs_to :lesson_session, class_name: "JamRuby::LessonSession"
belongs_to :music_notation, class_name: "JamRuby::MusicNotation"
belongs_to :claimed_recording, class_name: "JamRuby::ClaimedRecording"
validates :user, presence: true
validates :message, length: {minimum: 1, maximum: 255}, no_profanity: true, unless: :ignore_message_checks
def self.create(user, music_session, message, channel, client_id, target_user = nil, lesson_session = nil, purpose = nil)
def self.create(user, music_session, message, channel, client_id, target_user = nil, lesson_session = nil, purpose = nil, music_notation = nil, recording = nil)
chat_msg = ChatMessage.new
chat_msg.user_id = user.id
chat_msg.music_session_id = music_session.id if music_session
@ -30,6 +32,8 @@ module JamRuby
chat_msg.target_user = target_user
chat_msg.lesson_session = lesson_session
chat_msg.purpose = purpose
chat_msg.music_notation = music_notation
chat_msg.claimed_recording = recording
if lesson_session
@ -37,23 +41,25 @@ module JamRuby
if user.id == lesson_session.student.id
lesson_session.teacher_unread_messages = true
target = lesson_session.teacher
Notification.send_lesson_message('chat', lesson_session, false, message)
else
lesson_session.student_unread_messages = true
target = lesson_session.student
Notification.send_lesson_message('chat', lesson_session, true, message)
end
lesson_session.save(validate: false)
# a nil purpose means 'normal chat', which is the only time we should send an email
if !target_user.online? && purpose.nil? && message.present?
if !target.online? && purpose.nil? && message.present?
UserMailer.lesson_chat(chat_msg).deliver!
end
end
if chat_msg.save
ChatMessage.send_chat_msg music_session, chat_msg, user, client_id, channel, lesson_session, purpose, target_user
ChatMessage.send_chat_msg music_session, chat_msg, user, client_id, channel, lesson_session, purpose, target_user, music_notation, recording
end
chat_msg
end
@ -94,10 +100,24 @@ module JamRuby
end
end
def send_chat_msg(music_session, chat_msg, user, client_id, channel, lesson_session, purpose, target_user)
def send_chat_msg(music_session, chat_msg, user, client_id, channel, lesson_session, purpose, target_user, music_notation, claimed_recording)
music_session_id = music_session.id if music_session
lesson_session_id = lesson_session.id if lesson_session
if music_notation
puts "IS MUSIC NOTATION"
attachment_id = music_notation.id
attachment_type = music_notation.attachment_type
attachment_name = music_notation.file_name
elsif claimed_recording
attachment_id = claimed_recording.id
attachment_type = 'recording'
attachment_name = claimed_recording.name
end
puts "ATTACMENT #{}"
msg = @@message_factory.chat_message(
music_session_id,
user.name,
@ -107,7 +127,10 @@ module JamRuby
chat_msg.created_at.utc.iso8601,
channel,
lesson_session_id,
purpose
purpose,
attachment_id,
attachment_type,
attachment_name
)
if channel == 'session'
@ -116,6 +139,7 @@ module JamRuby
@@mq_router.publish_to_active_clients(msg)
elsif channel == 'lesson'
@@mq_router.publish_to_user(target_user.id, msg, sender = {:client_id => client_id})
@@mq_router.publish_to_user(user.id, msg, sender = {:client_id => client_id})
end
end

View File

@ -4,6 +4,10 @@ module JamRuby
NOTATION_FILE_DIR = "music_session_notations"
TYPE_NOTATION = 'notation'
TYPE_AUDIO = 'audio'
ATTACHMENT_TYPES = [TYPE_NOTATION, TYPE_AUDIO]
self.primary_key = 'id'
attr_accessible :file_url, :size, :file_name
@ -16,10 +20,12 @@ module JamRuby
before_destroy :delete_s3_files
#validates :file_url, :presence => true
validates :attachment_type, :presence => true, inclusion: {in: ATTACHMENT_TYPES}
validates :size, :presence => true
def self.create(session_id, file, current_user)
def self.create(session_id, type, file, current_user)
music_notation = MusicNotation.new
music_notation.attachment_type = type
music_notation.file_name = file.original_filename
music_notation.music_session_id = session_id
music_notation.user = current_user

View File

@ -201,7 +201,15 @@ module JamRuby
def has_access?(user)
users.exists?(user) || plays.where("player_id=?", user).count != 0
return false if user.nil?
users.exists?(user) || attached_with_lesson(user) #|| plays.where("player_id=?", user).count != 0
end
def attached_with_lesson(user)
ChatMessage.joins(:claimed_recording => [:recording]).where('recordings.id = ?', self.id).where('chat_messages.user_id = ?', user.id).count > 0 ||
ChatMessage.joins(:claimed_recording => [:recording]).where('recordings.id = ?', self.id).where('chat_messages.target_user_id = ?', user.id).count > 0
end
# Start recording a session.

View File

@ -205,7 +205,7 @@ module JamRuby
teacher.price_per_month_120_cents = params[:price_per_month_120_cents] if params.key?(:price_per_month_120_cents)
teacher.teaches_test_drive = params[:teaches_test_drive] if params.key?(:teaches_test_drive)
teacher.test_drives_per_week = params[:test_drives_per_week] if params.key?(:test_drives_per_week)
teacher.test_drives_per_week ||= 10 # default to 10 in absence of others
teacher.test_drives_per_week = 10 if !params.key?(:test_drives_per_week) # default to 10 in absence of others
teacher.school_id = params[:school_id] if params.key?(:school_id)

View File

@ -189,7 +189,10 @@
function sessionStarted(e, data) {
$sessionId = data.session.id;
var lessonId = null;
if (data.session.lesson_session) {
lessonId = data.session.lesson_session.id
}
// open chat panel
//$chatSender.show();
//$chatMessagesScroller.show();
@ -199,7 +202,7 @@
reset();
context.ChatActions.sessionStarted($sessionId);
context.ChatActions.sessionStarted($sessionId, lessonId);
showing = true
fullyInitialized = true;
drainQueue();

View File

@ -2111,6 +2111,18 @@
})
}
function attachRecordingToLesson(data) {
return $.ajax({
type: "POST",
url: '/api/lesson_sessions/' + data.id + '/attach_recording',
dataType: "json",
contentType: 'application/json',
data: JSON.stringify(data)
})
}
function bookLesson(data) {
return $.ajax({
type: "POST",
@ -2695,6 +2707,7 @@
this.signup = signup;
this.portOverCarts = portOverCarts;
this.bookLesson = bookLesson;
this.attachRecordingToLesson = attachRecordingToLesson;
this.getLessonBooking = getLessonBooking;
this.getUnprocessedLesson = getUnprocessedLesson;
this.getUnprocessedLessonOrIntent = getUnprocessedLessonOrIntent;

View File

@ -12,7 +12,7 @@
return this.each(function(index) {
function close() {
$parent.btOff();
//$parent.btOff();
$parent.focus();
}
@ -41,7 +41,7 @@
}
},
out: function() {
$parent.btOff();
//$parent.btOff();
}});
}
@ -53,6 +53,10 @@
var width = 100;
var otherOverlap = 22;
if (options.attachments_only) {
extraClasses += 'attachments-only'
width = 120;
}
if(options.isRequested) {
extraClasses += 'is-requested '
width = 100;
@ -69,6 +73,15 @@
extraClasses += 'is-admin '
width = 135;
}
if (options.chat_dialog) {
var $sidebar = $parent.closest('.dialog')
}
else if (options.attachments_only) {
var $sidebar = $parent.closest('#sidebar-div');
}
else {
var $sidebar = $parent.closest('.screen')
}
context.JK.hoverBubble($parent, html, {
trigger:'none',
cssClass: 'lesson-action-popup' + extraClasses,
@ -78,7 +91,7 @@
overlap: -10,
width:width,
closeWhenOthersOpen: true,
offsetParent: $parent.closest('.screen'),
offsetParent: $sidebar,
positions:['bottom'],
preShow: function() {
@ -90,7 +103,7 @@
timeout = null;
}
waitForBubbleHover($(container))
timeout = setTimeout(function() {$parent.btOff()}, 6000)
//timeout = setTimeout(function() {$parent.btOff()}, 6000)
}
});
});

View File

@ -19,12 +19,28 @@ AttachmentStore = context.AttachmentStore
notationSelected: (e) ->
files = $(e.target).get(0).files
logger.debug("notation files selected: " + files)
window.AttachmentActions.uploadNotations(files)
logger.debug("notation files selected: ", files)
window.AttachmentActions.uploadNotations.trigger(files, @notationUploadDone, @notationUploadFail)
notationUploadDone: () ->
logger.debug("AttachmentStatus: notationUploadDone")
context.JK.Banner.showNotice('Notation Uploaded', 'The music notation file has been uploaded, and can be accessed from the Messages window for this lesson.')
notationUploadFail: () ->
logger.debug("AttachmentStatus: notationUploadFail")
audioSelected: (e) ->
files = $(e.target).get(0).files
logger.debug("audio files selected: " + files)
logger.debug("audio files selected: ", files)
window.AttachmentActions.uploadAudio.trigger(files, @notationUploadDone, @notationUploadFail)
audioUploadDone: () ->
logger.debug("AttachmentStatus: audioUploadDone")
context.JK.Banner.showNotice('Audio file Uploaded', 'The audio file has been uploaded, and can be accessed from the Messages window for this lesson.')
audioUploadFail: () ->
logger.debug("AttachmentStatus: audioUploadFail")
render: () ->
`<div className="attachment-status">

View File

@ -27,6 +27,7 @@ context = window
@app.ajaxError(jqXHR)
afterHide: () ->
window.ChatActions.activateChannel('global')
parseId:(id) ->
if !id?

View File

@ -13,6 +13,9 @@ ChatActions = @ChatActions
state = context.ChatStore.getState()
state
getInitialProps: () ->
{newFormat:true}
onChatChanged: (chatState) ->
@setState(chatState)
@ -38,11 +41,41 @@ ChatActions = @ChatActions
when 'Lesson Updated Time Approved' then 'updated lesson time'
when 'New Time Proposed' then 'proposed new time'
when 'Lesson Declined' then 'declined lesson'
when 'Notation File' then 'attached a notation file'
when 'Audio File' then 'attached an audio file'
when 'JamKazam Recording' then 'attached a recording'
else purpose
notationClicked: (music_notation, e) ->
e.preventDefault()
context.JK.popExternalLink("/api/music_notations/#{music_notation.id}?target=_blank")
audioClicked: (music_notation, e) ->
e.preventDefault()
context.JK.popExternalLink("/api/music_notations/#{music_notation.id}?target=_blank")
recordingClicked: (recording, e) ->
e.preventDefault()
context.JK.popExternalLink("/recordings/#{recording.id}")
openMenu: (lesson, e) ->
$this = $(e.target)
if !$this.is('.lesson-session-actions-btn')
$this = $this.closest('.lesson-session-actions-btn')
$this.btOn()
render: () ->
if !this.props.hideHeader
if @state.channel == 'lesson'
chatTabs = `<div className="chat-tabs">
<a data-lesson-id={this.state.lessonSessionId} className="lesson-session-actions-btn"
onClick={this.openMenu.bind(this, {id: this.state.lessonSessionId})}>attach file
<div className="details-arrow arrow-down"/>
</a>
</div>`
else if !this.props.hideHeader
tabs = []
for channel of @state.msgs
classes = {}
@ -71,8 +104,8 @@ ChatActions = @ChatActions
msgs = []
if this.props?.channel?
activeChannel = this.props.channel
if @activeChannelType() == 'lesson'
activeChannel = @state.lessonSessionId
else
activeChannel = @state.channel
@ -86,23 +119,25 @@ ChatActions = @ChatActions
else
sender = msg.sender_name
if this.props.newFormat
if msg.purpose
purpose = `<div className="chat-message-purpose">{this.convertPurpose(msg.purpose)}</div>`
else
purpose = null
if msg.purpose == 'Notation File'
additional = `<a className="additional" onClick={this.notationClicked.bind(this, msg.music_notation)}>{msg.music_notation.file_name}</a>`
else if msg.purpose == 'Audio File'
additional = `<a className="additional" onClick={this.audioClicked.bind(this, msg.music_notation)}>{msg.music_notation.file_name}</a>`
else if msg.purpose == 'JamKazam Recording'
additional = `<a className="additional" onClick={this.recordingClicked.bind(this, msg.claimed_recording)}>{msg.claimed_recording.name}</a>`
msgs.push(`<div key={msg.msg_id} className="chat-message">
<span className="chat-message-sender">{sender}</span>{purpose}<time className="chat-message-timestamp timeago">{timeago}</time>
<span className="chat-message-text">{msg.msg}</span>
{additional}
</div>`)
else
msgs.push(`<div key={msg.msg_id} className="chat-message">
<span className="chat-message-sender">{sender}</span>
<span className="chat-message-text">{msg.msg}</span>
<time className="chat-message-timestamp timeago">{timeago}</time>
</div>`)
if this.props?.showEmailNotice
otherName = this.props?.other?.name
@ -114,6 +149,12 @@ ChatActions = @ChatActions
if this.props?.rootClass?
topClasses[this.props.rootClass] = true
if this.props.rootClass == 'ChatDialog'
attachFiles = `<a id="attach-files-chat-dialog" data-lesson-id={this.state.lessonSessionId} className="lesson-session-actions-btn"
onClick={this.openMenu.bind(this, {id: this.state.lessonSessionId})}>attach file
<div className="details-arrow arrow-down"/>
</a>`
`<div className={classNames(topClasses)}>
{chatTabs}
<div className="active-tab">
@ -128,16 +169,14 @@ ChatActions = @ChatActions
<a className="button-orange btn-send-chat-message" onClick={this.handleSendMessage}>SEND</a>
{closeBtn}
{emailSentNotice}
{attachFiles}
</form>
</div>
</div>`
activeChannelType: () ->
if this.props?.channelType?
this.props.channelType
else
@state.channel
@state.channelType
sendMessage:()->
if !context.JK.JamServer.connected
@ -187,6 +226,33 @@ ChatActions = @ChatActions
$scroller = @root.find('.chat-list-scroller')
$scroller.animate({scrollTop: $scroller[0].scrollHeight}, speed)
items = @root.find('.chat-tabs .lesson-session-actions-btn')
@hookupMenu(items)
items = @root.find('#attach-files-chat-dialog')
@hookupMenu(items)
hookupMenu: (items) ->
$.each(items, (i, node) => (
$node = $(node)
chat_dialog = this.props.rootClass == 'ChatDialog'
lesson = {id: $node.attr('data-lesson-id'), attachments_only: true, chat_dialog: chat_dialog}
$node.lessonSessionActions(lesson).off(context.JK.EVENTS.LESSON_SESSION_ACTION).on(context.JK.EVENTS.LESSON_SESSION_ACTION, @lessonSessionActionSelected)
))
lessonSessionActionSelected: (e, data) ->
lessonId = data.options.id
if data.lessonAction == 'attach-recording'
window.AttachmentActions.startAttachRecording(lessonId)
else if data.lessonAction == 'attach-notation'
window.AttachmentActions.startAttachNotation(lessonId)
else if data.lessonAction == 'attach-audio'
window.AttachmentActions.startAttachAudio(lessonId)
pasteIntoInput: (el, text) ->
el.focus();
if typeof el.selectionStart == "number" && typeof el.selectionEnd == "number"

View File

@ -869,6 +869,20 @@ UserStore = context.UserStore
else if @isPast()
else
if @studentMadeDefaultSlot()
message = this.slotMessage(this.state.booking.default_slot, 'accept')
if @isRecurring()
detail = `<p className="lesson-time">Your {this.lessonDesc()} will take place each {this.slotTime(this.state.booking.default_slot)}</p>`
else
detail = `<p className="lesson-time">Your {this.lessonDesc()} will take place this {this.slotTime(this.state.booking.default_slot)}</p>`
summary = `<div className="row">
{this.userHeader(this.teacher())}
<p>Has accepted your lesson request.</p>
{detail}
{message}
</div>`
decision = `<LessonBookingDecision {...this.decisionProps([])} />`
`<div className="contents">

View File

@ -177,7 +177,7 @@
<select className="hour">{this.hours}</select> : <select disabled={this.props.disabled} className="minute">{this.minutes}</select>
<select className="am_pm">{this.am_pm}</select>
<br/>
<span>* Time will be local to {window.jstz.determine().name()}</span>
<span>* Time will be local to {context.JK.currentTimezone()}</span>
{errorText}
</span>
@ -199,7 +199,7 @@
<select className="hour">{this.hours}</select> : <select disabled={this.props.disabled} className="minute">{this.minutes}</select>
<select disabled={this.props.disabled} className="am_pm">{this.am_pm}</select>
<br/>
<span>*Time will be local to {window.jstz.determine().name()}</span>
<span>*Time will be local to {context.JK.currentTimezone()}</span>
{errorText}
</span>
</div>`

View File

@ -118,6 +118,7 @@ proficiencyDescriptionMap = {
afterShow: (e) ->
UserActions.refresh()
@visible = true
logger.debug("TeacherProfile: afterShow")
@setState({userId: e.id, user: null})

View File

@ -37,6 +37,8 @@ ProfileActions = @ProfileActions
@visible = true
afterShow: (e) ->
UserActions.refresh()
#@setState(TeacherSearchStore.getState())
#if @state.results.length == 0
# don't issue a new search every time someone comes to the screen, to preserve location from previous browsing

View File

@ -23,8 +23,18 @@ AttachmentActions = @AttachmentActions
recordingsSelected: (recordings) ->
logger.debug("recording selected", recordings)
options = {id: @lessonId}
options.recordings = recordings
rest.attachRecordingToLesson(options).done((response) => @attachedRecordingsToLesson(response)).fail((jqXHR) => @attachedRecordingsFail(jqXHR))
attachedRecordingsToLesson: (response) ->
context.JK.Banner.showNotice('Recording Attached', 'Your recording has been associated with this lesson, and can be accessed from the Messages window for this lesson.')
attachedRecordingsFail: (jqXHR) ->
@app.ajaxError(jqXHR)
onStartAttachRecording: (lessonId) ->
if @lessonId?
if @uploading
logger.warn("rejecting startAttachRecording attempt as currently busy")
return
@lessonId = lessonId
@ -32,25 +42,25 @@ AttachmentActions = @AttachmentActions
@ui.launchRecordingSelectorDialog([], (recordings) =>
@recordingsSelected(recordings)
)
@change()
@changed()
onStartAttachNotation: (lessonId) ->
if @lessonId?
if @uploading
logger.warn("rejecting onStartAttachNotation attempt as currently busy")
return
@lessonId = lessonId
logger.debug("notation upload started")
logger.debug("notation upload started for lesson: " + lessonId)
@triggerNotation()
@change()
@changed()
onStartAttachAudio: (lessonId) ->
if @lessonId?
if @uploading
logger.warn("rejecting onStartAttachAudio attempt as currently busy")
return
@lessonId = lessonId
logger.debug("audio upload started")
logger.debug("audio upload started for lesson: " + lessonId)
@triggerAudio()
@changed()
@ -66,7 +76,7 @@ AttachmentActions = @AttachmentActions
onUploadNotations: (notations, doneCallback, failCallback) ->
logger.debug("beginning upload of notations")
logger.debug("beginning upload of notations", notations)
@uploading = true
@changed()
@ -92,14 +102,16 @@ AttachmentActions = @AttachmentActions
return
formData.append('client_id', app.clientId)
formData.append('lesson_session_id', @lessonid);
formData.append('lesson_session_id', @lessonId);
formData.append('attachment_type', 'notation')
rest.uploadMusicNotations(formData)
.done((response) => @doneUploadingNotatations(notations, response))
.fail((jqXHR) => @failUploadingNotations(jqXHR))
.done((response) => @doneUploadingNotatations(notations, response, doneCallback, failCallback))
.fail((jqXHR) => @failUploadingNotations(jqXHR, failCallback))
doneUploadingNotatations: (notations, response) ->
doneUploadingNotatations: (notations, response, doneCallback, failCallback) ->
@uploading = false
@changed()
error_files = [];
$.each(response, (i, music_notation) => (
if music_notation.errors
@ -112,7 +124,9 @@ AttachmentActions = @AttachmentActions
else
doneCallback()
failUploadingNotations: (jqXHR) ->
failUploadingNotations: (jqXHR, failCallback) ->
@uploading = false
@changed()
if jqXHR.status == 413
# the file is too big. Let the user know.
# This should happen when they select the file, but a misconfiguration on the server could cause this.

View File

@ -14,6 +14,7 @@ SessionStore = context.SessionStore
systemMsgId: 0
msgs: {global:[], session:[]}
max_global_msgs: 100
channelType: null
init: () ->
# Register with the app store to get @app
@ -46,11 +47,22 @@ SessionStore = context.SessionStore
@changed()
# called from ChatPanel
onSessionStarted: () ->
@msgs['session'] = []
@channel = 'session'
onSessionStarted: (sessionId, lessonId) ->
logger.debug("ChatStore.sessionStarted sessionId: #{sessionId} lessonId: #{lessonId}")
if lessonId?
@lessonSessionId = lessonId
#@msgs['session'] = []
@channel = 'lesson'
@channelType = 'lesson'
@fetchHistory()
@onEmptyChannel(@channel)
else
@msgs['session'] = []
@channel = 'session'
@channelType = null
@fetchHistory()
@onEmptyChannel(@channel)
buildQuery: (channel = null) ->
if !channel?
@ -71,6 +83,7 @@ SessionStore = context.SessionStore
onInitializeLesson: (lessonSessionId) ->
@lessonSessionId = lessonSessionId
@channelType = 'lesson'
@fetchHistory('lesson')
@ -102,6 +115,8 @@ SessionStore = context.SessionStore
convert.created_at = chat.created_at
convert.channel = chat.channel
convert.purpose = chat.purpose
convert.music_notation = chat.music_notation
convert.claimed_recording = chat.claimed_recording
converted.push(convert)
converted
@ -146,7 +161,13 @@ SessionStore = context.SessionStore
if msg.channel == 'lesson'
effectiveChannel = msg.lesson_session_id
console.log("effective channel", effectiveChannel, @msgs)
if msg.attachment_type?
console.log("attachment type seen")
if msg.attachment_type == 'notation' || msg.attachment_type == 'audio'
msg.music_notation = {id: msg.attachment_id, file_name: msg.attachment_name, attachment_type: msg.attachment_type}
else
msg.claimed_recording = {id: msg.attachment_id, name: msg.attachment_name}
channelMsgs = @msgs[effectiveChannel]
if !channelMsgs?
@ -174,7 +195,10 @@ SessionStore = context.SessionStore
payload
onActivateChannel: (channel) ->
logger.debug("onActivateChannel: " + channel)
@channel = channel
if @channel != 'lesson'
@channelType = null
@fetchHistory()
@changed()
@ -218,7 +242,7 @@ SessionStore = context.SessionStore
window.JK.JamServer.sendChatMessage(channel, msg)
getState: () ->
return {msgs: @msgs, channel: @channel}
return {msgs: @msgs, channel: @channel, channelType: @channelType, lessonSessionId: @lessonSessionId}
changed: () ->
@trigger(@getState())

View File

@ -774,7 +774,8 @@ ConfigureTracksActions = @ConfigureTracksActions
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.TRACKS_CHANGED, @trackChanges);
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.HEARTBEAT_ACK, @trackChanges);
$(document).trigger(EVENTS.SESSION_STARTED, {session: {id: @currentSessionId}}) if document
console.log("SESSION STARTED EVENT")
$(document).trigger(EVENTS.SESSION_STARTED, {session: {id: @currentSessionId, lesson_session: response.lesson_session}}) if document
@handleAutoOpenJamTrack()

View File

@ -605,6 +605,7 @@
return deferred;
}
formData.append('attachment_type', 'notation')
formData.append('client_id', app.clientId);
$btnSelectFiles.text('UPLOADING...').data('uploading', true)
$uploadSpinner.show();

View File

@ -274,7 +274,7 @@
server.registerMessageCallback(context.JK.MessageType.TRACKS_CHANGED, trackChanges);
server.registerMessageCallback(context.JK.MessageType.HEARTBEAT_ACK, trackChanges);
$(document).trigger(EVENTS.SESSION_STARTED, {session: {id: sessionId}});
$(document).trigger(EVENTS.SESSION_STARTED, {session: {id: sessionId, lesson_session: session.lesson_session}});
})
.fail(function() {
updateCurrentSession(null);

View File

@ -1218,6 +1218,23 @@
})
}
context.JK.currentTimezone = function() {
var tz = window.jstz.determine().name()
if (tz == 'America/Chicago') {
tz = 'US Central Time'
}
else if(tz == 'America/Los_Angeles' || tz == 'America/Los Angeles') {
tz = 'US Pacific Time'
}
else if(tz == 'America/New_York' || tz == 'America/New York') {
tz = 'US Eastern Time'
}
else if (tz == 'America/Arizona') {
tz = 'US Mountain Time'
}
return tz;
}
context.JK.flash = function(msg, options) {
options = options || {}

View File

@ -2,6 +2,12 @@
.lesson-action-popup {
a{
color:#fc0 !important;
}
li {
border-bottom:0 !important;
}
&.not-card-ok .bt-content{
height:20px !important;
width:90px !important;

View File

@ -6,6 +6,28 @@ div[data-react-class="ChatWindow"] {
.ChatWindow {
.chat-message-purpose {
display:inline;
color: $ColorTextTypical;
margin: 0 10px 0 0;
font-size:11px;
}
.chat-message-text {
margin-top:8px;
display:block;
}
.chat-message-timestamp {
display:inline-block;
}
.additional {
margin-left:4px;
display: block;
margin-top:4px;
}
&.ChatDialog {
.active-tab {
top: 0;
@ -30,13 +52,7 @@ div[data-react-class="ChatWindow"] {
}
.chat-message {
margin:0 0 15px 0;
.chat-message-text {
margin-top:8px;
display:block;
}
.chat-message-timestamp {
display:inline-block;
}
.chat-message-sender {
&:after {
content: '';
@ -65,12 +81,6 @@ div[data-react-class="ChatWindow"] {
margin: 8px -4px 0 0;
width:50px;
}
.chat-message-purpose {
display:inline;
color: $ColorTextTypical;
margin: 0 10px 0 0;
font-size:11px;
}
}
height: 100%;
@ -166,7 +176,6 @@ div[data-react-class="ChatWindow"] {
.chat-message-timestamp {
margin-top: 4px;
color: #AAA;
display: block;
margin-left:4px;
}
}
@ -181,4 +190,32 @@ div[data-react-class="ChatWindow"] {
box-sizing: border-box;
}
.arrow-down {
float:none;
margin-left:5px;
margin-top:0;
margin-right:0;
border-top: 4px solid #fc0;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
display:inline-block;
}
.arrow-up {
float:none;
margin-right:0;
margin-left:5px;
margin-bottom:2px;
border-bottom: 4px solid #fc0;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
display:inline-block;
}
.lesson-session-actions-btn {
margin-bottom:5px;
}
#attach-files-chat-dialog {
font-size:12px;
float:left;
margin-top:6px;
}
}

View File

@ -150,7 +150,7 @@ class ApiLessonBookingsController < ApiController
def counter
if params[:lesson_session_id]
target_lesson = @lesson_session.find(params[:lesson_session_id])
target_lesson = LessonSession.find(params[:lesson_session_id])
else
target_lesson = @lesson_booking.next_lesson
end

View File

@ -115,6 +115,32 @@ class ApiLessonSessionsController < ApiController
@lesson_payment_charges = current_user.uncollectables
end
def attach_recording
if @lesson_session.student.id == current_user.id
me = @lesson_session.student
other = @lesson_session.teacher
else
me = @lesson_session.teacher
other = @lesson_session.student
end
recordings = params[:recordings]
recordings.each do |recording_data|
#recording = Recording.find(recording_data[:id])
claimed_recording = ClaimedRecording.find_by_id(recording_data[:id])
if claimed_recording.nil? || claimed_recording.user != current_user
raise JamPermissionError, 'only owner of claimed_recording can associated it with a lesson'
end
msg = ChatMessage.create(me, nil, '', ChatMessage::CHANNEL_LESSON, nil, other, @lesson_session, 'JamKazam Recording', nil, claimed_recording)
end
render :json => {}, :status => 200
end
private
def lookup_lesson

View File

@ -6,17 +6,43 @@ class ApiMusicNotationsController < ApiController
respond_to :json
def create
client_id = params[:client_id]
if client_id.nil?
raise JamArgumentError, "client_id must be specified"
end
@music_notations = []
lesson_session = LessonSession.find_by_id(params[:lesson_session_id])
if lesson_session
session_id = lesson_session.music_session.id
else
session_id = params[:session_id]
end
params[:files].each do |file|
music_notation = MusicNotation.create(params[:session_id], file, current_user)
music_notation = MusicNotation.create(session_id, params[:attachment_type], file, current_user)
@music_notations.push music_notation
if lesson_session
if lesson_session.student.id == current_user.id
me = lesson_session.student
other = lesson_session.teacher
else
me = lesson_session.teacher
other = lesson_session.student
end
if !music_notation.errors.any?
# if no error and it's a lesson, then make a chat about it
if params[:attachment_type] == MusicNotation::TYPE_NOTATION
purpose = "Notation File"
else
purpose = "Audio File"
end
msg = ChatMessage.create(me, nil, '', ChatMessage::CHANNEL_LESSON, nil, other, lesson_session, purpose, music_notation)
end
end
end if params[:files]
respond_with @music_notations, responder: ApiResponder, :status => 201

View File

@ -9,3 +9,11 @@ node :user do |c|
end
user_data
end
child :music_notation do
attributes :id, :file_name, :attachment_type
end
child :claimed_recording do
attributes :id, :name
end

View File

@ -140,6 +140,10 @@ else
}
}
child(lesson_session: :lesson_session) {
attributes :id, :scheduled_start, :status, :teacher_id, :success, :duration, :student_id
}
child(:active_music_session => :active_music_session) {
attributes :claimed_recording_initiator_id, :track_changes_counter

View File

@ -1,10 +1,19 @@
script type='text/template' id='template-lesson-session-actions'
= '{% if (data.cardNotOk) { %}'
ul
li data-lesson-option="enter-payment"
a href='#' Enter Payment
= '{% } else if (data.attachments_only) { %}'
ul
li data-lesson-option="attach-recording"
a href='#' Attach Recording
li data-lesson-option="attach-notation"
a href='#' Attach Notation File
li data-lesson-option="attach-audio"
a href='#' Attach Audio File
= '{% } else if (data.isRequested) { %}'
ul
li data-lesson-option="status"

View File

@ -696,6 +696,7 @@ SampleApp::Application.routes.draw do
match '/lesson_sessions/uncollectable' => 'api_lesson_sessions#uncollectable', :via => :get
match '/lesson_sessions/:id' => 'api_lesson_sessions#show', :via => :get
match '/lesson_sessions/:id/update_unread_messages' => 'api_lesson_sessions#update_unread_messages', :via => :post
match '/lesson_sessions/:id/attach_recording' => 'api_lesson_sessions#attach_recording', :via => :post
match '/lesson_sessions/:id/start_time' => 'api_lesson_sessions#start_time', :via => :post
match '/lesson_sessions/:id/reschedule_check' => 'api_lesson_sessions#reschedule_check', :via => :post
match '/lesson_sessions/:id/cancel_check' => 'api_lesson_sessions#cancel_check', :via => :post