diff --git a/web/Gemfile b/web/Gemfile index d7975e309..c23b8b6ca 100644 --- a/web/Gemfile +++ b/web/Gemfile @@ -94,6 +94,7 @@ gem 'react-rails', '~> 1.0' source 'https://rails-assets.org' do gem 'rails-assets-reflux' + gem 'rails-assets-classnames' end group :development, :test do diff --git a/web/app/assets/javascripts/application.js b/web/app/assets/javascripts/application.js index 8fa2e95ef..ea252201f 100644 --- a/web/app/assets/javascripts/application.js +++ b/web/app/assets/javascripts/application.js @@ -38,6 +38,7 @@ //= require jquery.exists //= require jquery.payment //= require jquery.visible +//= require classnames //= require reflux //= require howler.core.js //= require jstz diff --git a/web/app/assets/javascripts/backend_alerts.js b/web/app/assets/javascripts/backend_alerts.js index 189fab1e1..5d1f74ff5 100644 --- a/web/app/assets/javascripts/backend_alerts.js +++ b/web/app/assets/javascripts/backend_alerts.js @@ -78,7 +78,7 @@ if (type === 2) { // BACKEND_MIXER_CHANGE - context.MixerActions.mixersChanged(type, text) + context.SessionActions.mixersChanged(type, text) if(context.JK.CurrentSessionModel) context.JK.CurrentSessionModel.onBackendMixerChanged(type, text) diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js index c2f2f7e07..6d4f74316 100644 --- a/web/app/assets/javascripts/jam_rest.js +++ b/web/app/assets/javascripts/jam_rest.js @@ -479,7 +479,7 @@ } function deleteParticipant(clientId) { - var url = "/api/participants/" + lientId; + var url = "/api/participants/" + clientId; return $.ajax({ type: "DELETE", url: url diff --git a/web/app/assets/javascripts/react-components/SessionLeaveBtn.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionLeaveBtn.js.jsx.coffee index 0edb5c058..90f37ecb6 100644 --- a/web/app/assets/javascripts/react-components/SessionLeaveBtn.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SessionLeaveBtn.js.jsx.coffee @@ -2,8 +2,21 @@ context = window @SessionLeaveBtn = React.createClass({ + onLeave: (e) -> + e.preventDefault() + @rateSession() + + SessionActions.leaveSession.trigger({location: '/client#/home'}) + + rateSession: () -> + unless @rateSessionDialog? + @rateSessionDialog = new context.JK.RateSessionDialog(context.JK.app); + @rateSessionDialog.initialize(); + + @rateSessionDialog.showDialog(); + render: () -> - ` + ` X LEAVE ` }) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/SessionMyTrack.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionMyTrack.js.jsx.coffee index f6d4963e7..d2612de05 100644 --- a/web/app/assets/javascripts/react-components/SessionMyTrack.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SessionMyTrack.js.jsx.coffee @@ -29,12 +29,17 @@ MixerActions = @MixerActions muteMixer = this.state.mixers.muteMixer vuMixer = this.state.mixers.vuMixer - classes = React.addons.classSet({ + classes = classNames({ 'track-icon-mute': true 'enabled' : !muteMixer.mute 'muted' : muteMixer.mute }) + panStyle = { + transform: "rotate(#{this.state.mixers.mixer.pan}deg)" + WebkitTransform: "rotate(#{this.state.mixers.mixer.pan}deg)" + } + `
{this.props.name}
@@ -43,7 +48,7 @@ MixerActions = @MixerActions
-
+

diff --git a/web/app/assets/javascripts/react-components/SessionScreen.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionScreen.js.jsx.coffee index 1b3685bcb..79b346147 100644 --- a/web/app/assets/javascripts/react-components/SessionScreen.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SessionScreen.js.jsx.coffee @@ -4,7 +4,7 @@ SessionActions = @SessionActions @SessionScreen = React.createClass({ - mixins: [Reflux.listenTo(@AppStore,"onAppInit")] + mixins: [Reflux.listenTo(@AppStore,"onAppInit"), Reflux.listenTo(@SessionActions.allowLeaveSession, "onAllowLeaveSession")] render: () -> `
@@ -29,8 +29,9 @@ SessionActions = @SessionActions componentDidMount: () -> @logger = context.JK.logger - beforeShow: (data) => + beforeShow: (data) -> @logger.debug("session beforeShow") + @allowLeave = false afterShow: (data) -> @logger.debug("session afterShow") @@ -38,14 +39,30 @@ SessionActions = @SessionActions SessionActions.joinSession.trigger(data.id) beforeHide: () -> - @logger.debug("session beforeHide") + context.JK.HelpBubbleHelper.clearJamTrackGuide(); - beforeLeave: () -> - @logger.debug("session beforeLeave") + beforeLeave: (data) -> + @logger.debug("session beforeLeave", @allowLeave) + + if @allowLeave + return true + else + leaveSessionWarningDialog = new context.JK.LeaveSessionWarningDialog(context.JK.app, + () => + @allowLeave = true + context.location.hash = data.hash + ) + + leaveSessionWarningDialog.initialize() + @app.layout.showDialog('leave-session-warning') + return false beforeDisconnect: () -> @logger.debug("session beforeDisconnect") + onAllowLeaveSession: () -> + @allowLeave = true + onAppInit: (@app) -> screenBindings = { diff --git a/web/app/assets/javascripts/react-components/SessionTrackVU.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionTrackVU.js.jsx.coffee index 5374a6956..d378136d7 100644 --- a/web/app/assets/javascripts/react-components/SessionTrackVU.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SessionTrackVU.js.jsx.coffee @@ -16,11 +16,11 @@ context = window for i in [0..this.props.lightCount-1] lightClass = if i >= redSwitch then 'vu-red-off' else 'vu-green-off' - lightClasses = React.addons.classSet('vulight', 'vu' + i, lightClass) + lightClasses = classNames('vulight', 'vu' + i, lightClass) - lights.push(``) + lights.push(``) - tableClasses = React.addons.classSet('vu', 'horizontal', this.props.side + '-' + this.state.mixers.mixer.mixerId) + tableClasses = classNames('vu', 'horizontal', this.props.side + '-' + this.state.mixers.mixer.mixerId) ` @@ -32,11 +32,11 @@ context = window for i in [0..this.props.lightCount-1].reverse() lightClass = if (i >= redSwitch) then "vu-red-off" else "vu-green-off" - lightClasses = React.addons.classSet('vulight', 'vu' + i, lightClass) + lightClasses = classNames('vulight', 'vu' + i, lightClass) - lights.push(``) + lights.push(``) - tableClasses = React.addons.classSet('vu', 'vertical', this.props.side + '-' + this.state.mixers.mixer.mixerId) + tableClasses = classNames('vu', 'vertical', this.props.side + '-' + this.state.mixers.mixer.mixerId) `
{lights} diff --git a/web/app/assets/javascripts/react-components/SessionTrackVolumeHover.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionTrackVolumeHover.js.jsx.coffee index 1778c6d2c..c766d4651 100644 --- a/web/app/assets/javascripts/react-components/SessionTrackVolumeHover.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SessionTrackVolumeHover.js.jsx.coffee @@ -32,7 +32,7 @@ MixerActions = @MixerActions muteMixer = this.state.mixers.muteMixer - classes = React.addons.classSet({ + classes = classNames({ 'track-icon-mute': true 'enabled' : !muteMixer.mute 'muted' : muteMixer.mute diff --git a/web/app/assets/javascripts/react-components/actions/SessionActions.js.coffee b/web/app/assets/javascripts/react-components/actions/SessionActions.js.coffee index d815e60c0..41ff315f6 100644 --- a/web/app/assets/javascripts/react-components/actions/SessionActions.js.coffee +++ b/web/app/assets/javascripts/react-components/actions/SessionActions.js.coffee @@ -4,5 +4,5 @@ context = window joinSession: {} leaveSession: {} resyncServer: {asyncResult: true} - myTracksChanged: {} + mixersChanged: {} }) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/helpers/MixerHelper.js.coffee b/web/app/assets/javascripts/react-components/helpers/MixerHelper.js.coffee index b92e0055c..3233de9b8 100644 --- a/web/app/assets/javascripts/react-components/helpers/MixerHelper.js.coffee +++ b/web/app/assets/javascripts/react-components/helpers/MixerHelper.js.coffee @@ -17,15 +17,12 @@ MIX_MODES = context.JK.MIX_MODES; @muteBothMasterAndPersonalGroups = [ChannelGroupIds.AudioInputMusicGroup, ChannelGroupIds.MediaTrackGroup, ChannelGroupIds.JamTrackGroup, ChannelGroupIds.MetronomeGroup] - @organize() updateMixers: (type, text, @masterMixers, @personalMixers) -> @organize() - if @session.inSession() && text == 'RebuildAudioIoControl' - SessionActions.myTracksChanged.trigger() @@ -62,7 +59,7 @@ MIX_MODES = context.JK.MIX_MODES; localMediaMixers = @mixersForGroupIds(@mediaTrackGroups, MIX_MODES.MASTER) peerLocalMediaMixers = @mixersForGroupId(ChannelGroupIds.PeerMediaTrackGroup, MIX_MODES.MASTER) - logger.debug("localMediaMixers", localMediaMixers) + #logger.debug("localMediaMixers", localMediaMixers) #logger.debug("peerLocalMediaMixers", peerLocalMediaMixers) # get the server data regarding various media tracks @@ -263,7 +260,7 @@ MIX_MODES = context.JK.MIX_MODES; else if oppositeMixer.group_id != ChannelGroupIds.AudioInputMusicGroup logger.error("found local mixer in opposite mode that was not of groupID: AudioInputMusicGroup", mixer, oppositeMixer) else - logger.debug("local track is not present: ", track, mixer) + logger.debug("local track is not present: ", track, @allMixers) else switch @mixMode when MIX_MODES.MASTER @@ -459,3 +456,6 @@ MIX_MODES = context.JK.MIX_MODES; context.JK.VuHelpers.updateVU2('vul', mixer, value) # Do the right context.JK.VuHelpers.updateVU2('vur', mixer, value) + + getTrackInfo: () -> + context.JK.TrackHelpers(context.jamClient, @masterMixers) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/stores/MixerStore.js.coffee b/web/app/assets/javascripts/react-components/stores/MixerStore.js.coffee index 829f9c29c..cdb0908a0 100644 --- a/web/app/assets/javascripts/react-components/stores/MixerStore.js.coffee +++ b/web/app/assets/javascripts/react-components/stores/MixerStore.js.coffee @@ -13,7 +13,6 @@ MIX_MODES = context.JK.MIX_MODES; this.listenTo(context.MixerActions.initGain, this.onInitGain) this.listenTo(context.MixerActions.initPan, this.onInitPan) this.listenTo(context.MixerActions.panChanged, this.onPanChanged) - this.listenTo(context.MixerActions.mixersChanged, this.onMixersChanged) context.JK.HandleVolumeChangeCallback2 = @handleVolumeChangeCallback context.JK.HandleMetronomeCallback2 = @handleMetronomeCallback @@ -104,6 +103,8 @@ MIX_MODES = context.JK.MIX_MODES; # TODO: grab correct mix mode , line 870 sessionModel.js @mixers.updateMixers(type, text, masterMixers, personalMixers) - this.trigger({session: @session, mixers: @mixers}) + SessionActions.mixersChanged.trigger(type, text, @mixers.getTrackInfo()) + + this.trigger({session: @session, mixers: @mixers}) } ) diff --git a/web/app/assets/javascripts/react-components/stores/SessionStore.js.coffee b/web/app/assets/javascripts/react-components/stores/SessionStore.js.coffee index da5fa7642..04bd575ff 100644 --- a/web/app/assets/javascripts/react-components/stores/SessionStore.js.coffee +++ b/web/app/assets/javascripts/react-components/stores/SessionStore.js.coffee @@ -4,6 +4,7 @@ logger = context.JK.logger rest = context.JK.Rest() EVENTS = context.JK.EVENTS; + SessionActions = @SessionActions @SessionStore = Reflux.createStore( @@ -24,27 +25,77 @@ SessionActions = @SessionActions sessionPageEnterDeferred: null gearUtils: null sessionUtils: null + joinDeffered: null + recordingModel: null + currentTrackChanges: 0 init: -> # Register with the app store to get @app - this.listenTo(context.AppStore, this.onAppInit); + this.listenTo(context.AppStore, this.onAppInit) onAppInit: (@app) -> @gearUtils = context.JK.GearUtilsInstance @sessionUtils = context.JK.SessionUtils + @recordingModel = new context.JK.RecordingModel(@app, this, rest, context.jamClient); + + onWatchedInputs: (inputTracks) -> + + logger.debug("obtained tracks at start of session") + @sessionPageEnterDeferred.resolve(inputTracks); + @sessionPageEnterDeferred = null + + onMixersChanged: (type, text, trackInfo) -> + + return unless @inSession() + + if text == 'RebuildAudioIoControl' + if @backendMixerAlertThrottleTimer + clearTimeout(@backendMixerAlertThrottleTimer) + + @backendMixerAlertThrottleTimer = setTimeout( + () => + @backendMixerAlertThrottleTimer = null + if @sessionPageEnterDeferred + # this means we are still waiting for the BACKEND_MIXER_CHANGE that indicates we have user tracks built-out/ready + + # we will get at least one BACKEND_MIXER_CHANGE that corresponds to the backend doing a 'audio pause', which won't matter much + # so we need to check that we actaully have userTracks before considering ourselves done + if trackInfo.userTracks.length > 0 + logger.debug("obtained tracks at start of session") + @sessionPageEnterDeferred.resolve(trackInfo.userTracks); + @sessionPageEnterDeferred = null + + return + + + // wait until we are fully in session before trying to sync tracks to server + if @joinDeferred + @joinDeferred + .done(()=> + @syncTracks() + + , 100) + else if @session.inSession() && text == 'RebuildMediaControl' + SessionActions.mediaTracksChanged.trigger(@mixers.getTrackInfo()) + else if @session.inSession() && text == 'RebuildRemoteUserControl' + SessionActions.otherTracksChanged.trigger(@mixers.getTrackInfo()) + onJoinSession: (sessionId) -> # initialize webcamViewer if gon.global.video_available && gon.global.video_available != "none" - webcamViewer.beforeShow() + @webcamViewer.beforeShow() # double-check that we are connected to the server via websocket return unless @ensureConnected() + # just make double sure a previous session state is cleared out + @sessionEnded() + # update the session data to be empty @updateCurrentSession(null) @@ -153,7 +204,7 @@ SessionActions = @SessionActions # tell the server we want to join - rest.joinSession({ + @joinDeferred = rest.joinSession({ client_id: @app.clientId, ip_address: context.JK.JamServer.publicIP, as_musician: true, @@ -177,7 +228,7 @@ SessionActions = @SessionActions else context.JK.GA.trackSessionMusicians(context.JK.GA.SessionCreationTypes.join); - #recordingModel.reset(); + @recordingModel.reset(response.id); context.jamClient.JoinSession({sessionID: response.id}); @@ -417,7 +468,96 @@ SessionActions = @SessionActions context.JK.JamServer.connected + # called by anyone wanting to leave the session with a certain behavior + onLeaveSession: (behavior) -> + logger.debug("attempting to leave session", behavior) + if behavior.notify + @app.layout.notify(behavior.notify) + SessionActions.allowLeaveSession.trigger() + if behavior.location + if jQuery.isNumeric(behavior.location) + window.history.go(behavior.location) + else + window.location = behavior.location + else + logger.warn("no location specified in leaveSession action", behavior) + window.location = '/client#/home' + + if gon.global.video_available && gon.global.video_available != "none" + @webcamViewer.setVideoOff() + + @leaveSession() + + @sessionUtils.SessionPageLeave() + + leaveSession: () -> + + if @joinDeferred?.state() == 'resolved' + deferred = new $.Deferred() + + @recordingModel.stopRecordingIfNeeded() + .always(()=> + @performLeaveSession(deferred) + ) + + performLeaveSession: (deferred) -> + + logger.debug("SessionModel.leaveCurrentSession()") + # TODO - sessionChanged will be called with currentSession = null\ + + # leave the session right away without waiting on REST. Why? If you can't contact the server, or if it takes a long + # time, for that entire duration you'll still be sending voice data to the other users. + # this may be bad if someone decides to badmouth others in the left-session during this time + logger.debug("performLeaveSession: calling jamClient.LeaveSession for clientId=" + @app.clientId) + context.jamClient.LeaveSession({ sessionID: @currentSessionId }) + @leaveSessionRest(@currentSessionId) + .done(=> + deferred.resolve(arguments[0], arguments[1], arguments[2])) + .fail(=> + deferred.reject(arguments[0], arguments[1], arguments[2]); + ) + + # 'unregister' for callbacks + context.jamClient.SessionRegisterCallback(""); + #context.jamClient.SessionSetAlertCallback(""); + context.jamClient.SessionSetConnectionStatusRefreshRate(0); + + @sessionEnded() + + this.trigger(new context.SessionHelper(@app, @currentSession)) + + sessionEnded: () -> + # cleanup + + context.JK.JamServer.unregisterMessageCallback(context.JK.MessageType.SESSION_JOIN, @trackChanges); + context.JK.JamServer.unregisterMessageCallback(context.JK.MessageType.SESSION_DEPART, @trackChanges); + context.JK.JamServer.unregisterMessageCallback(context.JK.MessageType.TRACKS_CHANGED, @trackChanges); + context.JK.JamServer.unregisterMessageCallback(context.JK.MessageType.HEARTBEAT_ACK, @trackChanges); + + if @sessionPageEnterDeferred? + @sessionPageEnterDeferred.reject('session_over') + @sessionPageEnterDeferred = null + + if @backendMixerAlertThrottleTimer + clearTimeout(@backendMixerAlertThrottleTimer) + @backendMixerAlertThrottleTimer = null + + @userTracks = null; + @startTime = null; + + if @joinDeffered?.state() == 'resolved' + $(document).trigger(EVENTS.SESSION_ENDED, {session: {id: @currentSessionId}}) + + @currentTrackChanges = 0 + @currentSession = null + @joinDeferred = null + @currentSessionId = null + @currentParticipants = {} + @previousAllTracks = {userTracks: [], backingTracks: [], metronomeTracks: []} + @openBackingTrack = null + @shownAudioMediaMixerHelp = false + @controlsLockedForJamTrackRecording = false; } ) \ No newline at end of file diff --git a/web/app/assets/javascripts/recordingModel.js b/web/app/assets/javascripts/recordingModel.js index 4f0b84e00..ebfff5ca8 100644 --- a/web/app/assets/javascripts/recordingModel.js +++ b/web/app/assets/javascripts/recordingModel.js @@ -18,7 +18,7 @@ context.JK = context.JK || {}; var logger = context.JK.logger; - context.JK.RecordingModel = function(app, sessionModel, _rest, _jamClient) { + context.JK.RecordingModel = function(app, _rest, _jamClient) { var currentRecording = null; // the JSON response from the server for a recording var currentOrLastRecordingId = null; var currentRecordingId = null; @@ -31,7 +31,7 @@ var waitingOnStopTimer = null; var jamClient = _jamClient; - var sessionModel = sessionModel; + var sessionId = null; var $self = $(this); function isRecording (recordingId) { @@ -46,7 +46,7 @@ } /** called every time a session is joined, to ensure clean state */ - function reset() { + function reset(_sessionId) { currentlyRecording = false; waitingOnServerStop = false; waitingOnClientStop = false; @@ -57,6 +57,7 @@ currentRecording = null; currentRecordingId = null; stoppingRecording = false; + sessionId = _sessionId } @@ -84,7 +85,7 @@ currentlyRecording = true; stoppingRecording = false; - currentRecording = rest.startRecording({"music_session_id": sessionModel.id()}) + currentRecording = rest.startRecording({"music_session_id": sessionId}) .done(function(recording) { currentRecordingId = recording.id; currentOrLastRecordingId = recording.id; diff --git a/web/app/assets/javascripts/scheduled_session.js.erb b/web/app/assets/javascripts/scheduled_session.js.erb index 11891e938..021c90bb7 100644 --- a/web/app/assets/javascripts/scheduled_session.js.erb +++ b/web/app/assets/javascripts/scheduled_session.js.erb @@ -720,7 +720,7 @@ // we redirect to the session screen, which handles the REST call to POST /participants. logger.debug("joining session screen: " + sessionId) - context.location = '/client#/session/' + sessionId; + context.location = '/client#/session2/' + sessionId; }; if (createSessionSettings.createType == '<%= MusicSession::CREATE_TYPE_START_SCHEDULED%>') { diff --git a/web/app/assets/javascripts/trackHelpers.js b/web/app/assets/javascripts/trackHelpers.js index ec9c8771d..f2be21ea5 100644 --- a/web/app/assets/javascripts/trackHelpers.js +++ b/web/app/assets/javascripts/trackHelpers.js @@ -16,12 +16,14 @@ // take all necessary arguments to complete its work. context.JK.TrackHelpers = { - getTrackInfo: function(jamClient) { + getTrackInfo: function(jamClient, masterTracks) { - var allTracks = context.jamClient.SessionGetAllControlState(true); + if(masterTracks === undefined) { + masterTracks = context.jamClient.SessionGetAllControlState(true); + } - var userTracks = context.JK.TrackHelpers.getUserTracks(jamClient, allTracks); - var backingTracks = context.JK.TrackHelpers.getBackingTracks(jamClient, allTracks); + var userTracks = context.JK.TrackHelpers.getUserTracks(jamClient, masterTracks); + var backingTracks = context.JK.TrackHelpers.getBackingTracks(jamClient, masterTracks); var metronomeTracks = context.JK.TrackHelpers.getTracks(jamClient, ChannelGroupIds.MetronomeGroup); return { diff --git a/web/app/assets/javascripts/utils.js b/web/app/assets/javascripts/utils.js index af46ad1b1..8a30cc099 100644 --- a/web/app/assets/javascripts/utils.js +++ b/web/app/assets/javascripts/utils.js @@ -22,6 +22,8 @@ var os = null; + var reactHovers = [] + context.JK.stringToBool = function (s) { switch (s.toLowerCase()) { case "true": @@ -217,6 +219,11 @@ if(!options) options = {}; + context._.each(reactHovers, function(react) { + reactHovers.btOff(); + }) + reactHovers = [] + function waitForBubbleHover($bubble) { $bubble.hoverIntent({ over: function() { @@ -235,6 +242,7 @@ options.trigger = 'none' options.clickAnywhereToClose = true + options.closeWhenOthersOpen = true options.preShow = function(container) { var reactElement = context[reactElementName] if(!reactElementName) { diff --git a/web/app/assets/stylesheets/client/react-components/SessionScreen.css.scss b/web/app/assets/stylesheets/client/react-components/SessionScreen.css.scss index 796e053e2..8796f4553 100644 --- a/web/app/assets/stylesheets/client/react-components/SessionScreen.css.scss +++ b/web/app/assets/stylesheets/client/react-components/SessionScreen.css.scss @@ -301,6 +301,8 @@ background-repeat:no-repeat; text-align: center; margin-left:10px; + //-webkit-transform: rotate(7deg); /* Chrome, Safari, Opera */ + //transform: rotate(7deg); } .track-icon-equalizer {