diff --git a/web/app/assets/javascripts/JamServer.js b/web/app/assets/javascripts/JamServer.js index 61b889eb2..ee570a513 100644 --- a/web/app/assets/javascripts/JamServer.js +++ b/web/app/assets/javascripts/JamServer.js @@ -198,50 +198,56 @@ function loggedIn(header, payload) { - server.signedIn = true; - server.clientID = payload.client_id; - server.publicIP = payload.public_ip; + // reason for setTimeout: + // loggedIn causes an absolute ton of initialization to happen, and errors sometimes happen + // but because loggedIn(header,payload) is a callback from a websocket, the browser doesn't show a stack trace... - if (context.jamClient !== undefined) { - context.jamClient.connected = true; - context.jamClient.clientID = server.clientID; - } + setTimeout(function() { + server.signedIn = true; + server.clientID = payload.client_id; + server.publicIP = payload.public_ip; - clearConnectTimeout(); + if (context.jamClient !== undefined) { + context.jamClient.connected = true; + context.jamClient.clientID = server.clientID; + } - heartbeatStateReset(); + clearConnectTimeout(); - app.clientId = payload.client_id; + heartbeatStateReset(); - if(isClientMode()) { - // tell the backend that we have logged in - context.jamClient.OnLoggedIn(payload.user_id, payload.token); // ACTS AS CONTINUATION - $.cookie('client_id', payload.client_id); - } + app.clientId = payload.client_id; - // this has to be after context.jamclient.OnLoggedIn, because it hangs in scenarios - // where there is no device on startup for the current profile. - // So, in that case, it's possible that a reconnect loop will attempt, but we *do not want* - // it to go through unless we've passed through .OnLoggedIn - server.connected = true; - server.reconnecting = false; - server.connecting = false; - initialConnectAttempt = false; + if (isClientMode()) { + // tell the backend that we have logged in + context.jamClient.OnLoggedIn(payload.user_id, payload.token); // ACTS AS CONTINUATION + $.cookie('client_id', payload.client_id); + } - heartbeatMS = payload.heartbeat_interval * 1000; - connection_expire_time = payload.connection_expire_time * 1000; - logger.info("loggedIn(): clientId=" + app.clientId + " heartbeat=" + payload.heartbeat_interval + "s expire_time=" + payload.connection_expire_time + 's'); - heartbeatInterval = context.setInterval(_heartbeat, heartbeatMS); - heartbeatAckCheckInterval = context.setInterval(_heartbeatAckCheck, 1000); - lastHeartbeatAckTime = new Date(new Date().getTime() + heartbeatMS); // add a little forgiveness to server for initial heartbeat - connectDeferred.resolve(); - $self.triggerHandler(EVENTS.CONNECTION_UP) + // this has to be after context.jamclient.OnLoggedIn, because it hangs in scenarios + // where there is no device on startup for the current profile. + // So, in that case, it's possible that a reconnect loop will attempt, but we *do not want* + // it to go through unless we've passed through .OnLoggedIn + server.connected = true; + server.reconnecting = false; + server.connecting = false; + initialConnectAttempt = false; - activeElementEvent('afterConnect', payload); + heartbeatMS = payload.heartbeat_interval * 1000; + connection_expire_time = payload.connection_expire_time * 1000; + logger.info("loggedIn(): clientId=" + app.clientId + " heartbeat=" + payload.heartbeat_interval + "s expire_time=" + payload.connection_expire_time + 's'); + heartbeatInterval = context.setInterval(_heartbeat, heartbeatMS); + heartbeatAckCheckInterval = context.setInterval(_heartbeatAckCheck, 1000); + lastHeartbeatAckTime = new Date(new Date().getTime() + heartbeatMS); // add a little forgiveness to server for initial heartbeat + connectDeferred.resolve(); + $self.triggerHandler(EVENTS.CONNECTION_UP) - if(payload.client_update && context.JK.ClientUpdateInstance) { - context.JK.ClientUpdateInstance.runCheck(payload.client_update.product, payload.client_update.version, payload.client_update.uri, payload.client_update.size) - } + activeElementEvent('afterConnect', payload); + + if (payload.client_update && context.JK.ClientUpdateInstance) { + context.JK.ClientUpdateInstance.runCheck(payload.client_update.product, payload.client_update.version, payload.client_update.uri, payload.client_update.size) + } + }, 0) } function heartbeatAck(header, payload) { diff --git a/web/app/assets/javascripts/dialog/sessionSettingsDialog.js b/web/app/assets/javascripts/dialog/sessionSettingsDialog.js index 0c47eb20e..0718d0e89 100644 --- a/web/app/assets/javascripts/dialog/sessionSettingsDialog.js +++ b/web/app/assets/javascripts/dialog/sessionSettingsDialog.js @@ -6,11 +6,12 @@ var gearUtils = context.JK.GearUtilsInstance; var $dialog; var $screen = $('#session-settings'); - var $selectedFilenames = $screen.find('#selected-filenames'); - var $uploadSpinner = $screen.find('.upload-spinner'); - var $selectedFilenames = $('#settings-selected-filenames'); + //var $selectedFilenames = $screen.find('#selected-filenames'); + var $uploadSpinner = $screen.find('.spinner-small'); + //var $selectedFilenames = $('#settings-selected-filenames'); var $inputFiles = $screen.find('#session-select-files'); var $btnSelectFiles = $screen.find('.btn-select-files'); + var $inputBox = $screen.find('.inputbox') var rest = new JK.Rest(); var sessionId; @@ -21,7 +22,7 @@ context.JK.GenreSelectorHelper.render('#session-settings-genre'); $dialog = $('[layout-id="session-settings"]'); - var currentSession = sessionScreen.getCurrentSession(); + var currentSession = context.SessionStore.currentSession; sessionId = currentSession.id; // id @@ -65,13 +66,21 @@ $('#session-settings-fan-access').val('listen-chat-band'); } - // notation files + /** + // notation files in the account screen. ugh. $selectedFilenames.empty(); for (var i=0; i < currentSession.music_notations.length; i++) { var notation = currentSession.music_notations[i]; $selectedFilenames.append('' + notation.file_name + ' '); + }*/ + + $inputBox.empty(); + for (var i=0; i < currentSession.music_notations.length; i++) { + var notation = currentSession.music_notations[i]; + addNotation(notation) } + context.JK.dropdown($('#session-settings-language')); context.JK.dropdown($('#session-settings-musician-access')); context.JK.dropdown($('#session-settings-fan-access')); @@ -81,6 +90,29 @@ $('#session-settings-fan-access').easyDropDown(easyDropDownState) } + function addNotation(notation) { + + var $notation = $('
' + notation.file_name + '
X
') + $notation.find('a').on('click', function(e) { + + if($(this).attr('data-deleting')) { + // ignore duplicate delete attempts + return false; + } + + $(this).attr('data-deleting', true) + var $notationEntry = $(this).closest('.notation-entry').find('div').text('deleting...') + + rest.deleteMusicNotation({id: notation.id}) + .done(function() { + $notation.remove() + }) + .fail(app.ajaxError) + return false; + }) + $inputBox.append($notation); + } + function saveSettings(evt) { var data = {}; @@ -111,11 +143,7 @@ data.fan_access = false; data.fan_chat = false; } - else if (fanAccess == 'listen-chat-each') { - data.fan_access = true; - data.fan_chat = false; - } - else if (fanAccess == 'listen-chat-band') { + else if (fanAccess == 'listen-chat') { data.fan_access = true; data.fan_chat = true; } @@ -177,7 +205,7 @@ } }) .always(function() { - $btnSelectFiles.text('SELECT FILES...').data('uploading', null) + $btnSelectFiles.text('ADD FILES...').data('uploading', null) $uploadSpinner.hide(); }); } @@ -203,10 +231,9 @@ else { // upload as soon as user picks their files. uploadNotations($inputFiles.get(0).files) - .done(function() { - context._.each(fileNames, function(fileName) { - var $text = $('').text(fileName); - $selectedFilenames.append($text); + .done(function(response) { + context._.each(response, function(notation) { + addNotation(notation) }) }) } @@ -225,7 +252,7 @@ function settingsSaved(response) { // No response returned from this call. 204. - sessionScreen.refreshCurrentSession(true); + context.SessionActions.syncWithServer() app.layout.closeDialog('session-settings'); } diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js index 6d4f74316..7a2e68a2f 100644 --- a/web/app/assets/javascripts/jam_rest.js +++ b/web/app/assets/javascripts/jam_rest.js @@ -95,6 +95,14 @@ }); } + + function deleteMusicNotation(options) { + return $.ajax({ + type: "DELETE", + url: "/api/music_notations/" +options.id + }); + } + function legacyJoinSession(options) { var sessionId = options["session_id"]; delete options["session_id"]; @@ -1808,6 +1816,7 @@ this.createScheduledSession = createScheduledSession; this.uploadMusicNotations = uploadMusicNotations; this.getMusicNotation = getMusicNotation; + this.deleteMusicNotation = deleteMusicNotation; this.getBroadcastNotification = getBroadcastNotification; this.quietBroadcastNotification = quietBroadcastNotification; this.legacyJoinSession = legacyJoinSession; diff --git a/web/app/assets/javascripts/minimal/minimal.js b/web/app/assets/javascripts/minimal/minimal.js index e69de29bb..37c70efb7 100644 --- a/web/app/assets/javascripts/minimal/minimal.js +++ b/web/app/assets/javascripts/minimal/minimal.js @@ -0,0 +1,20 @@ +//= require bugsnag +//= require bind-polyfill +//= require jquery +//= require jquery.monkeypatch +//= require jquery_ujs +//= require jquery.bt +//= require jquery.icheck +//= require jquery.easydropdown +//= require classnames +//= require reflux +//= require AAC_underscore +//= require AAA_Log +//= require globals +//= require jam_rest +//= require ga +//= require utils +//= require react +//= require react_ujs +//= require react-init +//= require react-components \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components.js b/web/app/assets/javascripts/react-components.js index c5c6bb14b..793ddb737 100644 --- a/web/app/assets/javascripts/react-components.js +++ b/web/app/assets/javascripts/react-components.js @@ -1,6 +1,7 @@ //= require_directory ./react-components/helpers //= require_directory ./react-components/actions //= require ./react-components/stores/AppStore +//= require ./react-components/stores/RecordingStore //= require ./react-components/stores/SessionStore //= require ./react-components/stores/MixerStore //= require ./react-components/stores/SessionMyTracksStore diff --git a/web/app/assets/javascripts/react-components/PopupRecordingStartStop.js.jsx.coffee b/web/app/assets/javascripts/react-components/PopupRecordingStartStop.js.jsx.coffee new file mode 100644 index 000000000..a4d3d6c76 --- /dev/null +++ b/web/app/assets/javascripts/react-components/PopupRecordingStartStop.js.jsx.coffee @@ -0,0 +1,131 @@ +context = window + +mixins = [] + +# this check ensures we attempt to listen if this component is created in a popup +if window.opener + mixins.push(Reflux.listenTo(window.opener.RecordingStore,"onRecordingStateChanged")) + +@PopupRecordingStartStop = React.createClass({ + + # this comes from the parent window + mixins: mixins + + onRecordingStateChanged: (recordingState) -> + this.setState(isRecording: recordingState.isRecording, recordedOnce: this.state.recordedOnce || recordingState.isRecording) + + startStopRecording: () -> + if this.state.isRecording + window.opener.RecordingActions.stopRecording() + else + window.opener.RecordingActions.startRecording() + + onNoteShowHide: () -> + this.setState(showNote: !this.state.showNote) + + getInitialState: () -> + {isRecording: window.ParentIsRecording, showNote: true, recordedOnce: false} + + render: () -> + + recordingVerb = if this.state.isRecording then 'Stop' else 'Start' + + recordingBtnClasses = classNames({ + "currently-recording" : this.state.isRecording, + "control" : true + }) + + noteJSX = `
+
+ Important Note +
+
+ While playing in your session, you are listening to your own personal mix. This recording will use the master mix, + which may sound very different. To hear and adjust your master mix settings, click the MIXER button in the session toolbar. +
+
` + + recordingJSX = `
+
+ + +
+
+
+ + +
+
+
` + + if this.state.showNote + noteText = 'hide note' + else + noteText = 'show note' + + noteShowHideJSX = `{noteText}` + + note = null + recordingOptions = null + noteShowHide = null + + if this.state.showNote && !this.state.isRecording && !this.state.recordedOnce + # should we show the note itself? Only if not recording, too + note = noteJSX + + if !this.state.isRecording && !this.state.recordedOnce + noteShowHide = noteShowHideJSX + + if gon.global.video_available == "full" + recordingOptions = recordingJSX + + + `
+
+ + + + {recordingVerb} Recording + +
+ + {recordingOptions} + + {note} + + {noteShowHide} + +
` + + componentDidMount: () -> + $root = jQuery(this.getDOMNode()) + + $recordingType = $root.find('input[type="radio"]') + context.JK.checkbox($recordingType) + + @resizeWindow() + + # this is necessary due to whatever the client's rendering behavior is. + setTimeout(@resizeWindow, 300) + + componentDidUpdate: () -> + @resizeWindow() + + resizeWindow: () => + $container = $('#minimal-container') + width = $container.width() + height = $container.height() + + # there is 20px or so of unused space at the top of the page. can't figure out why it's there. (above #minimal-container) + mysteryTopMargin = 20 + + # deal with chrome in real browsers + offset = (window.outerHeight - window.innerHeight) + mysteryTopMargin + + # handle native client chrome that the above outer-inner doesn't catch + #if navigator.userAgent.indexOf('JamKazam') > -1 + + #offset += 25 + + window.resizeTo(width, height + offset) +}) \ 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 94fa7ab25..c120b08e4 100644 --- a/web/app/assets/javascripts/react-components/SessionMyTrack.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SessionMyTrack.js.jsx.coffee @@ -35,6 +35,8 @@ MixerActions = @MixerActions WebkitTransform: "rotate(#{pan}deg)" } + #
+ `
@@ -46,7 +48,6 @@ MixerActions = @MixerActions
-

diff --git a/web/app/assets/javascripts/react-components/SessionOtherTracks.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionOtherTracks.js.jsx.coffee index 29060d8ad..0de14ad6b 100644 --- a/web/app/assets/javascripts/react-components/SessionOtherTracks.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SessionOtherTracks.js.jsx.coffee @@ -10,7 +10,6 @@ ReactCSSTransitionGroup = React.addons.CSSTransitionGroup; session = sessionMixers.session mixers = sessionMixers.mixers - logger.debug("SessionOtherTracks: onInputsChanged") participants = [] if session.inSession() diff --git a/web/app/assets/javascripts/react-components/SessionRecordBtn.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionRecordBtn.js.jsx.coffee index a406c7b25..e6bf9c2d5 100644 --- a/web/app/assets/javascripts/react-components/SessionRecordBtn.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SessionRecordBtn.js.jsx.coffee @@ -2,17 +2,32 @@ context = window @SessionRecordBtn = React.createClass({ - datAss: () -> - alert("dat ass") + mixins: [Reflux.listenTo(@MixerStore,"onSessionMixerChange")] + + onSessionMixerChange: (sessionMixers) -> + + this.setState({isRecording: sessionMixers.session.isRecording}) + + getInitialState: () -> + {childWindow: null, isRecording: false} + openRecording: () -> - recording = window.open("/popups/session/#{1}/recording-controls", 'Recording', 'scrollbars=yes,toolbar=no,status=no,height=315,width=350') + if this.state.childWindow? + this.state.childWindow.close() - window.callMe = @datAss - $(recording).on('load', ()-> - alert("loaded") - recording.bling() + childWindow = window.open("/popups/recording-controls", 'Recording', 'scrollbars=yes,toolbar=no,status=no,height=315,width=350') + childWindow.ParentRecordingStore = context.RecordingStore + childWindow.ParentIsRecording = this.state.isRecording + + ### + $(childWindow).on('load', ()=> + childWindow.focus() ) + ### + + this.setState({childWindow: childWindow}) + render: () -> ` diff --git a/web/app/assets/javascripts/react-components/SessionResyncBtn.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionResyncBtn.js.jsx.coffee index b59d766b8..91fea907b 100644 --- a/web/app/assets/javascripts/react-components/SessionResyncBtn.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SessionResyncBtn.js.jsx.coffee @@ -2,9 +2,19 @@ context = window @SessionResyncBtn = React.createClass({ + mixins: [Reflux.listenTo(@AppStore,"onAppInit")] + + resync: (e) -> + e.preventDefault() + + SessionActions.audioResync() + render: () -> - ` + ` RESYNC ` + + onAppInit: (app) -> + @app = app }) \ No newline at end of file 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 79b346147..5f807a5c5 100644 --- a/web/app/assets/javascripts/react-components/SessionScreen.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SessionScreen.js.jsx.coffee @@ -74,5 +74,4 @@ SessionActions = @SessionActions }; @app.bindScreen('session2', screenBindings); - }) diff --git a/web/app/assets/javascripts/react-components/SessionSelfVolumeHover.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionSelfVolumeHover.js.jsx.coffee new file mode 100644 index 000000000..b01da5418 --- /dev/null +++ b/web/app/assets/javascripts/react-components/SessionSelfVolumeHover.js.jsx.coffee @@ -0,0 +1,121 @@ +context = window + +MixerActions = @MixerActions + +@SessionSelfVolumeHover = React.createClass({ + + + getInitialState: () -> + {mixers: this.props.mixers} + + handleMute: (e) -> + e.preventDefault() + + muting = $(e.currentTarget).is('.enabled') + + MixerActions.mute([this.props.mixers.mixer], muting) + + handleMuteCheckbox: (e) -> + muting = $(e.target).is(':checked') + + MixerActions.mute([this.props.mixers.mixer, this.props.mixers.oppositeMixer], muting) + + render: () -> + + monitorMuteMixer = this.props.inputGroupMixers.muteMixer + monitorMuteMixerId = monitorMuteMixer?.id + monitorVolumeLeft = this.props.inputGroupMixers.mixer?.volume_left + monitorMuteClasses = classNames({ + 'track-icon-mute': true + 'enabled' : !monitorMuteMixer?.mute + 'muted' : monitorMuteMixer?.mute + }) + + chatMuteMixer = this.props.chatGroupMixers.muteMixer + chatMuteMixerId = chatMuteMixer?.id + chatVolumeLeft = this.props.chatGroupMixers.mixer?.volume_left + chatMuteClasses = classNames({ + 'track-icon-mute': true + 'enabled' : !chatMuteMixer?.mute + 'muted' : chatMuteMixer?.mute + }) + + `
+
+

Music

+
+
+ +
+
+ +
+
+
Volume
+
{monitorVolumeLeft}dB
+
+ +
+ + + +
+ +
+

Use this slider to control the volume of all the music in the session in your headphones or speakers.

+

This will not affect the volume for other musicians in the session.

+

To adjust master levels for all musicians for recordings and broadcasts, use Mixer button in the toolbar.

+
+
+ +
+

Chat

+
+
+ +
+
+ +
+
+
Volume
+
{chatVolumeLeft}dB
+
+ +
+ + + +
+ +
+

Use this slider to control the volume of all the voice chat in the session in your headphones or speakers.

+

This will not affect the volume for other musicians in the session.

+
+
+
` + + componentDidMount: () -> + $root = jQuery(this.getDOMNode()) + + # initialize icheck + $checkbox = $root.find('input') + context.JK.checkbox($checkbox) + $checkbox.on('ifChanged', this.handleMuteCheckbox); + + #if this.props.mixers.muteMixer.mute + # $checkbox.iCheck('check').attr('checked', true) + #else + # $checkbox.iCheck('uncheck').attr('checked', false) + + componentWillUpdate: (nextProps, nextState) -> + $root = jQuery(this.getDOMNode()) + + # re-initialize icheck + $checkbox = $root.find('input') + + #if nextState.mixers.muteMixer?.mute + # $checkbox.iCheck('check').attr('checked', true) + #else + # $checkbox.iCheck('uncheck').attr('checked', false) +}) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/SessionSettingsBtn.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionSettingsBtn.js.jsx.coffee index 604a9194c..228d17f31 100644 --- a/web/app/assets/javascripts/react-components/SessionSettingsBtn.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SessionSettingsBtn.js.jsx.coffee @@ -2,9 +2,19 @@ context = window @SessionSettingsBtn = React.createClass({ + mixins: [Reflux.listenTo(@AppStore,"onAppInit")] + + openSettings: (e) -> + e.preventDefault() + + @app.layout.showDialog('session-settings') + render: () -> - ` + ` SETTINGS ` + + onAppInit: (app) -> + @app = app }) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/SessionShareBtn.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionShareBtn.js.jsx.coffee index 7572ec67b..aac4906a0 100644 --- a/web/app/assets/javascripts/react-components/SessionShareBtn.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SessionShareBtn.js.jsx.coffee @@ -2,9 +2,19 @@ context = window @SessionShareBtn = React.createClass({ + mixins: [Reflux.listenTo(@AppStore,"onAppInit")] + + onShare: (e) -> + e.preventDefault() + + @app.layout.showDialog('share-dialog') + render: () -> - ` + ` SHARE ` + + onAppInit: (app) -> + @app = app }) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/SessionVideoBtn.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionVideoBtn.js.jsx.coffee index d30b7c42f..8cfd1a344 100644 --- a/web/app/assets/javascripts/react-components/SessionVideoBtn.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SessionVideoBtn.js.jsx.coffee @@ -1,9 +1,15 @@ context = window +SessionActions = @SessionActions @SessionVideoBtn = React.createClass({ + sessionWebCam: (e) -> + e.preventDefault(); + + SessionActions.toggleSessionVideo() + render: () -> - ` + ` VIDEO ` diff --git a/web/app/assets/javascripts/react-components/SessionVolumeSettingsBtn.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionVolumeSettingsBtn.js.jsx.coffee index 7afe17866..0a9ee0b9b 100644 --- a/web/app/assets/javascripts/react-components/SessionVolumeSettingsBtn.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SessionVolumeSettingsBtn.js.jsx.coffee @@ -2,9 +2,26 @@ context = window @SessionVolumeSettingsBtn = React.createClass({ + mixins: [Reflux.listenTo(@MixerStore,"onInputsChanged")] + + onInputsChanged: (sessionMixers) -> + this.setState(sessionMixers) + render: () -> ` VOLUME ` + + componentDidMount: () -> + $root = $(this.getDOMNode()) + + context.JK.interactReactBubble( + $root, + 'SessionSelfVolumeHover', + () => + {inputGroupMixers: this.state.mixers.getAudioInputChatGroupMixer(), chatGroupMixers: this.state.mixers.getChatGroupMixer()} + , + {width:470, positions:['right', 'bottom', 'left'], offsetParent:$root.closest('.screen')}) + }) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/actions/RecordingActions.js.coffee b/web/app/assets/javascripts/react-components/actions/RecordingActions.js.coffee new file mode 100644 index 000000000..546176110 --- /dev/null +++ b/web/app/assets/javascripts/react-components/actions/RecordingActions.js.coffee @@ -0,0 +1,13 @@ +context = window + +@RecordingActions = Reflux.createActions({ + initModel: {} + startRecording: {} + stopRecording: {} + startingRecording:{} + startedRecording: {} + stoppingRecording: {} + stoppedRecording: {} + abortedRecording: {} + +}) \ No newline at end of file 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 44ea905cf..50d071cf2 100644 --- a/web/app/assets/javascripts/react-components/actions/SessionActions.js.coffee +++ b/web/app/assets/javascripts/react-components/actions/SessionActions.js.coffee @@ -5,4 +5,7 @@ context = window leaveSession: {} mixersChanged: {} allowLeaveSession: {} + syncWithServer: {} + toggleSessionVideo : {} + audioResync: {} }) \ 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 a1352040c..07107298d 100644 --- a/web/app/assets/javascripts/react-components/helpers/MixerHelper.js.coffee +++ b/web/app/assets/javascripts/react-components/helpers/MixerHelper.js.coffee @@ -461,4 +461,28 @@ MIX_MODES = context.JK.MIX_MODES; context.JK.VuHelpers.updateVU2('vur', mixer, value) getTrackInfo: () -> - context.JK.TrackHelpers.getTrackInfo(context.jamClient, @masterMixers) \ No newline at end of file + context.JK.TrackHelpers.getTrackInfo(context.jamClient, @masterMixers) + + getGroupMixer: (groupId, mode) -> + mixers = @mixersForGroupId(groupId, MIX_MODES.PERSONAL) + + if mixers.length == 0 + logger.warn("could not find mixer with group ID: " + groupId + ', mode:' + mode) + return {} + else + mixer = mixers[0] + { + mixer: mixer, + muteMixer : mixer, + vuMixer: mixer, + oppositeMixer: mixer + } + + console.log("M MIXERS", @masterMixers) + console.log("P MIXERS", @personalMixers) + + getAudioInputChatGroupMixer: () -> + @getGroupMixer(ChannelGroupIds.AudioInputMusicGroup, MIX_MODES.PERSONAL) + + getChatGroupMixer: () -> + @getGroupMixer(ChannelGroupIds.AudioInputChatGroup, MIX_MODES.PERSONAL) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/helpers/SessionHelper.js.coffee b/web/app/assets/javascripts/react-components/helpers/SessionHelper.js.coffee index d61e3b71b..839d53b45 100644 --- a/web/app/assets/javascripts/react-components/helpers/SessionHelper.js.coffee +++ b/web/app/assets/javascripts/react-components/helpers/SessionHelper.js.coffee @@ -2,9 +2,10 @@ context = window @SessionHelper = class SessionHelper - constructor: (app, session) -> + constructor: (app, session, isRecording) -> @app = app @session = session + @isRecording = isRecording inSession: () -> @session? @@ -87,4 +88,4 @@ context = window found id: () -> - @session.id + @session.id \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/stores/RecordingStore.js.jsx.coffee b/web/app/assets/javascripts/react-components/stores/RecordingStore.js.jsx.coffee new file mode 100644 index 000000000..d4fb8e447 --- /dev/null +++ b/web/app/assets/javascripts/react-components/stores/RecordingStore.js.jsx.coffee @@ -0,0 +1,46 @@ +$ = jQuery +context = window +logger = context.JK.logger + +@RecordingStore = Reflux.createStore( + { + listenables: @RecordingActions + + init: -> + # Register with the app store to get @app + this.listenTo(context.AppStore, this.onAppInit) + + onAppInit: (app) -> + @app = app + + onInitModel: (recordingModel) -> + @recordingModel = recordingModel + this.trigger({isRecording: @recordingModel.isRecording()}) + + onStartRecording: () -> + @recordingModel.startRecording() + + onStopRecording: () -> + @recordingModel.stopRecording() + + onStartingRecording: (details) -> + details.cause = 'starting' + this.trigger(details) + + onStartedRecording: (details) -> + details.cause = 'started' + this.trigger(details) + + onStoppingRecording: (details) -> + details.cause = 'stopping' + this.trigger(details) + + onStoppedRecording: (details) -> + details.cause = 'stopped' + this.trigger(details) + + onAbortedRecording: (details) -> + details.cause = 'aborted' + this.trigger(details) + } +) 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 701e90c56..6a9ed99df 100644 --- a/web/app/assets/javascripts/react-components/stores/SessionStore.js.coffee +++ b/web/app/assets/javascripts/react-components/stores/SessionStore.js.coffee @@ -7,6 +7,7 @@ MIX_MODES = context.JK.MIX_MODES SessionActions = @SessionActions +RecordingActions = @RecordingActions @SessionStore = Reflux.createStore( { @@ -26,19 +27,43 @@ SessionActions = @SessionActions sessionPageEnterDeferred: null gearUtils: null sessionUtils: null - joinDeffered: null + joinDeferred: null recordingModel: null currentTrackChanges: 0 + isRecording: false previousAllTracks: {userTracks: [], backingTracks: [], metronomeTracks: []} + webcamViewer: null init: -> # Register with the app store to get @app this.listenTo(context.AppStore, this.onAppInit) + this.listenTo(context.RecordingStore, this.onRecordingChanged) + + if gon.global.video_available && gon.global.video_available!="none" + @webcamViewer.init() + @webcamViewer.setVideoOff() onAppInit: (@app) -> @gearUtils = context.JK.GearUtilsInstance @sessionUtils = context.JK.SessionUtils - @recordingModel = new context.JK.RecordingModel(@app, this, rest, context.jamClient); + @recordingModel = new context.JK.RecordingModel(@app, rest, context.jamClient); + RecordingActions.initModel(@recordingModel) + + onToggleSessionVideo: () -> + logger.debug("toggle session video") + @webcamViewer.toggleWebcam() if @webcamViewer? + + onAudioResync: () -> + logger.debug("audio resyncing") + response = context.jamClient.SessionAudioResync() + if response? + @app.notify({ + "title": "Error", + "text": response, + "icon_url": "/assets/content/icon_alert_big.png"}) + + onSyncWithServer: () -> + @refreshCurrentSession(true) onWatchedInputs: (inputTracks) -> @@ -103,8 +128,176 @@ SessionActions = @SessionActions else if text == 'Local Peer Stream Mixer Mode' MixerActions.mixerModeChanged(MIX_MODES.PERSONAL) + onRecordingChanged: (details) -> + logger.debug("SessionStore.onRecordingChanged: " + details.cause) + @isRecording = details.isRecording + + switch details.cause + when 'started' + + if details.reason + reason = details.reason; + detail = details.detail; + title = "Could Not Start Recording"; + + switch reason + when 'client-no-response' + @notifyWithUserInfo(title, 'did not respond to the start signal.', detail) + when 'empty-recording-id' + @app.notifyAlert(title, "No recording ID specified.") + when 'missing-client' + @notifyWithUserInfo(title, 'could not be signalled to start recording.', detail) + when 'already-recording' + @app.notifyAlert(title, 'Already recording. If this appears incorrect, try restarting JamKazam.') + when 'recording-engine-unspecified' + @notifyWithUserInfo(title, 'had a problem writing recording data to disk.', detail) + when 'recording-engine-create-directory' + @notifyWithUserInfo(title, 'had a problem creating a recording folder.', detail) + when 'recording-engine-create-file' + @notifyWithUserInfo(title, 'had a problem creating a recording file.', detail) + when 'recording-engine-sample-rate' + @notifyWithUserInfo(title, 'had a problem recording at the specified sample rate.', detail) + when 'rest' + jqXHR = detail[0]; + @app.notifyServerError(jqXHR); + else + @notifyWithUserInfo(title, 'Error Reason: ' + reason) + else + @displayWhoCreatedRecording(details.clientId) + + when 'stopped' + if @selfOpenedJamTracks() + timeline = context.jamClient.GetJamTrackTimeline(); + + rest.addRecordingTimeline(details.recordingId, timeline) + .fail(()=> + @app.notify({ + title: "Unable to Add JamTrack Volume Data", + text: "The volume of the JamTrack will not be correct in the recorded mix." + }, null, true) + ) + + if details.reason + logger.warn("Recording Discarded: ", details) + reason = details.reason + detail = details.detail + title = "Recording Discarded" + + switch reason + when 'client-no-response' + @notifyWithUserInfo(title, 'did not respond to the stop signal.', detail) + when 'missing-client' + @notifyWithUserInfo(title, 'could not be signalled to stop recording.', detail) + when 'empty-recording-id' + @app.notifyAlert(title, "No recording ID specified.") + when 'wrong-recording-id' + @app.notifyAlert(title, "Wrong recording ID specified.") + when 'not-recording' + @app.notifyAlert(title, "Not currently recording.") + when 'already-stopping' + @app.notifyAlert(title, "Already stopping the current recording.") + when 'start-before-stop' + @notifyWithUserInfo(title, 'asked that we start a new recording; cancelling the current one.', detail) + else + @app.notifyAlert(title, "Error reason: " + reason) + else + @promptUserToSave(details.recordingId, timeline); + + when 'abortedRecording' + reason = details.reason + detail = details.detail + + title = "Recording Cancelled" + + switch reason + when 'client-no-response' + @notifyWithUserInfo(title, 'did not respond to the start signal.', detail) + when 'missing-client' + @notifyWithUserInfo(title, 'could not be signalled to start recording.', detail) + when 'populate-recording-info' + @notifyWithUserInfo(title, 'could not synchronize with the server.', detail) + when 'recording-engine-unspecified' + @notifyWithUserInfo(title, 'had a problem writing recording data to disk.', detail) + when 'recording-engine-create-directory' + @notifyWithUserInfo(title, 'had a problem creating a recording folder.', detail) + when 'recording-engine-create-file' + @notifyWithUserInfo(title, 'had a problem creating a recording file.', detail) + when 'recording-engine-sample-rate' + @notifyWithUserInfo(title, 'had a problem recording at the specified sample rate.', detail) + else + @app.notifyAlert(title, "Error reason: " + reason) + + this.trigger(new context.SessionHelper(@app, @currentSession, @isRecording)) + + notifyWithUserInfo: (title , text, clientId) -> + @findUserBy({clientId: clientId}) + .done((user)=> + @app.notify({ + "title": title, + "text": user.name + " " + text, + "icon_url": context.JK.resolveAvatarUrl(user.photo_url) + }); + ) + .fail(()=> + @app.notify({ + "title": title, + "text": 'Someone ' + text, + "icon_url": "/assets/content/icon_alert_big.png" + }) + ) + + findUserBy: (finder) -> + if finder.clientId + foundParticipant = null + for participant in @participants() + if participant.client_id == finder.clientId + foundParticipant = participant + break + + if foundParticipant + return $.Deferred().resolve(foundParticipant.user).promise(); + + # TODO: find it via some REST API if not found? + return $.Deferred().reject().promise(); + + displayWhoCreatedRecording: (clientId) -> + if @app.clientId != clientId # don't show to creator + @findUserBy({clientId: clientId}) + .done((user) => + @app.notify({ + "title": "Recording Started", + "text": user.name + " started a recording", + "icon_url": context.JK.resolveAvatarUrl(user.photo_url) + }) + ) + .fail(() => + @app.notify({ + "title": "Recording Started", + "text": "Oops! Can't determine who started this recording", + "icon_url": "/assets/content/icon_alert_big.png" + }) + ) + + promptUserToSave: (recordingId, timeline) -> + rest.getRecording( {id: recordingId} ) + .done((recording) => + if timeline + recording.timeline = timeline.global + + context.JK.recordingFinishedDialog.setRecording(recording) + @app.layout.showDialog('recordingFinished').one(EVENTS.DIALOG_CLOSED, (e, data) => + if data.result && data.result.keep + context.JK.prodBubble($('#recording-manager-viewer'), 'file-manager-poke', {}, {positions:['top', 'left', 'right', 'bottom'], offsetParent: $('#session-screen2').parent()}) + ) + ) + .fail(@app.ajaxError) + onJoinSession: (sessionId) -> + # poke ShareDialog + shareDialog = new JK.ShareDialog(@app, sessionId, "session"); + shareDialog.initialize(context.JK.FacebookHelperInstance); + # initialize webcamViewer if gon.global.video_available && gon.global.video_available != "none" @webcamViewer.beforeShow() @@ -215,7 +408,7 @@ SessionActions = @SessionActions joinSession: () -> context.jamClient.SessionRegisterCallback("JK.HandleBridgeCallback2"); - #context.jamClient.RegisterRecordingCallbacks("JK.HandleRecordingStartResult", "JK.HandleRecordingStopResult", "JK.HandleRecordingStarted", "JK.HandleRecordingStopped", "JK.HandleRecordingAborted"); + context.jamClient.RegisterRecordingCallbacks("JK.HandleRecordingStartResult", "JK.HandleRecordingStopResult", "JK.HandleRecordingStarted", "JK.HandleRecordingStopped", "JK.HandleRecordingAborted"); context.jamClient.SessionSetConnectionStatusRefreshRate(1000); #context.JK.HelpBubbleHelper.jamtrackGuideSession($screen.find('li.open-a-jamtrack'), $screen) @@ -233,10 +426,11 @@ SessionActions = @SessionActions audio_latency: context.jamClient.FTUEGetExpectedLatency().latency }) .done((response) => + unless @inSession() # the user has left the session before they got joined. We need to issue a leave again to the server to make sure they are out logger.debug("user left before fully joined to session. telling server again that they have left") - @leaveSessionRest(response.id) + @leaveSessionRest(@currentSessionId) return logger.debug("calling jamClient.JoinSession"); @@ -248,9 +442,9 @@ SessionActions = @SessionActions else context.JK.GA.trackSessionMusicians(context.JK.GA.SessionCreationTypes.join); - @recordingModel.reset(response.id); + @recordingModel.reset(@currentSessionId); - context.jamClient.JoinSession({sessionID: response.id}); + context.jamClient.JoinSession({sessionID: @currentSessionId}); @refreshCurrentSession(true); @@ -474,7 +668,7 @@ SessionActions = @SessionActions console.log("SESSION CHANGED", sessionData) - this.trigger(new context.SessionHelper(@app, @currentSession)) + this.trigger(new context.SessionHelper(@app, @currentSession, @isRecording)) ensureConnected: () -> unless context.JK.JamServer.connected @@ -546,7 +740,10 @@ SessionActions = @SessionActions @sessionEnded() - this.trigger(new context.SessionHelper(@app, @currentSession)) + this.trigger(new context.SessionHelper(@app, @currentSession, @isRecording)) + + selfOpenedJamTracks: () -> + @currentSession && (@currentSession.jam_track_initiator_id == context.JK.currentUserId) sessionEnded: () -> # cleanup @@ -567,12 +764,13 @@ SessionActions = @SessionActions @userTracks = null; @startTime = null; - if @joinDeffered?.state() == 'resolved' + if @joinDeferred?.state() == 'resolved' $(document).trigger(EVENTS.SESSION_ENDED, {session: {id: @currentSessionId}}) @currentTrackChanges = 0 @currentSession = null @joinDeferred = null + @isRecording = false @currentSessionId = null @currentParticipants = {} @previousAllTracks = {userTracks: [], backingTracks: [], metronomeTracks: []} diff --git a/web/app/assets/javascripts/recordingModel.js b/web/app/assets/javascripts/recordingModel.js index ebfff5ca8..669321c41 100644 --- a/web/app/assets/javascripts/recordingModel.js +++ b/web/app/assets/javascripts/recordingModel.js @@ -61,6 +61,7 @@ } + function groupTracksToClient(recording) { // group N tracks to the same client Id var groupedTracks = {}; @@ -85,6 +86,8 @@ currentlyRecording = true; stoppingRecording = false; + context.RecordingActions.startingRecording({isRecording: false}) + currentRecording = rest.startRecording({"music_session_id": sessionId}) .done(function(recording) { currentRecordingId = recording.id; @@ -95,8 +98,10 @@ jamClient.StartRecording(recording["id"], groupedTracks); }) .fail(function(jqXHR) { - $self.triggerHandler('startedRecording', { clientId: app.clientId, reason: 'rest', detail: arguments }); + var details = { clientId: app.clientId, reason: 'rest', detail: arguments, isRecording: false } + $self.triggerHandler('startedRecording', details); currentlyRecording = false; + context.RecordingActions.startedRecording(details); }) @@ -117,6 +122,7 @@ waitingOnStopTimer = setTimeout(timeoutTransitionToStop, 5000); $self.triggerHandler('stoppingRecording', {reason: reason, detail: detail}); + context.RecordingActions.stoppingRecording({reason: reason, detail: detail, isRecording:true}) // this path assumes that the currentRecording info has, or can be, retrieved // failure for currentRecording is handled elsewhere @@ -146,7 +152,9 @@ else { logger.error("unable to stop recording %o", arguments); transitionToStopped(); - $self.triggerHandler('stoppedRecording', {'recordingId': recording.id, 'reason' : 'rest', 'details' : arguments}); + var details = {'recordingId': recording.id, 'reason' : 'rest', 'details' : arguments, isRecording: false} + $self.triggerHandler('stoppedRecording', details); + context.RecordingActions.stoppedRecording(details) } }); }); @@ -169,7 +177,9 @@ if(!waitingOnClientStop && !waitingOnServerStop) { transitionToStopped(); - $self.triggerHandler('stoppedRecording', {recordingId: recordingId, reason: errorReason, detail: errorDetail}); + var details = {recordingId: recordingId, reason: errorReason, detail: errorDetail, isRecording: false} + $self.triggerHandler('stoppedRecording', details) + context.RecordingActions.stoppedRecording(details) } } @@ -199,12 +209,16 @@ if(success) { - $self.triggerHandler('startedRecording', {clientId: app.clientId}) + var details = {clientId: app.clientId, isRecording:true} + $self.triggerHandler('startedRecording', details) + context.RecordingActions.startedRecording(details) } else { currentlyRecording = false; logger.error("unable to start the recording %o, %o", reason, detail); - $self.triggerHandler('startedRecording', { clientId: app.clientId, reason: reason, detail: detail}); + var details = { clientId: app.clientId, reason: reason, detail: detail, isRecording: false} + $self.triggerHandler('startedRecording', details); + context.RecordingActions.startedRecording(details) } } @@ -222,7 +236,9 @@ else { transitionToStopped(); logger.error("backend unable to stop the recording %o, %o", reason, detail); - $self.triggerHandler('stoppedRecording', {recordingId: recordingId, reason: reason, detail : detail}); + var details = {recordingId: recordingId, reason: reason, detail : detail, isRecording: false} + $self.triggerHandler('stoppedRecording', details); + context.RecordingActions.stoppedRecording(details) } } @@ -243,9 +259,14 @@ currentOrLastRecordingId = recording.id; }); - $self.triggerHandler('startingRecording', {recordingId: recordingId}); + var details = {recordingId: recordingId, isRecording: false} + $self.triggerHandler('startingRecording', details); + this.RecordingActions.startingRecording(details) currentlyRecording = true; - $self.triggerHandler('startedRecording', {clientId: clientId, recordingId: recordingId}); + + details = {clientId: clientId, recordingId: recordingId, isRecording: true} + $self.triggerHandler('startedRecording', details); + this.RecordingActions.startedRecording(details) } function handleRecordingStopped(recordingId, result) { @@ -254,7 +275,10 @@ var detail = result.detail; - $self.triggerHandler('stoppingRecording', {recordingId: recordingId, reason: reason, detail: detail }); + var details = {recordingId: recordingId, reason: reason, detail: detail, isRecording: true } + $self.triggerHandler('stoppingRecording', details); + context.RecordingActions.stoppingRecording(details) + // the backend says the recording must be stopped. // tell the server to stop it too rest.stopRecording({ @@ -266,18 +290,26 @@ .fail(function(jqXHR, textStatus, errorMessage) { if(jqXHR.status == 422) { logger.debug("recording already stopped %o", arguments); - $self.triggerHandler('stoppedRecording', {recordingId: recordingId, reason: reason, detail: detail}); + var details = {recordingId: recordingId, reason: reason, detail: detail, isRecording: false} + $self.triggerHandler('stoppedRecording', details); + context.RecordingActions.stoppedRecording(details) } else if(jqXHR.status == 404) { logger.debug("recording is already deleted %o", arguments); - $self.triggerHandler('stoppedRecording', {recordingId: recordingId, reason: reason, detail: detail}); + var details = {recordingId: recordingId, reason: reason, detail: detail, isRecording: false} + $self.triggerHandler('stoppedRecording', details); + context.RecordingActions.stoppedRecording(details) } else { - $self.triggerHandler('stoppedRecording', {recordingId: recordingId, reason: textStatus, detail: errorMessage}); + var details = {recordingId: recordingId, reason: textStatus, detail: errorMessage, isRecording: false} + $self.triggerHandler('stoppedRecording', details); + context.RecordingActions.stoppedRecording(details) } }) .done(function() { - $self.triggerHandler('stoppedRecording', {recordingId: recordingId, reason: reason, detail: detail}); + var details = {recordingId: recordingId, reason: reason, detail: detail, isRecording: false} + $self.triggerHandler('stoppedRecording', details); + context.RecordingActions.stoppedRecording(details) }) } @@ -288,7 +320,9 @@ stoppingRecording = false; - $self.triggerHandler('abortedRecording', {recordingId: recordingId, reason: reason, detail: detail }); + var details = {recordingId: recordingId, reason: reason, detail: detail, isRecording: false } + $self.triggerHandler('abortedRecording', details); + context.RecordingActions.abortedRecording(details) // the backend says the recording must be stopped. // tell the server to stop it too rest.stopRecording({ diff --git a/web/app/assets/javascripts/webcam_viewer.js.coffee b/web/app/assets/javascripts/webcam_viewer.js.coffee index ed2b1d772..931fef139 100644 --- a/web/app/assets/javascripts/webcam_viewer.js.coffee +++ b/web/app/assets/javascripts/webcam_viewer.js.coffee @@ -14,6 +14,10 @@ context.JK.WebcamViewer = class WebcamViewer @resolution=null init: (root) => + + # the session usage of webcamViewer does not actually pass in anything + root = $() unless root? + @root = root @toggleBtn = @root.find(".webcam-test-btn") @webcamSelect = @root.find(".webcam-select-container select") diff --git a/web/app/assets/stylesheets/client/content.css.scss b/web/app/assets/stylesheets/client/content.css.scss index 50d7e4876..852622cf8 100644 --- a/web/app/assets/stylesheets/client/content.css.scss +++ b/web/app/assets/stylesheets/client/content.css.scss @@ -217,7 +217,7 @@ .content-wrapper, .dialog, .dialog-inner, .ftue-inner { - select, textarea, input[type=text], input[type=password], div.friendbox { + select, textarea, input[type=text], input[type=password], div.friendbox, div.inputbox { background-color:#c5c5c5; border:none; -webkit-box-shadow: inset 2px 2px 3px 0px #888; 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 f8de208a1..237d06fbf 100644 --- a/web/app/assets/stylesheets/client/react-components/SessionScreen.css.scss +++ b/web/app/assets/stylesheets/client/react-components/SessionScreen.css.scss @@ -106,7 +106,7 @@ } .react-holder { - &.SessionTrackVolumeHover { + &.SessionTrackVolumeHover, &.SessionSelfVolumeHover { height:331px; width:235px; @@ -182,6 +182,72 @@ left:34px } } + + #self-volume-hover { + h3 { + font-size:16px; + font-weight:bold; + margin-bottom:10px; + } + + .monitor-mixer { + float:left; + width:235px; + @include border_box_sizing; + padding: 15px 0 15px 0; + + h3 { + margin-left:36px; + } + + .textual-help { + border-right:1px solid $ColorTextTypical; + float:right; + padding-right:25px !important; + + p:nth-child(1) { + margin-top:0; + } + } + } + + .chat-mixer { + float:left; + width:235px; + @include border-box-sizing; + padding: 15px 0 15px 0; + + h3 { + margin-left:41px; + } + } + + .mixer-holder { + + .session-track { + margin-top:0; + } + + .textual-help { + margin-top:0; + padding-right:10px; + + p:nth-child(1) { + margin-top:0; + } + } + } + + } + &.SessionTrackVolumeHover { + + } + + &.SessionSelfVolumeHover { + width:470px ! important; + height:360px ! important; + } + &.SessionTrackPanHover { width:331px; height:197px; diff --git a/web/app/assets/stylesheets/client/react-components/SessionSelfVolumeHover.css.scss b/web/app/assets/stylesheets/client/react-components/SessionSelfVolumeHover.css.scss new file mode 100644 index 000000000..1d6c5eb35 --- /dev/null +++ b/web/app/assets/stylesheets/client/react-components/SessionSelfVolumeHover.css.scss @@ -0,0 +1,2 @@ +@import "client/common"; + diff --git a/web/app/assets/stylesheets/dialogs/sessionSettingsDialog.css.scss b/web/app/assets/stylesheets/dialogs/sessionSettingsDialog.css.scss new file mode 100644 index 000000000..13395483d --- /dev/null +++ b/web/app/assets/stylesheets/dialogs/sessionSettingsDialog.css.scss @@ -0,0 +1,67 @@ +@import "client/common"; + +#session-settings { + + width:500px; + + .dropdown-wrapper { + width:100%; + } + + input, textarea { + width:100%; + @include border_box_sizing; + } + + .btn-select-files { + position:absolute; + width: 90px; + top: 2px; + } + + .notation-selector { + position:absolute; + right:0 + } + + .notation-files { + position:relative; + } + + .inputbox { + height:60px; + padding:5px; + } + + .notation-file { + + } + + .input-holder { + width:350px; + } + + .notation-entry { + div { + display:block; + width:90%; + overflow:hidden; + white-space: nowrap; + float: left; + color:black; + } + a { + float:right; + color:black; + } + } + + #session-settings-dialog-submit { + margin-right:1px; + } + + .spinner-small { + position: absolute; + top: 20px; + } +} \ No newline at end of file diff --git a/web/app/assets/stylesheets/dialogs/shareDialog.css.scss b/web/app/assets/stylesheets/dialogs/shareDialog.css.scss index 60bff5e51..541d0489a 100644 --- a/web/app/assets/stylesheets/dialogs/shareDialog.css.scss +++ b/web/app/assets/stylesheets/dialogs/shareDialog.css.scss @@ -2,6 +2,10 @@ width:500px; + .dialog-inner { + padding-bottom:6px; + } + .button-orange { margin:0 2px 0 0; } @@ -272,6 +276,9 @@ } .share-link { + + height:75px; + h3 { margin-bottom:20px; } @@ -292,4 +299,9 @@ text-align: center; margin: 125px auto; } + + .actions { + text-align:center; + margin-top:20px; + } } \ No newline at end of file diff --git a/web/app/assets/stylesheets/minimal/minimal.css.scss b/web/app/assets/stylesheets/minimal/minimal.css.scss index f159f3f0d..7ea6d2c7c 100644 --- a/web/app/assets/stylesheets/minimal/minimal.css.scss +++ b/web/app/assets/stylesheets/minimal/minimal.css.scss @@ -5,6 +5,8 @@ *= require client/screen_common *= require client/content *= require client/ftue +*= require icheck/minimal/minimal +*= require minimal/popup *= require minimal/recording_controls *= require minimal/minimal_main */ \ No newline at end of file diff --git a/web/app/assets/stylesheets/minimal/minimal_main.css.scss b/web/app/assets/stylesheets/minimal/minimal_main.css.scss index 50feaf50f..78ca52f36 100644 --- a/web/app/assets/stylesheets/minimal/minimal_main.css.scss +++ b/web/app/assets/stylesheets/minimal/minimal_main.css.scss @@ -8,9 +8,4 @@ body { overflow: visible !important; height:100%; margin:0 !important; -} - -.wrapper { - width:1280px; - margin:0 auto; } \ No newline at end of file diff --git a/web/app/assets/stylesheets/minimal/popup.css.scss b/web/app/assets/stylesheets/minimal/popup.css.scss new file mode 100644 index 000000000..5db8190c4 --- /dev/null +++ b/web/app/assets/stylesheets/minimal/popup.css.scss @@ -0,0 +1,6 @@ +body.popup { + width:100%; + height:100%; + background-color:#404040; + overflow: hidden !important; +} \ No newline at end of file diff --git a/web/app/assets/stylesheets/minimal/recording_controls.css.scss b/web/app/assets/stylesheets/minimal/recording_controls.css.scss index 1ba110ca7..b2817b8ee 100644 --- a/web/app/assets/stylesheets/minimal/recording_controls.css.scss +++ b/web/app/assets/stylesheets/minimal/recording_controls.css.scss @@ -1,6 +1,95 @@ -body.recording-controls { - width:100%; - height:100%; - background-color:#404040; - overflow: hidden !important; +@import "client/common"; + +body.recording-start-stop { + + position:relative; + color: $ColorTextTypical; + + #minimal-container { + padding-bottom:20px; + } + + .recording-start-stop { + padding-left:44px; + } + + .control-holder { + width:100%; + margin: 1em 0; + } + + .helper { + display: inline-block; + height: 100%; + vertical-align: middle; + } + + .control { + width:231px; + height:34px; + @include border_box_sizing; + margin-top:15px; + padding:3px; + background-color:#242323; + text-align:center; + font-size:13px; + border-radius:5px; + vertical-align:middle; + color:#ccc; + } + + + .control img { + vertical-align:middle; + margin-right:5px; + } + + .control span { + vertical-align:middle; + } + + .iradio_minimal { + float:left; + margin-right:5px; + } + + label { + padding-top:2px; + } + + .field { + height:18px; + &:nth-child(1) { + + } + &:nth-child(2) { + margin-top:9px; + } + } + + .note-show-hide { + font-size:11px; + } + + h5 { + text-decoration:underline; + margin-bottom:5px; + } + + .important-note { + margin-top:30px; + line-height:150%; + font-size:12px; + width:260px; + } + + a.note-show-hide { + margin-top:5px; + text-decoration:underline; + font-size:11px; + } + + .currently-recording { + background-color: $ColorRecordingBackground; + } } \ No newline at end of file diff --git a/web/app/controllers/api_music_notations_controller.rb b/web/app/controllers/api_music_notations_controller.rb index 25997b24b..90e64435c 100644 --- a/web/app/controllers/api_music_notations_controller.rb +++ b/web/app/controllers/api_music_notations_controller.rb @@ -25,14 +25,28 @@ class ApiMusicNotationsController < ApiController def download @music_notation = MusicNotation.find(params[:id]) - unless @music_notation.music_session.nil? || @music_notation.music_session.can_join?(current_user, true) + unless @music_notation.music_session.can_join?(current_user, true) render :text => "Permission denied", status:403 return end + if '_blank'==params[:target] redirect_to @music_notation.sign_url else render :text => @music_notation.sign_url end end + + def delete + @music_notation = MusicNotation.find(params[:id]) + + unless @music_notation.music_session.can_join?(current_user, true) + render :text => "Permission denied", status:403 + return + end + + @music_notation.destroy + + render :json => {}, status: 204 + end end diff --git a/web/app/views/api_music_notations/create.rabl b/web/app/views/api_music_notations/create.rabl index 1df3bde7d..1e9d0b313 100644 --- a/web/app/views/api_music_notations/create.rabl +++ b/web/app/views/api_music_notations/create.rabl @@ -1,3 +1,7 @@ object @music_notations -attribute :id, :file_name \ No newline at end of file +attribute :id, :file_name + +node do |music_notation| + { file_url: "/api/music_notations/#{music_notation.id}" } +end \ No newline at end of file diff --git a/web/app/views/clients/_sessionSettings.html.haml b/web/app/views/clients/_sessionSettings.html.haml index 3a115260a..abd85514e 100644 --- a/web/app/views/clients/_sessionSettings.html.haml +++ b/web/app/views/clients/_sessionSettings.html.haml @@ -1,5 +1,5 @@ -.dialog{:layout => 'dialog', 'layout-id' => 'session-settings', :id => 'session-settings', :width => '600px', :height => '800px'} +.dialog{:layout => 'dialog', 'layout-id' => 'session-settings', :id => 'session-settings'} .content-head = image_tag "content/icon_settings_lg.png", :width => 18, :height => 18, :class => "content-icon" @@ -10,7 +10,7 @@ %input{:type => "hidden", :name => "id", :id => "session-settings-id"} - .left.mr35 + .left.input-holder .left.ib Genre: @@ -53,21 +53,19 @@ .clearall.left.w25.ib.mb10 Notation Files: - .right.w75.ib.mb10 - .w40.left - .selected-files-section - %div{:id => "settings-selected-filenames"} - .right.ib.mb10 - %a.button-orange.btn-select-files SELECT FILES... + .right.w75.ib.mb10.notation-files + .notation-selector + %a.button-orange.btn-select-files ADD FILES... %input.hidden{:type => "file", :id => "session-select-files", :value => "Select Files...", :accept => ".pdf, .png, .jpg, .jpeg, .gif, .xml, .mxl, .txt"} - .spinner-small.upload-spinner - - .clearall.right.mt10 - %a.button-orange{:href => 'https://jamkazam.desk.com', :rel => 'external'} HELP + .spinner-small.hidden + + .inputbox + + + .clearall.right.mt20 %a.button-grey{'layout-action' => "close"} CANCEL %a.button-orange{:id => "session-settings-dialog-submit"} UPDATE SETTINGS .clearall - %br/ %br{:clear => 'all'}/ \ No newline at end of file diff --git a/web/app/views/clients/index.html.erb b/web/app/views/clients/index.html.erb index 1b6ed5a96..06670850e 100644 --- a/web/app/views/clients/index.html.erb +++ b/web/app/views/clients/index.html.erb @@ -153,6 +153,7 @@ var recordingFinishedDialog = new JK.RecordingFinishedDialog(JK.app); recordingFinishedDialog.initialize(); + JK.recordingFinishedDialog = recordingFinishedDialog var localRecordingsDialog = new JK.LocalRecordingsDialog(JK.app); localRecordingsDialog.initialize(); diff --git a/web/app/views/dialogs/_shareDialog.html.erb b/web/app/views/dialogs/_shareDialog.html.erb index 828f07193..589e6b2d3 100644 --- a/web/app/views/dialogs/_shareDialog.html.erb +++ b/web/app/views/dialogs/_shareDialog.html.erb @@ -3,9 +3,6 @@

share this

- -
- - diff --git a/web/app/views/popups/recording_controls.html.slim b/web/app/views/popups/recording_controls.html.slim index 531396f40..5df94974d 100644 --- a/web/app/views/popups/recording_controls.html.slim +++ b/web/app/views/popups/recording_controls.html.slim @@ -1,11 +1,2 @@ -- provide(:page_name, 'recording-controls') -.recording-controls - | IM HERE - -javascript: - window.bling = function () { - alert("bling!!") - window.opener.callMe() - } - - console.log("window.opener", window.opener) \ No newline at end of file +- provide(:page_name, 'recording-start-stop popup') += react_component 'PopupRecordingStartStop', {} \ No newline at end of file diff --git a/web/config/routes.rb b/web/config/routes.rb index 72437c717..b51157a0c 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -136,7 +136,7 @@ SampleApp::Application.routes.draw do match '/extras/settings', to: 'extras#settings' scope '/popups' do - match '/session/:id/recording-controls', to: 'popups#recording_controls' + match '/recording-controls', to: 'popups#recording_controls' end scope '/corp' do @@ -219,6 +219,8 @@ SampleApp::Application.routes.draw do # Music notations match '/music_notations' => 'api_music_notations#create', :via => :post match '/music_notations/:id' => 'api_music_notations#download', :via => :get, :as => :download_music_notation + match '/music_notations/:id' => 'api_music_notations#delete', :via => :delete, :as => :delete_music_notation + # Backing track_show match '/backing_tracks' => 'api_backing_tracks#index', :via => :get, :as => 'api_backing_tracks_list'