attach notations, audio, and recordings done
This commit is contained in:
parent
5939079a89
commit
cc3576f70f
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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 %>
|
||||
|
|
|
|||
|
|
@ -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 today’s lesson to help other students in the community find the best instructors.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ context = window
|
|||
@app.ajaxError(jqXHR)
|
||||
|
||||
afterHide: () ->
|
||||
window.ChatActions.activateChannel('global')
|
||||
|
||||
parseId:(id) ->
|
||||
if !id?
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>`
|
||||
|
|
|
|||
|
|
@ -118,6 +118,7 @@ proficiencyDescriptionMap = {
|
|||
|
||||
|
||||
afterShow: (e) ->
|
||||
UserActions.refresh()
|
||||
@visible = true
|
||||
logger.debug("TeacherProfile: afterShow")
|
||||
@setState({userId: e.id, user: null})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 || {}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue